neo4j 4.0.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +10 -0
  3. data/Gemfile +6 -16
  4. data/README.md +5 -2
  5. data/bin/neo4j-jars +6 -6
  6. data/lib/neo4j.rb +13 -9
  7. data/lib/neo4j/active_node.rb +9 -9
  8. data/lib/neo4j/active_node/dependent.rb +11 -0
  9. data/lib/neo4j/active_node/dependent/association_methods.rb +28 -0
  10. data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +48 -0
  11. data/lib/neo4j/active_node/has_n.rb +124 -112
  12. data/lib/neo4j/active_node/has_n/association.rb +45 -30
  13. data/lib/neo4j/active_node/id_property.rb +22 -19
  14. data/lib/neo4j/active_node/initialize.rb +2 -4
  15. data/lib/neo4j/active_node/labels.rb +23 -22
  16. data/lib/neo4j/active_node/node_wrapper.rb +5 -8
  17. data/lib/neo4j/active_node/orm_adapter.rb +2 -4
  18. data/lib/neo4j/active_node/persistence.rb +5 -10
  19. data/lib/neo4j/active_node/property.rb +3 -4
  20. data/lib/neo4j/active_node/query.rb +27 -6
  21. data/lib/neo4j/active_node/query/query_proxy.rb +65 -110
  22. data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +67 -0
  23. data/lib/neo4j/active_node/query/query_proxy_find_in_batches.rb +0 -1
  24. data/lib/neo4j/active_node/query/query_proxy_methods.rb +29 -28
  25. data/lib/neo4j/active_node/query_methods.rb +6 -6
  26. data/lib/neo4j/active_node/reflection.rb +3 -2
  27. data/lib/neo4j/active_node/rels.rb +1 -1
  28. data/lib/neo4j/active_node/scope.rb +13 -8
  29. data/lib/neo4j/active_node/validations.rb +5 -6
  30. data/lib/neo4j/active_rel.rb +1 -2
  31. data/lib/neo4j/active_rel/callbacks.rb +3 -3
  32. data/lib/neo4j/active_rel/persistence.rb +9 -7
  33. data/lib/neo4j/active_rel/property.rb +12 -4
  34. data/lib/neo4j/active_rel/query.rb +6 -8
  35. data/lib/neo4j/active_rel/rel_wrapper.rb +0 -2
  36. data/lib/neo4j/active_rel/related_node.rb +4 -5
  37. data/lib/neo4j/active_rel/types.rb +4 -6
  38. data/lib/neo4j/active_rel/validations.rb +0 -1
  39. data/lib/neo4j/config.rb +11 -23
  40. data/lib/neo4j/core/query.rb +1 -1
  41. data/lib/neo4j/migration.rb +17 -18
  42. data/lib/neo4j/paginated.rb +4 -4
  43. data/lib/neo4j/railtie.rb +19 -19
  44. data/lib/neo4j/shared.rb +7 -3
  45. data/lib/neo4j/shared/callbacks.rb +15 -4
  46. data/lib/neo4j/shared/identity.rb +2 -2
  47. data/lib/neo4j/shared/persistence.rb +10 -21
  48. data/lib/neo4j/shared/property.rb +17 -30
  49. data/lib/neo4j/shared/rel_type_converters.rb +1 -3
  50. data/lib/neo4j/shared/type_converters.rb +13 -25
  51. data/lib/neo4j/shared/validations.rb +3 -3
  52. data/lib/neo4j/tasks/migration.rake +7 -7
  53. data/lib/neo4j/type_converters.rb +1 -1
  54. data/lib/neo4j/version.rb +1 -1
  55. data/lib/rails/generators/neo4j/model/model_generator.rb +16 -12
  56. data/lib/rails/generators/neo4j_generator.rb +18 -18
  57. data/neo4j.gemspec +22 -18
  58. metadata +103 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6f576c543973dba1fb67e8c000487496014fdf85
