Skip to content
This repository has been archived by the owner on Oct 9, 2024. It is now read-only.

Commit

Permalink
Merge pull request neo4jrb#445 from andreasronge/query_proxy_refactor
Browse files Browse the repository at this point in the history
Query proxy refactor. Fixes multithreading issue and adds scope.
  • Loading branch information
subvertallchris committed Aug 28, 2014
2 parents 0555460 + 7df3db4 commit 1982dfc
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 17 deletions.
1 change: 1 addition & 0 deletions lib/neo4j.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
require 'neo4j/active_node/query/query_proxy'
require 'neo4j/active_node/query'
require 'neo4j/active_node/serialized_properties'
require 'neo4j/active_node/scope'
require 'neo4j/active_node'

require 'neo4j/active_node/orm_adapter'
Expand Down
1 change: 1 addition & 0 deletions lib/neo4j/active_node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ module ActiveNode
include Neo4j::ActiveNode::Labels
include Neo4j::ActiveNode::Rels
include Neo4j::ActiveNode::HasN
include Neo4j::ActiveNode::Scope

def neo4j_obj
_persisted_obj || raise("Tried to access native neo4j object on a non persisted object")
Expand Down
17 changes: 10 additions & 7 deletions lib/neo4j/active_node/has_n.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,16 @@ def #{name}_rels
end}, __FILE__, __LINE__)

instance_eval(%Q{
def #{name}(node = nil, rel = nil)
context = (self.query_proxy && self.query_proxy.context ? self.query_proxy.context : '#{self.name}') + '##{name}'
def #{name}(node = nil, rel = nil, proxy_obj = nil)
query_proxy = proxy_obj || Neo4j::ActiveNode::Query::QueryProxy.new(#{self.name}, nil, {
session: self.neo4j_session, query_proxy: query_proxy, context: '#{self.name}' + '##{name}'
})
context = (query_proxy && query_proxy.context ? query_proxy.context : '#{self.name}') + '##{name}'
Neo4j::ActiveNode::Query::QueryProxy.new(#{target_class_name},
@associations[#{name.inspect}],
{
session: self.neo4j_session,
query_proxy: self.query_proxy,
query_proxy: query_proxy,
node: node,
rel: rel,
context: context
Expand Down Expand Up @@ -98,7 +101,7 @@ def #{name}_rel
#{name}_query_proxy(rel: :r).pluck(:r).first
end
def #{name}(node = nil, rel = nil)
def #{name}(node = nil, rel = nil, query_proxy = nil)
return nil unless self.persisted?
#{name}_query_proxy(node: node, rel: rel, context: '#{self.name}##{name}').first
end}, __FILE__, __LINE__)
Expand All @@ -110,9 +113,9 @@ def #{name}_query_proxy(options = {})
{session: self.neo4j_session}.merge(options))
end
def #{name}(node = nil, rel = nil)
context = (self.query_proxy && self.query_proxy.context ? self.query_proxy.context : '#{self.name}') + '##{name}'
#{name}_query_proxy(query_proxy: self.query_proxy, node: node, rel: rel, context: context)
def #{name}(node = nil, rel = nil, query_proxy = nil)
context = (query_proxy && query_proxy.context ? query_proxy.context : '#{self.name}') + '##{name}'
#{name}_query_proxy(query_proxy: query_proxy, node: node, rel: rel, context: context)
end}, __FILE__, __LINE__)
end

Expand Down
4 changes: 2 additions & 2 deletions lib/neo4j/active_node/query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def query_as(var)
module ClassMethods
include Enumerable

attr_writer :query_proxy
#attr_writer :query_proxy

def each
self.query_as(:n).pluck(:n).each {|o| yield o }
Expand All @@ -49,7 +49,7 @@ def #{method}(*args)
end

def query_proxy(options = {})
@query_proxy || Neo4j::ActiveNode::Query::QueryProxy.new(self, nil, options)
Neo4j::ActiveNode::Query::QueryProxy.new(self, nil, options)
end

