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