lexster 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +58 -0
- data/Gemfile +4 -0
- data/LICENSE +19 -0
- data/README.md +467 -0
- data/Rakefile +7 -0
- data/TODO.md +4 -0
- data/lexster.gemspec +28 -0
- data/lib/lexster.rb +239 -0
- data/lib/lexster/batch.rb +168 -0
- data/lib/lexster/config.rb +6 -0
- data/lib/lexster/database_cleaner.rb +12 -0
- data/lib/lexster/middleware.rb +16 -0
- data/lib/lexster/model_additions.rb +121 -0
- data/lib/lexster/model_config.rb +57 -0
- data/lib/lexster/node.rb +210 -0
- data/lib/lexster/railtie.rb +15 -0
- data/lib/lexster/relationship.rb +163 -0
- data/lib/lexster/search_session.rb +28 -0
- data/lib/lexster/version.rb +3 -0
- data/spec/lexster/batch_spec.rb +170 -0
- data/spec/lexster/config_spec.rb +13 -0
- data/spec/lexster/model_config_spec.rb +24 -0
- data/spec/lexster/node_spec.rb +131 -0
- data/spec/lexster/relationship_spec.rb +102 -0
- data/spec/lexster/search_spec.rb +117 -0
- data/spec/lexster_spec.rb +11 -0
- data/spec/spec_helper.rb +46 -0
- data/spec/support/database.yml +6 -0
- data/spec/support/models.rb +106 -0
- data/spec/support/schema.rb +44 -0
- metadata +172 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
module Lexster
|
2
|
+
class NeoDatabaseCleaner
|
3
|
+
def self.clean_db(start_node = Lexster.db.get_root)
|
4
|
+
Lexster.db.execute_script <<-GREMLIN
|
5
|
+
g.V.toList().each { if (it.id != 0) g.removeVertex(it) }
|
6
|
+
g.indices.each { g.dropIndex(it.indexName); }
|
7
|
+
GREMLIN
|
8
|
+
|
9
|
+
true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Ndoid
|
2
|
+
class Middleware
|
3
|
+
def initialize(app)
|
4
|
+
@app = app
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(env)
|
8
|
+
old_enabled, Thread.current[:lexster_enabled] = Thread.current[:lexster_enabled], true
|
9
|
+
old_batch, Thread.current[:lexster_current_batch] = Thread.current[:lexster_current_batch], nil
|
10
|
+
@app.call(env)
|
11
|
+
ensure
|
12
|
+
Thread.current[:lexster_enabled] = old_enabled
|
13
|
+
Thread.current[:lexster_current_batch] = old_batch
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module Lexster
|
2
|
+
module ModelAdditions
|
3
|
+
module ClassMethods
|
4
|
+
attr_reader :lexster_config
|
5
|
+
|
6
|
+
def lexster_config
|
7
|
+
@lexster_config ||= Lexster::ModelConfig.new(self)
|
8
|
+
end
|
9
|
+
|
10
|
+
def lexsterable(options = {})
|
11
|
+
# defaults
|
12
|
+
lexster_config.auto_index = true
|
13
|
+
lexster_config.enable_model_index = true # but the Lexster.enable_per_model_indexes is false by default. all models will be true only if the primary option is turned on.
|
14
|
+
|
15
|
+
yield(lexster_config) if block_given?
|
16
|
+
|
17
|
+
options.each do |key, value|
|
18
|
+
raise "Lexster #{self.name} model options: No such option #{key}" unless lexster_config.respond_to?("#{key}=")
|
19
|
+
lexster_config.send("#{key}=", value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def neo_model_index_name
|
24
|
+
raise "Per Model index is not enabled. Nodes/Relationships are auto indexed with node_auto_index/relationship_auto_index" unless Lexster.config.enable_per_model_indexes || lexster_config.enable_model_index
|
25
|
+
@index_name ||= "#{self.name.tableize}_index"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module InstanceMethods
|
30
|
+
def to_neo
|
31
|
+
if self.class.lexster_config.stored_fields
|
32
|
+
hash = self.class.lexster_config.stored_fields.inject({}) do |all, (field, block)|
|
33
|
+
all[field] = if block
|
34
|
+
instance_eval(&block)
|
35
|
+
else
|
36
|
+
self.send(field) rescue (raise "No field #{field} for #{self.class.name}")
|
37
|
+
end
|
38
|
+
|
39
|
+
all
|
40
|
+
end
|
41
|
+
|
42
|
+
hash.reject { |k, v| v.nil? }
|
43
|
+
else
|
44
|
+
{}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def neo_save_after_model_save
|
49
|
+
return unless self.class.lexster_config.auto_index
|
50
|
+
neo_save
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
def neo_save
|
55
|
+
@_neo_destroyed = false
|
56
|
+
@_neo_representation = _neo_save
|
57
|
+
end
|
58
|
+
|
59
|
+
alias neo_create neo_save
|
60
|
+
alias neo_update neo_save
|
61
|
+
|
62
|
+
def neo_destroy
|
63
|
+
return if @_neo_destroyed
|
64
|
+
@_neo_destroyed = true
|
65
|
+
|
66
|
+
neo_representation = neo_find_by_id
|
67
|
+
return unless neo_representation
|
68
|
+
|
69
|
+
begin
|
70
|
+
neo_representation.del
|
71
|
+
rescue Neography::NodeNotFoundException => e
|
72
|
+
Lexster::logger.info "Lexster#neo_destroy entity not found #{self.class.name} #{self.id}"
|
73
|
+
end
|
74
|
+
|
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
|
76
|
+
# if Lexster::Batch.current_batch
|
77
|
+
# Lexster::Batch.current_batch << [self.class.delete_command, neo_representation.neo_id]
|
78
|
+
# else
|
79
|
+
# begin
|
80
|
+
# neo_representation.del
|
81
|
+
# rescue Neography::NodeNotFoundException => e
|
82
|
+
# Lexster::logger.info "Lexster#neo_destroy entity not found #{self.class.name} #{self.id}"
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
|
86
|
+
_reset_neo_representation
|
87
|
+
|
88
|
+
true
|
89
|
+
end
|
90
|
+
|
91
|
+
def neo_unique_id
|
92
|
+
"#{self.class.name}:#{self.id}"
|
93
|
+
end
|
94
|
+
|
95
|
+
protected
|
96
|
+
def neo_properties_to_hash(*attribute_list)
|
97
|
+
attribute_list.flatten.inject({}) { |all, property|
|
98
|
+
all[property] = self.send(property)
|
99
|
+
all
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
def _neo_representation
|
105
|
+
@_neo_representation ||= neo_find_by_id || neo_save
|
106
|
+
end
|
107
|
+
|
108
|
+
def _reset_neo_representation
|
109
|
+
@_neo_representation = nil
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.included(receiver)
|
114
|
+
receiver.extend ClassMethods
|
115
|
+
receiver.send :include, InstanceMethods
|
116
|
+
|
117
|
+
receiver.after_save :neo_save_after_model_save
|
118
|
+
receiver.after_destroy :neo_destroy
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Lexster
|
2
|
+
class ModelConfig
|
3
|
+
attr_reader :properties
|
4
|
+
attr_reader :search_options
|
5
|
+
attr_reader :relationship_options
|
6
|
+
attr_accessor :enable_model_index
|
7
|
+
attr_accessor :auto_index
|
8
|
+
|
9
|
+
def initialize(klass)
|
10
|
+
@klass = klass
|
11
|
+
end
|
12
|
+
|
13
|
+
def stored_fields
|
14
|
+
@stored_fields ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def field(name, &block)
|
18
|
+
self.stored_fields[name] = block
|
19
|
+
end
|
20
|
+
|
21
|
+
def relationship(options)
|
22
|
+
@relationship_options = options
|
23
|
+
end
|
24
|
+
|
25
|
+
def search(&block)
|
26
|
+
raise "search needs a block" unless block_given?
|
27
|
+
@search_options = SearchConfig.new
|
28
|
+
block.(@search_options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def inspect
|
32
|
+
"#<Lexster::ModelConfig @properties=#{properties.inspect} @search_options=#{@search_options.inspect}>"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class SearchConfig
|
37
|
+
def index_fields
|
38
|
+
@index_fields ||= {}
|
39
|
+
end
|
40
|
+
|
41
|
+
def fulltext_fields
|
42
|
+
@fulltext_fields ||= {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def index(field, options = {}, &block)
|
46
|
+
index_fields[field] = options.merge(block: block)
|
47
|
+
end
|
48
|
+
|
49
|
+
def fulltext(field, options = {}, &block)
|
50
|
+
fulltext_fields[field] = options.merge(block: block)
|
51
|
+
end
|
52
|
+
|
53
|
+
def inspect
|
54
|
+
"#<Lexster::SearchConfig @index_fields=#{index_fields.inspect} @fulltext_fields=#{fulltext_fields.inspect}>"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/lexster/node.rb
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
module Lexster
|
2
|
+
module Node
|
3
|
+
def self.from_hash(hash)
|
4
|
+
node = Neography::Node.new(hash)
|
5
|
+
node.neo_server = Lexster.db
|
6
|
+
node
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
attr_accessor :neo_subref_node
|
11
|
+
|
12
|
+
def neo_subref_rel_type
|
13
|
+
@_neo_subref_rel_type ||= "#{self.name.tableize}_subref"
|
14
|
+
end
|
15
|
+
|
16
|
+
def neo_subref_node_rel_type
|
17
|
+
@_neo_subref_node_rel_type ||= self.name.tableize
|
18
|
+
end
|
19
|
+
|
20
|
+
def delete_command
|
21
|
+
:delete_node
|
22
|
+
end
|
23
|
+
|
24
|
+
def neo_model_index
|
25
|
+
return nil unless Lexster.config.enable_per_model_indexes
|
26
|
+
|
27
|
+
Lexster::logger.info "Node#neo_model_index #{neo_subref_rel_type}"
|
28
|
+
|
29
|
+
gremlin_query = <<-GREMLIN
|
30
|
+
g.createManualIndex(neo_model_index_name, Vertex.class);
|
31
|
+
GREMLIN
|
32
|
+
|
33
|
+
# Lexster.logger.info "subref query:\n#{gremlin_query}"
|
34
|
+
|
35
|
+
script_vars = { neo_model_index_name: neo_model_index_name }
|
36
|
+
|
37
|
+
Lexster.execute_script_or_add_to_batch gremlin_query, script_vars
|
38
|
+
end
|
39
|
+
|
40
|
+
def neo_subref_node
|
41
|
+
return nil unless Lexster.config.enable_subrefs
|
42
|
+
|
43
|
+
@neo_subref_node ||= begin
|
44
|
+
Lexster::logger.info "Node#neo_subref_node #{neo_subref_rel_type}"
|
45
|
+
|
46
|
+
gremlin_query = <<-GREMLIN
|
47
|
+
q = g.v(0).out(neo_subref_rel_type);
|
48
|
+
|
49
|
+
subref = q.hasNext() ? q.next() : null;
|
50
|
+
|
51
|
+
if (!subref) {
|
52
|
+
subref = g.addVertex([name: neo_subref_rel_type, type: name]);
|
53
|
+
g.addEdge(g.v(0), subref, neo_subref_rel_type);
|
54
|
+
}
|
55
|
+
|
56
|
+
subref
|
57
|
+
GREMLIN
|
58
|
+
|
59
|
+
# Lexster.logger.info "subref query:\n#{gremlin_query}"
|
60
|
+
|
61
|
+
script_vars = {
|
62
|
+
neo_subref_rel_type: neo_subref_rel_type,
|
63
|
+
name: self.name
|
64
|
+
}
|
65
|
+
|
66
|
+
Lexster.execute_script_or_add_to_batch gremlin_query, script_vars do |value|
|
67
|
+
Lexster::Node.from_hash(value)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def reset_neo_subref_node
|
73
|
+
@neo_subref_node = nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def neo_search(term, options = {})
|
77
|
+
Lexster.search(self, term, options)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
module InstanceMethods
|
82
|
+
def neo_find_by_id
|
83
|
+
# Lexster::logger.info "Node#neo_find_by_id #{self.class.neo_index_name} #{self.id}"
|
84
|
+
node = Lexster.db.get_node_auto_index(Lexster::UNIQUE_ID_KEY, self.neo_unique_id)
|
85
|
+
node.present? ? Lexster::Node.from_hash(node[0]) : nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def _neo_save
|
89
|
+
return unless Lexster.enabled?
|
90
|
+
|
91
|
+
data = self.to_neo.merge(ar_type: self.class.name, ar_id: self.id, Lexster::UNIQUE_ID_KEY => self.neo_unique_id)
|
92
|
+
data.reject! { |k, v| v.nil? }
|
93
|
+
|
94
|
+
gremlin_query = <<-GREMLIN
|
95
|
+
idx = g.idx('node_auto_index');
|
96
|
+
q = null;
|
97
|
+
if (idx) q = idx.get(unique_id_key, unique_id);
|
98
|
+
|
99
|
+
node = null;
|
100
|
+
if (q && q.hasNext()) {
|
101
|
+
node = q.next();
|
102
|
+
node_data.each {
|
103
|
+
if (node.getProperty(it.key) != it.value) {
|
104
|
+
node.setProperty(it.key, it.value);
|
105
|
+
}
|
106
|
+
}
|
107
|
+
} else {
|
108
|
+
node = g.addVertex(node_data);
|
109
|
+
if (enable_subrefs) g.addEdge(g.v(subref_id), node, neo_subref_node_rel_type);
|
110
|
+
|
111
|
+
if (enable_model_index) g.idx(neo_model_index_name).put('ar_id', node.ar_id, node);
|
112
|
+
}
|
113
|
+
|
114
|
+
node
|
115
|
+
GREMLIN
|
116
|
+
|
117
|
+
script_vars = {
|
118
|
+
unique_id_key: Lexster::UNIQUE_ID_KEY,
|
119
|
+
node_data: data,
|
120
|
+
unique_id: self.neo_unique_id,
|
121
|
+
enable_subrefs: Lexster.config.enable_subrefs,
|
122
|
+
enable_model_index: Lexster.config.enable_per_model_indexes && self.class.lexster_config.enable_model_index
|
123
|
+
}
|
124
|
+
|
125
|
+
if Lexster.config.enable_subrefs
|
126
|
+
script_vars.update(
|
127
|
+
subref_id: self.class.neo_subref_node.neo_id,
|
128
|
+
neo_subref_node_rel_type: self.class.neo_subref_node_rel_type
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
if Lexster.config.enable_per_model_indexes && self.class.lexster_config.enable_model_index
|
133
|
+
script_vars.update(
|
134
|
+
neo_model_index_name: self.class.neo_model_index_name
|
135
|
+
)
|
136
|
+
end
|
137
|
+
|
138
|
+
Lexster::logger.info "Node#neo_save #{self.class.name} #{self.id}"
|
139
|
+
|
140
|
+
node = Lexster.execute_script_or_add_to_batch(gremlin_query, script_vars) do |value|
|
141
|
+
@_neo_representation = Lexster::Node.from_hash(value)
|
142
|
+
end.then do |result|
|
143
|
+
neo_search_index
|
144
|
+
end
|
145
|
+
|
146
|
+
node
|
147
|
+
end
|
148
|
+
|
149
|
+
def neo_search_index
|
150
|
+
return if self.class.lexster_config.search_options.blank? || (
|
151
|
+
self.class.lexster_config.search_options.index_fields.blank? &&
|
152
|
+
self.class.lexster_config.search_options.fulltext_fields.blank?
|
153
|
+
)
|
154
|
+
|
155
|
+
Lexster.ensure_default_fulltext_search_index
|
156
|
+
|
157
|
+
Lexster.db.add_node_to_index(DEFAULT_FULLTEXT_SEARCH_INDEX_NAME, 'ar_type', self.class.name, neo_node.neo_id)
|
158
|
+
|
159
|
+
self.class.lexster_config.search_options.fulltext_fields.each do |field, options|
|
160
|
+
Lexster.db.add_node_to_index(DEFAULT_FULLTEXT_SEARCH_INDEX_NAME, "#{field}_fulltext", neo_helper_get_field_value(field, options), neo_node.neo_id)
|
161
|
+
end
|
162
|
+
|
163
|
+
self.class.lexster_config.search_options.index_fields.each do |field, options|
|
164
|
+
Lexster.db.add_node_to_index(DEFAULT_FULLTEXT_SEARCH_INDEX_NAME, field, neo_helper_get_field_value(field, options), neo_node.neo_id)
|
165
|
+
end
|
166
|
+
|
167
|
+
neo_node
|
168
|
+
end
|
169
|
+
|
170
|
+
def neo_helper_get_field_value(field, options = {})
|
171
|
+
if options[:block]
|
172
|
+
options[:block].call
|
173
|
+
else
|
174
|
+
self.send(field) rescue (raise "No field #{field} for #{self.class.name}")
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def neo_load(hash)
|
179
|
+
Lexster::Node.from_hash(hash)
|
180
|
+
end
|
181
|
+
|
182
|
+
def neo_node
|
183
|
+
_neo_representation
|
184
|
+
end
|
185
|
+
|
186
|
+
def neo_after_relationship_remove(relationship)
|
187
|
+
relationship.neo_destroy
|
188
|
+
end
|
189
|
+
|
190
|
+
def neo_before_relationship_through_remove(record)
|
191
|
+
rel_model, foreign_key_of_owner, foreign_key_of_record = Lexster::Relationship.meta_data[self.class.name.to_s][record.class.name.to_s]
|
192
|
+
rel_model = rel_model.to_s.constantize
|
193
|
+
@__neo_temp_rels ||= {}
|
194
|
+
@__neo_temp_rels[record] = rel_model.where(foreign_key_of_owner => self.id, foreign_key_of_record => record.id).first
|
195
|
+
end
|
196
|
+
|
197
|
+
def neo_after_relationship_through_remove(record)
|
198
|
+
@__neo_temp_rels.each { |record, relationship| relationship.neo_destroy }
|
199
|
+
@__neo_temp_rels.delete(record)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def self.included(receiver)
|
204
|
+
receiver.send :include, Lexster::ModelAdditions
|
205
|
+
receiver.extend ClassMethods
|
206
|
+
receiver.send :include, InstanceMethods
|
207
|
+
Lexster.node_models << receiver
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'lexster/middleware'
|
2
|
+
|
3
|
+
module Lexster
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
initializer "lexster.configure_rails_initialization" do
|
6
|
+
config.after_initialize do
|
7
|
+
Lexster.initialize_all
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
initializer 'lexster.inject_middleware' do |app|
|
12
|
+
app.middleware.use Ndoid::Middleware
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module Lexster
|
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, Lexster.db)
|
8
|
+
end
|
9
|
+
|
10
|
+
def end_node
|
11
|
+
@end_node_from_db ||= @end_node = Neography::Node.load(@end_node, Lexster.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
|
+
|
28
|
+
module InstanceMethods
|
29
|
+
def neo_find_by_id
|
30
|
+
results = Lexster.db.get_relationship_auto_index(Lexster::UNIQUE_ID_KEY, self.neo_unique_id)
|
31
|
+
relationship = results.present? ? Lexster::Relationship.from_hash(results[0]) : nil
|
32
|
+
relationship
|
33
|
+
end
|
34
|
+
|
35
|
+
def _neo_save
|
36
|
+
return unless Lexster.enabled?
|
37
|
+
|
38
|
+
options = self.class.lexster_config.relationship_options
|
39
|
+
|
40
|
+
start_item = self.send(options[:start_node])
|
41
|
+
end_item = self.send(options[:end_node])
|
42
|
+
|
43
|
+
return unless start_item && end_item
|
44
|
+
|
45
|
+
# initialize nodes
|
46
|
+
start_item.neo_node
|
47
|
+
end_item.neo_node
|
48
|
+
|
49
|
+
data = self.to_neo.merge(ar_type: self.class.name, ar_id: self.id, Lexster::UNIQUE_ID_KEY => self.neo_unique_id)
|
50
|
+
data.reject! { |k, v| v.nil? }
|
51
|
+
|
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: Lexster::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
|
+
Lexster::logger.info "Relationship#neo_save #{self.class.name} #{self.id}"
|
88
|
+
|
89
|
+
relationship = Lexster.execute_script_or_add_to_batch gremlin_query, script_vars do |value|
|
90
|
+
Lexster::Relationship.from_hash(value)
|
91
|
+
end
|
92
|
+
|
93
|
+
relationship
|
94
|
+
end
|
95
|
+
|
96
|
+
def neo_load(hash)
|
97
|
+
Lexster::Relationship.from_hash(hash)
|
98
|
+
end
|
99
|
+
|
100
|
+
def neo_relationship
|
101
|
+
_neo_representation
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.included(receiver)
|
106
|
+
receiver.send :include, Lexster::ModelAdditions
|
107
|
+
receiver.send :include, InstanceMethods
|
108
|
+
receiver.extend ClassMethods
|
109
|
+
|
110
|
+
initialize_relationship receiver if Lexster.env_loaded
|
111
|
+
|
112
|
+
Lexster.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
|
+
(Lexster::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
|
+
end
|
163
|
+
end
|