dynamoid 3.2.0 → 3.6.0

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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +111 -1
  3. data/README.md +580 -241
  4. data/lib/dynamoid.rb +2 -0
  5. data/lib/dynamoid/adapter.rb +15 -15
  6. data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +82 -102
  7. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb +108 -0
  8. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +29 -16
  9. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +3 -2
  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 +15 -6
  14. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +15 -5
  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 +5 -3
  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 +4 -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 +65 -22
  25. data/lib/dynamoid/associations/single_association.rb +28 -1
  26. data/lib/dynamoid/components.rb +8 -3
  27. data/lib/dynamoid/config.rb +16 -3
  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 +2 -1
  32. data/lib/dynamoid/criteria/chain.rb +418 -46
  33. data/lib/dynamoid/criteria/ignored_conditions_detector.rb +3 -3
  34. data/lib/dynamoid/criteria/key_fields_detector.rb +109 -32
  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 +239 -32
  38. data/lib/dynamoid/document.rb +130 -251
  39. data/lib/dynamoid/dumping.rb +9 -0
  40. data/lib/dynamoid/dynamodb_time_zone.rb +1 -0
  41. data/lib/dynamoid/fields.rb +246 -20
  42. data/lib/dynamoid/finders.rb +69 -32
  43. data/lib/dynamoid/identity_map.rb +6 -0
  44. data/lib/dynamoid/indexes.rb +76 -17
  45. data/lib/dynamoid/loadable.rb +31 -0
  46. data/lib/dynamoid/log/formatter.rb +26 -0
  47. data/lib/dynamoid/middleware/identity_map.rb +1 -0
  48. data/lib/dynamoid/persistence.rb +592 -122
  49. data/lib/dynamoid/persistence/import.rb +73 -0
  50. data/lib/dynamoid/persistence/save.rb +64 -0
  51. data/lib/dynamoid/persistence/update_fields.rb +63 -0
  52. data/lib/dynamoid/persistence/upsert.rb +60 -0
  53. data/lib/dynamoid/primary_key_type_mapping.rb +1 -0
  54. data/lib/dynamoid/railtie.rb +1 -0
  55. data/lib/dynamoid/tasks.rb +3 -1
  56. data/lib/dynamoid/tasks/database.rb +1 -0
  57. data/lib/dynamoid/type_casting.rb +12 -2
  58. data/lib/dynamoid/undumping.rb +8 -0
  59. data/lib/dynamoid/validations.rb +2 -0
  60. data/lib/dynamoid/version.rb +1 -1
  61. metadata +49 -71
  62. data/.coveralls.yml +0 -1
  63. data/.document +0 -5
  64. data/.gitignore +0 -74
  65. data/.rspec +0 -2
  66. data/.rubocop.yml +0 -71
  67. data/.rubocop_todo.yml +0 -55
  68. data/.travis.yml +0 -41
  69. data/Appraisals +0 -28
  70. data/Gemfile +0 -8
  71. data/Rakefile +0 -46
  72. data/Vagrantfile +0 -29
  73. data/docker-compose.yml +0 -7
  74. data/dynamoid.gemspec +0 -57
  75. data/gemfiles/rails_4_2.gemfile +0 -11
  76. data/gemfiles/rails_5_0.gemfile +0 -10
  77. data/gemfiles/rails_5_1.gemfile +0 -10
  78. data/gemfiles/rails_5_2.gemfile +0 -10
@@ -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
 
@@ -116,7 +118,7 @@ module Dynamoid #:nodoc:
116
118
 
117
119
  # 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
120
  #
119
- # @param [Hash] attribute hash for the new object
121
+ # @param attributes [Hash] attribute values for the new object
120
122
  #
121
123
  # @return [Dynamoid::Document] the newly-created object
122
124
  #
@@ -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,13 @@ 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
28
  Array(target_class.find(source_ids.to_a))
26
29
  end
27
30
 
