activegraph icon indicating copy to clipboard operation
activegraph copied to clipboard

feature: an ActiveLabel module for sharing ActiveNode logic

Open jorroll opened this issue 6 years ago • 28 comments

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

jorroll avatar Dec 15 '17 10:12 jorroll