neo4j 5.2.15 → 6.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -19
  3. data/Gemfile +1 -1
  4. data/lib/neo4j.rb +4 -1
  5. data/lib/neo4j/active_node.rb +6 -15
  6. data/lib/neo4j/active_node/has_n.rb +52 -21
  7. data/lib/neo4j/active_node/has_n/association.rb +1 -1
  8. data/lib/neo4j/active_node/id_property.rb +1 -1
  9. data/lib/neo4j/active_node/id_property/accessor.rb +1 -1
  10. data/lib/neo4j/active_node/labels.rb +7 -101
  11. data/lib/neo4j/active_node/labels/index.rb +87 -0
  12. data/lib/neo4j/active_node/persistence.rb +3 -2
  13. data/lib/neo4j/active_node/query.rb +1 -1
  14. data/lib/neo4j/active_node/query/query_proxy.rb +7 -9
  15. data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +0 -1
  16. data/lib/neo4j/active_node/query/query_proxy_link.rb +12 -4
  17. data/lib/neo4j/active_node/query/query_proxy_methods.rb +4 -6
  18. data/lib/neo4j/active_node/query/query_proxy_unpersisted.rb +4 -8
  19. data/lib/neo4j/active_node/unpersisted.rb +12 -10
  20. data/lib/neo4j/active_node/validations.rb +2 -2
  21. data/lib/neo4j/active_rel.rb +7 -4
  22. data/lib/neo4j/active_rel/persistence.rb +13 -4
  23. data/lib/neo4j/active_rel/query.rb +8 -0
  24. data/lib/neo4j/active_rel/related_node.rb +1 -27
  25. data/lib/neo4j/errors.rb +2 -0
  26. data/lib/neo4j/schema/operation.rb +91 -0
  27. data/lib/neo4j/shared.rb +3 -3
  28. data/lib/neo4j/shared/callbacks.rb +2 -7
  29. data/lib/neo4j/shared/{declared_property_manager.rb → declared_properties.rb} +34 -2
  30. data/lib/neo4j/shared/declared_property.rb +19 -0
  31. data/lib/neo4j/shared/declared_property/index.rb +37 -0
  32. data/lib/neo4j/shared/initialize.rb +2 -2
  33. data/lib/neo4j/shared/persistence.rb +3 -25
  34. data/lib/neo4j/shared/property.rb +24 -10
  35. data/lib/neo4j/shared/type_converters.rb +131 -6
  36. data/lib/neo4j/tasks/migration.rake +3 -3
  37. data/lib/neo4j/type_converters.rb +1 -1
  38. data/lib/neo4j/version.rb +1 -1
  39. data/neo4j.gemspec +2 -2
  40. metadata +13 -10
data/lib/neo4j/errors.rb CHANGED
@@ -7,4 +7,6 @@ module Neo4j
7
7
  # Raised when Neo4j.rb cannot find record by given id.
8
8
  class RecordNotFound < Neo4jrbError
9
9
  end
10
+
11
+ class InvalidPropertyOptionsError < Neo4jrbError; end
10
12
  end