31
+ # @private
28
32
  def records
29
33
  if query.empty?
30
34
  target
@@ -43,13 +47,20 @@ module Dynamoid #:nodoc:
43
47
  records.include?(object)
44
48
  end
45
49
 
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.
50
+ # Delete an object or array of objects from the association.
51
+ #
52
+ # tag.posts.delete(post)
53
+ # tag.posts.delete([post1, post2, post3])
48
54
  #
49
- # @param [Dynamoid::Document] object the object (or array of objects) to remove from the association
55
+ # This removes their records from the association field on the source,
56
+ # and attempts to remove the source from the target association if it is
57
+ # detected to exist.
50
58
  #
51
- # @return [Dynamoid::Document] the deleted object
59
+ # It saves both models immediately - the source model and the target one
60
+ # so any not saved changes will be saved as well.
52
61
  #
62
+ # @param object [Dynamoid::Document|Array] model (or array of models) to remove from the association
63
+ # @return [Dynamoid::Document|Array] the deleted model
53
64
  # @since 0.2.0
54
65
  def delete(object)
55
66
  disassociate(Array(object).collect(&:hash_key))
@@ -59,13 +70,19 @@ module Dynamoid #:nodoc:
59
70
  object
60
71
  end
61
72
 
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.
73
+ # Add an object or array of objects to an association.
64
74
  #
65
- # @param [Dynamoid::Document] object the object (or array of objects) to add to the association
75
+ # tag.posts << post
76
+ # tag.posts << [post1, post2, post3]
77
+ #
78
+ # This preserves the current records in the association (if any) and adds
79
+ # the object to the target association if it is detected to exist.
66
80
  #
67
- # @return [Dynamoid::Document] the added object
81
+ # It saves both models immediately - the source model and the target one
82
+ # so any not saved changes will be saved as well.
68
83
  #
84
+ # @param object [Dynamoid::Document|Array] model (or array of models) to add to the association
85
+ # @return [Dynamoid::Document] the added model
69
86
  # @since 0.2.0
70
87
  def <<(object)
71
88
  associate(Array(object).collect(&:hash_key))
@@ -82,8 +99,9 @@ module Dynamoid #:nodoc:
82
99
  #
83
100
  # @param [Dynamoid::Document] object the object (or array of objects) to add to the association
84
101
  #
85
- # @return [Dynamoid::Document] the added object
102
+ # @return [Dynamoid::Document|Array] the added object
86
103
  #
104
+ # @private
87
105
  # @since 0.2.0
88
106
  def setter(object)
89
107
  target.each { |o| delete(o) }
@@ -91,23 +109,37 @@ module Dynamoid #:nodoc:
91
109
  object
92
110
  end
93
111
 
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.
112
+ # Create a new instance of the target class, persist it and add directly
113
+ # to the association.
95
114
  #
96
- # @param [Hash] attribute hash for the new object
115
+ # tag.posts.create!(title: 'foo')
97
116
  #
98
- # @return [Dynamoid::Document] the newly-created object
117
+ # Several models can be created at once when an array of attributes
118
+ # specified:
119
+ #
120
+ # tag.posts.create!([{ title: 'foo' }, {title: 'bar'} ])
121
+ #
122
+ # If the creation fails an exception will be raised.
99
123
  #
124
+ # @param attributes [Hash] attribute values for the new object
125
+ # @return [Dynamoid::Document|Array] the newly-created object
100
126
  # @since 0.2.0
101
127
  def create!(attributes = {})
102
128
  self << target_class.create!(attributes)
103
129
  end
104
130
 
105
- # Create a new instance of the target class and add it directly to the association.
131
+ # Create a new instance of the target class, persist it and add directly
132
+ # to the association.
106
133
  #
107
- # @param [Hash] attribute hash for the new object
134
+ # tag.posts.create(title: 'foo')
108
135
  #
