Rails: Calling Common Method to Edit Various Model Field - ruby-on-rails

I want to create a simple method for initializing different counter fields for users. However, I'm not sure how to set the value of a field referred to as a variable.
def self.initialize(user, field)
counter = "#{field}".to_sym
user.send(counter, nil)
user.save
end
I tried:
user.counter instead of user.send(counter), but it comes back with an undefined method error
user.send(counter) = nil, but that's not the correct syntax

Ruby's accessors work using the name= method for an attribute called name.
You can probably access it this way through the model attributes interface:
user[counter] = nil
user.save
Alternatively, a more generic way that should work on any Ruby object that exposes an attr_writer, attr_accessor, or equivalent:
user.send("#{counter}=", nil)
user.save
You'd only use the send version when dealing with arbitrary method names, like you have here. Converting to_sym is not strictly necessary.
Always be careful to white-list the kinds of method calls you're accepting. You shouldn't let counter be an arbitrary user parameter without some validation.

Related

Streamline Ruby: update based on object attribute only if object is not nil

if SOMETHING
charge = Object (this object has a method ID)
end
DiffObject.update_attributes(specific_attribute: charge.id)
But obviously, if the SOMETHING did not evaluate to true then I don't need to update anything, or in my case, I think it might be easier to just run an update_attributes with specific_attribute:nil
So what I have right now is this:
DiffObject.update_attributes(specific_attribute: ((defined? charge) == nil ? nil : charge.id)))
But something tells me there's a MUCH better way of doing it
ActiveSupport's core extension provides a handy method try on almost every object.
try(*a, &b)
Invokes the public method whose name goes as first argument just like public_send does, except that if the receiver does not respond to it the call returns nil rather than raising an exception.
For example, you can use change.try(:id). If change is nil or something that doesn't respond to id, it returns nil, otherwise it returns the ID.
See RailsGuide and Rails API doc for details.
When you don't know whether charge is nil, you can use try to safely access the id. It will return the id if charge is present, and just return nil if the charge is nil, without an error.
DiffObject.update_attributes(specific_attribute: charge.try(:id))
The methods update_attributes, update, save etc will only save the record if it has actually changed, so you don't need to check that yourself. Just save it and let ActiveRecord figure out the rest.

Strong params and security

In a controller method I have:
#user = current_user
#rel = Relationship.where('user_id = ? and organization_id = ? and fish = ?', #user.id, params[:user][:default_relationship_id], true).first
#user.update_attributes(default_relationship_id: #rel.id)
I understand the last line is not secure and requires strong parameters to prevent mass assignment (meaning a user could set any other db variable for that user as well). But how to refactor to make this secure (in this case and more general)?
If I am correct there are two ways: 1) by replacing it with strong params, or 2) by using a model method.
Ad 1) Strong params:
#user.update_attributes(update_params)
private
def update_params
params.require(:user).permit(:default_relationship_id)
end
But how would this know to set default_relationship_id to #rel.id?
Ad 2) Add it to a model method:
#user.update_default(#rel.id) # In controller
def update_default(value) # In model file
self.update_attributes(default_relationship_id: value)
end
But would this indeed be secure since it's not a private model method?
Could someone explain the question I have for each of the two approaches, and perhaps explain which approach is preferred?
Strong params is for mass assignment, i.e. it is for cases like this
some_user.update_attributes(params[:user])
where the user might have manipulated the form to include extra values. What you're doing isn't mass assignment, so strong params is not relevant. In addition strong params doesn't check the content of the parameters (except for checking that values are scalars).
You may still want to check that is ok for the user to set their default_relationship_id to that value, but you'll need to implement those checks yourself. Neither of your 2 suggestions add any security (which may be fine if the query populating #rel will only return objects the user is allowed to associated with)

How to update table row from values determined within controller

