neo4j 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG +10 -0
- data/Gemfile +6 -16
- data/README.md +5 -2
- data/bin/neo4j-jars +6 -6
- data/lib/neo4j.rb +13 -9
- data/lib/neo4j/active_node.rb +9 -9
- data/lib/neo4j/active_node/dependent.rb +11 -0
- data/lib/neo4j/active_node/dependent/association_methods.rb +28 -0
- data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +48 -0
- data/lib/neo4j/active_node/has_n.rb +124 -112
- data/lib/neo4j/active_node/has_n/association.rb +45 -30
- data/lib/neo4j/active_node/id_property.rb +22 -19
- data/lib/neo4j/active_node/initialize.rb +2 -4
- data/lib/neo4j/active_node/labels.rb +23 -22
- data/lib/neo4j/active_node/node_wrapper.rb +5 -8
- data/lib/neo4j/active_node/orm_adapter.rb +2 -4
- data/lib/neo4j/active_node/persistence.rb +5 -10
- data/lib/neo4j/active_node/property.rb +3 -4
- data/lib/neo4j/active_node/query.rb +27 -6
- data/lib/neo4j/active_node/query/query_proxy.rb +65 -110
- data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +67 -0
- data/lib/neo4j/active_node/query/query_proxy_find_in_batches.rb +0 -1
- data/lib/neo4j/active_node/query/query_proxy_methods.rb +29 -28
- data/lib/neo4j/active_node/query_methods.rb +6 -6
- data/lib/neo4j/active_node/reflection.rb +3 -2
- data/lib/neo4j/active_node/rels.rb +1 -1
- data/lib/neo4j/active_node/scope.rb +13 -8
- data/lib/neo4j/active_node/validations.rb +5 -6
- data/lib/neo4j/active_rel.rb +1 -2
- data/lib/neo4j/active_rel/callbacks.rb +3 -3
- data/lib/neo4j/active_rel/persistence.rb +9 -7
- data/lib/neo4j/active_rel/property.rb +12 -4
- data/lib/neo4j/active_rel/query.rb +6 -8
- data/lib/neo4j/active_rel/rel_wrapper.rb +0 -2
- data/lib/neo4j/active_rel/related_node.rb +4 -5
- data/lib/neo4j/active_rel/types.rb +4 -6
- data/lib/neo4j/active_rel/validations.rb +0 -1
- data/lib/neo4j/config.rb +11 -23
- data/lib/neo4j/core/query.rb +1 -1
- data/lib/neo4j/migration.rb +17 -18
- data/lib/neo4j/paginated.rb +4 -4
- data/lib/neo4j/railtie.rb +19 -19
- data/lib/neo4j/shared.rb +7 -3
- data/lib/neo4j/shared/callbacks.rb +15 -4
- data/lib/neo4j/shared/identity.rb +2 -2
- data/lib/neo4j/shared/persistence.rb +10 -21
- data/lib/neo4j/shared/property.rb +17 -30
- data/lib/neo4j/shared/rel_type_converters.rb +1 -3
- data/lib/neo4j/shared/type_converters.rb +13 -25
- data/lib/neo4j/shared/validations.rb +3 -3
- data/lib/neo4j/tasks/migration.rake +7 -7
- data/lib/neo4j/type_converters.rb +1 -1
- data/lib/neo4j/version.rb +1 -1
- data/lib/rails/generators/neo4j/model/model_generator.rb +16 -12
- data/lib/rails/generators/neo4j_generator.rb +18 -18
- data/neo4j.gemspec +22 -18
- metadata +103 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d5ecc7877175dbfe04e305498f2c5501818abf22
|
4
|
+
data.tar.gz: eec46ea5ca11ab14fbc8844c14264b332ec56de4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a0514bacb5f60da1bba7e25b04a57fb016b0d7bae4b5fa4fb95a22e8ed746f541f63c6898864c34a174449318ed9216953e5faab84b78bdf97bdf25a9a02851f
|
7
|
+
data.tar.gz: d489717468051817dfb88e5fa878f15059e798bdf83b387ec0f3156e8cbf47371fefefae77b3d1b7b556d30c5d6e534f5ae2a38983e864d366b8d36bc0ef8adb
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
== 4.1.0
|
2
|
+
This release includes many performance fixes and new features. The most notable:
|
3
|
+
* Huge stylist cleanup/refactoring by Brian on the entire gem by Brian armed with Rubocop. See http://neo4jrb.io/blog/2014/12/29/stay-out-of-trouble.html.
|
4
|
+
* Every node create, update, and destroy is now wrapped in a transaction. See http://neo4jrb.io/blog/2015/01/06/transactions_everywhere.html.
|
5
|
+
* New `dependent` options for associations: `:delete`, `:destroy`, `:delete_orphans`, `:destroy_orphans`. See http://neo4jrb.io/blog/2015/01/07/association_dependent_options.html.
|
6
|
+
* New `unique: true` option for associations, `creates_unique_rel` class method for ActiveRel. Both of these will result in relationship creation Cypher using "CREATE UNIQUE" instead of "CREATE".
|
7
|
+
* Fixed an n+1 query issue during node creation and update.
|
8
|
+
* Dieter simplified some code related to frozen attributes. See https://github.com/neo4jrb/neo4j/pull/655.
|
9
|
+
We now have a new website online at http://neo4jrb.io! Keep an eye on it for news and blogs related to this and other projects.
|
10
|
+
|
1
11
|
== 4.0.0
|
2
12
|
* Change neo4j-core dependency from 3.1.0 to 4.0.0.
|
3
13
|
|
data/Gemfile
CHANGED
@@ -2,24 +2,14 @@ source 'http://rubygems.org'
|
|
2
2
|
|
3
3
|
gemspec
|
4
4
|
|
5
|
-
|
6
|
-
#gem 'neo4j-core',
|
7
|
-
#gem 'orm_adapter', :path => '../orm_adapter'
|
8
|
-
|
9
|
-
gem 'coveralls', require: false
|
10
|
-
|
11
|
-
|
12
|
-
group 'development' do
|
13
|
-
gem 'pry'
|
14
|
-
gem 'os' # for neo4j-server rake task
|
15
|
-
gem 'rake'
|
16
|
-
gem 'yard'
|
17
|
-
end
|
5
|
+
gem 'neo4j-core', github: 'neo4jrb/neo4j-core', branch: 'master'
|
6
|
+
# gem 'neo4j-core', path: '../neo4j-core'
|
18
7
|
|
19
8
|
group 'test' do
|
9
|
+
gem 'coveralls', require: false
|
20
10
|
gem 'simplecov', require: false
|
21
11
|
gem 'simplecov-html', require: false
|
22
|
-
gem
|
23
|
-
gem
|
24
|
-
gem
|
12
|
+
gem 'rspec', '~> 2.0'
|
13
|
+
gem 'its'
|
14
|
+
gem 'test-unit'
|
25
15
|
end
|
data/README.md
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# Welcome to Neo4j.rb
|
2
|
-
[](http://travis-ci.org/neo4jrb/neo4j) [](https://coveralls.io/r/neo4jrb/neo4j?branch=master) [](https://codeclimate.com/github/
|
2
|
+
[](http://travis-ci.org/neo4jrb/neo4j) [](https://coveralls.io/r/neo4jrb/neo4j?branch=master) [](https://codeclimate.com/github/neo4jrb/neo4j) [](https://www.pullreview.com/github/neo4jrb/neo4j/reviews/master)
|
3
3
|
|
4
4
|
Neo4j.rb is an Active Model compliant Ruby/JRuby wrapper for [the Neo4j graph database](http://www.neo4j.org/). It uses the [neo4j-core](https://github.com/neo4jrb/neo4j-core) and [active_attr](https://github.com/cgriego/active_attr) gems.
|
5
5
|
|
6
|
+
For a general overview see our website: http://neo4jrb.io/
|
7
|
+
|
6
8
|
Winner of a 2014 Graphie for "Best Community Contribution" at Neo4j's [Graph Connect](http://graphconnect.com) conference!
|
7
9
|

|
8
10
|
|
@@ -12,6 +14,7 @@ The master branch of this repo now holds stable builds of the v4 version of the
|
|
12
14
|
|
13
15
|
## Modern (3.X) Documentation
|
14
16
|
|
17
|
+
* [Website](http://neo4jrb.io/) (for an introduction)
|
15
18
|
* [Wiki](https://github.com/neo4jrb/neo4j/wiki/Neo4j.rb-v3-Introduction)
|
16
19
|
|
17
20
|
## Legacy (2.x) Documentation
|
@@ -34,7 +37,7 @@ The master branch of this repo now holds stable builds of the v4 version of the
|
|
34
37
|
|
35
38
|
## Contributing
|
36
39
|
|
37
|
-
Pull request with high test coverage and good [code climate](https://codeclimate.com/github/
|
40
|
+
Pull request with high test coverage and good [code climate](https://codeclimate.com/github/neo4jrb/neo4j) values will be accepted faster.
|
38
41
|
|
39
42
|
|
40
43
|
## License
|
data/bin/neo4j-jars
CHANGED
@@ -10,7 +10,7 @@ It copies all jar files which has been required (neo4j-community, neo4j-advanced
|
|
10
10
|
Usage: neo4j-jars <community|advanced|enterprise>
|
11
11
|
|
12
12
|
TEXT
|
13
|
-
exit
|
13
|
+
exit
|
14
14
|
end
|
15
15
|
|
16
16
|
if ARGV.include?('community')
|
@@ -20,14 +20,14 @@ elsif ARGV.include?('advanced')
|
|
20
20
|
elsif ARGV.include?('enterprise')
|
21
21
|
require 'neo4j-enterprise' # not really needed
|
22
22
|
else
|
23
|
-
puts
|
24
|
-
exit
|
23
|
+
puts 'Expected community, advanced, enterprise'
|
24
|
+
exit(-1)
|
25
25
|
end
|
26
26
|
|
27
27
|
lib_dir = File.join(Dir.pwd, 'lib')
|
28
|
-
|
28
|
+
fail "Expected a lib folder where to copy the jars file, mkdir #{lib_dir}? " unless File.exist?(lib_dir)
|
29
29
|
|
30
|
-
files = $CLASSPATH.find_all{|x| x =~ /\.jar$/}.collect{|y| y.sub('file:', '')}
|
31
|
-
files.each {|file| FileUtils.cp(file, lib_dir)}
|
30
|
+
files = $CLASSPATH.find_all { |x| x =~ /\.jar$/ }.collect { |y| y.sub('file:', '') }
|
31
|
+
files.each { |file| FileUtils.cp(file, lib_dir) }
|
32
32
|
|
33
33
|
puts "copied #{files.size} files to #{lib_dir}"
|
data/lib/neo4j.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
require 'neo4j/version'
|
2
2
|
|
3
|
-
#require "delegate"
|
4
|
-
#require "time"
|
5
|
-
#require "set"
|
3
|
+
# require "delegate"
|
4
|
+
# require "time"
|
5
|
+
# require "set"
|
6
6
|
#
|
7
|
-
#require "active_support/core_ext"
|
8
|
-
#require "active_support/json"
|
9
|
-
#require "active_support/inflector"
|
10
|
-
#require "active_support/time_with_zone"
|
7
|
+
# require "active_support/core_ext"
|
8
|
+
# require "active_support/json"
|
9
|
+
# require "active_support/inflector"
|
10
|
+
# require "active_support/time_with_zone"
|
11
11
|
|
12
|
-
require
|
12
|
+
require 'neo4j-core'
|
13
13
|
require 'neo4j/core/query'
|
14
|
-
require
|
14
|
+
require 'active_model'
|
15
15
|
require 'active_support/concern'
|
16
16
|
require 'active_support/core_ext/class/attribute.rb'
|
17
17
|
|
@@ -43,8 +43,12 @@ require 'neo4j/active_rel/related_node'
|
|
43
43
|
require 'neo4j/active_rel/types'
|
44
44
|
require 'neo4j/active_rel'
|
45
45
|
|
46
|
+
require 'neo4j/active_node/dependent'
|
47
|
+
require 'neo4j/active_node/dependent/query_proxy_methods'
|
48
|
+
require 'neo4j/active_node/dependent/association_methods'
|
46
49
|
require 'neo4j/active_node/query_methods'
|
47
50
|
require 'neo4j/active_node/query/query_proxy_methods'
|
51
|
+
require 'neo4j/active_node/query/query_proxy_enumerable'
|
48
52
|
require 'neo4j/active_node/query/query_proxy_find_in_batches'
|
49
53
|
require 'neo4j/active_node/labels'
|
50
54
|
require 'neo4j/active_node/id_property'
|
data/lib/neo4j/active_node.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
module Neo4j
|
2
|
-
|
3
2
|
# Makes Neo4j nodes and relationships behave like ActiveRecord objects.
|
4
3
|
# By including this module in your class it will create a mapping for the node to your ruby class
|
5
4
|
# by using a Neo4j Label with the same name as the class. When the node is loaded from the database it
|
@@ -39,24 +38,25 @@ module Neo4j
|
|
39
38
|
include Neo4j::ActiveNode::Rels
|
40
39
|
include Neo4j::ActiveNode::HasN
|
41
40
|
include Neo4j::ActiveNode::Scope
|
41
|
+
include Neo4j::ActiveNode::Dependent
|
42
42
|
|
43
43
|
def neo4j_obj
|
44
|
-
_persisted_obj ||
|
44
|
+
_persisted_obj || fail('Tried to access native neo4j object on a non persisted object')
|
45
45
|
end
|
46
46
|
|
47
47
|
included do
|
48
48
|
def self.inherited(other)
|
49
|
-
inherit_id_property(other) if self.
|
49
|
+
inherit_id_property(other) if self.id_property?
|
50
50
|
inherited_indexes(other) if self.respond_to?(:indexed_properties)
|
51
|
-
attributes.each_pair { |k,v| other.attributes[k] = v }
|
51
|
+
attributes.each_pair { |k, v| other.attributes[k] = v }
|
52
52
|
inherit_serialized_properties(other) if self.respond_to?(:serialized_properties)
|
53
53
|
Neo4j::ActiveNode::Labels.add_wrapped_class(other)
|
54
54
|
super
|
55
55
|
end
|
56
56
|
|
57
57
|
def self.inherited_indexes(other)
|
58
|
-
|
59
|
-
|
58
|
+
return if indexed_properties.nil?
|
59
|
+
self.indexed_properties.each { |property| other.index property }
|
60
60
|
end
|
61
61
|
|
62
62
|
def self.inherit_serialized_properties(other)
|
@@ -65,17 +65,17 @@ module Neo4j
|
|
65
65
|
|
66
66
|
def self.inherit_id_property(other)
|
67
67
|
id_prop = self.id_property_info
|
68
|
-
conf = id_prop[:type].empty? ? {
|
68
|
+
conf = id_prop[:type].empty? ? {auto: :uuid} : id_prop[:type]
|
69
69
|
other.id_property id_prop[:name], conf
|
70
70
|
end
|
71
71
|
|
72
72
|
Neo4j::Session.on_session_available do |_|
|
73
|
-
id_property :uuid, auto: :uuid unless self.
|
73
|
+
id_property :uuid, auto: :uuid unless self.id_property?
|
74
74
|
|
75
75
|
name = Neo4j::Config[:id_property]
|
76
76
|
type = Neo4j::Config[:id_property_type]
|
77
77
|
value = Neo4j::Config[:id_property_type_value]
|
78
|
-
id_property(name, type => value) if
|
78
|
+
id_property(name, type => value) if name && type && value
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module ActiveNode
|
3
|
+
module Dependent
|
4
|
+
module AssociationMethods
|
5
|
+
def add_destroy_callbacks(model)
|
6
|
+
return if dependent.nil?
|
7
|
+
# Bound value for procs
|
8
|
+
assoc = self
|
9
|
+
|
10
|
+
fn = case dependent
|
11
|
+
when :delete
|
12
|
+
proc { |o| o.send("#{assoc.name}_query_proxy").delete_all }
|
13
|
+
when :delete_orphans
|
14
|
+
proc { |o| o.as(:self).unique_nodes(assoc, :self, :n, :other_rel).query.delete(:n, :other_rel).exec }
|
15
|
+
when :destroy
|
16
|
+
proc { |o| o.send("#{assoc.name}_query_proxy").each_for_destruction(o) { |node| node.destroy } }
|
17
|
+
when :destroy_orphans
|
18
|
+
proc { |o| o.as(:self).unique_nodes(assoc, :self, :n, :other_rel).each_for_destruction(o) { |node| node.destroy } }
|
19
|
+
else
|
20
|
+
fail "Unknown dependent option #{dependent}"
|
21
|
+
end
|
22
|
+
|
23
|
+
model.before_destroy fn
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module ActiveNode
|
3
|
+
module Dependent
|
4
|
+
# methods used to resolve association dependencies
|
5
|
+
module QueryProxyMethods
|
6
|
+
# Used as part of `dependent: :destroy` and may not have any utility otherwise.
|
7
|
+
# It keeps track of the node responsible for a cascading `destroy` process.
|
8
|
+
# @param [#dependent_children] caller The node that called this method. Typically, we would use QueryProxy's `caller` method
|
9
|
+
# but this is not always available, so we require it explicitly.
|
10
|
+
def each_for_destruction(owning_node)
|
11
|
+
target = owning_node.called_by || owning_node
|
12
|
+
enumerable_query(identity).each do |obj|
|
13
|
+
# Cypher can return nil objects, check for empty results
|
14
|
+
next if !obj || target.dependent_children.include?(obj)
|
15
|
+
obj.called_by = target
|
16
|
+
target.dependent_children << obj
|
17
|
+
yield obj
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# This will match nodes who only have a single relationship of a given type.
|
22
|
+
# It's used by `dependent: :delete_orphans` and `dependent: :destroy_orphans` and may not have much utility otherwise.
|
23
|
+
# @param [Neo4j::ActiveNode::HasN::Association] association The Association object used throughout the match.
|
24
|
+
# @param [String, Symbol] other_node The identifier to use for the other end of the chain.
|
25
|
+
# @param [String, Symbol] other_rel The identifier to use for the relationship in the optional match.
|
26
|
+
# @return [Neo4j::ActiveNode::Query::QueryProxy]
|
27
|
+
def unique_nodes(association, self_identifer, other_node, other_rel)
|
28
|
+
fail 'Only supported by in QueryProxy chains started by an instance' unless caller
|
29
|
+
both_string = "-[:`#{association.relationship_type}`]-"
|
30
|
+
in_string = "<#{both_string}"
|
31
|
+
out_string = "#{both_string}>"
|
32
|
+
primary_rel, inverse_rel = case association.direction
|
33
|
+
when :out
|
34
|
+
[out_string, in_string]
|
35
|
+
when :in
|
36
|
+
[in_string, out_string]
|
37
|
+
else
|
38
|
+
[both_string, both_string]
|
39
|
+
end
|
40
|
+
|
41
|
+
query.with(identity).proxy_as_optional(caller.class, self_identifer)
|
42
|
+
.send("#{association.name}", other_node, other_rel)
|
43
|
+
.where("NOT EXISTS((#{self_identifer})#{primary_rel}(#{other_node})#{inverse_rel}())")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -1,135 +1,143 @@
|
|
1
1
|
module Neo4j::ActiveNode
|
2
|
-
module HasN
|
3
|
-
|
2
|
+
module HasN
|
3
|
+
extend ActiveSupport::Concern
|
4
4
|
|
5
|
-
|
5
|
+
class NonPersistedNodeError < StandardError; end
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
# Returns the current association cache. It is in the format
|
13
|
-
# { :association_name => { :hash_of_cypher_string => [collection] }}
|
14
|
-
def association_cache
|
15
|
-
@association_cache ||= {}
|
16
|
-
end
|
17
|
-
|
18
|
-
# Returns the specified association instance if it responds to :loaded?, nil otherwise.
|
19
|
-
# @param [String] cypher_string the cypher, with params, used for lookup
|
20
|
-
# @param [Enumerable] association_obj the HasN::Association object used to perform this query
|
21
|
-
def association_instance_get(cypher_string, association_obj)
|
22
|
-
return if association_cache.nil? || association_cache.empty?
|
23
|
-
lookup_obj = cypher_hash(cypher_string)
|
24
|
-
reflection = association_reflection(association_obj)
|
25
|
-
return if reflection.nil?
|
26
|
-
association_cache[reflection.name] ? association_cache[reflection.name][lookup_obj] : nil
|
27
|
-
end
|
7
|
+
# Clears out the association cache.
|
8
|
+
def clear_association_cache #:nodoc:
|
9
|
+
association_cache.clear if _persisted_obj
|
10
|
+
end
|
28
11
|
|
29
|
-
|
30
|
-
|
31
|
-
association_cache
|
32
|
-
|
12
|
+
# Returns the current association cache. It is in the format
|
13
|
+
# { :association_name => { :hash_of_cypher_string => [collection] }}
|
14
|
+
def association_cache
|
15
|
+
@association_cache ||= {}
|
16
|
+
end
|
33
17
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
reflection = association_reflection(association_obj)
|
44
|
-
return if reflection.nil?
|
45
|
-
if @association_cache[reflection.name]
|
46
|
-
@association_cache[reflection.name][cache_key] = collection_result
|
47
|
-
else
|
48
|
-
@association_cache[reflection.name] = { cache_key => collection_result }
|
18
|
+
# Returns the specified association instance if it responds to :loaded?, nil otherwise.
|
19
|
+
# @param [String] cypher_string the cypher, with params, used for lookup
|
20
|
+
# @param [Enumerable] association_obj the HasN::Association object used to perform this query
|
21
|
+
def association_instance_get(cypher_string, association_obj)
|
22
|
+
return if association_cache.nil? || association_cache.empty?
|
23
|
+
lookup_obj = cypher_hash(cypher_string)
|
24
|
+
reflection = association_reflection(association_obj)
|
25
|
+
return if reflection.nil?
|
26
|
+
association_cache[reflection.name] ? association_cache[reflection.name][lookup_obj] : nil
|
49
27
|
end
|
50
|
-
collection_result
|
51
|
-
end
|
52
28
|
|
53
|
-
|
54
|
-
|
55
|
-
|
29
|
+
# @return [Hash] A hash of all queries in @association_cache created from the association owning this reflection
|
30
|
+
def association_instance_get_by_reflection(reflection_name)
|
31
|
+
association_cache[reflection_name]
|
32
|
+
end
|
56
33
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
34
|
+
# Caches an association result. Unlike ActiveRecord, which stores results in @association_cache using { :association_name => [collection_result] },
|
35
|
+
# ActiveNode stores it using { :association_name => { :hash_string_of_cypher => [collection_result] }}.
|
36
|
+
# This is necessary because an association name by itself does not take into account :where, :limit, :order, etc,... so it's prone to error.
|
37
|
+
# @param [Neo4j::ActiveNode::Query::QueryProxy] query_proxy The QueryProxy object that resulted in this result
|
38
|
+
# @param [Enumerable] collection_result The result of the query after calling :each
|
39
|
+
# @param [Neo4j::ActiveNode::HasN::Association] association_obj The association traversed to create the result
|
40
|
+
def association_instance_set(cypher_string, collection_result, association_obj)
|
41
|
+
return collection_result if Neo4j::Transaction.current
|
42
|
+
cache_key = cypher_hash(cypher_string)
|
43
|
+
reflection = association_reflection(association_obj)
|
44
|
+
return if reflection.nil?
|
45
|
+
if @association_cache[reflection.name]
|
46
|
+
@association_cache[reflection.name][cache_key] = collection_result
|
47
|
+
else
|
48
|
+
@association_cache[reflection.name] = {cache_key => collection_result}
|
49
|
+
end
|
50
|
+
collection_result
|
51
|
+
end
|
64
52
|
|
65
|
-
|
66
|
-
|
67
|
-
!!associations[name.to_sym]
|
53
|
+
def association_reflection(association_obj)
|
54
|
+
self.class.reflect_on_association(association_obj.name)
|
68
55
|
end
|
69
56
|
|
70
|
-
|
71
|
-
|
57
|
+
# Uses the cypher generated by a QueryProxy object, complete with params, to generate a basic non-cryptographic hash
|
58
|
+
# for use in @association_cache.
|
59
|
+
# @param [String] the cypher used in the query
|
60
|
+
# @return [String] A basic hash of the query
|
61
|
+
def cypher_hash(cypher_string)
|
62
|
+
cypher_string.hash.abs
|
72
63
|
end
|
73
64
|
|
74
|
-
|
75
|
-
|
76
|
-
|
65
|
+
module ClassMethods
|
66
|
+
# :nocov:
|
67
|
+
# rubocop:disable Style/PredicateName
|
68
|
+
def has_association?(name)
|
69
|
+
ActiveSupport::Deprecation.warn 'has_association? is deprecated and may be removed from future releases, use association? instead.', caller
|
77
70
|
|
78
|
-
|
79
|
-
|
71
|
+
association?(name)
|
72
|
+
end
|
73
|
+
# rubocop:enable Style/PredicateName
|
74
|
+
# :nocov:
|
75
|
+
|
76
|
+
def association?(name)
|
77
|
+
!!associations[name.to_sym]
|
78
|
+
end
|
80
79
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
association = Neo4j::ActiveNode::HasN::Association.new(:has_many, direction, name, options)
|
85
|
-
@associations ||= {}
|
86
|
-
@associations[name] = association
|
87
|
-
|
88
|
-
target_class_name = association.target_class_name || 'nil'
|
89
|
-
create_reflection(:has_many, name, association)
|
90
|
-
|
91
|
-
# TODO: Make assignment more efficient? (don't delete nodes when they are being assigned)
|
92
|
-
module_eval(%Q{
|
93
|
-
def #{name}(node = nil, rel = nil)
|
94
|
-
return [].freeze unless self.persisted?
|
95
|
-
Neo4j::ActiveNode::Query::QueryProxy.new(#{target_class_name},
|
96
|
-
self.class.associations[#{name.inspect}],
|
97
|
-
{
|
98
|
-
session: self.class.neo4j_session,
|
99
|
-
start_object: self,
|
100
|
-
node: node,
|
101
|
-
rel: rel,
|
102
|
-
context: '#{self.name}##{name}',
|
103
|
-
caller: self
|
104
|
-
})
|
80
|
+
def associations
|
81
|
+
@associations || {}
|
82
|
+
end
|
105
83
|
|
84
|
+
# make sure the inherited classes inherit the <tt>_decl_rels</tt> hash
|
85
|
+
def inherited(klass)
|
86
|
+
klass.instance_variable_set(:@associations, associations.clone)
|
87
|
+
super
|
88
|
+
end
|
89
|
+
|
90
|
+
# rubocop:disable Style/PredicateName
|
91
|
+
def has_many(direction, name, options = {})
|
92
|
+
name = name.to_sym
|
93
|
+
association = build_association(:has_many, direction, name, options)
|
94
|
+
# TODO: Make assignment more efficient? (don't delete nodes when they are being assigned)
|
95
|
+
module_eval(%{
|
96
|
+
def #{name}(node = nil, rel = nil)
|
97
|
+
return [].freeze unless self._persisted_obj
|
98
|
+
#{name}_query_proxy(node: node, rel: rel)
|
99
|
+
end
|
100
|
+
|
101
|
+
def #{name}_query_proxy(options = {})
|
102
|
+
Neo4j::ActiveNode::Query::QueryProxy.new(#{association.target_class_name_or_nil},
|
103
|
+
self.class.associations[#{name.inspect}],
|
104
|
+
{
|
105
|
+
session: self.class.neo4j_session,
|
106
|
+
start_object: self,
|
107
|
+
node: options[:node],
|
108
|
+
rel: options[:rel],
|
109
|
+
context: '#{self.name}##{name}',
|
110
|
+
caller: self
|
111
|
+
})
|
106
112
|
end
|
107
113
|
|
108
114
|
def #{name}=(other_nodes)
|
109
115
|
#{name}(nil, :r).query_as(:n).delete(:r).exec
|
110
116
|
clear_association_cache
|
111
|
-
other_nodes.each
|
112
|
-
#{name} << node
|
113
|
-
end
|
117
|
+
other_nodes.each { |node| #{name} << node }
|
114
118
|
end
|
115
119
|
|
116
120
|
def #{name}_rels
|
117
121
|
#{name}(nil, :r).pluck(:r)
|
118
122
|
end}, __FILE__, __LINE__)
|
119
123
|
|
120
|
-
instance_eval(%
|
124
|
+
instance_eval(%{
|
121
125
|
def #{name}(node = nil, rel = nil, proxy_obj = nil)
|
122
|
-
|
126
|
+
#{name}_query_proxy(node: node, rel: rel, proxy_obj: proxy_obj)
|
127
|
+
end
|
128
|
+
|
129
|
+
def #{name}_query_proxy(options = {})
|
130
|
+
query_proxy = options[:proxy_obj] || Neo4j::ActiveNode::Query::QueryProxy.new(::#{self.name}, nil, {
|
123
131
|
session: self.neo4j_session, query_proxy: nil, context: '#{self.name}' + '##{name}'
|
124
132
|
})
|
125
133
|
context = (query_proxy && query_proxy.context ? query_proxy.context : '#{self.name}') + '##{name}'
|
126
|
-
Neo4j::ActiveNode::Query::QueryProxy.new(#{
|
127
|
-
|
134
|
+
Neo4j::ActiveNode::Query::QueryProxy.new(#{association.target_class_name_or_nil},
|
135
|
+
associations[#{name.inspect}],
|
128
136
|
{
|
129
137
|
session: self.neo4j_session,
|
130
138
|
query_proxy: query_proxy,
|
131
|
-
node: node,
|
132
|
-
rel: rel,
|
139
|
+
node: options[:node],
|
140
|
+
rel: options[:rel],
|
133
141
|
context: context,
|
134
142
|
optional: query_proxy.optional?,
|
135
143
|
caller: query_proxy.caller
|
@@ -139,17 +147,11 @@ module HasN
|
|
139
147
|
|
140
148
|
def has_one(direction, name, options = {})
|
141
149
|
name = name.to_sym
|
150
|
+
association = build_association(:has_one, direction, name, options)
|
142
151
|
|
143
|
-
|
144
|
-
@associations ||= {}
|
145
|
-
@associations[name] = association
|
146
|
-
|
147
|
-
target_class_name = association.target_class_name || 'nil'
|
148
|
-
create_reflection(:has_one, name, association)
|
149
|
-
|
150
|
-
module_eval(%Q{
|
152
|
+
module_eval(%{
|
151
153
|
def #{name}=(other_node)
|
152
|
-
raise(Neo4j::ActiveNode::HasN::NonPersistedNodeError, 'Unable to create relationship with non-persisted nodes') unless self.
|
154
|
+
raise(Neo4j::ActiveNode::HasN::NonPersistedNodeError, 'Unable to create relationship with non-persisted nodes') unless self._persisted_obj
|
153
155
|
clear_association_cache
|
154
156
|
#{name}_query_proxy(rel: :r).query_as(:n).delete(:r).exec
|
155
157
|
#{name}_query_proxy << other_node
|
@@ -164,17 +166,17 @@ module HasN
|
|
164
166
|
end
|
165
167
|
|
166
168
|
def #{name}(node = nil, rel = nil)
|
167
|
-
return nil unless self.
|
169
|
+
return nil unless self._persisted_obj
|
168
170
|
result = #{name}_query_proxy(node: node, rel: rel, context: '#{self.name}##{name}')
|
169
171
|
association = self.class.reflect_on_association(__method__)
|
170
172
|
query_return = association_instance_get(result.to_cypher_with_params, association)
|
171
173
|
query_return || association_instance_set(result.to_cypher_with_params, result.first, association)
|
172
174
|
end}, __FILE__, __LINE__)
|
173
175
|
|
174
|
-
instance_eval(%
|
176
|
+
instance_eval(%{
|
175
177
|
def #{name}_query_proxy(options = {})
|
176
|
-
Neo4j::ActiveNode::Query::QueryProxy.new(#{
|
177
|
-
|
178
|
+
Neo4j::ActiveNode::Query::QueryProxy.new(#{association.target_class_name_or_nil},
|
179
|
+
associations[#{name.inspect}],
|
178
180
|
{session: self.neo4j_session}.merge(options))
|
179
181
|
end
|
180
182
|
|
@@ -183,7 +185,17 @@ module HasN
|
|
183
185
|
#{name}_query_proxy(query_proxy: query_proxy, node: node, rel: rel, context: context)
|
184
186
|
end}, __FILE__, __LINE__)
|
185
187
|
end
|
188
|
+
# rubocop:enable Style/PredicateName
|
189
|
+
|
190
|
+
private
|
191
|
+
|
192
|
+
def build_association(macro, direction, name, options)
|
193
|
+
Neo4j::ActiveNode::HasN::Association.new(macro, direction, name, options).tap do |association|
|
194
|
+
@associations ||= {}
|
195
|
+
@associations[name] = association
|
196
|
+
create_reflection(macro, name, association, self)
|
197
|
+
end
|
198
|
+
end
|
186
199
|
end
|
187
200
|
end
|
188
|
-
|
189
201
|
end
|