activegraph 10.0.0.pre.alpha.6

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 (142) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1989 -0
  3. data/CONTRIBUTORS +12 -0
  4. data/Gemfile +24 -0
  5. data/README.md +107 -0
  6. data/bin/rake +17 -0
  7. data/config/locales/en.yml +5 -0
  8. data/config/neo4j/add_classnames.yml +1 -0
  9. data/config/neo4j/config.yml +38 -0
  10. data/lib/neo4j.rb +116 -0
  11. data/lib/neo4j/active_base.rb +89 -0
  12. data/lib/neo4j/active_node.rb +108 -0
  13. data/lib/neo4j/active_node/callbacks.rb +8 -0
  14. data/lib/neo4j/active_node/dependent.rb +11 -0
  15. data/lib/neo4j/active_node/dependent/association_methods.rb +49 -0
  16. data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +51 -0
  17. data/lib/neo4j/active_node/enum.rb +26 -0
  18. data/lib/neo4j/active_node/has_n.rb +612 -0
  19. data/lib/neo4j/active_node/has_n/association.rb +278 -0
  20. data/lib/neo4j/active_node/has_n/association/rel_factory.rb +61 -0
  21. data/lib/neo4j/active_node/has_n/association/rel_wrapper.rb +23 -0
  22. data/lib/neo4j/active_node/has_n/association_cypher_methods.rb +108 -0
  23. data/lib/neo4j/active_node/id_property.rb +224 -0
  24. data/lib/neo4j/active_node/id_property/accessor.rb +62 -0
  25. data/lib/neo4j/active_node/initialize.rb +21 -0
  26. data/lib/neo4j/active_node/labels.rb +207 -0
  27. data/lib/neo4j/active_node/labels/index.rb +37 -0
  28. data/lib/neo4j/active_node/labels/reloading.rb +21 -0
  29. data/lib/neo4j/active_node/node_list_formatter.rb +13 -0
  30. data/lib/neo4j/active_node/node_wrapper.rb +54 -0
  31. data/lib/neo4j/active_node/orm_adapter.rb +82 -0
  32. data/lib/neo4j/active_node/persistence.rb +187 -0
  33. data/lib/neo4j/active_node/property.rb +60 -0
  34. data/lib/neo4j/active_node/query.rb +76 -0
  35. data/lib/neo4j/active_node/query/query_proxy.rb +374 -0
  36. data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +177 -0
  37. data/lib/neo4j/active_node/query/query_proxy_eager_loading/association_tree.rb +75 -0
  38. data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +110 -0
  39. data/lib/neo4j/active_node/query/query_proxy_find_in_batches.rb +19 -0
  40. data/lib/neo4j/active_node/query/query_proxy_link.rb +139 -0
  41. data/lib/neo4j/active_node/query/query_proxy_methods.rb +302 -0
  42. data/lib/neo4j/active_node/query/query_proxy_methods_of_mass_updating.rb +86 -0
  43. data/lib/neo4j/active_node/query_methods.rb +68 -0
  44. data/lib/neo4j/active_node/reflection.rb +86 -0
  45. data/lib/neo4j/active_node/rels.rb +11 -0
  46. data/lib/neo4j/active_node/scope.rb +166 -0
  47. data/lib/neo4j/active_node/unpersisted.rb +48 -0
  48. data/lib/neo4j/active_node/validations.rb +59 -0
  49. data/lib/neo4j/active_rel.rb +67 -0
  50. data/lib/neo4j/active_rel/callbacks.rb +15 -0
  51. data/lib/neo4j/active_rel/initialize.rb +28 -0
  52. data/lib/neo4j/active_rel/persistence.rb +134 -0
  53. data/lib/neo4j/active_rel/persistence/query_factory.rb +95 -0
  54. data/lib/neo4j/active_rel/property.rb +95 -0
  55. data/lib/neo4j/active_rel/query.rb +101 -0
  56. data/lib/neo4j/active_rel/rel_wrapper.rb +31 -0
  57. data/lib/neo4j/active_rel/related_node.rb +87 -0
  58. data/lib/neo4j/active_rel/types.rb +82 -0
  59. data/lib/neo4j/active_rel/validations.rb +8 -0
  60. data/lib/neo4j/ansi.rb +14 -0
  61. data/lib/neo4j/class_arguments.rb +39 -0
  62. data/lib/neo4j/config.rb +135 -0
  63. data/lib/neo4j/core.rb +14 -0
  64. data/lib/neo4j/core/connection_failed_error.rb +6 -0
  65. data/lib/neo4j/core/cypher_error.rb +37 -0
  66. data/lib/neo4j/core/driver.rb +66 -0
  67. data/lib/neo4j/core/has_uri.rb +63 -0
  68. data/lib/neo4j/core/instrumentable.rb +36 -0
  69. data/lib/neo4j/core/label.rb +158 -0
  70. data/lib/neo4j/core/logging.rb +44 -0
  71. data/lib/neo4j/core/node.rb +23 -0
  72. data/lib/neo4j/core/querable.rb +88 -0
  73. data/lib/neo4j/core/query.rb +487 -0
  74. data/lib/neo4j/core/query_builder.rb +32 -0
  75. data/lib/neo4j/core/query_clauses.rb +727 -0
  76. data/lib/neo4j/core/query_ext.rb +24 -0
  77. data/lib/neo4j/core/query_find_in_batches.rb +49 -0
  78. data/lib/neo4j/core/relationship.rb +13 -0
  79. data/lib/neo4j/core/responses.rb +50 -0
  80. data/lib/neo4j/core/result.rb +33 -0
  81. data/lib/neo4j/core/schema.rb +30 -0
  82. data/lib/neo4j/core/schema_errors.rb +12 -0
  83. data/lib/neo4j/core/wrappable.rb +30 -0
  84. data/lib/neo4j/errors.rb +57 -0
  85. data/lib/neo4j/migration.rb +148 -0
  86. data/lib/neo4j/migrations.rb +27 -0
  87. data/lib/neo4j/migrations/base.rb +77 -0
  88. data/lib/neo4j/migrations/check_pending.rb +20 -0
  89. data/lib/neo4j/migrations/helpers.rb +105 -0
  90. data/lib/neo4j/migrations/helpers/id_property.rb +75 -0
  91. data/lib/neo4j/migrations/helpers/relationships.rb +66 -0
  92. data/lib/neo4j/migrations/helpers/schema.rb +51 -0
  93. data/lib/neo4j/migrations/migration_file.rb +24 -0
  94. data/lib/neo4j/migrations/runner.rb +195 -0
  95. data/lib/neo4j/migrations/schema.rb +44 -0
  96. data/lib/neo4j/migrations/schema_migration.rb +14 -0
  97. data/lib/neo4j/model_schema.rb +139 -0
  98. data/lib/neo4j/paginated.rb +27 -0
  99. data/lib/neo4j/railtie.rb +105 -0
  100. data/lib/neo4j/schema/operation.rb +102 -0
  101. data/lib/neo4j/shared.rb +60 -0
  102. data/lib/neo4j/shared/attributes.rb +216 -0
  103. data/lib/neo4j/shared/callbacks.rb +68 -0
  104. data/lib/neo4j/shared/cypher.rb +37 -0
  105. data/lib/neo4j/shared/declared_properties.rb +204 -0
  106. data/lib/neo4j/shared/declared_property.rb +109 -0
  107. data/lib/neo4j/shared/declared_property/index.rb +37 -0
  108. data/lib/neo4j/shared/enum.rb +167 -0
  109. data/lib/neo4j/shared/filtered_hash.rb +79 -0
  110. data/lib/neo4j/shared/identity.rb +34 -0
  111. data/lib/neo4j/shared/initialize.rb +64 -0
  112. data/lib/neo4j/shared/marshal.rb +23 -0
  113. data/lib/neo4j/shared/mass_assignment.rb +64 -0
  114. data/lib/neo4j/shared/permitted_attributes.rb +28 -0
  115. data/lib/neo4j/shared/persistence.rb +282 -0
  116. data/lib/neo4j/shared/property.rb +240 -0
  117. data/lib/neo4j/shared/query_factory.rb +102 -0
  118. data/lib/neo4j/shared/rel_type_converters.rb +43 -0
  119. data/lib/neo4j/shared/serialized_properties.rb +30 -0
  120. data/lib/neo4j/shared/type_converters.rb +433 -0
  121. data/lib/neo4j/shared/typecasted_attributes.rb +98 -0
  122. data/lib/neo4j/shared/typecaster.rb +53 -0
  123. data/lib/neo4j/shared/validations.rb +44 -0
  124. data/lib/neo4j/tasks/migration.rake +202 -0
  125. data/lib/neo4j/timestamps.rb +11 -0
  126. data/lib/neo4j/timestamps/created.rb +9 -0
  127. data/lib/neo4j/timestamps/updated.rb +9 -0
  128. data/lib/neo4j/transaction.rb +139 -0
  129. data/lib/neo4j/type_converters.rb +7 -0
  130. data/lib/neo4j/undeclared_properties.rb +53 -0
  131. data/lib/neo4j/version.rb +3 -0
  132. data/lib/neo4j/wrapper.rb +4 -0
  133. data/lib/rails/generators/neo4j/migration/migration_generator.rb +14 -0
  134. data/lib/rails/generators/neo4j/migration/templates/migration.erb +9 -0
  135. data/lib/rails/generators/neo4j/model/model_generator.rb +88 -0
  136. data/lib/rails/generators/neo4j/model/templates/migration.erb +9 -0
  137. data/lib/rails/generators/neo4j/model/templates/model.erb +15 -0
  138. data/lib/rails/generators/neo4j/upgrade_v8/templates/migration.erb +17 -0
  139. data/lib/rails/generators/neo4j/upgrade_v8/upgrade_v8_generator.rb +32 -0
  140. data/lib/rails/generators/neo4j_generator.rb +119 -0
  141. data/neo4j.gemspec +51 -0
  142. metadata +421 -0
