Как стать автором
Обновить

Комментарии 34

как минимум — полезно
Спасибо за пост, идея расширить виды связей очень полезна может быть для соцсетей. А не считаете ли вы избыточным хранение в базе записи инвертированного близнеца?
Приблизительно этого вопроса я очень ждал.

Создание записи близнеца в БД с обратным расположением id пользователей позволяет выполнить выборку социальных связей пользователя за один запрос. Однако, это требует контроля состояний обеих записей. pending, accepted, rejected — должны меняться синхронно, что требует дополнительных манипуляций.

Я выбрал другой подход — запись одна. Контроля состояний не требуется. Однако, как видите, требуется два запроса на выборку всех связей.

Что выбрать?

Если честно — то в этом то и суть поста.
Я не знаю что выгоднее в production решениях, школьный рельсовый сайт с посещением 3000 пользователей в месяц не позволит дать мне никаких рекомендаций. Я надеялся, что кто-нибудь поделится опытом. Ради этого то все и затевалось.
Интересный пост.
Сразу вопрос: учитывая что запись в БД и само отношение между пользователями является уникальным в комбинации «отправитель: получатель: контекст», отправитель может иметь несколько записей с одним и тем же получателем, но в разных контекстах. Как вы планируете визуализацию этих связей?

>Я надеялся, что кто-нибудь поделится опытом.
Для школьного сайта с небольшим посещением такие решения может быть и подходят, не вижу смысла волноваться. Но, в production большого сервиса правильнее будет денормализовать БД. Пусть будет запись близнец, но не 2 запроса в ДБ.
user = User.first
another_user = User.last

user.graph_to(another_user, :context=>:job, :me_as=>:boss, :him_as=>:staff_member)

user.graph_to(another_user, :context=>:job, :me_as=>:boss, :him_as=>:staff_member)
user.graph_to(another_user, :context=>:job, :me_as=>:boss, :him_as=>:staff_member)
user.graph_to(another_user, :context=>:job, :me_as=>:boss, :him_as=>:staff_member)
Извините — сорвалось.

user = User.first
another_user = User.last

user.graph_to(another_user, :context=>:job, :me_as=>:boss, :him_as=>:staff_member)
user.graph_to(another_user, :context=>:live, :me_as=>:friend, :him_as=>:friend)
user.graph_to(another_user, :context=>:sport, :me_as=>:student, :him_as=>:trainer)
user.graph_to(another_user, :context=>:cafe, :me_as=>:barmen, :him_as=>:client)

как душе угодно.
Под «визуализацией» я имел ввиду, как всё это будет выглядеть для обычного пользователя вашего сервиса?
Как ему определить контекст связи? Выбрать из вашего огромного списка, возможен мултиселект?
Возможно это страница с заголовками: Мои Друзья, Мои учителя, Мои знакомые, Мои родители, Мои соседи, а под ними ссылки на пользователей или юзерпики.

Кнопку — это мой друг, это мой босс, это мой одноклассник, это моя школа — можно выводить в зависимости от контекста (статуса пользователя). По тысячи других условий.

Я не хочу рассуждать об этом — это больше дело дизайнера, наверное. Я не проектировщих интерфейсов.
Ваше решение больно смахивает на «Списки» (Lists) в друзьях Facebook. Недостаток Ваш, контекст определяется программистом и он статичен для всех пользователей.
«Зайка моя я твой зайчик
Ручка моя я твой пальчик
Рыбка моя я твой глазик
Банька моя я твой тазик»

:)
Как минимум — RDBMS для этого подходит слабо.
Посмотрите на графовые базы данных, я на все сто уверен, что к наиболее популярным есть привязка из Ruby.
wiki.neo4j.org/content/Ruby
Спасибо! Что-то новое для меня. С удовольствием посмотрю на досуге.
Вконтакте есть «категории», одного человека можно распихнуть сразу в несколько
доступ к альбомам можно ограничивать категориями

не кажется ли надстройка категорий поверх друзей более функциональной?

+в инстант_мессанджере у них еще отдельные категории, никак не связанные с вконтактными
Я, например, хотел бы устанавливать связь пользователя со школьным сайтом.
Представьте, кнопка — Я здесь учился!
Выше я приводил пример: u.graph_to(User.find(20), :context=>:school, :me_as=>:student, :him_as=>:school)
где u — текущий пользователь, а User.find(20) — учетная запись администратора школьного сайта.

