Fill in unfilled properties with other model - ruby-on-rails

I have an ActiveRecord model #new_profile that has some, but not all of its properties filled in. I have another model #default_profile that has a bunch of values I want to copy over, but only if the properties from the first are not filled in. Is there a built in way to do this besides a block like...
#new_profile.name ||= #default_profile.name
#new_profile.address ||= #default_profile.address
# etc.

This might work
#new_profile.update_attributes!(#default_profile.attributes.merge(#new_profile.attributes))
The problem with this, is that if the attribute is in #new_profile, but it is nil, the merge might leave the value set as nil. You might need to do the following.
new_profile_attrs = #new_profile.attributes.reject{ |key,value| !value }
#new_profile.update_attributes!(#default_profile.attributes.merge(new_profile_attrs))

#new_profile.update_attributes(#default_profile.attributes.merge(#new_profile.attributes))

You could try something like
#new_profile.attributes = #new_profile.attributes.reverse_merge #default_profile.attributes

If you need to copy ALL the attributes (except id of course):
#new_profile.attributes.each{|k,v| #new_profile[k] ||= #default_profile[k] if k != 'id'}
Things like update_attributes won't allow you to copy attr_protected-attributes. This thing should.

Related

Ignore a column when using `.changed?` in rails

I want to use the .changed? method to check if a record has changed. The problem is that one of the fields will always be different. I would like to ignore the field. Something like:
record.changed?.except(:field_to_ignore)
How can I solve this?
ActiveModel::Dirty gives you a list of all the attributes that have changed via the changed method. So you could do something like
record.changed.reject { |attr| attr == 'field_to_ignore' }.size > 0
Read more about changed method here
EDITED
A simple solution to this is to use the same changed? functionality from ActiveModel::Dirty, so let's say that the attribute you don't want to take into account is x, with that said you can do something like this:
atttibutes_changed = record.changed_attributes.keys
atttibutes_changed.size > 1 || !atttibutes_changed.include?('x')
The above condition means: if more than one attribute changed or in case one changed need to be different from x. changed_attributes returns a hash with all the attributes that changed and his values (before change).

In Rails 5, is there a way to modify the underlying params in a controller? Or give it a default?

In a Rails 5 controller, you can call params and it returns a hash of the parameters from the request.
But you can't modify the params that way. Because what you're modifying is a copy of the params hash values, not a reference to the underlying params.
params[:starting_value] ||= "abc" # doesn't work for my purposes
What you're supposed to do is store the values elsewhere.
#starting_value = params[:starting_value] || "abc"
But if a bunch of other places in the code expect params[:starting_value], then this solution might require some messy changes.
Is there a way to set the default value of a param in the controller? Or am I going to have to do it the slightly messier way.
I could also accomplish what I want with a redirect, but that isn't ideal either.
I think you're looking for the merge! method. Docs Here
params = params.merge!(:starting_value, 'abc)
It returns the original params with the new one merged in or overwritten. Be aware that merge without an exclamation mark does not modify in place. You need it to keep the changes.

Rails 5 bulk-update

What I need to do is something like this:
users.update_all(number: some_specific_number_different_for_each_user)
Is there a better way of doing it other than iterating through users and updating each one separately? Even if I enclose it in transaction, it seems very inefficient...
Or is there a way to just save all records once I adjust the value, something like:
users.each_with_index { |u,i| u.number = i }
users.save_all
Any ideas?
Not unless the value to be updated can be set by the SQL function.
See(EDIT2)
update_all with a method

Allowing only certain values though a strong parameter in Rails 4

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

Is the Active Record Base update method deprecated?

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.

Resources