dynamoid 2.2.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +53 -0
  3. data/.rubocop_todo.yml +55 -0
  4. data/.travis.yml +5 -27
  5. data/Appraisals +17 -15
  6. data/CHANGELOG.md +26 -3
  7. data/Gemfile +4 -2
  8. data/README.md +95 -77
  9. data/Rakefile +17 -17
  10. data/Vagrantfile +5 -3
  11. data/dynamoid.gemspec +39 -45
  12. data/gemfiles/rails_4_2.gemfile +7 -5
  13. data/gemfiles/rails_5_0.gemfile +6 -4
  14. data/gemfiles/rails_5_1.gemfile +6 -4
  15. data/gemfiles/rails_5_2.gemfile +6 -4
  16. data/lib/dynamoid.rb +11 -4
  17. data/lib/dynamoid/adapter.rb +21 -27
  18. data/lib/dynamoid/adapter_plugin/{aws_sdk_v2.rb → aws_sdk_v3.rb} +118 -113
  19. data/lib/dynamoid/application_time_zone.rb +27 -0
  20. data/lib/dynamoid/associations.rb +3 -6
  21. data/lib/dynamoid/associations/association.rb +3 -6
  22. data/lib/dynamoid/associations/belongs_to.rb +4 -5
  23. data/lib/dynamoid/associations/has_and_belongs_to_many.rb +2 -3
  24. data/lib/dynamoid/associations/has_many.rb +2 -3
  25. data/lib/dynamoid/associations/has_one.rb +2 -3
  26. data/lib/dynamoid/associations/many_association.rb +8 -9
  27. data/lib/dynamoid/associations/single_association.rb +3 -3
  28. data/lib/dynamoid/components.rb +2 -2
  29. data/lib/dynamoid/config.rb +9 -5
  30. data/lib/dynamoid/config/backoff_strategies/constant_backoff.rb +4 -2
  31. data/lib/dynamoid/config/backoff_strategies/exponential_backoff.rb +3 -1
  32. data/lib/dynamoid/config/options.rb +4 -4
  33. data/lib/dynamoid/criteria.rb +3 -5
  34. data/lib/dynamoid/criteria/chain.rb +42 -49
  35. data/lib/dynamoid/dirty.rb +5 -4
  36. data/lib/dynamoid/document.rb +142 -36
  37. data/lib/dynamoid/dumping.rb +167 -0
  38. data/lib/dynamoid/dynamodb_time_zone.rb +16 -0
  39. data/lib/dynamoid/errors.rb +7 -6
  40. data/lib/dynamoid/fields.rb +24 -23
  41. data/lib/dynamoid/finders.rb +101 -59
  42. data/lib/dynamoid/identity_map.rb +5 -11
  43. data/lib/dynamoid/indexes.rb +45 -46
  44. data/lib/dynamoid/middleware/identity_map.rb +2 -0
  45. data/lib/dynamoid/persistence.rb +67 -307
  46. data/lib/dynamoid/primary_key_type_mapping.rb +34 -0
  47. data/lib/dynamoid/railtie.rb +3 -1
  48. data/lib/dynamoid/tasks/database.rake +11 -11
  49. data/lib/dynamoid/tasks/database.rb +4 -3
  50. data/lib/dynamoid/type_casting.rb +193 -0
  51. data/lib/dynamoid/undumping.rb +188 -0
  52. data/lib/dynamoid/validations.rb +4 -7
  53. data/lib/dynamoid/version.rb +3 -1
  54. metadata +59 -53
  55. data/gemfiles/rails_4_0.gemfile +0 -9
  56. data/gemfiles/rails_4_1.gemfile +0 -9
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dynamoid
4
+ module ApplicationTimeZone
5
+ def self.at(value)
6
+ case Dynamoid::Config.application_timezone
7
+ when :utc
8
+ ActiveSupport::TimeZone['UTC'].at(value).to_datetime
9
+ when :local
10
+ Time.at(value).to_datetime
11
+ when String
12
+ ActiveSupport::TimeZone[Dynamoid::Config.application_timezone].at(value).to_datetime
13
+ end
14
+ end
15
+
16
+ def self.utc_offset
17
+ case Dynamoid::Config.application_timezone
18
+ when :utc
19
+ 0
20
+ when :local
21
+ Time.now.utc_offset
22
+ when String
23
+ ActiveSupport::TimeZone[Dynamoid::Config.application_timezone].now.utc_offset
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,4 +1,5 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  require 'dynamoid/associations/association'
3
4
  require 'dynamoid/associations/single_association'
4
5
  require 'dynamoid/associations/many_association'
