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.
Files changed (144) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2016 -0
  3. data/CONTRIBUTORS +12 -0
  4. data/Gemfile +24 -0
  5. data/README.md +111 -0
  6. data/activegraph.gemspec +52 -0
  7. data/bin/rake +17 -0
  8. data/config/locales/en.yml +5 -0
  9. data/config/neo4j/add_classnames.yml +1 -0
  10. data/config/neo4j/config.yml +35 -0
  11. data/lib/active_graph.rb +123 -0
  12. data/lib/active_graph/ansi.rb +14 -0
  13. data/lib/active_graph/attribute_set.rb +32 -0
  14. data/lib/active_graph/base.rb +77 -0
  15. data/lib/active_graph/class_arguments.rb +39 -0
  16. data/lib/active_graph/config.rb +135 -0
  17. data/lib/active_graph/core.rb +14 -0
  18. data/lib/active_graph/core/connection_failed_error.rb +6 -0
  19. data/lib/active_graph/core/cypher_error.rb +37 -0
  20. data/lib/active_graph/core/entity.rb +11 -0
  21. data/lib/active_graph/core/instrumentable.rb +37 -0
  22. data/lib/active_graph/core/label.rb +135 -0
  23. data/lib/active_graph/core/logging.rb +44 -0
  24. data/lib/active_graph/core/node.rb +15 -0
  25. data/lib/active_graph/core/querable.rb +41 -0
  26. data/lib/active_graph/core/query.rb +485 -0
  27. data/lib/active_graph/core/query_builder.rb +18 -0
  28. data/lib/active_graph/core/query_clauses.rb +727 -0
  29. data/lib/active_graph/core/query_ext.rb +24 -0
  30. data/lib/active_graph/core/query_find_in_batches.rb +46 -0
  31. data/lib/active_graph/core/record.rb +51 -0
  32. data/lib/active_graph/core/result.rb +31 -0
  33. data/lib/active_graph/core/schema.rb +65 -0
  34. data/lib/active_graph/core/schema_errors.rb +12 -0
  35. data/lib/active_graph/core/wrappable.rb +30 -0
  36. data/lib/active_graph/errors.rb +59 -0
  37. data/lib/active_graph/lazy_attribute_hash.rb +38 -0
  38. data/lib/active_graph/migration.rb +148 -0
  39. data/lib/active_graph/migrations.rb +27 -0
  40. data/lib/active_graph/migrations/base.rb +77 -0
  41. data/lib/active_graph/migrations/check_pending.rb +20 -0
  42. data/lib/active_graph/migrations/helpers.rb +105 -0
  43. data/lib/active_graph/migrations/helpers/id_property.rb +72 -0
  44. data/lib/active_graph/migrations/helpers/relationships.rb +66 -0
  45. data/lib/active_graph/migrations/helpers/schema.rb +63 -0
  46. data/lib/active_graph/migrations/migration_file.rb +24 -0
  47. data/lib/active_graph/migrations/runner.rb +195 -0
  48. data/lib/active_graph/migrations/schema.rb +64 -0
  49. data/lib/active_graph/migrations/schema_migration.rb +14 -0
  50. data/lib/active_graph/model_schema.rb +139 -0
  51. data/lib/active_graph/node.rb +110 -0
  52. data/lib/active_graph/node/callbacks.rb +8 -0
  53. data/lib/active_graph/node/dependent.rb +11 -0
  54. data/lib/active_graph/node/dependent/association_methods.rb +49 -0
  55. data/lib/active_graph/node/dependent/query_proxy_methods.rb +52 -0
  56. data/lib/active_graph/node/dependent_callbacks.rb +31 -0
  57. data/lib/active_graph/node/enum.rb +26 -0
  58. data/lib/active_graph/node/has_n.rb +602 -0
  59. data/lib/active_graph/node/has_n/association.rb +278 -0
  60. data/lib/active_graph/node/has_n/association/rel_factory.rb +61 -0
  61. data/lib/active_graph/node/has_n/association/rel_wrapper.rb +23 -0
  62. data/lib/active_graph/node/has_n/association_cypher_methods.rb +108 -0
  63. data/lib/active_graph/node/id_property.rb +224 -0
  64. data/lib/active_graph/node/id_property/accessor.rb +62 -0
  65. data/lib/active_graph/node/initialize.rb +21 -0
  66. data/lib/active_graph/node/labels.rb +207 -0
  67. data/lib/active_graph/node/labels/index.rb +37 -0
  68. data/lib/active_graph/node/labels/reloading.rb +21 -0
  69. data/lib/active_graph/node/node_list_formatter.rb +13 -0
  70. data/lib/active_graph/node/node_wrapper.rb +54 -0
  71. data/lib/active_graph/node/orm_adapter.rb +82 -0
  72. data/lib/active_graph/node/persistence.rb +186 -0
  73. data/lib/active_graph/node/property.rb +60 -0
  74. data/lib/active_graph/node/query.rb +76 -0
  75. data/lib/active_graph/node/query/query_proxy.rb +367 -0
  76. data/lib/active_graph/node/query/query_proxy_eager_loading.rb +177 -0
  77. data/lib/active_graph/node/query/query_proxy_eager_loading/association_tree.rb +75 -0
  78. data/lib/active_graph/node/query/query_proxy_enumerable.rb +110 -0
  79. data/lib/active_graph/node/query/query_proxy_find_in_batches.rb +19 -0
  80. data/lib/active_graph/node/query/query_proxy_link.rb +139 -0
  81. data/lib/active_graph/node/query/query_proxy_methods.rb +303 -0
  82. data/lib/active_graph/node/query/query_proxy_methods_of_mass_updating.rb +99 -0
  83. data/lib/active_graph/node/query_methods.rb +68 -0
  84. data/lib/active_graph/node/reflection.rb +86 -0
  85. data/lib/active_graph/node/rels.rb +11 -0
  86. data/lib/active_graph/node/scope.rb +166 -0
  87. data/lib/active_graph/node/unpersisted.rb +48 -0
  88. data/lib/active_graph/node/validations.rb +59 -0
  89. data/lib/active_graph/paginated.rb +27 -0
  90. data/lib/active_graph/railtie.rb +108 -0
  91. data/lib/active_graph/relationship.rb +68 -0
  92. data/lib/active_graph/relationship/callbacks.rb +21 -0
  93. data/lib/active_graph/relationship/initialize.rb +28 -0
  94. data/lib/active_graph/relationship/persistence.rb +133 -0
  95. data/lib/active_graph/relationship/persistence/query_factory.rb +95 -0
  96. data/lib/active_graph/relationship/property.rb +92 -0
  97. data/lib/active_graph/relationship/query.rb +99 -0
  98. data/lib/active_graph/relationship/rel_wrapper.rb +31 -0
  99. data/lib/active_graph/relationship/related_node.rb +87 -0
  100. data/lib/active_graph/relationship/types.rb +80 -0
  101. data/lib/active_graph/relationship/validations.rb +8 -0
  102. data/lib/active_graph/schema/operation.rb +102 -0
  103. data/lib/active_graph/shared.rb +48 -0
  104. data/lib/active_graph/shared/attributes.rb +217 -0
  105. data/lib/active_graph/shared/callbacks.rb +66 -0
  106. data/lib/active_graph/shared/cypher.rb +37 -0
  107. data/lib/active_graph/shared/declared_properties.rb +204 -0
  108. data/lib/active_graph/shared/declared_property.rb +109 -0
  109. data/lib/active_graph/shared/declared_property/index.rb +37 -0
  110. data/lib/active_graph/shared/enum.rb +167 -0
  111. data/lib/active_graph/shared/filtered_hash.rb +79 -0
  112. data/lib/active_graph/shared/identity.rb +34 -0
  113. data/lib/active_graph/shared/initialize.rb +65 -0
  114. data/lib/active_graph/shared/marshal.rb +23 -0
  115. data/lib/active_graph/shared/mass_assignment.rb +63 -0
  116. data/lib/active_graph/shared/permitted_attributes.rb +28 -0
  117. data/lib/active_graph/shared/persistence.rb +272 -0
  118. data/lib/active_graph/shared/property.rb +249 -0
  119. data/lib/active_graph/shared/query_factory.rb +122 -0
  120. data/lib/active_graph/shared/rel_type_converters.rb +43 -0
  121. data/lib/active_graph/shared/serialized_properties.rb +30 -0
  122. data/lib/active_graph/shared/type_converters.rb +439 -0
  123. data/lib/active_graph/shared/typecasted_attributes.rb +99 -0
  124. data/lib/active_graph/shared/typecaster.rb +53 -0
  125. data/lib/active_graph/shared/validations.rb +44 -0
  126. data/lib/active_graph/tasks/migration.rake +204 -0
  127. data/lib/active_graph/timestamps.rb +11 -0
  128. data/lib/active_graph/timestamps/created.rb +9 -0
  129. data/lib/active_graph/timestamps/updated.rb +9 -0
  130. data/lib/active_graph/transaction.rb +22 -0
  131. data/lib/active_graph/transactions.rb +57 -0
  132. data/lib/active_graph/type_converters.rb +7 -0
  133. data/lib/active_graph/undeclared_properties.rb +53 -0
  134. data/lib/active_graph/version.rb +3 -0
  135. data/lib/active_graph/wrapper.rb +4 -0
  136. data/lib/rails/generators/active_graph/migration/migration_generator.rb +16 -0
  137. data/lib/rails/generators/active_graph/migration/templates/migration.erb +9 -0
  138. data/lib/rails/generators/active_graph/model/model_generator.rb +89 -0
  139. data/lib/rails/generators/active_graph/model/templates/migration.erb +11 -0
  140. data/lib/rails/generators/active_graph/model/templates/model.erb +15 -0
  141. data/lib/rails/generators/active_graph/upgrade_v8/templates/migration.erb +17 -0
  142. data/lib/rails/generators/active_graph/upgrade_v8/upgrade_v8_generator.rb +34 -0
  143. data/lib/rails/generators/active_graph_generator.rb +121 -0
  144. 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