109
- # @return [Dynamoid::Document] the newly-created object
136
+ # Several models can be created at once when an array of attributes
137
+ # specified:
138
+ #
139
+ # tag.posts.create([{ title: 'foo' }, {title: 'bar'} ])
110
140
  #
141
+ # @param attributes [Hash] attribute values for the new object
142
+ # @return [Dynamoid::Document|Array] the newly-created object
111
143
  # @since 0.2.0
112
144
  def create(attributes = {})
113
145
  self << target_class.create(attributes)
@@ -115,16 +147,18 @@ module Dynamoid #:nodoc:
115
147
 
116
148
  # 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
149
  #
118
- # @param [Hash] attribute hash for the new object
119
- #
120
150
  # @return [Dynamoid::Document] the newly-created object
121
151
  #
152
+ # @private
122
153
  # @since 0.2.0
123
154
  def each(&block)
124
155
  records.each(&block)
125
156
  end
126
157
 
127
- # Destroys all members of the association and removes them from the association.
158
+ # Destroys all members of the association and removes them from the
159
+ # association.
160
+ #
161
+ # tag.posts.destroy_all
128
162
  #
129
163
  # @since 0.2.0
130
164
  def destroy_all
@@ -133,7 +167,10 @@ module Dynamoid #:nodoc:
133
167
  objs.each(&:destroy)
134
168
  end
135
169
 
136
- # Deletes all members of the association and removes them from the association.
170
+ # Deletes all members of the association and removes them from the
171
+ # association.
172
+ #
173
+ # tag.posts.delete_all
137
174
  #
138
175
  # @since 0.2.0
139
176
  def delete_all
@@ -144,10 +181,13 @@ module Dynamoid #:nodoc:
144
181
 
145
182
  # Naive association filtering.
146
183
  #
147
- # @param [Hash] A hash of attributes; each must match every returned object's attribute exactly.
184
+ # tag.posts.where(title: 'foo')
148
185
  #
149
- # @return [Dynamoid::Association] the association this method was called on (for chaining purposes)
186
+ # It loads lazily all the associated models and checks provided
187
+ # conditions. That's why only equality conditions can be specified.
150
188
  #
189
+ # @param args [Hash] A hash of attributes; each must match every returned object's attribute exactly.
190
+ # @return [Dynamoid::Association] the association this method was called on (for chaining purposes)
151
191
  # @since 0.2.0
152
192
  def where(args)
153
193
  filtered = clone
@@ -167,6 +207,7 @@ module Dynamoid #:nodoc:
167
207
 
168
208
  # Delegate methods we don't find directly to the records array.
169
209
  #
210
+ # @private
170
211
  # @since 0.2.0
171
212
  def method_missing(method, *args)
172
213
  if records.respond_to?(method)
@@ -176,10 +217,12 @@ module Dynamoid #:nodoc:
176
217
  end
177
218
  end
178
219
 
220
+ # @private
179
221
  def associate(hash_key)
180
222
  source.update_attribute(source_attribute, source_ids.merge(Array(hash_key)))
181
223
  end
182
224
 
225
+ # @private
183
226
  def disassociate(hash_key)
184
227
  source.update_attribute(source_attribute, source_ids - Array(hash_key))
185
228
  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
31
  target.send(target_association).disassociate(source.hash_key) if target && target_association
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
93
  target.send(target_association).disassociate(source.hash_key) if target && target_association
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
@@ -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
 
@@ -14,24 +15,28 @@ module Dynamoid
14
15
 
15
16
  before_create :set_created_at
16
17
  before_save :set_updated_at
18
+ before_save :set_expires_field
17
19
  after_initialize :set_inheritance_field
18
20
  end
19
21
 
20
- include ActiveModel::AttributeMethods
22
+ include ActiveModel::AttributeMethods # Actually it will be inclided in Dirty module again
21
23
  include ActiveModel::Conversion
22
24
  include ActiveModel::MassAssignmentSecurity if defined?(ActiveModel::MassAssignmentSecurity)
