activegraph 10.0.0.pre.alpha.6

Sign up to get free protection for your applications and to get access to all the features.
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