Neo4j gem - предпочтительный метод для работы с административными отношениями

В основном это вопрос дизайна/эффективности, но я хотел посмотреть, есть ли предпочтительный способ справиться с этим в neo4j, а не в sql db.

Сейчас у меня 2 модели - user и event. У меня также есть связь между user и event, чтобы показать, что они примут участие в мероприятии. Я хочу найти лучший способ представить администратора мероприятия. Другими словами, я хочу иметь возможность запрашивать события, которыми управляет пользователь.

Один из способов сделать это — создать новую связь с именем admin_of между пользователем и event. Другой способ — создать свойство администратора отношения присутствия. Что-то вроде admin: true

Запрос admin_of кажется простым, но добавляет еще одно отношение к db. Позже он также сможет обрабатывать нескольких администраторов.

Я думаю, что последний способ можно запросить примерно так: (из документации) EnrolledIn.where(since: 2002), поэтому мой поиск будет включать admin: true. Однако я не уверен, как связать это, так как я хочу, чтобы это были только события друзей. Другие запросы where обычно основывают свойства на узлах, а не на отношениях.

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

Обновление: я придумал что-то вроде этого

result = Event.query_as(:event).match("event<-[invite: INVITED]-(user: User)<-[:FRIENDS_WITH]-(user)").where(invite: {admin: /true/}).pluck(:event)

на основе подробных запросов в

https://github.com/neo4jrb/neo4j/wiki/Search-and-Match

Обновление 2

У меня есть это прямо сейчас, но я не получаю совпадений. Как лучше всего начать анализировать, что не так с моим запросом?

current_user.friends.events.query_as(:event).match("event<-[invite]-(user: User)<-[friends_with]-(user)").where(invite: {admin: true}, event: {detail: 'property'}).pluck(:event)

Моя пользовательская модель имеет

has_many :both, :events, model_class: 'Event', rel_class: 'Invite'

и моя модель события имеет

has_many :both, :users, model_class: 'User', rel_class: 'Invite'

и моя модель приглашения имеет

  from_class Event
  to_class   User
  type 'invited'

person Clam    schedule 25.11.2014    source источник
comment
comment
Кроме того, исправьте некоторые вещи, если ваш шифрованный запрос. Дайте ему тип rel, а не просто используйте идентификатор. current_user.friends.events.query_as(:event).match("event<-[invite:INVITED_BY]-(user: User)<-[friends_with:FRIENDS_WITH]-(user)").where(invite: {admin: true}, event: {detail: 'property'}).pluck(:event) или что-то в этом роде.   -  person subvertallchris    schedule 26.11.2014
comment
вредит ли запросу отсутствие типов? например какая эффективность достигается за счет этого? я не совсем уверен, что вы имеете в виду, заменяя параметры строками/целыми числами.. один из запросов: MATCH (user0:User), (node2:User), user0-[rel0:friends_with]-(node2:User), (event:Event), node2-[rel1:invited]-(event:Event), event<-[invite:INVITED]-(user: User)<-[friends_with:FRIENDS_WITH]-(user: User) WHERE ID(user0) = {ID_user0} AND invite.admin = {invite_admin} RETURN event | {:ID_user0=>0, :invite_admin=>true}   -  person Clam    schedule 26.11.2014
comment
я удалил кучу всего безрезультатно.. MATCH (user1:User), (user2:User), (event:Event), event<-[invite:INVITED]-(user: User)<-[friends_with:FRIENDS_WITH]-(user: User) WHERE user1.id = 133342 AND invite.admin = true RETURN event   -  person Clam    schedule 26.11.2014
comment
Действительно ли свойство ID пользователя 1 равно 133342? Если вы сделаете MATCH (u:User { id: 133342 }) return u, он вернется? Какова роль пользователя 1 и пользователя 2 в этом матче, если на то пошло? Они не появляются на этом пути.   -  person subvertallchris    schedule 26.11.2014
comment
совсем запутался. это не 133342, но это то, что было на сервере. Я попытался упростить до этого MATCH (user1:User), (user2:User), (event:Event), (event)<-[invite:INVITED]-(user2:User)<-[friends_with:FRIENDS_WITH]-(user1:User) RETURN event Пользователи 1 и 2 являются друзьями   -  person Clam    schedule 27.11.2014
comment
133342 выглядит как нео id. Вам нужно будет сделать WHERE ID(user1) = 133342. Ваш новый запрос дает вам что-нибудь?   -  person subvertallchris    schedule 27.11.2014


Ответы (1)


