neoid 0.1.2 → 0.2.0
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.
- 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
|