@@ -0,0 +1,91 @@
1
+ module Neo4j
2
+ module Schema
3
+ class Operation
4
+ attr_reader :label_name, :property, :options
5
+
6
+ def initialize(label_name, property, options = default_options)
7
+ @label_name = label_name.to_sym
8
+ @property = property.to_sym
9
+ @options = options
10
+ end
11
+
12
+ def self.incompatible_operation_classes
13
+ []
14
+ end
15
+
16
+ def create!
17
+ drop_incompatible!
18
+ return if exist?
19
+ label_object.send(:"create_#{type}", property, options)
20
+ end
21
+
22
+ def label_object
23
+ @label_object ||= Neo4j::Label.create(label_name)
24
+ end
25
+
26
+ def incompatible_operation_classes
27
+ self.class.incompatible_operation_classes
28
+ end
29
+
30
+ def drop!
31
+ label_object.send(:"drop_#{type}", property, options)
32
+ end
33
+
34
+ def drop_incompatible!
35
+ incompatible_operation_classes.each do |clazz|
36
+ operation = clazz.new(label_name, property)
37
+ operation.drop! if operation.exist?
38
+ end
39
+ end
40
+
41
+ def exist?
42
+ fail 'Abstract class, not implemented'
43
+ end
44
+
45
+ def default_options
46
+ {}
47
+ end
48
+
49
+ def type
50
+ fail 'Abstract class, not implemented'
51
+ end
52
+ end
53
+
54
+ class ExactIndexOperation < Neo4j::Schema::Operation
55
+ def self.incompatible_operation_classes
56
+ [UniqueConstraintOperation]
57
+ end
58
+
59
+ def type
60
+ 'index'
61
+ end
62
+
63
+ def exist?
64
+ label_object.indexes[:property_keys].include?([property])
65
+ end
66
+ end
67
+
68
+ class UniqueConstraintOperation < Neo4j::Schema::Operation
69
+ def self.incompatible_operation_classes
70
+ [ExactIndexOperation]
71
+ end
72
+
73
+ def type
74
+ 'constraint'
75
+ end
76
+
77
+ def create!
78
+ return if exist?
79
+ super
80
+ end
81
+
82
+ def exist?
83
+ Neo4j::Label.constraint?(label_name, property)
84
+ end
85
+
86
+ def default_options
87
+ {type: :unique}
88
+ end
89
+ end
90
+ end
91
+ end
data/lib/neo4j/shared.rb CHANGED
@@ -28,15 +28,15 @@ module Neo4j
28
28
 
29
29
  included do
30
30
  self.include_root_in_json = Neo4j::Config.include_root_in_json
31
- @_declared_property_manager ||= Neo4j::Shared::DeclaredPropertyManager.new(self)
31
+ @_declared_properties ||= Neo4j::Shared::DeclaredProperties.new(self)
32
32
 
33
33
  def self.i18n_scope
34
34
  :neo4j
35
35
  end
36
36
  end
37
37
 
38
- def declared_property_manager
39
- self.class.declared_property_manager
38
+ def declared_properties
39
+ self.class.declared_properties
40
40
  end
41
41
  end
42
42
  end
@@ -9,13 +9,8 @@ module Neo4j
9
9
 
10
10
  included do
11
11
  include ActiveModel::Validations::Callbacks
12
- # after_find is triggered by the `find` method defined in lib/neo4j/active_node/id_property.rb
13
12
  define_model_callbacks :initialize, :find, only: :after
14
- define_model_callbacks :save, :create, :update, :destroy, :touch
15
- end
16
-
17
- def initialize(args = nil)
18
- run_callbacks(:initialize) { super }
13
+ define_model_callbacks :save, :create, :update, :destroy
19
14
  end
20
15
 
21
16
  def destroy #:nodoc:
@@ -30,7 +25,7 @@ module Neo4j
30
25
  tx.close if tx
31
26
  end
32
27
 
33
- def touch #:nodoc:
28
+ def touch(*) #:nodoc:
34
29
  run_callbacks(:touch) { super }
35
30
  end
36
31
 
@@ -5,17 +5,26 @@ module Neo4j::Shared
5
5
  # a way of separating behavior from the general Active{obj} modules.
6
6
  #
7
7
  # See Neo4j::Shared::DeclaredProperty for definitions of the property objects themselves.
8
- class DeclaredPropertyManager
8
+ class DeclaredProperties
9
9
  include Neo4j::Shared::TypeConverters
10
10
 
11
11
  attr_reader :klass
12
+ delegate :each, :each_pair, :each_key, :each_value, to: :registered_properties
12
13
 
13
14
  # Each class that includes Neo4j::ActiveNode or Neo4j::ActiveRel gets one instance of this class.
14
- # @param [#declared_property_manager] klass An object that has the #declared_property_manager method.
15
+ # @param [#declared_properties] klass An object that has the #declared_properties method.
15
16
  def initialize(klass)