@@ -8,7 +9,6 @@ require 'dynamoid/associations/has_one'
8
9
  require 'dynamoid/associations/has_and_belongs_to_many'
9
10
 
10
11
  module Dynamoid
11
-
12
12
  # Connects models together through the magic of associations. We enjoy four different kinds of associations presently:
13
13
  # * belongs_to
14
14
  # * has_and_belongs_to_many
@@ -25,7 +25,6 @@ module Dynamoid
25
25
  end
26
26
 
27
27
  module ClassMethods
28
-
29
28
  # create a has_many association for this document.
30
29
  #
31
30
  # @param [Symbol] name the name of the association
@@ -97,7 +96,7 @@ module Dynamoid
97
96
 
98
97
  field field_name.to_sym, field_type
99
98
 
100
- self.associations[name] = options.merge(type: type)
99
+ associations[name] = options.merge(type: type)
101
100
 
102
101
  define_method(name) do
103
102
  @associations[:"#{name}_ids"] ||= Dynamoid::Associations.const_get(type.to_s.camelcase).new(self, name, options)
@@ -109,7 +108,5 @@ module Dynamoid
109
108
  end
110
109
  end
111
110
  end
112
-
113
111
  end
114
-
115
112
  end
@@ -1,6 +1,6 @@
1
- # encoding: utf-8
2
- module Dynamoid #:nodoc:
1
+ # frozen_string_literal: true
3
2
 
3
+ module Dynamoid #:nodoc:
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.
@@ -32,8 +32,7 @@ module Dynamoid #:nodoc:
32
32
  @loaded
33
33
  end
34
34
 
35
- def find_target
36
- end
35
+ def find_target; end
37
36
 
38
37
  def target
39
38
  unless loaded?
@@ -125,8 +124,6 @@ module Dynamoid #:nodoc:
125
124
  def build(attributes = {})
126
125
  target_class.build(attributes)
127
126
  end
128
-
129
127
  end
130
128
  end
131
-
132
129
  end
@@ -1,6 +1,6 @@
1
- # encoding: utf-8
2
- module Dynamoid #:nodoc:
1
+ # frozen_string_literal: true
3
2
 
3
+ module Dynamoid #:nodoc:
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
@@ -40,15 +40,14 @@ module Dynamoid #:nodoc:
40
40
  def target_association
41
41
  has_many_key_name = options[:inverse_of] || source.class.to_s.underscore.pluralize.to_sym
42
42
  has_one_key_name = options[:inverse_of] || source.class.to_s.underscore.to_sym
43
- if !target_class.associations[has_many_key_name].nil?
43
+ unless target_class.associations[has_many_key_name].nil?
44
44
  return has_many_key_name if target_class.associations[has_many_key_name][:type] == :has_many
45
45
  end
46
46
 
47
- if !target_class.associations[has_one_key_name].nil?
47
+ unless target_class.associations[has_one_key_name].nil?
48
48
  return has_one_key_name if target_class.associations[has_one_key_name][:type] == :has_one
49
49
  end
50
50
  end
51
51
  end
52
52
  end
53
-
54
53
  end
@@ -1,6 +1,6 @@
1
- # encoding: utf-8
2
- module Dynamoid #:nodoc:
1
+ # frozen_string_literal: true
3
2
 
3
+ module Dynamoid #:nodoc:
4
4
  # The has and belongs to many association.
5
5
  module Associations
6
6
  class HasAndBelongsToMany
@@ -20,5 +20,4 @@ module Dynamoid #:nodoc:
20
20
  end
21
21
  end
22
22
  end
23
-
24
23
  end
@@ -1,6 +1,6 @@
1
- # encoding: utf-8
2
- module Dynamoid #:nodoc:
1
+ # frozen_string_literal: true
3
2
 
3
+ module Dynamoid #:nodoc:
4
4
  # The has_many association.
5
5
  module Associations
6
6
  class HasMany
@@ -20,5 +20,4 @@ module Dynamoid #:nodoc:
20
20
  end
21
21
  end
22
22
  end
23
-
24
23
  end
@@ -1,6 +1,6 @@
1
- # encoding: utf-8
2
- module Dynamoid #:nodoc:
1
+ # frozen_string_literal: true
3
2
 
3
+ module Dynamoid #:nodoc:
4
4
  # The HasOne association.
5
5
  module Associations
6
6
  class HasOne
@@ -21,5 +21,4 @@ module Dynamoid #:nodoc:
21
21
  end
22
22
  end
23
23
  end
24
-
25
24
  end
@@ -1,6 +1,6 @@
1
- # encoding: utf-8
2
- module Dynamoid #:nodoc:
1
+ # frozen_string_literal: true
3
2
 
