neo4j 6.1.12 → 7.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,9 @@
1
1
  module Neo4j::Shared
2
2
  # Contains methods related to the management
3
3
  class DeclaredProperty
4
- class IllegalPropertyError < StandardError; end
4
+ include Comparable
5
+
6
+ class IllegalPropertyError < Neo4j::Error; end
5
7
  include Neo4j::Shared::DeclaredProperty::Index
6
8
 
7
9
  ILLEGAL_PROPS = %w(from_node to_node start_node end_node)
@@ -9,12 +11,46 @@ module Neo4j::Shared
9
11
 
10
12
  def initialize(name, options = {})
11
13
  fail IllegalPropertyError, "#{name} is an illegal property" if ILLEGAL_PROPS.include?(name.to_s)
12
- @name = @name_sym = name
14
+ fail TypeError, "can't convert #{name.class} into Symbol" unless name.respond_to?(:to_sym)
15
+ @name = @name_sym = name.to_sym
13
16
  @name_string = name.to_s
14
17
  @options = options
15
18
  fail_invalid_options!
16
19
  end
17
20
 
21
+ # Compare attribute definitions
22
+ #
23
+ # @example
24
+ # attribute_definition <=> other
25
+ #
26
+ # @param [Neo4j::Shared::DeclaredProperty, Object] other The other
27
+ # attribute definition to compare with.
28
+ #
29
+ # @return [-1, 0, 1, nil]
30
+ def <=>(other)
31
+ return nil unless other.instance_of? self.class
32
+ return nil if name == other.name && options != other.options
33
+ self.to_s <=> other.to_s
34
+ end
35
+
36
+ def inspect
37
+ options_description = options.map { |key, value| "#{key.inspect} => #{value.inspect}" }.sort.join(', ')
38
+ inspected_options = ", #{options_description}" unless options_description.empty?
39
+ "attribute :#{name}#{inspected_options}"
40
+ end
41
+
42
+ def to_s
43
+ name.to_s
44
+ end
45
+
46
+ def to_sym
47
+ name
48
+ end
49
+
50
+ def [](key)
51
+ respond_to?(key) ? public_send(key) : nil
52
+ end
53
+
18
54
  def register
19
55
  register_magic_properties
20
56
  end
@@ -30,6 +66,7 @@ module Neo4j::Shared
30
66
  def default_value
31
67
  options[:default]
32
68
  end
69
+ alias_method :default, :default_value
33
70
 
34
71
  def fail_invalid_options!
35
72
  case
@@ -74,7 +111,7 @@ module Neo4j::Shared
74
111
  converter = options[:serializer]
75
112
  return unless converter
76
113
  options[:type] = converter.convert_type
77
- options[:typecaster] = ActiveAttr::Typecasting::ObjectTypecaster.new
114
+ options[:typecaster] = Neo4j::Shared::TypeConverters::ObjectConverter
78
115
  Neo4j::Shared::TypeConverters.register_converter(converter)
79
116
  end
80
117
  end