@@ -0,0 +1,79 @@
1
+ module Neo4j::Shared
2
+ class FilteredHash
3
+ class InvalidHashFilterType < Neo4j::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 Neo4j
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.neo_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
+ # ActiveRel
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,64 @@
1
+ module Neo4j::Shared
2
+ module Initialize
3
+ extend ActiveSupport::Concern
4
+
5
+ # Implements the Neo4j::Node#wrapper and Neo4j::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 ||= Hash[self.class.attributes_nil_hash]
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
+ end
20
+
21
+ def stringify_attributes!(attr, properties)
22
+ properties.each_pair do |k, v|
23
+ key = self.class.declared_properties.string_key(k)
24
+ attr[key.freeze] = v
25
+ end
26
+ end
27
+
28
+ # We should be using #clear_changes_information
29
+ # but right now we don't use `ActiveModel` attributes correctly and so it doesn't work
30
+ # Once we set @attribute correctly from using class ActiveModel::Attribute
31
+ # we will no longer need to explicitly call following method and can safely remove it
32
+ def changed_attributes_clear!
33
+ return if changed_attributes.nil?
34
+
35
+ # with ActiveModel 6.0.0 we have to clear attribute changes with clear_attribute_changes
36
+ clear_attribute_changes(self.attributes.keys)
37
+
38
+ # changed_attributes is frozen starting with ActiveModel 5.2.0
39
+ # Not a good long term solution
40
+ if changed_attributes.frozen?
41
+ @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
42
+ else
43
+ changed_attributes && changed_attributes.clear
44
+ end
45
+ end
46
+
47
+ # Once we set @attribute correctly from using class ActiveModel::Attribute
48
+ # we will no longer need to explicitly call following method and can safely remove it
49
+ def changed_attributes_selective_clear!(hash_to_clear)
50
+ # with ActiveModel 6.0.0 we have to clear attribute changes with clear_attribute_change
51
+ hash_to_clear.each_key { |k| clear_attribute_change(k) } if defined?(ActiveModel::ForcedMutationTracker)
52
+
53
+ # changed_attributes is frozen starting with ActiveModel 5.2.0
54
+ # Not a good long term solution
55
+ if changed_attributes.frozen?
56
+ attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new(changed_attributes)
57
+ hash_to_clear.each_key { |k| attributes_changed_by_setter.delete(k) }
58
+ @attributes_changed_by_setter = attributes_changed_by_setter
59
+ else
60
+ hash_to_clear.each_key { |k| changed_attributes.delete(k) }
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,23 @@
1
+ module Neo4j
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,64 @@
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
+ if respond_to?(writer)
30
+ send(writer, value)
31
+ else
32
+ add_undeclared_property(name, value)
33
+ end
34
+ end
35
+ end
36
+
37
+ def add_undeclared_property(_, _); end
38
+
39
+ # Mass update a model's attributes
40
+ #
41
+ # @example Assigning a hash
42
+ # person.attributes = { :first_name => "Chris", :last_name => "Griego" }
43
+ # person.first_name #=> "Chris"
44
+ # person.last_name #=> "Griego"
45
+ #
46
+ # @param (see #assign_attributes)
47
+ def attributes=(new_attributes)
48
+ assign_attributes(new_attributes)
49
+ end
50
+
51
+ # Initialize a model with a set of attributes
52
+ #
53
+ # @example Initializing with a hash
54
+ # person = Person.new(:first_name => "Chris", :last_name => "Griego")
55
+ # person.first_name #=> "Chris"
56
+ # person.last_name #=> "Griego"
57
+ #
58
+ # @param (see #assign_attributes)
59
+ def initialize(attributes = nil)
60
+ assign_attributes(attributes)
61
+ super()
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,28 @@
1
+ module Neo4j::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,282 @@
1
+ module Neo4j::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.props.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] name of the attribute to increment
50
+ # @param [Integer, Float] 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] name of the attribute to increment
59
+ # @param [Integer, Float] 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] name of the attribute to increment
66
+ # @param [Integer, Float] 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
+ current_transaction = Neo4j::ActiveBase.current_transaction
93
+
94
+ current_transaction.mark_failed if result == false && current_transaction
95
+
96
+ result != false
97
+ rescue => e
98
+ current_transaction.mark_failed if current_transaction
99
+ raise e
100
+ ensure
101
+ @_create_or_updating = nil
102
+ end
103
+
104
+ def apply_default_values
105
+ return if self.class.declared_property_defaults.empty?
106
+ self.class.declared_property_defaults.each_pair do |key, value|
107
+ self.send("#{key}=", value.respond_to?(:call) ? value.call : value) if self.send(key).nil?
108
+ end
109
+ end
110
+
111
+ def touch
112
+ fail 'Cannot touch on a new record object' unless persisted?
113
+ update_attribute!(:updated_at, Time.now) if respond_to?(:updated_at=)
114
+ end
115
+
116
+ # Returns +true+ if the record is persisted, i.e. it's not a new record and it was not destroyed
117
+ def persisted?
118
+ !new_record? && !destroyed?
119
+ end
120
+
121
+ # Returns +true+ if the record hasn't been saved to Neo4j yet.
122
+ def new_record?
123
+ !_persisted_obj
124
+ end
125
+
126
+ alias new? new_record?
127
+
128
+ def destroy
129
+ freeze
130
+
131
+ destroy_query.exec if _persisted_obj
132
+
133
+ @_deleted = true
134
+
135
+ self
136
+ end
137
+
138
+ def exist?
139
+ return if !_persisted_obj
140
+
141
+ neo4j_query(query_as(:n).return('ID(n)')).any?
142
+ end
143
+
144
+ # Returns +true+ if the object was destroyed.
145
+ def destroyed?
146
+ @_deleted
147
+ end
148
+
149
+ # @return [Hash] all defined and none nil properties
150
+ def props
151
+ attributes.reject { |_, v| v.nil? }.symbolize_keys
152
+ end
153
+
154
+ # @return true if the attributes hash has been frozen
155
+ def frozen?
156
+ @attributes.frozen?
157
+ end
158
+
159
+ def freeze
160
+ @attributes.freeze
161
+ self
162
+ end
163
+
164
+ def reload
165
+ return self if new_record?
166
+ association_proxy_cache.clear if respond_to?(:association_proxy_cache)
167
+ changed_attributes_clear!
168
+ unless reload_from_database
169
+ @_deleted = true
170
+ freeze
171
+ end
172
+ self
173
+ end
174
+
175
+ def reload_from_database
176
+ reloaded = self.class.load_entity(neo_id)
177
+ reloaded ? init_on_reload(reloaded._persisted_obj) : nil
178
+ end
179
+
180
+ # Updates this resource with all the attributes from the passed-in Hash and requests that the record be saved.
181
+ # If saving fails because the resource is invalid then false will be returned.
182
+ def update(attributes)
183
+ self.class.run_transaction do |tx|
184
+ self.attributes = process_attributes(attributes)
185
+ saved = save
186
+ tx.mark_failed unless saved
187
+ saved
188
+ end
189
+ end
190
+ alias update_attributes update
191
+
192
+ def update_db_property(field, value)
193
+ update_db_properties(field => value)
194
+ true
195
+ end
196
+ alias update_column update_db_property
197
+
198
+ def update_db_properties(hash)
199
+ fail ::Neo4j::Error, 'can not update on a new record object' unless persisted?
200
+ self.class.run_transaction do
201
+ db_values = props_for_db(hash)
202
+ neo4j_query(query_as(:n).set(n: db_values))
203
+ db_values.each_pair { |k, v| self.public_send(:"#{k}=", v) }
204
+ _persisted_obj.props.merge!(db_values)
205
+ changed_attributes_selective_clear!(db_values)
206
+ true
207
+ end
208
+ end
209
+ alias update_columns update_db_properties
210
+
211
+ # Same as {#update_attributes}, but raises an exception if saving fails.
212
+ def update!(attributes)
213
+ self.class.run_transaction do
214
+ self.attributes = process_attributes(attributes)
215
+ save!
216
+ end
217
+ end
218
+ alias update_attributes! update!
219
+
220
+ def cache_key
221
+ if self.new_record?
222
+ "#{model_cache_key}/new"
223
+ elsif self.respond_to?(:updated_at) && !self.updated_at.blank?
224
+ "#{model_cache_key}/#{neo_id}-#{self.updated_at.utc.to_s(:number)}"
225
+ else
226
+ "#{model_cache_key}/#{neo_id}"
227
+ end
228
+ end
229
+
230
+ module ClassMethods
231
+ def run_transaction(run_in_tx = true)
232
+ Neo4j::ActiveBase.run_transaction(run_in_tx) { |tx| yield tx }
233
+ end
234
+ end
235
+
236
+ protected
237
+
238
+ def increment_by_query!(match_query, attribute, by, element_name = :n)
239
+ new_attribute = match_query.with(element_name)
240
+ .set("#{element_name}.`#{attribute}` = COALESCE(#{element_name}.`#{attribute}`, 0) + {by}")
241
+ .params(by: by).limit(1)
242
+ .pluck("#{element_name}.`#{attribute}`").first
243
+ return false unless new_attribute
244
+ self[attribute] = new_attribute
245
+
246
+ if defined? ActiveModel::ForcedMutationTracker
247
+ # with ActiveModel 6.0.0 set_attribute_was is removed
248
+ # so we mark attribute's previous value using attr_will_change method
249
+ clear_attribute_change(attribute)
250
+ else
251
+ set_attribute_was(attribute, new_attribute)
252
+ end
253
+ true
254
+ end
255
+
256
+ private
257
+
258
+ def props_for_db(props_hash)
259
+ self.class.declared_properties.convert_properties_to(self, :db, props_hash)
260
+ end
261
+
262
+ def model_cache_key
263
+ self.class.model_name.cache_key
264
+ end
265
+
266
+ def update_magic_properties
267
+ self.updated_at = DateTime.now if respond_to?(:updated_at=) && (updated_at.nil? || (changed? && !updated_at_changed?))
268
+ end
269
+
270
+ def inject_timestamps!
271
+ now = DateTime.now
272
+ self.created_at ||= now if respond_to?(:created_at=)
273
+ self.updated_at ||= now if respond_to?(:updated_at=)
274
+ end
275
+
276
+ def set_timestamps
277
+ warning = 'This method has been replaced with `inject_timestamps!` and will be removed in a future version'.freeze
278
+ ActiveSupport::Deprecation.warn warning, caller
279
+ inject_timestamps!
280
+ end
281
+ end
282
+ end