3
+ module Dynamoid #:nodoc:
4
4
  module Associations
5
5
  module ManyAssociation
6
6
  include Association
@@ -34,9 +34,9 @@ module Dynamoid #:nodoc:
34
34
  end
35
35
 
36
36
  # Alias convenience methods for the associations.
37
- alias :all :records
38
- alias :count :size
39
- alias :nil? :empty?
37
+ alias all records
38
+ alias count size
39
+ alias nil? empty?
40
40
 
41
41
  # Delegate include? to the records.
42
42
  def include?(object)
@@ -86,8 +86,8 @@ module Dynamoid #:nodoc:
86
86
  #
87
87
  # @since 0.2.0
88
88
  def setter(object)
89
- target.each {|o| delete(o)}
90
- self << (object)
89
+ target.each { |o| delete(o) }
90
+ self << object
91
91
  object
92
92
  end
93
93
 
@@ -152,7 +152,7 @@ module Dynamoid #:nodoc:
152
152
  def where(args)
153
153
  filtered = clone
154
154
  filtered.query = query.clone
155
- args.each {|k, v| filtered.query[k] = v}
155
+ args.each { |k, v| filtered.query[k] = v }
156
156
  filtered
157
157
  end
158
158
 
@@ -200,7 +200,6 @@ module Dynamoid #:nodoc:
200
200
  end
201
201
  end
202
202
  end
203
-
204
203
  end
205
204
  end
206
205
  end
@@ -1,6 +1,6 @@
1
- # encoding: utf-8
2
- module Dynamoid #:nodoc:
1
+ # frozen_string_literal: true
3
2
 
3
+ module Dynamoid #:nodoc:
4
4
  module Associations
5
5
  module SingleAssociation
6
6
  include Association
@@ -68,7 +68,7 @@ module Dynamoid #:nodoc:
68
68
  source.update_attribute(source_attribute, Set[hash_key])
69
69
  end
70
70
 
71
- def disassociate(hash_key=nil)
71
+ def disassociate(_hash_key = nil)
72
72
  source.update_attribute(source_attribute, nil)
73
73
  end
74
74
 
@@ -1,6 +1,6 @@
1
- # encoding: utf-8
2
- module Dynamoid
1
+ # frozen_string_literal: true
3
2
 
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
6
  module Components
@@ -1,19 +1,21 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  require 'uri'
4
+ require 'logger'
3
5
  require 'dynamoid/config/options'
4
6
  require 'dynamoid/config/backoff_strategies/constant_backoff'
5
7
  require 'dynamoid/config/backoff_strategies/exponential_backoff'
6
8
 
7
9
  module Dynamoid
8
-
9
10
  # Contains all the basic configuration information required for Dynamoid: both sensible defaults and required fields.
10
11
  module Config
11
12
  extend self
13
+
12
14
  extend Options
13
15
  include ActiveModel::Observing if defined?(ActiveModel::Observing)
14
16
 
15
17
  # All the default options.
16
- option :adapter, default: 'aws_sdk_v2'
18
+ option :adapter, default: 'aws_sdk_v3'
17
19
  option :namespace, default: defined?(Rails) ? "dynamoid_#{Rails.application.class.parent_name}_#{Rails.env}" : 'dynamoid'
18
20
  option :access_key, default: nil
19
21
  option :secret_key, default: nil
@@ -29,9 +31,11 @@ module Dynamoid
29
31
  option :sync_retry_wait_seconds, default: 2
30
32
  option :convert_big_decimal, default: false
31
33
  option :models_dir, default: './app/models' # perhaps you keep your dynamoid models in a different directory?
32
- option :application_timezone, default: :local # available values - :utc, :local, time zone names
34
+ option :application_timezone, default: :utc # available values - :utc, :local, time zone name like "Hawaii"
35
+ option :dynamodb_timezone, default: :utc # available values - :utc, :local, time zone name like "Hawaii"
33
36
  option :store_datetime_as_string, default: false # store Time fields in ISO 8601 string format
34
37
  option :store_date_as_string, default: false # store Date fields in ISO 8601 string format
38
+ option :store_boolean_as_native, default: true
35
39
  option :backoff, default: nil # callable object to handle exceeding of table throughput limit
