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,249 @@
1
+ module ActiveGraph::Shared
2
+ module Property
3
+ extend ActiveSupport::Concern
4
+
5
+ include ActiveGraph::Shared::MassAssignment
6
+ include ActiveGraph::Shared::TypecastedAttributes
7
+ include ActiveModel::Dirty
8
+
9
+ class UndefinedPropertyError < ActiveGraph::Error; end
10
+ class MultiparameterAssignmentError < ActiveGraph::Error; end
11
+
12
+ attr_reader :_persisted_obj
13
+
14
+ # This list should not be statically created. All types which have converters should by type casted
15
+ NEO4J_DRIVER_DATA_TYPES = [Hash, Neo4j::Driver::Types::Bytes, ActiveSupport::Duration, Neo4j::Driver::Types::Point,
16
+ Neo4j::Driver::Types::OffsetTime, Neo4j::Driver::Types::LocalTime, Neo4j::Driver::Types::LocalDateTime]
17
+
18
+ # TODO: Set @attribute correctly using class ActiveModel::Attribute, and after that
19
+ # remove mutations_from_database and other ActiveModel::Dirty overrided methods
20
+ def mutations_from_database
21
+ @mutations_from_database ||=
22
+ defined?(ActiveModel::ForcedMutationTracker) ? ActiveModel::ForcedMutationTracker.new(self) : ActiveModel::NullMutationTracker.instance
23
+ end
24
+
25
+ def inspect
26
+ attribute_descriptions = inspect_attributes.map do |key, value|
27
+ "#{ActiveGraph::ANSI::CYAN}#{key}: #{ActiveGraph::ANSI::CLEAR}#{value.inspect}"
28
+ end.join(', ')
29
+
30
+ separator = ' ' unless attribute_descriptions.empty?
31
+ "#<#{ActiveGraph::ANSI::YELLOW}#{self.class.name}#{ActiveGraph::ANSI::CLEAR}#{separator}#{attribute_descriptions}>"
32
+ end
33
+
34
+ def initialize(attributes = nil)
35
+ @attributes ||= ActiveGraph::AttributeSet.new({}, self.class.attributes.keys)
36
+ attributes = process_attributes(attributes)
37
+ modded_attributes = inject_defaults!(attributes)
38
+ validate_attributes!(modded_attributes)
39
+ writer_method_props = extract_writer_methods!(modded_attributes)
40
+ send_props(writer_method_props)
41
+ self.undeclared_properties = attributes
42
+ @_persisted_obj = nil
43
+ end
44
+
45
+ def undeclared_properties=(_); end
46
+
47
+ def inject_defaults!(starting_props)
48
+ return starting_props if self.class.declared_properties.declared_property_defaults.empty?
49
+ self.class.declared_properties.inject_defaults!(self, starting_props || {})
50
+ end
51
+
52
+ def read_attribute(name)
53
+ respond_to?(name) ? send(name) : nil
54
+ end
55
+ alias [] read_attribute
56
+
57
+ def send_props(hash)
58
+ return hash if hash.blank?
59
+ hash.each { |key, value| send("#{key}=", value) }
60
+ end
61
+
62
+ def reload_properties!(properties)
63
+ @attributes = nil
64
+ convert_and_assign_attributes(properties)
65
+ end
66
+
67
+ private
68
+
69
+ # Changes attributes hash to remove relationship keys
70
+ # Raises an error if there are any keys left which haven't been defined as properties on the model
71
+ # TODO: use declared_properties instead of self.attributes
72
+ def validate_attributes!(attributes)
73
+ return attributes if attributes.blank?
74
+ invalid_properties = attributes.keys.map(&:to_s) - self.attributes.keys
75
+ invalid_properties.reject! { |name| self.respond_to?("#{name}=") }
76
+ fail UndefinedPropertyError, "Undefined properties: #{invalid_properties.join(',')}" if !invalid_properties.empty?
77
+ end
78
+
79
+ def extract_writer_methods!(attributes)
80
+ return attributes if attributes.blank?
81
+ {}.tap do |writer_method_props|
82
+ attributes.keys.each do |key|
83
+ writer_method_props[key] = attributes.delete(key) if self.respond_to?("#{key}=")
84
+ end
85
+ end
86
+ end
87
+
88
+ DATE_KEY_REGEX = /\A([^\(]+)\((\d+)([ifs])\)$/
89
+ # Gives support for Rails date_select, datetime_select, time_select helpers.
90
+ def process_attributes(attributes = nil)
91
+ return attributes if attributes.blank?
92
+ multi_parameter_attributes = {}
93
+ new_attributes = {}
94
+ attributes.each_pair do |key, value|
95
+ if key.match(DATE_KEY_REGEX)
96
+ match = key.to_s.match(DATE_KEY_REGEX)
97
+ found_key = match[1]
98
+ index = match[2].to_i
99
+ (multi_parameter_attributes[found_key] ||= {})[index] = value.empty? ? nil : value.send("to_#{$3}")
100
+ else
101
+ new_attributes[key] = value
102
+ end
103
+ end
104
+
105
+ multi_parameter_attributes.empty? ? new_attributes : process_multiparameter_attributes(multi_parameter_attributes, new_attributes)
106
+ end
107
+
108
+ def process_multiparameter_attributes(multi_parameter_attributes, new_attributes)
109
+ multi_parameter_attributes.each_with_object(new_attributes) do |(key, values), attributes|
110
+ values = (values.keys.min..values.keys.max).map { |i| values[i] }
111
+ if (field = self.class.attributes[key.to_sym]).nil?
112
+ fail MultiparameterAssignmentError, "error on assignment #{values.inspect} to #{key}"
113
+ end
114
+
115
+ attributes[key] = instantiate_object(field, values)
116
+ end
117
+ end
118
+
119
+ def instantiate_object(field, values_with_empty_parameters)
120
+ return nil if values_with_empty_parameters.all?(&:nil?)
121
+ values = values_with_empty_parameters.collect { |v| v.nil? ? 1 : v }
122
+ klass = field.type
123
+ klass ? klass.new(*values) : values
124
+ end
125
+
126
+ module ClassMethods
127
+ extend Forwardable
128
+
129
+ def_delegators :declared_properties, :serialized_properties, :serialized_properties=, :serialize, :declared_property_defaults
130
+
131
+ VALID_PROPERTY_OPTIONS = %w(type default index constraint serializer typecaster).map(&:to_sym)
132
+ # Defines a property on the class
133
+ #
134
+ # See active_attr gem for allowed options, e.g which type
135
+ # Notice, in ActiveGraph you don't have to declare properties before using them, see the ActiveGraph::Coree api.
136
+ #
137
+ # @example Without type
138
+ # class Person
139
+ # # declare a property which can have any value
140
+ # property :name
141
+ # end
142
+ #
143
+ # @example With type and a default value
144
+ # class Person
145
+ # # declare a property which can have any value
146
+ # property :score, type: Integer, default: 0
147
+ # end
148
+ #
149
+ # @example With an index
150
+ # class Person
151
+ # # declare a property which can have any value
152
+ # property :name, index: :exact
153
+ # end
154
+ #
155
+ # @example With a constraint
156
+ # class Person
157
+ # # declare a property which can have any value
158
+ # property :name, constraint: :unique
159
+ # end
160
+ def property(name, options = {})
161
+ invalid_option_keys = options.keys.map(&:to_sym) - VALID_PROPERTY_OPTIONS
162
+ fail ArgumentError, "Invalid options for property `#{name}` on `#{self.name}`: #{invalid_option_keys.join(', ')}" if invalid_option_keys.any?
163
+ build_property(name, options) do |prop|
164
+ attribute(prop)
165
+ end
166
+ end
167
+
168
+ # @param [Symbol] name The property name
169
+ # @param [ActiveGraph::Shared::AttributeDefinition] attr_def A cloned AttributeDefinition to reuse
170
+ # @param [Hash] options An options hash to use in the new property definition
171
+ def inherit_property(name, attr_def, options = {})
172
+ build_property(name, options) do |prop_name|
173
+ attributes[prop_name] = attr_def
174
+ end
175
+ end
176
+
177
+ def build_property(name, options)
178
+ decl_prop = DeclaredProperty.new(name, options).tap do |prop|
179
+ prop.register
180
+ declared_properties.register(prop)
181
+ yield name
182
+ constraint_or_index(name, options)
183
+ end
184
+
185
+ # If this class has already been inherited, make sure subclasses inherit property
186
+ subclasses.each do |klass|
187
+ klass.inherit_property name, decl_prop.clone, declared_properties[name].options
188
+ end
189
+
190
+ decl_prop
191
+ end
192
+
193
+ def undef_property(name)
194
+ undef_constraint_or_index(name)
195
+ declared_properties.unregister(name)
196
+ attribute_methods(name).each { |method| undef_method(method) }
197
+ end
198
+
199
+ def declared_properties
200
+ @_declared_properties ||= DeclaredProperties.new(self)
201
+ end
202
+
203
+ # @return [Hash] A frozen hash of all model properties with nil values. It is used during node loading and prevents
204
+ # an extra call to a slow dependency method.
205
+ def attributes_nil_hash
206
+ declared_properties.attributes_nil_hash
207
+ end
208
+
209
+ def extract_association_attributes!(props)
210
+ props
211
+ end
212
+
213
+ private
214
+
215
+ def attribute!(name)
216
+ remove_instance_variable('@attribute_methods_generated') if instance_variable_defined?('@attribute_methods_generated')
217
+ define_attribute_methods([name]) unless attribute_names.include?(name)
218
+ attributes[name.to_s] = declared_properties[name]
219
+ define_method("#{name}=") do |value|
220
+ typecast_value = if NEO4J_DRIVER_DATA_TYPES.include?(_attribute_type(name))
221
+ value
222
+ else
223
+ typecast_attribute(_attribute_typecaster(name), value)
224
+ end
225
+ send("#{name}_will_change!") unless typecast_value == read_attribute(name)
226
+ super(value)
227
+ end
228
+ end
229
+
230
+ def constraint_or_index(name, options)
231
+ # either constraint or index, do not set both
232
+ if options[:constraint]
233
+ fail "unknown constraint type #{options[:constraint]}, only :unique supported" if options[:constraint] != :unique
234
+ constraint(name, type: :unique)
235
+ elsif options[:index]
236
+ fail "unknown index type #{options[:index]}, only :exact supported" if options[:index] != :exact
237
+ index(name) if options[:index] == :exact
238
+ end
239
+ end
240
+
241
+ def undef_constraint_or_index(name)
242
+ prop = declared_properties[name]
243
+ return unless prop.index_or_constraint?
244
+ type = prop.constraint? ? :constraint : :index
245
+ send(:"drop_#{type}", name)
246
+ end
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,122 @@
1
+ module ActiveGraph::Shared
2
+ # Acts as a bridge between the node and rel models and ActiveGraph::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?(: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 [ActiveGraph::Core::Query] query An instance of ActiveGraph::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 || ActiveGraph::Base.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(create_props)).break
88
+ .set(identifier => set_props)
89
+ .params(params(create_props))
90
+ end
91
+
92
+ private
93
+
94
+ def filtered_props
95
+ ActiveGraph::Shared::FilteredHash.new(graph_object.props_for_create, graph_object.creates_unique_option).filtered_base
96
+ end
97
+
98
+ def query_string(create_props)
99
+ "(#{graph_object.from_node_identifier})-[#{identifier}:`#{graph_object.type}` #{pattern(create_props)}]->(#{graph_object.to_node_identifier})"
100
+ end
101
+
102
+ def params(create_props)
103
+ unique? ? create_props.transform_keys { |key| scoped(key).to_sym } : { namespace.to_sym => create_props }
104
+ end
105
+
106
+ def unique?
107
+ graph_object.create_method == :create_unique
108
+ end
109
+
110
+ def pattern(create_props)
111
+ unique? ? "{#{create_props.keys.map { |key| "#{key}: $#{scoped(key)}" }.join(', ')}}" : "$#{namespace}"
112
+ end
113
+
114
+ def scoped(key)
115
+ "#{namespace}_#{key}"
116
+ end
117
+
118
+ def namespace
119
+ "#{identifier}_create_props"
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,43 @@
1
+ module ActiveGraph::Shared
2
+ # This module controls changes to relationship type based on ActiveGraph::Config.transform_rel_type.
3
+ # It's used whenever a rel type is automatically determined based on Relationship model name or
4
+ # association type.
5
+ module RelTypeConverters
6
+ def decorated_rel_type(type)
7
+ @decorated_rel_type ||= ActiveGraph::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 Relationship 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 ||= ActiveGraph::Config[:transform_rel_type].nil? ? :upcase : ActiveGraph::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 ActiveGraph::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,439 @@
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 ActiveGraph::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
+ NEO4J_LARGEST_INT = 9223372036854775807
29
+ NEO4J_SMALLEST_INT = -9223372036854775808
30
+ class << self
31
+ def converted?(value)
32
+ false
33
+ end
34
+
35
+ def convert_type
36
+ Integer
37
+ end
38
+
39
+ def db_type
40
+ Integer
41
+ end
42
+
43
+ def to_db(value)
44
+ val = value.to_i
45
+ val > NEO4J_LARGEST_INT || val < NEO4J_SMALLEST_INT ? val.to_s : val
46
+ end
47
+
48
+ def to_ruby(value)
49
+ value.to_i
50
+ end
51
+ end
52
+ end
53
+
54
+ class FloatConverter < BaseConverter
55
+ class << self
56
+ def convert_type
57
+ Float
58
+ end
59
+
60
+ def db_type
61
+ Float
62
+ end
63
+
64
+ def to_db(value)
65
+ value.to_f
66
+ end
67
+
68
+ alias to_ruby to_db
69
+ end
70
+ end
71
+
72
+ class BigDecimalConverter < BaseConverter
73
+ class << self
74
+ def convert_type
75
+ BigDecimal
76
+ end
77
+
78
+ def db_type
79
+ String
80
+ end
81
+
82
+ def to_db(value)
83
+ case value
84
+ when Rational
85
+ value.to_f.to_d
86
+ when respond_to?(:to_d)
87
+ value.to_d
88
+ else
89
+ BigDecimal(value.to_s)
90
+ end.to_s
91
+ end
92
+
93
+ def to_ruby(value)
94
+ value.to_d
95
+ end
96
+ end
97
+ end
98
+
99
+ class StringConverter < BaseConverter
100
+ class << self
101
+ def convert_type
102
+ String
103
+ end
104
+
105
+ def db_type
106
+ String
107
+ end
108
+
109
+ def to_db(value)
110
+ value.to_s
111
+ end
112
+
113
+ alias to_ruby to_db
114
+ end
115
+ end
116
+
117
+ # Converts Java long types to Date objects. Must be timezone UTC.
118
+ class DateConverter < BaseConverter
119
+ class << self
120
+ def convert_type
121
+ Date
122
+ end
123
+
124
+ def db_type
125
+ Date
126
+ end
127
+
128
+ def to_ruby(value)
129
+ value.respond_to?(:to_date) ? value.to_date : Time.at(value).utc.to_date
130
+ end
131
+
132
+ alias to_db to_ruby
133
+ end
134
+ end
135
+
136
+ # Converts DateTime objects to and from Java long types. Must be timezone UTC.
137
+ class DateTimeConverter < BaseConverter
138
+ class << self
139
+ def convert_type
140
+ DateTime
141
+ end
142
+
143
+ def db_type
144
+ Integer
145
+ end
146
+
147
+ # Converts the given DateTime (UTC) value to an Integer.
148
+ # DateTime values are automatically converted to UTC.
149
+ def to_db(value)
150
+ value = value.new_offset(0) if value.respond_to?(:new_offset)
151
+
152
+ args = [value.year, value.month, value.day]
153
+ args += (value.class == Date ? [0, 0, 0] : [value.hour, value.min, value.sec])
154
+
155
+ Time.utc(*args).to_i
156
+ end
157
+
158
+ def to_ruby(value)
159
+ return value if value.is_a?(DateTime)
160
+ t = case value
161
+ when Time
162
+ return value.to_datetime.utc
163
+ when Integer
164
+ Time.at(value).utc
165
+ when String
166
+ return value.to_datetime
167
+ else
168
+ fail ArgumentError, "Invalid value type for DateType property: #{value.inspect}"
169
+ end
170
+
171
+ DateTime.civil(t.year, t.month, t.day, t.hour, t.min, t.sec)
172
+ end
173
+ end
174
+ end
175
+
176
+ class TimeConverter < BaseConverter
177
+ class << self
178
+ def convert_type
179
+ Time
180
+ end
181
+
182
+ def db_type
183
+ Time
184
+ end
185
+
186
+ def to_ruby(value)
187
+ value.respond_to?(:to_time) ? value.to_time : Time.at(value).utc
188
+ end
189
+
190
+ alias to_db to_ruby
191
+ end
192
+ end
193
+
194
+ class BooleanConverter < BaseConverter
195
+ FALSE_VALUES = %w(n N no No NO false False FALSE off Off OFF f F).to_set
196
+
197
+ class << self
198
+ def converted?(value)
199
+ converted_values.include?(value)
200
+ end
201
+
202
+ def converted_values
203
+ [true, false]
204
+ end
205
+
206
+ def db_type
207
+ ActiveGraph::Shared::Boolean
208
+ end
209
+
210
+ alias convert_type db_type
211
+
212
+ def to_db(value)
213
+ return false if FALSE_VALUES.include?(value)
214
+ case value
215
+ when TrueClass, FalseClass
216
+ value
217
+ when Numeric, /^\-?[0-9]/
218
+ !value.to_f.zero?
219
+ else
220
+ value.present?
221
+ end
222
+ end
223
+
224
+ alias to_ruby to_db
225
+ end
226
+ end
227
+
228
+ # Converts hash to/from YAML
229
+ class YAMLConverter < BaseConverter
230
+ class << self
231
+ def convert_type
232
+ Hash
233
+ end
234
+
235
+ def db_type
236
+ String
237
+ end
238
+
239
+ def to_db(value)
240
+ Psych.dump(value)
241
+ end
242
+
243
+ def to_ruby(value)
244
+ value.is_a?(Hash) ? value : Psych.load(value)
245
+ end
246
+ end
247
+ end
248
+
249
+ # Converts hash to/from JSON
250
+ class JSONConverter < BaseConverter
251
+ class << self
252
+ def converted?(_value)
253
+ false
254
+ end
255
+
256
+ def convert_type
257
+ JSON
258
+ end
259
+
260
+ def db_type
261
+ String
262
+ end
263
+
264
+ def to_db(value)
265
+ value.to_json
266
+ end
267
+
268
+ def to_ruby(value)
269
+ JSON.parse(value, quirks_mode: true)
270
+ end
271
+ end
272
+ end
273
+
274
+ class EnumConverter
275
+ def initialize(enum_keys, options)
276
+ @enum_keys = enum_keys
277
+ @options = options
278
+
279
+ return unless @options[:case_sensitive].nil?
280
+
281
+ @options[:case_sensitive] = ActiveGraph::Config.enums_case_sensitive
282
+ end
283
+
284
+ def converted?(value)
285
+ value.is_a?(db_type)
286
+ end
287
+
288
+ def supports_array?
289
+ true
290
+ end
291
+
292
+ def db_type
293
+ Integer
294
+ end
295
+
296
+ def convert_type
297
+ Symbol
298
+ end
299
+
300
+ def to_ruby(value)
301
+ @enum_keys.key(value) unless value.nil?
302
+ end
303
+
304
+ alias call to_ruby
305
+
306
+ def to_db(value)
307
+ if value.is_a?(Array)
308
+ value.map(&method(:to_db))
309
+ elsif @options[:case_sensitive]
310
+ @enum_keys[value.to_s.to_sym] ||
311
+ fail(ActiveGraph::Shared::Enum::InvalidEnumValueError, 'Value passed to an enum property must match one of the enum keys')
312
+ else
313
+ @enum_keys[value.to_s.downcase.to_sym] ||
314
+ fail(ActiveGraph::Shared::Enum::InvalidEnumValueError, 'Case-insensitive (downcased) value passed to an enum property must match one of the enum keys')
315
+ end
316
+ end
317
+ end
318
+
319
+ class ObjectConverter < BaseConverter
320
+ class << self
321
+ def convert_type
322
+ Object
323
+ end
324
+
325
+ def to_ruby(value)
326
+ value
327
+ end
328
+ end
329
+ end
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 [ActiveGraph::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.to_h.each_pair do |key, value|
338
+ next if skip_conversion?(obj, key, value)
339
+
340
+ converted_value = convert_property(key, value, direction)
341
+ if properties.is_a?(ActiveGraph::AttributeSet)
342
+ properties.write_cast_value(key, converted_value)
343
+ else
344
+ properties[key] = converted_value
345
+ end
346
+ end
347
+ end
348
+
349
+ # Converts a single property from its current format to its db- or Ruby-expected output type.
350
+ # @param [Symbol] key A property declared on the model
351
+ # @param value The value intended for conversion
352
+ # @param [Symbol] direction Either :to_ruby or :to_db, indicates the type of conversion to perform
353
+ def convert_property(key, value, direction)
354
+ converted_property(primitive_type(key.to_sym), value, direction)
355
+ end
356
+
357
+ def supports_array?(key)
358
+ type = primitive_type(key.to_sym)
359
+ type.respond_to?(:supports_array?) && type.supports_array?
360
+ end
361
+
362
+ def typecaster_for(value)
363
+ ActiveGraph::Shared::TypeConverters.typecaster_for(value)
364
+ end
365
+
366
+ def typecast_attribute(typecaster, value)
367
+ ActiveGraph::Shared::TypeConverters.typecast_attribute(typecaster, value)
368
+ end
369
+
370
+ private
371
+
372
+ def converted_property(type, value, direction)
373
+ return nil if value.nil?
374
+ CONVERTERS[type] || type.respond_to?(:db_type) ? TypeConverters.to_other(direction, value, type) : value
375
+ end
376
+
377
+ # 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.
378
+ def primitive_type(attr)
379
+ case
380
+ when serialized_properties.include?(attr)
381
+ serialized_properties[attr]
382
+ when magic_typecast_properties.include?(attr)
383
+ magic_typecast_properties[attr]
384
+ else
385
+ fetch_upstream_primitive(attr)
386
+ end
387
+ end
388
+
389
+ # Returns true if the property isn't defined in the model or if it is nil
390
+ def skip_conversion?(obj, attr, value)
391
+ value.nil? || !obj.class.attributes.key?(attr)
392
+ end
393
+
394
+ class << self
395
+ def included(_)
396
+ ActiveGraph::Shared::TypeConverters.constants.each do |constant_name|
397
+ constant = ActiveGraph::Shared::TypeConverters.const_get(constant_name)
398
+ register_converter(constant) if constant.respond_to?(:convert_type)
399
+ end
400
+ end
401
+
402
+ def typecast_attribute(typecaster, value)
403
+ fail ArgumentError, "A typecaster must be given, #{typecaster} is invalid" unless typecaster.respond_to?(:to_ruby)
404
+ return value if value.nil?
405
+ typecaster.to_ruby(value)
406
+ end
407
+
408
+ def typecaster_for(primitive_type)
409
+ return nil if primitive_type.nil?
410
+ CONVERTERS[primitive_type]
411
+ end
412
+
413
+ # @param [Symbol] direction either :to_ruby or :to_other
414
+ def to_other(direction, value, type)
415
+ fail "Unknown direction given: #{direction}" unless direction == :to_ruby || direction == :to_db
416
+ found_converter = converter_for(type)
417
+ return value unless found_converter
418
+ return value if direction == :to_db && formatted_for_db?(found_converter, value)
419
+ found_converter.send(direction, value)
420
+ end
421
+
422
+ def converter_for(type)
423
+ type.respond_to?(:db_type) ? type : CONVERTERS[type]
424
+ end
425
+
426
+ # Attempts to determine whether conversion should be skipped because the object is already of the anticipated output type.
427
+ # @param [#convert_type] found_converter An object that responds to #convert_type, hinting that it is a type converter.
428
+ # @param value The value for conversion.
429
+ def formatted_for_db?(found_converter, value)
430
+ return false unless found_converter.respond_to?(:db_type)
431
+ found_converter.respond_to?(:converted?) ? found_converter.converted?(value) : value.is_a?(found_converter.db_type)
432
+ end
433
+
434
+ def register_converter(converter)
435
+ CONVERTERS[converter.convert_type] = converter
436
+ end
437
+ end
438
+ end
439
+ end