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.
Related
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 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.
The goal
I need to do something like this on my layout /views/layouts/application.html.erb:
<% if free %>
<!-- do something -->
<% end %>
BUT, the free variable must be declared on the partials or yielded views, not in controllers or models – this is a visualization rule.
The problem
Currently, I'm doing the following in my /controllers/welcome.rb:
def index
#free = true
end
The point is: this isn't reponsibility of the controller and then I don't feel things good this way.
Environment
CentOS 6.5 with Rails 4.1.
this isn't reponsibility of the controller and then I don't feel
things good this way.
I understand. Although you should not be declaring variables on your view, that could get messy. Imagine if there's a bug with the variable and you would be looking into all your views that set the variable looking for the problem. IMHO it is way better to declare it as a helper in your ApplicationController, that way all the views will have your variable already defined and it will be up to you to use it or not.
class ApplicationController < ActionController::Base
helper_method :free
protected
def free
true
end
end
This way you would be able to override the 'free' method in other controllers if the value of free should be different.
I have a custom helper in app/helpers/posts_helpers.rb:
module PostsHelper
def custom_helper(post)
#do something
end
end
When I call this from one of views, I get the error:
ActionView::Template::Error (undefined method 'custom_helper' for class..
But if I call the same helper from app/helpers/application_helper.rb as follows, then the view is able to detect the helper.
module ApplicationHelper
def custom_helper(post)
#do something
end
end
Even tried setting the include all helper option to true explicitly although it is true by default in config/application.rb. That didn't help either.
config.action_controller.include_all_helpers = true
Why didn't it work?
By default, methods in helpers are available only to their corresponding controllers. For example, PostsController has access to methods in the PostsHelper, but the various posts views don't. If you want to make those methods available to the views, designate them as helper_methods, like so:
module PostsHelper
helper_method :custom_helper
def custom_helper(post)
#do something
end
end
In contrast, methods defined in ApplicationHelper are available globally, i.e. in all controllers and views.
You can read the excellent answer provided here for more detail.
You can also include a module in ApplicationHelper to give its methods global accessibility:
module ApplicationHelper
include PostsHelper
...
end
That's not always a good idea though, because it can make your code difficult to understand and maintain later.