neoid 0.0.51 → 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,59 +1,100 @@
1
1
  module Neoid
2
2
  module Relationship
3
+ # this is a proxy that delays loading of start_node and end_node from Neo4j until accessed.
4
+ # the original Neography Relatioship loaded them on initialization
5
+ class RelationshipLazyProxy < ::Neography::Relationship
6
+ def start_node
7
+ @start_node_from_db ||= @start_node = Neography::Node.load(@start_node, Neoid.db)
8
+ end
9
+
10
+ def end_node
11
+ @end_node_from_db ||= @end_node = Neography::Node.load(@end_node, Neoid.db)
12
+ end
13
+ end
14
+
15
+ def self.from_hash(hash)
16
+ relationship = RelationshipLazyProxy.new(hash)
17
+
18
+ relationship
19
+ end
20
+
21
+
22
+ module ClassMethods
23
+ def delete_command
24
+ :delete_relationship
25
+ end
26
+ end
27
+
3
28
  module InstanceMethods
4
29
  def neo_find_by_id
5
- Neoid.db.get_relationship_index(self.class.neo_index_name, :ar_id, self.id)
6
- rescue Neography::NotFoundException
7
- nil
30
+ results = Neoid.db.get_relationship_auto_index(Neoid::UNIQUE_ID_KEY, self.neo_unique_id)
31
+ relationship = results.present? ? Neoid::Relationship.from_hash(results[0]) : nil
32
+ relationship
8
33
  end
9
34
 
10
- def neo_create
35
+ def _neo_save
11
36
  return unless Neoid.enabled?
12
- @_neo_destroyed = false
13
37
 
14
38
  options = self.class.neoid_config.relationship_options
15
39
 
16
- start_node = self.send(options[:start_node])
17
- end_node = self.send(options[:end_node])
40
+ start_item = self.send(options[:start_node])
41
+ end_item = self.send(options[:end_node])
18
42
 
19
- return unless start_node && end_node
43
+ return unless start_item && end_item
44
+
45
+ # initialize nodes
46
+ start_item.neo_node
47
+ end_item.neo_node
20
48
 
21
- data = self.to_neo.merge(ar_type: self.class.name, ar_id: self.id)
49
+ data = self.to_neo.merge(ar_type: self.class.name, ar_id: self.id, Neoid::UNIQUE_ID_KEY => self.neo_unique_id)
22
50
  data.reject! { |k, v| v.nil? }
23
-
24
- relationship = Neography::Relationship.create(
25
- options[:type].is_a?(Proc) ? options[:type].call(self) : options[:type],
26
- start_node.neo_node,
27
- end_node.neo_node,
28
- data
29
- )
30
-
31
- Neoid.db.add_relationship_to_index(self.class.neo_index_name, :ar_id, self.id, relationship)
32
51
 
33
- Neoid::logger.info "Relationship#neo_create #{self.class.name} #{self.id}, index = #{self.class.neo_index_name}"
52
+ rel_type = options[:type].is_a?(Proc) ? options[:type].call(self) : options[:type]
53
+
54
+ gremlin_query = <<-GREMLIN
55
+ idx = g.idx('relationship_auto_index');
56
+ q = null;
57
+ if (idx) q = idx.get(unique_id_key, unique_id);
58
+
59
+ relationship = null;
60
+ if (q && q.hasNext()) {
61
+ relationship = q.next();
62
+ relationship_data.each {
63
+ if (relationship.getProperty(it.key) != it.value) {
64
+ relationship.setProperty(it.key, it.value);
65
+ }
66
+ }
67
+ } else {
68
+ node_index = g.idx('node_auto_index');
69
+ start_node = node_index.get(unique_id_key, start_node_unique_id).next();
70
+ end_node = node_index.get(unique_id_key, end_node_unique_id).next();
71
+
72
+ relationship = g.addEdge(start_node, end_node, rel_type, relationship_data);
73
+ }
74
+
75
+ relationship
76
+ GREMLIN
77
+
78
+ script_vars = {
79
+ unique_id_key: Neoid::UNIQUE_ID_KEY,
80
+ relationship_data: data,
81
+ unique_id: self.neo_unique_id,
82
+ start_node_unique_id: start_item.neo_unique_id,
83
+ end_node_unique_id: end_item.neo_unique_id,
84
+ rel_type: rel_type
85
+ }
86
+
87
+ Neoid::logger.info "Relationship#neo_save #{self.class.name} #{self.id}"
34
88
 
