Should I use controller methods from a form object? - ruby-on-rails

I'm using Sorcery gem.
I have a sessions_controller which handles login/logout. I want to extract controller-unrelated code like form validation and signing the user into a form object.
However, to login user, I need an access to the sessions_controller, as it requires access to the controller context. Obviously, I can pass controller into the form object, but I don't like tying them at all. Here's how it looks:
# SessionsController:
# ...
def create
#form = SignInForm.new(session_params)
#form.set_controller self
if #form.valid?
redirect_signed_in_users
else
render :new, status: :unauthorized
end
end
# SignInForm
( unnecessary stuff omitted
class SignInForm
include ActiveModel::Model
include Virtus.model
attribute :email, String
attribute :password, String
attribute :controller, ActionController::Base, writer: :private
validates_presence_of :email, :password
validate :valid_credentials
def set_controller(controller)
self.controller = controller
end
private
def valid_credentials
errors.add(:base) unless controller.login(self.email, self.password, self.remember_me)
end
end
Any ideas?

Related

ActiveModel::ForbiddenAttributesError rspec on request post [duplicate]

I have this model in Ruby but it throws a ActiveModel::ForbiddenAttributesError
class User < ActiveRecord::Base
attr_accessor :password
validates :username, :presence => true, :uniqueness => true, :length => {:in => 3..20}
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, :uniqueness => true, format: { with: VALID_EMAIL_REGEX }
validates :password, :confirmation => true
validates_length_of :password, :in => 6..20, :on => :create
before_save :encrypt_password
after_save :clear_password
def encrypt_password
if password.present?
self.salt = BCrypt::Engine.generate_salt
self.encrypted_password= BCrypt::Engine.hash_secret(password, salt)
end
end
def clear_password
self.password = nil
end
end
when I run this action
def create
#user = User.new(params[:user])
if #user.save
flash[:notice] = "You Signed up successfully"
flash[:color]= "valid"
else
flash[:notice] = "Form is invalid"
flash[:color]= "invalid"
end
render "new"
end
on ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-linux].
Can you please tell me how to get rid of this error or establish a proper user registration form?
I guess you are using Rails 4. If so, the needed parameters must be marked as required.
You might want to do it like this:
class UsersController < ApplicationController
def create
#user = User.new(user_params)
# ...
end
private
def user_params
params.require(:user).permit(:username, :email, :password, :salt, :encrypted_password)
end
end
For those using CanCan. People might be experiencing this if they use CanCan with Rails 4+. Try AntonTrapps's rather clean workaround solution here until CanCan gets updated:
In the ApplicationController:
before_filter do
resource = controller_name.singularize.to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
and in the resource controller (for example NoteController):
private
def note_params
params.require(:note).permit(:what, :ever)
end
Update:
Here's a continuation project for CanCan called CanCanCan, which looks promising:
CanCanCan
For those using CanCanCan:
You will get this error if CanCanCan cannot find the correct params method.
For the :create action, CanCan will try to initialize a new instance with sanitized input by seeing if your controller will respond to the following methods (in order):
create_params
<model_name>_params such as article_params (this is
the default convention in rails for naming your param method)
resource_params (a generically named method you could specify in
each controller)
Additionally, load_and_authorize_resource can now take a param_method option to specify a custom method in the controller to run to sanitize input.
You can associate the param_method option with a symbol corresponding to the name of a method that will get called:
class ArticlesController < ApplicationController
load_and_authorize_resource param_method: :my_sanitizer
def create
if #article.save
# hurray
else
render :new
end
end
private
def my_sanitizer
params.require(:article).permit(:name)
end
end
source:
https://github.com/CanCanCommunity/cancancan#33-strong-parameters
There is an easier way to avoid the Strong Parameters at all, you just need to convert the parameters to a regular hash, as:
unlocked_params = ActiveSupport::HashWithIndifferentAccess.new(params)
model.create!(unlocked_params)
This defeats the purpose of strong parameters of course, but if you are in a situation like mine (I'm doing my own management of allowed params in another part of my system) this will get the job done.
If using ActiveAdmin don't forget that there is also a permit_params in the model register block:
ActiveAdmin.register Api::V1::Person do
permit_params :name, :address, :etc
end
These need to be set along with those in the controller:
def api_v1_person_params
params.require(:api_v1_person).permit(:name, :address, :etc)
end
Otherwise you will get the error:
ActiveModel::ForbiddenAttributesError
Alternatively you can use the Protected Attributes gem, however this defeats the purpose of requiring strong params. However if you're upgrading an older app, Protected Attributes does provide an easy pathway to upgrade until such time that you can refactor the attr_accessible to strong params.
One more reason is that you override permitted_params by a method. For example
def permitted_params
params.permit(:email, :password)
end
If you are on Rails 4 and you get this error, it could happen if you are using enum on the model if you've defined with symbols like this:
class User
enum preferred_phone: [:home_phone, :mobile_phone, :work_phone]
end
The form will pass say a radio selector as a string param. That's what happened in my case. The simple fix is to change enum to strings instead of symbols
enum preferred_phone: %w[home_phone mobile_phone work_phone]
# or more verbose
enum preferred_phone: ['home_phone', 'mobile_phone', 'work_phone']

Devise - overriding ParameterSanitizer

I am attempting to configure a different Devise strong parameter sanitizer per model following the instructions at; https://github.com/plataformatec/devise#strong-parameters
I have created a new file named parameter_sanitizer within my Employer model directory;
app/controllers/Employers/paramater_sanitizer.rb
class Employer::ParameterSanitizer < Devise::ParameterSanitizer
def initialize(*)
super
devise_parameter_sanitizer.permit(:sign_up, keys: [:forename, :surname, :username)
end
end
Within my application controller I have;
require 'employers/parameter_sanitizer'
class ApplicationController < ActionController::Base
before_filter :devise_parameter_sanitizer, if: :devise_controller?
protect_from_forgery with: :exception
protected
def devise_parameter_sanitizer
if resource_class == Employer
Employer::ParameterSanitizer.new(Employer, :employer, params)
else
super # Use the default one
end
end
end
The error I get from signing up an Employer object is;
NameError in Devise::ConfirmationsController#show
undefined local variable or method `devise_parameter_sanitizer' for #
Any advice on how to overcome this?
Thanks,
Mark
In this initialize method you save the params as an instance variables #params so in your method you should do:
class Employer::ParameterSanitizer < Devise::ParameterSanitizer
def initialize(*)
super
#params.permit(:sign_up, keys: [:forename, :surname, :username])
end
also I believe this should work without specifying #params
Explanation
to find the solution to this problem check the devise api to better understand the methods you are calling and read the `Devise::ParameterSanitizer source code
I am quoting their ruby-rocs about the #permit() method
Instance Method Details
#permit(action, keys: nil, except: nil, &block) ⇒ Object
Add or remove new parameters to the permitted list of an action.
Arguments
action - A Symbol with the action that the controller is performing, like sign_up, sign_in, etc.
keys: - An Array of keys that also should be permitted.
except: - An Array of keys that shouldn't be permitted.
block - A block that should be used to permit the action parameters instead of the Array based approach. The block will be called with an ActionController::Parameters instance.
Examples
# Adding new parameters to be permitted in the `sign_up` action.
devise_parameter_sanitizer.permit(:sign_up, keys: [:subscribe_newsletter])
# Removing the `password` parameter from the `account_update` action.
devise_parameter_sanitizer.permit(:account_update, except: [:password])
# Using the block form to completely override how we permit the
# parameters for the `sign_up` action.
devise_parameter_sanitizer.permit(:sign_up) do |user|
user.permit(:email, :password, :password_confirmation)
end
Returns nothing.
Also I quote
If you have multiple Devise models, you may want to set up a different parameter sanitizer per model. In this case, we recommend inheriting from Devise::ParameterSanitizer and adding your own logic:
class ApplicationController < ActionController::Base
protected
def devise_parameter_sanitizer
if resource_class == User
User::ParameterSanitizer.new(User, :user, params)
else
super # Use the default one
end
end
end
User::ParameterSanitizer.new(User, :user, params) will call this initializer method from parameter_sanitizer.rb source code
def initialize(resource_class, resource_name, params)
#auth_keys = extract_auth_keys(resource_class)
#params = params
#resource_name = resource_name
#permitted = {}
DEFAULT_PERMITTED_ATTRIBUTES.each_pair do |action, keys|
permit(action, keys: keys)
end
end
so basically you are calling initialize(User, :user, params), I don't understand why devise is accepting params in this method, as it has his own way of allowing attributes by saving a static hash of permitted field.
DEFAULT_PERMITTED_ATTRIBUTES = {
sign_in: [:password, :remember_me],
sign_up: [:password, :password_confirmation],
account_update: [:password, :password_confirmation, :current_password]
}
and the permitting them with a loop
DEFAULT_PERMITTED_ATTRIBUTES.each_pair do |action, keys|
permit(action, keys: keys)
end
In this initialize method you save the params as an instance variables #params so in your method you should do:
class Employer::ParameterSanitizer < Devise::ParameterSanitizer
def initialize(*)
super
#params.permit(:sign_up, keys: [:forename, :surname, :username])
end

How to implement strong parameters in Rails 4

I'm having trouble implementing strong parameters, receiving the Undefined Method attr_accessible error locally.
Could anyone explain what exactly I have done wrong here.
users_controller.rb:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to root_url, :notice => "Signed up!"
else
render "new"
end
end
def user_params
params.require(:user).permit(:username, :email, :password, :password_confirmation)
end
end
And in user.rb:
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation
has_secure_password
validates_presence_of :password, :on => :create
end
And perhaps a foolproof fix for this...I've tried a number of attempts but I just can't seem to get this right.
strong_params are usually done in the controller, not in the model. it's also described like this in the api. so, there's also no need for you to set attr_accesible. this way different controllers can also set different fields on a model, e.g. a backend users controller could be allowed to set an admin flag, while the users controller on the frontend is not allowed to do that.
so, your user_params method belongs in your UsersController, and the create and update action use user_params to filter out the params you don't allow to be set. e.g.:
#user = User.new(user_params)
Rails 4 uses strong params by default, and you don't need attr_accessible. Also in rails 4 you permit params in the controller instead of the model.
How is attr_accessible used in Rails 4?

Add attributes to an instance using strong params

The prime example of this feature is when creating a user. Instead of saving 'password' to the database you want to take an instance's password and create a password_hash and password_salt from it.
So if I'm creating a form where a user can be created, how can I have a password field if there's no 'password' attribute?
I think previously this could be solved by using attr_accessor in the user model, but I don't know how to do this with strong params:
def user_params
params.require(:user).permit(:username, :email, :password, :password_confirmation)
end
Just results in a UnknownAttributes error when trying to create a new instance via the user_params:
#user = User.new(user_params)
If you want to add to your strong params, you can use the .merge method
private
def user_params
params.require(:user).permit(:x, :y, :z).merge(hair_color: "brown")
end
Alternatively, if you're looking to manipulate the data on save, you'd probably use the ActiveRecord callbacks, namely before_create:
#app/models/user.rb
Class User < ActiveRecord::Base
before_create :hash_password
private
def hash_password
if password #-> == self.password
#hash the password
#runs after validations
#runs just before DB insert
end
end
end

ActiveModel::ForbiddenAttributesError when creating new user

I have this model in Ruby but it throws a ActiveModel::ForbiddenAttributesError
class User < ActiveRecord::Base
attr_accessor :password
validates :username, :presence => true, :uniqueness => true, :length => {:in => 3..20}
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, :uniqueness => true, format: { with: VALID_EMAIL_REGEX }
validates :password, :confirmation => true
validates_length_of :password, :in => 6..20, :on => :create
before_save :encrypt_password
after_save :clear_password
def encrypt_password
if password.present?
self.salt = BCrypt::Engine.generate_salt
self.encrypted_password= BCrypt::Engine.hash_secret(password, salt)
end
end
def clear_password
self.password = nil
end
end
when I run this action
def create
#user = User.new(params[:user])
if #user.save
flash[:notice] = "You Signed up successfully"
flash[:color]= "valid"
else
flash[:notice] = "Form is invalid"
flash[:color]= "invalid"
end
render "new"
end
on ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-linux].
Can you please tell me how to get rid of this error or establish a proper user registration form?
I guess you are using Rails 4. If so, the needed parameters must be marked as required.
You might want to do it like this:
class UsersController < ApplicationController
def create
#user = User.new(user_params)
# ...
end
private
def user_params
params.require(:user).permit(:username, :email, :password, :salt, :encrypted_password)
end
end
For those using CanCan. People might be experiencing this if they use CanCan with Rails 4+. Try AntonTrapps's rather clean workaround solution here until CanCan gets updated:
In the ApplicationController:
before_filter do
resource = controller_name.singularize.to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
and in the resource controller (for example NoteController):
private
def note_params
params.require(:note).permit(:what, :ever)
end
Update:
Here's a continuation project for CanCan called CanCanCan, which looks promising:
CanCanCan
For those using CanCanCan:
You will get this error if CanCanCan cannot find the correct params method.
For the :create action, CanCan will try to initialize a new instance with sanitized input by seeing if your controller will respond to the following methods (in order):
create_params
<model_name>_params such as article_params (this is
the default convention in rails for naming your param method)
resource_params (a generically named method you could specify in
each controller)
Additionally, load_and_authorize_resource can now take a param_method option to specify a custom method in the controller to run to sanitize input.
You can associate the param_method option with a symbol corresponding to the name of a method that will get called:
class ArticlesController < ApplicationController
load_and_authorize_resource param_method: :my_sanitizer
def create
if #article.save
# hurray
else
render :new
end
end
private
def my_sanitizer
params.require(:article).permit(:name)
end
end
source:
https://github.com/CanCanCommunity/cancancan#33-strong-parameters
There is an easier way to avoid the Strong Parameters at all, you just need to convert the parameters to a regular hash, as:
unlocked_params = ActiveSupport::HashWithIndifferentAccess.new(params)
model.create!(unlocked_params)
This defeats the purpose of strong parameters of course, but if you are in a situation like mine (I'm doing my own management of allowed params in another part of my system) this will get the job done.
If using ActiveAdmin don't forget that there is also a permit_params in the model register block:
ActiveAdmin.register Api::V1::Person do
permit_params :name, :address, :etc
end
These need to be set along with those in the controller:
def api_v1_person_params
params.require(:api_v1_person).permit(:name, :address, :etc)
end
Otherwise you will get the error:
ActiveModel::ForbiddenAttributesError
Alternatively you can use the Protected Attributes gem, however this defeats the purpose of requiring strong params. However if you're upgrading an older app, Protected Attributes does provide an easy pathway to upgrade until such time that you can refactor the attr_accessible to strong params.
One more reason is that you override permitted_params by a method. For example
def permitted_params
params.permit(:email, :password)
end
If you are on Rails 4 and you get this error, it could happen if you are using enum on the model if you've defined with symbols like this:
class User
enum preferred_phone: [:home_phone, :mobile_phone, :work_phone]
end
The form will pass say a radio selector as a string param. That's what happened in my case. The simple fix is to change enum to strings instead of symbols
enum preferred_phone: %w[home_phone mobile_phone work_phone]
# or more verbose
enum preferred_phone: ['home_phone', 'mobile_phone', 'work_phone']

Resources