Skip to content

Commit

Permalink
Merge pull request maxdemarzi#50 from kamranjon/batch
Browse files Browse the repository at this point in the history
adding order, limit, skip and cleaning up deja a bit
  • Loading branch information
kamranjon committed Oct 23, 2013
2 parents 3e44535 + 60528b3 commit 85d8d5b
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 70 deletions.
34 changes: 29 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ To implement a model using Deja, inherit from Deja::Node
class Person < Deja::Node
attr_accessor :name, :permalink, :type

relationship :invested_in
relationship :friends
relationship :hates
relationship :invested_in, :out => investment, :in => investor
relationship :friends, :out => friend
relationship :hates, :out => hates
end
```
Relationship Structure:
Expand All @@ -49,7 +49,7 @@ Interface:
### Loading Nodes:
To load a node with a given id, use the **find** method:
```ruby
Person.find(3)
Person.find(3, :include => :none) # does not include any related nodes
```
To load a person with a given id, and eager load a specific relationship, use the **:include** option:
```ruby
Expand Down Expand Up @@ -93,6 +93,30 @@ By default Deja supports lazy loading. To load a given relationship on the fly,
end
```

### Count:
To count the number of related nodes without actually fetching them, call the count method passing in the name of the relationship alias as an argument.
```ruby
node.count(:investments) # returns the total count of all investments
```

### Order:
To order by a given property on end nodes of a relationship, pass an order option into the relationship alias method.
```ruby
node.investments(:order => 'name ASC') # returns the related nodes ordered by name
```

### Limit:
To limit the results of relationship load query, pass in a limit argument.
```ruby
node.investments(:limit => 10) # returns only the first 10 investments
```

### Offset:
To offset the results of relationship load query, pass in a offset argument.
```ruby
node.investments(:offset => 5) # returns all investments offset by the first 5
```

### Index Methods:
Deja allows you to create indexes for both nodes and relationships.
```ruby
Expand All @@ -102,7 +126,7 @@ Deja allows you to create indexes for both nodes and relationships.

