I am a relative newbie so would really appreciate any assistance.
I'm using Rails 4.2, with the Clearance gem for authentication. I'm hoping someone could describe the best practise for over-riding the controllers to include custom attributes on the sign_up form.
I've read a lot of suggestions with varied advice, many of which are from previous versions of rails that do not use strong_parameters.
If anyone could provide a brief breakdown of the methods I need to be over-riding (user_params/user_from_params/etc) I would be most grateful. I can get things functioning by defining a new 'new' method that just includes #user = User.new, and the a new 'user_params' method using .permit, but I'm concerned about the default code I'm bypassing in user_from_params.
Any advice on best practise here would be fantastic!
Thanks
First, extend Clearance::UsersController and override #user_params to permit the new attribute(s):
# app/controllers/users_controller.rb
class UsersController < Clearance::UsersController
private
def user_params
params[:user].permit(:email, :password, :your_custom_attribute)
end
end
Then, update your routes file to use your new controller:
# config/routes.rb
resources :users, controller: :users, only: :create
I'm working when I have time on updating the docs for Clearance, but right now the simplest thing to do is to inspect the Clearance users controller to see the various methods that can be overridden.
The default implementation of user_from_params is a bit odd now because Clearance 1.x still supports Rails 3.x, so it was written for a time when we used attr_accessible rather than strong parameters.
I'd probably do something like:
def user_params
# Use strong params to do what you need
end
def user_from_params
User.new(user_params)
end
You may need to massage user_params a bit so that whatever you do there is still valid for the new action, as user_from_params is still used there. I've opened an issue in Clearance to see if we can improve that aspect before 2.0.
Related
I am working on rails 4 and having above mentioned error in the following line in my controller's create action
sep_account = VaSeparateAccount.new(params[:separate_account])
Can anyone solve my problem?
Thanks in advance
If you are working with Rails < 4.2, you might need to declare all the attributes under the corresponding model. For eg. lets consider a Book model. Under book.rb, you should have all the attributes like the ones below
attr_accessible :title, :author, :pages, :description
Also, make sure you are not confusing attr_accessible with attr_accessor.
probably it is about your private / protected methods (for :separate_account). You should show ur controller. I guess you do not allow to use them on create action.
I have just been doing a bit of reading on attr_accessor, attr_accessible and strong parameters at a few different locations:
Difference between attr_accessor and attr_accessible
How is attr_accessible used in Rails 4?
http://edgeapi.rubyonrails.org/classes/ActionController/StrongParameters.html
And I am looking at mass assignment:
http://code.tutsplus.com/tutorials/mass-assignment-rails-and-you--net-31695
I can't get my head around the difference between attr_accessible and strong parameters. I am not 100% confident in my understanding of the subjects mentioned above so I could be missing something simple but I know they do a similar job.
However, what is the difference between attr_accessible and strong parameters? Are they just a different name for the same thing? Why did we move from one to the other?
Any info is appreciated.
attr_accessible has been deprecated in Rails 4 in favor of Strong Parameters.
Both are different approaches to the mass assignment problem but Strong Parameters is more flexible.
In example, you have an Usermodel with the attributes email:string and is_admin:boolean. You wish to allow the users to modify their email through a form but not the is_admin field.
In Rails 3 you should do:
attr_accesible :email
With this approach it's not possible for an user to modify is_admin because that attribute is protected.
One of the good things of Strong Parameters is that you could do the following in your controller:
def user_params
if current_user.admin?
params.require(:user).permit(:email, :is_admin)
else
params.require(:user).permit(:email)
end
end
This way one admin user will be able to modify is_admin while a normal user won't.
This is just one example and not the best way to grant administrative permissions to an user but it's quite illustrative.
The main advantage of Strong Parameters is that they are defined in the controller and can be dynamically assigned in run time. attr_accessible was a more static and monolithic way to whitelist attributes.
On the other hand attr_accessor is completely different thing and still can be used in Rails 4, in example, if you need one attribute in your model that it's not necessary to persist or to be written into the database but you require it in a form. Think about:
attr_accessor :has_accepted_legal_terms
It's a Ruby method that can be used to declare an attribute of your model that is not related to the database, an attribute of a Class or PORO (plain old ruby object).
Strong parameters and attr_accessible are two different ways of adding security protections to the Rails "mass assignment" feature. Strong Parameters is the way that is prescribed by the current version of Rails.
"Mass assignment" is a convenient shorthand in Rails that allows you to set many properties of a model in a single statement.
For example, imagine you have a #user that you want to update with data from a form submission. Without mass assignment, you'd have to write tedious code like this:
#user.first_name = params[:user][:first_name]
#user.last_name = params[:user][:last_name]
#user.phone_number = params[:user][:phone_number]
...
#user.save
And on and on for every form field.
With mass assignment, all of that code becomes a single line:
#user.update(params[:user])
But, this is full of security holes. Since params contains whatever data was submitted by the browser, a malicious user could add data to that submission that you did not expect. For example, they could add is_admin=1 to the parameters. If you have an is_admin database column, then mass assignment just let the user upgrade to an administrator. Yikes!
This is where Strong Parameters comes in. With Strong Parameters, Rails will raise a ActiveModel::ForbiddenAttributesError if you try to do the naïve update(params[:user]). Instead, you need to be explicit about what parameters you expect from the browser submission, using the require and permit helpers that Strong Parameters provides. Like this:
def user_params
# Note that :is_admin is not permitted!
params.require(:user).permit(:first_name, :last_name, :phone_number)
end
...
#user.update(user_params)
I can't speak for the maintainers of Rails, but I like Strong Parameters because it is flexible. If I have multiple actions in the users controller that expect different parameters, I can easily describe using permit the parameters that should be allowed.
Or if I have different user roles that are allowed to update different attributes, then I can easily model those permissions. As #CV-Gate mentioned, you can even change these permissions at runtime, which is a powerful.
In short, the flexibility of Strong Parameters is due to the fact that you can define that user_params method anywhere, and however you like. You have the full power of Ruby and OO concepts to make it work the way you want.
What about attr_accessible?
Without going into too much detail (since this feature is no longer supported by Rails): instead of using the permit method, you would do something similar using an attr_accessible macro in the model itself, like this:
class User < ActiveRecord::Base
attr_accessible :first_name, :last_name, :phone_number
...
end
So for simple cases, it works very similar to Strong Parameters; you just define the list of attributes in a different place.
However, since attr_accessible is strongly coupled with the definition of the model class, you lose a lot of flexibility. What if you have two different controller actions that need to do mass assignment for the same User model? Now you are stuck.
attr_accessor
The attr_accessor macro is built into Ruby and has nothing to do with Rails, mass assignment, or security. It just happens to have a similar name. :)
I am facing the following problem: in my application I use engines. Let's say I have a shop engine. Within that shop engine I have two controllers: carts_controller and products_controller and their helpers: carts_helper and products_helper.
Now on my views/shop/products/index.html.erb view, I try to call the method cart_action which is defined in helpers/shop/carts_helper.rb. However, unfortunately I get a undefined method `cart_action' for #<#<Class:0x007fb3627af090>:0x007fb3627aab08> when I do this. When I place the same method in helpers/shop/products_helper.rb I do not get this message and the method works fine.... Why can't I use the method from carts_helper, but can I use the method from products_helper? In a normal rails app I can use any helper method on any view, right?
It might have to do something with the namespacing i.e. the helper files are not in helpers but in helpers/shop however this helps prevent conflicts with helpers from other engines or apps...
module Shop
module CartsHelper
def cart_action(package_id)
#some code
end
end
end
How I call it on shop/products/index.html.erb:
<%= cart_action(package['id']) %>
Could it have to do with the fact that I inherit functionality for my applications_controller from my main app?:
class Shop::ApplicationController < ApplicationController
end
instead of
module Shop
class ApplicationController < ActionController::Base
end
end
FWIW my routes for this engine looks like:
Shop::Engine.routes.draw do
resources :products, only: [:index]
# shopping cart
resource :cart, only: [:show] do
put 'add/:package_id', to: 'carts#add', as: :add_to
put 'remove/:package_id', to: 'carts#remove', as: :remove_from
end
end
Thanks for any help in advance!
Note: I do not want to use my helper method in my main app, rather just on another view in the same engine.
In addition to the ApplicationHelper, only view-specific helpers are accessible to the view. Since this is your product-related view, only the ApplicationHelper + ProductsHelper are accessible. So the solution is to either move this method to ProductsHelper or to ApplicationHelper.
I found two ways to make the methods available to other views as well:
By creating an initializer in my /my_engine/lib/my_engine/engine.rb file as described here: https://stackoverflow.com/a/9641149/3519981
Or by including helper :all in my controllers/myengine/application_controller.rb file as described here: https://stackoverflow.com/a/1179900/3519981
Note both will make all the helpers available in the main application as well. For me that is not a problem (at the moment).
Not sure if anyone here has the same issue as me, but basically after I made a new custom helper and it wasn't accessible in a view, all I had to do was restart the rails server. I felt pretty silly after realizing that, well also relieved :-)
I have two different isolated mountable rails engines; one named Core, other as Finance;
The core engine has a comment resource and routing concern like;
Core::Engine.routes.draw do
concern :commentable do
resources :comments
end
end
And the Finance engine has a invoice model;
Finance::Engine.routes.draw do
resources :invoices, concerns: :commentable
end
Both these engines added main app's Gemfile, and routes.rb file like below;
Gemfile;
gem 'core', path: "../core"
gem 'finance', path: "../finance"
routes.rb;
mount Core::Engine, at: "/"
mount Finance::Engine, at: "/"
At the finance gem; invoice show.erb has comment form like below;
<%= form_for [#invoice, #comment] %>
but it seems rails 4 can't share routing concerns between engines. I have found so many questions on stackoverflow, but still can't find a good solution.
Maybe this not avaliable in rails engines; is there any way two handle this.
Thanks.
Struggling a little bit with Ryan's answer (how to correctly include it where?), I came up with the following solution without explicitly using concerns but still sharing routes:
# 1. Define a new method for your concern in a module,
# maybe in `lib/commentable_concern.rb`
module CommentableConcern
def commentable_concern
resources :comments
end
end
# 2. Include the module it into ActionDispatch::Routing::Mapper to make
# the methods available everywhere, maybe in an initializer
ActionDispatch::Routing::Mapper.include CommentableConcern
# 3. Call the new method where you want
# (this way `concerns: []` does not work) obviously
Finance::Engine.routes.draw do
resources :invoices do
commentable_concern
end
end
This monkey patches ActionDispatch::Routing::Mapper, but as long as you only define new methods (and do not touch existing ones) it should be safe.
I don't think that it's possible to do that because each engine is its own container and you can't reach across between engines to do what you're attempting to do.
Instead, define a module which you can include in both contexts which define the same concern:
module CommentableConcern
def self.included(base)
base.instance_eval do
concern :commentable do
resources :comments
end
end
end
end
I think this is the only way you can accomplish that.
I have a contact form partial that I include in two different forms on my app. I was wondering if there is a better approach to strong parameters than duplicating the permitted params in each respective controller?
Users controller:
def user_params
params.require(:user).permit(:name, :email, contact_attributes: [:city, :state])
end
User Applications controller:
def user_application_params
params.require(:user_application).permit(:gender, :birthdate,
user_attributes: [contact_attributes: [:city, :state]])
end
So ideally this code would be in one place, I'm not sure how to achieve this though.
First, let me say, whitelisting your all params automatically is a hack that might be ok in some situations but could cause some substantial security risks in others and exposes you to risk as your codebase evolves (so it's never really ok). Here's a much better way to solve the problem that is about as easy and should work in just about all situations.
Instead, write a controller concern. This is just a module that goes into your app/controllers/concerns directory.
so create app/controllers/concerns/shared_contact_permitted_attributes.rb as follows:
module SharedContactPermittedAttributes
def shared_contact_permitted_attributes
contact_attributes: [:city, :state]
end
end
Then in whatever controller you want it to show up in, add include SharedContactPermittedAttributes at the top and use the method defined in the module in your strong params. For example:
def user_params
params.require(:user).permit(:name, :email, shared_contact_permitted_attributes)
end
Then you're done!
What I ended up doing was permitting all parameters and blacklisting certain ones. The blacklist params is a lot shorter than the whitelist.
params[:profile].permit!.except(user_attributes: [role_ids: []])
This allowed me to easily reuse certain form partials.