dynamoid 3.4.0 → 3.7.1

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +94 -3
  3. data/README.md +52 -26
  4. data/lib/dynamoid.rb +1 -0
  5. data/lib/dynamoid/adapter.rb +15 -6
  6. data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +48 -36
  7. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb +13 -1
  8. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +9 -8
  9. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +5 -4
  10. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/backoff.rb +2 -2
  11. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/limit.rb +2 -3
  12. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/start_key.rb +2 -2
  13. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +4 -2
  14. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +6 -3
  15. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/table.rb +1 -0
  16. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/until_past_table_status.rb +2 -1
  17. data/lib/dynamoid/application_time_zone.rb +1 -0
  18. data/lib/dynamoid/associations.rb +182 -19
  19. data/lib/dynamoid/associations/association.rb +10 -2
  20. data/lib/dynamoid/associations/belongs_to.rb +2 -1
  21. data/lib/dynamoid/associations/has_and_belongs_to_many.rb +2 -1
  22. data/lib/dynamoid/associations/has_many.rb +2 -1
  23. data/lib/dynamoid/associations/has_one.rb +2 -1
  24. data/lib/dynamoid/associations/many_association.rb +68 -23
  25. data/lib/dynamoid/associations/single_association.rb +31 -4
  26. data/lib/dynamoid/components.rb +1 -0
  27. data/lib/dynamoid/config.rb +5 -5
  28. data/lib/dynamoid/config/backoff_strategies/constant_backoff.rb +1 -0
  29. data/lib/dynamoid/config/backoff_strategies/exponential_backoff.rb +1 -0
  30. data/lib/dynamoid/config/options.rb +1 -0
  31. data/lib/dynamoid/criteria.rb +9 -1
  32. data/lib/dynamoid/criteria/chain.rb +422 -46
  33. data/lib/dynamoid/criteria/ignored_conditions_detector.rb +3 -3
  34. data/lib/dynamoid/criteria/key_fields_detector.rb +32 -11
  35. data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +3 -2
  36. data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +1 -1
  37. data/lib/dynamoid/dirty.rb +119 -64
  38. data/lib/dynamoid/document.rb +125 -43
  39. data/lib/dynamoid/dumping.rb +9 -0
  40. data/lib/dynamoid/dynamodb_time_zone.rb +1 -0
  41. data/lib/dynamoid/errors.rb +2 -0
  42. data/lib/dynamoid/fields.rb +217 -36
  43. data/lib/dynamoid/fields/declare.rb +86 -0
  44. data/lib/dynamoid/finders.rb +69 -32
  45. data/lib/dynamoid/identity_map.rb +6 -0
  46. data/lib/dynamoid/indexes.rb +86 -17
  47. data/lib/dynamoid/loadable.rb +2 -2
  48. data/lib/dynamoid/log/formatter.rb +26 -0
  49. data/lib/dynamoid/middleware/identity_map.rb +1 -0
  50. data/lib/dynamoid/persistence.rb +496 -104
  51. data/lib/dynamoid/persistence/import.rb +1 -0
  52. data/lib/dynamoid/persistence/save.rb +1 -0
  53. data/lib/dynamoid/persistence/update_fields.rb +5 -2
  54. data/lib/dynamoid/persistence/update_validations.rb +18 -0
  55. data/lib/dynamoid/persistence/upsert.rb +5 -3
  56. data/lib/dynamoid/primary_key_type_mapping.rb +1 -0
  57. data/lib/dynamoid/railtie.rb +1 -0
  58. data/lib/dynamoid/tasks.rb +3 -1
  59. data/lib/dynamoid/tasks/database.rb +1 -0
  60. data/lib/dynamoid/type_casting.rb +12 -2
  61. data/lib/dynamoid/undumping.rb +8 -0
  62. data/lib/dynamoid/validations.rb +6 -1
  63. data/lib/dynamoid/version.rb +1 -1
  64. metadata +48 -74
  65. data/.coveralls.yml +0 -1
  66. data/.document +0 -5
  67. data/.gitignore +0 -74
  68. data/.rspec +0 -2
  69. data/.rubocop.yml +0 -71
  70. data/.rubocop_todo.yml +0 -55
  71. data/.travis.yml +0 -44
  72. data/Appraisals +0 -22
  73. data/Gemfile +0 -8
  74. data/Rakefile +0 -46
  75. data/Vagrantfile +0 -29
  76. data/docker-compose.yml +0 -7
  77. data/dynamoid.gemspec +0 -57
  78. data/gemfiles/rails_4_2.gemfile +0 -9
  79. data/gemfiles/rails_5_0.gemfile +0 -8
  80. data/gemfiles/rails_5_1.gemfile +0 -8
  81. data/gemfiles/rails_5_2.gemfile +0 -8
  82. data/gemfiles/rails_6_0.gemfile +0 -8
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Dynamoid #:nodoc:
3
+ module Dynamoid
4
4
  # The base association module which all associations include. Every association has two very important components: the source and
