一个ActiveRecord模型可以有无限制的任意属性(键/值对)。

8
使用Ruby on Rails,我有一个Customer表,我希望能够添加无限的属性(键值对)。因为我不确定键/值对会是什么,所以我不确定该如何做。例如,一个客户可能是:
  • 客户1的属性:
    • 颜色:'黄色'
    • 品牌:'耐克'
    • 销售量:'33'
  • 客户2的属性:
    • 颜色:'红色'
    • 电话号码:'1111111111'
    • 购买数量:'2'
基本上,客户可以拥有任意数量的键/值属性。
如何实现呢?
3个回答

11

实现此功能的“传统”方法是使用实体-属性-值(Entity-Attribute-Value,简称EAV)模式。顾名思义,您将创建一个具有三个列的新表:一个用于“实体”,这种情况下是“客户”,一个用于“属性”名称或键,以及一个用于该值。因此,您将拥有如下表格:

customer_properties
+----+-------------+--------------+------------+
| id | customer_id | key          | value      |
+----+-------------+--------------+------------+
|  1 |           1 | color        | yellow     |
|  2 |           1 | brand        | nike       |
|  3 |           1 | sales        | 33         |
|  4 |           2 | color        | red        |
|  5 |           2 | phone_number | 1111111111 |
|  6 |           2 | purchases    | 2          |
+----+-------------+--------------+------------+

你肯定需要在key上建立索引,而在value可能也需要(当然还有customer_id,但是当你在迁移中使用relationbelongs_to时,Rails会为你完成这些操作)。

然后在你的模型中:


然后在你的模型中:
# customer.rb
class Customer < ActiveRecord::Base
  has_many :customer_properties
end

# customer_property.rb
class CustomerProperty < ActiveRecord::Base
  belongs_to :customer
end

这使得像这样使用成为可能:

customer = Customer.joins(:customer_properties)
             .includes(:customer_properties)
             .where(customer_properties: { key: "brand", value: "nike" })
             .first

customer.customer_properties.each_with_object({}) do |prop, hsh|
  hsh[prop.key] = prop.val
end
# => { "color" => "yellow",
#      "brand" => "nike",
#      "sales" => "33" }

customer.customer_properties.create(key: "email", value: "foo@bar.com")
# => #<CustomerProperty id: 7, customer_id: 1, key: "email", ...>

就数据库设计而言,这是相当坚实的,但正如你所见,它有一些限制:特别是,使用起来很麻烦。此外,你只能使用单个值类型 (:string/VARCHAR 是常见的)。如果你选择这条路,你可能想要在"Customer"上定义一些便利方法,以使访问和更新属性变得更加简便。我猜测可能有一些针对使用 ActiveRecord 与 EAV 模式协同工作的宝石,但我不知道它们是什么,希望你原谅我没有搜索,因为我在移动设备上。

正如 Brad Werth 所指出的,如果你只需要存储任意属性而不查询它们,serialize 是一个很好的替代方案,如果你使用 PostgreSQL,甚至查询问题也可以通过其伟大的 hstore 特性克服。

祝你好运!


太好了,你帮我省去了麻烦 - 说得好! - Brad Werth
哇,太棒了(你也是,布拉德)。我用的是序列化,但这个也非常有趣。非常感谢。 - Matthew Berman
1
有没有人知道在多个Rails模型中使用此模式的标准Rails宝石? - Eric Walker

2
你可以考虑使用hydra_attribute gem,它是一个为ActiveRecord模型实现实体-属性-值(EAV)模式的工具。

1
你应该可以使用 serialize,将属性哈希分配给属性属性,并以相同的方式检索它们。

然而,这将使按这些属性查询变得困难,甚至不可能。 - Jordan Running
这取决于所使用的数据库。Postgres很容易支持它 - http://travisjeffery.com/b/2012/02/using-postgress-hstore-with-rails/ - Brad Werth
是的,如果你安装了这个 gem。 - Jordan Running
这是“困难的,如果不是不可能的”吗?此外,Rails 4本地支持它。为什么你不直接指出它可能需要gem呢? - Brad Werth
1
因为我不知道这个宝石,而且并不是每个人都使用Postgres?你的回答很好,但有一些注意事项——我只是想确保楼主知道其中一个注意事项。 - Jordan Running
没错,出于你所提到的原因,它们很可能值得在实际答案中提及。 - Brad Werth

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