neo4j_legacy 7.2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1357 -0
  3. data/CONTRIBUTORS +8 -0
  4. data/Gemfile +38 -0
  5. data/README.md +103 -0
  6. data/bin/neo4j-jars +33 -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_support/per_thread_registry.rb +1 -0
  12. data/lib/backports/action_controller/metal/strong_parameters.rb +672 -0
  13. data/lib/backports/active_model/forbidden_attributes_protection.rb +30 -0
  14. data/lib/backports/active_support/concern.rb +13 -0
  15. data/lib/backports/active_support/core_ext/module/attribute_accessors.rb +10 -0
  16. data/lib/backports/active_support/logger.rb +99 -0
  17. data/lib/backports/active_support/logger_silence.rb +27 -0
  18. data/lib/backports/active_support/logger_thread_safe_level.rb +32 -0
  19. data/lib/backports/active_support/per_thread_registry.rb +60 -0
  20. data/lib/backports.rb +4 -0
  21. data/lib/neo4j/active_node/callbacks.rb +8 -0
  22. data/lib/neo4j/active_node/dependent/association_methods.rb +48 -0
  23. data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +50 -0
  24. data/lib/neo4j/active_node/dependent.rb +11 -0
  25. data/lib/neo4j/active_node/enum.rb +29 -0
  26. data/lib/neo4j/active_node/has_n/association/rel_factory.rb +61 -0
  27. data/lib/neo4j/active_node/has_n/association/rel_wrapper.rb +23 -0
  28. data/lib/neo4j/active_node/has_n/association.rb +280 -0
  29. data/lib/neo4j/active_node/has_n/association_cypher_methods.rb +108 -0
  30. data/lib/neo4j/active_node/has_n.rb +532 -0
  31. data/lib/neo4j/active_node/id_property/accessor.rb +62 -0
  32. data/lib/neo4j/active_node/id_property.rb +187 -0
  33. data/lib/neo4j/active_node/initialize.rb +21 -0
  34. data/lib/neo4j/active_node/labels/index.rb +87 -0
  35. data/lib/neo4j/active_node/labels/reloading.rb +21 -0
  36. data/lib/neo4j/active_node/labels.rb +198 -0
  37. data/lib/neo4j/active_node/node_wrapper.rb +52 -0
  38. data/lib/neo4j/active_node/orm_adapter.rb +82 -0
  39. data/lib/neo4j/active_node/persistence.rb +175 -0
  40. data/lib/neo4j/active_node/property.rb +60 -0
  41. data/lib/neo4j/active_node/query/query_proxy.rb +361 -0
  42. data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +61 -0
  43. data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +90 -0
  44. data/lib/neo4j/active_node/query/query_proxy_find_in_batches.rb +19 -0
  45. data/lib/neo4j/active_node/query/query_proxy_link.rb +117 -0
  46. data/lib/neo4j/active_node/query/query_proxy_methods.rb +210 -0
  47. data/lib/neo4j/active_node/query/query_proxy_methods_of_mass_updating.rb +83 -0
  48. data/lib/neo4j/active_node/query.rb +76 -0
  49. data/lib/neo4j/active_node/query_methods.rb +65 -0
  50. data/lib/neo4j/active_node/reflection.rb +86 -0
  51. data/lib/neo4j/active_node/rels.rb +11 -0
  52. data/lib/neo4j/active_node/scope.rb +146 -0
  53. data/lib/neo4j/active_node/unpersisted.rb +48 -0
  54. data/lib/neo4j/active_node/validations.rb +59 -0
  55. data/lib/neo4j/active_node.rb +105 -0
  56. data/lib/neo4j/active_rel/callbacks.rb +15 -0
  57. data/lib/neo4j/active_rel/initialize.rb +28 -0
  58. data/lib/neo4j/active_rel/persistence/query_factory.rb +95 -0
  59. data/lib/neo4j/active_rel/persistence.rb +114 -0
  60. data/lib/neo4j/active_rel/property.rb +95 -0
  61. data/lib/neo4j/active_rel/query.rb +95 -0
  62. data/lib/neo4j/active_rel/rel_wrapper.rb +22 -0
  63. data/lib/neo4j/active_rel/related_node.rb +83 -0
  64. data/lib/neo4j/active_rel/types.rb +82 -0
  65. data/lib/neo4j/active_rel/validations.rb +8 -0
  66. data/lib/neo4j/active_rel.rb +67 -0
  67. data/lib/neo4j/class_arguments.rb +39 -0
  68. data/lib/neo4j/config.rb +124 -0
  69. data/lib/neo4j/core/query.rb +22 -0
  70. data/lib/neo4j/errors.rb +28 -0
  71. data/lib/neo4j/migration.rb +127 -0
  72. data/lib/neo4j/paginated.rb +27 -0
  73. data/lib/neo4j/railtie.rb +169 -0
  74. data/lib/neo4j/schema/operation.rb +91 -0
  75. data/lib/neo4j/shared/attributes.rb +220 -0
  76. data/lib/neo4j/shared/callbacks.rb +64 -0
  77. data/lib/neo4j/shared/cypher.rb +37 -0
  78. data/lib/neo4j/shared/declared_properties.rb +204 -0
  79. data/lib/neo4j/shared/declared_property/index.rb +37 -0
  80. data/lib/neo4j/shared/declared_property.rb +118 -0
  81. data/lib/neo4j/shared/enum.rb +148 -0
  82. data/lib/neo4j/shared/filtered_hash.rb +79 -0
  83. data/lib/neo4j/shared/identity.rb +28 -0
  84. data/lib/neo4j/shared/initialize.rb +28 -0
  85. data/lib/neo4j/shared/marshal.rb +23 -0
  86. data/lib/neo4j/shared/mass_assignment.rb +58 -0
  87. data/lib/neo4j/shared/permitted_attributes.rb +28 -0
  88. data/lib/neo4j/shared/persistence.rb +231 -0
  89. data/lib/neo4j/shared/property.rb +220 -0
  90. data/lib/neo4j/shared/query_factory.rb +101 -0
  91. data/lib/neo4j/shared/rel_type_converters.rb +43 -0
  92. data/lib/neo4j/shared/serialized_properties.rb +30 -0
  93. data/lib/neo4j/shared/type_converters.rb +418 -0
  94. data/lib/neo4j/shared/typecasted_attributes.rb +98 -0
  95. data/lib/neo4j/shared/typecaster.rb +53 -0
  96. data/lib/neo4j/shared/validations.rb +48 -0
  97. data/lib/neo4j/shared.rb +51 -0
  98. data/lib/neo4j/tasks/migration.rake +24 -0
  99. data/lib/neo4j/timestamps/created.rb +9 -0
  100. data/lib/neo4j/timestamps/updated.rb +9 -0
  101. data/lib/neo4j/timestamps.rb +11 -0
  102. data/lib/neo4j/type_converters.rb +7 -0
  103. data/lib/neo4j/version.rb +3 -0
  104. data/lib/neo4j/wrapper.rb +4 -0
  105. data/lib/neo4j.rb +96 -0
  106. data/lib/rails/generators/neo4j/model/model_generator.rb +86 -0
  107. data/lib/rails/generators/neo4j/model/templates/model.erb +15 -0
  108. data/lib/rails/generators/neo4j_generator.rb +67 -0
  109. data/neo4j.gemspec +43 -0
  110. metadata +389 -0
