neoid 0.0.51 → 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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