35
- relationship
36
- end
37
-
38
- def neo_load(relationship)
39
- Neography::Relationship.load(relationship)
40
- end
41
-
42
- def neo_destroy
43
- return if @_neo_destroyed
44
- @_neo_destroyed = true
45
- return unless neo_relationship
46
- Neoid.db.remove_relationship_from_index(self.class.neo_index_name, neo_relationship)
47
- neo_relationship.del
48
- _reset_neo_representation
49
-
50
- Neoid::logger.info "Relationship#neo_destroy #{self.class.name} #{self.id}, index = #{self.class.neo_index_name}"
89
+ relationship = Neoid.execute_script_or_add_to_batch gremlin_query, script_vars do |value|
90
+ Neoid::Relationship.from_hash(value)
91
+ end
51
92
 
52
- true
93
+ relationship
53
94
  end
54
95
 
55
- def neo_update
56
- Neoid.db.set_relationship_properties(neo_relationship, self.to_neo) if neo_relationship
96
+ def neo_load(hash)
97
+ Neoid::Relationship.from_hash(hash)
57
98
  end
58
99
 
59
100
  def neo_relationship
@@ -64,16 +105,15 @@ module Neoid
64
105
  def self.included(receiver)
65
106
  receiver.send :include, Neoid::ModelAdditions
66
107
  receiver.send :include, InstanceMethods
108
+ receiver.extend ClassMethods
67
109
 
68
- receiver.after_create :neo_create
69
- receiver.after_destroy :neo_destroy
70
- receiver.after_update :neo_update
71
-
72
- if Neoid.env_loaded
73
- initialize_relationship receiver
74
- else
75
- Neoid.relationship_models << receiver
76
- end
110
+ initialize_relationship receiver if Neoid.env_loaded
111
+
112
+ Neoid.relationship_models << receiver
113
+ end
114
+
115
+ def self.meta_data
116
+ @meta_data ||= {}
77
117
  end
78
118
 
79
119
  def self.initialize_relationship(rel_model)
@@ -90,7 +130,6 @@ module Neoid
90
130
  # e.g. User has_many :likes, after_remove: ...
91
131
  full_callback_name = "after_remove_for_#{this_has_many.name}"
92
132
  belongs_to.klass.send(full_callback_name) << :neo_after_relationship_remove if belongs_to.klass.method_defined?(full_callback_name)
93
- # belongs_to.klass.send(:has_many, this_has_many.name, this_has_many.options.merge(after_remove: :neo_after_relationship_remove))
94
133
 
95
134
  # has_many (with through) on the side of the relationship that removes a relationship. e.g. User has_many :movies, through :likes
96
135
  many_to_many = all_has_many.find { |o| o.options[:through] == this_has_many.name }
@@ -104,24 +143,20 @@ module Neoid
104
143
  # movie_id
105
144
  foreign_key_of_record = many_to_many.source_reflection.foreign_key
106
145
 
107
- (Neoid.config[:relationship_meta_data] ||= {}).tap do |data|
146
+ (Neoid::Relationship.meta_data ||= {}).tap do |data|
108
147
  (data[belongs_to.klass.name.to_s] ||= {}).tap do |model_data|
109
148
  model_data[many_to_many.klass.name.to_s] = [rel_model.name.to_s, foreign_key_of_owner, foreign_key_of_record]
110
149
  end
111
150
  end
112
151
 
113
- # puts Neoid.config[:relationship_meta_data].inspect
114
-
115
152
  # e.g. User has_many :movies, through: :likes, before_remove: ...
116
153
  full_callback_name = "before_remove_for_#{many_to_many.name}"
117
154
  belongs_to.klass.send(full_callback_name) << :neo_before_relationship_through_remove if belongs_to.klass.method_defined?(full_callback_name)
118
- # belongs_to.klass.send(:has_many, many_to_many.name, many_to_many.options.merge(before_remove: :neo_after_relationship_remove))
119
155
 
120
156
 
121
157
  # e.g. User has_many :movies, through: :likes, after_remove: ...
122
158
  full_callback_name = "after_remove_for_#{many_to_many.name}"
123
159
  belongs_to.klass.send(full_callback_name) << :neo_after_relationship_through_remove if belongs_to.klass.method_defined?(full_callback_name)
124
- # belongs_to.klass.send(:has_many, many_to_many.name, many_to_many.options.merge(after_remove: :neo_after_relationship_remove))
125
160
  end
126
161
  end
127
162
  end
@@ -1,3 +1,3 @@
1
1
  module Neoid
2
- VERSION = "0.0.51"
2
+ VERSION = "0.1"
3
3
  end