36
40
  option :backoff_strategies, default: {
37
41
  constant: BackoffStrategies::ConstantBackoff,
@@ -57,7 +61,7 @@ module Dynamoid
57
61
  # @since 0.2.0
58
62
  def logger=(logger)
59
63
  case logger
60
- when false, nil then @logger = nil
64
+ when false, nil then @logger = Logger.new('/dev/null')
61
65
  when true then @logger = default_logger
62
66
  else
63
67
  @logger = logger if logger.respond_to?(:info)
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dynamoid
2
4
  module Config
3
5
  module BackoffStrategies
4
6
  class ConstantBackoff
5
- def self.call(n = 1)
6
- -> { sleep n }
7
+ def self.call(sec = 1)
8
+ -> { sleep sec }
7
9
  end
8
10
  end
9
11
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dynamoid
2
4
  module Config
3
5
  module BackoffStrategies
@@ -13,7 +15,7 @@ module Dynamoid
13
15
 
14
16
  lambda do
15
17
  power = [times - 1, ceiling - 1].min
16
- backoff = base_backoff * (2 ** power)
18
+ backoff = base_backoff * (2**power)
17
19
  sleep backoff
18
20
 
19
21
  times += 1
@@ -1,10 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Shamelessly stolen from Mongoid!
2
- module Dynamoid #:nodoc
4
+ module Dynamoid
3
5
  module Config
4
-
5
6
  # Encapsulates logic for setting options.
6
7
  module Options
7
-
8
8
  # Get the defaults or initialize a new empty hash.
9
9
  #
10
10
  # @example Get the defaults.
@@ -31,7 +31,7 @@ module Dynamoid #:nodoc
31
31
  def option(name, options = {})
32
32
  defaults[name] = settings[name] = options[:default]
33
33
 
34
- class_eval <<-RUBY
34
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
35
35
  def #{name}
36
36
  settings[#{name.inspect}]
37
37
  end
@@ -1,15 +1,14 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  require 'dynamoid/criteria/chain'
3
4
 
4
5
  module Dynamoid
5
-
6
6
  # Allows classes to be queried by where, all, first, and each and return criteria chains.
7
7
  module Criteria
8
8
  extend ActiveSupport::Concern
9
9
 
10
10
  module ClassMethods
11
-
12
- [:where, :all, :first, :last, :each, :record_limit, :scan_limit, :batch, :start, :scan_index_forward].each do |meth|
11
+ %i[where all first last each record_limit scan_limit batch start scan_index_forward].each do |meth|
13
12
  # Return a criteria chain in response to a method that will begin or end a chain. For more information,
14
13
  # see Dynamoid::Criteria::Chain.
15
14
  #
@@ -25,5 +24,4 @@ module Dynamoid
25
24
  end
26
25
  end
27
26
  end
28
-
29
27
  end
@@ -1,12 +1,10 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  module Dynamoid #:nodoc:
3
4
  module Criteria
4
-
5
5
  # The criteria chain is equivalent to an ActiveRecord relation (and realistically I should change the name from
6
6
  # chain to relation). It is a chainable object that builds up a query and eventually executes it by a Query or Scan.
7
7
  class Chain
8
- # TODO: Should we transform any other types of query values?
9
- TYPES_TO_DUMP_FOR_QUERY = [:string, :integer, :boolean, :serialized]
10
8
  attr_accessor :query, :source, :values, :consistent_read
11
9
  attr_reader :hash_key, :range_key, :index_name
12
10
  include Enumerable
@@ -37,14 +35,7 @@ module Dynamoid #:nodoc:
37
35
  #
38
36
  # @since 0.2.0
39
37
  def where(args)
40
- args.each do |k, v|
41
- sym = k.to_sym
42
- query[sym] = if (field_options = source.attributes[sym]) && (type = field_options[:type]) && TYPES_TO_DUMP_FOR_QUERY.include?(type)
43
- source.dump_field(v, field_options)
44
- else
45
- v
46
- end
47
- end
38
+ query.update(args.dup.symbolize_keys)
48
39
  self
49
40
  end
50
41
 
@@ -79,18 +70,16 @@ module Dynamoid #:nodoc:
79
70
  ids << hash[source.hash_key.to_sym]
80
71
  ranges << hash[source.range_key.to_sym] if source.range_key
81
72
  end
82
-
83
- Dynamoid.adapter.delete(source.table_name, ids, range_key: ranges.presence)
84
73
  else
85
74
  Dynamoid.adapter.scan(source.table_name, scan_query, scan_opts).collect do |hash|
86
75
  ids << hash[source.hash_key.to_sym]
87
76
  ranges << hash[source.range_key.to_sym] if source.range_key
88
77
  end
89
-
90
- Dynamoid.adapter.delete(source.table_name, ids, range_key: ranges.presence)
91
78
  end
79
+
80
+ Dynamoid.adapter.delete(source.table_name, ids, range_key: ranges.presence)
92
81
  end
93
- alias_method :destroy_all, :delete_all
82
+ alias destroy_all delete_all
94
83
 
95
84
  # The record limit is the limit of evaluated records returned by the
96
85
  # query or scan.
@@ -159,12 +148,12 @@ module Dynamoid #:nodoc:
159
148
  #
160
149
  # @since 0.2.0
161
150
  def records_via_scan
162
- if Dynamoid::Config.warn_on_scan
151
+ if Dynamoid::Config.warn_on_scan && query.present?
163
152
  Dynamoid.logger.warn 'Queries without an index are forced to use scan and are generally much slower than indexed queries!'
164
153
  Dynamoid.logger.warn "You can index this query by adding index declaration to #{source.to_s.downcase}.rb:"
165
154
  Dynamoid.logger.warn "* global_secondary_index hash_key: 'some-name', range_key: 'some-another-name'"
166
155
  Dynamoid.logger.warn "* local_secondary_indexe range_key: 'some-name'"
167
- Dynamoid.logger.warn "Not indexed attributes: #{query.keys.sort.collect{|name| ":#{name}"}.join(', ')}"
156
+ Dynamoid.logger.warn "Not indexed attributes: #{query.keys.sort.collect { |name| ":#{name}" }.join(', ')}"
168
157
  end
169
158
 
170
159
  Enumerator.new do |yielder|
@@ -199,29 +188,29 @@ module Dynamoid #:nodoc:
199
188
  val = type_cast_condition_parameter(name, query[key])
200
189
 
201
190
  hash = case operation
202
- when 'ne'
203
- { ne: val }
204
- when 'gt'
205
- { gt: val }
206
- when 'lt'
207
- { lt: val }
208
- when 'gte'
209
- { gte: val }
210
- when 'lte'
211
- { lte: val }
212
- when 'between'
213
- { between: val }
214
- when 'begins_with'
215
- { begins_with: val }
216
- when 'in'
217
- { in: val }
218
- when 'contains'
219
- { contains: val }
220
- when 'not_contains'
221
- { not_contains: val }
222
- end
223
-
224
- return { name.to_sym => hash }
191
+ when 'ne'
192
+ { ne: val }
193
+ when 'gt'
194
+ { gt: val }
195
+ when 'lt'
196
+ { lt: val }
197
+ when 'gte'
198
+ { gte: val }
199
+ when 'lte'
200
+ { lte: val }
201
+ when 'between'
202
+ { between: val }
203
+ when 'begins_with'
204
+ { begins_with: val }
205
+ when 'in'
206
+ { in: val }
207
+ when 'contains'
208
+ { contains: val }
209
+ when 'not_contains'
210
+ { not_contains: val }
211
+ end
212
+
213
+ { name.to_sym => hash }
225
214
  end
226
215
 
227
216
  def consistent_opts
@@ -255,7 +244,7 @@ module Dynamoid #:nodoc:
255
244
  opts.update(field_hash(key))
256
245
  else
257
246
  value = type_cast_condition_parameter(key, query[key])
258
- opts[key] = {eq: value}
247
+ opts[key] = { eq: value }
259
248
  end
260
249
  end
261
250
 
@@ -263,12 +252,18 @@ module Dynamoid #:nodoc:
263
252
  end
264
253
 
265
254
  def type_cast_condition_parameter(key, value)
266
- return value if [:array, :set].include?(source.attributes[key.to_sym][:type])
255
+ return value if %i[array set].include?(source.attributes[key.to_sym][:type])
267
256
 
268
257
  if !value.respond_to?(:to_ary)
269
- source.dump_field(value, source.attributes[key.to_sym])
258
+ options = source.attributes[key.to_sym]
259
+ value_casted = TypeCasting.cast_field(value, options)
260
+ Dumping.dump_field(value_casted, options)
270
261
  else
271
- value.to_ary.map { |el| source.dump_field(el, source.attributes[key.to_sym]) }
262
+ value.to_ary.map do |el|
263
+ options = source.attributes[key.to_sym]
264
+ value_casted = TypeCasting.cast_field(el, options)
265
+ Dumping.dump_field(value_casted, options)
266
+ end
272
267
  end
273
268
  end
274
269
 
@@ -354,7 +349,7 @@ module Dynamoid #:nodoc:
354
349
  opts.update(field_hash(key))
355
350
  else
356
351
  value = type_cast_condition_parameter(key, query[key])
357
- opts[key] = {eq: value}
352
+ opts[key] = { eq: value }
358
353
  end
359
354
  end
360
355
  end
@@ -370,7 +365,5 @@ module Dynamoid #:nodoc:
370
365
  opts
371
366
  end
372
367
  end
373
-
374
368
  end
375
-
376
369
  end