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

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.
@@ -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