@@ -0,0 +1,170 @@
1
+ require 'spec_helper'
2
+
3
+ describe Neoid::ModelAdditions do
4
+ context "promises" do
5
+ it "should run scripts in a batch and return results" do
6
+ Neoid.batch do |batch|
7
+ batch << [:execute_script, "1"]
8
+ batch << [:execute_script, "2"]
9
+ end.then do |results|
10
+ results.should == [1, 2]
11
+ end
12
+ end
13
+
14
+ it "should run scripts in a batch with batch_size and flush batch when it's full" do
15
+ Neoid.batch(batch_size: 3) do |batch|
16
+ (0...9).each do |i|
17
+ batch.count.should == i % 3
18
+ batch << [:execute_script, i.to_s]
19
+ if i % 3 == 0
20
+ batch.results.count.should == i
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ it "should run scripts in a batch with batch_size and return all results" do
27
+ Neoid.batch(batch_size: 2) do |batch|
28
+ (1..6).each do |i|
29
+ batch << [:execute_script, i.to_s]
30
+ end
31
+ end.then do |results|
32
+ results.should == [1, 2, 3, 4, 5, 6]
33
+ end
34
+ end
35
+
36
+ it "should return results then process them" do
37
+ node_1 = Neoid.db.create_node
38
+ node_2 = Neoid.db.create_node
39
+ rel = Neoid.db.create_relationship(:related, node_1, node_2)
40
+
41
+ Neoid.batch do |batch|
42
+ batch << [:execute_script, "g.v(neo_id)", neo_id: node_1['self'].split('/').last.to_i]
43
+ batch << [:execute_script, "g.v(neo_id)", neo_id: node_2['self'].split('/').last]
44
+ batch << [:execute_script, "g.e(neo_id)", neo_id: rel['self'].split('/').last]
45
+ end.then do |results|
46
+ results[0].should be_a(Neography::Node)
47
+ results[1].should be_a(Neography::Node)
48
+ results[2].should be_a(Neography::Relationship)
49
+ end
50
+ end
51
+
52
+ it "should remember what to do after each script has executed, and perform it when batch is flushed" do
53
+ then_results = []
54
+
55
+ Neoid.batch do |batch|
56
+ (batch << [:execute_script, "1"]).then { |res| then_results << res }
57
+ (batch << [:execute_script, "2"]).then { |res| then_results << res }
58
+ batch << [:execute_script, "3"]
59
+ (batch << [:execute_script, "4"]).then { |res| then_results << res }
60
+ end.then do |results|
61
+ results.should == [1, 2, 3, 4]
62
+ then_results.should == [1, 2, 4]
63
+ end
64
+ end
65
+ end
66
+
67
+ context "nodes" do
68
+ it "should not execute until batch is done" do
69
+ u1 = u2 = nil
70
+
71
+ res = Neoid.batch do
72
+ u1 = User.create!(name: "U1")
73
+ u2 = User.create!(name: "U2")
74
+
75
+ u1.neo_find_by_id.should be_nil
76
+ u2.neo_find_by_id.should be_nil
77
+ end
78
+
79
+ res.length.should == 2
80
+
81
+ u1.neo_find_by_id.should_not be_nil
82
+ u2.neo_find_by_id.should_not be_nil
83
+ end
84
+
85
+ it "should update nodes in batch" do
86
+ u1 = User.create!(name: "U1")
87
+ u2 = User.create!(name: "U2")
88
+
89
+ res = Neoid.batch do
90
+ u1.name = "U1 update"
91
+ u2.name = "U2 update"
92
+
93
+ u1.save!
94
+ u2.save!
95
+
96
+ u1.neo_find_by_id.name.should == "U1"
97
+ u2.neo_find_by_id.name.should == "U2"
98
+ end
99
+
100
+ res.length.should == 2
101
+
102
+ u1.neo_find_by_id.name.should == "U1 update"
103
+ u2.neo_find_by_id.name.should == "U2 update"
104
+ end
105
+
106
+
107
+ # Not working yet because Neography can't delete a node and all of its realtionships in a batch, and deleting a node with relationships results an error
108
+ # it "should delete nodes in batch" do
109
+ # u1 = User.create!(name: "U1")
110
+ # u2 = User.create!(name: "U2")
111
+
112
+ # res = Neoid.batch do
113
+ # u1_node_id = u1.neo_find_by_id.neo_id
114
+ # u2_node_id = u2.neo_find_by_id.neo_id
115
+
116
+ # u1.destroy
117
+ # u2.destroy
118
+
119
+ # Neoid.db.get_node(u1_node_id).should_not be_nil
120
+ # Neoid.db.get_node(u2_node_id).should_not be_nil
121
+ # end
122
+
123
+ # res.length.should == 2
124
+
125
+ # Neoid.db.get_node(u1_node_id).should be_nil
126
+ # Neoid.db.get_node(u2_node_id).should be_nil
127
+ # end
128
+ end
129
+
130
+ context "relationships" do
131
+ let(:user) { User.create(name: "Elad Ossadon", slug: "elado") }
132
+ let(:movie) { Movie.create(name: "Memento", slug: "memento-1999", year: 1999) }
133
+
134
+ it "should not execute until batch is done" do
135
+ # ensure user and movie nodes are inserted
136
+ user
137
+ movie
138
+
139
+ res = Neoid.batch do |batch|
140
+ user.like! movie
141
+
142
+ user.likes.last.neo_find_by_id.should be_nil
143
+ end
144
+
145
+ res.length.should == 1
146
+
147
+ user.likes.last.neo_find_by_id.should_not be_nil
148
+ end
149
+
150
+ it "should not execute until batch is done" do
151
+ # ensure user and movie nodes are inserted
152
+ user
153
+ movie
154
+
155
+ # then destroy the nodes, allow the relationship do that in the batch
156
+ user.neo_destroy
157
+ movie.neo_destroy
158
+
159
+ res = Neoid.batch do |batch|
160
+ user.like! movie
161
+
162
+ user.likes.last.neo_find_by_id.should be_nil
163
+ end
164
+
165
+ res.length.should == 3
166
+
167
+ user.likes.last.neo_find_by_id.should_not be_nil
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe Neoid::Config do
4
+ context "config" do
5
+ it "should store and read config" do
6
+ Neoid.configure do |config|
7
+ config.enable_subrefs = false
8
+ end
9
+
10
+ Neoid.config.enable_subrefs.should == false
11
+ end
12
+ end
13
+ end
@@ -1,16 +1,15 @@
1
1
  require 'spec_helper'