@@ -0,0 +1,148 @@
1
+ module Neo4j::Shared
2
+ module Enum
3
+ extend ActiveSupport::Concern
4
+
5
+ class ConflictingEnumMethodError < Neo4j::Error; end
6
+
7
+ module ClassMethods
8
+ attr_reader :neo4j_enum_data
9
+
10
+ # Similar to ActiveRecord enum, maps an integer value on the database to
11
+ # a set of enum keys.
12
+ #
13
+ # @example Base example
14
+ # class Media
15
+ # include Neo4j::ActiveNode
16
+ # enum type: [:image, :video, :unknown]
17
+ # end
18
+ # Media.types # => { :images => 0, :video => 1, :unknown => 2 }
19
+ #
20
+ # media.video!
21
+ # media.image? # => false
22
+ # media.type # => :video
23
+ #
24
+ # Media.videos # => All medias with type = 1 (:video)
25
+ # Media.where(type: :video) # => All medias with type = 1 (:video)
26
+ #
27
+ # @example Prefix-ing an enum
28
+ # Media.enum type: [:image, :video, :unknown], _prefix: :enum
29
+ #
30
+ # media.enum_video!
31
+ # media.enum_video? # => true
32
+ #
33
+ # @example Suffix-ing an enum
34
+ # Media.enum type: [:image, :video, :unknown], _suffix: true
35
+ #
36
+ # media.video_type!
37
+ # media.video_type? # => true
38
+ #
39
+ # @example Disable index: :exact for enum elements
40
+ # Media.enum type: [:image, :video, :unknown], _index: false
41
+ #
42
+ # @example Define a custom mapping for keys-numbers
43
+ # Media.enum type: { image: 1, video: 2, unknown: 3 }
44
+ #
45
+ # @see http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html
46
+ def enum(parameters = {})
47
+ options, parameters = *split_options_and_parameters(parameters)
48
+ parameters.each do |property_name, enum_keys|
49
+ enum_keys = normalize_key_list enum_keys
50
+ @neo4j_enum_data ||= {}
51
+ @neo4j_enum_data[property_name] = enum_keys
52
+ define_property(property_name, enum_keys, options)
53
+ define_enum_methods(property_name, enum_keys, options)
54
+ end
55
+ end
56
+
57
+ protected
58
+
59
+ def normalize_key_list(enum_keys)
60
+ case enum_keys
61
+ when Hash
62
+ enum_keys
63
+ when Array
64
+ 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
+ end
69
+
70
+ VALID_OPTIONS_FOR_ENUMS = [:_index, :_prefix, :_suffix]
71
+ DEFAULT_OPTIONS_FOR_ENUMS = {
72
+ _index: true
73
+ }
74
+
75
+ def split_options_and_parameters(parameters)
76
+ options = DEFAULT_OPTIONS_FOR_ENUMS.clone
77
+ new_parameters = {}
78
+ parameters.each do |k, v|
79
+ if VALID_OPTIONS_FOR_ENUMS.include? k
80
+ options[k] = v
81
+ else
82
+ new_parameters[k] = v
83
+ end
84
+ end
85
+ [options, new_parameters]
86
+ end
87
+
88
+ def define_property(property_name, enum_keys, options)
89
+ property_options = build_property_options(enum_keys, options)
90
+ property property_name, property_options
91
+ serialize property_name, Neo4j::Shared::TypeConverters::EnumConverter.new(enum_keys)
92
+ end
93
+
94
+ def build_property_options(enum_keys, _options = {})
95
+ {
96
+ default: enum_keys.keys.first
97
+ }
98
+ end
99
+
100
+ def define_enum_methods(property_name, enum_keys, options)
101
+ define_enum_methods_?(property_name, enum_keys, options)
102
+ define_enum_methods_!(property_name, enum_keys, options)
103
+ define_class_methods(property_name, enum_keys)
104
+ end
105
+
106
+ def define_class_methods(property_name, enum_keys)
107
+ plural_property_name = property_name.to_s.pluralize.to_sym
108
+ define_singleton_method(plural_property_name) do
109
+ enum_keys
110
+ end
111
+ end
112
+
113
+ def define_enum_methods_?(property_name, enum_keys, options)
114
+ enum_keys.keys.each do |enum_value|
115
+ method_name = build_method_name(enum_value, property_name, options)
116
+ check_enum_method_conflicts! property_name, :"#{method_name}?"
117
+ define_method("#{method_name}?") do
118
+ __send__(property_name).to_s.to_sym == enum_value
119
+ end
120
+ end
121
+ end
122
+
123
+ def define_enum_methods_!(property_name, enum_keys, options)
124
+ enum_keys.keys.each do |enum_value|
125
+ method_name = build_method_name(enum_value, property_name, options)
126
+ check_enum_method_conflicts! property_name, :"#{method_name}!"
127
+ define_method("#{method_name}!") do
128
+ __send__("#{property_name}=", enum_value)
129
+ end
130
+ end
131
+ end
132
+
133
+ def build_method_name(base_name, property_name, options)
134
+ method_name = base_name
135
+ method_name = "#{method_name}_#{property_name}" if options[:_suffix]
136
+ method_name = "#{options[:_prefix]}_#{method_name}" if options[:_prefix]
137
+ method_name
138
+ end
139
+
140
+ def check_enum_method_conflicts!(property_name, method_name)
141
+ fail ConflictingEnumMethodError,
142
+ "The enum `#{property_name}` is trying to define a `#{method_name}` method, "\
143
+ 'that is already defined. Try to use options `:prefix` or `:suffix` '\
144
+ 'to avoid conflicts.' if instance_methods(false).include?(method_name)
145
+ end
146
+ end
147
+ end
148
+ end
@@ -1,6 +1,6 @@
1
1
  module Neo4j::Shared
2
2
  class FilteredHash
3
- class InvalidHashFilterType < Neo4j::Neo4jrbError; end
3
+ class InvalidHashFilterType < Neo4j::Error; end
4
4
  VALID_SYMBOL_INSTRUCTIONS = [:all, :none]
5
5
  VALID_HASH_INSTRUCTIONS = [:on]
6
6
  VALID_INSTRUCTIONS_TYPES = [Hash, Symbol]