В социальной сети, по моему мнению под пользователем вполне можно понимать и организацию.
А к какой категории друзей тогда отнести свою школу/компанию?

В посте я попробовал создавать неравнозначные связи в различных контекстах. Как это получилось — это уже другое дело.
Сомневаюсь, что предложенный вами вариант лучше хранения в базе инвертированной записи.
НЛО прилетело и опубликовало эту надпись здесь
Делается не так, что вот так:

WHERE user_id=1 AND friend_id=5 OR user_id=5 AND friend_id=1

То есть когда вы ищите всех своих друзей, вам нужно пройти по всей таблице дружб и найти все записи, которые accepted и в которых вы находитесь либо в user_id, либо в friend_id. Если есть инвертированная запись, то нужно выбирать только те записи, где я (user_id) равен какому-то значению.
Синхронизация состояния записей несложна, сами понимаете. Зато выгода стандартного подхода очевидна:
Минус -количество запросов INSERT, UPDATE, DELETE увеличивается
Плюс - уменьшается количество запросов SELECT.

А селекта ведь в разы больше
Согласен. Спасибо.
Спасибо за пост, интересно почитать.

Немного по руби коду:
1) в times не нужно вручную менять i, а когда i не нужно, можно ее не писать:
10.times { |i| puts i }; 5.times { puts :hello; }


2) при объявлении method_missing лучше так же объявлять respond_to? с той же регуляркой (чтобы проверки на наличие метода срабатывали, если кто-то захочет проверить, подробнее www.dcmanges.com/blog/30; понятно, что код в статье на поиграться, но все же):
def respond_to?(method_name)
/^(.*)_(.*)_from_(.*)$/.match(method_name.to_s) || super
end
а ничего так смайлик получился
Спасибо. Очень ценное замечание. Учту.
но вернувшись из школы (я работаю учителем)

Даёшь больше таких учителей в школы!
Если method_missing(method_name, *args) не находит какой-то метод, то он попытается его распарсить по регулярке.

Если подобный подход применять регулярно и в большом проекте, то глюкабитили проекта будет на высоте. Особенно обрадуются вновь пришедшие в проект разработчики.
Увы я не могу определить границы применимости данного метода. Если сможете что-то пояснить на этот счет — то это будет очень хорошо. Но в данном случае альтернатив не вижу.
Основные проблемы будут с трудно уловимыми опечатками.
Добавьте сюда написание одних и тех же слов на разных языках, транслитерациях.
Для перцу можно добавить опечатки в разных раскладках, например, job и jоb — для ЭВМ это два разных слова.
Если в команде есть один программист с Пунто-свитчером, то это уже потенциальная бомба замедленного действия.
А на живом запуске проекта ошибки будут накапливаться долго и постепенно, что потом откат бэкапа базы данных не спасет (бэкап тоже будет с глючными записями).

В общем, метод удобный, но работать надо с ним аккуратно.
Мне бы, блин, такого учителя в школе в свое время…
вам нужно в калифорнию!
у меня тут после 6 уроков и так калифорния.
Помимо удвоенного количества селектов, такой метод привносит один очень важный нюанс: эту базу очень сложно разбить на несколько. Например, если одного сервера БД уже не будет хватать.

Существуют, конечно, нереляционные решения, которые позволяют это сделать (MongoDB, например). Но во-первых это прощай джойны, а во-вторых, всё равно один из двух селектов будет глобальным (отправляться на все шарды).

Вывод: инвертированные записи и копирование изменений — необходимая оптимизация в реальном мире.
Посмотрите на Диаспору, там реализована подобная «дружба». Добавляется контекст, а потом в него добавляются контакты.
вы описали приблизительно то же самое, что делает FlockDB.
вообще мы решали проблему несколькими путями. во-первых, если граф направленый (twitter model), то все немного по-другому. говорим что A => B, а взаимная связь будет уже B => A. Соответственно, чтобы понять, кто с кем «дружит» — нужно сделать запрос с join на себя.

на самом деле, очень удобно для таких вещей использовать Graph DB, такие как, например, neo4j. Там на каждую связь (т.к. база по сути schema-less), можно вешать различиные аттрибуты, и по ним производить поиск и фильтрацию. делать одно и двунаправленные графы. делать traversals, каскадом, рекурсивно — как пожелаете.

посмотрите на github.com/maxdemarzi/neography гем для упрощения работы с neo4j. мы решили его написать в силу того, что через Rest c neo4j общаться не очень удобно, а ближайший аналог — требует jruby.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.