activegraph
activegraph copied to clipboard
feature: an ActiveLabel module for sharing ActiveNode logic
I think issue #1424 has started to bloom into multiple issues. I'm opening this up to continue the discussion of creating some sort of includeable ActiveLabel module functionality.
Preface
@cheerfulstoic began this conversation in #1424 but I didn't really "get it" (see the use case) until, unrelated, I just embarked on a side quest to benchmark the effect of label ordering on query time. As part of this, I downloaded the Neo4j example movie database to test against. Lo-and-behold, as I tried to use ActiveNode to model the data, I begun to see problems....
I initially thought to model the database like so:
class Person
include Neo4j::ActiveNode
property :name
class User < Person
self.mapped_label_name = "User"
property :login
property :password
property :roles
end
class Actor < Person
include InShowbusiness
self.mapped_label_name = "Actor"
end
class Director < Person
include InShowbusiness
self.mapped_label_name = "Director"
end
end
module InShowbusiness
extend ActiveSupport::Concern
included do
property :biography
property :lastModified
property :version
end
end
If you check out the example movie database, you'll notice problems with the above though. This is because a node can have :Person:Actor:User
labels associated with it (i.e. can be both an Actor and a User at the same time). So inheritance doesn't work, because then a node would be wrapped as either an Actor or a User, but would not be both (when it should be both).
Adding multiple labels to Person
(such as using modules) doesn't work either though, as then Person
will always have those labels (which is inappropriate). So :User
, :Actor
, and :Director
labels are truly optional to the person node, except their presence alter's the properties of the person node. In the case of :User
, it adds properties.
The exciting part...
So after reading the ideas in #1424 and trying to solve the movie database problem, here's my take which, the more I think about, the more I like. This is definitely similar to what @leehericks wrote down in #1424 -- I just didn't understand it at the time. ~~(it's also possible that this is exactly what @cheerfulstoic and @leehericks where getting at over in #1424)~~
class Person
include Neo4j::ActiveNode
include User
include Actor
include Director
property :name
# If the person node has the label associated with User do ...
if_is(User) do
def admin_powers(id)
Person.find(id).destroy
end
end
# If the person node has the label associated with either Actor or Director do ...
if_is(Actor, Director) do
def favorite_tv_show
puts "3rd rock from the sun"
end
end
end
module User
include Neo4j::ActiveModule
property :login
property :password
property :roles
def stats
puts "the stats"
end
end
module Actor
include Neo4j::ActiveModule
include Showbusiness
end
module Director
include Neo4j::ActiveModule
include Showbusiness
end
module Showbusiness
extend ActiveSupport::Concern
included do
property :biography
property :lastModified
property :version
end
end
standard_person = Person.new(name: 'John')
standard_person.name #=> John
standard_person.responds_to? :biography #=> false
standard_person.actor? #=> false
actor = Person.actor.new(name: 'Sam', biography: 'cool')
actor.actor? #=> true
actor.director? #=> false
actor.favorite_tv_show #=> "3rd rock from the sun"
director_actor = Person.director.actor.new(biography: 'stuff')
director_actor.actor? #=> true
director_actor.director? #=> true
director_actor.user? #=> false
director_actor.respond_to? :admin_powers #=> false
director_actor.mapped_label_names #=> [:Person, :Actor, :Director]
So in the above we can see a few things going on. We have a base Person
class. This person class includes User
, Actor
, and Director
ActiveModules. These ActiveModules are associated with a label of the same name. If a :Person
node is retrieved from the database, and it includes a label associated with one of the ActiveModules, lets say the User
module, then the node gets wrapped as a Person
object but gains the properties associated with a User
. User
properties can be defined in the User
module. They can also be defined in a special if_is(User) do
block inside the Person
class. This way, if a User
module is included in several classes, those classes all gain the properties associated with the User
modules. Additionally, a specific class could say that it's users gain properties / methods that other classes don't get.
At this point, I should also mention that I'm not committed to any of the naming / DSL, but hopefully you can see what I'm going for here.
Things like
module Animal
include Neo4j::ActiveModule
end
has_many :out, :pets, type: :PET, model_class: :Animal
If you wanted a class to always have a ActiveModule's label / property, you could
class Person
include Neo4j::ActiveNode
include User
include Actor
by_default_has :User
end