I allow users to login using Facebook and Google using OmniAuth, also I allow them to create accounts using their emails. Everything is working fine except for the part in which the users are able to update their account information.
I added a field called username, in which the sign_up and sign_in handles Ok, for the update I don't want to require the user enter password if he is signed in using facebook but I keep getting ForbiddenAttributesError.
here is the code I wrote.
class Users::RegistrationsController < Devise::RegistrationsController
before_filter :configure_permitted_parameters, if: :devise_controller?
def update
#provider = Provider.find_by_user_id(current_user.id)
#user = User.find(current_user.id)
email_changed = current_user.email != params[resource_name][:email]
is_omniauth_account = #provider.blank?
successfully_updated = if is_omniauth_account
resource.update_with_password(params[resource_name])
else
resource.update_without_password(params[resource_name])
end
if successfully_updated
# Sign in the user bypassing validation in case his password changed
sign_in #user, :bypass => true
redirect_to root_path
else
clean_up_passwords(resource)
render_with_scope :edit
end
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :email, :password, :password_confirmation) }
devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:username, :email, :password, :password_confirmation, :current_password) }
devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email, :password) }
end
def resource_params
params.require(:user).permit(:username, :email, :password, :password_confirmation, :current_password)
end
private :resource_params
end
It looks like you have the method defined to handle the strong parameters, but you aren't using it in your update action.
Give this a try:
successfully_updated = if is_omniauth_account
resource.update_with_password(resource_params)
else
resource.update_without_password(resource_params)
end
This solution worked out, but I still have issue I am not sure if it is a good practice to remove a parameter at run time
class Users::RegistrationsController < Devise::RegistrationsController
before_filter :configure_permitted_parameters, if: :devise_controller?
def update
#provider = Provider.find_by_user_id(current_user.id)
#user = User.find(current_user.id)
email_changed = current_user.email != resource_params[:email]
is_omniauth_account = !#provider.blank?
successfully_updated = if is_omniauth_account
devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:username, :email, :password, :password_confirmation) }
resource.update_without_password(devise_parameter_sanitizer.for(:account_update))
else
resource.update_with_password(devise_parameter_sanitizer.for(:account_update))
end
if successfully_updated
# Sign in the user bypassing validation in case his password changed
sign_in #user, :bypass => true
redirect_to root_path
else
clean_up_passwords(resource)
render :edit
end
end
def create
super
UserMailer.welcome(#user).deliver
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :email, :password, :password_confirmation) }
devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:username, :email, :password, :password_confirmation, :current_password) }
devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email, :password) }
end
end
Related
I'm trying to use my Users controller to notify the user when their email has already been used in a registration, but even when the email already exists, I still get the error "Plase validate your input and try again," rather than "You've already registered! Thanks for being enthusiastic!" Is using the controller not the create way of achieving this behavior?
In the rails console (assuming "foo#bar.com" is in the database"), when I use user = User.new(name:"Example", email:"foo#bar.com") then User.find_by_email(user.email) it does return the proper User entry, so I'm not sure if I'm on the right track and just executing it incorrectly or what. Any ideas?
users_controller.rb:
class UsersController < ApplicationController
def new
#user = User.new(params[:user])
end
def create
#user = User.new(params[:user])
if #user.save
flash[:success] = "Thanks for supporting cofind! We'll be in touch!"
redirect_to root_path
UserMailer.welcome_email(#user).deliver
else
if #user.email == User.find_by_email(#user.email)
flash[:error] = "You've already registered! Thanks for being enthusiastic!"
redirect_to root_path
else
flash[:error] = "Plase validate your input and try again."
redirect_to signup_path
end
end
end
end
user.rb:
class User < ActiveRecord::Base
attr_accessible :email, :name
before_save { |user| user.email = email.downcase }
validates :name, presence: true
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
end
this line
if #user.email == User.find_by_email(#user.email)
checks the user's email (a string) against a user record (an ActiveRecord object) which will always be false. You should change that to
if User.where(email: #user.email).exists?
Rails 3.0
Following these instructions:
https://github.com/plataformatec/devise/wiki/How-To%3a-Require-admin-to-activate-account-before-sign_in
I've generated a migration :approved (boolean) for my devise user.rb. Now I want to edit it with a checkbox from a different controller: unapproved_users_controller.rb.
When I load the form in the edit I get this error: undefined method `user_path'.
routes.rb, the resources for my new controller
resources :unapproved_users
app/models/user.rb, notice that :approved is attr_accessible.
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me, :approved
def active_for_authentication?
super && approved?
end
def inactive_message
if !approved?
:not_approved
else
super # Use whatever other message
end
end
def self.send_reset_password_instructions(attributes={})
recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
if !recoverable.approved?
recoverable.errors[:base] << I18n.t("devise.failure.not_approved")
elsif recoverable.persisted?
recoverable.send_reset_password_instructions
end
recoverable
end
end
app/controllers/unapproved_controllers.rb
class UnapprovedUsersController < ApplicationController
def index
if params[:approved] == "false"
#users = User.find_all_by_approved(false)
else
#users = User.all
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
#user.update_attributes(params[:user])
end
end
app/views/unapproved_users/index.html.haml
%h1 Users
= link_to "All Users", :action => "index"
|
= link_to "Users awaiting approval", :action => "index", :approved => "false"
%table
- #users.each do |user|
%tr
%td= user.email
%td= user.approved
%td= link_to "Edit", edit_unapproved_user_path(user)
app/views/unapproved_users/edit.html.haml
= render 'form'
app/views/unapproved_users/_form.html.haml
= form_for (#user) do |f|
-if #user.errors.any?
#error_explanation
%h2= "#{pluralize(#user.errors.count, "error")} prohibited this user from being saved:"
%ul
- #user.errors.full_messages.each do |msg|
%li= msg
.field
= f.label :approved, 'Approved?'
= f.check_box :approved
.actions
= f.submit 'Save'
You need to change the form_for.
It should be
= form_for(#user, :url => unapproved_user_path(#user)) do |f|
I'm learning SOLID and trying to introduce SRP into my rails app. I have the following user model with basic authentication:
class User < ActiveRecord::Base
attr_accessible :password, :password_confirmation
attr_accessor :password
before_save :encrypt_password
validates_confirmation_of :password
validates_presence_of :password, :on => :create
def self.authenticate(email, password)
user = find_by_email(email)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
def self.generate_random_password
return ActiveSupport::SecureRandom.hex(12)
end
end
I want to move all the authentication logic to a module like so:
module Authentication
attr_accessible :password, :password_confirmation
attr_accessor :password
before_save :encrypt_password
validates_confirmation_of :password
validates_presence_of :password, :on => :create
def self.authenticate(email, password)
user = find_by_email(email)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
def self.generate_random_password
return ActiveSupport::SecureRandom.hex(12)
end
end
And my user model would be like this:
class User < ActiveRecord::Base
include Authentication #SRP in action! :P
end
And now the errors begin:
undefined method `attr_accessible' for Authentication:Module
How would I fix this error? I am convinced this is the best start to introduce SRP to my Rails app.
Thanks
The attr_accessible method is called in the wrong scope. Take a look at Concerns to fix this:
http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
This would result in:
module Authentication
extend ActiveSupport::Concern
included do
attr_accessible :password, :password_confirmation
end
...
end
This will also take care of you class and instance method definitions.
NOTE: To be specific, this does not quite achieve SRP, since multiple responsibilities are still shared within the same class, even though they are separated into modules. Class composition through referencing or decorating would be a more strict solution , but I prefer the pragmatic approach of modules.
I'm just following Ruby on Rails 3 Tutorials (Mhartl) chapter-7 at the stage of 7.3.2 name and Gravatar.
Here I am facing a problem when I open on my browser it's says:
ActiveRecord::RecordNotFound in UsersController#show
Couldn't find User with id=1
Rails.root: C:/RubyOnRails/MyWorkPlace/sample_app_1
Application Trace | Framework Trace | Full Trace
app/controllers/users_controller.rb:5:in `show'
Request
Parameters:
{"id"=>"1"}
Show session dump
Show env dump
Response
Headers:
None
Also I pasted below User_controller.rb and user.rb
user.rb:
require 'digest'
class User < ActiveRecord::Base
attr_accessor :pasword
attr_accessible :login,
:username,
:email,
:password,
:password_confirmation,
:remember_me
email_regex = /\A[\w+\-.]+#[a-z\-.]+\.[a-z]+\z/i
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => { :case_sensitive => false }
validates :pasword, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
def self.authenticate(email, submitted_password)
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
private
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
users_controller.rb:
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#title = #user.name
end
def new
#title = "Sign up"
end
end
Are you sure you created any user with id=1 ?
To check, go to rails console and get the user with id 1. If there is no user, then create one.
At firest, I see you have attr_accessor :pasword
I think it should be :password
Ontopic:
There are some actions missing in the restful controller, so it wont be possible to create a user.
See http://guides.rubyonrails.org/getting_started.html#rest for more details on RESTful controllers.
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#title = #user.name
end
def new
#user = User.new #this creates a empty user object to be filled with signup data
#title = "Sign up"
end
def create
#user = User.new(params[:user]) #this creates a new user object with the data you entered before.
if #user.save #if the data is valid, save it
redirect_to user_path(#user) #and go to the #user show action
else
render :action => :new #edit the invalid user data
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
redirect_to user_url(#user)
else
render edit_user_url(#user)
end
end
def index
#users = User.all
end
def destroy
#user = User.find(params[:id]
#user.destroy
redirect_to :action => :index
end
end
edit: complete restful actions
I had the same problema. In my case, my 'redirect_to' on my detroy action was missin a 's' in 'posts_path'. It was post_path Noob, but worth i had checked up.
The reason you could not find the "user/1" is when you Added microposts to the sample data(db/seeds.rb) by typing
users = User.order(:created_at).take(6)
50.times do
content = Faker::Lorem.sentence(5)
users.each { |user| user.microposts.create!(content: content) }
end
You forgot the "END" of the previous code, so the full picture of db/seeds.rb is
User.create!(name: "Example User",
email: "example#railstutorial.org",
password: "foobar",
password_confirmation: "foobar",
admin: true,
activated: true,
activated_at: Time.zone.now)
99.times do |n|
name = Faker::Name.name
email = "example-#{n+1}#railstutorial.org"
password = "password"
User.create!(name: name,
email: email,
password: password,
password_confirmation: password,
activated: true,
activated_at: Time.zone.now)
end
users = User.order(:created_at).take(6)
50.times do
content = Faker::Lorem.sentence(5)
users.each { |user| user.microposts.create!(content: content) }
end
I have the following models: user, role, user_role (user_role is a join model)
I am trying to edit a user's roles using checkboxes on the user#edit page. Here's my attempt, I feel like I'm missing something significant, or taking the wrong approach.
user.rb
has_many :user_roles, dependent: :destroy
has_many :roles, through: :user_roles
attr_accessible :user_roles_attributes
accepts_nested_attributes_for :user_roles, reject_if: lambda { |a| a[:role_id] == 0 }, allow_destroy: true
def has_role?(role_sym)
roles.any? { |r| r.name.underscore.to_sym == role_sym.downcase }
end
def setup_roles!
Role.all.each { |role|
user_roles.build(user_id: id, role_id: role.id) unless has_role?(role.name.to_sym)
}
end
user_role.rb
belongs_to :user
belongs_to :role
delegate :name, to: :role
role.rb
has_many :user_roles
has_many :users, through: :user_role
users_controller.rb
def edit
#user = User.find(params[:id])
#user.setup_roles!
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
flash[:notice] = 'User was successfully updated.'
redirect_to edit_user_path(#user)
else
render :edit
end
end
users/edit.html.haml
= form_for #user do |f|
= f.fields_for(:user_roles) do |role_form|
= role_form.check_box :role_id, {}, role_form.object.role_id, 0
= role_form.hidden_field :user_id
= role_form.label :name, role_form.object.name
= f.submit 'Update'
Here is my solution. I received a lot of help from This Post at RubySource. The way the checkbox is setup, it will destroy a UserRole if "unchecked", and only create it when it is "checked" (why the '0', '1' is on that line.)
users_controller.rb
def edit
#user = User.find(params[:id])
#user.setup_roles!
end
user.rb
def has_role?(role_sym)
roles.any? { |r| r.name.underscore.to_sym == role_sym.downcase }
end
def setup_roles!
Role.all.each { |role|
user_roles.build(role: role) unless has_role?(role.name.to_sym)
}
end
users/edit.html.haml
= form_for #user do |f|
= f.fields_for :user_roles do |builder|
= builder.check_box :_destroy, { checked: builder.object.persisted? }, '0', '1'
= builder.label :_destroy, builder.object.role.name
= builder.hidden_field :role_id
= f.submit 'Update'