问题
This is mostly a design/efficiency question but I wanted to see if there was a preferred way to handle this in neo4j as opposed to how I would do it in a sql db.
Right now I have 2 models - user
and event
. I also have a relationship between user
and event
to represent that they will attend the event. I want to figure out the best way to represent the admin of the event. In other words, I want to be able to query the events that are admined by a user.
One way to do it is to create a new relationship called admin_of
between user and event
. Another way is to create an admin property of the attending relationship. Something like admin: true
The admin_of
query seems easy but adds another relationship to the db. It would be able to handle multi admins later on as well.
The latter way could be queried by something like this I think: (From documentation) EnrolledIn.where(since: 2002)
so my search would include admin: true
. However I am not sure how to chain this as I want it to be only friends events. The other where
queries usually base the properties on the nodes rather than relationships.
The latter method seems like to be a preferred method to do it but what would be the correct way to query it? Or is the first method better for simplicity even though it adds an additional relationship?
Update: I came up with something like this
result = Event.query_as(:event).match("event<-[invite: INVITED]-(user: User)<-[:FRIENDS_WITH]-(user)").where(invite: {admin: /true/}).pluck(:event)
based off of the detailed querying in
https://github.com/neo4jrb/neo4j/wiki/Search-and-Match
Update 2
I have this right now but not getting a match. What's the best way to start disecting what's wrong with my query?
current_user.friends.events.query_as(:event).match("event<-[invite]-(user: User)<-[friends_with]-(user)").where(invite: {admin: true}, event: {detail: 'property'}).pluck(:event)
My User model has
has_many :both, :events, model_class: 'Event', rel_class: 'Invite'
and my Event model has
has_many :both, :users, model_class: 'User', rel_class: 'Invite'
and my invite model has
from_class Event
to_class User
type 'invited'
回答1:
This is something I think about a lot and worthy of a blog post or screencast in itself. I have my best practices but wouldn't go as far as to say that this is THE BEST way of doing it. There may be something I haven't considered. Here's what I'm working with, though.
You nailed the big pros and cons of each: the extra relationship makes traversal easy and adding new admins easy but maintaining two sets of relationships that essentially do the same thing is a total drag. For me, it's not even about the extra crap in the database, it's about all the extra work that goes into managing that extra crap.
Generally speaking, I try to avoid creating extra relationships for things like administrative info when I can leverage existing rels. I've settled on two practices:
First, you can get a basic "has access/doesn't have access" by following a path to an object, as you showed in the update. If you want to narrow that down to only events of friends, do something like this:
friend.events.query_as(:event).match.all_the_rest_of_your_chain
By starting for the friend, you will only return events they are related to. Now, if you only want events they own...
You can use an integer property, I usually call mine
score
, in the relationship to represent that user's access level to it. Integers are cool because you can set a scoring convention, 0 is no rights, 50 is editor, 99 is admin -- something like that -- and then say "where rel.score > {admin_score}" and you'll get only those relationships where they have the correct access level or greater. That'd be something like...friend.events(:e, :rel).where("rel.score > {privileged_score}").params(privileged_score: 0).continue_your_chain
Note that we've gotta use a string and set our own param because where
in QueryProxy will target the most recent node, we can't do .where(rel: { score: privileged_score })
. (I'm planning on adding a rel_where
method ASAP to handle this, BTW.)
Anyway, that'd return only events of the friend where their access level is greater than the default, which implies some sort of advanced security level.
I'd start there. When you get into more advanced authorization questions, like "Match events where the user owns the event OR owns the venue where the event is occurring, include privileged information, but how much privileged information depends on which of those items they own... and what if it's both?" there are some more considerations but we can talk about that another time. ;-)
You may also want to read up on scope at https://github.com/neo4jrb/neo4j/wiki/Search-and-Scope. It's a little rough and buggy in current release but Brian has an open PR for an update that makes it much better. You'll be able to write a custom method:
def privileged_events(score = 0
events(:e, :rel).where("rel.score = {rel_score}").params(rel_score: score)
end
And then do something like user.privileged_events.more_query_chain_methods
to make pieces of queries more reusable. We have one spec to fix, it's just an issue with a double, and it'll be merged into master. We should have the 4.0 release candidate (if we even think an RC is necessary) out in a few days.
One more thing...
Something else to consider is that you could also do two queries to only return privileged information: return all events the user is supposed to see, then filter based on the relationship in the view.
<%= @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 %>
That include?
call will be handled serverside and just return a boolean, it's not terribly expensive.
I don't think it's ideal -- it's certainly less efficient -- but it is a lot easier to build. You can always refactor; hell, you should expect that you'll need to refactor at some point anyway.
来源:https://stackoverflow.com/questions/27136947/neo4j-gem-preferred-method-to-deal-with-admin-relationship