activegraph 10.0.0.pre.alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1989 -0
  3. data/CONTRIBUTORS +12 -0
  4. data/Gemfile +24 -0
  5. data/README.md +107 -0
  6. data/bin/rake +17 -0
  7. data/config/locales/en.yml +5 -0
  8. data/config/neo4j/add_classnames.yml +1 -0
  9. data/config/neo4j/config.yml +38 -0
  10. data/lib/neo4j.rb +116 -0
  11. data/lib/neo4j/active_base.rb +89 -0
  12. data/lib/neo4j/active_node.rb +108 -0
  13. data/lib/neo4j/active_node/callbacks.rb +8 -0
  14. data/lib/neo4j/active_node/dependent.rb +11 -0
  15. data/lib/neo4j/active_node/dependent/association_methods.rb +49 -0
  16. data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +51 -0
  17. data/lib/neo4j/active_node/enum.rb +26 -0
  18. data/lib/neo4j/active_node/has_n.rb +612 -0
  19. data/lib/neo4j/active_node/has_n/association.rb +278 -0
  20. data/lib/neo4j/active_node/has_n/association/rel_factory.rb +61 -0
  21. data/lib/neo4j/active_node/has_n/association/rel_wrapper.rb +23 -0
  22. data/lib/neo4j/active_node/has_n/association_cypher_methods.rb +108 -0
  23. data/lib/neo4j/active_node/id_property.rb +224 -0
  24. data/lib/neo4j/active_node/id_property/accessor.rb +62 -0
  25. data/lib/neo4j/active_node/initialize.rb +21 -0
  26. data/lib/neo4j/active_node/labels.rb +207 -0
  27. data/lib/neo4j/active_node/labels/index.rb +37 -0
  28. data/lib/neo4j/active_node/labels/reloading.rb +21 -0
  29. data/lib/neo4j/active_node/node_list_formatter.rb +13 -0
  30. data/lib/neo4j/active_node/node_wrapper.rb +54 -0
  31. data/lib/neo4j/active_node/orm_adapter.rb +82 -0
  32. data/lib/neo4j/active_node/persistence.rb +187 -0
  33. data/lib/neo4j/active_node/property.rb +60 -0
  34. data/lib/neo4j/active_node/query.rb +76 -0
  35. data/lib/neo4j/active_node/query/query_proxy.rb +374 -0
  36. data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +177 -0
  37. data/lib/neo4j/active_node/query/query_proxy_eager_loading/association_tree.rb +75 -0
  38. data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +110 -0
  39. data/lib/neo4j/active_node/query/query_proxy_find_in_batches.rb +19 -0
  40. data/lib/neo4j/active_node/query/query_proxy_link.rb +139 -0
  41. data/lib/neo4j/active_node/query/query_proxy_methods.rb +302 -0
  42. data/lib/neo4j/active_node/query/query_proxy_methods_of_mass_updating.rb +86 -0
  43. data/lib/neo4j/active_node/query_methods.rb +68 -0
  44. data/lib/neo4j/active_node/reflection.rb +86 -0
  45. data/lib/neo4j/active_node/rels.rb +11 -0
  46. data/lib/neo4j/active_node/scope.rb +166 -0
  47. data/lib/neo4j/active_node/unpersisted.rb +48 -0
  48. data/lib/neo4j/active_node/validations.rb +59 -0
  49. data/lib/neo4j/active_rel.rb +67 -0
  50. data/lib/neo4j/active_rel/callbacks.rb +15 -0
  51. data/lib/neo4j/active_rel/initialize.rb +28 -0
  52. data/lib/neo4j/active_rel/persistence.rb +134 -0
  53. data/lib/neo4j/active_rel/persistence/query_factory.rb +95 -0
  54. data/lib/neo4j/active_rel/property.rb +95 -0
  55. data/lib/neo4j/active_rel/query.rb +101 -0
  56. data/lib/neo4j/active_rel/rel_wrapper.rb +31 -0
  57. data/lib/neo4j/active_rel/related_node.rb +87 -0
  58. data/lib/neo4j/active_rel/types.rb +82 -0
  59. data/lib/neo4j/active_rel/validations.rb +8 -0
  60. data/lib/neo4j/ansi.rb +14 -0
  61. data/lib/neo4j/class_arguments.rb +39 -0
  62. data/lib/neo4j/config.rb +135 -0
  63. data/lib/neo4j/core.rb +14 -0
  64. data/lib/neo4j/core/connection_failed_error.rb +6 -0
  65. data/lib/neo4j/core/cypher_error.rb +37 -0
  66. data/lib/neo4j/core/driver.rb +66 -0
  67. data/lib/neo4j/core/has_uri.rb +63 -0
  68. data/lib/neo4j/core/instrumentable.rb +36 -0
  69. data/lib/neo4j/core/label.rb +158 -0
  70. data/lib/neo4j/core/logging.rb +44 -0
  71. data/lib/neo4j/core/node.rb +23 -0
  72. data/lib/neo4j/core/querable.rb +88 -0
  73. data/lib/neo4j/core/query.rb +487 -0
  74. data/lib/neo4j/core/query_builder.rb +32 -0
  75. data/lib/neo4j/core/query_clauses.rb +727 -0
  76. data/lib/neo4j/core/query_ext.rb +24 -0
  77. data/lib/neo4j/core/query_find_in_batches.rb +49 -0
  78. data/lib/neo4j/core/relationship.rb +13 -0
  79. data/lib/neo4j/core/responses.rb +50 -0
  80. data/lib/neo4j/core/result.rb +33 -0
  81. data/lib/neo4j/core/schema.rb +30 -0
  82. data/lib/neo4j/core/schema_errors.rb +12 -0
  83. data/lib/neo4j/core/wrappable.rb +30 -0
  84. data/lib/neo4j/errors.rb +57 -0
  85. data/lib/neo4j/migration.rb +148 -0
  86. data/lib/neo4j/migrations.rb +27 -0
  87. data/lib/neo4j/migrations/base.rb +77 -0
  88. data/lib/neo4j/migrations/check_pending.rb +20 -0
  89. data/lib/neo4j/migrations/helpers.rb +105 -0
  90. data/lib/neo4j/migrations/helpers/id_property.rb +75 -0
  91. data/lib/neo4j/migrations/helpers/relationships.rb +66 -0
  92. data/lib/neo4j/migrations/helpers/schema.rb +51 -0
  93. data/lib/neo4j/migrations/migration_file.rb +24 -0
  94. data/lib/neo4j/migrations/runner.rb +195 -0
  95. data/lib/neo4j/migrations/schema.rb +44 -0
  96. data/lib/neo4j/migrations/schema_migration.rb +14 -0
  97. data/lib/neo4j/model_schema.rb +139 -0
  98. data/lib/neo4j/paginated.rb +27 -0
  99. data/lib/neo4j/railtie.rb +105 -0
  100. data/lib/neo4j/schema/operation.rb +102 -0
  101. data/lib/neo4j/shared.rb +60 -0
  102. data/lib/neo4j/shared/attributes.rb +216 -0
  103. data/lib/neo4j/shared/callbacks.rb +68 -0
  104. data/lib/neo4j/shared/cypher.rb +37 -0
  105. data/lib/neo4j/shared/declared_properties.rb +204 -0
  106. data/lib/neo4j/shared/declared_property.rb +109 -0
  107. data/lib/neo4j/shared/declared_property/index.rb +37 -0
  108. data/lib/neo4j/shared/enum.rb +167 -0
  109. data/lib/neo4j/shared/filtered_hash.rb +79 -0
  110. data/lib/neo4j/shared/identity.rb +34 -0
  111. data/lib/neo4j/shared/initialize.rb +64 -0
  112. data/lib/neo4j/shared/marshal.rb +23 -0
  113. data/lib/neo4j/shared/mass_assignment.rb +64 -0
  114. data/lib/neo4j/shared/permitted_attributes.rb +28 -0
  115. data/lib/neo4j/shared/persistence.rb +282 -0
  116. data/lib/neo4j/shared/property.rb +240 -0
  117. data/lib/neo4j/shared/query_factory.rb +102 -0
  118. data/lib/neo4j/shared/rel_type_converters.rb +43 -0
  119. data/lib/neo4j/shared/serialized_properties.rb +30 -0
  120. data/lib/neo4j/shared/type_converters.rb +433 -0
  121. data/lib/neo4j/shared/typecasted_attributes.rb +98 -0
  122. data/lib/neo4j/shared/typecaster.rb +53 -0
  123. data/lib/neo4j/shared/validations.rb +44 -0
  124. data/lib/neo4j/tasks/migration.rake +202 -0
  125. data/lib/neo4j/timestamps.rb +11 -0
  126. data/lib/neo4j/timestamps/created.rb +9 -0
  127. data/lib/neo4j/timestamps/updated.rb +9 -0
  128. data/lib/neo4j/transaction.rb +139 -0
  129. data/lib/neo4j/type_converters.rb +7 -0
  130. data/lib/neo4j/undeclared_properties.rb +53 -0
  131. data/lib/neo4j/version.rb +3 -0
  132. data/lib/neo4j/wrapper.rb +4 -0
  133. data/lib/rails/generators/neo4j/migration/migration_generator.rb +14 -0
  134. data/lib/rails/generators/neo4j/migration/templates/migration.erb +9 -0
  135. data/lib/rails/generators/neo4j/model/model_generator.rb +88 -0
  136. data/lib/rails/generators/neo4j/model/templates/migration.erb +9 -0
  137. data/lib/rails/generators/neo4j/model/templates/model.erb +15 -0
  138. data/lib/rails/generators/neo4j/upgrade_v8/templates/migration.erb +17 -0
  139. data/lib/rails/generators/neo4j/upgrade_v8/upgrade_v8_generator.rb +32 -0
  140. data/lib/rails/generators/neo4j_generator.rb +119 -0
  141. data/neo4j.gemspec +51 -0
  142. metadata +421 -0
