Rails 3.2 model constructor with Rails 4 - ruby-on-rails

My model constructor:
3.2:
def initialize(attributes = {})
super # must allow the active record to initialize!
attributes.each do |name, value|
send("#{name}=", value)
end
end
4.0.4:
ActiveModel::ForbiddenAttributesError
How do I change the 3.2 constructor for 4.0.4 compatibility?

I guess you are getting this error when you try to create a new user from your User controller?
If you have code like:
class UsersController < ApplicationController
...
def create
#user = User.new(params[:user])
...
end
...
end
Then this won't work because you can no longer do mass assignments in Rails 4.
Instead you need to whitelist the parameters in the controller something like this:
class UsersController < ApplicationController
...
def create
#user = User.new(user_params)
...
end
...
private
def user_params
params.require(:user).permit(:username, :password, :password_confirmation, :email) # etc. according to your user model
end
end
Having said this, I'm not sure why you need the constructor in your model at all? The initialize method you get from inheriting from ActiveRecord::Base should be enough. But in any event you will need to whitelist the parameters in your controller to avoid the ForbiddenAttributesError error.

Related

Rails: Using Strong Params as keyword parameters

Let's say I have a User Model with a class method create_with_info. Currently if I want to password the params into the method using keyword parameters, It will be something like this.
# user_controller.rb
def create_with_info
User.create_with_info(**user_info_params)
end
private
def user_info_params
params.require([:name, :age, :email])
params.permit(:name, :age, :email).to_h.symbolize_keys
end
# user.rb
def self.create_with_info(name:, age:, email:)
# do something
end
I'm not sure is it the correct way to use keyword parameters in controller or is there a better way to handle? using to_h.symbolize_keys is annoying for me.

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 fix controller error in Ruby on Rails and get Paperclip up and running

I have been trying for quite a while to get Paperclip up and running on my website and have followed the step-by-step process outlined on github multiple times and it still won't work. I really need to get this up and running as soon as possible. When I run the code through localhost, I get the message "undefined method `id' for nil:NilClass." This is located in line 20 (commented below). Why doesn't it appear as an error for the identical line under the owners method?
class SurveysController < ApplicationController
def new
#survey = Survey.new
end
def create
end
def survey_params
params.require(:survey).permit(:name, :email, :password)
end
def owners
#survey = Survey.new(survey_params)
#survey.user_id=current_user.id
#survey.save
end
def seeker
#survey = Survey.new
#survey.user_id=current_user.id # line with the error (line 20)
#survey.save
end
private
def survey_params
params.require(:survey).permit(:first_name, :last_name, :email, :looking_for, :moving_to, :gender, :coed, :age, :roommate_type, :housing_type, :roommates_estimate, :roommates_amount, :roommates_group, :roommates_names, :max_rent, :move_in, :move_out, :bedrooms, :amenities, :apartment_pet, :roommate_pet, :hometown, :school, :company, :terms, :avatar, :wake_up, :bedtime, :smoke, :smokeoften, :smokesocially, :smokequit, :drink, :drinkoften, :drinksocially, :drinkquit, :drugs, :drugsoften, :drugssocially, :drugsquit, :interest, :sexualactivity, :sexprivacy, :roommatesexprivacy, :overnight, :overnightoften, :roommateovernight, :realty, :availability, :rent, :address, :otherroom, :age_min, :age_max, :age_mode, :pad_photo, :user_status, :sociability, :tidiness, :question, :noise, :political, :religion, :user_id)
end
def idcheck
end
end
What am I doing wrong and how can I get Paperclip up and running? I would really appreciate any kind of assistance I can get with this because I have a pretty close deadline.
Where are you setting current_user?
Generally this is set via a "sign_in" method or some such, eg:
app/helpers/sessions_helper.rb
module SessionsHelper
def sign_in(user)
# "permanent" = expires: 20.years.from_now.utc
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
def current_user=(user)
#current_user = user
end
def current_user
return unless cookies[:remember_token]
#current_user ||= User.find_by(remember_token: cookies[:remember_token])
end
end
Your specific methods likely vary, but that general pattern is often assumed to be in place in your project. Wherever you handle login flow, the current user should be set to some variable that you can refer to via your controllers. You need to make sure you're setting and using that variable.
Additionally, you should have a before_filter in the controller to ensure only logged-in users can access these actions.
do you also use Devise?
if yes,
please add
before_filter :authenticate_user!, only: [:seeker]
after
class SurveysController < ApplicationController
then Devise will ask you login before you do the seeker action.
If you don't use devise, would you mind post your Gemfile or let me know where did you get this current_suer