2
- require 'fileutils'
3
2
 
4
3
  describe Neoid::ModelAdditions do
5
4
  context "nodes" do
6
5
  context "create graph nodes" do
7
- it "should call neo_create on a neo_node for user" do
8
- User.any_instance.should_receive(:neo_create)
9
-
10
- User.create!(name: "Elad Ossadon")
6
+ it "should call neo_save after model creation" do
7
+ user = User.new(name: "Elad Ossadon")
8
+ user.should_receive(:neo_save)
9
+ user.save!
11
10
  end
12
11
 
13
- it "should create a neo_node for user" do
12
+ it "should create a node for user" do
14
13
  user = User.create!(name: "Elad Ossadon", slug: "elado")
15
14
 
16
15
  user.neo_node.should_not be_nil
@@ -22,7 +21,7 @@ describe Neoid::ModelAdditions do
22
21
 
23
22
  it "should create a neo_node for movie" do
24
23
  movie = Movie.create!(name: "Memento", slug: "memento-1999", year: 1999)
25
-
24
+
26
25
  movie.neo_node.should_not be_nil
27
26
 
28
27
  movie.neo_node.ar_id.should == movie.id
@@ -31,26 +30,110 @@ describe Neoid::ModelAdditions do
31
30
  end
32
31
  end
33
32
 
33
+ context "update graph nodes" do
34
+ it "should call neo_save after model update" do
35
+ user = User.create!(name: "Elad Ossadon")
36
+ user.should_receive(:neo_save)
37
+ user.name = "John Doe"
38
+ user.save!
39
+ end
40
+
41
+ it "should update a node after model update" do
42
+ user = User.create!(name: "Elad Ossadon")
43
+ user.neo_node.name.should == "Elad Ossadon"
44
+
45
+ user.name = "John Doe"
46
+ user.save!
47
+
48
+ user.neo_node.name.should == "John Doe"
49
+ end
50
+ end
51
+
34
52
  context "find by id" do
35
53
  it "should find a neo_node for user" do
36
54
  user = User.create!(name: "Elad Ossadon", slug: "elado")
37
-
55
+
38
56
  user.neo_node.should_not be_nil
39
57
  user.neo_find_by_id.should_not be_nil
40
58
  end
41
59
  end