@@ -0,0 +1,240 @@
1
+ module Neo4j::Shared
2
+ module Property
3
+ extend ActiveSupport::Concern
4
+
5
+ include Neo4j::Shared::MassAssignment
6
+ include Neo4j::Shared::TypecastedAttributes
7
+ include ActiveModel::Dirty
8
+
9
+ class UndefinedPropertyError < Neo4j::Error; end
10
+ class MultiparameterAssignmentError < Neo4j::Error; end
11
+
12
+ attr_reader :_persisted_obj
13
+
14
+ # TODO: Set @attribute correctly using class ActiveModel::Attribute, and after that
15
+ # remove mutations_from_database and other ActiveModel::Dirty overrided methods
16
+ def mutations_from_database
17
+ @mutations_from_database ||=
18
+ defined?(ActiveModel::ForcedMutationTracker) ? ActiveModel::ForcedMutationTracker.new(self) : ActiveModel::NullMutationTracker.instance
19
+ end
20
+
21
+ def inspect
22
+ attribute_descriptions = inspect_attributes.map do |key, value|
23
+ "#{Neo4j::ANSI::CYAN}#{key}: #{Neo4j::ANSI::CLEAR}#{value.inspect}"
24
+ end.join(', ')
25
+
26
+ separator = ' ' unless attribute_descriptions.empty?
27
+ "#<#{Neo4j::ANSI::YELLOW}#{self.class.name}#{Neo4j::ANSI::CLEAR}#{separator}#{attribute_descriptions}>"
28
+ end
29
+
30
+ def initialize(attributes = nil)
31
+ attributes = process_attributes(attributes)
32
+ modded_attributes = inject_defaults!(attributes)
33
+ validate_attributes!(modded_attributes)
34
+ writer_method_props = extract_writer_methods!(modded_attributes)
35
+ send_props(writer_method_props)
36
+ self.undeclared_properties = attributes
37
+ @_persisted_obj = nil
38
+ end
39
+
40
+ def undeclared_properties=(_); end
41
+
42
+ def inject_defaults!(starting_props)
43
+ return starting_props if self.class.declared_properties.declared_property_defaults.empty?
44
+ self.class.declared_properties.inject_defaults!(self, starting_props || {})
45
+ end
46
+
47
+ def read_attribute(name)
48
+ respond_to?(name) ? send(name) : nil
49
+ end
50
+ alias [] read_attribute
51
+
52
+ def send_props(hash)
53
+ return hash if hash.blank?
54
+ hash.each { |key, value| send("#{key}=", value) }
55
+ end
56
+
57
+ def reload_properties!(properties)
58
+ @attributes = nil
59
+ convert_and_assign_attributes(properties)
60
+ end
61
+
62
+ private
63
+
64
+ # Changes attributes hash to remove relationship keys
65
+ # Raises an error if there are any keys left which haven't been defined as properties on the model
66
+ # TODO: use declared_properties instead of self.attributes
67
+ def validate_attributes!(attributes)
68
+ return attributes if attributes.blank?
69
+ invalid_properties = attributes.keys.map(&:to_s) - self.attributes.keys
70
+ invalid_properties.reject! { |name| self.respond_to?("#{name}=") }
71
+ fail UndefinedPropertyError, "Undefined properties: #{invalid_properties.join(',')}" if !invalid_properties.empty?
72
+ end
73
+
74
+ def extract_writer_methods!(attributes)
75
+ return attributes if attributes.blank?
76
+ {}.tap do |writer_method_props|
77
+ attributes.each_key do |key|
78
+ writer_method_props[key] = attributes.delete(key) if self.respond_to?("#{key}=")
79
+ end
80
+ end
81
+ end
82
+
83
+ DATE_KEY_REGEX = /\A([^\(]+)\((\d+)([ifs])\)$/
84
+ # Gives support for Rails date_select, datetime_select, time_select helpers.
85
+ def process_attributes(attributes = nil)
86
+ return attributes if attributes.blank?
87
+ multi_parameter_attributes = {}
88
+ new_attributes = {}
89
+ attributes.each_pair do |key, value|
90
+ if key.match(DATE_KEY_REGEX)
91
+ match = key.to_s.match(DATE_KEY_REGEX)
92
+ found_key = match[1]
93
+ index = match[2].to_i
94
+ (multi_parameter_attributes[found_key] ||= {})[index] = value.empty? ? nil : value.send("to_#{$3}")
95
+ else
96
+ new_attributes[key] = value
97
+ end
98
+ end
99
+
100
+ multi_parameter_attributes.empty? ? new_attributes : process_multiparameter_attributes(multi_parameter_attributes, new_attributes)
101
+ end
102
+
103
+ def process_multiparameter_attributes(multi_parameter_attributes, new_attributes)
104
+ multi_parameter_attributes.each_with_object(new_attributes) do |(key, values), attributes|
105
+ values = (values.keys.min..values.keys.max).map { |i| values[i] }
106
+ if (field = self.class.attributes[key.to_sym]).nil?
107
+ fail MultiparameterAssignmentError, "error on assignment #{values.inspect} to #{key}"
108
+ end
109
+
110
+ attributes[key] = instantiate_object(field, values)
111
+ end
112
+ end
113
+
114
+ def instantiate_object(field, values_with_empty_parameters)
115
+ return nil if values_with_empty_parameters.all?(&:nil?)
116
+ values = values_with_empty_parameters.collect { |v| v.nil? ? 1 : v }
117
+ klass = field.type
118
+ klass ? klass.new(*values) : values
119
+ end
120
+
121
+ module ClassMethods
122
+ extend Forwardable
123
+
124
+ def_delegators :declared_properties, :serialized_properties, :serialized_properties=, :serialize, :declared_property_defaults
125
+
126
+ VALID_PROPERTY_OPTIONS = %w(type default index constraint serializer typecaster).map(&:to_sym)
127
+ # Defines a property on the class
128
+ #
129
+ # See active_attr gem for allowed options, e.g which type
130
+ # Notice, in Neo4j you don't have to declare properties before using them, see the neo4j-core api.
131
+ #
132
+ # @example Without type
133
+ # class Person
134
+ # # declare a property which can have any value
135
+ # property :name
136
+ # end
137
+ #
138
+ # @example With type and a default value
139
+ # class Person
140
+ # # declare a property which can have any value
141
+ # property :score, type: Integer, default: 0
142
+ # end
143
+ #
144
+ # @example With an index
145
+ # class Person
146
+ # # declare a property which can have any value
147
+ # property :name, index: :exact
148
+ # end
149
+ #
150
+ # @example With a constraint
151
+ # class Person
152
+ # # declare a property which can have any value
153
+ # property :name, constraint: :unique
154
+ # end
155
+ def property(name, options = {})
156
+ invalid_option_keys = options.keys.map(&:to_sym) - VALID_PROPERTY_OPTIONS
157
+ fail ArgumentError, "Invalid options for property `#{name}` on `#{self.name}`: #{invalid_option_keys.join(', ')}" if invalid_option_keys.any?
158
+ build_property(name, options) do |prop|
159
+ attribute(prop)
160
+ end
161
+ end
162
+
163
+ # @param [Symbol] name The property name
164
+ # @param [Neo4j::Shared::AttributeDefinition] attr_def A cloned AttributeDefinition to reuse
165
+ # @param [Hash] options An options hash to use in the new property definition
166
+ def inherit_property(name, attr_def, options = {})
167
+ build_property(name, options) do |prop_name|
168
+ attributes[prop_name] = attr_def
169
+ end
170
+ end
171
+
172
+ def build_property(name, options)
173
+ decl_prop = DeclaredProperty.new(name, options).tap do |prop|
174
+ prop.register
175
+ declared_properties.register(prop)
176
+ yield name
177
+ constraint_or_index(name, options)
178
+ end
179
+
180
+ # If this class has already been inherited, make sure subclasses inherit property
181
+ subclasses.each do |klass|
182
+ klass.inherit_property name, decl_prop.clone, declared_properties[name].options
183
+ end
184
+
185
+ decl_prop
186
+ end
187
+
188
+ def undef_property(name)
189
+ undef_constraint_or_index(name)
190
+ declared_properties.unregister(name)
191
+ attribute_methods(name).each { |method| undef_method(method) }
192
+ end
193
+
194
+ def declared_properties
195
+ @_declared_properties ||= DeclaredProperties.new(self)
196
+ end
197
+
198
+ # @return [Hash] A frozen hash of all model properties with nil values. It is used during node loading and prevents
199
+ # an extra call to a slow dependency method.
200
+ def attributes_nil_hash
201
+ declared_properties.attributes_nil_hash
202
+ end
203
+
204
+ def extract_association_attributes!(props)
205
+ props
206
+ end
207
+
208
+ private
209
+
210
+ def attribute!(name)
211
+ remove_instance_variable('@attribute_methods_generated') if instance_variable_defined?('@attribute_methods_generated')
212
+ define_attribute_methods([name]) unless attribute_names.include?(name)
213
+ attributes[name.to_s] = declared_properties[name]
214
+ define_method("#{name}=") do |value|
215
+ typecast_value = typecast_attribute(_attribute_typecaster(name), value)
216
+ send("#{name}_will_change!") unless typecast_value == read_attribute(name)
217
+ super(value)
218
+ end
219
+ end
220
+
221
+ def constraint_or_index(name, options)
222
+ # either constraint or index, do not set both
223
+ if options[:constraint]
224
+ fail "unknown constraint type #{options[:constraint]}, only :unique supported" if options[:constraint] != :unique
225
+ constraint(name, type: :unique)
226
+ elsif options[:index]
227
+ fail "unknown index type #{options[:index]}, only :exact supported" if options[:index] != :exact
228
+ index(name) if options[:index] == :exact
229
+ end
230
+ end
231
+
232
+ def undef_constraint_or_index(name)
233
+ prop = declared_properties[name]
234
+ return unless prop.index_or_constraint?
235
+ type = prop.constraint? ? :constraint : :index
236
+ send(:"drop_#{type}", name)
237
+ end
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,102 @@
1
+ module Neo4j::Shared
2
+ # Acts as a bridge between the node and rel models and Neo4j::Core::Query.
3
+ # If the object is persisted, it returns a query matching; otherwise, it returns a query creating it.
4
+ # This class does not execute queries, so it keeps no record of what identifiers have been set or what has happened in previous factories.
5
+ class QueryFactory
6
+ attr_reader :graph_object, :identifier
7
+
8
+ def initialize(graph_object, identifier)
9
+ @graph_object = graph_object
10
+ @identifier = identifier.to_sym
11
+ end
12
+
13
+ def self.create(graph_object, identifier)
14
+ factory_for(graph_object).new(graph_object, identifier)
15
+ end
16
+
17
+ def self.factory_for(graph_obj)
18
+ case
19
+ when graph_obj.respond_to?(:labels_for_create)
20
+ NodeQueryFactory
21
+ when graph_obj.respond_to?(:rel_type)
22
+ RelQueryFactory
23
+ else
24
+ fail "Unable to find factory for #{graph_obj}"
25
+ end
26
+ end
27
+
28
+ def query
29
+ graph_object.persisted? ? match_query : create_query
30
+ end
31
+
32
+ # @param [Neo4j::Core::Query] query An instance of Neo4j::Core::Query upon which methods will be chained.
33
+ def base_query=(query)
34
+ return if query.blank?
35
+ @base_query = query
36
+ end
37
+
38
+ def base_query
39
+ @base_query || Neo4j::ActiveBase.new_query
40
+ end
41
+
42
+ protected
43
+
44
+ def create_query
45
+ fail 'Abstract class, not implemented'
46
+ end
47
+
48
+ def match_query
49
+ base_query
50
+ .match(match_string).where("ID(#{identifier}) = {#{identifier_id}}")
51
+ .params(identifier_id.to_sym => graph_object.neo_id)
52
+ end
53
+
54
+ def identifier_id
55
+ @identifier_id ||= "#{identifier}_id"
56
+ end
57
+
58
+ def identifier_params
59
+ @identifier_params ||= "#{identifier}_params"
60
+ end
61
+ end
62
+
63
+ class NodeQueryFactory < QueryFactory
64
+ protected
65
+
66
+ def match_string
67
+ "(#{identifier})"
68
+ end
69
+
70
+ def create_query
71
+ return match_query if graph_object.persisted?
72
+ labels = graph_object.labels_for_create.map { |l| ":`#{l}`" }.join
73
+ base_query.create("(#{identifier}#{labels} {#{identifier}_params})").params(identifier_params => graph_object.props_for_create)
74
+ end
75
+ end
76
+
77
+ class RelQueryFactory < QueryFactory
78
+ protected
79
+
80
+ def match_string
81
+ "(#{graph_object.from_node_identifier})-[#{identifier}]->()"
82
+ end
83
+
84
+ def create_query
85
+ return match_query if graph_object.persisted?
86
+ create_props, set_props = filtered_props
87
+ base_query.send(graph_object.create_method, query_string).break
88
+ .set(identifier => set_props)
89
+ .params(:"#{identifier}_create_props" => create_props)
90
+ end
91
+
92
+ private
93
+
94
+ def filtered_props
95
+ Neo4j::Shared::FilteredHash.new(graph_object.props_for_create, graph_object.creates_unique_option).filtered_base
96
+ end
97
+
98
+ def query_string
99
+ "(#{graph_object.from_node_identifier})-[#{identifier}:`#{graph_object.type}` {#{identifier}_create_props}]->(#{graph_object.to_node_identifier})"
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,43 @@
1
+ module Neo4j::Shared
2
+ # This module controls changes to relationship type based on Neo4j::Config.transform_rel_type.
3
+ # It's used whenever a rel type is automatically determined based on ActiveRel model name or
4
+ # association type.
5
+ module RelTypeConverters
6
+ def decorated_rel_type(type)
7
+ @decorated_rel_type ||= Neo4j::Shared::RelTypeConverters.decorated_rel_type(type)
8
+ end
9
+
10
+ class << self
11
+ # Determines how relationship types should look when inferred based on association or ActiveRel model name.
12
+ # With the exception of `:none`, all options will call `underscore`, so `ThisClass` becomes `this_class`, with capitalization
13
+ # determined by the specific option passed.
14
+ # Valid options:
15
+ # * :upcase - `:this_class`, `ThisClass`, `thiS_claSs` (if you don't like yourself) becomes `THIS_CLASS`
16
+ # * :downcase - same as above, only... downcased.
17
+ # * :legacy - downcases and prepends `#`, so ThisClass becomes `#this_class`
18
+ # * :none - uses the string version of whatever is passed with no modifications
19
+ def rel_transformer
20
+ @rel_transformer ||= Neo4j::Config[:transform_rel_type].nil? ? :upcase : Neo4j::Config[:transform_rel_type]
21
+ end
22
+
23
+ # @param [String,Symbol] type The raw string or symbol to be used as the basis of the relationship type
24
+ # @return [String] A string that conforms to the set rel type conversion setting.
25
+ def decorated_rel_type(type)
26
+ type = type.to_s
27
+ decorated_type = case rel_transformer
28
+ when :upcase
29
+ type.underscore.upcase
30
+ when :downcase
31
+ type.underscore.downcase
32
+ when :legacy
33
+ "##{type.underscore.downcase}"
34
+ when :none
35
+ type
36
+ else
37
+ type.underscore.upcase
38
+ end
39
+ decorated_type.tap { |s| s.gsub!('/', '::') if type.include?('::') }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,30 @@
1
+ module Neo4j::Shared
2
+ # This module adds the `serialize` class method. It lets you store hashes and arrays in Neo4j properties.
3
+ # Be aware that you won't be able to search within serialized properties and stuff use indexes. If you do a regex search for portion of a string
4
+ # property, the search happens in Cypher and you may take a performance hit.
5
+ #
6
+ # See type_converters.rb for the serialization process.
7
+ module SerializedProperties
8
+ extend ActiveSupport::Concern
9
+
10
+ def serialized_properties
11
+ self.class.serialized_properties
12
+ end
13
+
14
+ def serializable_hash(*args)
15
+ super.merge(id: id)
16
+ end
17
+
18
+
19
+ module ClassMethods
20
+ def inherited(other)
21
+ inherit_serialized_properties(other) if self.respond_to?(:serialized_properties)
22
+ super
23
+ end
24
+
25
+ def inherit_serialized_properties(other)
26
+ other.serialized_properties = self.serialized_properties
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,433 @@
1
+ require 'date'
2
+ require 'bigdecimal'
3
+ require 'bigdecimal/util'
4
+ require 'active_support/core_ext/big_decimal/conversions'
5
+ require 'active_support/core_ext/string/conversions'
6
+
7
+ module Neo4j::Shared
8
+ class Boolean; end
9
+
10
+ module TypeConverters
11
+ CONVERTERS = {}
12
+
13
+ class Boolean; end
14
+
15
+ class BaseConverter
16
+ class << self
17
+ def converted?(value)
18
+ value.is_a?(db_type)
19
+ end
20
+ end
21
+
22
+ def supports_array?
23
+ false
24
+ end
25
+ end
26
+
27
+ class IntegerConverter < BaseConverter
28
+ class << self
29
+ def convert_type
30
+ Integer
31
+ end
32
+
33
+ def db_type
34
+ Integer
35
+ end
36
+
37
+ def to_db(value)
38
+ value.to_i
39
+ end
40
+
41
+ alias to_ruby to_db
42
+ end
43
+ end
44
+
45
+ class FloatConverter < BaseConverter
46
+ class << self
47
+ def convert_type
48
+ Float
49
+ end
50
+
51
+ def db_type
52
+ Float
53
+ end
54
+
55
+ def to_db(value)
56
+ value.to_f
57
+ end
58
+ alias to_ruby to_db
59
+ end
60
+ end
61
+
62
+ class BigDecimalConverter < BaseConverter
63
+ class << self
64
+ def convert_type
65
+ BigDecimal
66
+ end
67
+
68
+ def db_type
69
+ String
70
+ end
71
+
72
+ def to_db(value)
73
+ case value
74
+ when Rational
75
+ value.to_f.to_d
76
+ when respond_to?(:to_d)
77
+ value.to_d
78
+ else
79
+ BigDecimal(value.to_s)
80
+ end.to_s
81
+ end
82
+
83
+ def to_ruby(value)
84
+ value.to_d
85
+ end
86
+ end
87
+ end
88
+
89
+ class StringConverter < BaseConverter
90
+ class << self
91
+ def convert_type
92
+ String
93
+ end
94
+
95
+ def db_type
96
+ String
97
+ end
98
+
99
+ def to_db(value)
100
+ value.to_s
101
+ end
102
+ alias to_ruby to_db
103
+ end
104
+ end
105
+
106
+ class BooleanConverter < BaseConverter
107
+ FALSE_VALUES = %w(n N no No NO false False FALSE off Off OFF f F).to_set
108
+
109
+ class << self
110
+ def converted?(value)
111
+ converted_values.include?(value)
112
+ end
113
+
114
+ def converted_values
115
+ [true, false]
116
+ end
117
+
118
+ def db_type
119
+ Neo4j::Shared::Boolean
120
+ end
121
+
122
+ alias convert_type db_type
123
+
124
+ def to_db(value)
125
+ return false if FALSE_VALUES.include?(value)
126
+ case value
127
+ when TrueClass, FalseClass
128
+ value
129
+ when Numeric, /^\-?[0-9]/
130
+ !value.to_f.zero?
131
+ else
132
+ value.present?
133
+ end
134
+ end
135
+
136
+ alias to_ruby to_db
137
+ end
138
+ end
139
+
140
+ # Converts Date objects to Java long types. Must be timezone UTC.
141
+ class DateConverter < BaseConverter
142
+ class << self
143
+ def convert_type
144
+ Date
145
+ end
146
+
147
+ def db_type
148
+ Integer
149
+ end
150
+
151
+ def to_db(value)
152
+ Time.utc(value.year, value.month, value.day).to_i
153
+ end
154
+
155
+ def to_ruby(value)
156
+ value.respond_to?(:to_date) ? value.to_date : Time.at(value).utc.to_date
157
+ end
158
+ end
159
+ end
160
+
161
+ # Converts DateTime objects to and from Java long types. Must be timezone UTC.
162
+ class DateTimeConverter < BaseConverter
163
+ class << self
164
+ def convert_type
165
+ DateTime
166
+ end
167
+
168
+ def db_type
169
+ Integer
170
+ end
171
+
172
+ # Converts the given DateTime (UTC) value to an Integer.
173
+ # DateTime values are automatically converted to UTC.
174
+ def to_db(value)
175
+ value = value.new_offset(0) if value.respond_to?(:new_offset)
176
+
177
+ args = [value.year, value.month, value.day]
178
+ args += (value.class == Date ? [0, 0, 0] : [value.hour, value.min, value.sec])
179
+
180
+ Time.utc(*args).to_i
181
+ end
182
+
183
+ def to_ruby(value)
184
+ return value if value.is_a?(DateTime)
185
+ t = case value
186
+ when Time
187
+ return value.to_datetime.utc
188
+ when Integer
189
+ Time.at(value).utc
190
+ when String
191
+ return value.to_datetime
192
+ else
193
+ fail ArgumentError, "Invalid value type for DateType property: #{value.inspect}"
194
+ end
195
+
196
+ DateTime.civil(t.year, t.month, t.day, t.hour, t.min, t.sec)
197
+ end
198
+ end
199
+ end
200
+
201
+ class TimeConverter < BaseConverter
202
+ class << self
203
+ def convert_type
204
+ Time
205
+ end
206
+
207
+ def db_type
208
+ Integer
209
+ end
210
+
211
+ # Converts the given DateTime (UTC) value to an Integer.
212
+ # Only utc times are supported !
213
+ def to_db(value)
214
+ if value.class == Date
215
+ Time.utc(value.year, value.month, value.day, 0, 0, 0).to_i
216
+ else
217
+ value.utc.to_i
218
+ end
219
+ end
220
+
221
+ def to_ruby(value)
222
+ Time.at(value).utc
223
+ end
224
+ end
225
+ end
226
+
227
+ # Converts hash to/from YAML
228
+ class YAMLConverter < BaseConverter
229
+ class << self
230
+ def convert_type
231
+ Hash
232
+ end
233
+
234
+ def db_type
235
+ String
236
+ end
237
+
238
+ def to_db(value)
239
+ Psych.dump(value)
240
+ end
241
+
242
+ def to_ruby(value)
243
+ Psych.load(value)
244
+ end
245
+ end
246
+ end
247
+
248
+ # Converts hash to/from JSON
249
+ class JSONConverter < BaseConverter
250
+ class << self
251
+ def converted?(_value)
252
+ false
253
+ end
254
+
255
+ def convert_type
256
+ JSON
257
+ end
258
+
259
+ def db_type
260
+ String
261
+ end
262
+
263
+ def to_db(value)
264
+ value.to_json
265
+ end
266
+
267
+ def to_ruby(value)
268
+ JSON.parse(value, quirks_mode: true)
269
+ end
270
+ end
271
+ end
272
+
273
+ class EnumConverter
274
+ def initialize(enum_keys, options)
275
+ @enum_keys = enum_keys
276
+ @options = options
277
+
278
+ return unless @options[:case_sensitive].nil?
279
+
280
+ @options[:case_sensitive] = Neo4j::Config.enums_case_sensitive
281
+ end
282
+
283
+ def converted?(value)
284
+ value.is_a?(db_type)
285
+ end
286
+
287
+ def supports_array?
288
+ true
289
+ end
290
+
291
+ def db_type
292
+ Integer
293
+ end
294
+
295
+ def convert_type
296
+ Symbol
297
+ end
298
+
299
+ def to_ruby(value)
300
+ @enum_keys.key(value) unless value.nil?
301
+ end
302
+
303
+ alias call to_ruby
304
+
305
+ def to_db(value)
306
+ if value.is_a?(Array)
307
+ value.map(&method(:to_db))
308
+ elsif @options[:case_sensitive]
309
+ @enum_keys[value.to_s.to_sym] ||
310
+ fail(Neo4j::Shared::Enum::InvalidEnumValueError, 'Value passed to an enum property must match one of the enum keys')
311
+ else
312
+ @enum_keys[value.to_s.downcase.to_sym] ||
313
+ fail(Neo4j::Shared::Enum::InvalidEnumValueError, 'Case-insensitive (downcased) value passed to an enum property must match one of the enum keys')
314
+ end
315
+ end
316
+ end
317
+
318
+ class ObjectConverter < BaseConverter
319
+ class << self
320
+ def convert_type
321
+ Object
322
+ end
323
+
324
+ def to_ruby(value)
325
+ value
326
+ end
327
+ end
328
+ end
329
+
330
+
331
+ # Modifies a hash's values to be of types acceptable to Neo4j or matching what the user defined using `type` in property definitions.
332
+ # @param [Neo4j::Shared::Property] obj A node or rel that mixes in the Property module
333
+ # @param [Symbol] medium Indicates the type of conversion to perform.
334
+ # @param [Hash] properties A hash of symbol-keyed properties for conversion.
335
+ def convert_properties_to(obj, medium, properties)
336
+ direction = medium == :ruby ? :to_ruby : :to_db
337
+ properties.each_pair do |key, value|
338
+ next if skip_conversion?(obj, key, value)
339
+ properties[key] = convert_property(key, value, direction)
340
+ end
341
+ end
342
+
343
+ # Converts a single property from its current format to its db- or Ruby-expected output type.
344
+ # @param [Symbol] key A property declared on the model
345
+ # @param value The value intended for conversion
346
+ # @param [Symbol] direction Either :to_ruby or :to_db, indicates the type of conversion to perform
347
+ def convert_property(key, value, direction)
348
+ converted_property(primitive_type(key.to_sym), value, direction)
349
+ end
350
+
351
+ def supports_array?(key)
352
+ type = primitive_type(key.to_sym)
353
+ type.respond_to?(:supports_array?) && type.supports_array?
354
+ end
355
+
356
+ def typecaster_for(value)
357
+ Neo4j::Shared::TypeConverters.typecaster_for(value)
358
+ end
359
+
360
+ def typecast_attribute(typecaster, value)
361
+ Neo4j::Shared::TypeConverters.typecast_attribute(typecaster, value)
362
+ end
363
+
364
+ private
365
+
366
+ def converted_property(type, value, direction)
367
+ return nil if value.nil?
368
+ CONVERTERS[type] || type.respond_to?(:db_type) ? TypeConverters.to_other(direction, value, type) : value
369
+ end
370
+
371
+ # If the attribute is to be typecast using a custom converter, which converter should it use? If no, returns the type to find a native serializer.
372
+ def primitive_type(attr)
373
+ case
374
+ when serialized_properties.include?(attr)
375
+ serialized_properties[attr]
376
+ when magic_typecast_properties.include?(attr)
377
+ magic_typecast_properties[attr]
378
+ else
379
+ fetch_upstream_primitive(attr)
380
+ end
381
+ end
382
+
383
+ # Returns true if the property isn't defined in the model or if it is nil
384
+ def skip_conversion?(obj, attr, value)
385
+ value.nil? || !obj.class.attributes.key?(attr)
386
+ end
387
+
388
+ class << self
389
+ def included(_)
390
+ Neo4j::Shared::TypeConverters.constants.each do |constant_name|
391
+ constant = Neo4j::Shared::TypeConverters.const_get(constant_name)
392
+ register_converter(constant) if constant.respond_to?(:convert_type)
393
+ end
394
+ end
395
+
396
+ def typecast_attribute(typecaster, value)
397
+ fail ArgumentError, "A typecaster must be given, #{typecaster} is invalid" unless typecaster.respond_to?(:to_ruby)
398
+ return value if value.nil?
399
+ typecaster.to_ruby(value)
400
+ end
401
+
402
+ def typecaster_for(primitive_type)
403
+ return nil if primitive_type.nil?
404
+ CONVERTERS[primitive_type]
405
+ end
406
+
407
+ # @param [Symbol] direction either :to_ruby or :to_other
408
+ def to_other(direction, value, type)
409
+ fail "Unknown direction given: #{direction}" unless direction == :to_ruby || direction == :to_db
410
+ found_converter = converter_for(type)
411
+ return value unless found_converter
412
+ return value if direction == :to_db && formatted_for_db?(found_converter, value)
413
+ found_converter.send(direction, value)
414
+ end
415
+
416
+ def converter_for(type)
417
+ type.respond_to?(:db_type) ? type : CONVERTERS[type]
418
+ end
419
+
420
+ # Attempts to determine whether conversion should be skipped because the object is already of the anticipated output type.
421
+ # @param [#convert_type] found_converter An object that responds to #convert_type, hinting that it is a type converter.
422
+ # @param value The value for conversion.
423
+ def formatted_for_db?(found_converter, value)
424
+ return false unless found_converter.respond_to?(:db_type)
425
+ found_converter.respond_to?(:converted?) ? found_converter.converted?(value) : value.is_a?(found_converter.db_type)
426
+ end
427
+
428
+ def register_converter(converter)
429
+ CONVERTERS[converter.convert_type] = converter
430
+ end
431
+ end
432
+ end
433
+ end