activegraph 10.0.0.pre.alpha.6

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.
Files changed (142) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1989 -0
  3. data/CONTRIBUTORS +12 -0
  4. data/Gemfile +24 -0
  5. data/README.md +107 -0
  6. data/bin/rake +17 -0
  7. data/config/locales/en.yml +5 -0
  8. data/config/neo4j/add_classnames.yml +1 -0
  9. data/config/neo4j/config.yml +38 -0
  10. data/lib/neo4j.rb +116 -0
  11. data/lib/neo4j/active_base.rb +89 -0
  12. data/lib/neo4j/active_node.rb +108 -0
  13. data/lib/neo4j/active_node/callbacks.rb +8 -0
  14. data/lib/neo4j/active_node/dependent.rb +11 -0
  15. data/lib/neo4j/active_node/dependent/association_methods.rb +49 -0
  16. data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +51 -0
  17. data/lib/neo4j/active_node/enum.rb +26 -0
  18. data/lib/neo4j/active_node/has_n.rb +612 -0
  19. data/lib/neo4j/active_node/has_n/association.rb +278 -0
  20. data/lib/neo4j/active_node/has_n/association/rel_factory.rb +61 -0
  21. data/lib/neo4j/active_node/has_n/association/rel_wrapper.rb +23 -0
  22. data/lib/neo4j/active_node/has_n/association_cypher_methods.rb +108 -0
  23. data/lib/neo4j/active_node/id_property.rb +224 -0
  24. data/lib/neo4j/active_node/id_property/accessor.rb +62 -0
  25. data/lib/neo4j/active_node/initialize.rb +21 -0
  26. data/lib/neo4j/active_node/labels.rb +207 -0
  27. data/lib/neo4j/active_node/labels/index.rb +37 -0
  28. data/lib/neo4j/active_node/labels/reloading.rb +21 -0
  29. data/lib/neo4j/active_node/node_list_formatter.rb +13 -0
  30. data/lib/neo4j/active_node/node_wrapper.rb +54 -0
  31. data/lib/neo4j/active_node/orm_adapter.rb +82 -0
  32. data/lib/neo4j/active_node/persistence.rb +187 -0
  33. data/lib/neo4j/active_node/property.rb +60 -0
  34. data/lib/neo4j/active_node/query.rb +76 -0
  35. data/lib/neo4j/active_node/query/query_proxy.rb +374 -0
  36. data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +177 -0
  37. data/lib/neo4j/active_node/query/query_proxy_eager_loading/association_tree.rb +75 -0
  38. data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +110 -0
  39. data/lib/neo4j/active_node/query/query_proxy_find_in_batches.rb +19 -0
  40. data/lib/neo4j/active_node/query/query_proxy_link.rb +139 -0
  41. data/lib/neo4j/active_node/query/query_proxy_methods.rb +302 -0
  42. data/lib/neo4j/active_node/query/query_proxy_methods_of_mass_updating.rb +86 -0
  43. data/lib/neo4j/active_node/query_methods.rb +68 -0
  44. data/lib/neo4j/active_node/reflection.rb +86 -0
  45. data/lib/neo4j/active_node/rels.rb +11 -0
  46. data/lib/neo4j/active_node/scope.rb +166 -0
  47. data/lib/neo4j/active_node/unpersisted.rb +48 -0
  48. data/lib/neo4j/active_node/validations.rb +59 -0
  49. data/lib/neo4j/active_rel.rb +67 -0
  50. data/lib/neo4j/active_rel/callbacks.rb +15 -0
  51. data/lib/neo4j/active_rel/initialize.rb +28 -0
  52. data/lib/neo4j/active_rel/persistence.rb +134 -0
  53. data/lib/neo4j/active_rel/persistence/query_factory.rb +95 -0
  54. data/lib/neo4j/active_rel/property.rb +95 -0
  55. data/lib/neo4j/active_rel/query.rb +101 -0
  56. data/lib/neo4j/active_rel/rel_wrapper.rb +31 -0
  57. data/lib/neo4j/active_rel/related_node.rb +87 -0
  58. data/lib/neo4j/active_rel/types.rb +82 -0
  59. data/lib/neo4j/active_rel/validations.rb +8 -0
  60. data/lib/neo4j/ansi.rb +14 -0
  61. data/lib/neo4j/class_arguments.rb +39 -0
  62. data/lib/neo4j/config.rb +135 -0
  63. data/lib/neo4j/core.rb +14 -0
  64. data/lib/neo4j/core/connection_failed_error.rb +6 -0
  65. data/lib/neo4j/core/cypher_error.rb +37 -0
  66. data/lib/neo4j/core/driver.rb +66 -0
  67. data/lib/neo4j/core/has_uri.rb +63 -0
  68. data/lib/neo4j/core/instrumentable.rb +36 -0
  69. data/lib/neo4j/core/label.rb +158 -0
  70. data/lib/neo4j/core/logging.rb +44 -0
  71. data/lib/neo4j/core/node.rb +23 -0
  72. data/lib/neo4j/core/querable.rb +88 -0
  73. data/lib/neo4j/core/query.rb +487 -0
  74. data/lib/neo4j/core/query_builder.rb +32 -0
  75. data/lib/neo4j/core/query_clauses.rb +727 -0
  76. data/lib/neo4j/core/query_ext.rb +24 -0
  77. data/lib/neo4j/core/query_find_in_batches.rb +49 -0
  78. data/lib/neo4j/core/relationship.rb +13 -0
  79. data/lib/neo4j/core/responses.rb +50 -0
  80. data/lib/neo4j/core/result.rb +33 -0
  81. data/lib/neo4j/core/schema.rb +30 -0
  82. data/lib/neo4j/core/schema_errors.rb +12 -0
  83. data/lib/neo4j/core/wrappable.rb +30 -0
  84. data/lib/neo4j/errors.rb +57 -0
  85. data/lib/neo4j/migration.rb +148 -0
  86. data/lib/neo4j/migrations.rb +27 -0
  87. data/lib/neo4j/migrations/base.rb +77 -0
  88. data/lib/neo4j/migrations/check_pending.rb +20 -0
  89. data/lib/neo4j/migrations/helpers.rb +105 -0
  90. data/lib/neo4j/migrations/helpers/id_property.rb +75 -0
  91. data/lib/neo4j/migrations/helpers/relationships.rb +66 -0
  92. data/lib/neo4j/migrations/helpers/schema.rb +51 -0
  93. data/lib/neo4j/migrations/migration_file.rb +24 -0
  94. data/lib/neo4j/migrations/runner.rb +195 -0
  95. data/lib/neo4j/migrations/schema.rb +44 -0
  96. data/lib/neo4j/migrations/schema_migration.rb +14 -0
  97. data/lib/neo4j/model_schema.rb +139 -0
  98. data/lib/neo4j/paginated.rb +27 -0
  99. data/lib/neo4j/railtie.rb +105 -0
  100. data/lib/neo4j/schema/operation.rb +102 -0
  101. data/lib/neo4j/shared.rb +60 -0
  102. data/lib/neo4j/shared/attributes.rb +216 -0
  103. data/lib/neo4j/shared/callbacks.rb +68 -0
  104. data/lib/neo4j/shared/cypher.rb +37 -0
  105. data/lib/neo4j/shared/declared_properties.rb +204 -0
  106. data/lib/neo4j/shared/declared_property.rb +109 -0
  107. data/lib/neo4j/shared/declared_property/index.rb +37 -0
  108. data/lib/neo4j/shared/enum.rb +167 -0
  109. data/lib/neo4j/shared/filtered_hash.rb +79 -0
  110. data/lib/neo4j/shared/identity.rb +34 -0
  111. data/lib/neo4j/shared/initialize.rb +64 -0
  112. data/lib/neo4j/shared/marshal.rb +23 -0
  113. data/lib/neo4j/shared/mass_assignment.rb +64 -0
  114. data/lib/neo4j/shared/permitted_attributes.rb +28 -0
  115. data/lib/neo4j/shared/persistence.rb +282 -0
  116. data/lib/neo4j/shared/property.rb +240 -0
  117. data/lib/neo4j/shared/query_factory.rb +102 -0
  118. data/lib/neo4j/shared/rel_type_converters.rb +43 -0
  119. data/lib/neo4j/shared/serialized_properties.rb +30 -0
  120. data/lib/neo4j/shared/type_converters.rb +433 -0
  121. data/lib/neo4j/shared/typecasted_attributes.rb +98 -0
  122. data/lib/neo4j/shared/typecaster.rb +53 -0
  123. data/lib/neo4j/shared/validations.rb +44 -0
  124. data/lib/neo4j/tasks/migration.rake +202 -0
  125. data/lib/neo4j/timestamps.rb +11 -0
  126. data/lib/neo4j/timestamps/created.rb +9 -0
  127. data/lib/neo4j/timestamps/updated.rb +9 -0
  128. data/lib/neo4j/transaction.rb +139 -0
  129. data/lib/neo4j/type_converters.rb +7 -0
  130. data/lib/neo4j/undeclared_properties.rb +53 -0
  131. data/lib/neo4j/version.rb +3 -0
  132. data/lib/neo4j/wrapper.rb +4 -0
  133. data/lib/rails/generators/neo4j/migration/migration_generator.rb +14 -0
  134. data/lib/rails/generators/neo4j/migration/templates/migration.erb +9 -0
  135. data/lib/rails/generators/neo4j/model/model_generator.rb +88 -0
  136. data/lib/rails/generators/neo4j/model/templates/migration.erb +9 -0
  137. data/lib/rails/generators/neo4j/model/templates/model.erb +15 -0
  138. data/lib/rails/generators/neo4j/upgrade_v8/templates/migration.erb +17 -0
  139. data/lib/rails/generators/neo4j/upgrade_v8/upgrade_v8_generator.rb +32 -0
  140. data/lib/rails/generators/neo4j_generator.rb +119 -0
  141. data/neo4j.gemspec +51 -0
  142. metadata +421 -0
