neo4j 6.1.12 → 7.0.0.rc.1
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 +4 -4
- data/CHANGELOG.md +20 -33
- data/lib/neo4j.rb +6 -10
- data/lib/neo4j/active_node.rb +1 -0
- data/lib/neo4j/active_node/enum.rb +29 -0
- data/lib/neo4j/active_node/has_n.rb +1 -1
- data/lib/neo4j/active_node/has_n/association.rb +1 -1
- data/lib/neo4j/active_node/labels.rb +16 -8
- data/lib/neo4j/active_node/persistence.rb +14 -6
- data/lib/neo4j/active_node/query/query_proxy.rb +20 -0
- data/lib/neo4j/active_node/query/query_proxy_find_in_batches.rb +1 -1
- data/lib/neo4j/active_node/query/query_proxy_methods.rb +21 -66
- data/lib/neo4j/active_node/query/query_proxy_methods_of_mass_updating.rb +83 -0
- data/lib/neo4j/active_node/query_methods.rb +2 -4
- data/lib/neo4j/active_node/scope.rb +5 -9
- data/lib/neo4j/active_rel.rb +2 -1
- data/lib/neo4j/active_rel/related_node.rb +2 -3
- data/lib/neo4j/errors.rb +19 -3
- data/lib/neo4j/railtie.rb +13 -0
- data/lib/neo4j/shared/attributes.rb +207 -0
- data/lib/neo4j/shared/declared_properties.rb +2 -8
- data/lib/neo4j/shared/declared_property.rb +40 -3
- data/lib/neo4j/shared/enum.rb +148 -0
- data/lib/neo4j/shared/filtered_hash.rb +1 -1
- data/lib/neo4j/shared/mass_assignment.rb +58 -0
- data/lib/neo4j/shared/property.rb +41 -45
- data/lib/neo4j/shared/type_converters.rb +76 -17
- data/lib/neo4j/shared/typecasted_attributes.rb +98 -0
- data/lib/neo4j/version.rb +1 -1
- data/neo4j.gemspec +0 -1
- metadata +10 -19
- data/lib/neo4j/shared/property/default_property.rb +0 -0
@@ -1,7 +1,9 @@
|
|
1
1
|
module Neo4j::Shared
|
2
2
|
# Contains methods related to the management
|
3
3
|
class DeclaredProperty
|
4
|
-
|
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
|
-
|
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] =
|
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::
|
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
|
6
|
-
include
|
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 <
|
13
|
-
class MultiparameterAssignmentError <
|
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 =
|
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
|
-
|
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
|
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(
|
147
|
+
attribute(prop)
|
166
148
|
end
|
167
149
|
end
|
168
150
|
|
169
151
|
# @param [Symbol] name The property name
|
170
|
-
# @param [
|
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,
|
173
|
-
build_property(name, options) do |
|
174
|
-
attributes[
|
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
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
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
|