def as(node_var)
Expand Down
3 changes: 1 addition & 2 deletions lib/neo4j/active_node/query/query_proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,8 @@ def _rel_chain_var
private

def call_class_method(method_name, *args)
@model.query_proxy = self
args[2] = self
result = @model.send(method_name, *args)
@model.query_proxy = nil
result
end

Expand Down
87 changes: 87 additions & 0 deletions lib/neo4j/active_node/scope.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
module Neo4j::ActiveNode
module Scope
extend ActiveSupport::Concern

module ClassMethods

# Similar to ActiveRecord scope
#
# @example without argument
# class Person
# include Neo4j::ActiveNode
# property :name
# property :score
# has_many :out, :friends, model_class: self
# scope :top_students, -> { where(score: 42)}") }
# end
# Person.top_students.to_a
# a_person.friends.top_students.to_a
# a_person.friends.friends.top_students.to_a
# a_person.friends.top_students.friends.to_a
#
# @example Argument for scopes
# Person.scope :level, ->(num) { where(level_num: num)}
#
# @example Argument as a cypher identifier
# class Person
# include Neo4j::ActiveNode
# property :name
# property :score
# has_many :out, :friends, model_class: self
# scope :great_students, ->(identifier) { where("#{identifier}.score > 41") }
# end
# Person.as(:all_people).great_students(:all_people).to_a
#
# @see http://guides.rubyonrails.org/active_record_querying.html#scopes
def scope(name, proc)
_scope[name.to_sym] = proc

module_eval(%Q{
def #{name}(query_params=nil, _=nil, query_proxy=nil)
eval_context = ScopeEvalContext.new(self, query_proxy || self.class.query_proxy)
proc = self.class._scope[:"#{name}"]
self.class._call_scope_context(eval_context, query_params, proc)
end
}, __FILE__, __LINE__)

instance_eval(%Q{
def #{name}(query_params=nil, _=nil, query_proxy=nil)
eval_context = ScopeEvalContext.new(self, query_proxy || self.query_proxy)
proc = _scope[:"#{name}"]
_call_scope_context(eval_context, query_params, proc)
end
}, __FILE__, __LINE__)
end



def _scope
@_scope ||= {}
end

def _call_scope_context(eval_context, query_params, proc)
if proc.arity == 1
eval_context.instance_exec(query_params,&proc)
else
eval_context.instance_exec(&proc)
end
end


end

class ScopeEvalContext
def initialize(target, query_proxy)
@query_proxy = query_proxy
@target = target
end

Neo4j::ActiveNode::Query::QueryProxy::METHODS.each do |method|
module_eval(%Q{
def #{method}(params={})
(@query_proxy || @target).#{method}(params)
end}, __FILE__, __LINE__)
end
end
end
end
23 changes: 17 additions & 6 deletions spec/e2e/query_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,11 @@ class Lesson
has_many :in, :teachers, type: :teaching
has_many :in, :students, type: :is_enrolled_for

def self.max_level
self.query_as(:lesson).pluck('max(lesson.level)').first
def self.max_level(num=nil, _=nil, query_proxy=nil)
(query_proxy || self).query_as(:lesson).pluck('max(lesson.level)').first
end

def self.level(num)
self.where(level: num)
end
scope :level_number, ->(num) { where(level: num)}
end

class Student
Expand Down Expand Up @@ -190,7 +188,7 @@ class Teacher
end

it 'allows class methods on associations' do
samuels.lessons_teaching.level(101).to_a.should == [ss101]
samuels.lessons_teaching.level_number(101).to_a.should == [ss101]

samuels.lessons_teaching.max_level.should == 103
samuels.lessons_teaching.where(subject: 'Social Studies').max_level.should == 101
Expand Down Expand Up @@ -228,6 +226,19 @@ class Teacher
# Two variable assignments
it { othmar.lessons_teaching(:lesson).students(:student).where(age: 15).pluck(:lesson, :student).should == [[math101, danny]] }
end

