module Neo4j::Mapping
# A DSL for declared relationships has_n and has_one
# This DSL will be used to create accessor methods for relationships.
# Instead of using the 'raw' Neo4j::NodeMixin#rels method where one needs to know
# the name of relationship and direction one can use the generated accessor methods.
#
# The DSL can also be used to specify a mapping to a Ruby class for a relationship, see Neo4j::Relationships::DeclRelationshipDsl#relationship
#
# ==== Example
#
# class Folder
# include Neo4j::NodeMixin
# property :name
# # Declaring a Many relationship to any other node
# has_n(:files)
# end
#
# class File
# include Neo4j::NodeMixin
# # declaring a incoming relationship from Folder's relationship files
# has_one(:folder).from(Folder, :files)
# end
#
# The following methods will be generated:
# Folder#files :: returns an Enumerable of outgoing nodes for relationship 'files'
# Folder#files_rels :: returns an Enumerable of outgoing relationships for relationship 'files'
# File#folder :: for adding one node for the relationship 'files' from the outgoing Folder node
# File#folder_rel :: for accessing relationship 'files' from the outgoing Folder node
# File#folder :: for accessing nodes from relationship 'files' from the outgoing Folder node
#
class DeclRelationshipDsl
include Neo4j::ToJava
attr_reader :to_type, :to_class, :cascade_delete_prop_name, :counter, :rel_id, :direction
CASCADE_DELETE_PROP_NAMES = {:outgoing => :_cascade_delete_outgoing, :incoming => :_cascade_delete_incoming}
def initialize(rel_id, has_one, params)
@direction = :outgoing
@rel_id = rel_id
@to_type = rel_id
@has_one = has_one
@namespace_type = rel_id
@cascade_delete_prop_name = CASCADE_DELETE_PROP_NAMES[params[:cascade_delete]]
@counter = params[:counter] == true
end
def has_one?
@has_one
end
# If a counter was specified in the dsl for counting number of nodes in this relationship.
#
def counter?
@counter
end
# If cascade delete was specified for this relationship
#
def cascade_delete?
!@cascade_delete_prop_name.nil?
end
def class_and_type_from_args(args) # :nodoc:
if (args.size > 1)
return args[0], args[1]
else
return args[0], @rel_id
end
end
# The actual relationship type that this DSL will use
def namespace_type
@to_class.nil? ? @to_type.to_s : "#{@to_class.to_s}##{@to_type.to_s}"
end
def each_node(node, direction, &block)
type = type_to_java(namespace_type)
dir = dir_to_java(direction)
node._java_node.getRelationships(type, dir).each do |rel|
other = rel.getOtherNode(node).wrapper
block.call other
end
end
def incoming?
@direction == :incoming
end
def incoming_dsl
dsl = to_class._decl_rels[to_type]
raise "Unspecified outgoing relationship '#{to_type}' for incoming relationship '#{rel_id}' on class #{to_class}" if dsl.nil?
dsl
end
def single_node(node)
rel = single_relationship(node)
rel && rel.other_node(node).wrapper
end
def single_relationship(node)
dir = direction
dsl = incoming? ? incoming_dsl : self
node._java_node.rel(dir, dsl.namespace_type)
end
def all_relationships(node)
dsl = incoming? ? incoming_dsl : self
type = type_to_java(dsl.namespace_type)
dir = dir_to_java(direction)
node._java_node.getRelationships(type, dir)
end
def create_relationship_to(node, other)
dsl = incoming? ? incoming_dsl : self
# If the are creating an incoming relationship we need to swap incoming and outgoing nodes
if @direction == :outgoing
from, to = node, other
else
from, to = other, node
end
java_type = type_to_java(dsl.namespace_type)
rel = from._java_node.create_relationship_to(to._java_node, java_type)
rel[:_classname] = dsl.relationship_class.to_s if dsl.relationship_class
# TODO - not implemented yet
# the from.neo_id is only used for cascade_delete_incoming since that node will be deleted when all the list items has been deleted.
# if cascade_delete_outgoing all nodes will be deleted when the root node is deleted
# if cascade_delete_incoming then the root node will be deleted when all root nodes' outgoing nodes are deleted
#rel[@dsl.cascade_delete_prop_name] = node.neo_id if @dsl.cascade_delete?
rel.wrapper
end
# Specifies an outgoing relationship.
# The name of the outgoing class will be used as a prefix for the relationship used.
#
# ==== Arguments
# clazz:: to which class this relationship goes
# relationship:: optional, the relationship to use
#
# ==== Example
# class FolderNode
# include Neo4j::NodeMixin
# has_n(:files).to(FileNode)
# end
#
# folder = FolderNode.new
# # generate a relationship between folder and file of type 'FileNode#files'
# folder.files << FileNode.new
#
def to(*args)
@direction = :outgoing
@to_class, @to_type = class_and_type_from_args(args)
self
end
# Specifies an incoming relationship.
# Will use the outgoing relationship given by the from class.
#
# ==== Example
# class FolderNode
# include Neo4j::NodeMixin
# has_n(:files).to(FileNode)
# end
#
# class FileNode
# include Neo4j::NodeMixin
# has_one(:folder).from(FileNode, :files)
# end
#
# file = FileNode.new
# # create an outgoing relationship of type 'FileNode#files' from folder node to file
# file.folder = FolderNode.new
#
def from(*args)
#(clazz, type)
@direction = :incoming
@to_class, @to_type = class_and_type_from_args(args)
self
end
# Specifies which relationship ruby class to use for the relationship
#
# ==== Example
#
# class OrderLine
# include Neo4j::RelationshipMixin
# property :units
# property :unit_price
# end
#
# class Order
# property :total_cost
# property :dispatched
# has_n(:products).to(Product).relationship(OrderLine)
# end
#
# order = Order.new
# order.products << Product.new
# order.products_rels.first # => OrderLine
#
def relationship(rel_class)
@relationship = rel_class
self
end
def relationship_class # :nodoc:
@relationship
end
end
end