@@ -0,0 +1,76 @@
1
+ module Neo4j
2
+ module ActiveNode
3
+ # Helper methods to return Neo4j::Core::Query objects. A query object can be used to successively build a cypher query
4
+ #
5
+ # person.query_as(:n).match('n-[:friend]-o').return(o: :name) # Return the names of all the person's friends
6
+ #
7
+ module Query
8
+ extend ActiveSupport::Concern
9
+
10
+ # Returns a Query object with the current node matched the specified variable name
11
+ #
12
+ # @example Return the names of all of Mike's friends
13
+ # # Generates: MATCH (mike:Person), mike-[:friend]-friend WHERE ID(mike) = 123 RETURN friend.name
14
+ # mike.query_as(:mike).match('mike-[:friend]-friend').return(friend: :name)
15
+ #
16
+ # @param node_var [Symbol, String] The variable name to specify in the query
17
+ # @return [Neo4j::Core::Query]
18
+ def query_as(node_var)
19
+ self.class.query_as(node_var, false).where("ID(#{node_var})" => self.neo_id)
20
+ end
21
+
22
+ # Starts a new QueryProxy with the starting identifier set to the given argument and QueryProxy source_object set to the node instance.
23
+ # This method does not exist within QueryProxy and can only be used to start a new chain.
24
+ #
25
+ # @example Start a new QueryProxy chain with the first identifier set manually
26
+ # # Generates: MATCH (s:`Student`), (l:`Lesson`), s-[rel1:`ENROLLED_IN`]->(l:`Lesson`) WHERE ID(s) = {neo_id_17963}
27
+ # student.as(:s).lessons(:l)
28
+ #
29
+ # @param [String, Symbol] node_var The identifier to use within the QueryProxy object
30
+ # @return [Neo4j::ActiveNode::Query::QueryProxy]
31
+ def as(node_var)
32
+ self.class.query_proxy(node: node_var, source_object: self).match_to(self)
33
+ end
34
+
35
+ module ClassMethods
36
+ # Returns a Query object with all nodes for the model matched as the specified variable name
37
+ #
38
+ # @example Return the registration number of all cars owned by a person over the age of 30
39
+ # # Generates: MATCH (person:Person), person-[:owned]-car WHERE person.age > 30 RETURN car.registration_number
40
+ # Person.query_as(:person).where('person.age > 30').match('person-[:owned]-car').return(car: :registration_number)
41
+ #
42
+ # @param [Symbol, String] var The variable name to specify in the query
43
+ # @param [Boolean] with_labels Should labels be used to build the match? There are situations (neo_id used to filter,
44
+ # an early Cypher match has already filtered results) where including labels will degrade performance.
45
+ # @return [Neo4j::Core::Query]
46
+ def query_as(var, with_labels = true)
47
+ query_proxy.query_as(var, with_labels)
48
+ end
49
+
50
+ Neo4j::ActiveNode::Query::QueryProxy::METHODS.each do |method|
51
+ define_method(method) do |*args|
52
+ self.query_proxy.send(method, *args)
53
+ end
54
+ end
55
+
56
+ def query_proxy(options = {})
57
+ Neo4j::ActiveNode::Query::QueryProxy.new(self, nil, options)
58
+ end
59
+
60
+ # Start a new QueryProxy with the starting identifier set to the given argument.
61
+ # This method does not exist within QueryProxy, it can only be called at the class level to create a new QP object.
62
+ # To set an identifier within a QueryProxy chain, give it as the first argument to a chained association.
63
+ #
64
+ # @example Start a new QueryProxy where the first identifier is set manually.
65
+ # # Generates: MATCH (s:`Student`), (result_lessons:`Lesson`), s-[rel1:`ENROLLED_IN`]->(result_lessons:`Lesson`)
66
+ # Student.as(:s).lessons
67
+ #
68
+ # @param [String, Symbol] node_var A string or symbol to use as the starting identifier.
69
+ # @return [Neo4j::ActiveNode::Query::QueryProxy]
70
+ def as(node_var)
71
+ query_proxy(node: node_var, context: self.name)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,374 @@
1
+ module Neo4j
2
+ module ActiveNode
3
+ module Query
4
+ # rubocop:disable Metrics/ClassLength
5
+ class QueryProxy
6
+ # rubocop:enable Metrics/ClassLength
7
+ include Neo4j::ActiveNode::Query::QueryProxyEnumerable
8
+ include Neo4j::ActiveNode::Query::QueryProxyMethods
9
+ include Neo4j::ActiveNode::Query::QueryProxyMethodsOfMassUpdating
10
+ include Neo4j::ActiveNode::Query::QueryProxyFindInBatches
11
+ include Neo4j::ActiveNode::Query::QueryProxyEagerLoading
12
+ include Neo4j::ActiveNode::Dependent::QueryProxyMethods
13
+
14
+ # The most recent node to start a QueryProxy chain.
15
+ # Will be nil when using QueryProxy chains on class methods.
16
+ attr_reader :source_object, :association, :model, :starting_query
17
+
18
+ # QueryProxy is ActiveNode's Cypher DSL. While the name might imply that it creates queries in a general sense,
19
+ # it is actually referring to <tt>Neo4j::Core::Query</tt>, which is a pure Ruby Cypher DSL provided by the <tt>neo4j-core</tt> gem.
20
+ # QueryProxy provides ActiveRecord-like methods for common patterns. When it's not handling CRUD for relationships and queries, it
21
+ # provides ActiveNode's association chaining (`student.lessons.teachers.where(age: 30).hobbies`) and enjoys long walks on the
22
+ # beach.
23
+ #
24
+ # It should not ever be necessary to instantiate a new QueryProxy object directly, it always happens as a result of
25
+ # calling a method that makes use of it.
26
+ #
27
+ # @param [Constant] model The class which included ActiveNode (typically a model, hence the name) from which the query
28
+ # originated.
29
+ # @param [Neo4j::ActiveNode::HasN::Association] association The ActiveNode association (an object created by a <tt>has_one</tt> or
30
+ # <tt>has_many</tt>) that created this object.
31
+ # @param [Hash] options Additional options pertaining to the QueryProxy object. These may include:
32
+ # @option options [String, Symbol] :node_var A string or symbol to be used by Cypher within its query string as an identifier
33
+ # @option options [String, Symbol] :rel_var Same as above but pertaining to a relationship identifier
34
+ # @option options [Range, Integer, Symbol, Hash] :rel_length A Range, a Integer, a Hash or a Symbol to indicate the variable-length/fixed-length
35
+ # qualifier of the relationship. See http://neo4jrb.readthedocs.org/en/latest/Querying.html#variable-length-relationships.
36
+ # @option options [Neo4j::Session] :session The session to be used for this query
37
+ # @option options [Neo4j::ActiveNode] :source_object The node instance at the start of the QueryProxy chain
38
+ # @option options [QueryProxy] :query_proxy An existing QueryProxy chain upon which this new object should be built
39
+ #
40
+ # QueryProxy objects are evaluated lazily.
41
+ def initialize(model, association = nil, options = {})
42
+ @model = model
43
+ @association = association
44
+ @context = options.delete(:context)
45
+ @options = options
46
+ @associations_spec = []
47
+
48
+ instance_vars_from_options!(options)
49
+
50
+ @match_type = @optional ? :optional_match : :match
51
+
52
+ @rel_var = options[:rel] || _rel_chain_var
53
+
54
+ @chain = []
55
+ @params = @query_proxy ? @query_proxy.instance_variable_get('@params') : {}
56
+ end
57
+
58
+ def inspect
59
+ formatted_nodes = Neo4j::ActiveNode::NodeListFormatter.new(to_a)
60
+ "#<QueryProxy #{@context} #{formatted_nodes.inspect}>"
61
+ end
62
+
63
+ attr_reader :start_object, :query_proxy
64
+
65
+ # The current node identifier on deck, so to speak. It is the object that will be returned by calling `each` and the last node link
66
+ # in the QueryProxy chain.
67
+ attr_reader :node_var
68
+ def identity
69
+ @node_var || _result_string(_chain_level + 1)
70
+ end
71
+ alias node_identity identity
72
+
73
+ # The relationship identifier most recently used by the QueryProxy chain.
74
+ attr_reader :rel_var
75
+ def rel_identity
76
+ ActiveSupport::Deprecation.warn 'rel_identity is deprecated and may be removed from future releases, use rel_var instead.', caller
77
+
78
+ @rel_var
79
+ end
80
+
81
+ def params(params)
82
+ new_link.tap { |new_query| new_query._add_params(params) }
83
+ end
84
+
85
+ # Like calling #query_as, but for when you don't care about the variable name
86
+ def query
87
+ query_as(identity)
88
+ end
89
+
90
+ # Build a Neo4j::Core::Query object for the QueryProxy. This is necessary when you want to take an existing QueryProxy chain
91
+ # and work with it from the more powerful (but less friendly) Neo4j::Core::Query.
92
+ # @param [String,Symbol] var The identifier to use for node at this link of the QueryProxy chain.
93
+ #
94
+ # .. code-block:: ruby
95
+ #
96
+ # student.lessons.query_as(:l).with('your cypher here...')
97
+ def query_as(var, with_labels = true)
98
+ query_from_chain(chain, base_query(var, with_labels).params(@params), var)
99
+ .tap { |query| query.proxy_chain_level = _chain_level }
100
+ end
101
+
102
+ def query_from_chain(chain, base_query, var)
103
+ chain.inject(base_query) do |query, link|
104
+ args = link.args(var, rel_var)
105
+
106
+ args.is_a?(Array) ? query.send(link.clause, *args) : query.send(link.clause, args)
107
+ end
108
+ end
109
+
110
+ def base_query(var, with_labels = true)
111
+ if @association
112
+ chain_var = _association_chain_var
113
+ (_association_query_start(chain_var) & _query).break.send(@match_type,
114
+ "(#{chain_var})#{_association_arrow}(#{var}#{_model_label_string})")
115
+ else
116
+ starting_query ? starting_query : _query_model_as(var, with_labels)
117
+ end
118
+ end
119
+
120
+ # param [TrueClass, FalseClass] with_labels This param is used by certain QueryProxy methods that already have the neo_id and
121
+ # therefore do not need labels.
122
+ # The @association_labels instance var is set during init and used during association chaining to keep labels out of Cypher queries.
123
+ def _model_label_string(with_labels = true)
124
+ return if !@model || (!with_labels || @association_labels == false)
125
+ @model.mapped_label_names.map { |label_name| ":`#{label_name}`" }.join
126
+ end
127
+
128
+ # Scope all queries to the current scope.
129
+ #
130
+ # .. code-block:: ruby
131
+ #
132
+ # Comment.where(post_id: 1).scoping do
133
+ # Comment.first
134
+ # end
135
+ #
136
+ # TODO: unscoped
137
+ # Please check unscoped if you want to remove all previous scopes (including
138
+ # the default_scope) during the execution of a block.
139
+ def scoping
140
+ previous = @model.current_scope
141
+ @model.current_scope = self
142
+ yield
143
+ ensure
144
+ @model.current_scope = previous
145
+ end
146
+
147
+ METHODS = %w(where where_not rel_where rel_where_not rel_order order skip limit)
148
+
149
+ METHODS.each do |method|
150
+ define_method(method) { |*args| build_deeper_query_proxy(method.to_sym, args) }
151
+ end
152
+ # Since there are rel_where and rel_order methods, it seems only natural for there to be node_where and node_order
153
+ alias node_where where
154
+ alias node_order order
155
+ alias offset skip
156
+ alias order_by order
157
+
158
+ # Cypher string for the QueryProxy's query. This will not include params. For the full output, see <tt>to_cypher_with_params</tt>.
159
+ delegate :to_cypher, to: :query
160
+
161
+ delegate :print_cypher, to: :query
162
+
163
+ # Returns a string of the cypher query with return objects and params
164
+ # @param [Array] columns array containing symbols of identifiers used in the query
165
+ # @return [String]
166
+ def to_cypher_with_params(columns = [self.identity])
167
+ final_query = query.return_query(columns)
168
+ "#{final_query.to_cypher} | params: #{final_query.send(:merge_params)}"
169
+ end
170
+
171
+ # To add a relationship for the node for the association on this QueryProxy
172
+ def <<(other_node)
173
+ _create_relation_or_defer(other_node)
174
+ self
175
+ end
176
+
177
+ # Executes the relation chain specified in the block, while keeping the current scope
178
+ #
179
+ # @example Load all people that have friends
180
+ # Person.all.branch { friends }.to_a # => Returns a list of `Person`
181
+ #
182
+ # @example Load all people that has old friends
183
+ # Person.all.branch { friends.where('age > 70') }.to_a # => Returns a list of `Person`
184
+ #
185
+ # @yield the block that will be evaluated starting from the current scope
186
+ #
187
+ # @return [QueryProxy] A new QueryProxy
188
+ def branch(&block)
189
+ fail LocalJumpError, 'no block given' if block.nil?
190
+ # `as(identity)` is here to make sure we get the right variable
191
+ # There might be a deeper problem of the variable changing when we
192
+ # traverse an association
193
+ as(identity).instance_eval(&block).query.proxy_as(self.model, identity).tap do |new_query_proxy|
194
+ propagate_context(new_query_proxy)
195
+ end
196
+ end
197
+
198
+ def [](index)
199
+ # TODO: Maybe for this and other methods, use array if already loaded, otherwise
200
+ # use OFFSET and LIMIT 1?
201
+ self.to_a[index]
202
+ end
203
+
204
+ def create(other_nodes, properties = {})
205
+ fail 'Can only create relationships on associations' if !@association
206
+ other_nodes = _nodeify!(*other_nodes)
207
+
208
+ Neo4j::ActiveBase.run_transaction do
209
+ other_nodes.each do |other_node|
210
+ if other_node.neo_id
211
+ other_node.try(:validate_reverse_has_one_core_rel, association, @start_object)
212
+ else
213
+ other_node.save
214
+ end
215
+
216
+ @start_object.association_proxy_cache.clear
217
+
218
+ _create_relationship(other_node, properties)
219
+ end
220
+ end
221
+ end
222
+
223
+ def _nodeify!(*args)
224
+ other_nodes = [args].flatten!.map! do |arg|
225
+ (arg.is_a?(Integer) || arg.is_a?(String)) ? @model.find_by(id: arg) : arg
226
+ end.compact
227
+
228
+ if @model && other_nodes.any? { |other_node| !other_node.class.mapped_label_names.include?(@model.mapped_label_name) }
229
+ fail ArgumentError, "Node must be of the association's class when model is specified"
230
+ end
231
+
232
+ other_nodes
233
+ end
234
+
235
+ def _create_relationship(other_node_or_nodes, properties)
236
+ association._create_relationship(@start_object, other_node_or_nodes, properties)
237
+ end
238
+
239
+ def read_attribute_for_serialization(*args)
240
+ to_a.map { |o| o.read_attribute_for_serialization(*args) }
241
+ end
242
+
243
+ delegate :to_ary, to: :to_a
244
+
245
+ # QueryProxy objects act as a representation of a model at the class level so we pass through calls
246
+ # This allows us to define class functions for reusable query chaining or for end-of-query aggregation/summarizing
247
+ def method_missing(method_name, *args, &block)
248
+ if @model && @model.respond_to?(method_name)
249
+ scoping { @model.public_send(method_name, *args, &block) }
250
+ else
251
+ super
252
+ end
253
+ end
254
+
255
+ def respond_to_missing?(method_name, include_all = false)
256
+ (@model && @model.respond_to?(method_name, include_all)) || super
257
+ end
258
+
259
+ def optional?
260
+ @optional == true
261
+ end
262
+
263
+ attr_reader :context
264
+
265
+ def new_link(node_var = nil)
266
+ self.clone.tap do |new_query_proxy|
267
+ new_query_proxy.instance_variable_set('@result_cache', nil)
268
+ new_query_proxy.instance_variable_set('@node_var', node_var) if node_var
269
+ end
270
+ end
271
+
272
+ def unpersisted_start_object?
273
+ @start_object && @start_object.new_record?
274
+ end
275
+
276
+ protected
277
+
278
+ def _create_relation_or_defer(other_node)
279
+ if @start_object._persisted_obj
280
+ create(other_node, {})
281
+ elsif @association
282
+ @start_object.defer_create(@association.name, other_node)
283
+ else
284
+ fail 'Another crazy error!'
285
+ end
286
+ end
287
+
288
+ # Methods are underscored to prevent conflict with user class methods
289
+ def _add_params(params)
290
+ @params = @params.merge(params)
291
+ end
292
+
293
+ def _add_links(links)
294
+ @chain += links
295
+ end
296
+
297
+ def _query_model_as(var, with_labels = true)
298
+ _query.break.send(@match_type, _match_arg(var, with_labels))
299
+ end
300
+
301
+ # @param [String, Symbol] var The Cypher identifier to use within the match string
302
+ # @param [Boolean] with_labels Send "true" to include model labels where possible.
303
+ def _match_arg(var, with_labels)
304
+ if @model && with_labels != false
305
+ labels = @model.respond_to?(:mapped_label_names) ? _model_label_string : @model
306
+ {var.to_sym => labels}
307
+ else
308
+ var.to_sym
309
+ end
310
+ end
311
+
312
+ def _query
313
+ Neo4j::ActiveBase.new_query(context: @context)
314
+ end
315
+
316
+ def _result_string(index = nil)
317
+ "result_#{(association || model).try(:name)}#{index}".downcase.tr(':', '').to_sym
318
+ end
319
+
320
+ def _session
321
+ (@session || (@model && @model.neo4j_session)).tap do |session|
322
+ fail 'No session found!' if session.nil?
323
+ end
324
+ end
325
+
326
+ def _association_arrow(properties = {}, create = false)
327
+ @association && @association.arrow_cypher(@rel_var, properties, create, false, @rel_length)
328
+ end
329
+
330
+ def _chain_level
331
+ (@query_proxy ? @query_proxy._chain_level : (@chain_level || 0)) + 1
332
+ end
333
+
334
+ def _association_chain_var
335
+ fail 'Crazy error' if !(start_object || @query_proxy)
336
+
337
+ if start_object
338
+ :"#{start_object.class.name.gsub('::', '_').downcase}#{start_object.neo_id}"
339
+ else
340
+ @query_proxy.node_var || :"node#{_chain_level}"
341
+ end
342
+ end
343
+
344
+ def _association_query_start(var)
345
+ # TODO: Better error
346
+ fail 'Crazy error' if !(object = (start_object || @query_proxy))
347
+
348
+ object.query_as(var)
349
+ end
350
+
351
+ def _rel_chain_var
352
+ :"rel#{_chain_level - 1}"
353
+ end
354
+
355
+ attr_writer :context
356
+
357
+ private
358
+
359
+ def instance_vars_from_options!(options)
360
+ @node_var, @session, @source_object, @starting_query, @optional,
361
+ @start_object, @query_proxy, @chain_level, @association_labels,
362
+ @rel_length = options.values_at(:node, :session, :source_object, :starting_query, :optional,
363
+ :start_object, :query_proxy, :chain_level, :association_labels, :rel_length)
364
+ end
365
+
366
+ def build_deeper_query_proxy(method, args)
367
+ new_link.tap do |new_query_proxy|
368
+ Link.for_args(@model, method, args, association).each { |link| new_query_proxy._add_links(link) }
369
+ end
370
+ end
371
+ end
372
+ end
373
+ end
374
+ end