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.
Files changed (144) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2016 -0
  3. data/CONTRIBUTORS +12 -0
  4. data/Gemfile +24 -0
  5. data/README.md +111 -0
  6. data/activegraph.gemspec +52 -0
  7. data/bin/rake +17 -0
  8. data/config/locales/en.yml +5 -0
  9. data/config/neo4j/add_classnames.yml +1 -0
  10. data/config/neo4j/config.yml +35 -0
  11. data/lib/active_graph.rb +123 -0
  12. data/lib/active_graph/ansi.rb +14 -0
  13. data/lib/active_graph/attribute_set.rb +32 -0
  14. data/lib/active_graph/base.rb +77 -0
  15. data/lib/active_graph/class_arguments.rb +39 -0
  16. data/lib/active_graph/config.rb +135 -0
  17. data/lib/active_graph/core.rb +14 -0
  18. data/lib/active_graph/core/connection_failed_error.rb +6 -0
  19. data/lib/active_graph/core/cypher_error.rb +37 -0
  20. data/lib/active_graph/core/entity.rb +11 -0
  21. data/lib/active_graph/core/instrumentable.rb +37 -0
  22. data/lib/active_graph/core/label.rb +135 -0
  23. data/lib/active_graph/core/logging.rb +44 -0
  24. data/lib/active_graph/core/node.rb +15 -0
  25. data/lib/active_graph/core/querable.rb +41 -0
  26. data/lib/active_graph/core/query.rb +485 -0
  27. data/lib/active_graph/core/query_builder.rb +18 -0
  28. data/lib/active_graph/core/query_clauses.rb +727 -0
  29. data/lib/active_graph/core/query_ext.rb +24 -0
  30. data/lib/active_graph/core/query_find_in_batches.rb +46 -0
  31. data/lib/active_graph/core/record.rb +51 -0
  32. data/lib/active_graph/core/result.rb +31 -0
  33. data/lib/active_graph/core/schema.rb +65 -0
  34. data/lib/active_graph/core/schema_errors.rb +12 -0
  35. data/lib/active_graph/core/wrappable.rb +30 -0
  36. data/lib/active_graph/errors.rb +59 -0
  37. data/lib/active_graph/lazy_attribute_hash.rb +38 -0
  38. data/lib/active_graph/migration.rb +148 -0
  39. data/lib/active_graph/migrations.rb +27 -0
  40. data/lib/active_graph/migrations/base.rb +77 -0
  41. data/lib/active_graph/migrations/check_pending.rb +20 -0
  42. data/lib/active_graph/migrations/helpers.rb +105 -0
  43. data/lib/active_graph/migrations/helpers/id_property.rb +72 -0
  44. data/lib/active_graph/migrations/helpers/relationships.rb +66 -0
  45. data/lib/active_graph/migrations/helpers/schema.rb +63 -0
  46. data/lib/active_graph/migrations/migration_file.rb +24 -0
  47. data/lib/active_graph/migrations/runner.rb +195 -0
  48. data/lib/active_graph/migrations/schema.rb +64 -0
  49. data/lib/active_graph/migrations/schema_migration.rb +14 -0
  50. data/lib/active_graph/model_schema.rb +139 -0
  51. data/lib/active_graph/node.rb +110 -0
  52. data/lib/active_graph/node/callbacks.rb +8 -0
  53. data/lib/active_graph/node/dependent.rb +11 -0
  54. data/lib/active_graph/node/dependent/association_methods.rb +49 -0
  55. data/lib/active_graph/node/dependent/query_proxy_methods.rb +52 -0
  56. data/lib/active_graph/node/dependent_callbacks.rb +31 -0
  57. data/lib/active_graph/node/enum.rb +26 -0
  58. data/lib/active_graph/node/has_n.rb +602 -0
  59. data/lib/active_graph/node/has_n/association.rb +278 -0
  60. data/lib/active_graph/node/has_n/association/rel_factory.rb +61 -0
  61. data/lib/active_graph/node/has_n/association/rel_wrapper.rb +23 -0
  62. data/lib/active_graph/node/has_n/association_cypher_methods.rb +108 -0
  63. data/lib/active_graph/node/id_property.rb +224 -0
  64. data/lib/active_graph/node/id_property/accessor.rb +62 -0
  65. data/lib/active_graph/node/initialize.rb +21 -0
  66. data/lib/active_graph/node/labels.rb +207 -0
  67. data/lib/active_graph/node/labels/index.rb +37 -0
  68. data/lib/active_graph/node/labels/reloading.rb +21 -0
  69. data/lib/active_graph/node/node_list_formatter.rb +13 -0
  70. data/lib/active_graph/node/node_wrapper.rb +54 -0
  71. data/lib/active_graph/node/orm_adapter.rb +82 -0
  72. data/lib/active_graph/node/persistence.rb +186 -0
  73. data/lib/active_graph/node/property.rb +60 -0
  74. data/lib/active_graph/node/query.rb +76 -0
  75. data/lib/active_graph/node/query/query_proxy.rb +367 -0
  76. data/lib/active_graph/node/query/query_proxy_eager_loading.rb +177 -0
  77. data/lib/active_graph/node/query/query_proxy_eager_loading/association_tree.rb +75 -0
  78. data/lib/active_graph/node/query/query_proxy_enumerable.rb +110 -0
  79. data/lib/active_graph/node/query/query_proxy_find_in_batches.rb +19 -0
  80. data/lib/active_graph/node/query/query_proxy_link.rb +139 -0
  81. data/lib/active_graph/node/query/query_proxy_methods.rb +303 -0
  82. data/lib/active_graph/node/query/query_proxy_methods_of_mass_updating.rb +99 -0
  83. data/lib/active_graph/node/query_methods.rb +68 -0
  84. data/lib/active_graph/node/reflection.rb +86 -0
  85. data/lib/active_graph/node/rels.rb +11 -0
  86. data/lib/active_graph/node/scope.rb +166 -0
  87. data/lib/active_graph/node/unpersisted.rb +48 -0
  88. data/lib/active_graph/node/validations.rb +59 -0
  89. data/lib/active_graph/paginated.rb +27 -0
  90. data/lib/active_graph/railtie.rb +108 -0
  91. data/lib/active_graph/relationship.rb +68 -0
  92. data/lib/active_graph/relationship/callbacks.rb +21 -0
  93. data/lib/active_graph/relationship/initialize.rb +28 -0
  94. data/lib/active_graph/relationship/persistence.rb +133 -0
  95. data/lib/active_graph/relationship/persistence/query_factory.rb +95 -0
  96. data/lib/active_graph/relationship/property.rb +92 -0
  97. data/lib/active_graph/relationship/query.rb +99 -0
  98. data/lib/active_graph/relationship/rel_wrapper.rb +31 -0
  99. data/lib/active_graph/relationship/related_node.rb +87 -0
  100. data/lib/active_graph/relationship/types.rb +80 -0
  101. data/lib/active_graph/relationship/validations.rb +8 -0
  102. data/lib/active_graph/schema/operation.rb +102 -0
  103. data/lib/active_graph/shared.rb +48 -0
  104. data/lib/active_graph/shared/attributes.rb +217 -0
  105. data/lib/active_graph/shared/callbacks.rb +66 -0
  106. data/lib/active_graph/shared/cypher.rb +37 -0
  107. data/lib/active_graph/shared/declared_properties.rb +204 -0
  108. data/lib/active_graph/shared/declared_property.rb +109 -0
  109. data/lib/active_graph/shared/declared_property/index.rb +37 -0
  110. data/lib/active_graph/shared/enum.rb +167 -0
  111. data/lib/active_graph/shared/filtered_hash.rb +79 -0
  112. data/lib/active_graph/shared/identity.rb +34 -0
  113. data/lib/active_graph/shared/initialize.rb +65 -0
  114. data/lib/active_graph/shared/marshal.rb +23 -0
  115. data/lib/active_graph/shared/mass_assignment.rb +63 -0
  116. data/lib/active_graph/shared/permitted_attributes.rb +28 -0
  117. data/lib/active_graph/shared/persistence.rb +272 -0
  118. data/lib/active_graph/shared/property.rb +249 -0
  119. data/lib/active_graph/shared/query_factory.rb +122 -0
  120. data/lib/active_graph/shared/rel_type_converters.rb +43 -0
  121. data/lib/active_graph/shared/serialized_properties.rb +30 -0
  122. data/lib/active_graph/shared/type_converters.rb +439 -0
  123. data/lib/active_graph/shared/typecasted_attributes.rb +99 -0
  124. data/lib/active_graph/shared/typecaster.rb +53 -0
  125. data/lib/active_graph/shared/validations.rb +44 -0
  126. data/lib/active_graph/tasks/migration.rake +204 -0
  127. data/lib/active_graph/timestamps.rb +11 -0
  128. data/lib/active_graph/timestamps/created.rb +9 -0
  129. data/lib/active_graph/timestamps/updated.rb +9 -0
  130. data/lib/active_graph/transaction.rb +22 -0
  131. data/lib/active_graph/transactions.rb +57 -0
  132. data/lib/active_graph/type_converters.rb +7 -0
  133. data/lib/active_graph/undeclared_properties.rb +53 -0
  134. data/lib/active_graph/version.rb +3 -0
  135. data/lib/active_graph/wrapper.rb +4 -0
  136. data/lib/rails/generators/active_graph/migration/migration_generator.rb +16 -0
  137. data/lib/rails/generators/active_graph/migration/templates/migration.erb +9 -0
  138. data/lib/rails/generators/active_graph/model/model_generator.rb +89 -0
  139. data/lib/rails/generators/active_graph/model/templates/migration.erb +11 -0
  140. data/lib/rails/generators/active_graph/model/templates/model.erb +15 -0
  141. data/lib/rails/generators/active_graph/upgrade_v8/templates/migration.erb +17 -0
  142. data/lib/rails/generators/active_graph/upgrade_v8/upgrade_v8_generator.rb +34 -0
  143. data/lib/rails/generators/active_graph_generator.rb +121 -0
  144. 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