I have a field otp_set_up, which in the company_user model is allowed to be "true" or "false".
There is a use case where a sys admin user can reset this field to "false".
While the field can be set to "true" through code, NO user can set it to "true" via a form edit etc.
I haven't added to it the validation in the model since it can be "true" or "false".
I have the following code in a params method specific to an update in the controller before the params.require .permit bit:
if curr_company_user.is_sys_admin? && curr_company_user.can_crud_company_users? && params[:id].to_i != curr_company_user.id
params[:company_user] = params[:company_user].except(:otp_set_up) if params[:company_user][:otp_set_up] == true
params.require(:company_user).permit(:otp_setup, etc. etc....
elsif etc. etc...
This works. A Sys admin user can not set otp_set_up to "true".
My question is:
Is this the best and correct way to do this in Rails? It seems a bit hacky to me, going through the params hash and removing a bit.
Is there a better / cleaner way?
delete_if cleans it up. Still a bit hacky, but slightly less so : )
params.require(:company_user).permit(:otp_setup).delete_if do |key, val|
key == 'otp_setup' && val == true
end
This leaves the original params object intact.
There isn't a built in way to do this. It looks like there used to be but no more https://github.com/rails/strong_parameters/issues/167
delete_if is defined on Hash in the core library, so it is probably the best way to do it in Ruby and by extension in Rails in the absence of a built in method.
Update
I thought it was an interesting idea, so I wrote a small gem called allowable for this type of use case. It will add a few methods to Hash and ActionController::Parameters: #allow, #allow!, #forbid and #forbid!
You would use it like this
params.require(:company_user).permit(:otp_setup).forbid(otp_setup: [true])
# or
params.require(:company_user).permit(:otp_setup).allow(otp_setup: [false])
You can specify a single value or an array of values, and it doesn't mutate the original params object
I don't really recommend messing around with the params object in this case. I think it's best to leave that untouched for the most part to preserve what was actually requested. That way you're not left scratching your head if you need that value again somewhere downstream.
Another approach is to build the list of attributes to accept before passing into permit.
# Attributes that everyone can modify.
attrs = [:attrs, :everyone, :can, :modify]
# Then "whitelist" other attributes based on your permission logic.
if curr_company_user.is_sys_admin? && curr_company_user.can_crud_company_users? && params[:id].to_i != curr_company_user.id
attrs << :otp_set_up unless params[:company_user][:otp_set_up] == true
elsif something_else?
# Modify what can be permitted for this case.
# etc...
end
params.require(:company_user).permit(*attrs)
I have a suggestion that you set it in the params only if the user is an admin and not otherwise. I think this is a better way.
In the model, do something like this:
if user.role == 'admin'
attr_accessor #All the params
else
attr_accessor #All the other params except the one you want to
exclude
Related
I currently have a method with a lot of strict params that I want to shorten:
build_receipt(order_id:, order_rate:, ..... invoice_id:, invoice_date:...)
.
.
I'm thinking of grouping these up into hashes like so:
build_receipt(order_details: {}, invoice_details: {})
Would anyone know a sane way I can do the above while still throwing an error whenever a param is missing without explicitly having to write a validation for every key in the above hashes line by line (or if there is a better way the above method can be shortened)?
You can continue on from that and count the number of keys passed in, and that there are no missing values:
def build_receipt(order_details={}, invoice_details={})
return if order_details.merge(invoice_details).keys.count != 5 or order_details.merge(invoice_details).values.any?{|v| v.nil? }
#do something
end
The docs for attribute_present? say:
Returns true if the specified attribute has been set by the user or by
a database load ...
BUT... that's not true! We see here that Rails initializes attributes on a new object from database defaults.
So, suppose we have a users table with age not null default 0. Then
User.new.attribute_present?(:age) == true
But it hasn't been set by us OR a database load.
Perhaps I'm arguing semantics, but in any case, I'd like a method that does what it says: tells me if a field has been explicitly set.
e.g.
u = User.new
# u.attribute_set?(:age) == false
u.age = u.age #set explicitly to default, for example
# u.attribute_set?(:age) == true
Does that exist?
As far as I know, there isn't a way (provided you have default arguments in your db) (as is good design)
Edited: edited from original answer based on Z5h's comments below
I have a client that is sending params such as age, gender, name and so on.
I need to retrieve data from the table based on the params, but first I need to check for the presence of the param(to avoid a null param and therefore an empty result). The params are working as filters, so they can be triggered or they can be left blanck.
What I am doing right now is
#retieve = Student.all
unless params[:age].nil?
#retrieve = #retrieve.where(age: params[:age])
end
unless params[:gender].nil?
#retrieve = #retrieve.where(gender: params[:gender])
end
and so on for every param I receive. This way I check if the filter has been selected, and if it has I use the selection as a parameter for the query
It works, but as Ruby is known for the DRY statement, I am pretty sure someone out there knows a better way for putting this and to make this flexible.
Thank you for whatever answer or suggestion you will provide!
This will work best if all of these filters were in a subhash of params that you can iterate over without including unwanted parameters (eg the :action and :controller parameters that rails adds)
Once you've done that you could do
(params[:filters] || {}).inject(Student.all) do |scope, (key, value)|
scope.where(key => value)
end
There's a few ways to do this sort of thing and you have options for how far you want to go at this stage.
Two big things I'd consider -
1) Make nice scopes that allow you to send a param and ignore it if it's nil. That way you can just append another scope for each param from the form and it will be ignored without using if or unless
2) Move the search into a separate class (a concern) to keep your controller clean.
Here's a blog post that talks about some of the concepts (too much to post in this answer). There is lots of info on the web about this, I searched on the web under "rails search filter params concern" to get an example for you.
http://www.justinweiss.com/blog/2014/02/17/search-and-filter-rails-models-without-bloating-your-controller/
I have a parameter hash that contains different variable and name pairs such as:
param_hash = {"system_used"=>"metric", "person_height_feet"=>"5"}
I also have an object CalculationValidator that is not an ActiveRecord but a ActiveModel::Validations. The Object validates different types of input from forms. Thus it does not have a specific set of variables.
I want to create an Object to validate it like this:
validator = CalculationValidator.new()
validator.system_used = "metric"
validator.person_height_feet = 5
validator.valid?
my problem right now is that I really would not prefer to code each CalculationValidator manually but rather use the information in the Hash. The information is all there so what I would like to do is something like this, where MAKE_INTO_VARIABLE() is the functionality I am looking for.
validator = CalculationValidator.new()
param_hash.each do |param_pair|
["validator.", param_pair[0]].join.MAKE_INTO_VARIABLE() = param_pair[1]
# thus creating
# "validator.system_used".MAKE_INTO_VARIABLE() = "metric"
# while wanting: validator.system_used = "metric"
# ...and in the next loop
# "validator.person_height_feet".MAKE_INTO_VARIABLE() = 5
# while wanting: validator.person_height_feet = 5
end
validator.valid?
Problem:
Basically my problem is, how do I make the string "validator.person_height" into the variable validator.person_height that I can use to store the number 5?
Additionally, it is very important that the values of param_pair[1] are stored as their real formats (integer, string etc) since they will be validated.
I have tried .send() and instance_variable_set but I am not sure if they will do the trick.
Something like this might work for you:
param_hash.each do |param, val|
validator.instance_eval("def #{param}; ##{param} end")
validator.instance_variable_set("##{param}", val)
end
However, you might notice there's no casting or anything here. You'd need to communicate what type of value each is somehow, as it can't be assumed that "5" is supposed to be an integer, for example.
And of course I probably don't have to mention, eval'ing input that comes in from a form isn't exactly the safest thing in the world, so you'd have to think about how you want to handle this.
Have you looked at eval. As long as you can trust the inputs it should be ok to use.
I'm trying to update many active records at the same time using the :update method and they don't seem to update fine.
#drop_ship_order_line_items = DropShipOrderLineItem.update(params[:drop_ship_order_line_items].keys, params[:drop_ship_order_line_items].values).reject { |dsoli| dsoli.errors.empty? }
params[:drop_ship_order_line_items] returns the following hash:
{"11"=>{"available"=>"1"}, "2"=>{"available"=>"1"}}
But the models don't seem to update correctly...anyone with insides?
AFAIK you can't update models like this on rails, you would have to do it like this:
params[:drop_ship_order_line_items].each do |key,value|
DropShipOrderLineItem.find( key ).update_attributes( value )
end
EDIT
There's probably an attr_protected call somewhere in your code, you should check which attributes are protected or not in there.
If you think you can safely ignore the protection on this specific call, you can use some sending do work out the magic (disclaimer: this is on your own, i'm just showing a possibility):
params[:drop_ship_order_line_items].each do |key,value|
ship = DropShipOrderLineItem.find( key )
value.each do |property,value|
ship.send( "#{property}=", value )
end
ship.save
end
This is going to overcome the attribute protection, but you should make sure this is a safe call and you're not going to burn yourself by doing this.