16
17
  @klass = klass
17
18
  end
18
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
+
19
28
  # @param [Neo4j::Shared::DeclaredProperty] property An instance of DeclaredProperty, created as the result of calling
20
29
  # #property on an ActiveNode or ActiveRel class. The DeclaredProperty has specifics about the property, but registration
21
30
  # makes the management object aware of it. This is necessary for type conversion, defaults, and inclusion in the nil and string hashes.
@@ -27,6 +36,18 @@ module Neo4j::Shared
27
36
  declared_property_defaults[property.name] = property.default_value if !property.default_value.nil?
28
37
  end
29
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
+
30
51
  # The :default option in Neo4j::ActiveNode#property class method allows for setting a default value instead of
31
52
  # nil on declared properties. This holds those values.
32
53
  def declared_property_defaults
@@ -37,6 +58,10 @@ module Neo4j::Shared
37
58
  @_registered_properties ||= {}
38
59
  end
39
60
 
61
+ def indexed_properties
62
+ registered_properties.select { |_, p| p.index_or_constraint? }
63
+ end
64
+
40
65
  # During object wrap, a hash is needed that contains each declared property with a nil value.
41
66
  # The active_attr dependency is capable of providing this but it is expensive and calculated on the fly
42
67
  # each time it is called. Rather than rely on that, we build this progressively as properties are registered.
@@ -113,6 +138,13 @@ module Neo4j::Shared
113
138
  @upstream_primitives ||= {}
114
139
  end
115
140
 
141
+ EXCLUDED_TYPES = [Array, Range, Regexp]
142
+ def value_for_where(key, value)
143
+ return value unless prop = registered_properties[key]
144
+ return value_for_db(key, value) if prop.typecaster && prop.typecaster.convert_type == value.class
145
+ EXCLUDED_TYPES.include?(value.class) ? value : value_for_db(key, value)
146
+ end
147
+
116
148
  def value_for_db(key, value)
117
149
  return value unless registered_properties[key]
118
150
  convert_property(key, value, :to_db)
@@ -2,6 +2,7 @@ module Neo4j::Shared
2
2
  # Contains methods related to the management
3
3
  class DeclaredProperty
4
4
  class IllegalPropertyError < StandardError; end
5
+ include Neo4j::Shared::DeclaredProperty::Index
5
6
 
6
7
  ILLEGAL_PROPS = %w(from_node to_node start_node end_node)
7
8
  attr_reader :name, :name_string, :name_sym, :options, :magic_typecaster
@@ -11,6 +12,7 @@ module Neo4j::Shared
11
12
  @name = @name_sym = name
12
13
  @name_string = name.to_s
13
14
  @options = options
15
+ fail_invalid_options!
14
16
  end
15
17
 
16
18
  def register
@@ -29,8 +31,25 @@ module Neo4j::Shared
29
31
  options[:default]
30
32
  end
31
33
 
34
+ def fail_invalid_options!
35
+ case
36
+ when index?(:exact) && constraint?(:unique)
37
+ fail Neo4j::InvalidPropertyOptionsError,
38
+ "#Uniqueness constraints also provide exact indexes, cannot set both options on property #{name}"
39
+ end
40
+ end
41
+
32
42
  private
33
43
 
44
+ def option_with_value!(key, value)
45
+ options[key] = value
46
+ fail_invalid_options!
47
+ end
48
+
49
+ def option_with_value?(key, value)
50
+ options[key] == value
51
+ end
52
+
34
53
  # Tweaks properties
35
54
  def register_magic_properties
36
55
  options[:type] ||= Neo4j::Config.timestamp_type if timestamp_prop?
