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.
@@ -0,0 +1,168 @@
1
+ module Neoid
2
+ class Batch
3
+ def default_options=(value)
4
+ @default_options = value
5
+ end
6
+
7
+ def self.default_options
8
+ @default_options ||= { batch_size: 200, individual_promises: true }
9
+ end
10
+
11
+ def self.current_batch
12
+ Thread.current[:neoid_current_batch]
13
+ end
14
+
15
+ def self.current_batch=(batch)
16
+ Thread.current[:neoid_current_batch] = batch
17
+ end
18
+
19
+ def self.reset_current_batch
20
+ Thread.current[:neoid_current_batch] = nil
21
+ end
22
+
23
+ def initialize(options={}, &block)
24
+ if options.respond_to?(:call) && !block
25
+ block = options
26
+ options = {}
27
+ end
28
+
29
+ options.reverse_merge!(self.class.default_options)
30
+
31
+ @options = options
32
+ @block = block
33
+ end
34
+
35
+ def <<(command)
36
+ commands << command
37
+
38
+ if commands.length >= @options[:batch_size]
39
+ flush_batch
40
+ end
41
+
42
+ if @options[:individual_promises]
43
+ promise = SingleResultPromiseProxy.new(command)
44
+ thens << promise
45
+ promise
46
+ end
47
+ end
48
+
49
+ def commands
50
+ @commands ||= []
51
+ end
52
+
53
+ def thens
54
+ @thens ||= []
55
+ end
56
+
57
+ def count
58
+ @commands ? @commands.count : 0
59
+ end
60
+
61
+ def results
62
+ @results ||= []
63
+ end
64
+
65
+ def run
66
+ self.class.current_batch = self
67
+
68
+ begin
69
+ @block.call(self)
70
+ ensure
71
+ self.class.reset_current_batch
72
+ end
73
+
74
+ Neoid.logger.info "Neoid batch (#{commands.length} commands)"
75
+
76
+ flush_batch
77
+
78
+ BatchPromiseProxy.new(results)
79
+ end
80
+
81
+ private
82
+ def flush_batch
83
+ return [] if commands.empty?
84
+ current_results = nil
85
+
86
+ # results = Neoid.db.batch(*commands).collect { |result| result['body'] }
87
+
88
+ benchmark = Benchmark.measure {
89
+ current_results = Neoid.db.batch(*commands).collect { |result| result['body'] }
90
+ }
91
+ Neoid.logger.info "Neoid batch (#{commands.length} commands) - #{benchmark}"
92
+ commands.clear
93
+
94
+ process_results(current_results)
95
+
96
+ thens.zip(current_results).each { |t, result| t.perform(result) }
97
+
98
+ thens.clear
99
+
100
+ results.concat current_results
101
+ end
102
+
103
+ def process_results(results)
104
+ results.map! do |result|
105
+ return result unless result.is_a?(Hash) && result['self'] && result['self'][%r[^https?://.*/(node|relationship)/\d+]]
106
+
107
+ type = case $1
108
+ when 'node' then Neoid::Node
109
+ when 'relationship' then Neoid::Relationship
110
+ else return result
111
+ end
112
+
113
+ type.from_hash(result)
114
+ end
115
+ end
116
+ end
117
+
118
+ # returned from a full batch, after it has been executed,
119
+ # so a `.then` can be chained after the batch do ... end
120
+ # it proxies all methods to the result
121
+ class BatchPromiseProxy
122
+ def initialize(*results)
123
+ @results = results
124
+ end
125
+
126
+ def method_missing(method, *args)
127
+ @results.send(method, *args)
128
+ end
129
+
130
+ def then
131
+ yield(*@results)
132
+ end
133
+ end
134
+
135
+ # returned from adding (<<) an item to a batch in a batch block:
136
+ # Neoid.batch { |batch| (batch << [:neography_command, param]).is_a?(SingleResultPromiseProxy) }
137
+ # so a `.then` can be chained:
138
+ # Neoid.batch { |batch| (batch << [:neography_command, param]).then { |result| puts result } }
139
+ # the `then` is called once the batch is flushed with the result of the single job in the batch
140
+ # it proxies all methods to the result, so in case it is returned (like in Neoid.execute_script_or_add_to_batch)
141
+ # the result of the method will be proxied to the result from the batch. See Node#neo_save
142
+ class SingleResultPromiseProxy
143
+ def initialize(*args)
144
+ end
145
+
146
+ attr_accessor :result
147
+
148
+ def result
149
+ raise "Accessed result too soon" unless @result
150
+ @result
151
+ end
152
+
153
+ def method_missing(method, *args)
154
+ result.send(method, *args)
155
+ end
156
+
157
+ def then(&block)
158
+ @then = block
159
+ self
160
+ end
161
+
162
+ def perform(result)
163
+ @result = result
164
+ return unless @then
165
+ @then.call(result)
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,6 @@
1
+ module Neoid
2
+ class Config
3
+ attr_accessor :enable_subrefs
4
+ attr_accessor :enable_per_model_indexes
5
+ end
6
+ end
@@ -5,10 +5,12 @@ module Ndoid
5
5
  end
6
6
 
7
7
  def call(env)
8
- old, Thread.current[:neoid_enabled] = Thread.current[:neoid_enabled], true
8
+ old_enabled, Thread.current[:neoid_enabled] = Thread.current[:neoid_enabled], true
9
+ old_batch, Thread.current[:neoid_current_batch] = Thread.current[:neoid_current_batch], nil
9
10
  @app.call(env)
10
11
  ensure
11
- Thread.current[:neoid_enabled] = old
12
+ Thread.current[:neoid_enabled] = old_enabled
13
+ Thread.current[:neoid_current_batch] = old_batch
12
14
  end
13
15
  end
14
16
  end
@@ -2,18 +2,26 @@ module Neoid
2
2
  module ModelAdditions
3
3
  module ClassMethods
4
4
  attr_reader :neoid_config
5
- attr_reader :neoid_options
6
5
 
7
6
  def neoid_config
8
7
  @neoid_config ||= Neoid::ModelConfig.new(self)
9
8
  end
10
9
 
11
10
  def neoidable(options = {})
11
+ # defaults
12
+ neoid_config.auto_index = true
13
+ neoid_config.enable_model_index = true # but the Neoid.enable_per_model_indexes is false by default. all models will be true only if the primary option is turned on.
14
+
12
15
  yield(neoid_config) if block_given?
13
- @neoid_options = options
16
+
17
+ options.each do |key, value|
18
+ raise "Neoid #{self.name} model options: No such option #{key}" unless neoid_config.respond_to?("#{key}=")
19
+ neoid_config.send("#{key}=", value)
20
+ end
14
21
  end
15
-
16
- def neo_index_name
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 Neoid.config.enable_per_model_indexes || neoid_config.enable_model_index
17
25
  @index_name ||= "#{self.name.tableize}_index"
18
26
  end
19
27
  end
@@ -37,9 +45,51 @@ module Neoid
37
45
  end
38
46
  end
39
47
 
40
- def neo_resave
48
+ def neo_save_after_model_save
49
+ return unless self.class.neoid_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
+ Neoid::logger.info "Neoid#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 Neoid::Batch.current_batch
77
+ # Neoid::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
+ # Neoid::logger.info "Neoid#neo_destroy entity not found #{self.class.name} #{self.id}"
83
+ # end
84
+ # end
85
+
41
86
  _reset_neo_representation
42
- neo_update
87
+
88
+ true
89
+ end
90
+
91
+ def neo_unique_id
92
+ "#{self.class.name}:#{self.id}"
43
93
  end
44
94
 
45
95
  protected
@@ -52,14 +102,7 @@ module Neoid
52
102
 
53
103
  private
54
104
  def _neo_representation
55
- @_neo_representation ||= begin
56
- results = neo_find_by_id
57
- if results
58
- neo_load(results.first['self'])
59
- else
60
- neo_create
61
- end
62
- end
105
+ @_neo_representation ||= neo_find_by_id || neo_save
63
106
  end
64
107
 
65
108
  def _reset_neo_representation
@@ -70,7 +113,9 @@ module Neoid
70
113
  def self.included(receiver)
71
114
  receiver.extend ClassMethods
72
115
  receiver.send :include, InstanceMethods
73
- Neoid.models << receiver
116
+
117
+ receiver.after_save :neo_save_after_model_save
118
+ receiver.after_destroy :neo_destroy
74
119
  end
75
120
  end
76
121
  end
@@ -3,6 +3,8 @@ module Neoid
3
3
  attr_reader :properties
4
4
  attr_reader :search_options
5
5
  attr_reader :relationship_options
6
+ attr_accessor :enable_model_index
7
+ attr_accessor :auto_index
6
8
 
7
9
  def initialize(klass)
8
10
  @klass = klass
@@ -1,15 +1,46 @@
1
1
  module Neoid
2
2
  module Node
3
+ def self.from_hash(hash)
4
+ node = Neography::Node.new(hash)
5
+ node.neo_server = Neoid.db
6
+ node
7
+ end
8
+
3
9
  module ClassMethods
10
+ attr_accessor :neo_subref_node
11
+
4
12
  def neo_subref_rel_type
5
13
  @_neo_subref_rel_type ||= "#{self.name.tableize}_subref"
6
14
  end
15
+
7
16
  def neo_subref_node_rel_type
8
17
  @_neo_subref_node_rel_type ||= self.name.tableize
9
18
  end
10
-
19
+
20
+ def delete_command
21
+ :delete_node
22
+ end
23
+
24
+ def neo_model_index
25
+ return nil unless Neoid.config.enable_per_model_indexes
26
+
27
+ Neoid::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
+ # Neoid.logger.info "subref query:\n#{gremlin_query}"
34
+
35
+ script_vars = { neo_model_index_name: neo_model_index_name }
36
+
37
+ Neoid.execute_script_or_add_to_batch gremlin_query, script_vars
38
+ end
39
+
11
40
  def neo_subref_node
12
- @_neo_subref_node ||= begin
41
+ return nil unless Neoid.config.enable_subrefs
42
+
43
+ @neo_subref_node ||= begin
13
44
  Neoid::logger.info "Node#neo_subref_node #{neo_subref_rel_type}"
14
45
 
15
46
  gremlin_query = <<-GREMLIN
@@ -22,19 +53,26 @@ module Neoid
22
53
  g.addEdge(g.v(0), subref, neo_subref_rel_type);
23
54
  }
24
55
 
25
- g.createManualIndex(neo_index_name, Vertex.class);
26
-
27
56
  subref
28
57
  GREMLIN
29
58
 
30
- Neoid.logger.info "subref query:\n#{gremlin_query}"
59
+ # Neoid.logger.info "subref query:\n#{gremlin_query}"
31
60
 
32
- node = Neography::Node.load(Neoid.db.execute_script(gremlin_query, neo_subref_rel_type: neo_subref_rel_type, name: self.name, neo_index_name: self.neo_index_name))
61
+ script_vars = {
62
+ neo_subref_rel_type: neo_subref_rel_type,
63
+ name: self.name
64
+ }
33
65
 
34
- node
66
+ Neoid.execute_script_or_add_to_batch gremlin_query, script_vars do |value|
67
+ Neoid::Node.from_hash(value)
68
+ end
35
69
  end
36
70
  end
37
71
 
72
+ def reset_neo_subref_node
73
+ @neo_subref_node = nil
74
+ end
75
+
38
76
  def neo_search(term, options = {})
39
77
  Neoid.search(self, term, options)
40
78
  end
@@ -42,48 +80,72 @@ module Neoid
42
80
 
43
81
  module InstanceMethods
44
82
  def neo_find_by_id
45
- Neoid::logger.info "Node#neo_find_by_id #{self.class.neo_index_name} #{self.id}"
46
- Neoid.db.get_node_index(self.class.neo_index_name, :ar_id, self.id)
47
- rescue Neography::NotFoundException
48
- nil
83
+ # 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, self.neo_unique_id)
85
+ node.present? ? Neoid::Node.from_hash(node[0]) : nil
49
86
  end
50
87
 
51
- def neo_create
88
+ def _neo_save
52
89
  return unless Neoid.enabled?
53
-
54
- data = self.to_neo.merge(ar_type: self.class.name, ar_id: self.id)
90
+
91
+ data = self.to_neo.merge(ar_type: self.class.name, ar_id: self.id, Neoid::UNIQUE_ID_KEY => self.neo_unique_id)
55
92
  data.reject! { |k, v| v.nil? }
56
-
57
- node = Neography::Node.create(data)
58
-
59
- retires = 2
60
- begin
61
- Neography::Relationship.create(
62
- self.class.neo_subref_node_rel_type,
63
- self.class.neo_subref_node,
64
- node
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_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: Neoid::UNIQUE_ID_KEY,
119
+ node_data: data,
120
+ unique_id: self.neo_unique_id,
121
+ enable_subrefs: Neoid.config.enable_subrefs,
122
+ enable_model_index: Neoid.config.enable_per_model_indexes && self.class.neoid_config.enable_model_index
123
+ }
124
+
125
+ if Neoid.config.enable_subrefs
126
+ script_vars.update(
127
+ subref_id: self.class.neo_subref_node.neo_id,
128
+ neo_subref_rel_type: self.class.neo_subref_rel_type
65
129
  )
66
- rescue
67
- # something must've happened to the cached subref node, reset and retry
68
- @_neo_subref_node = nil
69
- retires -= 1
70
- retry if retires > 0
71
130
  end
72
131
 
73
- Neoid.db.add_node_to_index(self.class.neo_index_name, :ar_id, self.id, node)
132
+ if Neoid.config.enable_per_model_indexes && self.class.neoid_config.enable_model_index
133
+ script_vars.update(
134
+ neo_model_index_name: self.class.neo_model_index_name
135
+ )
136
+ end
74
137
 
75
- Neoid::logger.info "Node#neo_create #{self.class.name} #{self.id}, index = #{self.class.neo_index_name}"
76
-
77
- neo_search_index
138
+ Neoid::logger.info "Node#neo_save #{self.class.name} #{self.id}"
139
+
140
+ node = Neoid.execute_script_or_add_to_batch(gremlin_query, script_vars) do |value|
141
+ @_neo_representation = Neoid::Node.from_hash(value)
142
+ end.then do |result|
143
+ neo_search_index
144
+ end
78
145
 
79
146
  node
80
147
  end
81
-
82
- def neo_update
83
- Neoid.db.set_node_properties(neo_node, self.to_neo)
84
- neo_search_index
85
- end
86
-
148
+
87
149
  def neo_search_index
88
150
  return if self.class.neoid_config.search_options.blank? || (
89
151
  self.class.neoid_config.search_options.index_fields.blank? &&
@@ -113,29 +175,20 @@ module Neoid
113
175
  end
114
176
  end
115
177
 
116
- def neo_load(node)
117
- Neography::Node.load(node)
178
+ def neo_load(hash)
179
+ Neoid::Node.from_hash(hash)
118
180
  end
119
181
 
120
182
  def neo_node
121
183
  _neo_representation
122
184
  end
123
-
124
- def neo_destroy
125
- return unless neo_node
126
- Neoid.db.remove_node_from_index(DEFAULT_FULLTEXT_SEARCH_INDEX_NAME, neo_node)
127
-
128
- Neoid.db.remove_node_from_index(self.class.neo_index_name, neo_node)
129
- neo_node.del
130
- _reset_neo_representation
131
- end
132
185
 
133
186
  def neo_after_relationship_remove(relationship)
134
187
  relationship.neo_destroy
135
188
  end
136
189
 
137
190
  def neo_before_relationship_through_remove(record)
138
- rel_model, foreign_key_of_owner, foreign_key_of_record = Neoid.config[:relationship_meta_data][self.class.name.to_s][record.class.name.to_s]
191
+ rel_model, foreign_key_of_owner, foreign_key_of_record = Neoid::Relationship.meta_data[self.class.name.to_s][record.class.name.to_s]
139
192
  rel_model = rel_model.to_s.constantize
140
193
  @__neo_temp_rels ||= {}
141
194
  @__neo_temp_rels[record] = rel_model.where(foreign_key_of_owner => self.id, foreign_key_of_record => record.id).first
@@ -152,10 +205,6 @@ module Neoid
152
205
  receiver.extend ClassMethods
153
206
  receiver.send :include, InstanceMethods
154
207
  Neoid.node_models << receiver
155
-
156
- receiver.after_create :neo_create
157
- receiver.after_update :neo_update
158
- receiver.after_destroy :neo_destroy
159
208
  end
160
209
  end
161
210
  end