Skip to content

Commit

Permalink
adding select argument, making include :none default, and adding a co…
Browse files Browse the repository at this point in the history
…nnections method
  • Loading branch information
kamranjon committed Nov 1, 2013
1 parent 85dd366 commit 0f6e38c
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 62 deletions.
103 changes: 58 additions & 45 deletions lib/deja/bridge.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,25 @@ def is_query?(id)

## these methods take a cypher block context as an argument,
## it allows us to treat nodes/rels the same regardless of index or id
def node(id, context, return_root = true)
if return_root
return context.lookup(id[:index], id[:key], id[:value]).ret if is_index?(id)
return context.query(id[:index], id[:query].gsub("'", %q(\\\'))).ret if is_query?(id)
context.node(id).ret
else
return context.lookup(id[:index], id[:key], id[:value]) if is_index?(id)
return context.query(id[:index], id[:query].gsub("'", %q(\\\'))) if is_query?(id)
context.node(id)
end
def node(id, context, identifier = :root)
return context.lookup(id[:index], id[:key], id[:value]).as(identifier) if is_index?(id)
return context.query(id[:index], id[:query].gsub("'", %q(\\\'))).as(identifier) if is_query?(id)
context.node(id).as(identifier)
end

def rel(id, context, return_root = true)
if return_root
return context.lookup_rel(id[:index], id[:key], id[:value]).ret if is_index?(id)
return context.query_rel(id[:index], id[:query].gsub("'", %q(\\\'))).ret if is_query?(id)
context.rel(id).ret
else
return context.lookup_rel(id[:index], id[:key], id[:value]) if is_index?(id)
return context.query_rel(id[:index], id[:query].gsub("'", %q(\\\'))) if is_query?(id)
context.rel(id)
end
def rel(id, context, identifier = :relation)
return context.lookup_rel(id[:index], id[:key], id[:value]).as(identifier) if is_index?(id)
return context.query_rel(id[:index], id[:query].gsub("'", %q(\\\'))).as(identifier) if is_query?(id)
context.rel(id).as(identifier)
end

def apply_options(context, options = {})
context = filter(context, options[:filter]) if options[:filter]
context = order(context, options[:order]) if options[:order]
context = limit(context, options[:limit]) if options[:limit]
context = skip(context, options[:offset]) if options[:offset]
context
return select(context, options[:select]) if options[:select].present?
return_query(context, options[:return_root])
end

def filter(context, filter)
Expand All @@ -68,6 +57,17 @@ def skip(context, offset)
context.skip(offset)
end

def select(context, properties)
properties << :type
context.ret { |n| properties.map{|p| node(:root)[p]} << node(:root).neo_id.as(:id)}
end

def return_query(context, return_root = :root_rel_end)
context.ret { :root } if return_root == :root_only
context.ret { [:relation, :end]} if return_root == :rel_end
context.ret { [:root, :relation, :end] } if return_root == :root_rel_end
end

def create_node(attributes = {})
raise Deja::Error::InvalidParameter unless attributes
raise Deja::Error::NoParameter if attributes.empty?
Expand All @@ -76,13 +76,13 @@ def create_node(attributes = {})

def delete_node(id)
cypher {
Deja::Bridge.node(id, self, false).del.both(rel().as(:r).del)
Deja::Bridge.node(id, self).del.both(rel().as(:relation).del)
}
end

def update_node(id, attributes)
cypher do
Deja::Bridge.node(id, self, false).tap do |n|
Deja::Bridge.node(id, self).tap do |n|
attributes.each do |key, value|
n[key] = value
end
Expand All @@ -91,15 +91,21 @@ def update_node(id, attributes)
end

def create_relationship(start_node, end_node, name, attributes = {})
cypher { create_path{ Deja::Bridge.node(start_node, self, false) > rel(name, attributes).as(:r).neo_id.ret > Deja::Bridge.node(end_node, self, false)} }
cypher { create_path{ Deja::Bridge.node(start_node, self, :root) > rel(name, attributes).as(:relation).neo_id.ret > Deja::Bridge.node(end_node, self, :end)} }
end

def get_relationship(id)
cypher { node.ret < Deja::Bridge.rel(id, self) < node.ret }
cypher {
r = node.as(:root) < Deja::Bridge.rel(id, self) < node.as(:end)
Deja::Bridge.apply_options(r, {:return_root => :root_rel_end})
}
end

def get_relationship_from_nodes(start_node, end_node, type)
cypher { Deja::Bridge.node(start_node, self) > rel(type).ret > Deja::Bridge.node(end_node, self) }
cypher {
r = Deja::Bridge.node(start_node, self, :root) > rel(type).as(:relation) > Deja::Bridge.node(end_node, self, :end)
Deja::Bridge.apply_options(r, {:return_root => :root_rel_end})
}
end

def update_relationship(id, attributes = {})
Expand All @@ -108,56 +114,59 @@ def update_relationship(id, attributes = {})
attributes.each do |key, value|
r[key] = value
end
end.ret
end
end.ret end
end

def delete_relationship(id)
cypher {
Deja::Bridge.rel(id, self, false).del
Deja::Bridge.rel(id, self).del
}
end

def get_nodes(id, opts = {})
return single_node(id) if opts[:include] == :none
return single_node(id, opts) if opts[:include] == :none
opts[:direction] ||= :both
rels = opts[:include] == :all ? nil : opts[:include]
case opts[:direction]
when :out then outgoing_rel(id, rels, opts[:return_root], opts)
when :in then incoming_rel(id, rels, opts[:return_root], opts)
when :both then in_out_rel(id, rels, opts[:return_root], opts)
when :out then outgoing_rel(id, rels, opts)
when :in then incoming_rel(id, rels, opts)
when :both then in_out_rel(id, rels, opts)
else false
end
end

def single_node(id)
cypher { Deja::Bridge.node(id, self, true) }
def single_node(id, opts = {})
opts[:return_root] = :root_only
cypher {
n = Deja::Bridge.node(id, self)
Deja::Bridge.apply_options(n, opts)
}
end

def outgoing_rel(id, rels = nil, root = nil, opts = nil)
def outgoing_rel(id, rels = nil, opts = nil)
cypher {
r = Deja::Bridge.node(id, self, root).outgoing(rel(*rels).ret)
ret Deja::Bridge.apply_options(r, opts)
r = Deja::Bridge.node(id, self).outgoing(rel(*rels).as(:relation)).as(:end)
Deja::Bridge.apply_options(r, opts)
}
end

def incoming_rel(id, rels = nil, root = nil, opts = nil)
def incoming_rel(id, rels = nil, opts = nil)
cypher {
r = Deja::Bridge.node(id, self, root).incoming(rel(*rels).ret)
ret Deja::Bridge.apply_options(r, opts)
r = Deja::Bridge.node(id, self).incoming(rel(*rels).as(:relation)).as(:end)
Deja::Bridge.apply_options(r, opts)
}
end

def in_out_rel(id, rels = nil, root = nil, opts = nil)
def in_out_rel(id, rels = nil, opts = nil)
cypher {
r = Deja::Bridge.node(id, self, root).both(rel(*rels).ret)
ret Deja::Bridge.apply_options(r, opts)
r = Deja::Bridge.node(id, self).both(rel(*rels).as(:relation)).as(:end)
Deja::Bridge.apply_options(r, opts)
}
end

def count_nodes(index)
cypher {
Deja::Bridge.node(index, self, false).count
Deja::Bridge.node(index, self).count
}
end

Expand All @@ -171,6 +180,10 @@ def count_rels(id, rel = nil, direction = nil)
return false
end
end

def count_connections(id)
cypher { node(id).both().count }
end
end
end
end
30 changes: 18 additions & 12 deletions lib/deja/neo_parse.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,25 @@ def normalize(hash, type = :eager)
private
# separate neo4j returned data into flat array of node/relationship data
def sane_hash(hash)
hash['data'].map do |slice|
slice.map do |record|
next unless record
attr_hash = {
:id => record['self'].split('/').last.to_i,
:type => record['type']
}
attr_hash[:start_node] = record['start'].split('/').last.to_i if record['start']
attr_hash[:end_node] = record['end'].split('/').last.to_i if record['end']
record['data'].each do |key, value|
attr_hash[key.to_sym] = value
hash['data'].each_with_index.map do |slice, i|
## if we are selecting specific columns, we trigger this first condition
unless slice.first && slice.first.is_a?(Hash)
Hash[hash['columns'].map{|x|(x.split('.')[1] || x).to_sym}.zip(slice)]
## otherwise we are grabbing every field and we parse the data
else
slice.map do |record|
next unless record
attr_hash = {
:id => record['self'].split('/').last.to_i,
:type => record['type']
}
attr_hash[:start_node] = record['start'].split('/').last.to_i if record['start']
attr_hash[:end_node] = record['end'].split('/').last.to_i if record['end']
record['data'].each do |key, value|
attr_hash[key.to_sym] = value
end
attr_hash
end
attr_hash
end
end.flatten
end
Expand Down
5 changes: 4 additions & 1 deletion lib/deja/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ def add_property_to_index(property)
end

def find(id, options = {})
options[:include] ||= :all
result = Deja::Query.load_node(id, options)
objectify result
end
Expand Down Expand Up @@ -145,6 +144,10 @@ def count(rel_alias)
return false
end

def connections
return Deja::Query.count_connections(@id)
end

def outgoing_rel(type, cardinality="plural")
self.class.outgoing_rel(type, cardinality)
end
Expand Down
11 changes: 9 additions & 2 deletions lib/deja/query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ class Query

class << self
def load_node(neo_id, options = {})
options[:return_root] ||= true
options[:return_root] ||= :root_only
options[:include] ||= :none
cypher_query = Deja::Bridge.get_nodes(neo_id, options)
result_hash = Deja.execute_cypher(cypher_query)
normalize(result_hash)
end

def load_related_nodes(neo_id, options = {})
options[:return_root] ||= false
options[:return_root] ||= :rel_end
cypher_query = Deja::Bridge.get_nodes(neo_id, options)
result_hash = Deja.execute_cypher(cypher_query)
normalize(result_hash, :lazy)
Expand Down Expand Up @@ -46,6 +47,12 @@ def count_relationships(id, type, direction)
rel_count = result_hash['data'].first.first
end

def count_connections(id)
cypher_query = Deja::Bridge.count_connections(id)
result_hash = Deja.execute_cypher(cypher_query)
rel_count = result_hash['data'].first.first
end

def load_relationship(id_or_index)
cypher_query = Deja::Bridge.get_relationship(id_or_index)
result_hash = Deja.execute_cypher(cypher_query)
Expand Down
21 changes: 19 additions & 2 deletions spec/node_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class HasHate < Relationship; end

describe Node do
after :each do
Deja.neo.execute_query("START n=node(*) MATCH n-[r?]->() WHERE ID(n) <> 0 DELETE r DELETE n")
#Deja.neo.execute_query("START n=node(*) MATCH n-[r?]->() WHERE ID(n) <> 0 DELETE r DELETE n")
end

before :each do
Expand All @@ -47,7 +47,6 @@ class HasHate < Relationship; end
@friends = FriendsWith.new(@first_node, @second_node).create
@hates = HasHate.new(@first_node, @third_node).create
@hates2 = HasHate.new(@first_node, @second_node).create

end

describe ".find" do
Expand Down Expand Up @@ -117,6 +116,14 @@ class HasHate < Relationship; end
full_node_type_test(first_node)
end
end

context "given a node id and a select filter" do
it "should return a node object with only the selected fields" do
first_node = Person.find(@first_node.id, :include => :none, :select => [:name, :type])
first_node.name.should_not be_nil
first_node.permalink.should be_nil
end
end
end

describe ".related_nodes" do
Expand Down Expand Up @@ -236,6 +243,16 @@ class HasHate < Relationship; end
end
end

describe ".connections" do
context "given a node with relationships" do
it "should return an accurate count of all relationships" do
@first_node.connections.should be 4
@second_node.connections.should be 3
@third_node.connections.should be 1
end
end
end

describe ".relationships" do
context "with a node having relationships" do
it "should return a list of relationships" do
Expand Down

0 comments on commit 0f6e38c

Please sign in to comment.