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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2016 -0
- data/CONTRIBUTORS +12 -0
- data/Gemfile +24 -0
- data/README.md +111 -0
- data/activegraph.gemspec +52 -0
- data/bin/rake +17 -0
- data/config/locales/en.yml +5 -0
- data/config/neo4j/add_classnames.yml +1 -0
- data/config/neo4j/config.yml +35 -0
- data/lib/active_graph.rb +123 -0
- data/lib/active_graph/ansi.rb +14 -0
- data/lib/active_graph/attribute_set.rb +32 -0
- data/lib/active_graph/base.rb +77 -0
- data/lib/active_graph/class_arguments.rb +39 -0
- data/lib/active_graph/config.rb +135 -0
- data/lib/active_graph/core.rb +14 -0
- data/lib/active_graph/core/connection_failed_error.rb +6 -0
- data/lib/active_graph/core/cypher_error.rb +37 -0
- data/lib/active_graph/core/entity.rb +11 -0
- data/lib/active_graph/core/instrumentable.rb +37 -0
- data/lib/active_graph/core/label.rb +135 -0
- data/lib/active_graph/core/logging.rb +44 -0
- data/lib/active_graph/core/node.rb +15 -0
- data/lib/active_graph/core/querable.rb +41 -0
- data/lib/active_graph/core/query.rb +485 -0
- data/lib/active_graph/core/query_builder.rb +18 -0
- data/lib/active_graph/core/query_clauses.rb +727 -0
- data/lib/active_graph/core/query_ext.rb +24 -0
- data/lib/active_graph/core/query_find_in_batches.rb +46 -0
- data/lib/active_graph/core/record.rb +51 -0
- data/lib/active_graph/core/result.rb +31 -0
- data/lib/active_graph/core/schema.rb +65 -0
- data/lib/active_graph/core/schema_errors.rb +12 -0
- data/lib/active_graph/core/wrappable.rb +30 -0
- data/lib/active_graph/errors.rb +59 -0
- data/lib/active_graph/lazy_attribute_hash.rb +38 -0
- data/lib/active_graph/migration.rb +148 -0
- data/lib/active_graph/migrations.rb +27 -0
- data/lib/active_graph/migrations/base.rb +77 -0
- data/lib/active_graph/migrations/check_pending.rb +20 -0
- data/lib/active_graph/migrations/helpers.rb +105 -0
- data/lib/active_graph/migrations/helpers/id_property.rb +72 -0
- data/lib/active_graph/migrations/helpers/relationships.rb +66 -0
- data/lib/active_graph/migrations/helpers/schema.rb +63 -0
- data/lib/active_graph/migrations/migration_file.rb +24 -0
- data/lib/active_graph/migrations/runner.rb +195 -0
- data/lib/active_graph/migrations/schema.rb +64 -0
- data/lib/active_graph/migrations/schema_migration.rb +14 -0
- data/lib/active_graph/model_schema.rb +139 -0
- data/lib/active_graph/node.rb +110 -0
- data/lib/active_graph/node/callbacks.rb +8 -0
- data/lib/active_graph/node/dependent.rb +11 -0
- data/lib/active_graph/node/dependent/association_methods.rb +49 -0
- data/lib/active_graph/node/dependent/query_proxy_methods.rb +52 -0
- data/lib/active_graph/node/dependent_callbacks.rb +31 -0
- data/lib/active_graph/node/enum.rb +26 -0
- data/lib/active_graph/node/has_n.rb +602 -0
- data/lib/active_graph/node/has_n/association.rb +278 -0
- data/lib/active_graph/node/has_n/association/rel_factory.rb +61 -0
- data/lib/active_graph/node/has_n/association/rel_wrapper.rb +23 -0
- data/lib/active_graph/node/has_n/association_cypher_methods.rb +108 -0
- data/lib/active_graph/node/id_property.rb +224 -0
- data/lib/active_graph/node/id_property/accessor.rb +62 -0
- data/lib/active_graph/node/initialize.rb +21 -0
- data/lib/active_graph/node/labels.rb +207 -0
- data/lib/active_graph/node/labels/index.rb +37 -0
- data/lib/active_graph/node/labels/reloading.rb +21 -0
- data/lib/active_graph/node/node_list_formatter.rb +13 -0
- data/lib/active_graph/node/node_wrapper.rb +54 -0
- data/lib/active_graph/node/orm_adapter.rb +82 -0
- data/lib/active_graph/node/persistence.rb +186 -0
- data/lib/active_graph/node/property.rb +60 -0
- data/lib/active_graph/node/query.rb +76 -0
- data/lib/active_graph/node/query/query_proxy.rb +367 -0
- data/lib/active_graph/node/query/query_proxy_eager_loading.rb +177 -0
- data/lib/active_graph/node/query/query_proxy_eager_loading/association_tree.rb +75 -0
- data/lib/active_graph/node/query/query_proxy_enumerable.rb +110 -0
- data/lib/active_graph/node/query/query_proxy_find_in_batches.rb +19 -0
- data/lib/active_graph/node/query/query_proxy_link.rb +139 -0
- data/lib/active_graph/node/query/query_proxy_methods.rb +303 -0
- data/lib/active_graph/node/query/query_proxy_methods_of_mass_updating.rb +99 -0
- data/lib/active_graph/node/query_methods.rb +68 -0
- data/lib/active_graph/node/reflection.rb +86 -0
- data/lib/active_graph/node/rels.rb +11 -0
- data/lib/active_graph/node/scope.rb +166 -0
- data/lib/active_graph/node/unpersisted.rb +48 -0
- data/lib/active_graph/node/validations.rb +59 -0
- data/lib/active_graph/paginated.rb +27 -0
- data/lib/active_graph/railtie.rb +108 -0
- data/lib/active_graph/relationship.rb +68 -0
- data/lib/active_graph/relationship/callbacks.rb +21 -0
- data/lib/active_graph/relationship/initialize.rb +28 -0
- data/lib/active_graph/relationship/persistence.rb +133 -0
- data/lib/active_graph/relationship/persistence/query_factory.rb +95 -0
- data/lib/active_graph/relationship/property.rb +92 -0
- data/lib/active_graph/relationship/query.rb +99 -0
- data/lib/active_graph/relationship/rel_wrapper.rb +31 -0
- data/lib/active_graph/relationship/related_node.rb +87 -0
- data/lib/active_graph/relationship/types.rb +80 -0
- data/lib/active_graph/relationship/validations.rb +8 -0
- data/lib/active_graph/schema/operation.rb +102 -0
- data/lib/active_graph/shared.rb +48 -0
- data/lib/active_graph/shared/attributes.rb +217 -0
- data/lib/active_graph/shared/callbacks.rb +66 -0
- data/lib/active_graph/shared/cypher.rb +37 -0
- data/lib/active_graph/shared/declared_properties.rb +204 -0
- data/lib/active_graph/shared/declared_property.rb +109 -0
- data/lib/active_graph/shared/declared_property/index.rb +37 -0
- data/lib/active_graph/shared/enum.rb +167 -0
- data/lib/active_graph/shared/filtered_hash.rb +79 -0
- data/lib/active_graph/shared/identity.rb +34 -0
- data/lib/active_graph/shared/initialize.rb +65 -0
- data/lib/active_graph/shared/marshal.rb +23 -0
- data/lib/active_graph/shared/mass_assignment.rb +63 -0
- data/lib/active_graph/shared/permitted_attributes.rb +28 -0
- data/lib/active_graph/shared/persistence.rb +272 -0
- data/lib/active_graph/shared/property.rb +249 -0
- data/lib/active_graph/shared/query_factory.rb +122 -0
- data/lib/active_graph/shared/rel_type_converters.rb +43 -0
- data/lib/active_graph/shared/serialized_properties.rb +30 -0
- data/lib/active_graph/shared/type_converters.rb +439 -0
- data/lib/active_graph/shared/typecasted_attributes.rb +99 -0
- data/lib/active_graph/shared/typecaster.rb +53 -0
- data/lib/active_graph/shared/validations.rb +44 -0
- data/lib/active_graph/tasks/migration.rake +204 -0
- data/lib/active_graph/timestamps.rb +11 -0
- data/lib/active_graph/timestamps/created.rb +9 -0
- data/lib/active_graph/timestamps/updated.rb +9 -0
- data/lib/active_graph/transaction.rb +22 -0
- data/lib/active_graph/transactions.rb +57 -0
- data/lib/active_graph/type_converters.rb +7 -0
- data/lib/active_graph/undeclared_properties.rb +53 -0
- data/lib/active_graph/version.rb +3 -0
- data/lib/active_graph/wrapper.rb +4 -0
- data/lib/rails/generators/active_graph/migration/migration_generator.rb +16 -0
- data/lib/rails/generators/active_graph/migration/templates/migration.erb +9 -0
- data/lib/rails/generators/active_graph/model/model_generator.rb +89 -0
- data/lib/rails/generators/active_graph/model/templates/migration.erb +11 -0
- data/lib/rails/generators/active_graph/model/templates/model.erb +15 -0
- data/lib/rails/generators/active_graph/upgrade_v8/templates/migration.erb +17 -0
- data/lib/rails/generators/active_graph/upgrade_v8/upgrade_v8_generator.rb +34 -0
- data/lib/rails/generators/active_graph_generator.rb +121 -0
- 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
|