@@ -0,0 +1,58 @@
1
+ module Neo4j::Shared
2
+ # MassAssignment allows you to bulk set and update attributes
3
+ #
4
+ # Including MassAssignment into your model gives it a set of mass assignment
5
+ # methods, similar to those found in ActiveRecord.
6
+ #
7
+ # @example Usage
8
+ # class Person
9
+ # include Neo4j::Shared::MassAssignment
10
+ # end
11
+ #
12
+ # Originally part of ActiveAttr, https://github.com/cgriego/active_attr
13
+ module MassAssignment
14
+ extend ActiveSupport::Concern
15
+ # Mass update a model's attributes
16
+ #
17
+ # @example Assigning a hash
18
+ # person.assign_attributes(:first_name => "Chris", :last_name => "Griego")
19
+ # person.first_name #=> "Chris"
20
+ # person.last_name #=> "Griego"
21
+ #
22
+ # @param [Hash{#to_s => Object}, #each] attributes Attributes used to
23
+ # populate the model
24
+ # @param [Hash, #[]] options Options that affect mass assignment
25
+ def assign_attributes(new_attributes = nil)
26
+ return unless new_attributes.present?
27
+ new_attributes.each do |name, value|
28
+ writer = :"#{name}="
29
+ send(writer, value) if respond_to?(writer)
30
+ end
31
+ end
32
+
33
+ # Mass update a model's attributes
34
+ #
35
+ # @example Assigning a hash
36
+ # person.attributes = { :first_name => "Chris", :last_name => "Griego" }
37
+ # person.first_name #=> "Chris"
38
+ # person.last_name #=> "Griego"
39
+ #
40
+ # @param (see #assign_attributes)
41
+ def attributes=(new_attributes)
42
+ assign_attributes(new_attributes)
43
+ end
44
+
45
+ # Initialize a model with a set of attributes
46
+ #
47
+ # @example Initializing with a hash
48
+ # person = Person.new(:first_name => "Chris", :last_name => "Griego")
49
+ # person.first_name #=> "Chris"
50
+ # person.last_name #=> "Griego"
51
+ #
52
+ # @param (see #assign_attributes)
53
+ def initialize(attributes = nil)
54
+ assign_attributes(attributes)
55
+ super()
56
+ end
57
+ end
58
+ end
@@ -2,15 +2,12 @@ module Neo4j::Shared
2
2
  module Property
3
3
  extend ActiveSupport::Concern
4
4
 
5
- include ActiveAttr::Attributes
6
- include ActiveAttr::MassAssignment
7
- include ActiveAttr::TypecastedAttributes
8
- include ActiveAttr::AttributeDefaults
9
- include ActiveAttr::QueryAttributes
5
+ include Neo4j::Shared::MassAssignment
6
+ include Neo4j::Shared::TypecastedAttributes
10
7
  include ActiveModel::Dirty
11
8
 
12
- class UndefinedPropertyError < RuntimeError; end
13
- class MultiparameterAssignmentError < StandardError; end
9
+ class UndefinedPropertyError < Neo4j::Error; end
10
+ class MultiparameterAssignmentError < Neo4j::Error; end
14
11
 
15
12
  attr_reader :_persisted_obj
16
13
 
@@ -23,11 +20,9 @@ module Neo4j::Shared
23
20
  "#<#{Neo4j::ANSI::YELLOW}#{self.class.name}#{Neo4j::ANSI::CLEAR}#{separator}#{attribute_descriptions}>"
24
21
  end
25
22
 
26
- # TODO: Remove the commented :super entirely once this code is part of a release.
27
- # It calls an init method in active_attr that has a very negative impact on performance.
28
23
  def initialize(attributes = nil)
29
24
  attributes = process_attributes(attributes)
30
- @relationship_props = {} # self.class.extract_association_attributes!(attributes)
25
+ @relationship_props = self.class.extract_association_attributes!(attributes)
31
26
  modded_attributes = inject_defaults!(attributes)
32
27
  validate_attributes!(modded_attributes)
33
28
  writer_method_props = extract_writer_methods!(modded_attributes)
@@ -40,11 +35,8 @@ module Neo4j::Shared
40
35
  self.class.declared_properties.inject_defaults!(self, starting_props || {})
41
36
  end
42
37
 
43
- # Returning nil when we get ActiveAttr::UnknownAttributeError from ActiveAttr
44
38
  def read_attribute(name)
45
- super(name)
46
- rescue ActiveAttr::UnknownAttributeError
47
- nil
39
+ respond_to?(name) ? send(name) : nil
48
40
  end
49
41
  alias_method :[], :read_attribute
50
42
 
@@ -58,16 +50,6 @@ module Neo4j::Shared
58
50
  convert_and_assign_attributes(properties)
59
51
  end
60
52
 
