Skip to content
Chris Grigg edited this page Aug 15, 2014 · 17 revisions

PRERELEASE NOTES

The following contain notes for the code currently (8/14/2014) in the activerel branch.

ActiveRel is Neo4j.rb 3.0's the relationship wrapper. ActiveRel objects share most of their behavior with ActiveNode objects.

Benefits

  • Separation of relationship logic instead of shoehorning it into Node models
  • Validations, callbacks, custom methods, etc.
  • Centralize relationship type, no longer need to use :type or :origin options in models

Setup

ActiveRel model definitions have four requirements:

  • include Neo4j::ActiveRel
  • call from_class with a valid model constant or :any
  • call to_class with a valid model constant or :any
  • call type with a string to define the relationship type Name the file as you would any other model.

See the note on from/to at the end of this page for additional information.

# app/models/enrolled_in.rb
class EnrolledIn
  include Neo4j::ActiveRel
  before_save :do_this

  from_class Student
  to_class    Lesson
  type 'enrolled_in'

  property :since, type: Integer
  property :grade, type: Integer
  property :notes

  validates_presence_of :since

  def do_this
    #a callback
  end
end

Relationship Creation

From an ActiveRel Model

Once setup, ActiveRel models follow the same rules as ActiveNode in regard to properties. Declare them to create setter/getter methods, set them to created_at or updated_at for automatic timestamps.

ActiveRel instances require related nodes before they can be saved. Set these using the from_node and to_node methods.

rel = EnrolledIn.new
rel.from_node = student
rel.to_node = lesson

You can pass these as parameters when calling new or create if you so choose.

rel = EnrolledIn.new(from_node: student, to_node: lesson)
#or
rel = EnrolledIn.create(from_node: student, to_node: lesson)

From a has_many or has_one association

Pass the :rel_type option in a declared association with the constant of an ActiveRel model. When that relationship is created, it will add a hidden _classname property with that model's name. The association will use the type declared in the ActiveRel model and it will raise an error if it is included in more than one place.

To take advantage of callbacks and validations, declare your relationship using your ActiveRel model as described above.

class Student
  include Neo4j::ActiveNode
  has_many :out, :lessons, rel_class: EnrolledIn
end

Query and Loading existing relationships

Like nodes, you can load relationships a few different ways.

:each_rel, :each_with_rel, or :pluck methods

Any of these methods can return relationship objects.

Student.first.lessons.each_rel{|r| }
Student.first.lessons.each_with_rel{|node, rel| }
Student.first.query_as(:s).match('s-[rel1:`enrolled_in`]->n2').pluck(:rel1)

These are available as both class or instance methods. Because both each_rel and each_with_rel return enumerables when a block is skipped, you can take advantage of the full suite of enumerable methods:

Lesson.first.students.each_with_rel.select{|n, r| r.grade > 85}

Be aware that select would be performed in Ruby after a Cypher query is performed. The example above perform a Cypher query that matches all students with relationships of type enrolled_in to Lesson.first, then it would call select on that.

The :where method

Because you cannot search for a relationship the way you search for a node, ActiveRel's where method searches for the relationship relative to the labels found in the from_class and to_class models. Therefore:

EnrolledIn.where(since: 2002)
# "MATCH (node1:`Student`)-[rel1:`enrolled_in`]->(node2:`Lesson`) WHERE rel1.since = 2002 RETURN rel1"

If your from_class is :any, the same query looks like this:

"MATCH (node1)-[rel1:`enrolled_in`]->(node2:`Lesson`) WHERE rel1.since = 2002 RETURN rel1"

And if to_class is also :any, you end up with:

"MATCH (node1)-[rel1:`enrolled_in`]->(node2) WHERE rel1.since = 2002 RETURN rel1"

As a result, this combined with the inability to index relationship properties can result in extremely inefficient queries.

Accessing related nodes

Once a relationship has been wrapped, you can access the related nodes using from_node and to_node instance methods. Note that these cannot be changed once a relationship has been created.

student = Student.first
lesson = Lesson.first
rel = EnrolledIn.create(from_node: student, to_node: lesson, since: 2014)
rel.from_node
=> #<Neo4j::ActiveRel::RelatedNode:0x00000104589d78 @node=#<Student property: 'value'>>
rel.to_node
=> #<Neo4j::ActiveRel::RelatedNode:0x00000104589d50 @node=#<Lesson property: 'value'>>

As you can see, this returns objects of type RelatedNode which delegate to the nodes. This allows for lazy loading when a relationship is returned in the future: the nodes are not loaded until you interact with them, which is beneficial with something like each_with_rel where you already have access to the nodes and do not want superfluous calls to the server.

Additional methods

:type instance method, _:type class method: return the relationship type of the model

:_from_class and :_to_class class methods: return the expected classes declared in the model

Regarding: from and to

:from_node, :to_node, :from_class, and :to_class all have aliases using start and end: :start_class, :end_class, :start_node, :end_node, :start_node=, :end_node=. This maintains consistency with elements of the Neo4j::Core API while offering what may be more natural options for Rails users.

Clone this wiki locally