ruby_on_rails rails 4 assignment non-screen data to insert record
Rather than using screen values (e.g. simple_form_for #user_evaluation_result) to populate the columns to insert a row I need to calculate some of the values in controller.
For example if I have these statements in the controller
….
# which if I had simple_form_for user_evaluation_result would get populated by the screen
#user_evaluation_result = UserEvaluationResult.new(user_evaluation_result_params)
….
# standard stuff I use for screen derived updates
def user_evaluation_result_params
params.require(:user_evaluation_result).
permit(:evaluation_assumption_id,
:company_listing_id,
:target_share_price_dollars )
end
How do I assign values to :user_assumption_id etc so that insert works. I have tried all sorts of statements. Alternatively do I use another format instead of calling "def user_evaluation_result_params".
Thanks in advance - Pierre
I'm hoping I've interpreted the question properly...
First, to make sure we're on the same page... The code inside of your user_evaluation_result_params method is using Strong Parameters to create an ActionController::Parameters object for the purpose of protecting your model from unpermitted mass-assignments. So, in general, when you're creating or updating an ActiveRecord object from a form in a view template, you want to use Strong Parameters so that users can't manipulate your form to set attributes that you're not expecting.
That said, if you want to set attributes on an object you don't have to use mass assignment. Here is an example of using one-at-a-time assignment (the opposite of mass-assignment):
obj = MyObject.new
obj.attr_one = "One"
obj.attr_two = "Two"
obj.save
There is nothing wrong with this approach other than that it's kind of a lot of work for the general case. So mass-assignment just saves us from having to do this all the time. But it sounds like this one-at-a-time assignment is what you're wanting in this case. So try something like this:
def create
#user_evaluation_result = UserEvaluationResult.new
# assuming you have a UserAssumption object instance in `my_user_assumption`
#user_evaluation_result.user_assumption = my_user_assumption
#user_evaluation_result.some_other_attr = "some value"
#user_evaluation_result.save
end
Note, instead of setting #user_evaluation_result.user_assumption_id directly, as you asked about, it is preferred to set the actual object association as I did above. Try to keep associations outside of mass-assignment and use object relationships to build up your object graphs.
Or, if you have some attributes coming from a form you can mix and match the two approaches:
def create
#user_evaluation_result = UserEvaluationResult.new(user_evaluation_result_params)
# assuming you have a UserAssumption object instance in `my_user_assumption`
#user_evaluation_result.user_assumption = my_user_assumption
#user_evaluation_result.some_other_attr = params[:user_evaluation_result][:some_other_attr]
#user_evaluation_result.save
end
private
def user_evaluation_result_params
params.require(:user_evaluation_result)
.permit(:evaluation_assumption_id,
:company_listing_id,
:target_share_price_dollars)
end

How to assign to Rails ActiveRecord conditionally?

Hey I wasn't quite sure what to call this but here's the deal.
I'm trying to only assign things to my database value if
There isn't a value in the database already, and
The value I'm assigning isn't blank.
The rudimentary version of this code is:
venue.address = venue_json['address'] if venue.address.blank? && !venue_json['address'].blank?
where venue is my ActiveRecord result.
This is what I have now (a little better). With the init_value in the Venue.rb class.
Venue.init_value(venue.address, venue_json['address'])
def self.init_value(record, value)
if record.blank? && !value.blank?
record = value
end
end
I'd like to get to this point, but really have no idea how.
venue.address.init_value(venue_json['address'])
especially since I'd like it it work with any attribute of the ActiveRecord class not just the address value.
Separating it into a method sounds like a good idea, but in this case it makes more sense to use an instance method rather than a class method.
def init_attribute(attribute, value)
self.update(attribute => value) if self.send(attribute).blank? && value.present?
end
venue.init_attribute(:address, venue_json['address'])
Some quick comments on the snippet above:
Using direct assignment won't persist the database value. You could go with something else like update or update_column. Or you can use assignment and then call #save on the object.
Whenever you need something not to be blank, you can use the more readable Object#present? which is part of ActiveSupport.
You'll need to call the method with the same name as the attribute on the database object. For this you'll want to use Object#send from Ruby.

Convert Active Record set into an array of hashes

I saw this...
How to convert activerecord results into a array of hashes
and wanted to create a method that would allow me to turn any scoped or non-scoped record set into an array of hashes. I added this to my model:
def self.to_hash
to_a.map(&:serializable_hash)
end
However, I get this error.
NameError: undefined local variable or method `to_a' for #<Class:0x007fb0da2f2708>
Any idea?
You probably need to call all on that too. Just the to_a would work fine on a scope or existing result set (e.g. User.active.to_hash) but not directly on the model (e.g. User.to_hash). Using all.to_a will work for both scenarios.
def self.to_hash
all.to_a.map(&:serializable_hash)
end
Note that the all.to_a is a little duplicative since all already returns an array, but in Rails 4 it will be necessary.
You're performing the action on a class, not an instance of the class. You can either take away the self. then call this on an instance, or to call it on a collection you need to pass the collection into the class method:
def self.to_hash(collection)
collection.to_a.map(&:serializable_hash)
end

Resources