I need to compare some Rails (2.3.11) model attribute values before and after a database update, so I start by finding my record and saving the existing attribute values in a hash, as follows:
id = params[:id]
work_effort = WorkEffort.find(id)
ancestor_rollup_fields = {
:scheduled_completion_date => work_effort.scheduled_completion_date
}
work_effort.update_attributes(params.except(:controller, :action))
#etcetera
Note I am adhering to the "best practice" of using a symbol for a hash key.
Then I have a method that takes the model and the hash to determine possible additional steps to take if the values from the hash and the model attributes don't match. To determine this I tried to get at the model attribute value in an each loop but I was getting nil at first:
def rollup_ancestor_updates(work_effort, ancestor_rollup_fields)
ancestor_rollup_fields.each do |key, value|
model_val = work_effort.attributes[key] #nil
#etcetera
In debugging the above I noticed that hard-coding a string as a key:
work_effort.attribute['scheduled_completion_date']
Returned the desired value. So then in my each block I tried the following and it worked:
model_val = work_effort.attributes[key.to_s]
Is there a different way to do this? To me, with just 3 months Ruby/Rails experience, it's confusing to use symbols as hash keys as is the prescribed best practice, but then have to call .to_s on the symbol to get at a model attribute. Has anybody else experienced this, worked around this, been confused by this too? Thanks in advance
In Rails 5, model attributes go through the attributes API when they are set from user input (or any setter) and retrieved from the database (or any getter). Rails has used an internal attributes API for it's entire lifetime. When you set an integer field to “5”, it will be cast to 5.
In simple terms, models are Ruby classes that can holds value of a single row in a database table. Since they all inherit ActiveRecord::Base through ApplicationRecord class, they are equipped with all the ActiveRecord methods which enables them to interact with the database.
Models are Ruby classes. They talk to the database, store and validate data, perform the business logic and otherwise do the heavy lifting. They're the chubby guy in the back room crunching the numbers. In this case, the model retrieves video 15 from the database.
So, save! won't just return true or false but only true on success and raise an excpetion if it fails. The purpose of this distinction is that with save! , you are able to catch errors in your controller using the standard ruby facilities for doing so, while save enables you to do the same using standard if-clauses.
The Hash returned when you call #attributes
on a AR instance has string keys, which is why a symbol as an index into the hash doesn't work in your case. There is a subclass of Hash
called HashWithIndifferentAccess
which automatically converts symbol indexes into strings.
Quite often in Rails you'll encounter HashWithIndifferentAccess
instances. A perfect example is the params variable you access in your controller and view code.
Try using work_effort.attributes.with_indifferent_access[key]
Really it is just doing the same thing that you are, but it does it behind the scenes.
You can overwrite the attributes method with your own.
Open your WorkEffort class
class WorkEffort
def attributes
super.symbolize_keys
end
end
then when you call work_effort.attributes, you will have symbolized keys in the hash.
You may want to use : stringify_keys! which is extensively used all over Rails code.
def rollup_ancestor_updates(work_effort, ancestor_rollup_fields)
ancestor_rollup_fields.stringify_keys!.each do |key, value|
model_val = work_effort.attributes[key]
end
#....
end
All of your Rails models can have symbolized attribute names:
class ApplicationRecord < ActiveRecord::Base
# ...
class << self
def attribute_names(symbols = false)
return self.attribute_names.map(&:to_sym) if symbols
super()
end
end
end
Then you can call MyModel.attribute_names(symbols = true)
and get back [:id, :my_attribute, :updated_at, :created_at, ...]
while still allowing attribute_names
to return strings.
Note that you need the parentheses after super
to avoid an argument error with the originally defined function.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With