简体   繁体   中英

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

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.

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM