activegraph 11.0.0.beta.1-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|