describe 'on classes' do
before(:each) do
danny.lessons << math101
bobby.lessons << math101
sandra.lessons << ss101
end

context 'students, age 15, who are taking level 101 lessons' do
it { Student.as(:student).where(age: 15).lessons(:lesson).where(level: 101).pluck(:student).should == [danny] }
it { Student.where(age: 15).lessons(:lesson).where(level: '101').pluck(:lesson).should_not == [[othmar]] }
end
end
end

context 'othmar is also teaching math 201, brian is taking it' do
Expand Down
108 changes: 108 additions & 0 deletions spec/e2e/scope_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
require 'spec_helper'

#module Neo4j::ActiveNode::Scope

describe 'Neo4j::NodeMixin::Scope' do
class Person
include Neo4j::ActiveNode
property :name
property :score
property :level_num
has_many :out, :friends, model_class: self
end

before(:all) do
@a = Person.create name: 'a', score: 42, level_num: 1
@b = Person.create name: 'b', score: 42, level_num: 2
@b1 = Person.create name: 'b1', score: 42, level_num: 3
@b2 = Person.create name: 'b2', score: 42, level_num: 4

@a.friends << @b
@b.friends << @b1 << @b2
end


describe 'Person.scope :level, -> (num) { where(level: num)}' do
before(:all) do
Person.scope :level, ->(num) { where(level_num: num)}
end

describe 'Person.level(3)' do
it 'returns person with level 3' do
expect(Person.level(3).to_a).to eq([@b1])
end
end
end

describe 'Person.scope :in_order, -> { order(level: num)}' do
before(:all) do
Person.scope :in_order, ->(identifier){ order("#{identifier}.level_num DESC")}
end

describe 'Person.in_order' do
it 'returns person in order' do
expect(Person.as(:people).in_order(:people).to_a).to eq([@b2,@b1,@b,@a])
end
end
end

describe 'Person.scope :great_students, -> (identifier) { where("#{identifier}.score > 41")' do
before(:all) do
Person.scope :great_students, ->(identifier) { where("#{identifier}.score > 41") }
end


describe 'Person.top_students.to_a' do
subject do
Person.as(:foo).great_students(:foo).to_a
end
it { is_expected.to match_array([@a, @b, @b1, @b2])}
end

end

describe 'Person.scope :top_students, -> { where(score: 42)}' do
before(:all) do
Person.scope :top_students, -> { where(score: 42)}
end


describe 'Person.top_students.to_a' do
subject do
Person.top_students.to_a
end
it { is_expected.to match_array([@a, @b, @b1, @b2])}
end

describe 'person.friends.top_students.to_a' do
subject do
@a.friends.top_students.to_a
end
it { is_expected.to match_array([@b])}
end

describe 'person.friends.friend.top_students.to_a' do
subject do
@a.friends.friends.top_students.to_a
end
it { is_expected.to match_array([@b1, @b2])}
end

describe 'person.top_students.friends.to_a' do
subject do
@a.friends.top_students.friends.to_a
end
it { is_expected.to match_array([@b1, @b2])}
end

describe 'person.top_students.top_students.to_a' do
subject do
Person.top_students.friends.top_students.to_a
end
it { is_expected.to match_array([@b, @b1, @b2])}
end

end
#end
end

20 changes: 20 additions & 0 deletions spec/unit/scope_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require 'spec_helper'

describe Neo4j::ActiveNode::Scope do

let(:clazz) do
UniqueClass.create do
include Neo4j::ActiveNode
scope :active, -> do
where state: 'active'
end
end
end

it 'wraps the where method with a query_proxy' do
query_proxy = double(:query_proxy)
expect(query_proxy).to receive(:where).with({state: 'active'})
clazz.stub(:query_proxy).and_return(query_proxy)
clazz.active
end
end

0 comments on commit 1982dfc

Please sign in to comment.