neoid 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/LICENSE +1 -1
- data/README.md +27 -25
- data/Rakefile +2 -2
- data/lib/neoid.rb +74 -61
- data/lib/neoid/batch.rb +27 -27
- data/lib/neoid/database_cleaner.rb +1 -1
- data/lib/neoid/middleware.rb +1 -1
- data/lib/neoid/model_additions.rb +13 -11
- data/lib/neoid/model_config.rb +13 -13
- data/lib/neoid/node.rb +21 -21
- data/lib/neoid/railtie.rb +1 -1
- data/lib/neoid/relationship.rb +78 -78
- data/lib/neoid/search_session.rb +3 -3
- data/lib/neoid/version.rb +1 -1
- data/spec/factories.rb +12 -0
- data/spec/neoid/batch_spec.rb +68 -89
- data/spec/neoid/config_spec.rb +6 -6
- data/spec/neoid/model_config_spec.rb +14 -13
- data/spec/neoid/node_spec.rb +60 -79
- data/spec/neoid/relationship_spec.rb +39 -37
- data/spec/neoid/search_spec.rb +71 -46
- data/spec/neoid_spec.rb +11 -4
- data/spec/spec_helper.rb +23 -4
- data/spec/support/database.yml +1 -1
- data/spec/support/models.rb +16 -16
- data/spec/support/schema.rb +1 -2
- metadata +84 -31
- data/.gitignore +0 -5
- data/.rspec +0 -1
- data/.travis.yml +0 -4
- data/CHANGELOG.md +0 -58
- data/Gemfile +0 -4
- data/TODO.md +0 -4
- data/neoid.gemspec +0 -28
data/lib/neoid/middleware.rb
CHANGED
@@ -2,11 +2,11 @@ module Neoid
|
|
2
2
|
module ModelAdditions
|
3
3
|
module ClassMethods
|
4
4
|
attr_reader :neoid_config
|
5
|
-
|
5
|
+
|
6
6
|
def neoid_config
|
7
7
|
@neoid_config ||= Neoid::ModelConfig.new(self)
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
def neoidable(options = {})
|
11
11
|
# defaults
|
12
12
|
neoid_config.auto_index = true
|
@@ -15,17 +15,17 @@ module Neoid
|
|
15
15
|
yield(neoid_config) if block_given?
|
16
16
|
|
17
17
|
options.each do |key, value|
|
18
|
-
raise "Neoid #{
|
18
|
+
raise "Neoid #{name} model options: No such option #{key}" unless neoid_config.respond_to?("#{key}=")
|
19
19
|
neoid_config.send("#{key}=", value)
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
23
|
def neo_model_index_name
|
24
|
-
raise
|
25
|
-
@index_name ||= "#{
|
24
|
+
raise 'Per Model index is not enabled. Nodes/Relationships are auto indexed with node_auto_index/relationship_auto_index' unless Neoid.config.enable_per_model_indexes || neoid_config.enable_model_index
|
25
|
+
@index_name ||= "#{name.tableize}_index"
|
26
26
|
end
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
module InstanceMethods
|
30
30
|
def to_neo
|
31
31
|
if self.class.neoid_config.stored_fields
|
@@ -33,9 +33,9 @@ module Neoid
|
|
33
33
|
all[field] = if block
|
34
34
|
instance_eval(&block)
|
35
35
|
else
|
36
|
-
|
36
|
+
send(field) rescue (raise "No field #{field} for #{self.class.name}")
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
all
|
40
40
|
end
|
41
41
|
|
@@ -69,7 +69,7 @@ module Neoid
|
|
69
69
|
begin
|
70
70
|
neo_representation.del
|
71
71
|
rescue Neography::NodeNotFoundException => e
|
72
|
-
Neoid::logger.info "Neoid#neo_destroy entity not found #{self.class.name} #{
|
72
|
+
Neoid::logger.info "Neoid#neo_destroy entity not found #{self.class.name} #{id}"
|
73
73
|
end
|
74
74
|
|
75
75
|
# 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
|
@@ -89,18 +89,20 @@ module Neoid
|
|
89
89
|
end
|
90
90
|
|
91
91
|
def neo_unique_id
|
92
|
-
"#{self.class.name}:#{
|
92
|
+
"#{self.class.name}:#{id}"
|
93
93
|
end
|
94
94
|
|
95
95
|
protected
|
96
|
+
|
96
97
|
def neo_properties_to_hash(*attribute_list)
|
97
98
|
attribute_list.flatten.inject({}) { |all, property|
|
98
|
-
all[property] =
|
99
|
+
all[property] = send(property)
|
99
100
|
all
|
100
101
|
}
|
101
102
|
end
|
102
103
|
|
103
104
|
private
|
105
|
+
|
104
106
|
def _neo_representation
|
105
107
|
@_neo_representation ||= neo_find_by_id || neo_save
|
106
108
|
end
|
data/lib/neoid/model_config.rb
CHANGED
@@ -5,34 +5,34 @@ module Neoid
|
|
5
5
|
attr_reader :relationship_options
|
6
6
|
attr_accessor :enable_model_index
|
7
7
|
attr_accessor :auto_index
|
8
|
-
|
8
|
+
|
9
9
|
def initialize(klass)
|
10
10
|
@klass = klass
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
def stored_fields
|
14
14
|
@stored_fields ||= {}
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def field(name, &block)
|
18
|
-
|
18
|
+
stored_fields[name] = block
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def relationship(options)
|
22
22
|
@relationship_options = options
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def search(&block)
|
26
|
-
raise
|
26
|
+
raise 'search needs a block' unless block_given?
|
27
27
|
@search_options = SearchConfig.new
|
28
|
-
block.(@search_options)
|
28
|
+
block.call(@search_options)
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
def inspect
|
32
32
|
"#<Neoid::ModelConfig @properties=#{properties.inspect} @search_options=#{@search_options.inspect}>"
|
33
33
|
end
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
class SearchConfig
|
37
37
|
def index_fields
|
38
38
|
@index_fields ||= {}
|
@@ -41,15 +41,15 @@ module Neoid
|
|
41
41
|
def fulltext_fields
|
42
42
|
@fulltext_fields ||= {}
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
45
|
def index(field, options = {}, &block)
|
46
46
|
index_fields[field] = options.merge(block: block)
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
def fulltext(field, options = {}, &block)
|
50
50
|
fulltext_fields[field] = options.merge(block: block)
|
51
51
|
end
|
52
|
-
|
52
|
+
|
53
53
|
def inspect
|
54
54
|
"#<Neoid::SearchConfig @index_fields=#{index_fields.inspect} @fulltext_fields=#{fulltext_fields.inspect}>"
|
55
55
|
end
|
data/lib/neoid/node.rb
CHANGED
@@ -6,15 +6,22 @@ module Neoid
|
|
6
6
|
node
|
7
7
|
end
|
8
8
|
|
9
|
+
def self.included(receiver)
|
10
|
+
receiver.send :include, Neoid::ModelAdditions
|
11
|
+
receiver.extend ClassMethods
|
12
|
+
receiver.send :include, InstanceMethods
|
13
|
+
Neoid.node_models << receiver
|
14
|
+
end
|
15
|
+
|
9
16
|
module ClassMethods
|
10
17
|
attr_accessor :neo_subref_node
|
11
18
|
|
12
19
|
def neo_subref_rel_type
|
13
|
-
@_neo_subref_rel_type ||= "#{
|
20
|
+
@_neo_subref_rel_type ||= "#{name.tableize}_subref"
|
14
21
|
end
|
15
22
|
|
16
23
|
def neo_subref_node_rel_type
|
17
|
-
@_neo_subref_node_rel_type ||=
|
24
|
+
@_neo_subref_node_rel_type ||= name.tableize
|
18
25
|
end
|
19
26
|
|
20
27
|
def delete_command
|
@@ -60,7 +67,7 @@ module Neoid
|
|
60
67
|
|
61
68
|
script_vars = {
|
62
69
|
neo_subref_rel_type: neo_subref_rel_type,
|
63
|
-
name:
|
70
|
+
name: name
|
64
71
|
}
|
65
72
|
|
66
73
|
Neoid.execute_script_or_add_to_batch gremlin_query, script_vars do |value|
|
@@ -77,18 +84,18 @@ module Neoid
|
|
77
84
|
Neoid.search(self, term, options)
|
78
85
|
end
|
79
86
|
end
|
80
|
-
|
87
|
+
|
81
88
|
module InstanceMethods
|
82
89
|
def neo_find_by_id
|
83
90
|
# Neoid::logger.info "Node#neo_find_by_id #{self.class.neo_index_name} #{self.id}"
|
84
|
-
node = Neoid.db.get_node_auto_index(Neoid::UNIQUE_ID_KEY,
|
91
|
+
node = Neoid.db.get_node_auto_index(Neoid::UNIQUE_ID_KEY, neo_unique_id)
|
85
92
|
node.present? ? Neoid::Node.from_hash(node[0]) : nil
|
86
93
|
end
|
87
|
-
|
94
|
+
|
88
95
|
def _neo_save
|
89
96
|
return unless Neoid.enabled?
|
90
97
|
|
91
|
-
data =
|
98
|
+
data = to_neo.merge(ar_type: self.class.name, ar_id: id, Neoid::UNIQUE_ID_KEY => neo_unique_id)
|
92
99
|
data.reject! { |k, v| v.nil? }
|
93
100
|
|
94
101
|
gremlin_query = <<-GREMLIN
|
@@ -114,10 +121,10 @@ module Neoid
|
|
114
121
|
node
|
115
122
|
GREMLIN
|
116
123
|
|
117
|
-
|
124
|
+
script_vars = {
|
118
125
|
unique_id_key: Neoid::UNIQUE_ID_KEY,
|
119
126
|
node_data: data,
|
120
|
-
unique_id:
|
127
|
+
unique_id: neo_unique_id,
|
121
128
|
enable_subrefs: Neoid.config.enable_subrefs,
|
122
129
|
enable_model_index: Neoid.config.enable_per_model_indexes && self.class.neoid_config.enable_model_index
|
123
130
|
}
|
@@ -135,7 +142,7 @@ module Neoid
|
|
135
142
|
)
|
136
143
|
end
|
137
144
|
|
138
|
-
Neoid::logger.info "Node#neo_save #{self.class.name} #{
|
145
|
+
Neoid::logger.info "Node#neo_save #{self.class.name} #{id}"
|
139
146
|
|
140
147
|
node = Neoid.execute_script_or_add_to_batch(gremlin_query, script_vars) do |value|
|
141
148
|
@_neo_representation = Neoid::Node.from_hash(value)
|
@@ -171,14 +178,14 @@ module Neoid
|
|
171
178
|
if options[:block]
|
172
179
|
options[:block].call
|
173
180
|
else
|
174
|
-
|
181
|
+
send(field) rescue (raise "No field #{field} for #{self.class.name}")
|
175
182
|
end
|
176
183
|
end
|
177
|
-
|
184
|
+
|
178
185
|
def neo_load(hash)
|
179
186
|
Neoid::Node.from_hash(hash)
|
180
187
|
end
|
181
|
-
|
188
|
+
|
182
189
|
def neo_node
|
183
190
|
_neo_representation
|
184
191
|
end
|
@@ -191,7 +198,7 @@ module Neoid
|
|
191
198
|
rel_model, foreign_key_of_owner, foreign_key_of_record = Neoid::Relationship.meta_data[self.class.name.to_s][record.class.name.to_s]
|
192
199
|
rel_model = rel_model.to_s.constantize
|
193
200
|
@__neo_temp_rels ||= {}
|
194
|
-
@__neo_temp_rels[record] = rel_model.where(foreign_key_of_owner =>
|
201
|
+
@__neo_temp_rels[record] = rel_model.where(foreign_key_of_owner => id, foreign_key_of_record => record.id).first
|
195
202
|
end
|
196
203
|
|
197
204
|
def neo_after_relationship_through_remove(record)
|
@@ -199,12 +206,5 @@ module Neoid
|
|
199
206
|
@__neo_temp_rels.delete(record)
|
200
207
|
end
|
201
208
|
end
|
202
|
-
|
203
|
-
def self.included(receiver)
|
204
|
-
receiver.send :include, Neoid::ModelAdditions
|
205
|
-
receiver.extend ClassMethods
|
206
|
-
receiver.send :include, InstanceMethods
|
207
|
-
Neoid.node_models << receiver
|
208
|
-
end
|
209
209
|
end
|
210
210
|
end
|
data/lib/neoid/railtie.rb
CHANGED
data/lib/neoid/relationship.rb
CHANGED
@@ -1,5 +1,70 @@
|
|
1
1
|
module Neoid
|
2
2
|
module Relationship
|
3
|
+
class << self
|
4
|
+
def from_hash(hash)
|
5
|
+
relationship = RelationshipLazyProxy.new(hash)
|
6
|
+
|
7
|
+
relationship
|
8
|
+
end
|
9
|
+
|
10
|
+
def included(receiver)
|
11
|
+
receiver.send :include, Neoid::ModelAdditions
|
12
|
+
receiver.send :include, InstanceMethods
|
13
|
+
receiver.extend ClassMethods
|
14
|
+
|
15
|
+
initialize_relationship receiver if Neoid.env_loaded
|
16
|
+
|
17
|
+
Neoid.relationship_models << receiver
|
18
|
+
end
|
19
|
+
|
20
|
+
def meta_data
|
21
|
+
@meta_data ||= {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize_relationship(rel_model)
|
25
|
+
rel_model.reflect_on_all_associations(:belongs_to).each do |belongs_to|
|
26
|
+
return if belongs_to.options[:polymorphic]
|
27
|
+
|
28
|
+
# e.g. all has_many on User class
|
29
|
+
all_has_many = belongs_to.klass.reflect_on_all_associations(:has_many)
|
30
|
+
|
31
|
+
# has_many (without through) on the side of the relationship that removes a relationship. e.g. User has_many :likes
|
32
|
+
this_has_many = all_has_many.find { |o| o.klass == rel_model }
|
33
|
+
next unless this_has_many
|
34
|
+
|
35
|
+
# e.g. User has_many :likes, after_remove: ...
|
36
|
+
full_callback_name = "after_remove_for_#{this_has_many.name}"
|
37
|
+
belongs_to.klass.send(full_callback_name) << :neo_after_relationship_remove if belongs_to.klass.method_defined?(full_callback_name)
|
38
|
+
|
39
|
+
# has_many (with through) on the side of the relationship that removes a relationship. e.g. User has_many :movies, through :likes
|
40
|
+
many_to_many = all_has_many.find { |o| o.options[:through] == this_has_many.name }
|
41
|
+
next unless many_to_many
|
42
|
+
|
43
|
+
return if many_to_many.options[:as] # polymorphic are not supported here yet
|
44
|
+
|
45
|
+
# user_id
|
46
|
+
foreign_key_of_owner = many_to_many.through_reflection.foreign_key
|
47
|
+
|
48
|
+
# movie_id
|
49
|
+
foreign_key_of_record = many_to_many.source_reflection.foreign_key
|
50
|
+
|
51
|
+
(Neoid::Relationship.meta_data ||= {}).tap do |data|
|
52
|
+
(data[belongs_to.klass.name.to_s] ||= {}).tap do |model_data|
|
53
|
+
model_data[many_to_many.klass.name.to_s] = [rel_model.name.to_s, foreign_key_of_owner, foreign_key_of_record]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# e.g. User has_many :movies, through: :likes, before_remove: ...
|
58
|
+
full_callback_name = "before_remove_for_#{many_to_many.name}"
|
59
|
+
belongs_to.klass.send(full_callback_name) << :neo_before_relationship_through_remove if belongs_to.klass.method_defined?(full_callback_name)
|
60
|
+
|
61
|
+
# e.g. User has_many :movies, through: :likes, after_remove: ...
|
62
|
+
full_callback_name = "after_remove_for_#{many_to_many.name}"
|
63
|
+
belongs_to.klass.send(full_callback_name) << :neo_after_relationship_through_remove if belongs_to.klass.method_defined?(full_callback_name)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
3
68
|
# this is a proxy that delays loading of start_node and end_node from Neo4j until accessed.
|
4
69
|
# the original Neography Relatioship loaded them on initialization
|
5
70
|
class RelationshipLazyProxy < ::Neography::Relationship
|
@@ -12,13 +77,6 @@ module Neoid
|
|
12
77
|
end
|
13
78
|
end
|
14
79
|
|
15
|
-
def self.from_hash(hash)
|
16
|
-
relationship = RelationshipLazyProxy.new(hash)
|
17
|
-
|
18
|
-
relationship
|
19
|
-
end
|
20
|
-
|
21
|
-
|
22
80
|
module ClassMethods
|
23
81
|
def delete_command
|
24
82
|
:delete_relationship
|
@@ -27,26 +85,26 @@ module Neoid
|
|
27
85
|
|
28
86
|
module InstanceMethods
|
29
87
|
def neo_find_by_id
|
30
|
-
results = Neoid.db.get_relationship_auto_index(Neoid::UNIQUE_ID_KEY,
|
88
|
+
results = Neoid.db.get_relationship_auto_index(Neoid::UNIQUE_ID_KEY, neo_unique_id)
|
31
89
|
relationship = results.present? ? Neoid::Relationship.from_hash(results[0]) : nil
|
32
90
|
relationship
|
33
91
|
end
|
34
|
-
|
92
|
+
|
35
93
|
def _neo_save
|
36
94
|
return unless Neoid.enabled?
|
37
|
-
|
95
|
+
|
38
96
|
options = self.class.neoid_config.relationship_options
|
39
|
-
|
40
|
-
start_item =
|
41
|
-
end_item =
|
42
|
-
|
97
|
+
|
98
|
+
start_item = send(options[:start_node])
|
99
|
+
end_item = send(options[:end_node])
|
100
|
+
|
43
101
|
return unless start_item && end_item
|
44
102
|
|
45
103
|
# initialize nodes
|
46
104
|
start_item.neo_node
|
47
105
|
end_item.neo_node
|
48
106
|
|
49
|
-
data =
|
107
|
+
data = to_neo.merge(ar_type: self.class.name, ar_id: id, Neoid::UNIQUE_ID_KEY => neo_unique_id)
|
50
108
|
data.reject! { |k, v| v.nil? }
|
51
109
|
|
52
110
|
rel_type = options[:type].is_a?(Proc) ? options[:type].call(self) : options[:type]
|
@@ -75,24 +133,24 @@ module Neoid
|
|
75
133
|
relationship
|
76
134
|
GREMLIN
|
77
135
|
|
78
|
-
|
136
|
+
script_vars = {
|
79
137
|
unique_id_key: Neoid::UNIQUE_ID_KEY,
|
80
138
|
relationship_data: data,
|
81
|
-
unique_id:
|
139
|
+
unique_id: neo_unique_id,
|
82
140
|
start_node_unique_id: start_item.neo_unique_id,
|
83
141
|
end_node_unique_id: end_item.neo_unique_id,
|
84
142
|
rel_type: rel_type
|
85
143
|
}
|
86
144
|
|
87
|
-
Neoid::logger.info "Relationship#neo_save #{self.class.name} #{
|
88
|
-
|
145
|
+
Neoid::logger.info "Relationship#neo_save #{self.class.name} #{id}"
|
146
|
+
|
89
147
|
relationship = Neoid.execute_script_or_add_to_batch gremlin_query, script_vars do |value|
|
90
148
|
Neoid::Relationship.from_hash(value)
|
91
149
|
end
|
92
150
|
|
93
151
|
relationship
|
94
152
|
end
|
95
|
-
|
153
|
+
|
96
154
|
def neo_load(hash)
|
97
155
|
Neoid::Relationship.from_hash(hash)
|
98
156
|
end
|
@@ -101,63 +159,5 @@ module Neoid
|
|
101
159
|
_neo_representation
|
102
160
|
end
|
103
161
|
end
|
104
|
-
|
105
|
-
def self.included(receiver)
|
106
|
-
receiver.send :include, Neoid::ModelAdditions
|
107
|
-
receiver.send :include, InstanceMethods
|
108
|
-
receiver.extend ClassMethods
|
109
|
-
|
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 ||= {}
|
117
|
-
end
|
118
|
-
|
119
|
-
def self.initialize_relationship(rel_model)
|
120
|
-
rel_model.reflect_on_all_associations(:belongs_to).each do |belongs_to|
|
121
|
-
return if belongs_to.options[:polymorphic]
|
122
|
-
|
123
|
-
# e.g. all has_many on User class
|
124
|
-
all_has_many = belongs_to.klass.reflect_on_all_associations(:has_many)
|
125
|
-
|
126
|
-
# has_many (without through) on the side of the relationship that removes a relationship. e.g. User has_many :likes
|
127
|
-
this_has_many = all_has_many.find { |o| o.klass == rel_model }
|
128
|
-
next unless this_has_many
|
129
|
-
|
130
|
-
# e.g. User has_many :likes, after_remove: ...
|
131
|
-
full_callback_name = "after_remove_for_#{this_has_many.name}"
|
132
|
-
belongs_to.klass.send(full_callback_name) << :neo_after_relationship_remove if belongs_to.klass.method_defined?(full_callback_name)
|
133
|
-
|
134
|
-
# has_many (with through) on the side of the relationship that removes a relationship. e.g. User has_many :movies, through :likes
|
135
|
-
many_to_many = all_has_many.find { |o| o.options[:through] == this_has_many.name }
|
136
|
-
next unless many_to_many
|
137
|
-
|
138
|
-
return if many_to_many.options[:as] # polymorphic are not supported here yet
|
139
|
-
|
140
|
-
# user_id
|
141
|
-
foreign_key_of_owner = many_to_many.through_reflection.foreign_key
|
142
|
-
|
143
|
-
# movie_id
|
144
|
-
foreign_key_of_record = many_to_many.source_reflection.foreign_key
|
145
|
-
|
146
|
-
(Neoid::Relationship.meta_data ||= {}).tap do |data|
|
147
|
-
(data[belongs_to.klass.name.to_s] ||= {}).tap do |model_data|
|
148
|
-
model_data[many_to_many.klass.name.to_s] = [rel_model.name.to_s, foreign_key_of_owner, foreign_key_of_record]
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
# e.g. User has_many :movies, through: :likes, before_remove: ...
|
153
|
-
full_callback_name = "before_remove_for_#{many_to_many.name}"
|
154
|
-
belongs_to.klass.send(full_callback_name) << :neo_before_relationship_through_remove if belongs_to.klass.method_defined?(full_callback_name)
|
155
|
-
|
156
|
-
|
157
|
-
# e.g. User has_many :movies, through: :likes, after_remove: ...
|
158
|
-
full_callback_name = "after_remove_for_#{many_to_many.name}"
|
159
|
-
belongs_to.klass.send(full_callback_name) << :neo_after_relationship_through_remove if belongs_to.klass.method_defined?(full_callback_name)
|
160
|
-
end
|
161
|
-
end
|
162
162
|
end
|
163
163
|
end
|