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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2016 -0
- data/CONTRIBUTORS +12 -0
- data/Gemfile +24 -0
- data/README.md +111 -0
- data/activegraph.gemspec +52 -0
- data/bin/rake +17 -0
- data/config/locales/en.yml +5 -0
- data/config/neo4j/add_classnames.yml +1 -0
- data/config/neo4j/config.yml +35 -0
- data/lib/active_graph.rb +123 -0
- data/lib/active_graph/ansi.rb +14 -0
- data/lib/active_graph/attribute_set.rb +32 -0
- data/lib/active_graph/base.rb +77 -0
- data/lib/active_graph/class_arguments.rb +39 -0
- data/lib/active_graph/config.rb +135 -0
- data/lib/active_graph/core.rb +14 -0
- data/lib/active_graph/core/connection_failed_error.rb +6 -0
- data/lib/active_graph/core/cypher_error.rb +37 -0
- data/lib/active_graph/core/entity.rb +11 -0
- data/lib/active_graph/core/instrumentable.rb +37 -0
- data/lib/active_graph/core/label.rb +135 -0
- data/lib/active_graph/core/logging.rb +44 -0
- data/lib/active_graph/core/node.rb +15 -0
- data/lib/active_graph/core/querable.rb +41 -0
- data/lib/active_graph/core/query.rb +485 -0
- data/lib/active_graph/core/query_builder.rb +18 -0
- data/lib/active_graph/core/query_clauses.rb +727 -0
- data/lib/active_graph/core/query_ext.rb +24 -0
- data/lib/active_graph/core/query_find_in_batches.rb +46 -0
- data/lib/active_graph/core/record.rb +51 -0
- data/lib/active_graph/core/result.rb +31 -0
- data/lib/active_graph/core/schema.rb +65 -0
- data/lib/active_graph/core/schema_errors.rb +12 -0
- data/lib/active_graph/core/wrappable.rb +30 -0
- data/lib/active_graph/errors.rb +59 -0
- data/lib/active_graph/lazy_attribute_hash.rb +38 -0
- data/lib/active_graph/migration.rb +148 -0
- data/lib/active_graph/migrations.rb +27 -0
- data/lib/active_graph/migrations/base.rb +77 -0
- data/lib/active_graph/migrations/check_pending.rb +20 -0
- data/lib/active_graph/migrations/helpers.rb +105 -0
- data/lib/active_graph/migrations/helpers/id_property.rb +72 -0
- data/lib/active_graph/migrations/helpers/relationships.rb +66 -0
- data/lib/active_graph/migrations/helpers/schema.rb +63 -0
- data/lib/active_graph/migrations/migration_file.rb +24 -0
- data/lib/active_graph/migrations/runner.rb +195 -0
- data/lib/active_graph/migrations/schema.rb +64 -0
- data/lib/active_graph/migrations/schema_migration.rb +14 -0
- data/lib/active_graph/model_schema.rb +139 -0
- data/lib/active_graph/node.rb +110 -0
- data/lib/active_graph/node/callbacks.rb +8 -0
- data/lib/active_graph/node/dependent.rb +11 -0
- data/lib/active_graph/node/dependent/association_methods.rb +49 -0
- data/lib/active_graph/node/dependent/query_proxy_methods.rb +52 -0
- data/lib/active_graph/node/dependent_callbacks.rb +31 -0
- data/lib/active_graph/node/enum.rb +26 -0
- data/lib/active_graph/node/has_n.rb +602 -0
- data/lib/active_graph/node/has_n/association.rb +278 -0
- data/lib/active_graph/node/has_n/association/rel_factory.rb +61 -0
- data/lib/active_graph/node/has_n/association/rel_wrapper.rb +23 -0
- data/lib/active_graph/node/has_n/association_cypher_methods.rb +108 -0
- data/lib/active_graph/node/id_property.rb +224 -0
- data/lib/active_graph/node/id_property/accessor.rb +62 -0
- data/lib/active_graph/node/initialize.rb +21 -0
- data/lib/active_graph/node/labels.rb +207 -0
- data/lib/active_graph/node/labels/index.rb +37 -0
- data/lib/active_graph/node/labels/reloading.rb +21 -0
- data/lib/active_graph/node/node_list_formatter.rb +13 -0
- data/lib/active_graph/node/node_wrapper.rb +54 -0
- data/lib/active_graph/node/orm_adapter.rb +82 -0
- data/lib/active_graph/node/persistence.rb +186 -0
- data/lib/active_graph/node/property.rb +60 -0
- data/lib/active_graph/node/query.rb +76 -0
- data/lib/active_graph/node/query/query_proxy.rb +367 -0
- data/lib/active_graph/node/query/query_proxy_eager_loading.rb +177 -0
- data/lib/active_graph/node/query/query_proxy_eager_loading/association_tree.rb +75 -0
- data/lib/active_graph/node/query/query_proxy_enumerable.rb +110 -0
- data/lib/active_graph/node/query/query_proxy_find_in_batches.rb +19 -0
- data/lib/active_graph/node/query/query_proxy_link.rb +139 -0
- data/lib/active_graph/node/query/query_proxy_methods.rb +303 -0
- data/lib/active_graph/node/query/query_proxy_methods_of_mass_updating.rb +99 -0
- data/lib/active_graph/node/query_methods.rb +68 -0
- data/lib/active_graph/node/reflection.rb +86 -0
- data/lib/active_graph/node/rels.rb +11 -0
- data/lib/active_graph/node/scope.rb +166 -0
- data/lib/active_graph/node/unpersisted.rb +48 -0
- data/lib/active_graph/node/validations.rb +59 -0
- data/lib/active_graph/paginated.rb +27 -0
- data/lib/active_graph/railtie.rb +108 -0
- data/lib/active_graph/relationship.rb +68 -0
- data/lib/active_graph/relationship/callbacks.rb +21 -0
- data/lib/active_graph/relationship/initialize.rb +28 -0
- data/lib/active_graph/relationship/persistence.rb +133 -0
- data/lib/active_graph/relationship/persistence/query_factory.rb +95 -0
- data/lib/active_graph/relationship/property.rb +92 -0
- data/lib/active_graph/relationship/query.rb +99 -0
- data/lib/active_graph/relationship/rel_wrapper.rb +31 -0
- data/lib/active_graph/relationship/related_node.rb +87 -0
- data/lib/active_graph/relationship/types.rb +80 -0
- data/lib/active_graph/relationship/validations.rb +8 -0
- data/lib/active_graph/schema/operation.rb +102 -0
- data/lib/active_graph/shared.rb +48 -0
- data/lib/active_graph/shared/attributes.rb +217 -0
- data/lib/active_graph/shared/callbacks.rb +66 -0
- data/lib/active_graph/shared/cypher.rb +37 -0
- data/lib/active_graph/shared/declared_properties.rb +204 -0
- data/lib/active_graph/shared/declared_property.rb +109 -0
- data/lib/active_graph/shared/declared_property/index.rb +37 -0
- data/lib/active_graph/shared/enum.rb +167 -0
- data/lib/active_graph/shared/filtered_hash.rb +79 -0
- data/lib/active_graph/shared/identity.rb +34 -0
- data/lib/active_graph/shared/initialize.rb +65 -0
- data/lib/active_graph/shared/marshal.rb +23 -0
- data/lib/active_graph/shared/mass_assignment.rb +63 -0
- data/lib/active_graph/shared/permitted_attributes.rb +28 -0
- data/lib/active_graph/shared/persistence.rb +272 -0
- data/lib/active_graph/shared/property.rb +249 -0
- data/lib/active_graph/shared/query_factory.rb +122 -0
- data/lib/active_graph/shared/rel_type_converters.rb +43 -0
- data/lib/active_graph/shared/serialized_properties.rb +30 -0
- data/lib/active_graph/shared/type_converters.rb +439 -0
- data/lib/active_graph/shared/typecasted_attributes.rb +99 -0
- data/lib/active_graph/shared/typecaster.rb +53 -0
- data/lib/active_graph/shared/validations.rb +44 -0
- data/lib/active_graph/tasks/migration.rake +204 -0
- data/lib/active_graph/timestamps.rb +11 -0
- data/lib/active_graph/timestamps/created.rb +9 -0
- data/lib/active_graph/timestamps/updated.rb +9 -0
- data/lib/active_graph/transaction.rb +22 -0
- data/lib/active_graph/transactions.rb +57 -0
- data/lib/active_graph/type_converters.rb +7 -0
- data/lib/active_graph/undeclared_properties.rb +53 -0
- data/lib/active_graph/version.rb +3 -0
- data/lib/active_graph/wrapper.rb +4 -0
- data/lib/rails/generators/active_graph/migration/migration_generator.rb +16 -0
- data/lib/rails/generators/active_graph/migration/templates/migration.erb +9 -0
- data/lib/rails/generators/active_graph/model/model_generator.rb +89 -0
- data/lib/rails/generators/active_graph/model/templates/migration.erb +11 -0
- data/lib/rails/generators/active_graph/model/templates/model.erb +15 -0
- data/lib/rails/generators/active_graph/upgrade_v8/templates/migration.erb +17 -0
- data/lib/rails/generators/active_graph/upgrade_v8/upgrade_v8_generator.rb +34 -0
- data/lib/rails/generators/active_graph_generator.rb +121 -0
- metadata +423 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module ActiveGraph::Shared
|
|
2
|
+
class FilteredHash
|
|
3
|
+
class InvalidHashFilterType < ActiveGraph::Error; end
|
|
4
|
+
VALID_SYMBOL_INSTRUCTIONS = [:all, :none]
|
|
5
|
+
VALID_HASH_INSTRUCTIONS = [:on]
|
|
6
|
+
VALID_INSTRUCTIONS_TYPES = [Hash, Symbol]
|
|
7
|
+
|
|
8
|
+
attr_reader :base, :instructions, :instructions_type
|
|
9
|
+
|
|
10
|
+
def initialize(base, instructions)
|
|
11
|
+
@base = base
|
|
12
|
+
@instructions = instructions
|
|
13
|
+
@instructions_type = instructions.class
|
|
14
|
+
validate_instructions!(instructions)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def filtered_base
|
|
18
|
+
case instructions
|
|
19
|
+
when Symbol
|
|
20
|
+
filtered_base_by_symbol
|
|
21
|
+
when Hash
|
|
22
|
+
filtered_base_by_hash
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def filtered_base_by_symbol
|
|
29
|
+
case instructions
|
|
30
|
+
when :all
|
|
31
|
+
[base, {}]
|
|
32
|
+
when :none
|
|
33
|
+
[{}, base]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def filtered_base_by_hash
|
|
38
|
+
behavior_key = instructions.keys.first
|
|
39
|
+
filter_keys = keys_array(behavior_key)
|
|
40
|
+
[filter(filter_keys, :with), filter(filter_keys, :without)]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def key?(filter_keys, key)
|
|
44
|
+
filter_keys.include?(key)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def filter(filter_keys, key)
|
|
48
|
+
filtering = key == :with
|
|
49
|
+
base.select { |k, _v| key?(filter_keys, k) == filtering }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def keys_array(key)
|
|
53
|
+
instructions[key].is_a?(Array) ? instructions[key] : [instructions[key]]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validate_instructions!(instructions)
|
|
57
|
+
fail InvalidHashFilterType, "Filtering instructions #{instructions} are invalid" unless VALID_INSTRUCTIONS_TYPES.include?(instructions.class)
|
|
58
|
+
clazz = instructions_type.name.downcase
|
|
59
|
+
return if send(:"valid_#{clazz}_instructions?", instructions)
|
|
60
|
+
fail InvalidHashFilterType, "Invalid instructions #{instructions}, valid options for #{clazz}: #{send(:"valid_#{clazz}_instructions")}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def valid_symbol_instructions?(instructions)
|
|
64
|
+
valid_symbol_instructions.include?(instructions)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def valid_hash_instructions?(instructions)
|
|
68
|
+
valid_hash_instructions.include?(instructions.keys.first)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def valid_symbol_instructions
|
|
72
|
+
VALID_SYMBOL_INSTRUCTIONS
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def valid_hash_instructions
|
|
76
|
+
VALID_HASH_INSTRUCTIONS
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module ActiveGraph
|
|
2
|
+
module Shared
|
|
3
|
+
module Identity
|
|
4
|
+
def ==(other)
|
|
5
|
+
other.class == self.class && other.id == id
|
|
6
|
+
end
|
|
7
|
+
alias eql? ==
|
|
8
|
+
|
|
9
|
+
# Returns an Enumerable of all (primary) key attributes
|
|
10
|
+
# or nil if model.persisted? is false
|
|
11
|
+
def to_key
|
|
12
|
+
_persisted_obj ? [id] : nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @return [Integer, nil] the neo4j id of the node if persisted or nil
|
|
16
|
+
def neo_id
|
|
17
|
+
_persisted_obj ? _persisted_obj.id : nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def id
|
|
21
|
+
if self.class.id_property_name
|
|
22
|
+
send(self.class.id_property_name)
|
|
23
|
+
else
|
|
24
|
+
# Relationship
|
|
25
|
+
neo_id
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def hash
|
|
30
|
+
id.hash
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module ActiveGraph::Shared
|
|
2
|
+
module Initialize
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
# Implements the ActiveGraph::Node#wrapper and ActiveGraph::Relationship#wrapper method
|
|
6
|
+
# so that we don't have to care if the node is wrapped or not.
|
|
7
|
+
# @return self
|
|
8
|
+
def wrapper
|
|
9
|
+
self
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def convert_and_assign_attributes(properties)
|
|
15
|
+
@attributes ||= ActiveGraph::AttributeSet.new(self.class.attributes_nil_hash, self.class.attributes.keys)
|
|
16
|
+
stringify_attributes!(@attributes, properties)
|
|
17
|
+
self.default_properties = properties if respond_to?(:default_properties=)
|
|
18
|
+
self.class.declared_properties.convert_properties_to(self, :ruby, @attributes)
|
|
19
|
+
@attributes
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def stringify_attributes!(attr, properties)
|
|
23
|
+
properties.each_pair do |k, v|
|
|
24
|
+
key = self.class.declared_properties.string_key(k)
|
|
25
|
+
attr.write_cast_value(key.freeze, v)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# We should be using #clear_changes_information
|
|
30
|
+
# but right now we don't use `ActiveModel` attributes correctly and so it doesn't work
|
|
31
|
+
# Once we set @attribute correctly from using class ActiveModel::Attribute
|
|
32
|
+
# we will no longer need to explicitly call following method and can safely remove it
|
|
33
|
+
def changed_attributes_clear!
|
|
34
|
+
return if changed_attributes.nil?
|
|
35
|
+
|
|
36
|
+
# with ActiveModel 6.0.0 we have to clear attribute changes with clear_attribute_changes
|
|
37
|
+
clear_attribute_changes(self.attributes.keys)
|
|
38
|
+
|
|
39
|
+
# changed_attributes is frozen starting with ActiveModel 5.2.0
|
|
40
|
+
# Not a good long term solution
|
|
41
|
+
if changed_attributes.frozen?
|
|
42
|
+
@attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
|
|
43
|
+
else
|
|
44
|
+
changed_attributes && changed_attributes.clear
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Once we set @attribute correctly from using class ActiveModel::Attribute
|
|
49
|
+
# we will no longer need to explicitly call following method and can safely remove it
|
|
50
|
+
def changed_attributes_selective_clear!(hash_to_clear)
|
|
51
|
+
# with ActiveModel 6.0.0 we have to clear attribute changes with clear_attribute_change
|
|
52
|
+
hash_to_clear.each_key { |k| clear_attribute_change(k) } if defined?(ActiveModel::ForcedMutationTracker)
|
|
53
|
+
|
|
54
|
+
# changed_attributes is frozen starting with ActiveModel 5.2.0
|
|
55
|
+
# Not a good long term solution
|
|
56
|
+
if changed_attributes.frozen?
|
|
57
|
+
attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new(changed_attributes)
|
|
58
|
+
hash_to_clear.each_key { |k| attributes_changed_by_setter.delete(k) }
|
|
59
|
+
@attributes_changed_by_setter = attributes_changed_by_setter
|
|
60
|
+
else
|
|
61
|
+
hash_to_clear.each_key { |k| changed_attributes.delete(k) }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module ActiveGraph
|
|
2
|
+
module Shared
|
|
3
|
+
module Marshal
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
def marshal_dump
|
|
7
|
+
marshal_instance_variables.map(&method(:instance_variable_get))
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def marshal_load(array)
|
|
11
|
+
marshal_instance_variables.zip(array).each do |var, value|
|
|
12
|
+
instance_variable_set(var, value)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def marshal_instance_variables
|
|
19
|
+
self.class::MARSHAL_INSTANCE_VARIABLES
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module ActiveGraph::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 ActiveGraph::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] new_attributes Attributes used to
|
|
23
|
+
# populate the model
|
|
24
|
+
def assign_attributes(new_attributes = nil)
|
|
25
|
+
return unless new_attributes.present?
|
|
26
|
+
new_attributes.each do |name, value|
|
|
27
|
+
writer = :"#{name}="
|
|
28
|
+
if respond_to?(writer)
|
|
29
|
+
send(writer, value)
|
|
30
|
+
else
|
|
31
|
+
add_undeclared_property(name, value)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def add_undeclared_property(_, _); end
|
|
37
|
+
|
|
38
|
+
# Mass update a model's attributes
|
|
39
|
+
#
|
|
40
|
+
# @example Assigning a hash
|
|
41
|
+
# person.attributes = { :first_name => "Chris", :last_name => "Griego" }
|
|
42
|
+
# person.first_name #=> "Chris"
|
|
43
|
+
# person.last_name #=> "Griego"
|
|
44
|
+
#
|
|
45
|
+
# @param (see #assign_attributes)
|
|
46
|
+
def attributes=(new_attributes)
|
|
47
|
+
assign_attributes(new_attributes)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Initialize a model with a set of attributes
|
|
51
|
+
#
|
|
52
|
+
# @example Initializing with a hash
|
|
53
|
+
# person = Person.new(:first_name => "Chris", :last_name => "Griego")
|
|
54
|
+
# person.first_name #=> "Chris"
|
|
55
|
+
# person.last_name #=> "Griego"
|
|
56
|
+
#
|
|
57
|
+
# @param (see #assign_attributes)
|
|
58
|
+
def initialize(attributes = nil)
|
|
59
|
+
assign_attributes(attributes)
|
|
60
|
+
super()
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module ActiveGraph::Shared
|
|
2
|
+
module PermittedAttributes
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
include ActiveModel::ForbiddenAttributesProtection
|
|
5
|
+
|
|
6
|
+
def process_attributes(attributes)
|
|
7
|
+
attributes = sanitize_input_parameters(attributes)
|
|
8
|
+
super(attributes)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def attributes=(attributes)
|
|
12
|
+
attributes = sanitize_input_parameters(attributes)
|
|
13
|
+
super(attributes)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
protected
|
|
17
|
+
|
|
18
|
+
# Check if an argument is a string or an ActionController::Parameters
|
|
19
|
+
def hash_or_parameter?(args)
|
|
20
|
+
args.is_a?(Hash) || args.respond_to?(:to_unsafe_h)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def sanitize_input_parameters(attributes)
|
|
24
|
+
attributes = sanitize_for_mass_assignment(attributes)
|
|
25
|
+
attributes.respond_to?(:symbolize_keys) ? attributes.symbolize_keys : attributes
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
module ActiveGraph::Shared
|
|
2
|
+
# rubocop:disable Metrics/ModuleLength
|
|
3
|
+
module Persistence
|
|
4
|
+
# rubocop:enable Metrics/ModuleLength
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
# @return [Hash] Given a node's state, will call the appropriate `props_for_{action}` method.
|
|
8
|
+
def props_for_persistence
|
|
9
|
+
_persisted_obj ? props_for_update : props_for_create
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def update_model
|
|
13
|
+
return if skip_update?
|
|
14
|
+
props = props_for_update
|
|
15
|
+
neo4j_query(query_as(:n).set(n: props))
|
|
16
|
+
_persisted_obj.properties.merge!(props)
|
|
17
|
+
changed_attributes_clear!
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def skip_update?
|
|
21
|
+
changed_attributes.blank?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Returns a hash containing:
|
|
25
|
+
# * All properties and values for insertion in the database
|
|
26
|
+
# * A `uuid` (or equivalent) key and value
|
|
27
|
+
# * Timestamps, if the class is set to include them.
|
|
28
|
+
# Note that the UUID is added to the hash but is not set on the node.
|
|
29
|
+
# The timestamps, by comparison, are set on the node prior to addition in this hash.
|
|
30
|
+
# @return [Hash]
|
|
31
|
+
def props_for_create
|
|
32
|
+
inject_timestamps!
|
|
33
|
+
props_with_defaults = inject_defaults!(props)
|
|
34
|
+
converted_props = props_for_db(props_with_defaults)
|
|
35
|
+
return converted_props unless self.class.respond_to?(:default_property_values)
|
|
36
|
+
inject_primary_key!(converted_props)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [Hash] Properties and values, type-converted and timestamped for the database.
|
|
40
|
+
def props_for_update
|
|
41
|
+
update_magic_properties
|
|
42
|
+
changed_props = attributes.select { |k, _| changed_attributes.include?(k) }
|
|
43
|
+
changed_props.symbolize_keys!
|
|
44
|
+
inject_defaults!(changed_props)
|
|
45
|
+
props_for_db(changed_props)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Increments a numeric attribute by a centain amount
|
|
49
|
+
# @param [Symbol, String] attribute name of the attribute to increment
|
|
50
|
+
# @param [Integer, Float] by amount to increment
|
|
51
|
+
def increment(attribute, by = 1)
|
|
52
|
+
self[attribute] ||= 0
|
|
53
|
+
self[attribute] += by
|
|
54
|
+
self
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Convenience method to increment numeric attribute and #save at the same time
|
|
58
|
+
# @param [Symbol, String] attribute name of the attribute to increment
|
|
59
|
+
# @param [Integer, Float] by amount to increment
|
|
60
|
+
def increment!(attribute, by = 1)
|
|
61
|
+
increment(attribute, by).update_attribute(attribute, self[attribute])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Increments concurrently a numeric attribute by a centain amount
|
|
65
|
+
# @param [Symbol, String] _attribute name of the attribute to increment
|
|
66
|
+
# @param [Integer, Float] _by amount to increment
|
|
67
|
+
def concurrent_increment!(_attribute, _by = 1)
|
|
68
|
+
fail 'not_implemented'
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Convenience method to set attribute and #save at the same time
|
|
72
|
+
# @param [Symbol, String] attribute of the attribute to update
|
|
73
|
+
# @param [Object] value to set
|
|
74
|
+
def update_attribute(attribute, value)
|
|
75
|
+
write_attribute(attribute, value)
|
|
76
|
+
self.save
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Convenience method to set attribute and #save! at the same time
|
|
80
|
+
# @param [Symbol, String] attribute of the attribute to update
|
|
81
|
+
# @param [Object] value to set
|
|
82
|
+
def update_attribute!(attribute, value)
|
|
83
|
+
write_attribute(attribute, value)
|
|
84
|
+
self.save!
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def create_or_update
|
|
88
|
+
# since the same model can be created or updated twice from a relationship we have to have this guard
|
|
89
|
+
@_create_or_updating = true
|
|
90
|
+
apply_default_values
|
|
91
|
+
result = _persisted_obj ? update_model : create_model
|
|
92
|
+
|
|
93
|
+
ActiveGraph::Base.transaction(&:rollback) if result == false
|
|
94
|
+
|
|
95
|
+
result != false
|
|
96
|
+
ensure
|
|
97
|
+
@_create_or_updating = nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def apply_default_values
|
|
101
|
+
return if self.class.declared_property_defaults.empty?
|
|
102
|
+
self.class.declared_property_defaults.each_pair do |key, value|
|
|
103
|
+
self.send("#{key}=", value.respond_to?(:call) ? value.call : value) if self.send(key).nil?
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def touch
|
|
108
|
+
fail 'Cannot touch on a new record object' unless persisted?
|
|
109
|
+
update_attribute!(:updated_at, Time.now) if respond_to?(:updated_at=)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Returns +true+ if the record is persisted, i.e. it's not a new record and it was not destroyed
|
|
113
|
+
def persisted?
|
|
114
|
+
!new_record? && !destroyed?
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Returns +true+ if the record hasn't been saved to Neo4j yet.
|
|
118
|
+
def new_record?
|
|
119
|
+
!_persisted_obj
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
alias new? new_record?
|
|
123
|
+
|
|
124
|
+
def destroy
|
|
125
|
+
freeze
|
|
126
|
+
|
|
127
|
+
destroy_query.exec if _persisted_obj
|
|
128
|
+
|
|
129
|
+
@_deleted = true
|
|
130
|
+
|
|
131
|
+
self
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def exist?
|
|
135
|
+
return if !_persisted_obj
|
|
136
|
+
|
|
137
|
+
neo4j_query(query_as(:n).return('ID(n)')).any?
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Returns +true+ if the object was destroyed.
|
|
141
|
+
def destroyed?
|
|
142
|
+
@_deleted
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# @return [Hash] all defined and none nil properties
|
|
146
|
+
def props
|
|
147
|
+
attributes.reject { |_, v| v.nil? }.symbolize_keys
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# @return true if the attributes hash has been frozen
|
|
151
|
+
def frozen?
|
|
152
|
+
@attributes.frozen?
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def freeze
|
|
156
|
+
@attributes.freeze
|
|
157
|
+
self
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def reload
|
|
161
|
+
return self if new_record?
|
|
162
|
+
association_proxy_cache.clear if respond_to?(:association_proxy_cache)
|
|
163
|
+
changed_attributes_clear!
|
|
164
|
+
unless reload_from_database
|
|
165
|
+
@_deleted = true
|
|
166
|
+
freeze
|
|
167
|
+
end
|
|
168
|
+
self
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def reload_from_database
|
|
172
|
+
reloaded = self.class.load_entity(neo_id)
|
|
173
|
+
reloaded ? init_on_reload(reloaded._persisted_obj) : nil
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Updates this resource with all the attributes from the passed-in Hash and requests that the record be saved.
|
|
177
|
+
# If saving fails because the resource is invalid then false will be returned.
|
|
178
|
+
def update(attributes)
|
|
179
|
+
ActiveGraph::Base.transaction do |tx|
|
|
180
|
+
self.attributes = process_attributes(attributes)
|
|
181
|
+
saved = save
|
|
182
|
+
tx.rollback unless saved
|
|
183
|
+
saved
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
alias update_attributes update
|
|
187
|
+
|
|
188
|
+
def update_db_property(field, value)
|
|
189
|
+
update_db_properties(field => value)
|
|
190
|
+
true
|
|
191
|
+
end
|
|
192
|
+
alias update_column update_db_property
|
|
193
|
+
|
|
194
|
+
def update_db_properties(hash)
|
|
195
|
+
fail ::ActiveGraph::Error, 'can not update on a new record object' unless persisted?
|
|
196
|
+
ActiveGraph::Base.transaction do
|
|
197
|
+
db_values = props_for_db(hash)
|
|
198
|
+
neo4j_query(query_as(:n).set(n: db_values))
|
|
199
|
+
db_values.each_pair { |k, v| self.public_send(:"#{k}=", v) }
|
|
200
|
+
_persisted_obj.properties.merge!(db_values)
|
|
201
|
+
changed_attributes_selective_clear!(db_values)
|
|
202
|
+
true
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
alias update_columns update_db_properties
|
|
206
|
+
|
|
207
|
+
# Same as {#update_attributes}, but raises an exception if saving fails.
|
|
208
|
+
def update!(attributes)
|
|
209
|
+
ActiveGraph::Base.transaction do
|
|
210
|
+
self.attributes = process_attributes(attributes)
|
|
211
|
+
save!
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
alias update_attributes! update!
|
|
215
|
+
|
|
216
|
+
def cache_key
|
|
217
|
+
if self.new_record?
|
|
218
|
+
"#{model_cache_key}/new"
|
|
219
|
+
elsif self.respond_to?(:updated_at) && !self.updated_at.blank?
|
|
220
|
+
"#{model_cache_key}/#{neo_id}-#{self.updated_at.utc.to_s(:number)}"
|
|
221
|
+
else
|
|
222
|
+
"#{model_cache_key}/#{neo_id}"
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
protected
|
|
227
|
+
|
|
228
|
+
def increment_by_query!(match_query, attribute, by, element_name = :n)
|
|
229
|
+
new_attribute = match_query.with(element_name)
|
|
230
|
+
.set("#{element_name}.`#{attribute}` = COALESCE(#{element_name}.`#{attribute}`, 0) + $by")
|
|
231
|
+
.params(by: by).limit(1)
|
|
232
|
+
.pluck("#{element_name}.`#{attribute}`").first
|
|
233
|
+
return false unless new_attribute
|
|
234
|
+
self[attribute] = new_attribute
|
|
235
|
+
|
|
236
|
+
if defined? ActiveModel::ForcedMutationTracker
|
|
237
|
+
# with ActiveModel 6.0.0 set_attribute_was is removed
|
|
238
|
+
# so we mark attribute's previous value using attr_will_change method
|
|
239
|
+
clear_attribute_change(attribute)
|
|
240
|
+
else
|
|
241
|
+
set_attribute_was(attribute, new_attribute)
|
|
242
|
+
end
|
|
243
|
+
true
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
private
|
|
247
|
+
|
|
248
|
+
def props_for_db(props_hash)
|
|
249
|
+
self.class.declared_properties.convert_properties_to(self, :db, props_hash)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def model_cache_key
|
|
253
|
+
self.class.model_name.cache_key
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def update_magic_properties
|
|
257
|
+
self.updated_at = DateTime.now if respond_to?(:updated_at=) && (updated_at.nil? || (changed? && !updated_at_changed?))
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def inject_timestamps!
|
|
261
|
+
now = DateTime.now
|
|
262
|
+
self.created_at ||= now if respond_to?(:created_at=)
|
|
263
|
+
self.updated_at ||= now if respond_to?(:updated_at=)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def set_timestamps
|
|
267
|
+
warning = 'This method has been replaced with `inject_timestamps!` and will be removed in a future version'.freeze
|
|
268
|
+
ActiveSupport::Deprecation.warn warning, caller
|
|
269
|
+
inject_timestamps!
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|