开发者

Rails custom meta model?

I'd like to be able to add "meta" information to a model, basically user-defined fields. So, for instance, let's imagine a User model:

I define fields for first name, last name, age, gender.

I would like users to be able to define some "meta information", basically to go in their profile page and share other information. So one user might want to add "hobbies", "occupation", and "hometown", and another might want to add "hobbies", and "education".

So, I'd like to be able to have a standard view for this kind of stuff, so for instance in the view I might do something like (in HAML):

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

This way I can ensure that the information is consistently displayed, rather than just providing a user with a markdown te开发者_如何转开发xtbox that they may format all different ways.

I'd also love to be able to click on meta and see other users who have given the same thing, so in the example above both users defined "hobbies", it would be nice to be able to say I want to see users who have shared hobbies -- or even better I want to see users whose hobbies are ___.

So, since I don't know what fields users will want to define in advance, what kind of options are there for providing that kind of functionality?

Is there a gem that handles custom meta information on a model like this, or at least sort of similarly? Has anyone had experience with this kind of problem? If so, how did you solve it?

Thanks!


The dynamic field implementation depends upon following factors:

  1. Ability to dynamically add attributes
  2. Ability to support new data types
  3. Ability to retrieve the dynamic attributes without additional query
  4. Ability to access dynamic attributes like regular attributes
  5. Ability query the objects based on dynamic attributes. (eg: find the users with skiing hobbies)

Typically, a solution doesn't address all the requirements. Mike's solution addresses 1, and 5 elegantly. You should use his solution if 1 & 5 are important for you.

Here is a long solution that addresses 1,2,3, 4 and 5

Update the users table

Add a text field called meta to the users table.

Update your User model

class User < ActiveRecord::Base  
  serialize :meta, Hash  

  def after_initialize
    self.meta ||= {} if new_record?
  end

end

Adding a new meta field

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

Accessing a meta field

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

Iterating the meta fields

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

To address the 5th requirement you need to use Solr Or Sphinx full text search engines. They are efficient than relying on DB for LIKE queries.

Here is one approach if you use Solr through Sunspot gem.

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

Looking up similar users

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

The performance gain here is significant.


If each user is allowed to define their own attributes, one option might be to have a table with three columns: user_id, attribute_name, attribute_value. It might look like:

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

This table would be used for finding other users who have the same hobbies/pets/etc.

For performance reasons (this table is going to get large) you may want to maintain multiple places that the info is stored -- different sources of info for different purposes. I don't think it's bad to store the same info in multiple tables if absolutely necessary for performance.

It all depends on what functionality you need. Maybe it will end up making sense that each user has their key/value pairs serialized into a string column on the users table (Rails provides nice support for this type of serialization), so when you display info for a particular user you don't even need to touch the huge table. Or maybe you will end up having another table that looks like this:

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

This table would be useful if you need to find all users that have hobbies (run LIKE sql against the keys column), or all users that have anything to do with a dog (run LIKE sql against the values column).

That's the best answer I can give with the requirements you gave. Maybe there is a third-party solution available, but I'm skeptical. It's not really a "pop in a gem" type of problem.


In this case, I would at least consider a documentdb like mongo or couch, which can deal with this type of scenario much easier then an rdms.

If that isn't the case, I would probably end up doing something along the lines of what Mike A. described.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