Это то, о чем я много думаю и что само по себе достойно записи в блоге или скринкаста. У меня есть свои лучшие практики, но я бы не сказал, что это ЛУЧШИЙ способ сделать это. Может быть что-то, что я не учел. Но вот с чем я работаю.

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

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

  • Во-первых, вы можете получить базовый вариант «имеет доступ/не имеет доступа», следуя по пути к объекту, как показано в обновлении. Если вы хотите ограничить список только событиями друзей, сделайте что-то вроде этого:

    friend.events.query_as(:event).match.all_the_rest_of_your_chain

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

  • Вы можете использовать целочисленное свойство, которое я обычно называю своим score, в отношении, чтобы представить уровень доступа этого пользователя к нему. Целые числа — это круто, потому что вы можете установить соглашение о подсчете очков, 0 — нет прав, 50 — редактор, 99 — администратор — что-то в этом роде — а затем сказать «где rel.score > {admin_score}», и вы получите только те отношения, где они имеют правильный уровень доступа или выше. Это было бы что-то вроде...

    friend.events(:e, :rel).where("rel.score > {privileged_score}").params(privileged_score: 0).continue_your_chain

Обратите внимание, что мы должны использовать строку и установить собственный параметр, потому что where в QueryProxy будет нацелен на самый последний узел, мы не можем сделать .where(rel: { score: privileged_score }). (Кстати, я планирую как можно скорее добавить метод rel_where, чтобы справиться с этим.)

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

Я бы начал там. Когда вы задаете более сложные вопросы авторизации, такие как «Сопоставьте события, в которых пользователь владеет событием ИЛИ владеет местом, где происходит событие, включите привилегированную информацию, но количество привилегированной информации зависит от того, какие из этих элементов принадлежат ему... и а если и то и другое?" есть еще некоторые соображения, но мы можем поговорить об этом в другой раз. ;-)

Вы также можете прочитать об области действия на странице https://github.com/neo4jrb/neo4j/wiki/Search-and-Scope. В текущем выпуске он немного грубоват и содержит ошибки, но у Брайана есть открытый PR для обновления, которое делает это намного лучше. Вы сможете написать собственный метод:

def privileged_events(score = 0
  events(:e, :rel).where("rel.score = {rel_score}").params(rel_score: score) 
end

А затем сделайте что-то вроде user.privileged_events.more_query_chain_methods, чтобы части запросов можно было использовать повторно. Нам нужно исправить одну спецификацию, это просто проблема с двойником, и она будет объединена с мастером. У нас должен быть релиз-кандидат 4.0 (если мы вообще думаем, что RC необходим) через несколько дней.

Еще одно...

Еще следует учитывать, что вы также можете выполнить два запроса, чтобы вернуть только привилегированную информацию: вернуть все события, которые пользователь должен видеть, а затем отфильтровать на основе отношения в представлении.

<%= @events.each do |event| %>
  <% if @event.users(:u, :rel).where("rel.score = {admin_score}").params(admin_score: 99).include?(current_user) %>
    # do stuff
  <% end %>
<% end %>

Этот вызов include? будет обрабатываться на стороне сервера и просто возвращать логическое значение, это не очень дорого.

Я не думаю, что он идеален — он определенно менее эффективен — но его намного проще построить. Вы всегда можете провести рефакторинг; черт возьми, вы должны ожидать, что в какой-то момент вам все равно придется проводить рефакторинг.

person subvertallchris    schedule 25.11.2014
comment
это было много, чтобы переварить ха-ха. Для более упрощенной цели, используя ваш пример, я могу сделать что-то вроде current_user.friends.events.query_as(:event).match("event<-[invite: INVITED]-(user: User)<-[:FRIENDS_WITH]-(user)").where(invite: {admin: /true/}, event: {property: /something/} ).pluck(:event). Я не могу сразу назвать что-то вроде friend.events, потому что друзья идентифицируются через пользователей, а друзья тоже пользователи. - person Clam; 26.11.2014
comment
Я заметил, что в моей консоли rails, когда я выполняю этот запрос, я получаю =~true для совпадения where. Что с =~? а зачем / в запросе, а не в кавычках? - person Clam; 26.11.2014
comment
Не используйте /true/. Это в документах? Это делает поиск регулярных выражений. /something/ — это регулярное выражение Ruby, Cypher использует =~ для его представления, поэтому мы конвертируем его для вас. Просто сделайте where(admin: true). - person subvertallchris; 26.11.2014
comment
да, это в документах. начал думать, что это какой-то особый шифр. я, вероятно, добавлю примечание, разъясняющее регулярное выражение. Мне нравится идея иметь гибкость с разрешениями пользователя, но я не могу заставить работать даже базовый запрос. смотрите выше для обновления - person Clam; 26.11.2014