Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why aren't Rails model attributes accessible using symbols instead of strings?

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

like image 617
Dexygen Avatar asked Oct 12 '11 18:10

Dexygen


People also ask

What are attributes in Rails?

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.

What is the use of model in Rails?

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.

What are Ruby on Rails models?

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.

What happens on Save rails?

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.


4 Answers

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.

like image 161
Wizard of Ogz Avatar answered Sep 19 '22 08:09

Wizard of Ogz


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.

like image 31
Yurui Zhang Avatar answered Sep 17 '22 08:09

Yurui Zhang


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
like image 41
charlysisto Avatar answered Sep 20 '22 08:09

charlysisto


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.

like image 34
Sam Avatar answered Sep 21 '22 08:09

Sam