@@ -0,0 +1,37 @@
1
+ module Neo4j::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 Neo4j::InvalidPropertyOptionsError, "Unable to set index on constrainted property #{name}" if constraint?(:unique)
20
+ options[:index] = type
21
+ end
22
+
23
+ def constraint!(type = :unique)
24
+ fail Neo4j::InvalidPropertyOptionsError, "Unable to set constraint on indexed property #{name}" 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
@@ -15,12 +15,12 @@ module Neo4j::Shared
15
15
  @attributes ||= Hash[self.class.attributes_nil_hash]
16
16
  stringify_attributes!(@attributes, properties)
17
17
  self.default_properties = properties if respond_to?(:default_properties=)
18
- self.class.declared_property_manager.convert_properties_to(self, :ruby, @attributes)
18
+ self.class.declared_properties.convert_properties_to(self, :ruby, @attributes)
19
19
  end
20
20
 
21
21
  def stringify_attributes!(attr, properties)
22
22
  properties.each_pair do |k, v|
23
- key = self.class.declared_property_manager.string_key(k)
23
+ key = self.class.declared_properties.string_key(k)
24
24
  attr[key.freeze] = v
25
25
  end
26
26
  end
@@ -82,11 +82,6 @@ module Neo4j::Shared
82
82
  end
83
83
  end
84
84
 
85
- def touch
86
- fail 'Cannot touch on a new record object' unless persisted?
87
- update_attribute!(:updated_at, Time.now) if respond_to?(:updated_at=)
88
- end
89
-
90
85
  # Returns +true+ if the record is persisted, i.e. it's not a new record and it was not destroyed
91
86
  def persisted?
92
87
  !new_record? && !destroyed?
@@ -111,26 +106,9 @@ module Neo4j::Shared
111
106
 
112
107
  # Returns +true+ if the object was destroyed.
113
108
  def destroyed?
114
- @_deleted || _destroyed_double_check?
109
+ @_deleted
115
110
  end
116
111
 
117
- # These two methods should be removed in 6.0.0
118
- def _destroyed_double_check?
119
- if _active_record_destroyed_behavior?
120
- false
121
- else
122
- (!new_record? && !exist?)
123
- end
124
- end
125
-
126
- def _active_record_destroyed_behavior?
127
- fail 'Remove this workaround in 6.0.0' if Neo4j::VERSION >= '6.0.0'
128
-
129
- !!Neo4j::Config[:_active_record_destroyed_behavior]
130
- end
131
- # End of two methods which should be removed in 6.0.0
132
-
133
-
134
112
  # @return [Hash] all defined and none nil properties
135
113
  def props
136
114
  attributes.reject { |_, v| v.nil? }.symbolize_keys
@@ -193,7 +171,7 @@ module Neo4j::Shared
193
171
  private
194
172
 
195
173
  def props_for_db(props_hash)
196
- self.class.declared_property_manager.convert_properties_to(self, :db, props_hash)
174
+ self.class.declared_properties.convert_properties_to(self, :db, props_hash)
197
175
  end
198
176
 
199
177
  def model_cache_key
@@ -222,7 +200,7 @@ module Neo4j::Shared
222
200
  end
223
201
 
224
202
  def inject_defaults!(properties)
225
- self.class.declared_property_manager.declared_property_defaults.each_pair do |k, v|
203
+ self.class.declared_properties.declared_property_defaults.each_pair do |k, v|
226
204
  properties[k.to_sym] = v if send(k).nil?
227
205
  end
228
206
  properties
@@ -108,10 +108,10 @@ module Neo4j::Shared
108
108
  module ClassMethods
109
109
  extend Forwardable
110
110
 
111
- def_delegators :declared_property_manager, :serialized_properties, :serialized_properties=, :serialize, :declared_property_defaults
111
+ def_delegators :declared_properties, :serialized_properties, :serialized_properties=, :serialize, :declared_property_defaults
112
112
 
113
113
  def inherited(other)
114
- self.declared_property_manager.registered_properties.each_pair do |prop_key, prop_def|
114
+ self.declared_properties.registered_properties.each_pair do |prop_key, prop_def|
115
115
  other.property(prop_key, prop_def.options)
116
116
  end
117
117
  super
