activegraph 11.0.0.beta.1-java
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2016 -0
- data/CONTRIBUTORS +12 -0
- data/Gemfile +24 -0
- data/README.md +111 -0
- data/activegraph.gemspec +52 -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 +35 -0
- data/lib/active_graph.rb +123 -0
- data/lib/active_graph/ansi.rb +14 -0
- data/lib/active_graph/attribute_set.rb +32 -0
- data/lib/active_graph/base.rb +77 -0
- data/lib/active_graph/class_arguments.rb +39 -0
- data/lib/active_graph/config.rb +135 -0
- data/lib/active_graph/core.rb +14 -0
- data/lib/active_graph/core/connection_failed_error.rb +6 -0
- data/lib/active_graph/core/cypher_error.rb +37 -0
- data/lib/active_graph/core/entity.rb +11 -0
- data/lib/active_graph/core/instrumentable.rb +37 -0
- data/lib/active_graph/core/label.rb +135 -0
- data/lib/active_graph/core/logging.rb +44 -0
- data/lib/active_graph/core/node.rb +15 -0
- data/lib/active_graph/core/querable.rb +41 -0
- data/lib/active_graph/core/query.rb +485 -0
- data/lib/active_graph/core/query_builder.rb +18 -0
- data/lib/active_graph/core/query_clauses.rb +727 -0
- data/lib/active_graph/core/query_ext.rb +24 -0
- data/lib/active_graph/core/query_find_in_batches.rb +46 -0
- data/lib/active_graph/core/record.rb +51 -0
- data/lib/active_graph/core/result.rb +31 -0
- data/lib/active_graph/core/schema.rb +65 -0
- data/lib/active_graph/core/schema_errors.rb +12 -0
- data/lib/active_graph/core/wrappable.rb +30 -0
- data/lib/active_graph/errors.rb +59 -0
- data/lib/active_graph/lazy_attribute_hash.rb +38 -0
- data/lib/active_graph/migration.rb +148 -0
- data/lib/active_graph/migrations.rb +27 -0
- data/lib/active_graph/migrations/base.rb +77 -0
- data/lib/active_graph/migrations/check_pending.rb +20 -0
- data/lib/active_graph/migrations/helpers.rb +105 -0
- data/lib/active_graph/migrations/helpers/id_property.rb +72 -0
- data/lib/active_graph/migrations/helpers/relationships.rb +66 -0
- data/lib/active_graph/migrations/helpers/schema.rb +63 -0
- data/lib/active_graph/migrations/migration_file.rb +24 -0
- data/lib/active_graph/migrations/runner.rb +195 -0
- data/lib/active_graph/migrations/schema.rb +64 -0
- data/lib/active_graph/migrations/schema_migration.rb +14 -0
- data/lib/active_graph/model_schema.rb +139 -0
- data/lib/active_graph/node.rb +110 -0
- data/lib/active_graph/node/callbacks.rb +8 -0
- data/lib/active_graph/node/dependent.rb +11 -0
- data/lib/active_graph/node/dependent/association_methods.rb +49 -0
- data/lib/active_graph/node/dependent/query_proxy_methods.rb +52 -0
- data/lib/active_graph/node/dependent_callbacks.rb +31 -0
- data/lib/active_graph/node/enum.rb +26 -0
- data/lib/active_graph/node/has_n.rb +602 -0
- data/lib/active_graph/node/has_n/association.rb +278 -0
- data/lib/active_graph/node/has_n/association/rel_factory.rb +61 -0
- data/lib/active_graph/node/has_n/association/rel_wrapper.rb +23 -0
- data/lib/active_graph/node/has_n/association_cypher_methods.rb +108 -0
- data/lib/active_graph/node/id_property.rb +224 -0
- data/lib/active_graph/node/id_property/accessor.rb +62 -0
- data/lib/active_graph/node/initialize.rb +21 -0
- data/lib/active_graph/node/labels.rb +207 -0
- data/lib/active_graph/node/labels/index.rb +37 -0
- data/lib/active_graph/node/labels/reloading.rb +21 -0
- data/lib/active_graph/node/node_list_formatter.rb +13 -0
- data/lib/active_graph/node/node_wrapper.rb +54 -0
- data/lib/active_graph/node/orm_adapter.rb +82 -0
- data/lib/active_graph/node/persistence.rb +186 -0
- data/lib/active_graph/node/property.rb +60 -0
- data/lib/active_graph/node/query.rb +76 -0
- data/lib/active_graph/node/query/query_proxy.rb +367 -0
- data/lib/active_graph/node/query/query_proxy_eager_loading.rb +177 -0
- data/lib/active_graph/node/query/query_proxy_eager_loading/association_tree.rb +75 -0
- data/lib/active_graph/node/query/query_proxy_enumerable.rb +110 -0
- data/lib/active_graph/node/query/query_proxy_find_in_batches.rb +19 -0
- data/lib/active_graph/node/query/query_proxy_link.rb +139 -0
- data/lib/active_graph/node/query/query_proxy_methods.rb +303 -0
- data/lib/active_graph/node/query/query_proxy_methods_of_mass_updating.rb +99 -0
- data/lib/active_graph/node/query_methods.rb +68 -0
- data/lib/active_graph/node/reflection.rb +86 -0
- data/lib/active_graph/node/rels.rb +11 -0
- data/lib/active_graph/node/scope.rb +166 -0
- data/lib/active_graph/node/unpersisted.rb +48 -0
- data/lib/active_graph/node/validations.rb +59 -0
- data/lib/active_graph/paginated.rb +27 -0
- data/lib/active_graph/railtie.rb +108 -0
- data/lib/active_graph/relationship.rb +68 -0
- data/lib/active_graph/relationship/callbacks.rb +21 -0
- data/lib/active_graph/relationship/initialize.rb +28 -0
- data/lib/active_graph/relationship/persistence.rb +133 -0
- data/lib/active_graph/relationship/persistence/query_factory.rb +95 -0
- data/lib/active_graph/relationship/property.rb +92 -0
- data/lib/active_graph/relationship/query.rb +99 -0
- data/lib/active_graph/relationship/rel_wrapper.rb +31 -0
- data/lib/active_graph/relationship/related_node.rb +87 -0
- data/lib/active_graph/relationship/types.rb +80 -0
- data/lib/active_graph/relationship/validations.rb +8 -0
- data/lib/active_graph/schema/operation.rb +102 -0
- data/lib/active_graph/shared.rb +48 -0
- data/lib/active_graph/shared/attributes.rb +217 -0
- data/lib/active_graph/shared/callbacks.rb +66 -0
- data/lib/active_graph/shared/cypher.rb +37 -0
- data/lib/active_graph/shared/declared_properties.rb +204 -0
- data/lib/active_graph/shared/declared_property.rb +109 -0
- data/lib/active_graph/shared/declared_property/index.rb +37 -0
- data/lib/active_graph/shared/enum.rb +167 -0
- data/lib/active_graph/shared/filtered_hash.rb +79 -0
- data/lib/active_graph/shared/identity.rb +34 -0
- data/lib/active_graph/shared/initialize.rb +65 -0
- data/lib/active_graph/shared/marshal.rb +23 -0
- data/lib/active_graph/shared/mass_assignment.rb +63 -0
- data/lib/active_graph/shared/permitted_attributes.rb +28 -0
- data/lib/active_graph/shared/persistence.rb +272 -0
- data/lib/active_graph/shared/property.rb +249 -0
- data/lib/active_graph/shared/query_factory.rb +122 -0
- data/lib/active_graph/shared/rel_type_converters.rb +43 -0
- data/lib/active_graph/shared/serialized_properties.rb +30 -0
- data/lib/active_graph/shared/type_converters.rb +439 -0
- data/lib/active_graph/shared/typecasted_attributes.rb +99 -0
- data/lib/active_graph/shared/typecaster.rb +53 -0
- data/lib/active_graph/shared/validations.rb +44 -0
- data/lib/active_graph/tasks/migration.rake +204 -0
- data/lib/active_graph/timestamps.rb +11 -0
- data/lib/active_graph/timestamps/created.rb +9 -0
- data/lib/active_graph/timestamps/updated.rb +9 -0
- data/lib/active_graph/transaction.rb +22 -0
- data/lib/active_graph/transactions.rb +57 -0
- data/lib/active_graph/type_converters.rb +7 -0
- data/lib/active_graph/undeclared_properties.rb +53 -0
- data/lib/active_graph/version.rb +3 -0
- data/lib/active_graph/wrapper.rb +4 -0
- data/lib/rails/generators/active_graph/migration/migration_generator.rb +16 -0
- data/lib/rails/generators/active_graph/migration/templates/migration.erb +9 -0
- data/lib/rails/generators/active_graph/model/model_generator.rb +89 -0
- data/lib/rails/generators/active_graph/model/templates/migration.erb +11 -0
- data/lib/rails/generators/active_graph/model/templates/model.erb +15 -0
- data/lib/rails/generators/active_graph/upgrade_v8/templates/migration.erb +17 -0
- data/lib/rails/generators/active_graph/upgrade_v8/upgrade_v8_generator.rb +34 -0
- data/lib/rails/generators/active_graph_generator.rb +121 -0
- metadata +423 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
require 'active_support/inflector/inflections'
|
|
2
|
+
require 'active_graph/class_arguments'
|
|
3
|
+
|
|
4
|
+
module ActiveGraph
|
|
5
|
+
module Node
|
|
6
|
+
module HasN
|
|
7
|
+
class Association
|
|
8
|
+
include ActiveGraph::Shared::RelTypeConverters
|
|
9
|
+
include ActiveGraph::Node::Dependent::AssociationMethods
|
|
10
|
+
include ActiveGraph::Node::HasN::AssociationCypherMethods
|
|
11
|
+
|
|
12
|
+
attr_reader :type, :name, :relationship, :direction, :dependent, :model_class
|
|
13
|
+
|
|
14
|
+
def initialize(type, direction, name, options = {type: nil})
|
|
15
|
+
validate_init_arguments(type, direction, name, options)
|
|
16
|
+
@type = type.to_sym
|
|
17
|
+
@name = name
|
|
18
|
+
@direction = direction.to_sym
|
|
19
|
+
@target_class_name_from_name = name.to_s.classify
|
|
20
|
+
apply_vars_from_options(options)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def derive_model_class
|
|
24
|
+
refresh_model_class! if pending_model_refresh?
|
|
25
|
+
return @model_class unless @model_class.nil?
|
|
26
|
+
return nil if relationship_class.nil?
|
|
27
|
+
dir_class = direction == :in ? :from_class : :to_class
|
|
28
|
+
return false if relationship_class.send(dir_class).to_s.to_sym == :any
|
|
29
|
+
relationship_class.send(dir_class)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def refresh_model_class!
|
|
33
|
+
@pending_model_refresh = @target_classes_or_nil = nil
|
|
34
|
+
|
|
35
|
+
# Using #to_s on purpose here to take care of classes/strings/symbols
|
|
36
|
+
@model_class = ClassArguments.constantize_argument(@model_class.to_s) if @model_class
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def queue_model_refresh!
|
|
40
|
+
@pending_model_refresh = true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def target_class_option(model_class)
|
|
44
|
+
case model_class
|
|
45
|
+
when nil
|
|
46
|
+
@target_class_name_from_name ? "#{association_model_namespace}::#{@target_class_name_from_name}" : @target_class_name_from_name
|
|
47
|
+
when Array
|
|
48
|
+
model_class.map { |sub_model_class| target_class_option(sub_model_class) }
|
|
49
|
+
when false
|
|
50
|
+
false
|
|
51
|
+
else
|
|
52
|
+
model_class.to_s[0, 2] == '::' ? model_class.to_s : "::#{model_class}"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def pending_model_refresh?
|
|
57
|
+
!!@pending_model_refresh
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def target_class_names
|
|
61
|
+
option = target_class_option(derive_model_class)
|
|
62
|
+
|
|
63
|
+
@target_class_names ||= if option.is_a?(Array)
|
|
64
|
+
option.map(&:to_s)
|
|
65
|
+
elsif option
|
|
66
|
+
[option.to_s]
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def inverse_of?(other)
|
|
71
|
+
origin_association == other
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def target_classes
|
|
75
|
+
ClassArguments.constantize_argument(target_class_names)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def target_classes_or_nil
|
|
79
|
+
@target_classes_or_nil ||= discovered_model if target_class_names
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def target_where_clause(var = name)
|
|
83
|
+
return if model_class == false
|
|
84
|
+
|
|
85
|
+
Array.new(target_classes).map do |target_class|
|
|
86
|
+
"#{var}:`#{target_class.mapped_label_name}`"
|
|
87
|
+
end.join(' OR ')
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def discovered_model
|
|
91
|
+
target_classes.select do |constant|
|
|
92
|
+
constant.ancestors.include?(::ActiveGraph::Node)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def target_class
|
|
97
|
+
return @target_class if @target_class
|
|
98
|
+
|
|
99
|
+
return if !(target_class_names && target_class_names.size == 1)
|
|
100
|
+
|
|
101
|
+
class_const = ClassArguments.constantize_argument(target_class_names[0])
|
|
102
|
+
|
|
103
|
+
@target_class = class_const
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def relationship_type(create = false)
|
|
107
|
+
case
|
|
108
|
+
when relationship_class
|
|
109
|
+
relationship_class_type
|
|
110
|
+
when !@relationship_type.nil?
|
|
111
|
+
@relationship_type
|
|
112
|
+
when @origin
|
|
113
|
+
origin_type
|
|
114
|
+
else
|
|
115
|
+
# I think that this line is no longer readed since we require either
|
|
116
|
+
# `type`, `rel_class`, or `origin` in associations
|
|
117
|
+
(create || exceptional_target_class?) && decorated_rel_type(@name)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
attr_reader :relationship_class_name
|
|
122
|
+
|
|
123
|
+
def relationship_class_type
|
|
124
|
+
relationship_class.type.to_sym
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def relationship_class
|
|
128
|
+
@relationship_class ||= @relationship_class_name && @relationship_class_name.constantize
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def unique?
|
|
132
|
+
return relationship_class.unique? if rel_class?
|
|
133
|
+
@origin ? origin_association.unique? : !!@unique
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def creates_unique_option
|
|
137
|
+
@unique || :none
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def create_method
|
|
141
|
+
unique? ? :create_unique : :create
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def _create_relationship(start_object, node_or_nodes, properties)
|
|
145
|
+
RelFactory.create(start_object, node_or_nodes, properties, self)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def relationship_class?
|
|
149
|
+
!!relationship_class
|
|
150
|
+
end
|
|
151
|
+
alias rel_class? relationship_class?
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
def association_model_namespace
|
|
156
|
+
ActiveGraph::Config.association_model_namespace_string
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def get_direction(create, reverse = false)
|
|
160
|
+
dir = (create && @direction == :both) ? :out : @direction
|
|
161
|
+
if reverse
|
|
162
|
+
case dir
|
|
163
|
+
when :in then :out
|
|
164
|
+
when :out then :in
|
|
165
|
+
else :both
|
|
166
|
+
end
|
|
167
|
+
else
|
|
168
|
+
dir
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def origin_association
|
|
173
|
+
target_class && target_class.associations[@origin]
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def origin_type
|
|
177
|
+
origin_association.relationship_type
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
private
|
|
181
|
+
|
|
182
|
+
def apply_vars_from_options(options)
|
|
183
|
+
@relationship_class_name = options[:rel_class] && options[:rel_class].to_s
|
|
184
|
+
@relationship_type = options[:type] && options[:type].to_sym
|
|
185
|
+
|
|
186
|
+
@model_class = options[:model_class]
|
|
187
|
+
@origin = options[:origin] && options[:origin].to_sym
|
|
188
|
+
@dependent = options[:dependent].try(:to_sym)
|
|
189
|
+
@unique = options[:unique]
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Return basic details about association as declared in the model
|
|
193
|
+
# @example
|
|
194
|
+
# has_many :in, :bands, type: :has_band
|
|
195
|
+
def base_declaration
|
|
196
|
+
"#{type} #{direction.inspect}, #{name.inspect}"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def validate_init_arguments(type, direction, name, options)
|
|
200
|
+
validate_association_options!(name, options)
|
|
201
|
+
validate_option_combinations(options)
|
|
202
|
+
validate_dependent(options[:dependent].try(:to_sym))
|
|
203
|
+
check_valid_type_and_dir(type, direction)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# the ":labels" option is not used by the association per-say.
|
|
207
|
+
# Instead, if provided,it is used by the association getter as a default getter options argument
|
|
208
|
+
VALID_ASSOCIATION_OPTION_KEYS = [:type, :origin, :model_class, :rel_class, :dependent, :before, :after, :unique, :labels]
|
|
209
|
+
|
|
210
|
+
def validate_association_options!(_association_name, options)
|
|
211
|
+
ClassArguments.validate_argument!(options[:model_class], 'model_class')
|
|
212
|
+
ClassArguments.validate_argument!(options[:rel_class], 'rel_class')
|
|
213
|
+
|
|
214
|
+
message = case
|
|
215
|
+
when (message = type_keys_error_message(options.keys))
|
|
216
|
+
message
|
|
217
|
+
when !(unknown_keys = options.keys - VALID_ASSOCIATION_OPTION_KEYS).empty?
|
|
218
|
+
"Unknown option(s) specified: #{unknown_keys.join(', ')}"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
fail ArgumentError, message if message
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def type_keys_error_message(keys)
|
|
225
|
+
type_keys = (keys & [:type, :origin, :rel_class])
|
|
226
|
+
if type_keys.size > 1
|
|
227
|
+
"Only one of 'type', 'origin', or 'rel_class' options are allowed for associations"
|
|
228
|
+
elsif type_keys.empty?
|
|
229
|
+
"The 'type' option must be specified( even if it is `nil`) or `origin`/`rel_class` must be specified"
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def check_valid_type_and_dir(type, direction)
|
|
234
|
+
fail ArgumentError, "Invalid association type: #{type.inspect} (valid value: :has_many and :has_one)" if ![:has_many, :has_one].include?(type.to_sym)
|
|
235
|
+
fail ArgumentError, "Invalid direction: #{direction.inspect} (valid value: :out, :in, and :both)" if ![:out, :in, :both].include?(direction.to_sym)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def validate_option_combinations(options)
|
|
239
|
+
[[:type, :origin],
|
|
240
|
+
[:type, :rel_class],
|
|
241
|
+
[:origin, :rel_class]].each do |key1, key2|
|
|
242
|
+
if options[key1] && options[key2]
|
|
243
|
+
fail ArgumentError, "Cannot specify both :#{key1} and :#{key2} (#{base_declaration})"
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Determine if model class as derived from the association name would be different than the one specified via the model_class key
|
|
249
|
+
# @example
|
|
250
|
+
# has_many :friends # Would return false
|
|
251
|
+
# has_many :friends, model_class: Friend # Would return false
|
|
252
|
+
# has_many :friends, model_class: Person # Would return true
|
|
253
|
+
def exceptional_target_class?
|
|
254
|
+
# TODO: Exceptional if target_class.nil?? (when model_class false)
|
|
255
|
+
|
|
256
|
+
target_class && target_class.name != @target_class_name_from_name
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def validate_origin!
|
|
260
|
+
return if not @origin
|
|
261
|
+
|
|
262
|
+
association = origin_association
|
|
263
|
+
|
|
264
|
+
message = case
|
|
265
|
+
when !target_class
|
|
266
|
+
'Cannot use :origin without a model_class (implied or explicit)'
|
|
267
|
+
when !association
|
|
268
|
+
"Origin `#{@origin.inspect}` association not found for #{target_class} (specified in #{base_declaration})"
|
|
269
|
+
when @direction == association.direction
|
|
270
|
+
"Origin `#{@origin.inspect}` (specified in #{base_declaration}) has same direction `#{@direction}`)"
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
fail ArgumentError, message if message
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module ActiveGraph::Node::HasN
|
|
2
|
+
class Association
|
|
3
|
+
class RelFactory
|
|
4
|
+
[:start_object, :other_node_or_nodes, :properties, :association].tap do |accessors|
|
|
5
|
+
attr_reader(*accessors)
|
|
6
|
+
private(*accessors)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.create(start_object, other_node_or_nodes, properties, association)
|
|
10
|
+
factory = new(start_object, other_node_or_nodes, properties, association)
|
|
11
|
+
factory._create_relationship
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def _create_relationship
|
|
15
|
+
creator = association.relationship_class ? :rel_class : :factory
|
|
16
|
+
send(:"_create_relationship_with_#{creator}")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def initialize(start_object, other_node_or_nodes, properties, association)
|
|
22
|
+
@start_object = start_object
|
|
23
|
+
@other_node_or_nodes = other_node_or_nodes
|
|
24
|
+
@properties = properties
|
|
25
|
+
@association = association
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def _create_relationship_with_rel_class
|
|
29
|
+
Array(other_node_or_nodes).each do |other_node|
|
|
30
|
+
node_props = _nodes_for_create(other_node, :from_node, :to_node)
|
|
31
|
+
association.relationship_class.create!(properties.merge(node_props))
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def _create_relationship_with_factory
|
|
36
|
+
Array(other_node_or_nodes).each do |other_node|
|
|
37
|
+
wrapper = _rel_wrapper(properties)
|
|
38
|
+
base = _match_query(other_node, wrapper)
|
|
39
|
+
factory = ActiveGraph::Shared::RelQueryFactory.new(wrapper, wrapper.rel_identifier)
|
|
40
|
+
factory.base_query = base
|
|
41
|
+
factory.query.exec
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def _match_query(other_node, wrapper)
|
|
46
|
+
nodes = _nodes_for_create(other_node, wrapper.from_node_identifier, wrapper.to_node_identifier)
|
|
47
|
+
ActiveGraph::Base.new_query.match_nodes(nodes)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def _nodes_for_create(other_node, from_node_id, to_node_id)
|
|
51
|
+
nodes = [@start_object, other_node]
|
|
52
|
+
nodes.reverse! if association.direction == :in
|
|
53
|
+
{from_node_id => nodes[0], to_node_id => nodes[1]}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def _rel_wrapper(properties)
|
|
57
|
+
ActiveGraph::Node::HasN::Association::RelWrapper.new(association, properties)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class ActiveGraph::Node::HasN::Association
|
|
2
|
+
# Provides the interface needed to interact with the Relationship query factory.
|
|
3
|
+
class RelWrapper
|
|
4
|
+
include ActiveGraph::Shared::Cypher::RelIdentifiers
|
|
5
|
+
include ActiveGraph::Shared::Cypher::CreateMethod
|
|
6
|
+
|
|
7
|
+
attr_reader :type, :association
|
|
8
|
+
attr_accessor :properties
|
|
9
|
+
private :association
|
|
10
|
+
alias props_for_create properties
|
|
11
|
+
|
|
12
|
+
def initialize(association, properties = {})
|
|
13
|
+
@association = association
|
|
14
|
+
@properties = properties
|
|
15
|
+
@type = association.relationship_type(true)
|
|
16
|
+
creates_unique(association.creates_unique_option) if association.unique?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def persisted?
|
|
20
|
+
false
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module ActiveGraph
|
|
2
|
+
module Node
|
|
3
|
+
module HasN
|
|
4
|
+
module AssociationCypherMethods
|
|
5
|
+
# Return cypher partial query string for the relationship part of a MATCH (arrow / relationship definition)
|
|
6
|
+
def arrow_cypher(var = nil, properties = {}, create = false, reverse = false, length = nil)
|
|
7
|
+
validate_origin!
|
|
8
|
+
|
|
9
|
+
if create && length.present?
|
|
10
|
+
fail(ArgumentError, 'rel_length option cannot be specified when creating a relationship')
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
direction_cypher(get_relationship_cypher(var, properties, create, length), create, reverse)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def direction_cypher(relationship_cypher, create, reverse = false)
|
|
19
|
+
case get_direction(create, reverse)
|
|
20
|
+
when :out
|
|
21
|
+
"-#{relationship_cypher}->"
|
|
22
|
+
when :in
|
|
23
|
+
"<-#{relationship_cypher}-"
|
|
24
|
+
when :both
|
|
25
|
+
"-#{relationship_cypher}-"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def get_relationship_cypher(var, properties, create, length)
|
|
30
|
+
relationship_type = relationship_type(create)
|
|
31
|
+
relationship_name_cypher = ":`#{relationship_type}`" if relationship_type
|
|
32
|
+
rel_length_cypher = cypher_for_rel_length(length)
|
|
33
|
+
properties_string = get_properties_string(properties)
|
|
34
|
+
|
|
35
|
+
"[#{var}#{relationship_name_cypher}#{rel_length_cypher}#{properties_string}]"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def get_properties_string(properties)
|
|
39
|
+
p = properties.map do |key, value|
|
|
40
|
+
"#{key}: #{value.inspect}"
|
|
41
|
+
end.join(', ')
|
|
42
|
+
p.empty? ? '' : " {#{p}}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
VALID_REL_LENGTH_SYMBOLS = {
|
|
46
|
+
any: '*'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
def cypher_for_rel_length(length)
|
|
50
|
+
return nil if length.blank?
|
|
51
|
+
|
|
52
|
+
validate_rel_length!(length)
|
|
53
|
+
|
|
54
|
+
case length
|
|
55
|
+
when Symbol then VALID_REL_LENGTH_SYMBOLS[length]
|
|
56
|
+
when Integer then "*#{length}"
|
|
57
|
+
when Range then cypher_for_range_rel_length(length)
|
|
58
|
+
when Hash then cypher_for_hash_rel_length(length)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def cypher_for_range_rel_length(length)
|
|
63
|
+
range_end = length.end
|
|
64
|
+
range_end = nil if range_end == Float::INFINITY
|
|
65
|
+
"*#{length.begin}..#{range_end}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def cypher_for_hash_rel_length(length)
|
|
69
|
+
range_end = length[:max]
|
|
70
|
+
range_end = nil if range_end == Float::INFINITY
|
|
71
|
+
"*#{length[:min]}..#{range_end}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def validate_rel_length!(length)
|
|
75
|
+
message = rel_length_error_message(length)
|
|
76
|
+
fail(ArgumentError, "Invalid value for rel_length (#{length.inspect}): #{message}") if message
|
|
77
|
+
true
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def rel_length_error_message(length)
|
|
81
|
+
case length
|
|
82
|
+
when Integer then 'cannot be negative' if length < 0
|
|
83
|
+
when Symbol then rel_length_symbol_error_message(length)
|
|
84
|
+
when Range then rel_length_range_error_message(length)
|
|
85
|
+
when Hash then rel_length_hash_error_message(length)
|
|
86
|
+
else 'should be a Symbol, Integer, Range or Hash'
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def rel_length_symbol_error_message(length)
|
|
91
|
+
"expecting one of #{VALID_REL_LENGTH_SYMBOLS.keys.inspect}" if !VALID_REL_LENGTH_SYMBOLS.key?(length)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def rel_length_range_error_message(length)
|
|
95
|
+
if length.begin > length.end
|
|
96
|
+
'cannot be a decreasing Range'
|
|
97
|
+
elsif length.begin < 0
|
|
98
|
+
'cannot include negative values'
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def rel_length_hash_error_message(length)
|
|
103
|
+
'Hash keys should be a subset of [:min, :max]' if (length.keys & [:min, :max]) != length.keys
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|