Почему атрибуты модели Rails недоступны с использованием символов вместо строк?

Мне нужно сравнить некоторые значения атрибутов модели Rails (2.3.11) до и после обновления базы данных, поэтому я начинаю с поиска своей записи и сохранения существующих значений атрибутов в хеше следующим образом:

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

Примечание. Я придерживаюсь «лучшей практики» использования символа для ключа решетки.

Затем у меня есть метод, который берет модель и хэш для определения возможных дополнительных шагов, если значения из хэша и атрибутов модели не совпадают. Чтобы определить это, я попытался получить значение атрибута модели в каждом цикле, но сначала я получил ноль:

def rollup_ancestor_updates(work_effort, ancestor_rollup_fields)
    ancestor_rollup_fields.each do |key, value|
        model_val = work_effort.attributes[key] #nil
        #etcetera

При отладке вышеизложенного я заметил, что строка жестко кодируется как ключ:

work_effort.attribute['scheduled_completion_date']

Возвращает нужное значение. Итак, в каждом блоке я попробовал следующее, и это сработало:

model_val = work_effort.attributes[key.to_s]

Есть ли другой способ сделать это? Для меня, имея всего 3 месяца опыта работы с Ruby/Rails, использование символов в качестве хеш-ключей, как это предписано передовой практикой, сбивает с толку, но затем нужно вызывать .to_s для символа, чтобы получить атрибут модели. Кто-нибудь еще сталкивался с этим, работал над этим, тоже был смущен этим? заранее спасибо


person Dexygen    schedule 12.10.2011    source источник


Ответы (4)


Хэш, возвращаемый при вызове #attributes в экземпляре AR, имеет строковые ключи, поэтому символ в качестве индекса в хеше не работает в вашем случае. Существует подкласс Hash под названием HashWithIndifferentAccess, который автоматически преобразует индексы символов в строки.

Довольно часто в Rails вы столкнетесь с HashWithIndifferentAccess экземплярами. Прекрасным примером является переменная params, к которой вы обращаетесь в своем контроллере и коде представления.

Попробуйте использовать work_effort.attributes.with_indifferent_access[key]

На самом деле он просто делает то же самое, что и вы, но делает это за кулисами.

person Wizard of Ogz    schedule 12.10.2011
comment
Это работает, но есть ли причина, по которой я не должен использовать key.to_s, или это убедительный аргумент, который почти все используют with_in Different_access ? - person Dexygen; 12.10.2011
comment
Я бы просто использовал key.to_s, если бы у меня не было очень веских причин. В вашем случае получается, что key.to_s на самом деле дешевле, чем with_in Different_access - person Wizard of Ogz; 12.10.2011

Вы можете перезаписать метод атрибутов своим собственным.

Откройте свой класс WorkEffort

class WorkEffort
  def attributes
    super.symbolize_keys
  end
end

затем, когда вы вызываете work_effort.attributes, у вас будут символические ключи в хэше.

person Yurui Zhang    schedule 19.02.2013

Вы можете использовать: stringify_keys! который широко используется во всем коде Rails.

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
person charlysisto    schedule 12.10.2011
comment
Как это можно добавить в мой пример кода, чтобы заставить его работать без использования key.to_s ? Спасибо; тем временем я проверяю другой пример (с_indifferent_access, который, как я сейчас вспоминаю, видел в других местах). - person Dexygen; 12.10.2011
comment
Я думаю, ты должен был сказать symbolize_keys!. Эта идея лучше, чем вызов with_indifferent_access, потому что она не создает новый хэш. Если, конечно, вам не нужно использовать как строковые, так и символьные ключи. - person Wizard of Ogz; 12.10.2011

Все ваши модели Rails могут иметь символизированные имена атрибутов:

class ApplicationRecord < ActiveRecord::Base
  # ...

  class << self
    def attribute_names(symbols = false)
      return self.attribute_names.map(&:to_sym) if symbols

      super()
    end
  end
end

Затем вы можете вызвать MyModel.attribute_names(symbols = true) и вернуть [:id, :my_attribute, :updated_at, :created_at, ...], в то же время позволяя attribute_names возвращать строки.

Обратите внимание, что вам нужны круглые скобки после super, чтобы избежать ошибки аргумента с первоначально определенной функцией.

person Sam    schedule 08.07.2019