Skip to content

Commit

Permalink
Change to has_many <direction syntax>, relationship type specified …
Browse files Browse the repository at this point in the history
…by `type`, require `model: false` to make an association to any model
  • Loading branch information
cheerfulstoic committed Jul 24, 2014
1 parent a999ded commit debd31f
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 51 deletions.
4 changes: 2 additions & 2 deletions lib/neo4j/active_node/has_n.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,10 @@ def #{rel_type}
_decl_rels[rel_type.to_sym] = DeclRel.new(rel_type, false, clazz)
end

def has_many(name, options = {})
def has_many(direction, name, options = {})
name = name.to_sym

association = Neo4j::ActiveNode::HasN::Association.new(:has_many, name, options)
association = Neo4j::ActiveNode::HasN::Association.new(:has_many, direction, name, options)
@associations ||= {}
@associations[name] = association

Expand Down
55 changes: 17 additions & 38 deletions lib/neo4j/active_node/has_n/association.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,31 @@ module HasN
class Association
attr_reader :type, :name, :target_class, :relationship, :direction

def initialize(type, name, options = {})
def initialize(type, direction, name, options = {})
raise ArgumentError, "Invalid association type: #{type.inspect}" if not [:has_many, :has_one].include?(type)
raise ArgumentError, "Invalid direction: #{direction.inspect}" if not [:outbound, :inbound, :bidirectional].include?(direction)

@type = type
@name = name
@direction = direction_from_options(options)
@direction = direction
@target_class_name_from_name = name.to_s.classify
@target_class = begin
options[:model] || @target_class_name_from_name.constantize
if options[:model_class].nil?
@target_class_name_from_name.constantize
elsif options[:model_class]
options[:model_class]
end
rescue NameError
raise ArgumentError, "Could not find #{@target_class_name_from_name} class and no model_class specified"
end

@relationship = options[:via] || options[:from] || options[:with]
@relationship_type = options[:type]
end

# Return cypher partial query string for the relationship part of a MATCH (arrow / relationship definition)
def arrow_cypher(var = nil, properties = {}, create = false)
relationship_name = self.relationship_name(create)
relationship_name_cypher = ":`#{relationship_name}`" if relationship_name
relationship_type = self.relationship_type(create)
relationship_name_cypher = ":`#{relationship_type}`" if relationship_type

properties_string = properties.map do |key, value|
"#{key}: #{value.inspect}"
Expand All @@ -48,48 +54,21 @@ def arrow_cypher(var = nil, properties = {}, create = false)
end
end

def relationship_name(create = false)
@relationship || (create || exceptional_target_class?) && "##{@name}"
def relationship_type(create = false)
@relationship_type || (create || exceptional_target_class?) && "##{@name}"
end

private

# Determine if model class as derived from the association name would be different than the one specified via the model key
# Determine if model class as derived from the association name would be different than the one specified via the model_class key
# @example
# has_many :friends # Would return false
# has_many :friends, model: Friend # Would return false
# has_many :friends, model: Person # Would return true
# has_many :friends, model_class: Friend # Would return false
# has_many :friends, model_class: Person # Would return true
def exceptional_target_class?
@target_class && @target_class.name != @target_class_name_from_name
end

# Determine which direction is desired for the assication from the association options
# Can be specified by using the via/from/with keys, or by using the direction key
#
# @example
# has_many :a, via: Model
# has_many :a, from: Model
# has_many :a, with: Model
# has_many :a, direction: [:inbound|:outbound|:bidirectional]
def direction_from_options(options)
via, from, with = options.values_at(:via, :from, :with)

raise ArgumentError, "Can only specify one of :via, :from, and :with" if [via, from, with].compact.size > 1

if via
:outbound
elsif from
:inbound
elsif with
:bidirectional
elsif direction
raise ArgumentError, "Invalid direction: #{direction.inspect}" if not [:outbound, :inbound, :bidirectional].include?(direction)
direction
else
:bidirectional
end
end

end
end
end
Expand Down
22 changes: 11 additions & 11 deletions spec/e2e/query_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ class Interest

property :name

has_many :interested
has_many :bidirectional, :interested, model_class: false
end

class Lesson
include Neo4j::ActiveNode
property :subject
property :level

has_many :teachers, from: :teaching
has_many :students, from: :is_enrolled_for
has_many :inbound, :teachers, type: :teaching
has_many :inbound, :students, type: :is_enrolled_for

def self.max_level
self.query_as(:lesson).pluck('max(lesson.level)').first
Expand All @@ -33,24 +33,24 @@ class Student
property :name
property :age, type: Integer

has_many :lessons, via: :is_enrolled_for
has_many :outbound, :lessons, type: :is_enrolled_for

has_many :interests, direction: :outbound
has_many :outbound, :interests

has_many :favorite_teachers, model: Teacher
has_many :hated_teachers, model: Teacher
has_many :bidirectional, :favorite_teachers, model_class: Teacher
has_many :bidirectional, :hated_teachers, model_class: Teacher
end

class Teacher
include Neo4j::ActiveNode
property :name

has_many :lessons_teaching, via: :teaching, model: Lesson
has_many :lessons_taught, via: :taught, model: Lesson
has_many :bidirectional, :lessons

has_many :lessons
has_many :outbound, :lessons_teaching, model_class: Lesson
has_many :outbound, :lessons_taught, model_class: Lesson

has_many :interests, direction: :outbound
has_many :outbound, :interests
end

describe 'Query API' do
Expand Down

0 comments on commit debd31f

Please sign in to comment.