From e08a83c296a13b1e6612262a184fcad57ee05aa2 Mon Sep 17 00:00:00 2001 From: Chris Grigg Date: Tue, 14 Jul 2015 10:48:03 -0400 Subject: [PATCH 01/11] basic support for unpersisted nodes, now to fix a few tests --- lib/neo4j/active_node/has_n.rb | 33 ++++- lib/neo4j/active_node/persistence.rb | 23 +++- lib/neo4j/active_node/query/query_proxy.rb | 9 +- spec/e2e/unpersisted_association_spec.rb | 138 +++++++++++++++++++++ 4 files changed, 192 insertions(+), 11 deletions(-) create mode 100644 spec/e2e/unpersisted_association_spec.rb diff --git a/lib/neo4j/active_node/has_n.rb b/lib/neo4j/active_node/has_n.rb index 4298485e7..fef4d12c9 100644 --- a/lib/neo4j/active_node/has_n.rb +++ b/lib/neo4j/active_node/has_n.rb @@ -144,6 +144,24 @@ def association_proxy(name, options = {}) end end + def pending_associations + @pending_associations ||= {} + end + + def pending_associations? + !@pending_associations.blank? + end + + def pending_associations_hash + return unless pending_associations? + {}.tap do |deferred_nodes| + pending_associations.each_pair do |cache_key, (association_name, operator)| + nodes_for_creation = self.persisted? ? association_proxy_cache[cache_key].select { |n| !n.persisted? } : association_proxy_cache[cache_key] + deferred_nodes[association_name] = [nodes_for_creation, operator] + end + end + end + private def fresh_association_proxy(name, options = {}, cached_result = nil) @@ -292,7 +310,7 @@ def has_one(direction, name, options = {}) # rubocop:disable Style/PredicateName def define_has_many_methods(name) define_method(name) do |node = nil, rel = nil, options = {}| - return [].freeze unless self._persisted_obj + # return [].freeze unless self._persisted_obj if node.is_a?(Hash) options = node @@ -348,11 +366,14 @@ def define_has_one_getter(name) def define_has_one_setter(name) define_method("#{name}=") do |other_node| - handle_non_persisted_node(other_node) - validate_persisted_for_association! - association_proxy_cache.clear # TODO: Should probably just clear for this association... - - Neo4j::Transaction.run { association_proxy(name).replace_with(other_node) } + if persisted? + other_node.save unless other_node.persisted? + association_proxy_cache.clear # TODO: Should probably just clear for this association... + Neo4j::Transaction.run { association_proxy(name).replace_with(other_node) } + # handle_non_persisted_node(other_node) + else + association_proxy(name).defer_create(other_node, {}, :'=') + end end end diff --git a/lib/neo4j/active_node/persistence.rb b/lib/neo4j/active_node/persistence.rb index ac194c7a8..8127712be 100644 --- a/lib/neo4j/active_node/persistence.rb +++ b/lib/neo4j/active_node/persistence.rb @@ -24,8 +24,25 @@ def initialize(record) # If any of the before_* callbacks return false the action is cancelled and save returns false. def save(*) update_magic_properties - association_proxy_cache.clear - create_or_update + cascade_save do + association_proxy_cache.clear + create_or_update + end + end + + def cascade_save + deferred_nodes = pending_associations_hash + result = yield + deferred_nodes.each_pair { |k, (v, o)| save_and_associate!(k, v, o) } if deferred_nodes + result + end + + def save_and_associate!(association_name, cache_key, operator) + cache_key.each do |node| + node.save if node.changed? || !node.persisted? + fail "Unable to defer node persistence, could not save #{node.inspect}" unless node.persisted? + operator == :<< ? send(association_name).send(operator, node) : send(:"#{association_name}=", node) + end end # Persist the object to the database. Validations and Callbacks are included @@ -53,7 +70,7 @@ def create_model(*) node = _create_node(properties) init_on_load(node, node.props) send_props(@relationship_props) if @relationship_props - @relationship_props = nil + @relationship_props = @deferred_nodes = nil true end diff --git a/lib/neo4j/active_node/query/query_proxy.rb b/lib/neo4j/active_node/query/query_proxy.rb index 312b125a8..c5d2fb608 100644 --- a/lib/neo4j/active_node/query/query_proxy.rb +++ b/lib/neo4j/active_node/query/query_proxy.rb @@ -159,8 +159,7 @@ def to_cypher_with_params(columns = [self.identity]) # To add a relationship for the node for the association on this QueryProxy def <<(other_node) - create(other_node, {}) - + @start_object._persisted_obj ? create(other_node, {}) : defer_create(other_node, {}, :<<) self end @@ -191,6 +190,12 @@ def create(other_nodes, properties) end end + def defer_create(other_nodes, _properties, operator) + key = [@association.name, [nil, nil, nil]].hash + @start_object.pending_associations[key] = [@association.name, operator] + @start_object.association_proxy_cache[key] = [other_nodes] + end + def _nodeify!(*args) other_nodes = [args].flatten!.map! do |arg| (arg.is_a?(Integer) || arg.is_a?(String)) ? @model.find_by(@model.id_property_name => arg) : arg diff --git a/spec/e2e/unpersisted_association_spec.rb b/spec/e2e/unpersisted_association_spec.rb new file mode 100644 index 000000000..d6254efc8 --- /dev/null +++ b/spec/e2e/unpersisted_association_spec.rb @@ -0,0 +1,138 @@ +require 'spec_helper' + +def save_and_expect_rel! + proc do + chris.save + reloaded_chris = Student.find_by(chris.props) + expect(reloaded_chris.lessons.include?(math)).to be_truthy + end +end + +describe 'association creation' do + before do + stub_const('Student', Class.new do + include Neo4j::ActiveNode + property :name + has_many :out, :lessons, type: 'ENROLLED_IN' + has_one :out, :favorite_class, type: 'FAVORITE_CLASS', model_class: 'Lesson' + end) + + stub_const('Lesson', Class.new do + include Neo4j::ActiveNode + property :subject + has_many :in, :students, origin: :lesson + end) + end + + before { [Student, Lesson].each(&:delete_all) } + + describe 'has_one' do + context 'with persisted nodes' do + let(:chris) { Student.create(name: 'Chris') } + let(:math) { Lesson.create(subject: 'Math') } + + it 'creates the relationship' do + expect { chris.favorite_class = math }.to change { chris.favorite_class } + end + end + + context 'between two unpersisted nodes' do + let!(:chris) { Student.new(name: 'Chris') } + let!(:math) { Lesson.new(subject: 'Math') } + + it 'does not raise an error' do + expect { chris.favorite_class = math }.not_to raise_error + end + + it 'is aware that there are pending associations' do + expect { chris.favorite_class = math }.to change { chris.pending_associations? } + end + + context 'upon save...' do + before do + chris.favorite_class = math + end + + it 'saves both nodes and creates the relationship' do + expect(math).to receive(:save).and_call_original + expect { chris.save }.to change { chris.favorite_class } + end + end + end + end + + describe 'has_many' do + context 'with persisted nodes' do + let(:chris) { Student.create(name: 'Chris') } + let(:math) { Lesson.create(subject: 'Math') } + + it 'creates a relationship' do + expect { chris.lessons << math }.to change { chris.lessons.count } + end + end + + context 'between two unpersisted nodes' do + let(:chris) { Student.new(name: 'Chris') } + let(:math) { Lesson.new(subject: 'Math') } + + it 'does not raise an error' do + expect { chris.lessons << math }.not_to raise_error + end + + it 'does not create new nodes' do + expect { chris.lessons << math }.not_to change { Student.count == 0 && Lesson.count == 0 } + end + + it 'is aware that there are cascading relationships' do + expect { chris.lessons << math }.to change { chris.pending_associations? } + end + + context 'upon save...' do + before { chris.lessons << math } + + it 'saves both nodes' do + expect { chris.save }.to change { math.exist? } + end + + it 'creates the relationship' do + save_and_expect_rel!.call + end + end + end + + context 'between unpersisted and persisted, unchanged nodes' do + let(:chris) { Student.new(name: 'Chris') } + let(:math) { Lesson.create(subject: 'math') } + + it 'does not save the unpersisted node' do + expect(chris).not_to receive(:save) + chris.lessons << math + end + + context 'upon save...' do + it 'only saves the unpersisted node' do + expect(math).not_to receive(:save) + chris.lessons << math + chris.save + end + + it 'creates the relationship' do + chris.lessons << math + save_and_expect_rel!.call + end + end + end + + context 'between persisted and unpersisted' do + let!(:chris) { Student.create(name: 'Chris') } + let!(:math) { Lesson.new(subject: 'math') } + + it 'saves the unpersisted node and immediately creates the rel' do + expect { chris.lessons << math }.to change { chris.lessons.include?(math) } + end + end + end + + describe 'has_one' do + end +end From 47955ad497a36ae8de9cb5fce26c6b573b0e6371 Mon Sep 17 00:00:00 2001 From: Chris Grigg Date: Tue, 14 Jul 2015 18:07:26 -0400 Subject: [PATCH 02/11] removes tests broken by handling of unpersisted node associations, extra transaction for deferred persistence --- lib/neo4j/active_node/has_n.rb | 2 +- lib/neo4j/active_node/persistence.rb | 8 +++++--- spec/e2e/has_many_spec.rb | 5 +---- spec/e2e/has_one_spec.rb | 19 ++----------------- spec/e2e/unpersisted_association_spec.rb | 14 ++++++++++++++ 5 files changed, 23 insertions(+), 25 deletions(-) diff --git a/lib/neo4j/active_node/has_n.rb b/lib/neo4j/active_node/has_n.rb index fef4d12c9..7f272d60d 100644 --- a/lib/neo4j/active_node/has_n.rb +++ b/lib/neo4j/active_node/has_n.rb @@ -367,7 +367,7 @@ def define_has_one_getter(name) def define_has_one_setter(name) define_method("#{name}=") do |other_node| if persisted? - other_node.save unless other_node.persisted? + other_node.save if other_node.respond_to?(:persisted?) && !other_node.persisted? association_proxy_cache.clear # TODO: Should probably just clear for this association... Neo4j::Transaction.run { association_proxy(name).replace_with(other_node) } # handle_non_persisted_node(other_node) diff --git a/lib/neo4j/active_node/persistence.rb b/lib/neo4j/active_node/persistence.rb index 8127712be..b5a946d45 100644 --- a/lib/neo4j/active_node/persistence.rb +++ b/lib/neo4j/active_node/persistence.rb @@ -32,9 +32,11 @@ def save(*) def cascade_save deferred_nodes = pending_associations_hash - result = yield - deferred_nodes.each_pair { |k, (v, o)| save_and_associate!(k, v, o) } if deferred_nodes - result + Neo4j::Transaction.run(!deferred_nodes.blank?) do + result = yield + deferred_nodes.each_pair { |k, (v, o)| save_and_associate!(k, v, o) } if deferred_nodes + result + end end def save_and_associate!(association_name, cache_key, operator) diff --git a/spec/e2e/has_many_spec.rb b/spec/e2e/has_many_spec.rb index 16aa0aa2d..9a538a308 100644 --- a/spec/e2e/has_many_spec.rb +++ b/spec/e2e/has_many_spec.rb @@ -35,15 +35,12 @@ it { is_expected.to include(:friends, :knows, :knows_me) } end + # See unpersisted_association_spec.rb for more related tests describe 'non-persisted node' do let(:unsaved_node) { Person.new } it 'returns an empty array' do expect(unsaved_node.friends).to eq [] end - - it 'has a frozen array' do - expect { unsaved_node.friends << friend1 }.to raise_error(RuntimeError) - end end describe 'unique: true' do diff --git a/spec/e2e/has_one_spec.rb b/spec/e2e/has_one_spec.rb index 3ff1dbac2..e4de035e5 100644 --- a/spec/e2e/has_one_spec.rb +++ b/spec/e2e/has_one_spec.rb @@ -19,28 +19,13 @@ end end + # See unpersisted_association_spec.rb for additional tests related to this context 'with non-persisted node' do let(:unsaved_node) { HasOneB.new } + it 'returns a nil object' do expect(unsaved_node.parent).to eq nil end - - it 'raises an error when trying to create a relationship' do - expect { unsaved_node.parent = HasOneA.create }.to raise_error(Neo4j::ActiveNode::HasN::NonPersistedNodeError) - end - - context 'with enabled auto-saving' do - let_config(:autosave_on_assignment) { true } - - it 'saves the node' do - expect { unsaved_node.parent = HasOneA.create }.to change(unsaved_node, :persisted?).from(false).to(true) - end - - it 'saves the associated node' do - other_node = HasOneA.new - expect { unsaved_node.parent = other_node }.to change(other_node, :persisted?).from(false).to(true) - end - end end it 'find the nodes via the has_one accessor' do diff --git a/spec/e2e/unpersisted_association_spec.rb b/spec/e2e/unpersisted_association_spec.rb index d6254efc8..b72335636 100644 --- a/spec/e2e/unpersisted_association_spec.rb +++ b/spec/e2e/unpersisted_association_spec.rb @@ -20,6 +20,7 @@ def save_and_expect_rel! stub_const('Lesson', Class.new do include Neo4j::ActiveNode property :subject + validates_presence_of :subject has_many :in, :students, origin: :lesson end) end @@ -97,6 +98,19 @@ def save_and_expect_rel! it 'creates the relationship' do save_and_expect_rel!.call end + + context 'with a save failure' do + let(:unnamed_lesson) { Lesson.new } + before do + expect(unnamed_lesson).not_to be_valid + chris.lessons << unnamed_lesson + end + + it 'rolls back the entire transaction' do + expect { chris.save }.to raise_error + expect(chris).not_to be_persisted + end + end end end From 78eda066f03e50c490e44793e2186c2f914ca255 Mon Sep 17 00:00:00 2001 From: Chris Grigg Date: Tue, 14 Jul 2015 18:46:50 -0400 Subject: [PATCH 03/11] make rubocop a tiny bit more lenient --- .rubocop_todo.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6f6933a3d..f96d3dd61 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -16,7 +16,7 @@ Metrics/AbcSize: # Offense count: 2 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 208 + Max: 214 # Offense count: 768 # Configuration parameters: AllowURI, URISchemes. From 97e181e8005c80472be279ce30a3721a8d8eb674 Mon Sep 17 00:00:00 2001 From: Chris Grigg Date: Tue, 14 Jul 2015 18:46:57 -0400 Subject: [PATCH 04/11] fix failing spec --- spec/e2e/unpersisted_association_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/e2e/unpersisted_association_spec.rb b/spec/e2e/unpersisted_association_spec.rb index b72335636..e7979f2d8 100644 --- a/spec/e2e/unpersisted_association_spec.rb +++ b/spec/e2e/unpersisted_association_spec.rb @@ -108,7 +108,7 @@ def save_and_expect_rel! it 'rolls back the entire transaction' do expect { chris.save }.to raise_error - expect(chris).not_to be_persisted + expect(chris).not_to exist end end end From 51d4adf78deb2a06221e5f601aa7b6cfcf105c92 Mon Sep 17 00:00:00 2001 From: Chris Grigg Date: Tue, 14 Jul 2015 18:47:59 -0400 Subject: [PATCH 05/11] remove extra lines from spec --- spec/e2e/unpersisted_association_spec.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/spec/e2e/unpersisted_association_spec.rb b/spec/e2e/unpersisted_association_spec.rb index e7979f2d8..30159074a 100644 --- a/spec/e2e/unpersisted_association_spec.rb +++ b/spec/e2e/unpersisted_association_spec.rb @@ -146,7 +146,4 @@ def save_and_expect_rel! end end end - - describe 'has_one' do - end end From 7120439e85f29f3129924e6d3f76f78320c072ff Mon Sep 17 00:00:00 2001 From: Chris Grigg Date: Tue, 14 Jul 2015 21:56:43 -0400 Subject: [PATCH 06/11] fix lazy association test for 2.1.7 --- spec/e2e/unpersisted_association_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/e2e/unpersisted_association_spec.rb b/spec/e2e/unpersisted_association_spec.rb index 30159074a..6c5d6a18d 100644 --- a/spec/e2e/unpersisted_association_spec.rb +++ b/spec/e2e/unpersisted_association_spec.rb @@ -142,7 +142,7 @@ def save_and_expect_rel! let!(:math) { Lesson.new(subject: 'math') } it 'saves the unpersisted node and immediately creates the rel' do - expect { chris.lessons << math }.to change { chris.lessons.include?(math) } + expect { chris.lessons << math }.to change { chris.lessons.where(subject: 'math').empty? } end end end From 6a834d56f48d7d97789575a5c9e29c31389eead2 Mon Sep 17 00:00:00 2001 From: Chris Grigg Date: Thu, 16 Jul 2015 23:14:57 -0400 Subject: [PATCH 07/11] fixed handling associations w/multiple unpersisted nodes, reorganized, tests --- lib/neo4j.rb | 2 + lib/neo4j/active_node.rb | 1 + lib/neo4j/active_node/has_n.rb | 28 ----------- lib/neo4j/active_node/persistence.rb | 32 ++++++------ lib/neo4j/active_node/query/query_proxy.rb | 7 +-- .../query/query_proxy_unpersisted.rb | 17 +++++++ lib/neo4j/active_node/unpersisted.rb | 49 +++++++++++++++++++ spec/e2e/unpersisted_association_spec.rb | 31 ++++++++++++ spec/unit/persistance_spec.rb | 1 + spec/unit/validation_spec.rb | 1 + 10 files changed, 117 insertions(+), 52 deletions(-) create mode 100644 lib/neo4j/active_node/query/query_proxy_unpersisted.rb create mode 100644 lib/neo4j/active_node/unpersisted.rb diff --git a/lib/neo4j.rb b/lib/neo4j.rb index 607ef7235..e795269bf 100644 --- a/lib/neo4j.rb +++ b/lib/neo4j.rb @@ -52,6 +52,7 @@ require 'neo4j/active_node/dependent/query_proxy_methods' require 'neo4j/active_node/dependent/association_methods' require 'neo4j/active_node/query_methods' +require 'neo4j/active_node/query/query_proxy_unpersisted' require 'neo4j/active_node/query/query_proxy_methods' require 'neo4j/active_node/query/query_proxy_enumerable' require 'neo4j/active_node/query/query_proxy_find_in_batches' @@ -66,6 +67,7 @@ require 'neo4j/active_node/validations' require 'neo4j/active_node/rels' require 'neo4j/active_node/reflection' +require 'neo4j/active_node/unpersisted' require 'neo4j/active_node/has_n' require 'neo4j/active_node/has_n/association_cypher_methods' require 'neo4j/active_node/has_n/association' diff --git a/lib/neo4j/active_node.rb b/lib/neo4j/active_node.rb index 69017708d..cbb60dc09 100644 --- a/lib/neo4j/active_node.rb +++ b/lib/neo4j/active_node.rb @@ -36,6 +36,7 @@ module ActiveNode include Neo4j::ActiveNode::Query include Neo4j::ActiveNode::Labels include Neo4j::ActiveNode::Rels + include Neo4j::ActiveNode::Unpersisted include Neo4j::ActiveNode::HasN include Neo4j::ActiveNode::Scope include Neo4j::ActiveNode::Dependent diff --git a/lib/neo4j/active_node/has_n.rb b/lib/neo4j/active_node/has_n.rb index 7f272d60d..963c07235 100644 --- a/lib/neo4j/active_node/has_n.rb +++ b/lib/neo4j/active_node/has_n.rb @@ -144,24 +144,6 @@ def association_proxy(name, options = {}) end end - def pending_associations - @pending_associations ||= {} - end - - def pending_associations? - !@pending_associations.blank? - end - - def pending_associations_hash - return unless pending_associations? - {}.tap do |deferred_nodes| - pending_associations.each_pair do |cache_key, (association_name, operator)| - nodes_for_creation = self.persisted? ? association_proxy_cache[cache_key].select { |n| !n.persisted? } : association_proxy_cache[cache_key] - deferred_nodes[association_name] = [nodes_for_creation, operator] - end - end - end - private def fresh_association_proxy(name, options = {}, cached_result = nil) @@ -175,16 +157,6 @@ def previous_association_proxy_results_by_previous_id(association_proxy, associa Hash[*query_proxy.pluck('ID(previous)', 'collect(next)').flatten(1)] end - def handle_non_persisted_node(other_node) - return unless Neo4j::Config[:autosave_on_assignment] - other_node.try(:save) - save - end - - def validate_persisted_for_association! - fail(Neo4j::ActiveNode::HasN::NonPersistedNodeError, 'Unable to create relationship with non-persisted nodes') unless self._persisted_obj - end - module ClassMethods # :nocov: # rubocop:disable Style/PredicateName diff --git a/lib/neo4j/active_node/persistence.rb b/lib/neo4j/active_node/persistence.rb index b5a946d45..c291a72dd 100644 --- a/lib/neo4j/active_node/persistence.rb +++ b/lib/neo4j/active_node/persistence.rb @@ -30,23 +30,6 @@ def save(*) end end - def cascade_save - deferred_nodes = pending_associations_hash - Neo4j::Transaction.run(!deferred_nodes.blank?) do - result = yield - deferred_nodes.each_pair { |k, (v, o)| save_and_associate!(k, v, o) } if deferred_nodes - result - end - end - - def save_and_associate!(association_name, cache_key, operator) - cache_key.each do |node| - node.save if node.changed? || !node.persisted? - fail "Unable to defer node persistence, could not save #{node.inspect}" unless node.persisted? - operator == :<< ? send(association_name).send(operator, node) : send(:"#{association_name}=", node) - end - end - # Persist the object to the database. Validations and Callbacks are included # by default but validation can be disabled by passing :validate => false # to #save! Creates a new transaction. @@ -85,12 +68,25 @@ def _create_node(*args) session.create_node(props, labels) end + private + + # The pending associations are cleared during the save process, so it's necessary to + # build the processable hash before it begins. If there are nodes and associations that + # need to be created after the node is saved, a new transaction is started. + def cascade_save + deferred_nodes = pending_associations_with_nodes + Neo4j::Transaction.run(!deferred_nodes.blank?) do + result = yield + process_unpersisted_nodes!(deferred_nodes) if deferred_nodes + result + end + end + module ClassMethods # Creates and saves a new node # @param [Hash] props the properties the new node should have def create(props = {}) association_props = extract_association_attributes!(props) || {} - new(props).tap do |obj| yield obj if block_given? obj.save diff --git a/lib/neo4j/active_node/query/query_proxy.rb b/lib/neo4j/active_node/query/query_proxy.rb index c5d2fb608..cb5ef524e 100644 --- a/lib/neo4j/active_node/query/query_proxy.rb +++ b/lib/neo4j/active_node/query/query_proxy.rb @@ -6,6 +6,7 @@ class QueryProxy include Neo4j::ActiveNode::Query::QueryProxyMethods include Neo4j::ActiveNode::Query::QueryProxyFindInBatches include Neo4j::ActiveNode::Query::QueryProxyEagerLoading + include Neo4j::ActiveNode::Query::QueryProxyUnpersisted include Neo4j::ActiveNode::Dependent::QueryProxyMethods # The most recent node to start a QueryProxy chain. @@ -190,12 +191,6 @@ def create(other_nodes, properties) end end - def defer_create(other_nodes, _properties, operator) - key = [@association.name, [nil, nil, nil]].hash - @start_object.pending_associations[key] = [@association.name, operator] - @start_object.association_proxy_cache[key] = [other_nodes] - end - def _nodeify!(*args) other_nodes = [args].flatten!.map! do |arg| (arg.is_a?(Integer) || arg.is_a?(String)) ? @model.find_by(@model.id_property_name => arg) : arg diff --git a/lib/neo4j/active_node/query/query_proxy_unpersisted.rb b/lib/neo4j/active_node/query/query_proxy_unpersisted.rb new file mode 100644 index 000000000..ec8db291f --- /dev/null +++ b/lib/neo4j/active_node/query/query_proxy_unpersisted.rb @@ -0,0 +1,17 @@ +module Neo4j + module ActiveNode + module Query + module QueryProxyUnpersisted + def defer_create(other_nodes, _properties, operator) + key = [@association.name, [nil, nil, nil]].hash + @start_object.pending_associations[key] = [@association.name, operator] + if @start_object.association_proxy_cache[key] + @start_object.association_proxy_cache[key] << other_nodes + else + @start_object.association_proxy_cache[key] = [other_nodes] + end + end + end + end + end +end diff --git a/lib/neo4j/active_node/unpersisted.rb b/lib/neo4j/active_node/unpersisted.rb new file mode 100644 index 000000000..3b329cddf --- /dev/null +++ b/lib/neo4j/active_node/unpersisted.rb @@ -0,0 +1,49 @@ +module Neo4j + module ActiveNode + module Unpersisted + def pending_associations + @pending_associations ||= {} + end + + def pending_associations? + !@pending_associations.blank? + end + + private + + # TODO: Change this method's name. + # Takes the pending_associations hash, which is in the format { cache_key => [:association_name, :association_operator]}, + # and returns them as { association_name => [[nodes_for_persistence], :operator] } + def pending_associations_with_nodes + return unless pending_associations? + {}.tap do |deferred_nodes| + pending_associations.each_pair do |cache_key, (association_name, operator)| + nodes_for_creation = self.persisted? ? association_proxy_cache[cache_key].select { |n| !n.persisted? } : association_proxy_cache[cache_key] + deferred_nodes[association_name] = [nodes_for_creation, operator] + end + end + end + + # @param [Hash] deferred_nodes A hash created by :pending_associations_with_nodes + def process_unpersisted_nodes!(deferred_nodes) + deferred_nodes.each_pair do |k, (v, o)| + save_and_associate_queue(k, v, o) + end + end + + + def save_and_associate_queue(association_name, node_queue, operator) + association_proc = proc { |node| save_and_associate_node(association_name, node, operator) } + node_queue.each do |element| + element.is_a?(Array) ? element.each { |node| association_proc.call(node) } : association_proc.call(element) + end + end + + def save_and_associate_node(association_name, node, operator) + node.save if node.changed? || !node.persisted? + fail "Unable to defer node persistence, could not save #{node.inspect}" unless node.persisted? + operator == :<< ? send(association_name).send(operator, node) : send(:"#{association_name}=", node) + end + end + end +end diff --git a/spec/e2e/unpersisted_association_spec.rb b/spec/e2e/unpersisted_association_spec.rb index 6c5d6a18d..e7281f774 100644 --- a/spec/e2e/unpersisted_association_spec.rb +++ b/spec/e2e/unpersisted_association_spec.rb @@ -114,6 +114,37 @@ def save_and_expect_rel! end end + context 'between many unpersisted nodes' do + let(:chris) { Student.new(name: 'Chris') } + let(:math) { Lesson.new(subject: 'Math') } + let(:science) { Lesson.new(subject: 'Science') } + let(:lessons) { [math, science] } + + context 'associated as an array' do + it 'delays the call to :save' do + expect(science).not_to receive(:save) + expect { chris.lessons << lessons }.not_to raise_error + end + + it 'calls save on each element' do + expect(math).to receive(:save).and_call_original + expect(science).to receive(:save).and_call_original + chris.lessons << lessons + chris.save + end + end + + context 'associated individually' do + it 'calls save on each node' do + expect(math).to receive(:save).and_call_original + expect(science).to receive(:save).and_call_original + chris.lessons << math + chris.lessons << science + chris.save + end + end + end + context 'between unpersisted and persisted, unchanged nodes' do let(:chris) { Student.new(name: 'Chris') } let(:math) { Lesson.create(subject: 'math') } diff --git a/spec/unit/persistance_spec.rb b/spec/unit/persistance_spec.rb index e07488d36..1ee6bf0be 100644 --- a/spec/unit/persistance_spec.rb +++ b/spec/unit/persistance_spec.rb @@ -6,6 +6,7 @@ let(:clazz) do Class.new do include Neo4j::ActiveNode::Persistence + include Neo4j::ActiveNode::Unpersisted include Neo4j::ActiveNode::HasN include Neo4j::ActiveNode::Property diff --git a/spec/unit/validation_spec.rb b/spec/unit/validation_spec.rb index 72a1f0bf6..6c32060a7 100644 --- a/spec/unit/validation_spec.rb +++ b/spec/unit/validation_spec.rb @@ -7,6 +7,7 @@ let(:clazz) do Class.new do include Neo4j::ActiveNode::Persistence + include Neo4j::ActiveNode::Unpersisted include Neo4j::ActiveNode::HasN include Neo4j::ActiveNode::Property include Neo4j::ActiveNode::Validations From 5551b148ac2561b5794dd023c316ade291e177ee Mon Sep 17 00:00:00 2001 From: Chris Grigg Date: Fri, 17 Jul 2015 14:29:03 -0400 Subject: [PATCH 08/11] undo rubocop cheating --- .rubocop_todo.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f96d3dd61..6f6933a3d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -16,7 +16,7 @@ Metrics/AbcSize: # Offense count: 2 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 214 + Max: 208 # Offense count: 768 # Configuration parameters: AllowURI, URISchemes. From 718460da31d366824c7cd87100e92c98a639fbdc Mon Sep 17 00:00:00 2001 From: Chris Grigg Date: Fri, 17 Jul 2015 14:41:03 -0400 Subject: [PATCH 09/11] fix changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7910851a5..31631f331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Added a `before_remove_const` method to clear cached models when Rails `reload!` is called. 5.0.1 included a workaround but this appears to cut to the core of the issue. See https://github.com/neo4jrb/neo4j/pull/855. +- To prevent errors, changing an index to constraint or constraint to index will drop the existing index/constraint before adding the new. +- Fixed `AssociationProxy#method_missing` so it properly raises errors. ### Added - Added ability to view `model_class` from `Association` class for `rails_admin` Neo4j adapter - QueryProxy `where` will now look for declared properties matching hash keys. When found, it will send the value through that property's type converter if the type matches the property's unconverted state. +- Improved handling of unpersisted nodes with associations. You can now use `<<` to create associations between unpersisted nodes. A `save` will cascade through unpersisted objects, creating nodes and rels along the way. See https://github.com/neo4jrb/neo4j/pull/871 ## [5.0.3] - 2015-07-14 From 98cc0e43ea6fe1919738c1743d09a7bcbb2f59d1 Mon Sep 17 00:00:00 2001 From: Chris Grigg Date: Fri, 17 Jul 2015 15:05:21 -0400 Subject: [PATCH 10/11] omgdocs for unpersisted nodes --- docs/Querying.rst | 44 ++++++++ docs/_exts/hidden_code_block.pyc | Bin 4382 -> 4350 bytes docs/api/Neo4j.rst | 28 +++-- docs/api/Neo4j/ActiveNode.rst | 54 +++++++-- docs/api/Neo4j/ActiveNode/Dependent.rst | 8 +- .../Dependent/QueryProxyMethods.rst | 2 +- docs/api/Neo4j/ActiveNode/HasN.rst | 4 - .../api/Neo4j/ActiveNode/HasN/Association.rst | 102 ++++++++++++++++- .../ActiveNode/HasN/AssociationProxy.rst | 24 ---- .../Neo4j/ActiveNode/HasN/ClassMethods.rst | 2 +- docs/api/Neo4j/ActiveNode/Initialize.rst | 6 - .../Neo4j/ActiveNode/Labels/ClassMethods.rst | 37 ++++++- docs/api/Neo4j/ActiveNode/Persistence.rst | 12 +- .../ActiveNode/Persistence/ClassMethods.rst | 3 +- docs/api/Neo4j/ActiveNode/Query.rst | 8 ++ .../api/Neo4j/ActiveNode/Query/QueryProxy.rst | 75 ++++++++++--- .../ActiveNode/Query/QueryProxy/Link.rst | 22 +++- .../Query/QueryProxyEagerLoading.rst | 104 ++++++++++++++++++ .../Query/QueryProxyFindInBatches.rst | 4 +- .../ActiveNode/Query/QueryProxyMethods.rst | 11 +- .../Query/QueryProxyUnpersisted.rst | 62 +++++++++++ docs/api/Neo4j/ActiveNode/Unpersisted.rst | 79 +++++++++++++ docs/api/Neo4j/ActiveRel.rst | 26 ++--- docs/api/Neo4j/ActiveRel/Initialize.rst | 23 +--- docs/api/Neo4j/ActiveRel/Persistence.rst | 2 +- .../ActiveRel/Persistence/ClassMethods.rst | 9 +- docs/api/Neo4j/ActiveRel/Property.rst | 1 - .../Neo4j/ActiveRel/Property/ClassMethods.rst | 17 ++- docs/api/Neo4j/ActiveRel/Validations.rst | 2 +- docs/api/Neo4j/Railtie.rst | 2 +- docs/api/Neo4j/Relationship/Wrapper.rst | 1 - docs/api/Neo4j/Shared.rst | 8 +- .../Neo4j/Shared/DeclaredPropertyManager.rst | 55 ++++++++- docs/api/Neo4j/Shared/Initialize.rst | 61 ++++++++++ docs/api/Neo4j/Shared/Persistence.rst | 2 +- .../Neo4j/Shared/Property/ClassMethods.rst | 20 +++- .../api/Neo4j/Shared/SerializedProperties.rst | 2 + .../SerializedProperties/ClassMethods.rst | 72 ++++++++++++ docs/api/Neo4j/Shared/TypeConverters.rst | 44 +++++++- .../Shared/TypeConverters/DateConverter.rst | 15 +++ .../TypeConverters/DateTimeConverter.rst | 17 ++- .../Shared/TypeConverters/JSONConverter.rst | 17 ++- .../Shared/TypeConverters/TimeConverter.rst | 20 +++- .../Shared/TypeConverters/YAMLConverter.rst | 17 ++- docs/api/Neo4j/Shared/Validations.rst | 2 +- docs/api/Neo4j/TypeConverters.rst | 23 +++- 46 files changed, 984 insertions(+), 165 deletions(-) create mode 100644 docs/api/Neo4j/ActiveNode/Query/QueryProxyEagerLoading.rst create mode 100644 docs/api/Neo4j/ActiveNode/Query/QueryProxyUnpersisted.rst create mode 100644 docs/api/Neo4j/ActiveNode/Unpersisted.rst create mode 100644 docs/api/Neo4j/Shared/Initialize.rst create mode 100644 docs/api/Neo4j/Shared/SerializedProperties/ClassMethods.rst diff --git a/docs/Querying.rst b/docs/Querying.rst index e9a8a8767..5ba15b6b0 100644 --- a/docs/Querying.rst +++ b/docs/Querying.rst @@ -76,6 +76,50 @@ Here we are limiting lessons by the ``start_date`` and ``end_date`` on the relat student.lessons.where(subject: 'Math').rel_where(grade: 85) + +Associations and Unpersisted Nodes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There is some special behavior around association creation when nodes are new and unsaved. Below are a few scenarios and their outcomes. + +When both nodes are persisted, associations changes using ``<<`` or ``=`` take place immediately -- no need to call save. + +.. code-block:: ruby + + student = Student.first + Lesson = Lesson.first + student.lessons << lesson + +In that case, the relationship would be created immediately. + +When the node on which the association is called is unpersisted, no changes are made to the database until ``save`` is called. Once that happens, a cascading save event will occur. + +.. code-block:: ruby + + student = Student.new + lesson = Lesson.first || Lesson.new + # This method will not save `student` or change relationships in the database: + student.lessons << lesson + +Once we call ``save`` on ``student``, two or three things will happen: + +* Since ``student`` is unpersisted, it will be saved +* If ``lesson`` is unpersisted, it will be saved +* Once both nodes are saved, the relationship will be created + +This process occurs within a transaction. If any part fails, an error will be raised, the transaction will fail, and no changes will be made to the database. + +Finally, if you try to associate an unpersisted node with a persisted node, the unpersisted node will be saved and the relationship will be created immediately: + +.. code-block:: ruby + + student = Student.first + lesson = Lesson.new + student.lessons << lesson + +In the above example, ``lesson`` would be saved and the relationship would be created immediately. There is no need to call ``save`` on ``student``. + + Parameters ~~~~~~~~~~ diff --git a/docs/_exts/hidden_code_block.pyc b/docs/_exts/hidden_code_block.pyc index 667770216304512312a7ea171e8200b174bca070..e8da4e5bc5b01e5d85a8e34a4a227b7a88834659 100644 GIT binary patch delta 303 zcmbQI^iPqU`7`00N_Bs{jB1 delta 334 zcmeyTI8TY4`7`_ + * `lib/neo4j/errors.rb:1 `_ * `lib/neo4j/shared.rb:1 `_ - * `lib/neo4j/config.rb:1 `_ + * `lib/neo4j/railtie.rb:4 `_ * `lib/neo4j/version.rb:1 `_ * `lib/neo4j/wrapper.rb:1 `_ - * `lib/neo4j/railtie.rb:4 `_ - * `lib/neo4j/paginated.rb:1 `_ * `lib/neo4j/migration.rb:3 `_ @@ -82,18 +82,18 @@ Files * `lib/neo4j/type_converters.rb:1 `_ - * `lib/neo4j/active_rel/types.rb:1 `_ - * `lib/neo4j/shared/callbacks.rb:1 `_ + * `lib/neo4j/active_rel/types.rb:1 `_ + * `lib/neo4j/active_node/query.rb:1 `_ * `lib/neo4j/shared/typecaster.rb:1 `_ - * `lib/neo4j/active_node/labels.rb:1 `_ - * `lib/neo4j/shared/validations.rb:1 `_ + * `lib/neo4j/active_node/labels.rb:1 `_ + * `lib/neo4j/active_rel/callbacks.rb:1 `_ * `lib/neo4j/active_node/callbacks.rb:1 `_ @@ -104,6 +104,8 @@ Files * `lib/neo4j/active_node/validations.rb:1 `_ + * `lib/neo4j/active_node/unpersisted.rb:1 `_ + * `lib/neo4j/active_node/orm_adapter.rb:3 `_ * `lib/neo4j/active_node/query_methods.rb:1 `_ @@ -120,9 +122,13 @@ Files * `lib/neo4j/active_node/query/query_proxy_enumerable.rb:1 `_ + * `lib/neo4j/active_node/dependent/query_proxy_methods.rb:1 `_ + + * `lib/neo4j/active_node/query/query_proxy_unpersisted.rb:1 `_ + * `lib/neo4j/active_node/dependent/association_methods.rb:1 `_ - * `lib/neo4j/active_node/dependent/query_proxy_methods.rb:1 `_ + * `lib/neo4j/active_node/query/query_proxy_eager_loading.rb:1 `_ * `lib/neo4j/active_node/has_n/association_cypher_methods.rb:1 `_ diff --git a/docs/api/Neo4j/ActiveNode.rst b/docs/api/Neo4j/ActiveNode.rst index 9663478e7..7828683b1 100644 --- a/docs/api/Neo4j/ActiveNode.rst +++ b/docs/api/Neo4j/ActiveNode.rst @@ -29,10 +29,10 @@ in a new object of that class. ActiveNode/Query - ActiveNode/HasN - ActiveNode/Scope + ActiveNode/HasN + ActiveNode/Labels ActiveNode/Property @@ -49,12 +49,14 @@ in a new object of that class. ActiveNode/Validations + ActiveNode/Persistence + + ActiveNode/Unpersisted + ActiveNode/ClassMethods ActiveNode/OrmAdapter - ActiveNode/Persistence - ActiveNode/QueryMethods @@ -84,10 +86,10 @@ Files * `lib/neo4j/active_node/query.rb:2 `_ - * `lib/neo4j/active_node/has_n.rb:1 `_ - * `lib/neo4j/active_node/scope.rb:3 `_ + * `lib/neo4j/active_node/has_n.rb:1 `_ + * `lib/neo4j/active_node/labels.rb:2 `_ * `lib/neo4j/active_node/property.rb:1 `_ @@ -102,10 +104,12 @@ Files * `lib/neo4j/active_node/validations.rb:2 `_ - * `lib/neo4j/active_node/orm_adapter.rb:4 `_ - * `lib/neo4j/active_node/persistence.rb:1 `_ + * `lib/neo4j/active_node/unpersisted.rb:2 `_ + + * `lib/neo4j/active_node/orm_adapter.rb:4 `_ + * `lib/neo4j/active_node/query_methods.rb:2 `_ * `lib/neo4j/active_node/has_n/association.rb:4 `_ @@ -118,9 +122,13 @@ Files * `lib/neo4j/active_node/query/query_proxy_enumerable.rb:2 `_ + * `lib/neo4j/active_node/dependent/query_proxy_methods.rb:2 `_ + + * `lib/neo4j/active_node/query/query_proxy_unpersisted.rb:2 `_ + * `lib/neo4j/active_node/dependent/association_methods.rb:2 `_ - * `lib/neo4j/active_node/dependent/query_proxy_methods.rb:2 `_ + * `lib/neo4j/active_node/query/query_proxy_eager_loading.rb:2 `_ * `lib/neo4j/active_node/has_n/association_cypher_methods.rb:2 `_ @@ -470,7 +478,7 @@ Methods .. hidden-code-block:: ruby def destroyed? - @_deleted || (!new_record? && !exist?) + !!@_deleted end @@ -669,6 +677,32 @@ Methods +.. _`Neo4j/ActiveNode#pending_associations`: + +**#pending_associations** + + + .. hidden-code-block:: ruby + + def pending_associations + @pending_associations ||= {} + end + + + +.. _`Neo4j/ActiveNode#pending_associations?`: + +**#pending_associations?** + + + .. hidden-code-block:: ruby + + def pending_associations? + !@pending_associations.blank? + end + + + .. _`Neo4j/ActiveNode#persisted?`: **#persisted?** diff --git a/docs/api/Neo4j/ActiveNode/Dependent.rst b/docs/api/Neo4j/ActiveNode/Dependent.rst index 4186900e6..796b56a5a 100644 --- a/docs/api/Neo4j/ActiveNode/Dependent.rst +++ b/docs/api/Neo4j/ActiveNode/Dependent.rst @@ -15,10 +15,10 @@ Dependent - Dependent/AssociationMethods - Dependent/QueryProxyMethods + Dependent/AssociationMethods + @@ -36,10 +36,10 @@ Files * `lib/neo4j/active_node/dependent.rb:3 `_ - * `lib/neo4j/active_node/dependent/association_methods.rb:3 `_ - * `lib/neo4j/active_node/dependent/query_proxy_methods.rb:3 `_ + * `lib/neo4j/active_node/dependent/association_methods.rb:3 `_ + diff --git a/docs/api/Neo4j/ActiveNode/Dependent/QueryProxyMethods.rst b/docs/api/Neo4j/ActiveNode/Dependent/QueryProxyMethods.rst index 829349b33..08baef9fa 100644 --- a/docs/api/Neo4j/ActiveNode/Dependent/QueryProxyMethods.rst +++ b/docs/api/Neo4j/ActiveNode/Dependent/QueryProxyMethods.rst @@ -77,7 +77,7 @@ Methods def unique_nodes(association, self_identifer, other_node, other_rel) fail 'Only supported by in QueryProxy chains started by an instance' unless source_object - + return false if send(association.name).empty? unique_nodes_query(association, self_identifer, other_node, other_rel) .proxy_as(association.target_class, other_node) end diff --git a/docs/api/Neo4j/ActiveNode/HasN.rst b/docs/api/Neo4j/ActiveNode/HasN.rst index 9bdc83877..a67727ce6 100644 --- a/docs/api/Neo4j/ActiveNode/HasN.rst +++ b/docs/api/Neo4j/ActiveNode/HasN.rst @@ -27,10 +27,6 @@ HasN - - - - HasN/ClassMethods HasN/Association diff --git a/docs/api/Neo4j/ActiveNode/HasN/Association.rst b/docs/api/Neo4j/ActiveNode/HasN/Association.rst index 2c1c9ba7a..f966c5325 100644 --- a/docs/api/Neo4j/ActiveNode/HasN/Association.rst +++ b/docs/api/Neo4j/ActiveNode/HasN/Association.rst @@ -66,6 +66,18 @@ Association + + + + + + + + + + + + @@ -205,6 +217,7 @@ Methods .. hidden-code-block:: ruby def derive_model_class + refresh_model_class! if pending_model_refresh? return @model_class unless @model_class.nil? return nil if relationship_class.nil? dir_class = direction == :in ? :from_class : :to_class @@ -275,6 +288,19 @@ Methods +.. _`Neo4j/ActiveNode/HasN/Association#model_class`: + +**#model_class** + Returns the value of attribute model_class + + .. hidden-code-block:: ruby + + def model_class + @model_class + end + + + .. _`Neo4j/ActiveNode/HasN/Association#name`: **#name** @@ -288,6 +314,19 @@ Methods +.. _`Neo4j/ActiveNode/HasN/Association#pending_model_refresh?`: + +**#pending_model_refresh?** + + + .. hidden-code-block:: ruby + + def pending_model_refresh? + !!@pending_model_refresh + end + + + .. _`Neo4j/ActiveNode/HasN/Association#perform_callback`: **#perform_callback** @@ -302,6 +341,33 @@ Methods +.. _`Neo4j/ActiveNode/HasN/Association#queue_model_refresh!`: + +**#queue_model_refresh!** + + + .. hidden-code-block:: ruby + + def queue_model_refresh! + @pending_model_refresh = true + end + + + +.. _`Neo4j/ActiveNode/HasN/Association#refresh_model_class!`: + +**#refresh_model_class!** + + + .. hidden-code-block:: ruby + + def refresh_model_class! + @pending_model_refresh = @target_classes_or_nil = nil + @model_class = @model_class.name.constantize if @model_class + end + + + .. _`Neo4j/ActiveNode/HasN/Association#relationship`: **#relationship** @@ -422,11 +488,7 @@ Methods def target_class_option(model_class) case model_class when nil - if @target_class_name_from_name - "#{association_model_namespace}::#{@target_class_name_from_name}" - else - @target_class_name_from_name - end + @target_class_name_from_name ? "#{association_model_namespace}::#{@target_class_name_from_name}" : @target_class_name_from_name when Array model_class.map { |sub_model_class| target_class_option(sub_model_class) } when false @@ -438,6 +500,19 @@ Methods +.. _`Neo4j/ActiveNode/HasN/Association#target_classes`: + +**#target_classes** + + + .. hidden-code-block:: ruby + + def target_classes + target_class_names.map(&:constantize) + end + + + .. _`Neo4j/ActiveNode/HasN/Association#target_classes_or_nil`: **#target_classes_or_nil** @@ -451,6 +526,23 @@ Methods +.. _`Neo4j/ActiveNode/HasN/Association#target_where_clause`: + +**#target_where_clause** + + + .. hidden-code-block:: ruby + + def target_where_clause + return if model_class == false + + Array.new(target_classes).map do |target_class| + "#{name}:#{target_class.mapped_label_name}" + end.join(' OR ') + end + + + .. _`Neo4j/ActiveNode/HasN/Association#type`: **#type** diff --git a/docs/api/Neo4j/ActiveNode/HasN/AssociationProxy.rst b/docs/api/Neo4j/ActiveNode/HasN/AssociationProxy.rst index e28724579..9390c4b3f 100644 --- a/docs/api/Neo4j/ActiveNode/HasN/AssociationProxy.rst +++ b/docs/api/Neo4j/ActiveNode/HasN/AssociationProxy.rst @@ -32,10 +32,6 @@ But also caches results and can have results cached on it - - - - @@ -217,25 +213,5 @@ Methods -.. _`Neo4j/ActiveNode/HasN/AssociationProxy#with_associations`: - -**#with_associations** - - - .. hidden-code-block:: ruby - - def with_associations(*spec) - return_object_clause = '[' + spec.map { |n| "collect(#{n})" }.join(',') + ']' - query_from_association_spec(spec).pluck(:previous, return_object_clause).map do |record, eager_data| - eager_data.each_with_index do |eager_records, index| - record.send(spec[index]).cache_result(eager_records) - end - - record - end - end - - - diff --git a/docs/api/Neo4j/ActiveNode/HasN/ClassMethods.rst b/docs/api/Neo4j/ActiveNode/HasN/ClassMethods.rst index 2f0caabd2..05d729521 100644 --- a/docs/api/Neo4j/ActiveNode/HasN/ClassMethods.rst +++ b/docs/api/Neo4j/ActiveNode/HasN/ClassMethods.rst @@ -64,7 +64,7 @@ Files - * `lib/neo4j/active_node/has_n.rb:188 `_ + * `lib/neo4j/active_node/has_n.rb:160 `_ diff --git a/docs/api/Neo4j/ActiveNode/Initialize.rst b/docs/api/Neo4j/ActiveNode/Initialize.rst index 4e5f68240..a3f45e1a8 100644 --- a/docs/api/Neo4j/ActiveNode/Initialize.rst +++ b/docs/api/Neo4j/ActiveNode/Initialize.rst @@ -15,12 +15,6 @@ Initialize - - - - - - diff --git a/docs/api/Neo4j/ActiveNode/Labels/ClassMethods.rst b/docs/api/Neo4j/ActiveNode/Labels/ClassMethods.rst index 0ebf4c088..546c55e7a 100644 --- a/docs/api/Neo4j/ActiveNode/Labels/ClassMethods.rst +++ b/docs/api/Neo4j/ActiveNode/Labels/ClassMethods.rst @@ -46,6 +46,10 @@ ClassMethods + + + + @@ -98,6 +102,21 @@ Methods +.. _`Neo4j/ActiveNode/Labels/ClassMethods#before_remove_const`: + +**#before_remove_const** + + + .. hidden-code-block:: ruby + + def before_remove_const + associations.each_value(&:queue_model_refresh!) + MODELS_FOR_LABELS_CACHE.clear + WRAPPED_CLASSES.clear + end + + + .. _`Neo4j/ActiveNode/Labels/ClassMethods#blank?`: **#blank?** @@ -122,6 +141,7 @@ Methods Neo4j::Session.on_session_available do |session| unless Neo4j::Label.constraint?(mapped_label_name, property) label = Neo4j::Label.create(mapped_label_name) + drop_index(property, label) if index?(property) label.create_constraint(property, constraints, session) end end @@ -179,7 +199,7 @@ Methods .. hidden-code-block:: ruby - def drop_constraint(property, constraint) + def drop_constraint(property, constraint = {type: :unique}) Neo4j::Session.on_session_available do |session| label = Neo4j::Label.create(mapped_label_name) label.drop_constraint(property, constraint, session) @@ -188,6 +208,20 @@ Methods +.. _`Neo4j/ActiveNode/Labels/ClassMethods#drop_index`: + +**#drop_index** + + + .. hidden-code-block:: ruby + + def drop_index(property, label = nil) + label_obj = label || Neo4j::Label.create(mapped_label_name) + label_obj.drop_index(property) + end + + + .. _`Neo4j/ActiveNode/Labels/ClassMethods#empty?`: **#empty?** @@ -320,6 +354,7 @@ Methods def index(property, conf = {}) Neo4j::Session.on_session_available do |_| + drop_constraint(property, type: :unique) if Neo4j::Label.constraint?(mapped_label_name, property) _index(property, conf) end indexed_properties.push property unless indexed_properties.include? property diff --git a/docs/api/Neo4j/ActiveNode/Persistence.rst b/docs/api/Neo4j/ActiveNode/Persistence.rst index a9f1527a2..680f66528 100644 --- a/docs/api/Neo4j/ActiveNode/Persistence.rst +++ b/docs/api/Neo4j/ActiveNode/Persistence.rst @@ -21,6 +21,8 @@ Persistence + + Persistence/ClassMethods @@ -119,7 +121,7 @@ Methods node = _create_node(properties) init_on_load(node, node.props) send_props(@relationship_props) if @relationship_props - @relationship_props = nil + @relationship_props = @deferred_nodes = nil true end @@ -175,7 +177,7 @@ Methods .. hidden-code-block:: ruby def destroyed? - @_deleted || (!new_record? && !exist?) + !!@_deleted end @@ -326,8 +328,10 @@ Methods def save(*) update_magic_properties - association_proxy_cache.clear - create_or_update + cascade_save do + association_proxy_cache.clear + create_or_update + end end diff --git a/docs/api/Neo4j/ActiveNode/Persistence/ClassMethods.rst b/docs/api/Neo4j/ActiveNode/Persistence/ClassMethods.rst index 9b3d25ff9..b8fff16a3 100644 --- a/docs/api/Neo4j/ActiveNode/Persistence/ClassMethods.rst +++ b/docs/api/Neo4j/ActiveNode/Persistence/ClassMethods.rst @@ -48,7 +48,7 @@ Files - * `lib/neo4j/active_node/persistence.rb:69 `_ + * `lib/neo4j/active_node/persistence.rb:85 `_ @@ -68,7 +68,6 @@ Methods def create(props = {}) association_props = extract_association_attributes!(props) || {} - new(props).tap do |obj| yield obj if block_given? obj.save diff --git a/docs/api/Neo4j/ActiveNode/Query.rst b/docs/api/Neo4j/ActiveNode/Query.rst index de9428e73..79f09d778 100644 --- a/docs/api/Neo4j/ActiveNode/Query.rst +++ b/docs/api/Neo4j/ActiveNode/Query.rst @@ -25,6 +25,10 @@ Helper methods to return Neo4j::Core::Query objects. A query object can be used Query/QueryProxyEnumerable + Query/QueryProxyUnpersisted + + Query/QueryProxyEagerLoading + Query/QueryProxyFindInBatches @@ -52,6 +56,10 @@ Files * `lib/neo4j/active_node/query/query_proxy_enumerable.rb:3 `_ + * `lib/neo4j/active_node/query/query_proxy_unpersisted.rb:3 `_ + + * `lib/neo4j/active_node/query/query_proxy_eager_loading.rb:3 `_ + * `lib/neo4j/active_node/query/query_proxy_find_in_batches.rb:3 `_ diff --git a/docs/api/Neo4j/ActiveNode/Query/QueryProxy.rst b/docs/api/Neo4j/ActiveNode/Query/QueryProxy.rst index 8bb4e675b..7ee7a12a2 100644 --- a/docs/api/Neo4j/ActiveNode/Query/QueryProxy.rst +++ b/docs/api/Neo4j/ActiveNode/Query/QueryProxy.rst @@ -157,8 +157,7 @@ Methods .. hidden-code-block:: ruby def <<(other_node) - create(other_node, {}) - + @start_object._persisted_obj ? create(other_node, {}) : defer_create(other_node, {}, :<<) self end @@ -389,6 +388,25 @@ Methods +.. _`Neo4j/ActiveNode/Query/QueryProxy#defer_create`: + +**#defer_create** + + + .. hidden-code-block:: ruby + + def defer_create(other_nodes, _properties, operator) + key = [@association.name, [nil, nil, nil]].hash + @start_object.pending_associations[key] = [@association.name, operator] + if @start_object.association_proxy_cache[key] + @start_object.association_proxy_cache[key] << other_nodes + else + @start_object.association_proxy_cache[key] = [other_nodes] + end + end + + + .. _`Neo4j/ActiveNode/Query/QueryProxy#delete`: **#delete** @@ -454,18 +472,23 @@ Methods .. _`Neo4j/ActiveNode/Query/QueryProxy#each`: **#each** - Just like every other each but it allows for optional params to support the versions that also return relationships. - The node and rel params are typically used by those other methods but there's nothing stopping you from - using `your_node.each(true, true)` instead of `your_node.each_with_rel`. + .. hidden-code-block:: ruby def each(node = true, rel = nil, &block) - pluck_vars = [] - pluck_vars << identity if node - pluck_vars << @rel_var if rel + if @associations_spec.size > 0 + return_object_clause = '[' + @associations_spec.map { |n| "collect(#{n})" }.join(',') + ']' + query_from_association_spec.pluck(identity, return_object_clause).map do |record, eager_data| + eager_data.each_with_index do |eager_records, index| + record.association_proxy(@associations_spec[index]).cache_result(eager_records) + end - pluck(*pluck_vars).each(&block) + block.call(record) + end + else + super + end end @@ -548,7 +571,7 @@ Methods fail(InvalidParameterError, ':exists? only accepts neo_ids') unless node_condition.is_a?(Integer) || node_condition.is_a?(Hash) || node_condition.nil? query_with_target(target) do |var| start_q = exists_query_start(node_condition, var) - start_q.query.return("COUNT(#{var}) AS count").first.count > 0 + start_q.query.reorder.return("COUNT(#{var}) AS count").first.count > 0 end end @@ -577,7 +600,7 @@ Methods def find_each(options = {}) query.return(identity).find_each(identity, @model.primary_key, options) do |result| - yield result + yield result.send(identity) end end @@ -592,7 +615,7 @@ Methods def find_in_batches(options = {}) query.return(identity).find_in_batches(identity, @model.primary_key, options) do |batch| - yield batch + yield batch.map(&:identity) end end @@ -667,9 +690,14 @@ Methods .. hidden-code-block:: ruby def include?(other, target = nil) - fail(InvalidParameterError, ':include? only accepts nodes') unless other.respond_to?(:neo_id) query_with_target(target) do |var| - self.where("ID(#{var}) = {other_node_id}").params(other_node_id: other.neo_id).query.return("count(#{var}) as count").first.count > 0 + where_filter = if other.respond_to?(:neo_id) + "ID(#{var}) = {other_node_id}" + else + "#{var}.#{association_id_key} = {other_node_id}" + end + node_id = other.respond_to?(:neo_id) ? other.neo_id : other + self.where(where_filter).params(other_node_id: node_id).query.return("count(#{var}) as count").first.count > 0 end end @@ -698,6 +726,7 @@ Methods @association = association @context = options.delete(:context) @options = options + @associations_spec = [] instance_vars_from_options!(options) @@ -1245,12 +1274,28 @@ Methods def unique_nodes(association, self_identifer, other_node, other_rel) fail 'Only supported by in QueryProxy chains started by an instance' unless source_object - + return false if send(association.name).empty? unique_nodes_query(association, self_identifer, other_node, other_rel) .proxy_as(association.target_class, other_node) end +.. _`Neo4j/ActiveNode/Query/QueryProxy#with_associations`: + +**#with_associations** + + + .. hidden-code-block:: ruby + + def with_associations(*spec) + new_link.tap do |new_query_proxy| + new_spec = new_query_proxy.instance_variable_get('@associations_spec') + spec + new_query_proxy.instance_variable_set('@associations_spec', new_spec) + end + end + + + diff --git a/docs/api/Neo4j/ActiveNode/Query/QueryProxy/Link.rst b/docs/api/Neo4j/ActiveNode/Query/QueryProxy/Link.rst index c1ee8e3e6..de7c11872 100644 --- a/docs/api/Neo4j/ActiveNode/Query/QueryProxy/Link.rst +++ b/docs/api/Neo4j/ActiveNode/Query/QueryProxy/Link.rst @@ -30,6 +30,8 @@ Link + + @@ -170,10 +172,9 @@ Methods arg.each do |key, value| if model && model.association?(key) result += for_association(key, value, "n#{node_num}", model) - node_num += 1 else - result << new(:where, ->(v, _) { {v => {key => value}} }) + result << new(:where, ->(v, _) { {v => {key => where_val(model, key, value)}} }) end end elsif arg.is_a?(String) @@ -226,10 +227,9 @@ Methods arg.each do |key, value| if model && model.association?(key) result += for_association(key, value, "n#{node_num}", model) - node_num += 1 else - result << new(:where, ->(v, _) { {v => {key => value}} }) + result << new(:where, ->(v, _) { {v => {key => where_val(model, key, value)}} }) end end elsif arg.is_a?(String) @@ -255,5 +255,19 @@ Methods +.. _`Neo4j/ActiveNode/Query/QueryProxy/Link.where_val`: + +**.where_val** + + + .. hidden-code-block:: ruby + + def where_val(model, key, value) + return value unless model + model.declared_property_manager.value_for_db(key, value) + end + + + diff --git a/docs/api/Neo4j/ActiveNode/Query/QueryProxyEagerLoading.rst b/docs/api/Neo4j/ActiveNode/Query/QueryProxyEagerLoading.rst new file mode 100644 index 000000000..c89f3efc1 --- /dev/null +++ b/docs/api/Neo4j/ActiveNode/Query/QueryProxyEagerLoading.rst @@ -0,0 +1,104 @@ +QueryProxyEagerLoading +====================== + + + + + + +.. toctree:: + :maxdepth: 3 + :titlesonly: + + + + + + + + + + + + + +Constants +--------- + + + + + +Files +----- + + + + * `lib/neo4j/active_node/query/query_proxy_eager_loading.rb:4 `_ + + + + + +Methods +------- + + + +.. _`Neo4j/ActiveNode/Query/QueryProxyEagerLoading#each`: + +**#each** + + + .. hidden-code-block:: ruby + + def each(node = true, rel = nil, &block) + if @associations_spec.size > 0 + return_object_clause = '[' + @associations_spec.map { |n| "collect(#{n})" }.join(',') + ']' + query_from_association_spec.pluck(identity, return_object_clause).map do |record, eager_data| + eager_data.each_with_index do |eager_records, index| + record.association_proxy(@associations_spec[index]).cache_result(eager_records) + end + + block.call(record) + end + else + super + end + end + + + +.. _`Neo4j/ActiveNode/Query/QueryProxyEagerLoading#initialize`: + +**#initialize** + + + .. hidden-code-block:: ruby + + def initialize(model, association = nil, options = {}) + @associations_spec = [] + + super + end + + + +.. _`Neo4j/ActiveNode/Query/QueryProxyEagerLoading#with_associations`: + +**#with_associations** + + + .. hidden-code-block:: ruby + + def with_associations(*spec) + new_link.tap do |new_query_proxy| + new_spec = new_query_proxy.instance_variable_get('@associations_spec') + spec + new_query_proxy.instance_variable_set('@associations_spec', new_spec) + end + end + + + + + diff --git a/docs/api/Neo4j/ActiveNode/Query/QueryProxyFindInBatches.rst b/docs/api/Neo4j/ActiveNode/Query/QueryProxyFindInBatches.rst index 1ebe6e153..a6f3e4076 100644 --- a/docs/api/Neo4j/ActiveNode/Query/QueryProxyFindInBatches.rst +++ b/docs/api/Neo4j/ActiveNode/Query/QueryProxyFindInBatches.rst @@ -50,7 +50,7 @@ Methods def find_each(options = {}) query.return(identity).find_each(identity, @model.primary_key, options) do |result| - yield result + yield result.send(identity) end end @@ -65,7 +65,7 @@ Methods def find_in_batches(options = {}) query.return(identity).find_in_batches(identity, @model.primary_key, options) do |batch| - yield batch + yield batch.map(&:identity) end end diff --git a/docs/api/Neo4j/ActiveNode/Query/QueryProxyMethods.rst b/docs/api/Neo4j/ActiveNode/Query/QueryProxyMethods.rst index 2cbf22852..455c2e8d7 100644 --- a/docs/api/Neo4j/ActiveNode/Query/QueryProxyMethods.rst +++ b/docs/api/Neo4j/ActiveNode/Query/QueryProxyMethods.rst @@ -262,7 +262,7 @@ Methods fail(InvalidParameterError, ':exists? only accepts neo_ids') unless node_condition.is_a?(Integer) || node_condition.is_a?(Hash) || node_condition.nil? query_with_target(target) do |var| start_q = exists_query_start(node_condition, var) - start_q.query.return("COUNT(#{var}) AS count").first.count > 0 + start_q.query.reorder.return("COUNT(#{var}) AS count").first.count > 0 end end @@ -338,9 +338,14 @@ Methods .. hidden-code-block:: ruby def include?(other, target = nil) - fail(InvalidParameterError, ':include? only accepts nodes') unless other.respond_to?(:neo_id) query_with_target(target) do |var| - self.where("ID(#{var}) = {other_node_id}").params(other_node_id: other.neo_id).query.return("count(#{var}) as count").first.count > 0 + where_filter = if other.respond_to?(:neo_id) + "ID(#{var}) = {other_node_id}" + else + "#{var}.#{association_id_key} = {other_node_id}" + end + node_id = other.respond_to?(:neo_id) ? other.neo_id : other + self.where(where_filter).params(other_node_id: node_id).query.return("count(#{var}) as count").first.count > 0 end end diff --git a/docs/api/Neo4j/ActiveNode/Query/QueryProxyUnpersisted.rst b/docs/api/Neo4j/ActiveNode/Query/QueryProxyUnpersisted.rst new file mode 100644 index 000000000..017b9416d --- /dev/null +++ b/docs/api/Neo4j/ActiveNode/Query/QueryProxyUnpersisted.rst @@ -0,0 +1,62 @@ +QueryProxyUnpersisted +===================== + + + + + + +.. toctree:: + :maxdepth: 3 + :titlesonly: + + + + + + + +Constants +--------- + + + + + +Files +----- + + + + * `lib/neo4j/active_node/query/query_proxy_unpersisted.rb:4 `_ + + + + + +Methods +------- + + + +.. _`Neo4j/ActiveNode/Query/QueryProxyUnpersisted#defer_create`: + +**#defer_create** + + + .. hidden-code-block:: ruby + + def defer_create(other_nodes, _properties, operator) + key = [@association.name, [nil, nil, nil]].hash + @start_object.pending_associations[key] = [@association.name, operator] + if @start_object.association_proxy_cache[key] + @start_object.association_proxy_cache[key] << other_nodes + else + @start_object.association_proxy_cache[key] = [other_nodes] + end + end + + + + + diff --git a/docs/api/Neo4j/ActiveNode/Unpersisted.rst b/docs/api/Neo4j/ActiveNode/Unpersisted.rst new file mode 100644 index 000000000..049947c58 --- /dev/null +++ b/docs/api/Neo4j/ActiveNode/Unpersisted.rst @@ -0,0 +1,79 @@ +Unpersisted +=========== + + + + + + +.. toctree:: + :maxdepth: 3 + :titlesonly: + + + + + + + + + + + + + + + + + +Constants +--------- + + + + + +Files +----- + + + + * `lib/neo4j/active_node/unpersisted.rb:3 `_ + + + + + +Methods +------- + + + +.. _`Neo4j/ActiveNode/Unpersisted#pending_associations`: + +**#pending_associations** + + + .. hidden-code-block:: ruby + + def pending_associations + @pending_associations ||= {} + end + + + +.. _`Neo4j/ActiveNode/Unpersisted#pending_associations?`: + +**#pending_associations?** + + + .. hidden-code-block:: ruby + + def pending_associations? + !@pending_associations.blank? + end + + + + + diff --git a/docs/api/Neo4j/ActiveRel.rst b/docs/api/Neo4j/ActiveRel.rst index b586e6cfe..787406260 100644 --- a/docs/api/Neo4j/ActiveRel.rst +++ b/docs/api/Neo4j/ActiveRel.rst @@ -18,20 +18,20 @@ See documentation at https://github.com/neo4jrb/neo4j/wiki/Neo4j%3A%3AActiveRel - ActiveRel/Types - ActiveRel/Query + ActiveRel/Types + ActiveRel/Property ActiveRel/Callbacks ActiveRel/Initialize - ActiveRel/Validations - ActiveRel/Persistence + ActiveRel/Validations + ActiveRel/RelatedNode @@ -59,20 +59,20 @@ Files * `lib/neo4j/active_rel.rb:4 `_ - * `lib/neo4j/active_rel/types.rb:2 `_ - * `lib/neo4j/active_rel/query.rb:1 `_ + * `lib/neo4j/active_rel/types.rb:2 `_ + * `lib/neo4j/active_rel/property.rb:1 `_ * `lib/neo4j/active_rel/callbacks.rb:2 `_ * `lib/neo4j/active_rel/initialize.rb:1 `_ - * `lib/neo4j/active_rel/validations.rb:2 `_ - * `lib/neo4j/active_rel/persistence.rb:1 `_ + * `lib/neo4j/active_rel/validations.rb:2 `_ + * `lib/neo4j/active_rel/related_node.rb:1 `_ @@ -257,7 +257,7 @@ Methods .. hidden-code-block:: ruby def destroyed? - @_deleted || (!new_record? && !exist?) + !!@_deleted end @@ -361,13 +361,11 @@ Methods .. hidden-code-block:: ruby def init_on_load(persisted_rel, from_node_id, to_node_id, type) - @_persisted_obj = persisted_rel @rel_type = type + @_persisted_obj = persisted_rel changed_attributes && changed_attributes.clear - @attributes = attributes.merge(persisted_rel.props.stringify_keys) + @attributes = convert_and_assign_attributes(persisted_rel.props) load_nodes(from_node_id, to_node_id) - self.default_properties = persisted_rel.props - @attributes = self.class.declared_property_manager.convert_properties_to(self, :ruby, @attributes) end @@ -742,7 +740,7 @@ Methods .. hidden-code-block:: ruby def valid?(context = nil) - context ||= (new_record? ? :create : :update) + context ||= (new_record? ? :create : :update) super(context) errors.empty? end diff --git a/docs/api/Neo4j/ActiveRel/Initialize.rst b/docs/api/Neo4j/ActiveRel/Initialize.rst index 695457aa1..1740a501a 100644 --- a/docs/api/Neo4j/ActiveRel/Initialize.rst +++ b/docs/api/Neo4j/ActiveRel/Initialize.rst @@ -13,10 +13,6 @@ Initialize - - - - @@ -43,19 +39,6 @@ Methods -.. _`Neo4j/ActiveRel/Initialize#_persisted_obj`: - -**#_persisted_obj** - Returns the value of attribute _persisted_obj - - .. hidden-code-block:: ruby - - def _persisted_obj - @_persisted_obj - end - - - .. _`Neo4j/ActiveRel/Initialize#init_on_load`: **#init_on_load** @@ -64,13 +47,11 @@ Methods .. hidden-code-block:: ruby def init_on_load(persisted_rel, from_node_id, to_node_id, type) - @_persisted_obj = persisted_rel @rel_type = type + @_persisted_obj = persisted_rel changed_attributes && changed_attributes.clear - @attributes = attributes.merge(persisted_rel.props.stringify_keys) + @attributes = convert_and_assign_attributes(persisted_rel.props) load_nodes(from_node_id, to_node_id) - self.default_properties = persisted_rel.props - @attributes = self.class.declared_property_manager.convert_properties_to(self, :ruby, @attributes) end diff --git a/docs/api/Neo4j/ActiveRel/Persistence.rst b/docs/api/Neo4j/ActiveRel/Persistence.rst index 8aaf33eb1..f7c3ee385 100644 --- a/docs/api/Neo4j/ActiveRel/Persistence.rst +++ b/docs/api/Neo4j/ActiveRel/Persistence.rst @@ -191,7 +191,7 @@ Methods .. hidden-code-block:: ruby def destroyed? - @_deleted || (!new_record? && !exist?) + !!@_deleted end diff --git a/docs/api/Neo4j/ActiveRel/Persistence/ClassMethods.rst b/docs/api/Neo4j/ActiveRel/Persistence/ClassMethods.rst index 135d80f5f..dd04cc6f7 100644 --- a/docs/api/Neo4j/ActiveRel/Persistence/ClassMethods.rst +++ b/docs/api/Neo4j/ActiveRel/Persistence/ClassMethods.rst @@ -68,7 +68,14 @@ Methods .. hidden-code-block:: ruby def create!(*args) - fail RelInvalidError, self unless create(*args) + props = args[0] || {} + relationship_props = extract_association_attributes!(props) || {} + new(props).tap do |obj| + relationship_props.each do |prop, value| + obj.send("#{prop}=", value) + end + obj.save! + end end diff --git a/docs/api/Neo4j/ActiveRel/Property.rst b/docs/api/Neo4j/ActiveRel/Property.rst index b6ccaafa6..88a90110f 100644 --- a/docs/api/Neo4j/ActiveRel/Property.rst +++ b/docs/api/Neo4j/ActiveRel/Property.rst @@ -139,7 +139,6 @@ Methods def initialize(attributes = {}, options = {}) super(attributes, options) - send_props(@relationship_props) unless @relationship_props.nil? end diff --git a/docs/api/Neo4j/ActiveRel/Property/ClassMethods.rst b/docs/api/Neo4j/ActiveRel/Property/ClassMethods.rst index d18299709..d88d6cb94 100644 --- a/docs/api/Neo4j/ActiveRel/Property/ClassMethods.rst +++ b/docs/api/Neo4j/ActiveRel/Property/ClassMethods.rst @@ -23,6 +23,8 @@ ClassMethods + + @@ -38,7 +40,7 @@ Files - * `lib/neo4j/active_rel/property.rb:28 `_ + * `lib/neo4j/active_rel/property.rb:27 `_ @@ -91,6 +93,19 @@ Methods +.. _`Neo4j/ActiveRel/Property/ClassMethods#id_property_name`: + +**#id_property_name** + + + .. hidden-code-block:: ruby + + def id_property_name + false + end + + + .. _`Neo4j/ActiveRel/Property/ClassMethods#load_entity`: **#load_entity** diff --git a/docs/api/Neo4j/ActiveRel/Validations.rst b/docs/api/Neo4j/ActiveRel/Validations.rst index cbcb8d97c..a75bb48f6 100644 --- a/docs/api/Neo4j/ActiveRel/Validations.rst +++ b/docs/api/Neo4j/ActiveRel/Validations.rst @@ -76,7 +76,7 @@ Methods .. hidden-code-block:: ruby def valid?(context = nil) - context ||= (new_record? ? :create : :update) + context ||= (new_record? ? :create : :update) super(context) errors.empty? end diff --git a/docs/api/Neo4j/Railtie.rst b/docs/api/Neo4j/Railtie.rst index a875a592e..e49094349 100644 --- a/docs/api/Neo4j/Railtie.rst +++ b/docs/api/Neo4j/Railtie.rst @@ -98,7 +98,7 @@ Methods return if @neo4j_cypher_logging_registered Neo4j::Server::CypherSession.log_with do |message| - Rails.logger.info message + puts message end @neo4j_cypher_logging_registered = true diff --git a/docs/api/Neo4j/Relationship/Wrapper.rst b/docs/api/Neo4j/Relationship/Wrapper.rst index 902bf3ab2..2fab47be5 100644 --- a/docs/api/Neo4j/Relationship/Wrapper.rst +++ b/docs/api/Neo4j/Relationship/Wrapper.rst @@ -52,7 +52,6 @@ Methods def wrapper props.symbolize_keys! - # return self unless props.is_a?(Hash) begin most_concrete_class = sorted_wrapper_classes wrapped_rel = most_concrete_class.constantize.new diff --git a/docs/api/Neo4j/Shared.rst b/docs/api/Neo4j/Shared.rst index ebdbbb080..423aedb87 100644 --- a/docs/api/Neo4j/Shared.rst +++ b/docs/api/Neo4j/Shared.rst @@ -23,10 +23,12 @@ Shared Shared/Typecaster - Shared/Persistence + Shared/Initialize Shared/Validations + Shared/Persistence + Shared/TypeConverters Shared/DeclaredProperty @@ -62,10 +64,12 @@ Files * `lib/neo4j/shared/typecaster.rb:2 `_ - * `lib/neo4j/shared/persistence.rb:1 `_ + * `lib/neo4j/shared/initialize.rb:1 `_ * `lib/neo4j/shared/validations.rb:2 `_ + * `lib/neo4j/shared/persistence.rb:1 `_ + * `lib/neo4j/shared/type_converters.rb:1 `_ * `lib/neo4j/shared/declared_property.rb:1 `_ diff --git a/docs/api/Neo4j/Shared/DeclaredPropertyManager.rst b/docs/api/Neo4j/Shared/DeclaredPropertyManager.rst index 87fdf2444..d3429f2da 100644 --- a/docs/api/Neo4j/Shared/DeclaredPropertyManager.rst +++ b/docs/api/Neo4j/Shared/DeclaredPropertyManager.rst @@ -53,6 +53,10 @@ See Neo4j::Shared::DeclaredProperty for definitions of the property objects them + + + + @@ -126,20 +130,33 @@ Methods .. _`Neo4j/Shared/DeclaredPropertyManager#convert_properties_to`: **#convert_properties_to** - + Modifies a hash's values to be of types acceptable to Neo4j or matching what the user defined using `type` in property definitions. .. hidden-code-block:: ruby def convert_properties_to(obj, medium, properties) - converter = medium == :ruby ? :to_ruby : :to_db - properties.each_pair do |attr, value| - next if skip_conversion?(obj, attr, value) - properties[attr] = converted_property(primitive_type(attr.to_sym), value, converter) + direction = medium == :ruby ? :to_ruby : :to_db + properties.each_pair do |key, value| + next if skip_conversion?(obj, key, value) + properties[key] = convert_property(key, value, direction) end end +.. _`Neo4j/Shared/DeclaredPropertyManager#convert_property`: + +**#convert_property** + Converts a single property from its current format to its db- or Ruby-expected output type. + + .. hidden-code-block:: ruby + + def convert_property(key, value, direction) + converted_property(primitive_type(key.to_sym), value, direction) + end + + + .. _`Neo4j/Shared/DeclaredPropertyManager#declared_property_defaults`: **#declared_property_defaults** @@ -342,5 +359,33 @@ Methods +.. _`Neo4j/Shared/DeclaredPropertyManager#value_for_db`: + +**#value_for_db** + + + .. hidden-code-block:: ruby + + def value_for_db(key, value) + return value unless registered_properties[key] + convert_property(key, value, :to_db) + end + + + +.. _`Neo4j/Shared/DeclaredPropertyManager#value_for_ruby`: + +**#value_for_ruby** + + + .. hidden-code-block:: ruby + + def value_for_ruby(key, value) + return unless registered_properties[key] + convert_property(key, value, :to_ruby) + end + + + diff --git a/docs/api/Neo4j/Shared/Initialize.rst b/docs/api/Neo4j/Shared/Initialize.rst new file mode 100644 index 000000000..44d1ccdbd --- /dev/null +++ b/docs/api/Neo4j/Shared/Initialize.rst @@ -0,0 +1,61 @@ +Initialize +========== + + + + + + +.. toctree:: + :maxdepth: 3 + :titlesonly: + + + + + + + + + + + +Constants +--------- + + + + + +Files +----- + + + + * `lib/neo4j/shared/initialize.rb:2 `_ + + + + + +Methods +------- + + + +.. _`Neo4j/Shared/Initialize#wrapper`: + +**#wrapper** + Implements the Neo4j::Node#wrapper and Neo4j::Relationship#wrapper method + so that we don't have to care if the node is wrapped or not. + + .. hidden-code-block:: ruby + + def wrapper + self + end + + + + + diff --git a/docs/api/Neo4j/Shared/Persistence.rst b/docs/api/Neo4j/Shared/Persistence.rst index 7d2e32480..0dfb8e6fd 100644 --- a/docs/api/Neo4j/Shared/Persistence.rst +++ b/docs/api/Neo4j/Shared/Persistence.rst @@ -180,7 +180,7 @@ Methods .. hidden-code-block:: ruby def destroyed? - @_deleted || (!new_record? && !exist?) + !!@_deleted end diff --git a/docs/api/Neo4j/Shared/Property/ClassMethods.rst b/docs/api/Neo4j/Shared/Property/ClassMethods.rst index 22d9c2d12..1e015c214 100644 --- a/docs/api/Neo4j/Shared/Property/ClassMethods.rst +++ b/docs/api/Neo4j/Shared/Property/ClassMethods.rst @@ -30,6 +30,8 @@ ClassMethods + + @@ -48,7 +50,7 @@ Files - * `lib/neo4j/shared/property.rb:123 `_ + * `lib/neo4j/shared/property.rb:124 `_ @@ -158,6 +160,22 @@ Methods +.. _`Neo4j/Shared/Property/ClassMethods#inherited`: + +**#inherited** + + + .. hidden-code-block:: ruby + + def inherited(other) + self.declared_property_manager.registered_properties.each_pair do |prop_key, prop_def| + other.property(prop_key, prop_def.options) + end + super + end + + + .. _`Neo4j/Shared/Property/ClassMethods#property`: **#property** diff --git a/docs/api/Neo4j/Shared/SerializedProperties.rst b/docs/api/Neo4j/Shared/SerializedProperties.rst index f936f2496..31a286117 100644 --- a/docs/api/Neo4j/Shared/SerializedProperties.rst +++ b/docs/api/Neo4j/Shared/SerializedProperties.rst @@ -19,6 +19,8 @@ See type_converters.rb for the serialization process. + SerializedProperties/ClassMethods + diff --git a/docs/api/Neo4j/Shared/SerializedProperties/ClassMethods.rst b/docs/api/Neo4j/Shared/SerializedProperties/ClassMethods.rst new file mode 100644 index 000000000..cd234a2f8 --- /dev/null +++ b/docs/api/Neo4j/Shared/SerializedProperties/ClassMethods.rst @@ -0,0 +1,72 @@ +ClassMethods +============ + + + + + + +.. toctree:: + :maxdepth: 3 + :titlesonly: + + + + + + + + + +Constants +--------- + + + + + +Files +----- + + + + * `lib/neo4j/shared/serialized_properties.rb:19 `_ + + + + + +Methods +------- + + + +.. _`Neo4j/Shared/SerializedProperties/ClassMethods#inherit_serialized_properties`: + +**#inherit_serialized_properties** + + + .. hidden-code-block:: ruby + + def inherit_serialized_properties(other) + other.serialized_properties = self.serialized_properties + end + + + +.. _`Neo4j/Shared/SerializedProperties/ClassMethods#inherited`: + +**#inherited** + + + .. hidden-code-block:: ruby + + def inherited(other) + inherit_serialized_properties(other) if self.respond_to?(:serialized_properties) + super + end + + + + + diff --git a/docs/api/Neo4j/Shared/TypeConverters.rst b/docs/api/Neo4j/Shared/TypeConverters.rst index 778fb030f..7cda641c2 100644 --- a/docs/api/Neo4j/Shared/TypeConverters.rst +++ b/docs/api/Neo4j/Shared/TypeConverters.rst @@ -36,6 +36,10 @@ TypeConverters + + + + @@ -68,20 +72,33 @@ Methods .. _`Neo4j/Shared/TypeConverters#convert_properties_to`: **#convert_properties_to** - + Modifies a hash's values to be of types acceptable to Neo4j or matching what the user defined using `type` in property definitions. .. hidden-code-block:: ruby def convert_properties_to(obj, medium, properties) - converter = medium == :ruby ? :to_ruby : :to_db - properties.each_pair do |attr, value| - next if skip_conversion?(obj, attr, value) - properties[attr] = converted_property(primitive_type(attr.to_sym), value, converter) + direction = medium == :ruby ? :to_ruby : :to_db + properties.each_pair do |key, value| + next if skip_conversion?(obj, key, value) + properties[key] = convert_property(key, value, direction) end end +.. _`Neo4j/Shared/TypeConverters#convert_property`: + +**#convert_property** + Converts a single property from its current format to its db- or Ruby-expected output type. + + .. hidden-code-block:: ruby + + def convert_property(key, value, direction) + converted_property(primitive_type(key.to_sym), value, direction) + end + + + .. _`Neo4j/Shared/TypeConverters.converters`: **.converters** @@ -95,6 +112,19 @@ Methods +.. _`Neo4j/Shared/TypeConverters.formatted_for_db?`: + +**.formatted_for_db?** + Attempts to determine whether conversion should be skipped because the object is already of the anticipated output type. + + .. hidden-code-block:: ruby + + def formatted_for_db?(found_converter, value) + found_converter.respond_to?(:db_type) && value.is_a?(found_converter.db_type) + end + + + .. _`Neo4j/Shared/TypeConverters.included`: **.included** @@ -136,7 +166,9 @@ Methods def to_other(direction, value, type) fail "Unknown direction given: #{direction}" unless direction == :to_ruby || direction == :to_db found_converter = converters[type] - found_converter ? found_converter.send(direction, value) : value + return value unless found_converter + return value if direction == :to_db && formatted_for_db?(found_converter, value) + found_converter.send(direction, value) end diff --git a/docs/api/Neo4j/Shared/TypeConverters/DateConverter.rst b/docs/api/Neo4j/Shared/TypeConverters/DateConverter.rst index 6fe724dbd..8bceb6257 100644 --- a/docs/api/Neo4j/Shared/TypeConverters/DateConverter.rst +++ b/docs/api/Neo4j/Shared/TypeConverters/DateConverter.rst @@ -17,6 +17,8 @@ Converts Date objects to Java long types. Must be timezone UTC. + + @@ -56,6 +58,19 @@ Methods +.. _`Neo4j/Shared/TypeConverters/DateConverter.db_type`: + +**.db_type** + + + .. hidden-code-block:: ruby + + def db_type + Integer + end + + + .. _`Neo4j/Shared/TypeConverters/DateConverter.to_db`: **.to_db** diff --git a/docs/api/Neo4j/Shared/TypeConverters/DateTimeConverter.rst b/docs/api/Neo4j/Shared/TypeConverters/DateTimeConverter.rst index e9eedbc71..15401ecb9 100644 --- a/docs/api/Neo4j/Shared/TypeConverters/DateTimeConverter.rst +++ b/docs/api/Neo4j/Shared/TypeConverters/DateTimeConverter.rst @@ -19,6 +19,8 @@ Converts DateTime objects to and from Java long types. Must be timezone UTC. + + @@ -36,7 +38,7 @@ Files - * `lib/neo4j/shared/type_converters.rb:21 `_ + * `lib/neo4j/shared/type_converters.rb:25 `_ @@ -60,6 +62,19 @@ Methods +.. _`Neo4j/Shared/TypeConverters/DateTimeConverter.db_type`: + +**.db_type** + + + .. hidden-code-block:: ruby + + def db_type + Integer + end + + + .. _`Neo4j/Shared/TypeConverters/DateTimeConverter.to_db`: **.to_db** diff --git a/docs/api/Neo4j/Shared/TypeConverters/JSONConverter.rst b/docs/api/Neo4j/Shared/TypeConverters/JSONConverter.rst index 5d356e714..9fbcd3c72 100644 --- a/docs/api/Neo4j/Shared/TypeConverters/JSONConverter.rst +++ b/docs/api/Neo4j/Shared/TypeConverters/JSONConverter.rst @@ -17,6 +17,8 @@ Converts hash to/from JSON + + @@ -32,7 +34,7 @@ Files - * `lib/neo4j/shared/type_converters.rb:98 `_ + * `lib/neo4j/shared/type_converters.rb:117 `_ @@ -56,6 +58,19 @@ Methods +.. _`Neo4j/Shared/TypeConverters/JSONConverter.db_type`: + +**.db_type** + + + .. hidden-code-block:: ruby + + def db_type + String + end + + + .. _`Neo4j/Shared/TypeConverters/JSONConverter.to_db`: **.to_db** diff --git a/docs/api/Neo4j/Shared/TypeConverters/TimeConverter.rst b/docs/api/Neo4j/Shared/TypeConverters/TimeConverter.rst index 8cf38226c..3e84151d1 100644 --- a/docs/api/Neo4j/Shared/TypeConverters/TimeConverter.rst +++ b/docs/api/Neo4j/Shared/TypeConverters/TimeConverter.rst @@ -21,6 +21,8 @@ TimeConverter + + @@ -36,7 +38,7 @@ Files - * `lib/neo4j/shared/type_converters.rb:54 `_ + * `lib/neo4j/shared/type_converters.rb:62 `_ @@ -73,10 +75,24 @@ Methods +.. _`Neo4j/Shared/TypeConverters/TimeConverter.db_type`: + +**.db_type** + + + .. hidden-code-block:: ruby + + def db_type + Integer + end + + + .. _`Neo4j/Shared/TypeConverters/TimeConverter.primitive_type`: **.primitive_type** - + ActiveAttr, which assists with property management, does not recognize Time as a valid type. We tell it to interpret it as + Integer, as it will be when saved to the database. .. hidden-code-block:: ruby diff --git a/docs/api/Neo4j/Shared/TypeConverters/YAMLConverter.rst b/docs/api/Neo4j/Shared/TypeConverters/YAMLConverter.rst index 6d64c00d6..e73bc0418 100644 --- a/docs/api/Neo4j/Shared/TypeConverters/YAMLConverter.rst +++ b/docs/api/Neo4j/Shared/TypeConverters/YAMLConverter.rst @@ -17,6 +17,8 @@ Converts hash to/from YAML + + @@ -32,7 +34,7 @@ Files - * `lib/neo4j/shared/type_converters.rb:81 `_ + * `lib/neo4j/shared/type_converters.rb:96 `_ @@ -56,6 +58,19 @@ Methods +.. _`Neo4j/Shared/TypeConverters/YAMLConverter.db_type`: + +**.db_type** + + + .. hidden-code-block:: ruby + + def db_type + String + end + + + .. _`Neo4j/Shared/TypeConverters/YAMLConverter.to_db`: **.to_db** diff --git a/docs/api/Neo4j/Shared/Validations.rst b/docs/api/Neo4j/Shared/Validations.rst index 08847e24f..b71e6e24e 100644 --- a/docs/api/Neo4j/Shared/Validations.rst +++ b/docs/api/Neo4j/Shared/Validations.rst @@ -84,7 +84,7 @@ Methods .. hidden-code-block:: ruby def valid?(context = nil) - context ||= (new_record? ? :create : :update) + context ||= (new_record? ? :create : :update) super(context) errors.empty? end diff --git a/docs/api/Neo4j/TypeConverters.rst b/docs/api/Neo4j/TypeConverters.rst index 0a0b2157b..47684b9d6 100644 --- a/docs/api/Neo4j/TypeConverters.rst +++ b/docs/api/Neo4j/TypeConverters.rst @@ -40,19 +40,32 @@ Methods .. _`Neo4j/TypeConverters#convert_properties_to`: **#convert_properties_to** - + Modifies a hash's values to be of types acceptable to Neo4j or matching what the user defined using `type` in property definitions. .. hidden-code-block:: ruby def convert_properties_to(obj, medium, properties) - converter = medium == :ruby ? :to_ruby : :to_db - properties.each_pair do |attr, value| - next if skip_conversion?(obj, attr, value) - properties[attr] = converted_property(primitive_type(attr.to_sym), value, converter) + direction = medium == :ruby ? :to_ruby : :to_db + properties.each_pair do |key, value| + next if skip_conversion?(obj, key, value) + properties[key] = convert_property(key, value, direction) end end +.. _`Neo4j/TypeConverters#convert_property`: + +**#convert_property** + Converts a single property from its current format to its db- or Ruby-expected output type. + + .. hidden-code-block:: ruby + + def convert_property(key, value, direction) + converted_property(primitive_type(key.to_sym), value, direction) + end + + + From 1030b95d67bc0d771aa1cdfb3970f7ca013febb1 Mon Sep 17 00:00:00 2001 From: Chris Grigg Date: Fri, 17 Jul 2015 15:19:20 -0400 Subject: [PATCH 11/11] fix rubocop complaint about name --- lib/neo4j/active_node/has_n.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/neo4j/active_node/has_n.rb b/lib/neo4j/active_node/has_n.rb index f19b9ebbe..7d8915114 100644 --- a/lib/neo4j/active_node/has_n.rb +++ b/lib/neo4j/active_node/has_n.rb @@ -391,7 +391,7 @@ def association_target_classes(name) target_classes_or_nil end - def default_association_query_proxy(name) + def default_association_query_proxy(_name) Neo4j::ActiveNode::Query::QueryProxy.new("::#{self.name}".constantize, nil, session: neo4j_session,