23
25
  include ActiveModel::Naming
24
26
  include ActiveModel::Observing if defined?(ActiveModel::Observing)
25
27
  include ActiveModel::Serializers::JSON
26
28
  include ActiveModel::Serializers::Xml if defined?(ActiveModel::Serializers::Xml)
29
+ include Dynamoid::Persistence
30
+ include Dynamoid::Loadable
31
+ # Dirty module should be included after Persistence and Loadable
32
+ # because it overrides some methods declared in these modules
33
+ include Dynamoid::Dirty
27
34
  include Dynamoid::Fields
28
35
  include Dynamoid::Indexes
29
- include Dynamoid::Persistence
30
36
  include Dynamoid::Finders
31
37
  include Dynamoid::Associations
32
38
  include Dynamoid::Criteria
33
39
  include Dynamoid::Validations
34
40
  include Dynamoid::IdentityMap
35
- include Dynamoid::Dirty
36
41
  end
37
42
  end
@@ -2,14 +2,23 @@
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
+ # @since 3.3.1
14
+ DEFAULT_NAMESPACE = if defined?(Rails)
15
+ klass = Rails.application.class
16
+ app_name = Rails::VERSION::MAJOR >= 6 ? klass.module_parent_name : klass.parent_name
17
+ "dynamoid_#{app_name}_#{Rails.env}"
18
+ else
19
+ 'dynamoid'
20
+ end
21
+
13
22
  extend self
14
23
 
15
24
  extend Options
@@ -17,11 +26,13 @@ module Dynamoid
17
26
 
18
27
  # All the default options.
19
28
  option :adapter, default: 'aws_sdk_v3'
20
- option :namespace, default: defined?(Rails) ? "dynamoid_#{Rails.application.class.parent_name}_#{Rails.env}" : 'dynamoid'
29
+ option :namespace, default: DEFAULT_NAMESPACE
21
30
  option :access_key, default: nil
22
31
  option :secret_key, default: nil
32
+ option :credentials, default: nil
23
33
  option :region, default: nil
24
34
  option :batch_size, default: 100
35
+ option :capacity_mode, default: nil
25
36
  option :read_capacity, default: 100
26
37
  option :write_capacity, default: 20
27
38
  option :warn_on_scan, default: true
@@ -31,6 +42,7 @@ module Dynamoid
31
42
  option :sync_retry_max_times, default: 60 # a bit over 2 minutes
32
43
  option :sync_retry_wait_seconds, default: 2
33
44
  option :convert_big_decimal, default: false
45
+ option :store_attribute_with_nil_value, default: false # keep or ignore attribute with nil value at saving
34
46
  option :models_dir, default: './app/models' # perhaps you keep your dynamoid models in a different directory?
35
47
  option :application_timezone, default: :utc # available values - :utc, :local, time zone name like "Hawaii"
36
48
  option :dynamodb_timezone, default: :utc # available values - :utc, :local, time zone name like "Hawaii"
@@ -42,6 +54,7 @@ module Dynamoid
42
54
  constant: BackoffStrategies::ConstantBackoff,
43
55
  exponential: BackoffStrategies::ExponentialBackoff
44
56
  }
57
+ option :log_formatter, default: nil
45
58
  option :http_continue_timeout, default: nil # specify if you'd like to overwrite Aws Configure - default: 1
46
59
  option :http_idle_timeout, default: nil # - default: 5
47
60
  option :http_open_timeout, default: nil # - default: 15
@@ -66,7 +79,7 @@ module Dynamoid
66
79
  # @since 0.2.0
67
80
  def logger=(logger)
68
81
  case logger
69
- when false, nil then @logger = NullLogger.new
82
+ when false, nil then @logger = ::Logger.new(nil)
70
83
  when true then @logger = default_logger
71
84
  else
72
85
  @logger = logger if logger.respond_to?(:info)