61
- protected
62
-
63
- # This method is defined in ActiveModel.
64
- # When each node is loaded, it is called once in pursuit of 'sanitize_for_mass_assignment', which this gem does not implement.
65
- # In the course of doing that, it calls :attributes, which is quite expensive, so we return immediately.
66
- def attribute_method?(attr_name) #:nodoc:
67
- return false if attr_name == 'sanitize_for_mass_assignment'
68
- super(attr_name)
69
- end
70
-
71
53
  private
72
54
 
73
55
  # Changes attributes hash to remove relationship keys
@@ -123,7 +105,7 @@ module Neo4j::Shared
123
105
  def instantiate_object(field, values_with_empty_parameters)
124
106
  return nil if values_with_empty_parameters.all?(&:nil?)
125
107
  values = values_with_empty_parameters.collect { |v| v.nil? ? 1 : v }
126
- klass = field[:type]
108
+ klass = field.type
127
109
  klass ? klass.new(*values) : values
128
110
  end
129
111
 
@@ -162,54 +144,61 @@ module Neo4j::Shared
162
144
  # end
163
145
  def property(name, options = {})
164
146
  build_property(name, options) do |prop|
165
- attribute(name, prop.options)
147
+ attribute(prop)
166
148
  end
167
149
  end
168
150
 
169
151
  # @param [Symbol] name The property name
170
- # @param [ActiveAttr::AttributeDefinition] active_attr A cloned AttributeDefinition to reuse
152
+ # @param [Neo4j::Shared::AttributeDefinition] attr_def A cloned AttributeDefinition to reuse
171
153
  # @param [Hash] options An options hash to use in the new property definition
172
- def inherit_property(name, active_attr, options = {})
173
- build_property(name, options) do |prop|
174
- attributes[prop.name.to_s] = active_attr
154
+ def inherit_property(name, attr_def, options = {})
155
+ build_property(name, options) do |prop_name|
156
+ attributes[prop_name] = attr_def
175
157
  end
176
158
  end
177
159
 
178
160
  def build_property(name, options)
179
- prop = DeclaredProperty.new(name, options)
180
- prop.register
181
- declared_properties.register(prop)
182
- yield prop
183
- constraint_or_index(name, options)
161
+ DeclaredProperty.new(name, options).tap do |prop|
162
+ prop.register
163
+ declared_properties.register(prop)
164
+ yield name
165
+ constraint_or_index(name, options)
166
+ end
184
167
  end
185
168
 
186
169
  def undef_property(name)
170
+ undef_constraint_or_index(name)
187
171
  declared_properties.unregister(name)
188
172
  attribute_methods(name).each { |method| undef_method(method) }
189
- undef_constraint_or_index(name)
190
173
  end
191
174
 
192
175
  def declared_properties
193
176
  @_declared_properties ||= DeclaredProperties.new(self)
194
177
  end
195
178
 
196
- def attribute!(name, options = {})
197
- super(name, options)
198
- define_method("#{name}=") do |value|
199
- typecast_value = typecast_attribute(_attribute_typecaster(name), value)
200
- send("#{name}_will_change!") unless typecast_value == read_attribute(name)
201
- super(value)
202
- end
203
- end
204
-
205
179
  # @return [Hash] A frozen hash of all model properties with nil values. It is used during node loading and prevents
206
180
  # an extra call to a slow dependency method.
207
181
  def attributes_nil_hash
208
182
  declared_properties.attributes_nil_hash
209
183
  end
210
184
 
185
+ def extract_association_attributes!(props)
186
+ props
187
+ end
188
+
211
189
  private
212
190
 
191
+ def attribute!(name)
192
+ remove_instance_variable('@attribute_methods_generated') if instance_variable_defined?('@attribute_methods_generated')
193
+ define_attribute_methods([name]) unless attribute_names.include?(name)
194
+ attributes[name.to_s] = declared_properties[name]
195
+ define_method("#{name}=") do |value|
196
+ typecast_value = typecast_attribute(_attribute_typecaster(name), value)
197
+ send("#{name}_will_change!") unless typecast_value == read_attribute(name)
198
+ super(value)
199
+ end
200
+ end
201
+
213
202
  def constraint_or_index(name, options)
214
203
  # either constraint or index, do not set both
215
204
  if options[:constraint]
@@ -220,6 +209,13 @@ module Neo4j::Shared
220
209
  index(name) if options[:index] == :exact
221
210
  end
222
211
  end
212
+
213
+ def undef_constraint_or_index(name)
214
+ prop = declared_properties[name]
215
+ return unless prop.index_or_constraint?
216
+ type = prop.constraint? ? :constraint : :index
217
+ send(:"drop_#{type}", name)
218
+ end
223
219
  end
224
220
  end
225
221
  end