How to get Devise's current_user in ActiveRecord callback in Rails?

I'm using Devise and Rails 3.2.16. I want to automatically insert who created a record and who updated a record. So I have something like this in models:
before_create :insert_created_by
before_update :insert_updated_by
private
def insert_created_by
self.created_by_id = current_user.id
end
def insert_updated_by
self.updated_by_id = current_user.id
end
Problem is that I get the error undefined local variable or method 'current_user' because current_user is not visible in a callback. How can I automatically insert who created and updated this record?
If there's an easy way to do it in Rails 4.x I'll make the migration.
Editing #HarsHarl's answer would probably have made more sense since this answer is very much similar.
With the Thread.current[:current_user] approach, you would have to make this call to set the User for every request. You've said that you don't like the idea of setting a variable for every single request that is only used so seldom; you could chose to use skip_before_filter to skip setting the User or instead of placing the before_filter in the ApplicationController set it in the controllers where you need the current_user.
A modular approach would be to move the setting of created_by_id and updated_by_id to a concern and include it in models you need to use.
Auditable module:
# app/models/concerns/auditable.rb
module Auditable
extend ActiveSupport::Concern
included do
# Assigns created_by_id and updated_by_id upon included Class initialization
after_initialize :add_created_by_and_updated_by
# Updates updated_by_id for the current instance
after_save :update_updated_by
end
private
def add_created_by_and_updated_by
self.created_by_id ||= User.current.id if User.current
self.updated_by_id ||= User.current.id if User.current
end
# Updates current instance's updated_by_id if current_user is not nil and is not destroyed.
def update_updated_by
self.updated_by_id = User.current.id if User.current and not destroyed?
end
end
User Model:
#app/models/user.rb
class User < ActiveRecord::Base
...
def self.current=(user)
Thread.current[:current_user] = user
end
def self.current
Thread.current[:current_user]
end
...
end
Application Controller:
#app/controllers/application_controller
class ApplicationController < ActionController::Base
...
before_filter :authenticate_user!, :set_current_user
private
def set_current_user
User.current = current_user
end
end
Example Usage: Include auditable module in one of the models:
# app/models/foo.rb
class Foo < ActiveRecord::Base
include Auditable
...
end
Including Auditable concern in Foo model will assign created_by_id and updated_by_id to Foo's instance upon initialization so you have these attributes to use right after initialization, and they are persisted into the foos table on an after_save callback.
another approach is this
class User
class << self
def current_user=(user)
Thread.current[:current_user] = user
end
def current_user
Thread.current[:current_user]
end
end
end
class ApplicationController
before_filter :set_current_user
def set_current_user
User.current_user = current_user
end
end
current_user is not accessible from within model files in Rails, only controllers, views and helpers. Although , through class variable you can achieve that but this is not good approach so for that you can create two methods inside his model. When create action call from controller then send current user and field name to that model ex:
Contoller code
def create
your code goes here and after save then write
#model_instance.insert_created_by(current_user)
end
and in model write this method
def self.insert_created_by(user)
update_attributes(created_by_id: user.id)
end
same for other methods
just create an attribute accessor in the model and initialize it when your record is being saved in controller as below
# app/models/foo.rb
class Foo < ActiveRecord::Base
attr_accessor :current_user
before_create :insert_created_by
before_update :insert_updated_by
private
def insert_created_by
self.created_by_id = current_user.id
end
def insert_updated_by
self.updated_by_id = current_user.id
end
end
# app/controllers/foos_controller.rb
class FoosController < ApplicationController
def create
#foo = Foo.new(....)
#foo.current_user = current_user
#foo.save
end
end

How to add virtual attributes on the fly with strong parameters

I have this controller:
class AccountsController < ApplicationController
def create
if #current_account.update_attributes(account_params)
redirect_to :dashboard
end
end
def account_params
params.require(:account).permit(:company, users_attributes: [:name, phone_attributes: [:number]])
end
end
as you can see, i have 2 levels of nested attributes, the issue is I used to add a virtual attribute before using strong parameters like this:
def create
#phone = #current_account.users.phone.requires_us_format = true
...
end
How to achieve this using both nested attributes and strong parameters?

Resources