60
+
61
+ context "no auto_index" do
62
+ it "should not index a node if option :auto_index is set to false" do
63
+ model = NoAutoIndexNode.new(name: "Hello")
64
+ model.should_not_receive(:neo_save)
65
+ model.save!
66
+ end
67
+ end
68
+
69
+ context "subrefs" do
70
+ it "should create a relationship with a subref node" do
71
+ old, Neoid.config.enable_subrefs = Neoid.config.enable_subrefs, true
72
+
73
+ Neoid.send(:initialize_subrefs)
74
+
75
+ begin
76
+ user = User.create!(name: "Elad")
77
+ user.neo_node.rel(:incoming, :users_subref).should_not be_nil
78
+ ensure
79
+ Neoid.config.enable_subrefs = old
80
+ end
81
+ end
82
+
83
+ it "should not create a relationship with a subref node if disabled" do
84
+ old, Neoid.config.enable_subrefs = Neoid.config.enable_subrefs, false
85
+
86
+ begin
87
+ user = User.create!(name: "Elad")
88
+ user.neo_node.rel(:incoming, :users_subref).should be_nil
89
+ ensure
90
+ Neoid.config.enable_subrefs = old
91
+ end
92
+ end
93
+ end
94
+
95
+ context "per_model_indexes" do
96
+ it "should create a relationship with a subref node" do
97
+ old, Neoid.config.enable_per_model_indexes = Neoid.config.enable_per_model_indexes, true
98
+
99
+ Neoid.send(:initialize_per_model_indexes)
100
+
101
+ begin
102
+ user = User.create!(name: "Elad")
103
+ Neoid.db.get_node_index(User.neo_model_index_name, 'ar_id', user.id).should_not be_nil
104
+ ensure
105
+ Neoid.config.enable_per_model_indexes = old
106
+ end
107
+ end
108
+
109
+ it "should not create a relationship with a subref node if disabled" do
110
+ old, Neoid.config.enable_per_model_indexes = Neoid.config.enable_per_model_indexes, false
111
+
112
+ begin
113
+ user = User.create!(name: "Elad")
114
+ expect { Neoid.db.get_node_index(User.neo_model_index_name, 'ar_id', user.id) }.to raise_error(Neography::NotFoundException)
115
+ ensure
116
+ Neoid.config.enable_per_model_indexes = old
117
+ end
118
+ end
119
+ end
42
120
  end
43
121
 
44
122
  context "relationships" do
45
- let(:user) { User.create(name: "Elad Ossadon", slug: "elado") }
46
- let(:movie) { Movie.create(name: "Memento", slug: "memento-1999", year: 1999) }
123
+ let(:user) { User.create!(name: "Elad Ossadon", slug: "elado") }
124
+ let(:movie) { Movie.create!(name: "Memento", slug: "memento-1999", year: 1999) }
47
125
 
48
- it "should create a relationship on neo4j" do
126
+ it "should call neo_save after relationship model creation" do
127
+ Like.any_instance.should_receive(:neo_save)
49
128
  user.like! movie
50
- like = user.likes.first
51
-
129
+ end
130
+
131
+ it "should create a neo_relationship for like" do
132
+ like = user.like! movie
133
+ like = user.likes.last
134
+
52
135
  like.neo_find_by_id.should_not be_nil
53
-
136
+
54
137
  like.neo_relationship.should_not be_nil
55
138
 
56
139
  like.neo_relationship.start_node.should == user.neo_node
@@ -63,7 +146,7 @@ describe Neoid::ModelAdditions do
63
146
  like = user.likes.last
64
147
 
65
148
  relationship_neo_id = like.neo_relationship.neo_id
66
-
149
+
67
150
  Neography::Relationship.load(relationship_neo_id).should_not be_nil
68
151
 
69
152
  user.unlike! movie
@@ -98,12 +181,23 @@ describe Neoid::ModelAdditions do
98
181
  user.movie_ids = movies[0...2].collect(&:id)
99
182
  }.to change{ user.neo_node.outgoing(:likes).length }.to(2)
100
183
  end
184
+
185
+ it "should update a relationship after relationship model update" do
186
+ like = user.like! movie
187
+
188
+ like.neo_relationship.rate.should be_nil
189
+
190
+ like.rate = 10
191
+ like.save!
192
+
193
+ like.neo_relationship.rate.should == 10
194
+ end
101
195
  end
102
196
 
103
197
  context "polymorphic relationship" do
104
198
  let(:user) { User.create(name: "Elad Ossadon", slug: "elado") }
105
199
 
106
- it "description" do
200
+ it "should create relationships with polymorphic items" do
107
201
  followed = [
108
202
  User.create(name: "Some One", slug: "someone"),
109
203
  Movie.create(name: "The Prestige"),
@@ -112,7 +206,7 @@ describe Neoid::ModelAdditions do
112
206
 
113
207
  expect {
114
208
  followed.each do |item|
115
- user.user_follows.create(item: item)
209
+ user.user_follows.create!(item: item)
116
210
  end
117
211
  }.to change{ user.neo_node.outgoing(:follows).length }.to(followed.length)
118
212