dynamoid 3.2.0 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
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)