Пользовательская метамодель Rails?

Я хотел бы иметь возможность добавлять "мета" информацию в модель, в основном определяемые пользователем поля. Так, например, давайте представим модель User:

Я определяю поля для имени, фамилии, возраста, пола.

Я хотел бы, чтобы пользователи могли определять некоторую "метаинформацию", в основном заходить на страницу своего профиля и делиться другой информацией. Таким образом, один пользователь может захотеть добавить "хобби", "род занятий" и "родной город", а другой может захотеть добавить "хобби" и "образование".

Итак, я хотел бы иметь возможность иметь стандартное представление для такого рода вещей, поэтому, например, в представлении я мог бы сделать что-то вроде (в HAML):

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

Таким образом, я могу гарантировать, что информация отображается последовательно, а не просто предоставить пользователю текстовое поле для уценки, которое он может отформатировать по-разному.

Я также хотел бы иметь возможность нажимать на мета и видеть других пользователей, которые дали то же самое, поэтому в приведенном выше примере оба пользователя определили "хобби", было бы неплохо иметь возможность сказать, что я хочу видеть пользователей, которые поделились своими хобби - или еще лучше я хочу видеть пользователей, чьи хобби ___.

Итак, поскольку я не знаю, какие поля пользователи захотят определить заранее, какие есть варианты для предоставления такого рода функциональности?

Есть ли драгоценный камень, который обрабатывает пользовательскую мета-информацию в модели, подобной этой, или, по крайней мере, в некотором роде? Кто-нибудь имел опыт работы с такой проблемой? Если да, то как ты решил это?

Спасибо!

3 ответа

Решение

Если каждому пользователю разрешено определять свои собственные атрибуты, одним из вариантов может быть таблица с тремя столбцами: 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 для столбца значений).

Это лучший ответ, который я могу дать с учетом ваших требований. Может быть, есть стороннее решение, но я настроен скептически. На самом деле это не проблема типа "поп в жемчужине".

Реализация динамического поля зависит от следующих факторов:

  1. Возможность динамически добавлять атрибуты
  2. Возможность поддерживать новые типы данных
  3. Возможность извлечения динамических атрибутов без дополнительных запросов
  4. Возможность доступа к динамическим атрибутам, таким как обычные атрибуты
  5. Возможность запроса объектов на основе динамических атрибутов. (например: найти пользователей с лыжным хобби)

Как правило, решение не отвечает всем требованиям. Решение Майка обращается к 1 и 5 элегантно. Вы должны использовать его решение, если 1 и 5 важны для вас.

Вот длинное решение, которое обращается к 1,2,3, 4 и 5

Обновите users Таблица

Добавить text поле называется meta к таблице пользователей.

Обновите свой 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. Они эффективнее, чем полагаться на БД для LIKE запросы.

Вот один из подходов, если вы используете Solr через Gem Sunspot.

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

Прирост производительности здесь значителен.

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

Если бы это было не так, я бы, вероятно, сделал что-то в соответствии с тем, что описал Майк А.

Другие вопросы по тегам