neo4j 3.0.0.alpha.9 → 3.0.0.alpha.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,254 +0,0 @@
1
- module Neo4j
2
- module ActiveNode
3
- # An abstraction layer to quickly build and return objects from the Neo4j Core Query class.
4
- # It auto-increments node and relationship identifiers, uses relationships pre-defined in models to create match methods, and automatically maps
5
- # results to collections.
6
- class QuickQuery
7
- attr_reader :quick_query
8
-
9
- # Initialize sets the values of @node_on_deck and defines @rel_on_deck, among other things.
10
- # The objects on deck are the objects implicitly modified when calling a method without specifying an identifier.
11
- # They are auto-incremented at stages throughout the class.
12
- def initialize(caller, as, caller_class = nil)
13
- @caller_class = caller_class || caller
14
- @node_on_deck = @return_obj = as.to_sym
15
- @current_node_index = 2
16
- @current_rel_index = 1
17
- @rel_on_deck = nil
18
- @return_set = false
19
- @caller = caller
20
- @quick_query = caller.query_as(as)
21
- @identifiers = [@node_on_deck]
22
- set_rel_methods(@caller_class)
23
- return self
24
- end
25
-
26
- # sends the #to_cypher method to the core query class
27
- def to_cypher
28
- @quick_query.return(@return_obj).to_cypher
29
- end
30
-
31
-
32
- # Creates methods that send cleaned up arguments to the Core Query class
33
- # Pass a symbol to specify the target identifier.
34
- # Pass a hash to specify match parameters.
35
- # Pass a valid cypher string to send directly to Core Query.
36
- # If an identifier is not specified, it will apply them to the on-deck node.
37
- # @example
38
- # Student.qq.where(name: 'chris')
39
- # Student.qq.lessons.where(:n2, name: 'history 101')
40
- # Student.qq.lessons()
41
- CUSTOM_METHODS = %w[where set set_props]
42
-
43
- CUSTOM_METHODS.each do |method|
44
- class_eval(%Q{
45
- def #{method}(*args)
46
- result = prepare(args)
47
- final_query(__method__, result)
48
- return self
49
- end
50
- }, __FILE__, __LINE__)
51
- end
52
-
53
-
54
- # Creates methods that send strings directly to Core Query class
55
- LITERAL_METHODS = %w[limit skip match offset]
56
-
57
- LITERAL_METHODS.each do |method|
58
- class_eval(%Q{
59
- def #{method}(s)
60
- @quick_query = @quick_query.send(__method__, s)
61
- return self
62
- end
63
- }, __FILE__, __LINE__)
64
- end
65
-
66
- # Sends #return to the core query class, does not map to an enumerable.
67
- # Assumes the @return_obj if nothing is specified.
68
- # if you want distinct, pass boolean true
69
- # @example
70
- # Student.qq.lessons.return(:n2)
71
- # Student.qq.lessons.return(:n2, true)
72
- def return(*args)
73
- obj_sym = args.select{|el| el.is_a?(Symbol) }.first || @return_obj
74
- distinct = args.select{|el| el.is_a?(TrueClass) }.first || false
75
-
76
- r = final_return(obj_sym, distinct)
77
- @quick_query = @quick_query.return(r)
78
- return self
79
- end
80
-
81
- # Returns an enumerable of the query. If return has not been set, will set it to the on_deck node
82
- def to_a(distinct = false)
83
- @return_set ? result : self.return(distinct).to_a
84
- end
85
-
86
- # Same as to_a but with distinct set true
87
- def to_a!
88
- self.to_a(true)
89
- end
90
-
91
- # Set order for return.
92
- # @param prop_sym [Symbol] a symbol matching the property on the return class use for order
93
- # @param desc_bool [Boolean] boolean to dictate whether to sort descending. Defaults false, use true to descend
94
- def order(prop_sym, desc_bool = false)
95
- arg = "#{@return_obj}.#{prop_sym.to_s}"
96
- end_arg = desc_bool ? arg + ' DESC' : arg
97
- @quick_query = @quick_query.order(end_arg)
98
- return self
99
- end
100
-
101
- private
102
-
103
- def prepare(args)
104
- target = args.select{|el| el.is_a?(Symbol) }
105
- send_target = target.empty? ? @node_on_deck : target.first
106
- result = process_args(args, send_target)
107
- end
108
-
109
- def result
110
- response = @quick_query.response
111
- if response.is_a?(Neo4j::Server::CypherResponse)
112
- Neo4j::Session.current.search_result_to_enumerable_first_column(response)
113
- else
114
- Neo4j::Embedded::ResultWrapper.new(response, @quick_query.to_cypher).map{|x| x[0] }
115
- end
116
- end
117
-
118
- def final_return(return_obj, distinct)
119
- @return_set = true
120
- distinct ? "distinct #{return_obj.to_s}" : return_obj.to_sym
121
- end
122
-
123
- def final_query(method, result)
124
- @quick_query = @quick_query.send(method, result)
125
- end
126
-
127
- # Creates match methods based on the caller's relationships defined in the model.
128
- # It works best when a relationship is defined explicitly with a direction and a receiving/incoming model.
129
- # This fires once on initialize, again every time a matcher method is called to build methods for the next step.
130
- # The dynamic classes accept the following:
131
- # -a symbol to refer to the destination node
132
- # -a hash with key :rel_as, value a symbol to act as relationship identifier (otherwise it uses r#{@current_rel_index})
133
- # -a hash with key :rel_where containing other hashes of {parameter: value} to specify relationship conditions
134
- # -hashes of {parameter: value} that specify conditions for the destination nodes
135
- # @example
136
- # Student.qq.lessons
137
- # Student.qq.lessons(rel_as: :student_status)
138
- # Student.qq.lessons(rel_as: :student_status, rel_where: { grade: 'b-' })
139
- # Student.qq.lessons(rel_as: :student_status, rel_where: { grade: 'b-' }, :lessinzzz, class: 'history 101').teachers
140
- def set_rel_methods(caller_class)
141
- caller_class._decl_rels.each { |k,v|
142
- if v.target_class.nil?
143
- class_eval(%Q{
144
- def #{k}(*args)
145
- process_rel(args, "#{v.rel_type}")
146
- return self
147
- end}, __FILE__, __LINE__)
148
- else
149
- class_eval(%Q{
150
- def #{k}(*args)
151
- process_rel(args, "#{v.rel_type}", "#{v.target_class.name}")
152
- return self
153
- end}, __FILE__, __LINE__)
154
- end
155
- }
156
- end
157
-
158
- # Called when a matcher method is called
159
- # args are the arguments sent along with the matcher method
160
- # rel_type is the defined relationship type
161
- # right_label is the label to use for the destination, if available. It's the "right label" cause it's on the right side... get it?
162
- # A label can only be used if the model defines the destination class explicitly.
163
- def process_rel(args, rel_type, right_label = nil)
164
- from_node = @node_on_deck
165
- hashes, strings = setup_rel_args(args)
166
- set_on_deck(args)
167
-
168
- hashes = process_rel_hashes(hashes) unless hashes.nil?
169
- end_args = process_args([hashes] + strings, @node_on_deck) unless hashes.nil? && strings.nil?
170
-
171
- destination = right_label.nil? ? @node_on_deck : "(#{@node_on_deck}:#{right_label})"
172
- @quick_query = @quick_query.match("#{from_node}-[#{@rel_on_deck}:`#{rel_type}`]-#{destination}")
173
- @quick_query = @quick_query.where(end_args) unless end_args.nil?
174
- set_rel_methods(right_label.constantize) unless right_label.nil?
175
-
176
- return self
177
- end
178
-
179
- # Prepares arguments passed with the relationship matcher. It finds hashes, which contain properties and values, and strings, which
180
- # which literal cypher phrases.
181
- def setup_rel_args(args)
182
- hashes = args.select{|el| el.is_a?(Hash) }.first
183
- strings = args.select{|el| el.is_a?(String) }
184
- return hashes, strings
185
- end
186
-
187
- # Queues up the new node and relationship. This is only used during process_rel.
188
- def set_on_deck(args)
189
- @node_on_deck = @return_obj = node_as(args.select{|el| el.is_a?(Symbol) }.first)
190
- @rel_on_deck = new_rel_id
191
- @identifiers.push([@node_on_deck, @rel_on_deck])
192
- end
193
-
194
- # Prepares relationship-specific hashes found during the setup_rel_args method. Removes anything it finds from the hash and sends it back.
195
- def process_rel_hashes(hashes)
196
- @rel_on_deck = set_rel_as(hashes)
197
- @identifiers.push @rel_on_deck
198
-
199
- set_rel_where(hashes)
200
-
201
- hashes.delete_if{|k,v| k == :rel_as || k == :rel_where }
202
- end
203
-
204
- # Creates a new node identifier
205
- def new_node_id
206
- n = "n#{@current_node_index}"
207
- @current_node_index += 1
208
- return n
209
- end
210
-
211
- # Creates a new relationship identifier
212
- def new_rel_id
213
- r = "r#{@current_rel_index}"
214
- @current_rel_index += 1
215
- return r
216
- end
217
-
218
- def node_as(node_as)
219
- node_as.nil? ? new_node_id : node_as.to_sym
220
- end
221
-
222
- def set_rel_as(h)
223
- h.has_key?(:rel_as) ? h[:rel_as] : new_rel_id
224
- end
225
-
226
- def set_rel_where(h)
227
- if h.has_key?(:rel_where)
228
- @quick_query = @quick_query.where(Hash[@rel_on_deck => h[:rel_where]])
229
- end
230
- end
231
-
232
- # Utility method used to split up passed values and fix syntax to match Neo4j Core Query class
233
- def process_args(args, where_target)
234
- end_args = []
235
- args.each do |arg|
236
- if arg.is_a?(String)
237
- end_args.push process_string(arg, where_target)
238
- elsif arg.is_a?(Symbol)
239
- @node_on_deck = arg
240
- @return_obj = arg if @return_obj.nil?
241
- elsif arg.is_a?(Hash)
242
- end_args.push Hash[where_target => arg] unless arg.empty?
243
- end
244
- end
245
- return end_args
246
- end
247
-
248
- # Attempts to determine whether the passed string already contains a node/rel identifier or if it needs one prepended.
249
- def process_string(arg, where_target)
250
- @identifiers.include?(arg.split('.').first.to_sym) ? arg : "#{where_target}.#{arg}"
251
- end
252
- end
253
- end
254
- end