Does anyone know if/how you can reference an attribute of an ActiveRecord model when updating a model using a symbol? I have the code below, which updates my SocialMediaPost model where call[:model_attribute] is a symbol equal to :facebook_like_count.
I want to do something like this:
SocialMediaPost.find(id).update_columns(call[:model_attribute]: resp.size)
That is functionally equivalent to this:
SocialMediaPost.find(id).update_columns(facebook_like_count: resp.size)
Note: I need to use update_columns for this particular task to bypass all my before_save, after_save, and after_commit callbacks.
Note that
{ name: value }
is a special shortcut notation you can use for maps where the keys are symbols. It is equivalent to the following notation
{ :name => value }
If you use anything else than a symbol you must use the "hash rocket" notation, e.g.
"name" => value
So in your case you can use
SocialMediaPost.find(id).update_columns(call[:model_attribute] => resp.size)
Please try this:
SocialMediaPost.find(id).update_columns(call[:model_attribute].to_sym => resp.size)
You can use #to_sym:
call[:model_attribute].to_sym
Related
I have a DI routine where I have a large csv I'm importing with known column format. I first set up a column map:
col_map =
{
4 => :name,
6 => :description,
21 => :in_stock,
...
I then read each line in, and then using the column map, attempt to set the attribute:
i = Item.new
col_map.each do |k,v|
i[v] = chunks[k] #chunks is the line read in split by the delimiter
In my item declaration, I declare two attributes, b/c these are not stored in the database, they're used for other logic:
attr_writer :in_stock
attr_writer :end_date
When the code gets to this line:
i[v] = chunks[k]
I get this message:
X.DEPRECATION WARNING: You're trying to create an attribute `in_stock'. Writing arbitrary attributes on a model is deprecated. Please just use `attr_writer`
But I'm not trying to create an attribute, and I am using attr_writer. I suspect this has something to do with the [] I'm using instead of . for the lvalue.
Can anyone see what I'm doing wrong?
Thanks for any help,
Kevin
Admittedly, the deprecation wording is slightly confusing, but you're seeing this warning because the model[attribute_name] = ... style is only supported for ActiveRecord attributes on the model, not non-persisted attributes added with attr_writer.
You can see the code that produces the warning over here.
To address this I'd use send which will work for all attributes e.g.
i.send("#{v}=", chunks[k])
I'm trying to loop through all the columns of a model and (1) set the value to lowercase and (2) trim it but I can't seem to get the syntax right. This is what i have so far:
#response.attributes.each do |attr_name, attr_value|
#response."#{attr_name}".downcase.strip!
end
I've searched around and can't seem to find an example of actually setting the value of the model column. All the examples I find deal with displaying the value or field name of each column. In other languages there is an evaluate or eval function to do this but I can't seem to find the equivalent in Ruby.
You can use the write_attribute method to alter an ActiveRecord attribute by name
#response.attributes.each do |attr_name, attr_value|
#response.write_attribute( attr_name, attr_value.downcase.strip )
end
Outside of ActiveRecord framework it is common to use the send method to call a bunch of accessors by name. That would work here, too:
#response.attributes.each do |attr_name, attr_value|
setter = "#{attr_name}="
#response.send( setter, attr_value.downcase.strip )
end
However, the authors of ActiveRecord have foreseen this need, and the write_attribute syntax would be my recommendation here.
You should try this code:
#response.attributes.each do |attr_name, attr_value|
#response[attr_name.to_sym] = attr_value.to_s.downcase.strip
end
Then check #response. It will assign all the values with downcase and stripped in #response variable.
I think the best way for me to explain this question is with example. Here is a simple method with a hash:
def getValues(table)
allColumns = {
'User' => ['first_name', 'last_name'],
'Vehicle' => ['make', 'model', 'id'],
}
end
I am trying to pass in the table and based on that table return a range of values. I would like to know what would be (performance-wise) the best way to accomplish this. Is it using a switch statement, if/else, some sort of loop? If you come up with an answer, please be as kind to include an example so that I may understand better.
I suggest you to rename the parameter first, maybe to table_name or something more descriptive.
Second it is kind of a convention in ruby to use method names separated by _, and avoid using camelCase as another languages.
Third, i would put the list on a constant variable or something, and just reference it inside the method.
Then you can look up the values of some hash based on a key just by using hash[key].
LIST = {
'User' => ['first_name', 'last_name'],
'Vehicle' => ['make', 'model', 'id'],
}
def get_values(table_name)
LIST[table_name]
end
Hash lookup by key is probably one of the most performant operations you could do with a collection, so there is no need to worry about it.
I just started learning ruby on rails, and I'm wondering when I should use "=>" and when I should use "=" for assignment. I am seeing that you use "=>" for hash, for assigning values to symbols in migrations, but i'm not sure where to draw the line.
Thanks!
The => symbol is used solely for hashes. Ruby has a feature in which hashes can be passed as the last argument to a method call without including the surrounding braces. This provides something that resembles keyword arguments (though until Ruby 2.0, Ruby didn't have keyword arguments).
So when you see this:
t.integer :foo, :default => 5
What it really means is this:
t.integer(:foo, { :default => 5 })
The rest is just syntactic sugar designed to make it look nicer.
The = symbol, on the other hand, is the assignment operator you know and love from nearly any programming language.
I struggled with this for a while, but now prefer to use the new style for hashes wherever possible
t.integer :foo, default: 5
t.string :bar, default: 'Dave'
=> is not the same as assignment, but I can see why it is confusing. In a hash you create a key and a value as a pair. The key and value can be anything
{'key1' => 'some value', :symbol_key => 'other value'}
This is different to the assignment, which you can see clearly because if you want the above hash to remain available to your program, you either have to pass it to a method or assign it to a variable
myhash = {'key1' => 'some value', :symbol_key => 'other value'}
And only now can you retrieve stuff from your hash
puts myhash['key1']
So the => operator is actually used to construct hashes (or dictionary objects), assignment allows you to store values in the program.
What is happening quite commonly Rails (and therefore in migrations), is that the hash is being created and passed to the method call without you realising it. But the plumbing is still the same, it's still only a hash that is created.
In Ruby 1.9 you can now define hashes using a javascript-like syntax, so you might start seeing this as well.
myhash = {key1: 'some value', key2: 'other value'}
Need to check if a block of attributes has changed before update in Rails 3.
street1, street2, city, state, zipcode
I know I could use something like
if #user.street1 != params[:user][:street1]
then do something....
end
But that piece of code will be REALLY long. Is there a cleaner way?
Check out ActiveModel::Dirty (available on all models by default). The documentation is really good, but it lets you do things such as:
#user.street1_changed? # => true/false
This is how I solved the problem of checking for changes in multiple attributes.
attrs = ["street1", "street2", "city", "state", "zipcode"]
if (#user.changed & attrs).any?
then do something....
end
The changed method returns an array of the attributes changed for that object.
Both #user.changed and attrs are arrays so I can get the intersection (see ary & other ary method). The result of the intersection is an array. By calling any? on the array, I get true if there is at least one intersection.
Also very useful, the changed_attributes method returns a hash of the attributes with their original values and the changes returns a hash of the attributes with their original and new values (in an array).
You can check APIDock for which versions supported these methods.
http://apidock.com/rails/ActiveModel/Dirty
For rails 5.1+ callbacks
As of Ruby on Rails 5.1, the attribute_changed? and attribute_was ActiveRecord methods will be deprecated
Use saved_change_to_attribute? instead of attribute_changed?
#user.saved_change_to_street1? # => true/false
More examples here
ActiveModel::Dirty didn't work for me because the #model.update_attributes() hid the changes. So this is how I detected changes it in an update method in a controller:
def update
#model = Model.find(params[:id])
detect_changes
if #model.update_attributes(params[:model])
do_stuff if attr_changed?
end
end
private
def detect_changes
#changed = []
#changed << :attr if #model.attr != params[:model][:attr]
end
def attr_changed?
#changed.include :attr
end
If you're trying to detect a lot of attribute changes it could get messy though. Probably shouldn't do this in a controller, but meh.
Above answers are better but yet for knowledge we have another approch as well,
Lets 'catagory' column value changed for an object (#design),
#design.changes.has_key?('catagory')
The .changes will return a hash with key as column's name and values as a array with two values [old_value, new_value] for each columns. For example catagory for above is changed from 'ABC' to 'XYZ' of #design,
#design.changes # => {}
#design.catagory = 'XYZ'
#design.changes # => { 'catagory' => ['ABC', 'XYZ'] }
For references change in ROR