5
5
  # the target. The source is the object which is calling the association information. It always has the target_ids inside of an attribute on itself.
6
6
  # The target is the object which is referencing by this association.
7
+ # @private
7
8
  module Associations
9
+ # @private
8
10
  module Association
9
11
  attr_accessor :name, :options, :source, :loaded
10
12
 
@@ -56,6 +58,12 @@ module Dynamoid #:nodoc:
56
58
  :set
57
59
  end
58
60
 
61
+ def disassociate_source
62
+ Array(target).each do |target_entry|
63
+ target_entry.send(target_association).disassociate(source.hash_key) if target_entry && target_association
64
+ end
65
+ end
66
+
59
67
  private
60
68
 
61
69
  # The target class name, either inferred through the association's name or specified in options.
@@ -116,7 +124,7 @@ module Dynamoid #:nodoc:
116
124
 
117
125
  # Create a new instance of the target class without trying to add it to the association. This creates a base, that caller can update before setting or adding it.
118
126
  #
119
- # @param [Hash] attribute hash for the new object
127
+ # @param attributes [Hash] attribute values for the new object
120
128
  #
121
129
  # @return [Dynamoid::Document] the newly-created object
122
130
  #
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Dynamoid #:nodoc:
3
+ module Dynamoid
4
4
  # The belongs_to association. For belongs_to, we reference only a single target instead of multiple records; that target is the
5
5
  # object to which the association object is associated.
6
6
  module Associations
7
+ # @private
7
8
  class BelongsTo
8
9
  include SingleAssociation
9
10
 
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Dynamoid #:nodoc:
3
+ module Dynamoid
4
4
  # The has and belongs to many association.
5
5
  module Associations
6
+ # @private
6
7
  class HasAndBelongsToMany
7
8
  include ManyAssociation
8
9
 
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Dynamoid #:nodoc:
3
+ module Dynamoid
4
4
  # The has_many association.
5
5
  module Associations
6
+ # @private
6
7
  class HasMany
7
8
  include ManyAssociation
8
9
 
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Dynamoid #:nodoc:
3
+ module Dynamoid
4
4
  # The HasOne association.
5
5
  module Associations
6
+ # @private
6
7
  class HasOne
7
8
  include Association
8
9
  include SingleAssociation
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Dynamoid #:nodoc:
3
+ module Dynamoid
4
4
  module Associations
5
5
  module ManyAssociation
6
6
  include Association
@@ -13,6 +13,8 @@ module Dynamoid #:nodoc:
13
13
  end
14
14
 
15
15
  include Enumerable
16
+
17
+ # @private
16
18
  # Delegate methods to the records the association represents.
17
19
  delegate :first, :last, :empty?, :size, :class, to: :records
18
20
 
@@ -20,11 +22,15 @@ module Dynamoid #:nodoc:
20
22
  #
21
23
  # @return the association records; depending on which association this is, either a single instance or an array
22
24
  #
25
+ # @private
23
26
  # @since 0.2.0
24
27
  def find_target
25
- Array(target_class.find(source_ids.to_a))
28
+ return [] if source_ids.empty?
29
+
30
+ Array(target_class.find(source_ids.to_a, raise_error: false))
26
31
  end
27
32
 
33
+ # @private
28
34
  def records
29
35
  if query.empty?
30
36
  target
@@ -43,13 +49,20 @@ module Dynamoid #:nodoc:
43
49
  records.include?(object)
44
50
  end
45
51
 
46
- # Deletes an object or array of objects from the association. This removes their records from the association field on the source,
47
- # and attempts to remove the source from the target association if it is detected to exist.
52
+ # Delete an object or array of objects from the association.
48
53
  #
