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,278 @@
|
|
1
|
+
require 'active_support/inflector/inflections'
|
2
|
+
require 'neo4j/class_arguments'
|
3
|
+
|
4
|
+
module Neo4j
|
5
|
+
module ActiveNode
|
6
|
+
module HasN
|
7
|
+
class Association
|
8
|
+
include Neo4j::Shared::RelTypeConverters
|
9
|
+
include Neo4j::ActiveNode::Dependent::AssociationMethods
|
10
|
+
include Neo4j::ActiveNode::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?(::Neo4j::ActiveNode)
|
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
|
+
Neo4j::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 Neo4j::ActiveNode::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 = Neo4j::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
|
+
Neo4j::ActiveBase.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
|
+
Neo4j::ActiveNode::HasN::Association::RelWrapper.new(association, properties)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Neo4j::ActiveNode::HasN::Association
|
2
|
+
# Provides the interface needed to interact with the ActiveRel query factory.
|
3
|
+
class RelWrapper
|
4
|
+
include Neo4j::Shared::Cypher::RelIdentifiers
|
5
|
+
include Neo4j::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 Neo4j
|
2
|
+
module ActiveNode
|
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
|