【问题标题】:Rails custom meta model?Rails 自定义元模型?
【发布时间】:2011-03-10 03:32:59
【问题描述】:

我希望能够将“元”信息添加到模型中,基本上是用户定义的字段。因此,例如,让我们想象一个 User 模型:

我为名字、姓氏、年龄、性别定义字段。

我希望用户能够定义一些“元信息”,主要是进入他们的个人资料页面并分享其他信息。因此,一位用户可能想要添加“爱好”、“职业”和“家乡”,而另一位用户可能想要添加“爱好”和“教育”。

所以,我希望能够对这类东西有一个标准的视图,所以例如在视图中我可能会做一些类似(在 HAML 中)的事情:

- for item in @meta
  %li
    %strong= item.key + ":"
    = item.value

这样我可以确保信息始终如一地显示,而不是仅仅为用户提供一个markdown文本框,他们可以以不同的方式格式化。

我还希望能够点击 meta 并查看其他用户提供了相同的内容,因此在上面的示例中,两个用户都定义了“爱好”,如果能够说我想要查看有共同爱好的用户 -- 甚至更好 我想查看爱好为 ___ 的用户。

那么,既然我不知道用户会提前定义哪些字段,那么有哪些选项可以提供这种功能?

是否有一个 gem 可以处理这样的模型上的自定义元信息,或者至少是类似的?有没有人遇到过这种问题?如果有,你是怎么解决的?

谢谢!

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 metamodel


    【解决方案1】:

    动态字段的实现取决于以下因素:

    1. 能够动态添加属性
    2. 能够支持新的数据类型
    3. 无需额外查询即可检索动态属性
    4. 能够访问像常规属性一样的动态属性
    5. 能够根据动态属性查询对象。 (例如:找到用户 滑雪爱好)

    通常,解决方案不能满足所有要求。 Mike 的解决方案优雅地解决了 1 和 5。如果 1 和 5 对您很重要,您应该使用他的解决方案。

    这是一个解决 1、2、3、4 和 5 的长解决方案

    更新users

    在 users 表中添加一个名为 metatext 字段。

    更新您的 User 模型

    class User < ActiveRecord::Base  
      serialize :meta, Hash  
    
      def after_initialize
        self.meta ||= {} if new_record?
      end
    
    end
    

    添加新的元字段

    u = User.first
    u.meta[:hobbies] = "skiing"
    u.save
    

    访问元字段

    puts "hobbies=#{u.meta[:hobbies]}"
    

    迭代元字段

    u.meta.each do |k, v|
      puts "#{k}=#{v}"
    end
    

    要解决第 5 个要求,您需要使用 Solr 或 Sphinx 全文搜索引擎。它们比依赖 DB 进行 LIKE 查询更有效。

    如果您通过 Sunspot gem 使用 Solr,这是一种方法。

    class User    
      searchable do
        integer(:user_id, :using => :id)
        meta.each do |key, value|
          t = solr_type(value)
          send(t, key.to_sym) {value} if t
        end
      end
    
      def solr_type(value)
        return nil      if value.nil?
        return :integer if value.is_a?(Fixnum)
        return :float   if value.is_a?(Float)
        return :string  if value.is_a?(String)
        return :date    if value.is_a?(Date)
        return :time    if value.is_a?(Time)
      end    
    
      def similar_users(*args)
        keys = args.empty? ? meta.keys : [args].flatten.compact
        User.search do
          without(:user_id, id)
          any_of do
            keys.each do |key|
              value = meta[key]
              with(key, value) if value
            end
          and
        end
      end
    end
    

    查找相似用户

    u = User.first
    u.similar_users # matching any one of the meta fields
    u.similar_users :hobbies # with matching hobbies
    u.similar_users :hobbies, :city # with matching hobbies or the same city
    

    这里的性能提升是显着的。

    【讨论】:

    • 这是一个非常好的方法,非常感谢您分享它!
    【解决方案2】:

    如果允许每个用户定义自己的属性,一种选择可能是创建一个包含三列的表:user_id、attribute_name、attribute_value。它可能看起来像:

    | user_id | attribute_name | attribute_value |
    | 2       | hobbies        | skiing          |
    | 2       | hobbies        | running         |
    | 2       | pets           | dog             |
    | 3       | hobbies        | skiing          |
    | 3       | colours        | green           |
    

    此表将用于查找具有相同爱好/宠物/等的其他用户。

    出于性能原因(此表会变大),您可能希望维护存储信息的多个位置——用于不同目的的不同信息源。如果对性能绝对有必要,我认为将相同的信息存储在多个表中并不是什么坏事。

    这完全取决于您需要什么功能。也许最终每个用户都将他们的键/值对序列化为用户表上的字符串列是有意义的(Rails 为这种类型的序列化提供了很好的支持),因此当您显示特定用户的信息时,您不会甚至需要触摸巨大的桌子。或者也许你最终会得到另一个看起来像这样的表:

    | user_id | keys             | values               |
    | 2       | hobbies, pets    | skiing, running, dog |
    | 3       | hobbies, colours | skiing, green        |
    

    如果您需要查找所有有爱好的用户(对键列运行 LIKE sql)或所有与狗有任何关系的用户(对值列运行 LIKE sql),此表将很有用。

    根据您的要求,这是我能给出的最佳答案。也许有可用的第三方解决方案,但我持怀疑态度。这不是真正的“宝石中的流行”类型的问题。

    【讨论】:

      【解决方案3】:

      在这种情况下,我至少会考虑像 mongo 或 couch 这样的 documentdb,它可以比 rdms 更容易处理这种类型的场景。

      如果不是这样,我可能最终会按照 Mike A. 所描述的方式做一些事情。

      【讨论】:

        猜你喜欢
        • 2010-12-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-02
        • 1970-01-01
        相关资源
        最近更新 更多