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,204 @@
|
|
|
1
|
+
module ActiveGraph::Shared
|
|
2
|
+
# The DeclaredPropertyuManager holds details about objects created as a result of calling the #property
|
|
3
|
+
# class method on a class that includes ActiveGraph::Node or ActiveGraph::Relationship. There are many options
|
|
4
|
+
# that are referenced frequently, particularly during load and save, so this provides easy access and
|
|
5
|
+
# a way of separating behavior from the general Active{obj} modules.
|
|
6
|
+
#
|
|
7
|
+
# See ActiveGraph::Shared::DeclaredProperty for definitions of the property objects themselves.
|
|
8
|
+
class DeclaredProperties
|
|
9
|
+
include ActiveGraph::Shared::TypeConverters
|
|
10
|
+
|
|
11
|
+
attr_reader :klass
|
|
12
|
+
delegate :each, :each_pair, :each_key, :each_value, to: :registered_properties
|
|
13
|
+
|
|
14
|
+
# Each class that includes ActiveGraph::Node or ActiveGraph::Relationship gets one instance of this class.
|
|
15
|
+
# @param [#declared_properties] klass An object that has the #declared_properties method.
|
|
16
|
+
def initialize(klass)
|
|
17
|
+
@klass = klass
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def [](key)
|
|
21
|
+
registered_properties[key.to_sym]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def property?(key)
|
|
25
|
+
registered_properties.key?(key.to_sym)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @param [ActiveGraph::Shared::DeclaredProperty] property An instance of DeclaredProperty, created as the result of calling
|
|
29
|
+
# #property on an Node or Relationship class. The DeclaredProperty has specifics about the property, but registration
|
|
30
|
+
# makes the management object aware of it. This is necessary for type conversion, defaults, and inclusion in the nil and string hashes.
|
|
31
|
+
def register(property)
|
|
32
|
+
@_attributes_nil_hash = nil
|
|
33
|
+
@_attributes_string_map = nil
|
|
34
|
+
registered_properties[property.name] = property
|
|
35
|
+
register_magic_typecaster(property) if property.magic_typecaster
|
|
36
|
+
declared_property_defaults[property.name] = property.default_value if !property.default_value.nil?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def index_or_fail!(key, id_property_name, type = :exact)
|
|
40
|
+
return if key == id_property_name
|
|
41
|
+
fail "Cannot index undeclared property #{key}" unless property?(key)
|
|
42
|
+
registered_properties[key].index!(type)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def constraint_or_fail!(key, id_property_name, type = :unique)
|
|
46
|
+
return if key == id_property_name
|
|
47
|
+
fail "Cannot constraint undeclared property #{property}" unless property?(key)
|
|
48
|
+
registered_properties[key].constraint!(type)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# The :default option in ActiveGraph::Node#property class method allows for setting a default value instead of
|
|
52
|
+
# nil on declared properties. This holds those values.
|
|
53
|
+
def declared_property_defaults
|
|
54
|
+
@_default_property_values ||= {}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def registered_properties
|
|
58
|
+
@_registered_properties ||= {}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def indexed_properties
|
|
62
|
+
registered_properties.select { |_, p| p.index_or_constraint? }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# During object wrap, a hash is needed that contains each declared property with a nil value.
|
|
66
|
+
# The active_attr dependency is capable of providing this but it is expensive and calculated on the fly
|
|
67
|
+
# each time it is called. Rather than rely on that, we build this progressively as properties are registered.
|
|
68
|
+
# When the node or rel is loaded, this is used as a template.
|
|
69
|
+
def attributes_nil_hash
|
|
70
|
+
@_attributes_nil_hash ||= {}.tap do |attr_hash|
|
|
71
|
+
registered_properties.each_pair do |k, prop_obj|
|
|
72
|
+
val = prop_obj.default_value
|
|
73
|
+
attr_hash[k.to_s] = val
|
|
74
|
+
end
|
|
75
|
+
end.freeze
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# During object wrapping, a props hash is built with string keys but ActiveGraph::Core provides symbols.
|
|
79
|
+
# Rather than a `to_s` or `symbolize_keys` during every load, we build a map of symbol-to-string
|
|
80
|
+
# to speed up the process. This increases memory used by the gem but reduces object allocation and GC, so it is faster
|
|
81
|
+
# in practice.
|
|
82
|
+
def attributes_string_map
|
|
83
|
+
@_attributes_string_map ||= {}.tap do |attr_hash|
|
|
84
|
+
attributes_nil_hash.each_key { |k| attr_hash[k.to_sym] = k }
|
|
85
|
+
end.freeze
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @param [Symbol] k A symbol for which the String representation is sought. This might seem silly -- we could just call #to_s --
|
|
89
|
+
# but when this happens many times while loading many objects, it results in a surprisingly significant slowdown.
|
|
90
|
+
# The branching logic handles what happens if a property can't be found.
|
|
91
|
+
# The first option attempts to find it in the existing hash.
|
|
92
|
+
# The second option checks whether the key is the class's id property and, if it is, the string hash is rebuilt with it to prevent
|
|
93
|
+
# future lookups.
|
|
94
|
+
# The third calls `to_s`. This would happen if undeclared properties are found on the object. We could add them to the string map
|
|
95
|
+
# but that would result in unchecked, un-GCed memory consumption. In the event that someone is adding properties dynamically,
|
|
96
|
+
# maybe through user input, this would be bad.
|
|
97
|
+
def string_key(k)
|
|
98
|
+
attributes_string_map[k] || string_map_id_property(k) || k.to_s
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def unregister(name)
|
|
102
|
+
# might need to be include?(name.to_s)
|
|
103
|
+
fail ArgumentError, "Argument `#{name}` not an attribute" if not registered_properties[name]
|
|
104
|
+
registered_properties.delete(name)
|
|
105
|
+
unregister_magic_typecaster(name)
|
|
106
|
+
unregister_property_default(name)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def serialize(name, coder = JSON)
|
|
110
|
+
@serialize ||= {}
|
|
111
|
+
@serialize[name] = coder
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def serialized_properties=(serialize_hash)
|
|
115
|
+
@serialized_property_keys = nil
|
|
116
|
+
@serialize = serialize_hash.clone
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def serialized_properties
|
|
120
|
+
@serialize ||= {}
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def serialized_properties_keys
|
|
124
|
+
@serialized_property_keys ||= serialized_properties.keys
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def magic_typecast_properties_keys
|
|
128
|
+
@magic_typecast_properties_keys ||= magic_typecast_properties.keys
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def magic_typecast_properties
|
|
132
|
+
@magic_typecast_properties ||= {}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
EXCLUDED_TYPES = [Array, Range, Regexp]
|
|
136
|
+
def value_for_where(key, value)
|
|
137
|
+
return value unless prop = registered_properties[key]
|
|
138
|
+
return value_for_db(key, value) if prop.typecaster && prop.typecaster.convert_type == value.class
|
|
139
|
+
|
|
140
|
+
if should_convert_for_where?(key, value)
|
|
141
|
+
value_for_db(key, value)
|
|
142
|
+
else
|
|
143
|
+
value
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def value_for_db(key, value)
|
|
148
|
+
return value unless registered_properties[key]
|
|
149
|
+
convert_property(key, value, :to_db)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def value_for_ruby(key, value)
|
|
153
|
+
return unless registered_properties[key]
|
|
154
|
+
convert_property(key, value, :to_ruby)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def inject_defaults!(object, props)
|
|
158
|
+
declared_property_defaults.each_pair do |k, v|
|
|
159
|
+
props[k.to_sym] = v.respond_to?(:call) ? v.call : v if object.send(k).nil? && props[k.to_sym].nil?
|
|
160
|
+
end
|
|
161
|
+
props
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
protected
|
|
165
|
+
|
|
166
|
+
# Prevents repeated calls to :_attribute_type, which isn't free and never changes.
|
|
167
|
+
def fetch_upstream_primitive(attr)
|
|
168
|
+
registered_properties[attr].type
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
private
|
|
172
|
+
|
|
173
|
+
def should_convert_for_where?(key, value)
|
|
174
|
+
(value.is_a?(Array) && supports_array?(key)) || !EXCLUDED_TYPES.include?(value.class)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# @param [Symbol] key An undeclared property value found in the _persisted_obj.properties hash.
|
|
178
|
+
# Typically, this is a node's id property, which will not be registered as other properties are.
|
|
179
|
+
# In the future, this should probably be reworked a bit. This class should either not know or care
|
|
180
|
+
# about the id property or it should be in charge of it. In the meantime, this improves
|
|
181
|
+
# node load performance.
|
|
182
|
+
def string_map_id_property(key)
|
|
183
|
+
return unless klass.id_property_name == key
|
|
184
|
+
key.to_s.tap do |string_key|
|
|
185
|
+
@_attributes_string_map = attributes_string_map.dup.tap { |h| h[key] = string_key }.freeze
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def unregister_magic_typecaster(property)
|
|
190
|
+
magic_typecast_properties.delete(property)
|
|
191
|
+
@magic_typecast_properties_keys = nil
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def unregister_property_default(property)
|
|
195
|
+
declared_property_defaults.delete(property)
|
|
196
|
+
@_default_property_values = nil
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def register_magic_typecaster(property)
|
|
200
|
+
magic_typecast_properties[property.name] = property.magic_typecaster
|
|
201
|
+
@magic_typecast_properties_keys = nil
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
module ActiveGraph::Shared
|
|
2
|
+
# Contains methods related to the management
|
|
3
|
+
class DeclaredProperty
|
|
4
|
+
include Comparable
|
|
5
|
+
|
|
6
|
+
class IllegalPropertyError < ActiveGraph::Error; end
|
|
7
|
+
include ActiveGraph::Shared::DeclaredProperty::Index
|
|
8
|
+
|
|
9
|
+
ILLEGAL_PROPS = %w(from_node to_node start_node end_node)
|
|
10
|
+
attr_reader :name, :name_string, :name_sym, :options, :magic_typecaster, :type, :typecaster, :default_value
|
|
11
|
+
alias default default_value
|
|
12
|
+
|
|
13
|
+
def initialize(name, options = {})
|
|
14
|
+
fail IllegalPropertyError, "#{name} is an illegal property" if ILLEGAL_PROPS.include?(name.to_s)
|
|
15
|
+
fail TypeError, "can't convert #{name.class} into Symbol" unless name.respond_to?(:to_sym)
|
|
16
|
+
@name = @name_sym = name.to_sym
|
|
17
|
+
@name_string = name.to_s
|
|
18
|
+
@type = options[:type]
|
|
19
|
+
@typecaster = options[:typecaster]
|
|
20
|
+
@default_value = options[:default]
|
|
21
|
+
@options = options
|
|
22
|
+
fail_invalid_options!
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Compare attribute definitions
|
|
26
|
+
#
|
|
27
|
+
# @example
|
|
28
|
+
# attribute_definition <=> other
|
|
29
|
+
#
|
|
30
|
+
# @param [ActiveGraph::Shared::DeclaredProperty, Object] other The other
|
|
31
|
+
# attribute definition to compare with.
|
|
32
|
+
#
|
|
33
|
+
# @return [-1, 0, 1, nil]
|
|
34
|
+
def <=>(other)
|
|
35
|
+
return nil unless other.instance_of? self.class
|
|
36
|
+
return nil if name == other.name && options != other.options
|
|
37
|
+
self.to_s <=> other.to_s
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def inspect
|
|
41
|
+
options_description = options.map { |key, value| "#{key.inspect} => #{value.inspect}" }.sort.join(', ')
|
|
42
|
+
inspected_options = ", #{options_description}" unless options_description.empty?
|
|
43
|
+
"attribute :#{name}#{inspected_options}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_s
|
|
47
|
+
name.to_s
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def to_sym
|
|
51
|
+
name
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def [](key)
|
|
55
|
+
respond_to?(key) ? public_send(key) : nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def register
|
|
59
|
+
register_magic_properties
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def fail_invalid_options!
|
|
63
|
+
case
|
|
64
|
+
when index?(:exact) && constraint?(:unique)
|
|
65
|
+
fail ActiveGraph::InvalidPropertyOptionsError,
|
|
66
|
+
"#Uniqueness constraints also provide exact indexes, cannot set both options on property #{name}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def option_with_value!(key, value)
|
|
73
|
+
options[key] = value
|
|
74
|
+
fail_invalid_options!
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def option_with_value?(key, value)
|
|
78
|
+
options[key] == value
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Tweaks properties
|
|
82
|
+
def register_magic_properties
|
|
83
|
+
@type ||= ActiveGraph::Config.timestamp_type if timestamp_prop?
|
|
84
|
+
|
|
85
|
+
register_magic_typecaster
|
|
86
|
+
register_type_converter
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def timestamp_prop?
|
|
90
|
+
name.to_sym == :created_at || name.to_sym == :updated_at
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def register_magic_typecaster
|
|
94
|
+
found_typecaster = ActiveGraph::Shared::TypeConverters.typecaster_for(type)
|
|
95
|
+
return unless found_typecaster && found_typecaster.respond_to?(:primitive_type)
|
|
96
|
+
@typecaster = found_typecaster
|
|
97
|
+
@magic_typecaster = type
|
|
98
|
+
@type = found_typecaster.primitive_type
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def register_type_converter
|
|
102
|
+
converter = options[:serializer]
|
|
103
|
+
return unless converter
|
|
104
|
+
@type = converter.convert_type
|
|
105
|
+
@typecaster = ActiveGraph::Shared::TypeConverters::ObjectConverter
|
|
106
|
+
ActiveGraph::Shared::TypeConverters.register_converter(converter)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module ActiveGraph::Shared
|
|
2
|
+
class DeclaredProperty
|
|
3
|
+
# None of these methods interact with the database. They only keep track of property settings in models.
|
|
4
|
+
# It could (should?) handle the actual indexing/constraining, but that's TBD.
|
|
5
|
+
module Index
|
|
6
|
+
def index_or_constraint?
|
|
7
|
+
index?(:exact) || constraint?(:unique)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def index?(type = :exact)
|
|
11
|
+
options.key?(:index) && options[:index] == type
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def constraint?(type = :unique)
|
|
15
|
+
options.key?(:constraint) && options[:constraint] == type
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def index!(type = :exact)
|
|
19
|
+
fail ActiveGraph::InvalidPropertyOptionsError, "Can't set index on constrainted property #{name} (constraints get indexes automatically)" if constraint?(:unique)
|
|
20
|
+
options[:index] = type
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def constraint!(type = :unique)
|
|
24
|
+
fail ActiveGraph::InvalidPropertyOptionsError, "Can't set constraint on indexed property #{name} (constraints get indexes automatically)" if index?(:exact)
|
|
25
|
+
options[:constraint] = type
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def unindex!(type = :exact)
|
|
29
|
+
options.delete(:index) if index?(type)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def unconstraint!(type = :unique)
|
|
33
|
+
options.delete(:constraint) if constraint?(type)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
module ActiveGraph::Shared
|
|
2
|
+
module Enum
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
class ConflictingEnumMethodError < ActiveGraph::Error; end
|
|
6
|
+
class InvalidEnumValueError < ActiveGraph::InvalidParameterError; end
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
attr_reader :neo4j_enum_data
|
|
10
|
+
|
|
11
|
+
# Similar to ActiveRecord enum, maps an integer value on the database to
|
|
12
|
+
# a set of enum keys.
|
|
13
|
+
#
|
|
14
|
+
# @example Base example
|
|
15
|
+
# class Media
|
|
16
|
+
# include ActiveGraph::Node
|
|
17
|
+
# enum type: [:image, :video, :unknown]
|
|
18
|
+
# end
|
|
19
|
+
# Media.types # => { :images => 0, :video => 1, :unknown => 2 }
|
|
20
|
+
#
|
|
21
|
+
# media.video!
|
|
22
|
+
# media.image? # => false
|
|
23
|
+
# media.type # => :video
|
|
24
|
+
#
|
|
25
|
+
# Media.videos # => All medias with type = 1 (:video)
|
|
26
|
+
# Media.where(type: :video) # => All medias with type = 1 (:video)
|
|
27
|
+
#
|
|
28
|
+
# @example Prefix-ing an enum
|
|
29
|
+
# Media.enum type: [:image, :video, :unknown], _prefix: :enum
|
|
30
|
+
#
|
|
31
|
+
# media.enum_video!
|
|
32
|
+
# media.enum_video? # => true
|
|
33
|
+
#
|
|
34
|
+
# @example Suffix-ing an enum
|
|
35
|
+
# Media.enum type: [:image, :video, :unknown], _suffix: true
|
|
36
|
+
#
|
|
37
|
+
# media.video_type!
|
|
38
|
+
# media.video_type? # => true
|
|
39
|
+
#
|
|
40
|
+
# @example Define a custom mapping for keys-numbers
|
|
41
|
+
# Media.enum type: { image: 1, video: 2, unknown: 3 }
|
|
42
|
+
#
|
|
43
|
+
# @see http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html
|
|
44
|
+
def enum(parameters = {})
|
|
45
|
+
options, parameters = *split_options_and_parameters(parameters)
|
|
46
|
+
parameters.each do |property_name, enum_keys|
|
|
47
|
+
enum_keys = normalize_key_list enum_keys, options
|
|
48
|
+
@neo4j_enum_data ||= {}
|
|
49
|
+
@neo4j_enum_data[property_name] = enum_keys
|
|
50
|
+
define_property(property_name, enum_keys, options)
|
|
51
|
+
define_enum_methods(property_name, enum_keys, options)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
protected
|
|
56
|
+
|
|
57
|
+
def normalize_key_list(enum_keys, options)
|
|
58
|
+
case_sensitive = options.fetch(:_case_sensitive, ActiveGraph::Config.enums_case_sensitive)
|
|
59
|
+
|
|
60
|
+
case enum_keys
|
|
61
|
+
when Hash
|
|
62
|
+
enum_keys
|
|
63
|
+
when Array
|
|
64
|
+
enum_keys = Hash[enum_keys.each_with_index.to_a]
|
|
65
|
+
else
|
|
66
|
+
fail ArgumentError, 'Invalid parameter for enum. Please provide an Array or an Hash.'
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
unless case_sensitive
|
|
70
|
+
enum_keys.each_key do |key|
|
|
71
|
+
fail ArgumentError, 'Enum keys must be lowercase unless _case_sensitive = true' unless key.downcase == key
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
enum_keys
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
VALID_OPTIONS_FOR_ENUMS = [:_index, :_prefix, :_suffix, :_default, :_case_sensitive]
|
|
79
|
+
|
|
80
|
+
def split_options_and_parameters(parameters)
|
|
81
|
+
options = {}
|
|
82
|
+
new_parameters = {}
|
|
83
|
+
parameters.each do |k, v|
|
|
84
|
+
if VALID_OPTIONS_FOR_ENUMS.include? k
|
|
85
|
+
options[k] = v
|
|
86
|
+
else
|
|
87
|
+
new_parameters[k] = v
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
[options, new_parameters]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def define_property(property_name, enum_keys, options)
|
|
94
|
+
property property_name, build_property_options(enum_keys, options)
|
|
95
|
+
serialize property_name, ActiveGraph::Shared::TypeConverters::EnumConverter.new(enum_keys, build_enum_options(enum_keys, options))
|
|
96
|
+
|
|
97
|
+
# If class has already been inherited, make sure subclasses fully inherit enum
|
|
98
|
+
subclasses.each do |klass|
|
|
99
|
+
klass.serialized_properties = self.serialized_properties
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def build_property_options(_enum_keys, options = {})
|
|
104
|
+
{
|
|
105
|
+
default: options[:_default]
|
|
106
|
+
}
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def build_enum_options(enum_keys, options = {})
|
|
110
|
+
if options[:_default] && not(enum_keys.include?(options[:_default]))
|
|
111
|
+
fail ArgumentError, 'Enum default value must match an enum key'
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
build_property_options(enum_keys, options).tap do |enum_options|
|
|
115
|
+
enum_options[:case_sensitive] = options[:_case_sensitive]
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def define_enum_methods(property_name, enum_keys, options)
|
|
120
|
+
define_enum_methods_?(property_name, enum_keys, options)
|
|
121
|
+
define_enum_methods_!(property_name, enum_keys, options)
|
|
122
|
+
define_class_methods(property_name, enum_keys)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def define_class_methods(property_name, enum_keys)
|
|
126
|
+
plural_property_name = property_name.to_s.pluralize.to_sym
|
|
127
|
+
define_singleton_method(plural_property_name) do
|
|
128
|
+
enum_keys
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def define_enum_methods_?(property_name, enum_keys, options)
|
|
133
|
+
enum_keys.each_key do |enum_value|
|
|
134
|
+
method_name = build_method_name(enum_value, property_name, options)
|
|
135
|
+
check_enum_method_conflicts! property_name, :"#{method_name}?"
|
|
136
|
+
define_method("#{method_name}?") do
|
|
137
|
+
__send__(property_name).to_s.to_sym == enum_value
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def define_enum_methods_!(property_name, enum_keys, options)
|
|
143
|
+
enum_keys.each_key do |enum_value|
|
|
144
|
+
method_name = build_method_name(enum_value, property_name, options)
|
|
145
|
+
check_enum_method_conflicts! property_name, :"#{method_name}!"
|
|
146
|
+
define_method("#{method_name}!") do
|
|
147
|
+
__send__("#{property_name}=", enum_value)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def build_method_name(base_name, property_name, options)
|
|
153
|
+
method_name = base_name
|
|
154
|
+
method_name = "#{method_name}_#{property_name}" if options[:_suffix]
|
|
155
|
+
method_name = "#{options[:_prefix]}_#{method_name}" if options[:_prefix]
|
|
156
|
+
method_name
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def check_enum_method_conflicts!(property_name, method_name)
|
|
160
|
+
fail ConflictingEnumMethodError,
|
|
161
|
+
"The enum `#{property_name}` is trying to define a `#{method_name}` method, "\
|
|
162
|
+
'that is already defined. Try to use options `:prefix` or `:suffix` '\
|
|
163
|
+
'to avoid conflicts.' if instance_methods(false).include?(method_name)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|