49
- # @param [Dynamoid::Document] object the object (or array of objects) to remove from the association
54
+ # tag.posts.delete(post)
55
+ # tag.posts.delete([post1, post2, post3])
50
56
  #
51
- # @return [Dynamoid::Document] the deleted object
57
+ # This removes their records from the association field on the source,
58
+ # and attempts to remove the source from the target association if it is
59
+ # detected to exist.
52
60
  #
61
+ # It saves both models immediately - the source model and the target one
62
+ # so any not saved changes will be saved as well.
63
+ #
64
+ # @param object [Dynamoid::Document|Array] model (or array of models) to remove from the association
65
+ # @return [Dynamoid::Document|Array] the deleted model
53
66
  # @since 0.2.0
54
67
  def delete(object)
55
68
  disassociate(Array(object).collect(&:hash_key))
@@ -59,13 +72,19 @@ module Dynamoid #:nodoc:
59
72
  object
60
73
  end
61
74
 
62
- # Add an object or array of objects to an association. This preserves the current records in the association (if any)
63
- # and adds the object to the target association if it is detected to exist.
75
+ # Add an object or array of objects to an association.
64
76
  #
65
- # @param [Dynamoid::Document] object the object (or array of objects) to add to the association
77
+ # tag.posts << post
78
+ # tag.posts << [post1, post2, post3]
66
79
  #
67
- # @return [Dynamoid::Document] the added object
80
+ # This preserves the current records in the association (if any) and adds
81
+ # the object to the target association if it is detected to exist.
68
82
  #
83
+ # It saves both models immediately - the source model and the target one
84
+ # so any not saved changes will be saved as well.
85
+ #
86
+ # @param object [Dynamoid::Document|Array] model (or array of models) to add to the association
87
+ # @return [Dynamoid::Document] the added model
69
88
  # @since 0.2.0
70
89
  def <<(object)
71
90
  associate(Array(object).collect(&:hash_key))
@@ -82,8 +101,9 @@ module Dynamoid #:nodoc:
82
101
  #
83
102
  # @param [Dynamoid::Document] object the object (or array of objects) to add to the association
84
103
  #
85
- # @return [Dynamoid::Document] the added object
104
+ # @return [Dynamoid::Document|Array] the added object
86
105
  #
106
+ # @private
87
107
  # @since 0.2.0
88
108
  def setter(object)
89
109
  target.each { |o| delete(o) }
@@ -91,23 +111,37 @@ module Dynamoid #:nodoc:
91
111
  object
92
112
  end
93
113
 
94
- # Create a new instance of the target class and add it directly to the association. If the create fails an exception will be raised.
114
+ # Create a new instance of the target class, persist it and add directly
115
+ # to the association.
95
116
  #
96
- # @param [Hash] attribute hash for the new object
117
+ # tag.posts.create!(title: 'foo')
97
118
  #
98
- # @return [Dynamoid::Document] the newly-created object
119
+ # Several models can be created at once when an array of attributes
120
+ # specified:
121
+ #
122
+ # tag.posts.create!([{ title: 'foo' }, {title: 'bar'} ])
99
123
  #
124
+ # If the creation fails an exception will be raised.
125
+ #
126
+ # @param attributes [Hash] attribute values for the new object
127
+ # @return [Dynamoid::Document|Array] the newly-created object
100
128
  # @since 0.2.0
101
129
  def create!(attributes = {})
102
130
  self << target_class.create!(attributes)
103
131
  end
104
132
 
105
- # Create a new instance of the target class and add it directly to the association.
133
+ # Create a new instance of the target class, persist it and add directly
134
+ # to the association.
106
135
  #
107
- # @param [Hash] attribute hash for the new object
136
+ # tag.posts.create(title: 'foo')
108
137
  #
109
- # @return [Dynamoid::Document] the newly-created object
138
+ # Several models can be created at once when an array of attributes
139
+ # specified:
110
140
  #
141
+ # tag.posts.create([{ title: 'foo' }, {title: 'bar'} ])
142
+ #
143
+ # @param attributes [Hash] attribute values for the new object
144
+ # @return [Dynamoid::Document|Array] the newly-created object
111
145
  # @since 0.2.0
112
146
  def create(attributes = {})
113
147
  self << target_class.create(attributes)
@@ -115,16 +149,18 @@ module Dynamoid #:nodoc:
115
149
 
