Пользовательская метамодель 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 и 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.
Если бы это было не так, я бы, вероятно, сделал что-то в соответствии с тем, что описал Майк А.