为什么Rails模型属性不能使用符号而非字符串来访问?

8

我需要在数据库更新之前和之后比较一些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

请注意,我遵循使用符号作为哈希键的“最佳实践”。

然后,我有一个方法,它接受模型和哈希值,以确定如果哈希值和模型属性不匹配可能需要采取的其他步骤。为了确定这一点,我尝试在每个循环中获取模型属性值,但一开始我得到了nil:

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获取模型属性是很困惑的最佳实践。是否还有其他人遇到过这种情况,或者也感到困惑?谢谢

4个回答

15
当你在AR实例上调用#attributes方法时,返回的哈希表使用字符串作为键,这就是为什么在你的情况下使用符号作为哈希表的索引不起作用的原因。有一个称为HashWithIndifferentAccess的哈希子类会自动将符号索引转换为字符串。
在Rails中,您经常会遇到HashWithIndifferentAccess实例。一个完美的例子就是您在控制器和视图代码中访问的params变量。
尝试使用work_effort.attributes.with_indifferent_access[key] 实际上它只是在幕后做了与您相同的事情。

它能工作,但我是否有理由不使用key.to_s,或者最具有说服力的论点是大多数人都使用with_indifferent_access? - Dexygen
我会使用key.to_s,除非我有非常充分的理由不这样做。在你的情况下,看起来key.to_s比with_indifferent_access更便宜。 - Wizard of Ogz

8
你可以使用自己的方法覆盖属性方法。
打开你的WorkEffort类。
class WorkEffort
  def attributes
    super.symbolize_keys
  end
end

当你调用work_effort.attributes时,哈希表中的键将被符号化。


1

你可能想要使用: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

如何将此代码示例插入到我的代码中,而不使用 key.to_s?谢谢;与此同时,我正在研究另一个示例(with_indifferent_access,现在我回想起在其他地方看到过它)。 - Dexygen
1
我认为你应该说symbolize_keys!。这个想法比调用with_indifferent_access更好,因为它不会创建一个新的哈希表。当然,除非你需要同时使用字符串和符号键。 - Wizard of Ogz

1

您的所有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后面加上括号,以避免参数错误。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接