116
150
  # Create a new instance of the target class and add it directly to the association. If the create fails an exception will be raised.
117
151
  #
118
- # @param [Hash] attribute hash for the new object
119
- #
120
152
  # @return [Dynamoid::Document] the newly-created object
121
153
  #
154
+ # @private
122
155
  # @since 0.2.0
123
156
  def each(&block)
124
157
  records.each(&block)
125
158
  end
126
159
 
127
- # Destroys all members of the association and removes them from the association.
160
+ # Destroys all members of the association and removes them from the
161
+ # association.
162
+ #
163
+ # tag.posts.destroy_all
128
164
  #
129
165
  # @since 0.2.0
130
166
  def destroy_all
@@ -133,7 +169,10 @@ module Dynamoid #:nodoc:
133
169
  objs.each(&:destroy)
134
170
  end
135
171
 
136
- # Deletes all members of the association and removes them from the association.
172
+ # Deletes all members of the association and removes them from the
173
+ # association.
174
+ #
175
+ # tag.posts.delete_all
137
176
  #
138
177
  # @since 0.2.0
139
178
  def delete_all
@@ -144,10 +183,13 @@ module Dynamoid #:nodoc:
144
183
 
145
184
  # Naive association filtering.
146
185
  #
147
- # @param [Hash] A hash of attributes; each must match every returned object's attribute exactly.
186
+ # tag.posts.where(title: 'foo')
148
187
  #
149
- # @return [Dynamoid::Association] the association this method was called on (for chaining purposes)
188
+ # It loads lazily all the associated models and checks provided
189
+ # conditions. That's why only equality conditions can be specified.
150
190
  #
191
+ # @param args [Hash] A hash of attributes; each must match every returned object's attribute exactly.
192
+ # @return [Dynamoid::Association] the association this method was called on (for chaining purposes)
151
193
  # @since 0.2.0
152
194
  def where(args)
153
195
  filtered = clone
@@ -167,6 +209,7 @@ module Dynamoid #:nodoc:
167
209
 
168
210
  # Delegate methods we don't find directly to the records array.
169
211
  #
212
+ # @private
170
213
  # @since 0.2.0
171
214
  def method_missing(method, *args)
172
215
  if records.respond_to?(method)
@@ -176,10 +219,12 @@ module Dynamoid #:nodoc:
176
219
  end
177
220
  end
178
221
 
222
+ # @private
179
223
  def associate(hash_key)
180
224
  source.update_attribute(source_attribute, source_ids.merge(Array(hash_key)))
181
225
  end
182
226
 
227
+ # @private
183
228
  def disassociate(hash_key)
184
229
  source.update_attribute(source_attribute, source_ids - Array(hash_key))
185
230
  end
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Dynamoid #:nodoc:
3
+ module Dynamoid
4
4
  module Associations
5
5
  module SingleAssociation
6
6
  include Association
7
7
 
8
8
  delegate :class, to: :target
9
9
 
10
+ # @private
10
11
  def setter(object)
11
12
  if object.nil?
12
13
  delete
@@ -19,16 +20,37 @@ module Dynamoid #:nodoc:
19
20
  object
20
21
  end
21
22
 
23
+ # Delete a model from the association.
24
+ #
25
+ # post.logo.delete # => nil
26
+ #
27
+ # Saves both models immediately - a source model and a target one so any
28
+ # unsaved changes will be saved. Doesn't delete an associated model from
29
+ # DynamoDB.
22
30
  def delete
23
- target.send(target_association).disassociate(source.hash_key) if target && target_association
31
+ disassociate_source
24
32
  disassociate
25
33
  target
26
34
  end
27
35
 
36
+ # Create a new instance of the target class, persist it and associate.
37
+ #
38
+ # post.logo.create!(hight: 50, width: 90)
39
+ #
40
+ # If the creation fails an exception will be raised.
41
+ #
42
+ # @param attributes [Hash] attributes of a model to create
43
+ # @return [Dynamoid::Document] created model
28
44
  def create!(attributes = {})
29
45
  setter(target_class.create!(attributes))
30
46
  end
31
47
 
48
+ # Create a new instance of the target class, persist it and associate.
49
+ #
50
+ # post.logo.create(hight: 50, width: 90)
51
+ #
52
+ # @param attributes [Hash] attributes of a model to create
53
+ # @return [Dynamoid::Document] created model
32
54
  def create(attributes = {})
