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,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