4
- data.tar.gz: 46a1e8b4d8f7fcc2c388f4791fecea6bf6fec6c9
3
+ metadata.gz: d5ecc7877175dbfe04e305498f2c5501818abf22
4
+ data.tar.gz: eec46ea5ca11ab14fbc8844c14264b332ec56de4
5
5
  SHA512:
6
- metadata.gz: 844a3cd13ec87c091166cf8aef59e4ea5876b8a18b6552167ff5f61aed62f8d04abcb2ba33b303d8c9285c6cd1251427af866b48b6ea72bcbaa62ca5dedfca31
7
- data.tar.gz: 3824b4d1fd649f99f2eaf8f6bc93289008388ce578375b865d10372f56f5e35dd5430d2a5f5b0bb7ab80a8b1314a2773515fc16909b7fa1ed4c45c0d42439845
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
- #gem 'neo4j-core', github: 'neo4jrb/neo4j-core', branch: 'master'
6
- #gem 'neo4j-core', git: 'https://github.com/neo4jrb/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 "rspec", '~> 2.0'
23
- gem "its"
24
- gem "test-unit"
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/andreasronge/neo4j)
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/andreasronge/neo4j) values will be accepted faster.
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
@@ -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 "Expected community, advanced, enterprise"
24
- exit -1
23
+ puts 'Expected community, advanced, enterprise'
24
+ exit(-1)
25
25
  end
26
26
 
27
27
  lib_dir = File.join(Dir.pwd, 'lib')
28
- raise "Expected a lib folder where to copy the jars file, mkdir #{lib_dir}? " unless File.exist?(lib_dir)
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}"
@@ -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 "neo4j-core"
12
+ require 'neo4j-core'
13
13
  require 'neo4j/core/query'
14
- require "active_model"
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'
@@ -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 || raise("Tried to access native neo4j object on a non persisted object")
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.has_id_property?
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
- return if indexed_properties.nil?
59
- self.indexed_properties.each { |property| other.index property }
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? ? { auto: :uuid } : id_prop[:type]
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.has_id_property?
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 (name && type && value)
78
+ id_property(name, type => value) if name && type && value
79
79
  end
80
80
  end
81
81
 
@@ -0,0 +1,11 @@
1
+ module Neo4j
2
+ module ActiveNode
3
+ module Dependent
4
+ def dependent_children
5
+ @dependent_children ||= []
6
+ end
7
+
8
+ attr_writer :called_by
9
+ end
10
+ end
11
+ end
@@ -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
- extend ActiveSupport::Concern
2
+ module HasN
3
+ extend ActiveSupport::Concern
4
4
 
5
- class NonPersistedNodeError < StandardError; end
5
+ class NonPersistedNodeError < StandardError; end
6
6
 
7
- # Clears out the association cache.
8
- def clear_association_cache #:nodoc:
9
- association_cache.clear if persisted?
10
- end
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
- # @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
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
- # 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 }
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
- def association_reflection(association_obj)
54
- self.class.reflect_on_association(association_obj.name)
55
- end
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
- # 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
63
- end
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
- module ClassMethods
66
- def has_association?(name)
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
- def associations
71
- @associations || {}
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
- # make sure the inherited classes inherit the <tt>_decl_rels</tt> hash
75
- def inherited(klass)
76
- klass.instance_variable_set(:@associations, associations.clone)
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
- super
79
- end
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
- def has_many(direction, name, options = {})
82
- name = name.to_sym
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 do |node|
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(%Q{
124
+ instance_eval(%{
121
125
  def #{name}(node = nil, rel = nil, proxy_obj = nil)
122
- query_proxy = proxy_obj || Neo4j::ActiveNode::Query::QueryProxy.new(::#{self.name}, nil, {
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(#{target_class_name},
127
- @associations[#{name.inspect}],
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
- association = Neo4j::ActiveNode::HasN::Association.new(:has_one, direction, name, options)
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.persisted?
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.persisted?
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(%Q{
176
+ instance_eval(%{
175
177
  def #{name}_query_proxy(options = {})
176
- Neo4j::ActiveNode::Query::QueryProxy.new(#{target_class_name},
177
- @associations[#{name.inspect}],
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