@@ -0,0 +1,148 @@
1
+ module Neo4j::Shared
2
+ module Enum
3
+ extend ActiveSupport::Concern
4
+
5
+ class ConflictingEnumMethodError < Neo4j::Error; end
6
+
7
+ module ClassMethods
8
+ attr_reader :neo4j_enum_data
9
+
10
+ # Similar to ActiveRecord enum, maps an integer value on the database to
11
+ # a set of enum keys.
12
+ #
13
+ # @example Base example
14
+ # class Media
15
+ # include Neo4j::ActiveNode
16
+ # enum type: [:image, :video, :unknown]
17
+ # end
18
+ # Media.types # => { :images => 0, :video => 1, :unknown => 2 }
19
+ #
20
+ # media.video!
21
+ # media.image? # => false
22
+ # media.type # => :video
23
+ #
24
+ # Media.videos # => All medias with type = 1 (:video)
25
+ # Media.where(type: :video) # => All medias with type = 1 (:video)
26
+ #
27
+ # @example Prefix-ing an enum
28
+ # Media.enum type: [:image, :video, :unknown], _prefix: :enum
29
+ #
30
+ # media.enum_video!
31
+ # media.enum_video? # => true
32
+ #
33
+ # @example Suffix-ing an enum
34
+ # Media.enum type: [:image, :video, :unknown], _suffix: true
35
+ #
36
+ # media.video_type!
37
+ # media.video_type? # => true
38
+ #
39
+ # @example Disable index: :exact for enum elements
40
+ # Media.enum type: [:image, :video, :unknown], _index: false
41
+ #
42
+ # @example Define a custom mapping for keys-numbers
43
+ # Media.enum type: { image: 1, video: 2, unknown: 3 }
44
+ #
45
+ # @see http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html
46
+ def enum(parameters = {})
47
+ options, parameters = *split_options_and_parameters(parameters)
48
+ parameters.each do |property_name, enum_keys|
49
+ enum_keys = normalize_key_list enum_keys
50
+ @neo4j_enum_data ||= {}
51
+ @neo4j_enum_data[property_name] = enum_keys
52
+ define_property(property_name, enum_keys, options)
53
+ define_enum_methods(property_name, enum_keys, options)
54
+ end
55
+ end
56
+
57
+ protected
58
+
59
+ def normalize_key_list(enum_keys)
60
+ case enum_keys
61
+ when Hash
62
+ enum_keys
63
+ when Array
64
+ Hash[enum_keys.each_with_index.to_a]
65
+ else
66
+ fail ArgumentError, 'Invalid parameter for enum. Please provide an Array or an Hash.'
67
+ end
68
+ end
69
+
70
+ VALID_OPTIONS_FOR_ENUMS = [:_index, :_prefix, :_suffix, :_default]
71
+ DEFAULT_OPTIONS_FOR_ENUMS = {
72
+ _index: true
73
+ }
74
+
75
+ def split_options_and_parameters(parameters)
76
+ options = DEFAULT_OPTIONS_FOR_ENUMS.clone
77
+ new_parameters = {}
78
+ parameters.each do |k, v|
79
+ if VALID_OPTIONS_FOR_ENUMS.include? k
80
+ options[k] = v
81
+ else
82
+ new_parameters[k] = v
83
+ end
84
+ end
85
+ [options, new_parameters]
86
+ end
87
+
88
+ def define_property(property_name, enum_keys, options)
89
+ property_options = build_property_options(enum_keys, options)
90
+ property property_name, property_options
91
+ serialize property_name, Neo4j::Shared::TypeConverters::EnumConverter.new(enum_keys, property_options)
92
+ end
93
+
94
+ def build_property_options(_enum_keys, options = {})
95
+ {
96
+ default: options[:_default]
97
+ }
98
+ end
99
+
100
+ def define_enum_methods(property_name, enum_keys, options)
101
+ define_enum_methods_?(property_name, enum_keys, options)
102
+ define_enum_methods_!(property_name, enum_keys, options)
103
+ define_class_methods(property_name, enum_keys)
104
+ end
105
+
106
+ def define_class_methods(property_name, enum_keys)
107
+ plural_property_name = property_name.to_s.pluralize.to_sym
108
+ define_singleton_method(plural_property_name) do
109
+ enum_keys
110
+ end
111
+ end
112
+
113
+ def define_enum_methods_?(property_name, enum_keys, options)
114
+ enum_keys.keys.each do |enum_value|
115
+ method_name = build_method_name(enum_value, property_name, options)
116
+ check_enum_method_conflicts! property_name, :"#{method_name}?"
117
+ define_method("#{method_name}?") do
118
+ __send__(property_name).to_s.to_sym == enum_value
119
+ end
120
+ end
121
+ end
122
+
123
+ def define_enum_methods_!(property_name, enum_keys, options)
124
+ enum_keys.keys.each do |enum_value|
125
+ method_name = build_method_name(enum_value, property_name, options)
126
+ check_enum_method_conflicts! property_name, :"#{method_name}!"
127
+ define_method("#{method_name}!") do
128
+ __send__("#{property_name}=", enum_value)
129
+ end
130
+ end
131
+ end
132
+
133
+ def build_method_name(base_name, property_name, options)
134
+ method_name = base_name
135
+ method_name = "#{method_name}_#{property_name}" if options[:_suffix]
136
+ method_name = "#{options[:_prefix]}_#{method_name}" if options[:_prefix]
137
+ method_name
138
+ end
139
+
140
+ def check_enum_method_conflicts!(property_name, method_name)
141
+ fail ConflictingEnumMethodError,
142
+ "The enum `#{property_name}` is trying to define a `#{method_name}` method, "\
143
+ 'that is already defined. Try to use options `:prefix` or `:suffix` '\
144
+ 'to avoid conflicts.' if instance_methods(false).include?(method_name)
145
+ end
146
+ end
147
+ end
148
+ end
@@ -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,28 @@
1
+ module Neo4j::Shared
2
+ module Identity
3
+ def ==(other)
4
+ other.class == self.class && other.id == id
5
+ end
6
+ alias_method :eql?, :==
7
+
8
+ # Returns an Enumerable of all (primary) key attributes
9
+ # or nil if model.persisted? is false
10
+ def to_key
11
+ _persisted_obj ? [id] : nil
12
+ end
13
+
14
+ # @return [Integer, nil] the neo4j id of the node if persisted or nil
15
+ def neo_id
16
+ _persisted_obj ? _persisted_obj.neo_id : nil
17
+ end
18
+
19
+ def id
20
+ id = neo_id
21
+ id.is_a?(Integer) ? id : nil
22
+ end
23
+
24
+ def hash
25
+ id.hash
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
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
+ end
28
+ 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,58 @@
1
+ module Neo4j::Shared
2
+ # MassAssignment allows you to bulk set and update attributes
3
+ #
4
+ # Including MassAssignment into your model gives it a set of mass assignment
5
+ # methods, similar to those found in ActiveRecord.
6
+ #
7
+ # @example Usage
8
+ # class Person
9
+ # include Neo4j::Shared::MassAssignment
10
+ # end
11
+ #
12
+ # Originally part of ActiveAttr, https://github.com/cgriego/active_attr
13
+ module MassAssignment
14
+ extend ActiveSupport::Concern
15
+ # Mass update a model's attributes
16
+ #
17
+ # @example Assigning a hash
18
+ # person.assign_attributes(:first_name => "Chris", :last_name => "Griego")
19
+ # person.first_name #=> "Chris"
20
+ # person.last_name #=> "Griego"
21
+ #
22
+ # @param [Hash{#to_s => Object}, #each] attributes Attributes used to
23
+ # populate the model
24
+ # @param [Hash, #[]] options Options that affect mass assignment
25
+ def assign_attributes(new_attributes = nil)
26
+ return unless new_attributes.present?
27
+ new_attributes.each do |name, value|
28
+ writer = :"#{name}="
29
+ send(writer, value) if respond_to?(writer)
30
+ end
31
+ end
32
+
33
+ # Mass update a model's attributes
34
+ #
35
+ # @example Assigning a hash
36
+ # person.attributes = { :first_name => "Chris", :last_name => "Griego" }
37
+ # person.first_name #=> "Chris"
38
+ # person.last_name #=> "Griego"
39
+ #
40
+ # @param (see #assign_attributes)
41
+ def attributes=(new_attributes)
42
+ assign_attributes(new_attributes)
43
+ end
44
+
45
+ # Initialize a model with a set of attributes
46
+ #
47
+ # @example Initializing with a hash
48
+ # person = Person.new(:first_name => "Chris", :last_name => "Griego")
49
+ # person.first_name #=> "Chris"
50
+ # person.last_name #=> "Griego"
51
+ #
52
+ # @param (see #assign_attributes)
53
+ def initialize(attributes = nil)
54
+ assign_attributes(attributes)
55
+ super()
56
+ end
57
+ end
58
+ end
@@ -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,231 @@
1
+ module Neo4j::Shared
2
+ module Persistence
3
+ extend ActiveSupport::Concern
4
+
5
+ # @return [Hash] Given a node's state, will call the appropriate `props_for_{action}` method.
6
+ def props_for_persistence
7
+ _persisted_obj ? props_for_update : props_for_create
8
+ end
9
+
10
+ def update_model
11
+ return if !changed_attributes || changed_attributes.empty?
12
+ _persisted_obj.update_props(props_for_update)
13
+ changed_attributes.clear
14
+ end
15
+
16
+ # Returns a hash containing:
17
+ # * All properties and values for insertion in the database
18
+ # * A `uuid` (or equivalent) key and value
19
+ # * Timestamps, if the class is set to include them.
20
+ # Note that the UUID is added to the hash but is not set on the node.
21
+ # The timestamps, by comparison, are set on the node prior to addition in this hash.
22
+ # @return [Hash]
23
+ def props_for_create
24
+ inject_timestamps!
25
+ props_with_defaults = inject_defaults!(props)
26
+ converted_props = props_for_db(props_with_defaults)
27
+ return converted_props unless self.class.respond_to?(:default_property_values)
28
+ inject_primary_key!(converted_props)
29
+ end
30
+
31
+ # @return [Hash] Properties and values, type-converted and timestamped for the database.
32
+ def props_for_update
33
+ update_magic_properties
34
+ changed_props = attributes.select { |k, _| changed_attributes.include?(k) }
35
+ changed_props.symbolize_keys!
36
+ inject_defaults!(changed_props)
37
+ props_for_db(changed_props)
38
+ end
39
+
40
+ # Increments a numeric attribute by a centain amount
41
+ # @param [Symbol, String] name of the attribute to increment
42
+ # @param [Integer, Float] amount to increment
43
+ def increment(attribute, by = 1)
44
+ self[attribute] ||= 0
45
+ self[attribute] += by
46
+ self
47
+ end
48
+
49
+ # Convenience method to increment numeric attribute and #save at the same time
50
+ # @param [Symbol, String] name of the attribute to increment
51
+ # @param [Integer, Float] amount to increment
52
+ def increment!(attribute, by = 1)
53
+ increment(attribute, by).update_attribute(attribute, self[attribute])
54
+ end
55
+
56
+ # Increments concurrently a numeric attribute by a centain amount
57
+ # @param [Symbol, String] name of the attribute to increment
58
+ # @param [Integer, Float] amount to increment
59
+ def concurrent_increment!(_attribute, _by = 1)
60
+ fail 'not_implemented'
61
+ end
62
+
63
+ # Convenience method to set attribute and #save at the same time
64
+ # @param [Symbol, String] attribute of the attribute to update
65
+ # @param [Object] value to set
66
+ def update_attribute(attribute, value)
67
+ send("#{attribute}=", value)
68
+ self.save
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
+ send("#{attribute}=", value)
76
+ self.save!
77
+ end
78
+
79
+ def create_or_update
80
+ # since the same model can be created or updated twice from a relationship we have to have this guard
81
+ @_create_or_updating = true
82
+ apply_default_values
83
+ result = _persisted_obj ? update_model : create_model
84
+ if result == false
85
+ Neo4j::Transaction.current.failure if Neo4j::Transaction.current
86
+ false
87
+ else
88
+ true
89
+ end
90
+ rescue => e
91
+ Neo4j::Transaction.current.failure if Neo4j::Transaction.current
92
+ raise e
93
+ ensure
94
+ @_create_or_updating = nil
95
+ end
96
+
97
+ def apply_default_values
98
+ return if self.class.declared_property_defaults.empty?
99
+ self.class.declared_property_defaults.each_pair do |key, value|
100
+ self.send("#{key}=", value) if self.send(key).nil?
101
+ end
102
+ end
103
+
104
+ def touch
105
+ fail 'Cannot touch on a new record object' unless persisted?
106
+ update_attribute!(:updated_at, Time.now) if respond_to?(:updated_at=)
107
+ end
108
+
109
+ # Returns +true+ if the record is persisted, i.e. it's not a new record and it was not destroyed
110
+ def persisted?
111
+ !new_record? && !destroyed?
112
+ end
113
+
114
+ # Returns +true+ if the record hasn't been saved to Neo4j yet.
115
+ def new_record?
116
+ !_persisted_obj
117
+ end
118
+
119
+ alias_method :new?, :new_record?
120
+
121
+ def destroy
122
+ freeze
123
+ _persisted_obj && _persisted_obj.del
124
+ @_deleted = true
125
+ end
126
+
127
+ def exist?
128
+ _persisted_obj && _persisted_obj.exist?
129
+ end
130
+
131
+ # Returns +true+ if the object was destroyed.
132
+ def destroyed?
133
+ @_deleted
134
+ end
135
+
136
+ # @return [Hash] all defined and none nil properties
137
+ def props
138
+ attributes.reject { |_, v| v.nil? }.symbolize_keys
139
+ end
140
+
141
+ # @return true if the attributes hash has been frozen
142
+ def frozen?
143
+ @attributes.frozen?
144
+ end
145
+
146
+ def freeze
147
+ @attributes.freeze
148
+ self
149
+ end
150
+
151
+ def reload
152
+ return self if new_record?
153
+ association_proxy_cache.clear if respond_to?(:association_proxy_cache)
154
+ changed_attributes && changed_attributes.clear
155
+ unless reload_from_database
156
+ @_deleted = true
157
+ freeze
158
+ end
159
+ self
160
+ end
161
+
162
+ def reload_from_database
163
+ reloaded = self.class.load_entity(neo_id)
164
+ reloaded ? init_on_reload(reloaded._persisted_obj) : nil
165
+ end
166
+
167
+ # Updates this resource with all the attributes from the passed-in Hash and requests that the record be saved.
168
+ # If saving fails because the resource is invalid then false will be returned.
169
+ def update(attributes)
170
+ self.attributes = process_attributes(attributes)
171
+ save
172
+ end
173
+ alias_method :update_attributes, :update
174
+
175
+ # Same as {#update_attributes}, but raises an exception if saving fails.
176
+ def update!(attributes)
177
+ self.attributes = process_attributes(attributes)
178
+ save!
179
+ end
180
+ alias_method :update_attributes!, :update!
181
+
182
+ def cache_key
183
+ if self.new_record?
184
+ "#{model_cache_key}/new"
185
+ elsif self.respond_to?(:updated_at) && !self.updated_at.blank?
186
+ "#{model_cache_key}/#{neo_id}-#{self.updated_at.utc.to_s(:number)}"
187
+ else
188
+ "#{model_cache_key}/#{neo_id}"
189
+ end
190
+ end
191
+
192
+ protected
193
+
194
+ def increment_by_query!(match_query, attribute, by, element_name = :n)
195
+ new_attribute = match_query.with(element_name)
196
+ .set("#{element_name}.`#{attribute}` = COALESCE(#{element_name}.`#{attribute}`, 0) + {by}")
197
+ .params(by: by).limit(1)
198
+ .pluck("#{element_name}.`#{attribute}`").first
199
+ return false unless new_attribute
200
+ self[attribute] = new_attribute
201
+ changed_attributes.delete(attribute)
202
+ true
203
+ end
204
+
205
+ private
206
+
207
+ def props_for_db(props_hash)
208
+ self.class.declared_properties.convert_properties_to(self, :db, props_hash)
209
+ end
210
+
211
+ def model_cache_key
212
+ self.class.model_name.cache_key
213
+ end
214
+
215
+ def update_magic_properties
216
+ self.updated_at = DateTime.now if respond_to?(:updated_at=) && (updated_at.nil? || (changed? && !updated_at_changed?))
217
+ end
218
+
219
+ def inject_timestamps!
220
+ now = DateTime.now
221
+ self.created_at ||= now if respond_to?(:created_at=)
222
+ self.updated_at ||= now if respond_to?(:updated_at=)
223
+ end
224
+
225
+ def set_timestamps
226
+ warning = 'This method has been replaced with `inject_timestamps!` and will be removed in a future version'.freeze
227
+ ActiveSupport::Deprecation.warn warning, caller
228
+ inject_timestamps!
229
+ end
230
+ end
231
+ end