neo4j 4.0.0 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Build Status](https://secure.travis-ci.org/neo4jrb/neo4j.png?branch=master)](http://travis-ci.org/neo4jrb/neo4j) [![Coverage Status](https://coveralls.io/repos/neo4jrb/neo4j/badge.png?branch=master)](https://coveralls.io/r/neo4jrb/neo4j?branch=master) [![Code Climate](https://codeclimate.com/github/neo4jrb/neo4j.png)](https://codeclimate.com/github/
|
2
|
+
[![Build Status](https://secure.travis-ci.org/neo4jrb/neo4j.png?branch=master)](http://travis-ci.org/neo4jrb/neo4j) [![Coverage Status](https://coveralls.io/repos/neo4jrb/neo4j/badge.png?branch=master)](https://coveralls.io/r/neo4jrb/neo4j?branch=master) [![Code Climate](https://codeclimate.com/github/neo4jrb/neo4j.png)](https://codeclimate.com/github/neo4jrb/neo4j) [![PullReview stats](https://www.pullreview.com/github/neo4jrb/neo4j/badges/master.svg?)](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
|
![2014 Graphie](http://i.imgur.com/CkOoTTYm.jpg)
|
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
|