Deja also supports finding by index
```ruby
Person.find_by_index('idx_Person', :permalink, 'john_smith')
Person.find({:index => 'idx_Person', :key => :permalink, :value => 'john_smith'})
```
And for relationships
```ruby
Expand Down
58 changes: 43 additions & 15 deletions lib/deja/bridge.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,33 @@ def rel(id, context, return_root = true)
end
end

def attach_filter(result, filter = nil)
result.where{|n| n[:type] == filter.to_s.camelize} if filter
result
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
end

def filter(context, filter)
context.where{|n| n[:type] == filter.to_s.camelize}
end

def order(context, order_string)
property, order = order_string.split(' ')
if order == 'ASC'
context.asc(property.to_sym)
else
context.desc(property.to_sym)
end
end

def limit(context, size)
context.limit(size)
end

def skip(context, offset)
context.skip(offset)
end

def create_node(attributes = {})
Expand All @@ -39,7 +63,7 @@ def create_node(attributes = {})
end

def delete_node(id)
cypher{
cypher {
Deja::Bridge.node(id, self, false).del.both(rel().as(:r).del)
}
end
Expand Down Expand Up @@ -82,36 +106,40 @@ def delete_relationship(id)
}
end

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

def outgoing_rel(id, rels = nil, root = nil, filter = nil)
def single_node(id)
cypher { Deja::Bridge.node(id, self, true) }
end

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

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

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

Expand Down
10 changes: 3 additions & 7 deletions lib/deja/finders.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@ module Deja
module Finders
extend ActiveSupport::Concern
module ClassMethods
def find_by_index(index, key, value, options = {})
option_query { Deja::Query.load_node({:index => index, :key => key, :value => value}, options) }
end

def find_by_neo_id(neo_id, options = {})
option_query { Deja::Query.load_node(neo_id, options) }
def find(id, options = {})
option_query { Deja::Query.load_node(id, options) }
end

def where(key, value, options = {})
options[:include] ||= :all
find_by_index("idx_#{self.name}", key, value, options)
find({:index => "idx_#{self.name}", :key => key, :value => value}, options)
end

private
Expand Down
8 changes: 4 additions & 4 deletions lib/deja/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ def initialize(*args)
def define_alias_methods(rel, aliases)
self.class_eval do
if aliases[:out_plural] and aliases[:out_singular]
define_method aliases[:out_plural] do |filter = nil|
send(:related_nodes, {:include => rel, :direction => :out, :filter => filter})
define_method aliases[:out_plural] do |opts = {}|
send(:related_nodes, {:include => rel, :direction => :out}.merge(opts))
instance_variable_get("@#{rel}")
end

Expand All @@ -74,8 +74,8 @@ def define_alias_methods(rel, aliases)
end

if aliases[:in_plural] and aliases[:in_singular]
define_method aliases[:in_plural] do |filter = nil|
send(:related_nodes, {:include => rel, :direction => :in, :filter => filter})
define_method aliases[:in_plural] do |opts = {}|
send(:related_nodes, {:include => rel, :direction => :in}.merge(opts))
instance_variable_get("@#{rel}")
end

Expand Down
24 changes: 8 additions & 16 deletions lib/deja/query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@ class Query

class << self
def load_node(neo_id, options = {})
load_node_with_args(neo_id, options)
options[:return_root] ||= true
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 = {})
load_related_nodes_with_args(neo_id, options)
options[:return_root] ||= false
cypher_query = Deja::Bridge.get_nodes(neo_id, options)
result_hash = Deja.execute_cypher(cypher_query)
normalize(result_hash, :lazy)
end

def create_node(attributes = {})
Expand Down Expand Up @@ -62,20 +68,6 @@ def update_relationship(rel_id, attributes = {})
result_hash = Deja.execute_cypher(cypher_query)
result_hash["data"].empty? ? false : true
end

def load_node_with_args(neo_id, options)
options[:return_root] ||= true
cypher_query = Deja::Bridge.get_related_nodes(neo_id, options)
result_hash = Deja.execute_cypher(cypher_query)
normalize(result_hash)
end

def load_related_nodes_with_args(neo_id, options)
options[:return_root] ||= false
cypher_query = Deja::Bridge.get_related_nodes(neo_id, options)
result_hash = Deja.execute_cypher(cypher_query)
normalize(result_hash, :lazy)
end
end
end
end
4 changes: 2 additions & 2 deletions spec/bridge_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
end
end

describe ".update_node_by_id" do
describe ".update_node" do
context "given a node id" do
it "should return a cypher result" do
query = Deja::Bridge.update_node(1, {:some => :attr})
Expand Down Expand Up @@ -101,7 +101,7 @@
describe ".get_related_nodes" do
context "given a node id" do
it "should return a cypher result" do
query = Deja::Bridge.get_related_nodes(1, :include => :all)
query = Deja::Bridge.get_nodes(1, :include => :all)
query.should be_a(Neo4j::Cypher::Result)
end
end
Expand Down
31 changes: 16 additions & 15 deletions spec/finders_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,38 +42,39 @@ class HasHate < Relationship; end
@invested_in = InvestedIn.new(@first_node, @second_node).create
@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_by_neo_id" do
describe ".find" do
context "given a node id and no filters" do
before :each do
@node = Person.find_by_neo_id(@first_node.id, :include => :all)
@node = Person.find(@first_node.id, :include => :all)
end

it "should return a node and all related nodes by default" do
it "should return a node and all related nodes" do
@node.should_not_receive(:related_nodes)
@node.name.should eq(@first_node.name)
@node.permalink.should eq(@first_node.permalink)
end

it "calling invested_in should not call related_nodes" do
@node.should_not_receive(:related_nodes).with(:invested_in)
it "calling invested_in should call related_nodes" do
@node.should_receive(:related_nodes).and_call_original
@node.investment
end

it "calling invested_in should return an array of relNodeWrappers" do
it "calling invested_in should return an array of rel/node pairs" do
@node.investments.should be_a(Array)
@node.investments[0].should be_a(Array)
end
end

context "given a node id with an :invested_in argument" do
it "should not call related_nodes when eager loading" do
Person.find_by_neo_id(@first_node.id, :include => :invested_in).should_not_receive(:related_nodes)
Person.find(@first_node.id, :include => :invested_in).should_not_receive(:related_nodes)
end

it "should return only the invested_in relationship" do
first_node = Person.find_by_neo_id(@first_node.id, :include => :invested_in)
first_node = Person.find(@first_node.id, :include => :invested_in)
first_node.name.should eq(@first_node.name)
first_node.permalink.should eq(@first_node.permalink)
node_type_test(first_node, :investments)
Expand All @@ -82,11 +83,11 @@ class HasHate < Relationship; end

context "given a node id with an :invested_in and :friends argument" do
it "should not call related_nodes when eager loading multiple relations" do
first_node = Person.find_by_neo_id(@first_node.id, :include => [:invested_in, :friends]).should_not_receive(:related_nodes)
first_node = Person.find(@first_node.id, :include => [:invested_in, :friends]).should_not_receive(:related_nodes)
end

it "should return both relationships" do
first_node = Person.find_by_neo_id(@first_node.id, :include => [:invested_in, :friends])
first_node = Person.find(@first_node.id, :include => [:invested_in, :friends])
first_node.name.should eq(@first_node.name)
first_node.permalink.should eq(@first_node.permalink)
node_type_test(first_node, :investments)
Expand All @@ -96,7 +97,7 @@ class HasHate < Relationship; end

context "given a node id with a :none filter" do
it "should return a node and no related nodes" do
first_node = Person.find_by_neo_id(@first_node.id)
first_node = Person.find(@first_node.id, :include => :none)
first_node.should_receive(:related_nodes).at_least(:once).and_call_original
full_node_type_test(first_node)
end
Expand All @@ -106,7 +107,7 @@ class HasHate < Relationship; end
describe ".find" do
context "given a neo_id with associated nodes and :all argument" do
it "should return node objects with relationships" do
first_node = Person.find_by_neo_id(@first_node.id, :include => :all)
first_node = Person.find(@first_node.id, :include => :all)
first_node.investments.should_not be_nil
first_node.friends.should_not be_nil
first_node.hates.should_not be_nil
Expand All @@ -118,12 +119,12 @@ class HasHate < Relationship; end
describe ".related_nodes" do
context "on an instance of a single node" do
before :each do
@node = Person.find_by_neo_id(@first_node.id, :include => :all)
@node = Person.find(@first_node.id, :include => :none)
end

it "should not call related_nodes on already loaded relations" do
it "should call related_nodes on relations" do
@node.should_receive(:related_nodes).and_call_original
@node.investments(:person).each do |node, rel|
@node.investments(:filter => :person).each do |node, rel|
node.should be_a(Node)
rel.should be_a(Relationship)
end
Expand Down
Loading

0 comments on commit 85d8d5b

Please sign in to comment.