@@ -146,22 +146,36 @@ module Neo4j::Shared
146
146
  # property :name, constraint: :unique
147
147
  # end
148
148
  def property(name, options = {})
149
+ build_property(name, options) do |prop|
150
+ attribute(name, prop.options)
151
+ end
152
+ end
153
+
154
+ # @param [Symbol] name The property name
155
+ # @param [ActiveAttr::AttributeDefinition] active_attr A cloned AttributeDefinition to reuse
156
+ # @param [Hash] options An options hash to use in the new property definition
157
+ def inherit_property(name, active_attr, options = {})
158
+ build_property(name, options) do |prop|
159
+ attributes[prop.name.to_s] = active_attr
160
+ end
161
+ end
162
+
163
+ def build_property(name, options)
149
164
  prop = DeclaredProperty.new(name, options)
150
165
  prop.register
151
- declared_property_manager.register(prop)
152
-
153
- attribute(name, prop.options)
166
+ declared_properties.register(prop)
167
+ yield prop
154
168
  constraint_or_index(name, options)
155
169
  end
156
170
 
157
171
  def undef_property(name)
158
- declared_property_manager.unregister(name)
172
+ declared_properties.unregister(name)
159
173
  attribute_methods(name).each { |method| undef_method(method) }
160
174
  undef_constraint_or_index(name)
161
175
  end
162
176
 
163
- def declared_property_manager
164
- @_declared_property_manager ||= DeclaredPropertyManager.new(self)
177
+ def declared_properties
178
+ @_declared_properties ||= DeclaredProperties.new(self)
165
179
  end
166
180
 
167
181
  def attribute!(name, options = {})
@@ -176,7 +190,7 @@ module Neo4j::Shared
176
190
  # @return [Hash] A frozen hash of all model properties with nil values. It is used during node loading and prevents
177
191
  # an extra call to a slow dependency method.
178
192
  def attributes_nil_hash
179
- declared_property_manager.attributes_nil_hash
193
+ declared_properties.attributes_nil_hash
180
194
  end
181
195
 
182
196
  private
@@ -188,7 +202,7 @@ module Neo4j::Shared
188
202
  constraint(name, type: :unique)
189
203
  elsif options[:index]
190
204
  fail "unknown index type #{options[:index]}, only :exact supported" if options[:index] != :exact
191
- index(name, options) if options[:index] == :exact
205
+ index(name) if options[:index] == :exact
192
206
  end
193
207
  end
194
208
  end
@@ -1,9 +1,128 @@
1
1
  require 'date'
2
+ require 'bigdecimal'
3
+ require 'bigdecimal/util'
4
+ require 'active_support/core_ext/big_decimal/conversions'
2
5
 
3
6
  module Neo4j::Shared
4
7
  module TypeConverters