33
55
  setter(target_class.create(attributes))
34
56
  end
@@ -44,6 +66,7 @@ module Dynamoid #:nodoc:
44
66
 
45
67
  # Delegate methods we don't find directly to the target.
46
68
  #
69
+ # @private
47
70
  # @since 0.2.0
48
71
  def method_missing(method, *args)
49
72
  if target.respond_to?(method)
@@ -53,21 +76,25 @@ module Dynamoid #:nodoc:
53
76
  end
54
77
  end
55
78
 
79
+ # @private
56
80
  def nil?
57
81
  target.nil?
58
82
  end
59
83
 
84
+ # @private
60
85
  def empty?
61
86
  # This is needed to that ActiveSupport's #blank? and #present?
62
87
  # methods work as expected for SingleAssociations.
63
88
  target.nil?
64
89
  end
65
90
 
91
+ # @private
66
92
  def associate(hash_key)
67
- target.send(target_association).disassociate(source.hash_key) if target && target_association
93
+ disassociate_source
68
94
  source.update_attribute(source_attribute, Set[hash_key])
69
95
  end
70
96
 
97
+ # @private
71
98
  def disassociate(_hash_key = nil)
72
99
  source.update_attribute(source_attribute, nil)
73
100
  end
@@ -82,7 +109,7 @@ module Dynamoid #:nodoc:
82
109
  def find_target
83
110
  return if source_ids.empty?
84
111
 
85
- target_class.find(source_ids.first)
112
+ target_class.find(source_ids.first, raise_error: false)
86
113
  end
87
114
 
88
115
  def target=(object)
@@ -3,6 +3,7 @@
3
3
  module Dynamoid
4
4
  # All modules that a Document is composed of are defined in this
5
5
  # module, to keep the document class from getting too cluttered.
6
+ # @private
6
7
  module Components
7
8
  extend ActiveSupport::Concern
8
9
 
@@ -2,21 +2,21 @@
2
2
 
3
3
  require 'uri'
4
4
  require 'logger'
5
- require 'null_logger'
6
5
  require 'dynamoid/config/options'
7
6
  require 'dynamoid/config/backoff_strategies/constant_backoff'
8
7
  require 'dynamoid/config/backoff_strategies/exponential_backoff'
9
8
 
10
9
  module Dynamoid
11
10
  # Contains all the basic configuration information required for Dynamoid: both sensible defaults and required fields.
11
+ # @private
12
12
  module Config
13
13
  # @since 3.3.1
14
14
  DEFAULT_NAMESPACE = if defined?(Rails)
15
15
  klass = Rails.application.class
16
16
  app_name = Rails::VERSION::MAJOR >= 6 ? klass.module_parent_name : klass.parent_name
17
- "dynamoid_#{app_name}_#{Rails.env}".freeze
17
+ "dynamoid_#{app_name}_#{Rails.env}"
18
18
  else
19
- 'dynamoid'.freeze
19
+ 'dynamoid'
20
20
  end
21
21
 
22
22
  extend self
@@ -54,6 +54,7 @@ module Dynamoid
54
54
  constant: BackoffStrategies::ConstantBackoff,
55
55
  exponential: BackoffStrategies::ExponentialBackoff
56
56
  }
57
+ option :log_formatter, default: nil
57
58
  option :http_continue_timeout, default: nil # specify if you'd like to overwrite Aws Configure - default: 1
58
59
  option :http_idle_timeout, default: nil # - default: 5
59
60
  option :http_open_timeout, default: nil # - default: 15
@@ -78,7 +79,7 @@ module Dynamoid
78
79
  # @since 0.2.0
79
80
  def logger=(logger)
80
81
  case logger
81
- when false, nil then @logger = NullLogger.new
82
+ when false, nil then @logger = ::Logger.new(nil)
82
83
  when true then @logger = default_logger
83
84
  else
84
85
  @logger = logger if logger.respond_to?(:info)
@@ -95,6 +96,5 @@ module Dynamoid
95
96
  backoff_strategies[backoff].call
96
97
  end
97
98
  end
98
-
99
99
  end
100
100
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Dynamoid
4
4
  module Config
5
+ # @private
5
6
  module BackoffStrategies
6
7
  class ConstantBackoff
7
8
  def self.call(sec = 1)