activegraph 10.0.0.pre.alpha.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1989 -0
- data/CONTRIBUTORS +12 -0
- data/Gemfile +24 -0
- data/README.md +107 -0
- data/bin/rake +17 -0
- data/config/locales/en.yml +5 -0
- data/config/neo4j/add_classnames.yml +1 -0
- data/config/neo4j/config.yml +38 -0
- data/lib/neo4j.rb +116 -0
- data/lib/neo4j/active_base.rb +89 -0
- data/lib/neo4j/active_node.rb +108 -0
- data/lib/neo4j/active_node/callbacks.rb +8 -0
- data/lib/neo4j/active_node/dependent.rb +11 -0
- data/lib/neo4j/active_node/dependent/association_methods.rb +49 -0
- data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +51 -0
- data/lib/neo4j/active_node/enum.rb +26 -0
- data/lib/neo4j/active_node/has_n.rb +612 -0
- data/lib/neo4j/active_node/has_n/association.rb +278 -0
- data/lib/neo4j/active_node/has_n/association/rel_factory.rb +61 -0
- data/lib/neo4j/active_node/has_n/association/rel_wrapper.rb +23 -0
- data/lib/neo4j/active_node/has_n/association_cypher_methods.rb +108 -0
- data/lib/neo4j/active_node/id_property.rb +224 -0
- data/lib/neo4j/active_node/id_property/accessor.rb +62 -0
- data/lib/neo4j/active_node/initialize.rb +21 -0
- data/lib/neo4j/active_node/labels.rb +207 -0
- data/lib/neo4j/active_node/labels/index.rb +37 -0
- data/lib/neo4j/active_node/labels/reloading.rb +21 -0
- data/lib/neo4j/active_node/node_list_formatter.rb +13 -0
- data/lib/neo4j/active_node/node_wrapper.rb +54 -0
- data/lib/neo4j/active_node/orm_adapter.rb +82 -0
- data/lib/neo4j/active_node/persistence.rb +187 -0
- data/lib/neo4j/active_node/property.rb +60 -0
- data/lib/neo4j/active_node/query.rb +76 -0
- data/lib/neo4j/active_node/query/query_proxy.rb +374 -0
- data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +177 -0
- data/lib/neo4j/active_node/query/query_proxy_eager_loading/association_tree.rb +75 -0
- data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +110 -0
- data/lib/neo4j/active_node/query/query_proxy_find_in_batches.rb +19 -0
- data/lib/neo4j/active_node/query/query_proxy_link.rb +139 -0
- data/lib/neo4j/active_node/query/query_proxy_methods.rb +302 -0
- data/lib/neo4j/active_node/query/query_proxy_methods_of_mass_updating.rb +86 -0
- data/lib/neo4j/active_node/query_methods.rb +68 -0
- data/lib/neo4j/active_node/reflection.rb +86 -0
- data/lib/neo4j/active_node/rels.rb +11 -0
- data/lib/neo4j/active_node/scope.rb +166 -0
- data/lib/neo4j/active_node/unpersisted.rb +48 -0
- data/lib/neo4j/active_node/validations.rb +59 -0
- data/lib/neo4j/active_rel.rb +67 -0
- data/lib/neo4j/active_rel/callbacks.rb +15 -0
- data/lib/neo4j/active_rel/initialize.rb +28 -0
- data/lib/neo4j/active_rel/persistence.rb +134 -0
- data/lib/neo4j/active_rel/persistence/query_factory.rb +95 -0
- data/lib/neo4j/active_rel/property.rb +95 -0
- data/lib/neo4j/active_rel/query.rb +101 -0
- data/lib/neo4j/active_rel/rel_wrapper.rb +31 -0
- data/lib/neo4j/active_rel/related_node.rb +87 -0
- data/lib/neo4j/active_rel/types.rb +82 -0
- data/lib/neo4j/active_rel/validations.rb +8 -0
- data/lib/neo4j/ansi.rb +14 -0
- data/lib/neo4j/class_arguments.rb +39 -0
- data/lib/neo4j/config.rb +135 -0
- data/lib/neo4j/core.rb +14 -0
- data/lib/neo4j/core/connection_failed_error.rb +6 -0
- data/lib/neo4j/core/cypher_error.rb +37 -0
- data/lib/neo4j/core/driver.rb +66 -0
- data/lib/neo4j/core/has_uri.rb +63 -0
- data/lib/neo4j/core/instrumentable.rb +36 -0
- data/lib/neo4j/core/label.rb +158 -0
- data/lib/neo4j/core/logging.rb +44 -0
- data/lib/neo4j/core/node.rb +23 -0
- data/lib/neo4j/core/querable.rb +88 -0
- data/lib/neo4j/core/query.rb +487 -0
- data/lib/neo4j/core/query_builder.rb +32 -0
- data/lib/neo4j/core/query_clauses.rb +727 -0
- data/lib/neo4j/core/query_ext.rb +24 -0
- data/lib/neo4j/core/query_find_in_batches.rb +49 -0
- data/lib/neo4j/core/relationship.rb +13 -0
- data/lib/neo4j/core/responses.rb +50 -0
- data/lib/neo4j/core/result.rb +33 -0
- data/lib/neo4j/core/schema.rb +30 -0
- data/lib/neo4j/core/schema_errors.rb +12 -0
- data/lib/neo4j/core/wrappable.rb +30 -0
- data/lib/neo4j/errors.rb +57 -0
- data/lib/neo4j/migration.rb +148 -0
- data/lib/neo4j/migrations.rb +27 -0
- data/lib/neo4j/migrations/base.rb +77 -0
- data/lib/neo4j/migrations/check_pending.rb +20 -0
- data/lib/neo4j/migrations/helpers.rb +105 -0
- data/lib/neo4j/migrations/helpers/id_property.rb +75 -0
- data/lib/neo4j/migrations/helpers/relationships.rb +66 -0
- data/lib/neo4j/migrations/helpers/schema.rb +51 -0
- data/lib/neo4j/migrations/migration_file.rb +24 -0
- data/lib/neo4j/migrations/runner.rb +195 -0
- data/lib/neo4j/migrations/schema.rb +44 -0
- data/lib/neo4j/migrations/schema_migration.rb +14 -0
- data/lib/neo4j/model_schema.rb +139 -0
- data/lib/neo4j/paginated.rb +27 -0
- data/lib/neo4j/railtie.rb +105 -0
- data/lib/neo4j/schema/operation.rb +102 -0
- data/lib/neo4j/shared.rb +60 -0
- data/lib/neo4j/shared/attributes.rb +216 -0
- data/lib/neo4j/shared/callbacks.rb +68 -0
- data/lib/neo4j/shared/cypher.rb +37 -0
- data/lib/neo4j/shared/declared_properties.rb +204 -0
- data/lib/neo4j/shared/declared_property.rb +109 -0
- data/lib/neo4j/shared/declared_property/index.rb +37 -0
- data/lib/neo4j/shared/enum.rb +167 -0
- data/lib/neo4j/shared/filtered_hash.rb +79 -0
- data/lib/neo4j/shared/identity.rb +34 -0
- data/lib/neo4j/shared/initialize.rb +64 -0
- data/lib/neo4j/shared/marshal.rb +23 -0
- data/lib/neo4j/shared/mass_assignment.rb +64 -0
- data/lib/neo4j/shared/permitted_attributes.rb +28 -0
- data/lib/neo4j/shared/persistence.rb +282 -0
- data/lib/neo4j/shared/property.rb +240 -0
- data/lib/neo4j/shared/query_factory.rb +102 -0
- data/lib/neo4j/shared/rel_type_converters.rb +43 -0
- data/lib/neo4j/shared/serialized_properties.rb +30 -0
- data/lib/neo4j/shared/type_converters.rb +433 -0
- data/lib/neo4j/shared/typecasted_attributes.rb +98 -0
- data/lib/neo4j/shared/typecaster.rb +53 -0
- data/lib/neo4j/shared/validations.rb +44 -0
- data/lib/neo4j/tasks/migration.rake +202 -0
- data/lib/neo4j/timestamps.rb +11 -0
- data/lib/neo4j/timestamps/created.rb +9 -0
- data/lib/neo4j/timestamps/updated.rb +9 -0
- data/lib/neo4j/transaction.rb +139 -0
- data/lib/neo4j/type_converters.rb +7 -0
- data/lib/neo4j/undeclared_properties.rb +53 -0
- data/lib/neo4j/version.rb +3 -0
- data/lib/neo4j/wrapper.rb +4 -0
- data/lib/rails/generators/neo4j/migration/migration_generator.rb +14 -0
- data/lib/rails/generators/neo4j/migration/templates/migration.erb +9 -0
- data/lib/rails/generators/neo4j/model/model_generator.rb +88 -0
- data/lib/rails/generators/neo4j/model/templates/migration.erb +9 -0
- data/lib/rails/generators/neo4j/model/templates/model.erb +15 -0
- data/lib/rails/generators/neo4j/upgrade_v8/templates/migration.erb +17 -0
- data/lib/rails/generators/neo4j/upgrade_v8/upgrade_v8_generator.rb +32 -0
- data/lib/rails/generators/neo4j_generator.rb +119 -0
- data/neo4j.gemspec +51 -0
- 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
|