8
+ class BaseConverter
9
+ class << self
10
+ def converted?(value)
11
+ value.is_a?(db_type)
12
+ end
13
+ end
14
+ end
15
+
16
+ class IntegerConverter < BaseConverter
17
+ class << self
18
+ def convert_type
19
+ Integer
20
+ end
21
+
22
+ def db_type
23
+ Integer
24
+ end
25
+
26
+ def to_db(value)
27
+ value.to_i
28
+ end
29
+
30
+ alias_method :to_ruby, :to_db
31
+ end
32
+ end
33
+
34
+ class FloatConverter < BaseConverter
35
+ class << self
36
+ def convert_type
37
+ Float
38
+ end
39
+
40
+ def db_type
41
+ Float
42
+ end
43
+
44
+ def to_db(value)
45
+ value.to_f
46
+ end
47
+ alias_method :to_ruby, :to_db
48
+ end
49
+ end
50
+
51
+ class BigDecimalConverter < BaseConverter
52
+ class << self
53
+ def convert_type
54
+ BigDecimal
55
+ end
56
+
57
+ def db_type
58
+ BigDecimal
59
+ end
60
+
61
+ def to_db(value)
62
+ case value
63
+ when Rational
64
+ value.to_f.to_d
65
+ when respond_to?(:to_d)
66
+ value.to_d
67
+ else
68
+ BigDecimal.new(value.to_s)
69
+ end
70
+ end
71
+ alias_method :to_ruby, :to_db
72
+ end
73
+ end
74
+
75
+ class StringConverter < BaseConverter
76
+ class << self
77
+ def convert_type
78
+ String
79
+ end
80
+
81
+ def db_type
82
+ String
83
+ end
84
+
85
+ def to_db(value)
86
+ value.to_s
87
+ end
88
+ alias_method :to_ruby, :to_db
89
+ end
90
+ end
91
+
92
+ class BooleanConverter < BaseConverter
93
+ FALSE_VALUES = %w(n N no No NO false False FALSE off Off OFF f F)
94
+
95
+ class << self
96
+ def converted?(value)
97
+ convert_type.include?(value)
98
+ end
99
+
100
+ def convert_type
101
+ [true, false]
102
+ end
103
+
104
+ def db_type
105
+ ActiveAttr::Typecasting::Boolean
106
+ end
107
+
108
+ def to_db(value)
109
+ return false if FALSE_VALUES.include?(value)
110
+ case value
111
+ when TrueClass, FalseClass
112
+ value
113
+ when Numeric, /^\-?[0-9]/
114
+ !value.to_f.zero?
115
+ else
116
+ value.present?
117
+ end
118
+ end
119
+
120
+ alias_method :to_ruby, :to_db
121
+ end
122
+ end
123
+
5
124
  # Converts Date objects to Java long types. Must be timezone UTC.
6
- class DateConverter
125
+ class DateConverter < BaseConverter
7
126
  class << self
8
127
  def convert_type
9
128
  Date
@@ -24,7 +143,7 @@ module Neo4j::Shared
24
143
  end
25
144
 
26
145
  # Converts DateTime objects to and from Java long types. Must be timezone UTC.
27
- class DateTimeConverter
146
+ class DateTimeConverter < BaseConverter
28
147
  class << self
29
148
  def convert_type
30
149
  DateTime
@@ -61,7 +180,7 @@ module Neo4j::Shared
61
180
  end
62
181
  end
63
182
 
64
- class TimeConverter
183
+ class TimeConverter < BaseConverter
65
184
  class << self
66
185
  def convert_type
67
186
  Time
@@ -95,7 +214,7 @@ module Neo4j::Shared
95
214
  end
96
215
 
97
216
  # Converts hash to/from YAML
98
- class YAMLConverter
217
+ class YAMLConverter < BaseConverter
99
218
  class << self
100
219
  def convert_type
101
220
  Hash
@@ -116,7 +235,7 @@ module Neo4j::Shared
116
235
  end
117
236
 
118
237
  # Converts hash to/from JSON
119
- class JSONConverter
238
+ class JSONConverter < BaseConverter
120
239
  class << self
121
240
  def convert_type
122
241
  JSON
@@ -159,6 +278,7 @@ module Neo4j::Shared
159
278
  private
160
279
 
161
280
  def converted_property(type, value, converter)
281
+ return nil if value.nil?
162
282
  TypeConverters.converters[type].nil? ? value : TypeConverters.to_other(converter, value, type)
163
283
  end
164
284
 
@@ -209,7 +329,12 @@ module Neo4j::Shared
209
329
  # @param [#convert_type] found_converter An object that responds to #convert_type, hinting that it is a type converter.
210
330
  # @param value The value for conversion.
211
331
  def formatted_for_db?(found_converter, value)
212
- found_converter.respond_to?(:db_type) && value.is_a?(found_converter.db_type)
332
+ return false unless found_converter.respond_to?(:db_type)
333
+ if found_converter.respond_to?(:converted)
334
+ found_converter.converted?(value)
335
+ else
336
+ value.is_a?(found_converter.db_type)
337
+ end
213
338
  end
214
339
 
215
340
  def register_converter(converter)