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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 193003bce5df92dae1a2ad3a4f94345bc610193b0da7692eb6f722231e1becae
4
- data.tar.gz: 36fb0bdea8126c6fd9af32343ad5d0ebe87ca92d01caab17fb9fe87aa74d07a6
3
+ metadata.gz: 26990dd042447eeba355601d0c7eb3657bcc482e80d00c1da5602d2f82765504
4
+ data.tar.gz: 76e1778155fb8a7a1d3135d4886d9e7caf1a07b72263fc38d9162ae946d5dab1
5
5
  SHA512:
6
- metadata.gz: 3c53982905e1e487db42f0497f682e4a62b33d8adf1b1b7079e98f5d1635e338396aa62bef467483406ab558d4ad66a2a285ac72c90d5f278530026fbcb96fa1
7
- data.tar.gz: 2d8e83e5ea4ba495ac6ec37c7178b8f77db4d0b2e33210c698ef00f8f92f1762672df3ca64c70e053f173d040e2abafe01bdd0c850499f65523fb9a50c2f0a7b
6
+ metadata.gz: 2aadf93639577566ed5dd9dadff596316f3d22384f5d7dabd0e06aa49992d3a58e824a8c150002c3a39d6e42f7b2b17c4f383bda2c145f4d05e1d56bf21a5e54
7
+ data.tar.gz: 14801f95c8cd1a241d2b1522b05a1e7af307822cef50e4515502f641027ade16aaf750a099801a2f2e4e379b15a4cd96e78f84754278db37d2e6fcdaab57b242
@@ -1,10 +1,120 @@
1
1
  # HEAD
2
2
 
3
- ## Breaking
3
+ ## Features
4
+
5
+ ## Improvements
6
+
7
+ ## Fixes
8
+
9
+
10
+ ---
11
+
12
+
13
+
14
+ # 3.6.0 / 2020-07-13
15
+
16
+
17
+ ## Features
18
+
19
+ * [#458](https://github.com/Dynamoid/dynamoid/pull/458) Added `binary` field type
20
+ * [#459](https://github.com/Dynamoid/dynamoid/pull/459) Added `log_formatter` config option and changed default logging format
21
+
22
+ ## Improvements
23
+
24
+ * [#423](https://github.com/Dynamoid/dynamoid/pull/423) Added warning when generated for a field methods override existing ones
25
+ * [#429](https://github.com/Dynamoid/dynamoid/pull/429) Added `raise_error` option for `find` method
26
+ * [#440](https://github.com/Dynamoid/dynamoid/pull/440) Optimized performance of `first` method when there are only conditions on key attribute in a query (@mrkamel)
27
+ * [#445](https://github.com/Dynamoid/dynamoid/pull/445) Support `limit` parameter in `first` method (@mrkamel)
28
+ * [#450](https://github.com/Dynamoid/dynamoid/pull/450) Got rid of `null-logger` gem to make Dynamoid dependencies license suitable for commercial use (@yakjuly)
29
+ * [#454](https://github.com/Dynamoid/dynamoid/pull/454) Added block argument to `create`/`create!` methods
30
+ * [#456](https://github.com/Dynamoid/dynamoid/pull/456) Detect when `find` method requires a range key argument and raise `Dynamoid::Errors::MissingRangeKey` exception if it's missing
31
+ * YARD documentation:
32
+ * added missing documentation so now all the public methods are documented
33
+ * hid all the private methods and classes
34
+
35
+ ## Fixes
36
+
37
+ * [#425](https://github.com/Dynamoid/dynamoid/pull/425) Fixed typos in the README.md file (@omarsotillo)
38
+ * [#432](https://github.com/Dynamoid/dynamoid/pull/432) Support tables that use "hash_key" as their partition key name (@remomueller)
39
+ * [#434](https://github.com/Dynamoid/dynamoid/pull/434) Support tables that have attribute with name "range_value"
40
+ * [#453](https://github.com/Dynamoid/dynamoid/pull/453) Fixed issue with using `type` attribute as a GSI hash key
41
+
42
+ ---
43
+
44
+
45
+
46
+ # 3.5.0 / 2020-04-04
47
+
48
+
49
+ ## Features
50
+ * Feature: [#405](https://github.com/Dynamoid/dynamoid/pull/405) Added `update!` class method (@UrsaDK)
51
+ * Feature: [#408](https://github.com/Dynamoid/dynamoid/pull/408) Added `ActiveSupport` load hook on `Dynamoid` load (@aaronmallen)
52
+ * Feature: [#422](https://github.com/Dynamoid/dynamoid/pull/422) Added `.pluck` method
53
+
54
+ ## Fixes:
55
+ * Fix: [#410](https://github.com/Dynamoid/dynamoid/pull/410) Fixed creating GSI when table uses on-demand capacity provisioning (@icy-arctic-fox)
56
+ * Fix: [#414](https://github.com/Dynamoid/dynamoid/pull/414) Fixed lazy table creation
57
+ * Fix: [#415](https://github.com/Dynamoid/dynamoid/pull/415) Fixed RubyDoc comment (@walkersumida)
58
+ * Fix: [#420](https://github.com/Dynamoid/dynamoid/pull/420) Fixed `#persisted?` for deleted/destroyed models
59
+
60
+ ## Improvements:
61
+ * Improvement: [#416](https://github.com/Dynamoid/dynamoid/pull/416) Improved speed of Adapter's `truncate` method. It now uses `#batch_delete_item` method (@TheSmartnik)
62
+ * Improvement: [#421](https://github.com/Dynamoid/dynamoid/pull/421) Added `touch: false` option of the #save method
63
+ * Improvement: [#423](https://github.com/Dynamoid/dynamoid/pull/423) Added warning when generated for a field methods override existing ones
64
+
65
+ ---
66
+
67
+
68
+
69
+ # 3.4.1
70
+
71
+ ## Fixes
72
+ * Fix: [#398](https://github.com/Dynamoid/dynamoid/pull/398) Fix broken configuration
73
+
74
+ ---
75
+
76
+
77
+
78
+ # 3.4.0
79
+
80
+ ## Features
81
+ * Feature: [#386](https://github.com/Dynamoid/dynamoid/pull/386) Disable timestamps fields on a table level with new
82
+ table option `timestamps`
83
+ * Feature: [#387](https://github.com/Dynamoid/dynamoid/pull/387) Add TTL support with table option `expires`
84
+ * Feature: [#393](https://github.com/Dynamoid/dynamoid/pull/393) Support pre-configured credentials with new config
85
+ option `credentials` (@emmajhyde)
86
+ * Feature: [#397](https://github.com/Dynamoid/dynamoid/pull/397) Configure on-demand table capacity mode with `capacity_mode` option
4
87
 
5
88
  ## Improvements
89
+ * Improvement: [#388](https://github.com/Dynamoid/dynamoid/pull/388) Minor memory optimization - don't allocate excessive
90
+ hash (@arjes)
91
+
92
+ ## Fixes
93
+
94
+ * Fix: [#382](https://github.com/Dynamoid/dynamoid/pull/382) Fixed deprecation warning about `Module#parent_name` in Rails 6 (@tmandke)
95
+ * Fix: Typos in Readme.md (@romeuhcf)
96
+
97
+ ---
98
+
99
+
100
+
101
+ # 3.3.0
102
+
103
+ ## Features
104
+
105
+ * Feature: [#374](https://github.com/Dynamoid/dynamoid/pull/374) Add `#project` query method to load only specified fields
106
+
107
+ ## Improvements
108
+
109
+ * Improvement: [#359](https://github.com/Dynamoid/dynamoid/pull/359) Add support of `NULL` and `NOT_NULL` operators
110
+ * Improvement: [#360](https://github.com/Dynamoid/dynamoid/pull/360) Add `store_attribute_with_nil_value` config option
111
+ * Improvement: [#368](https://github.com/Dynamoid/dynamoid/pull/368) Support Rails 6 (RC1)
6
112
 
7
113
  ## Fixes
114
+ * Fix: [#357](https://github.com/Dynamoid/dynamoid/pull/357) Fix synchronous table creation issue
115
+ * Fix: [#362](https://github.com/Dynamoid/dynamoid/pull/362) Fix issue with selecting Global Secondary Index (@atyndall)
116
+ * Fix: [#368](https://github.com/Dynamoid/dynamoid/pull/368) Repair `#previous_changes` method from Dirty API
117
+ * Fix: [#373](https://github.com/Dynamoid/dynamoid/pull/373) Fix threadsafety of loading `Dynamoid::Adapter` (@tsub)
8
118
 
9
119
  ---
10
120
 
data/README.md CHANGED
@@ -9,26 +9,36 @@
9
9
  ![GitHub](https://img.shields.io/github/license/Dynamoid/dynamoid.svg)
10
10
 
11
11
  Dynamoid is an ORM for Amazon's DynamoDB for Ruby applications. It
12
- provides similar functionality to ActiveRecord and improves on
13
- Amazon's existing
12
+ provides similar functionality to ActiveRecord and improves on Amazon's
13
+ existing
14
14
  [HashModel](http://docs.amazonwebservices.com/AWSRubySDK/latest/AWS/Record/HashModel.html)
15
15
  by providing better searching tools and native association support.
16
16
 
17
- DynamoDB is not like other document-based databases you might know, and is very different indeed from relational databases. It sacrifices anything beyond the simplest relational queries and transactional support to provide a fast, cost-efficient, and highly durable storage solution. If your database requires complicated relational queries and transaction support, then this modest Gem cannot provide them for you, and neither can DynamoDB. In those cases you would do better to look elsewhere for your database needs.
17
+ DynamoDB is not like other document-based databases you might know, and
18
+ is very different indeed from relational databases. It sacrifices
19
+ anything beyond the simplest relational queries and transactional
20
+ support to provide a fast, cost-efficient, and highly durable storage
21
+ solution. If your database requires complicated relational queries and
22
+ transaction support, then this modest Gem cannot provide them for you,
23
+ and neither can DynamoDB. In those cases you would do better to look
24
+ elsewhere for your database needs.
18
25
 
19
- But if you want a fast, scalable, simple, easy-to-use database (and a Gem that supports it) then look no further!
26
+ But if you want a fast, scalable, simple, easy-to-use database (and a
27
+ Gem that supports it) then look no further!
20
28
 
21
29
  ## Installation
22
30
 
23
- Installing Dynamoid is pretty simple. First include the Gem in your Gemfile:
31
+ Installing Dynamoid is pretty simple. First include the Gem in your
32
+ Gemfile:
24
33
 
25
34
  ```ruby
26
35
  gem 'dynamoid'
27
36
  ```
28
- ## Prerequisities
37
+ ## Prerequisites
29
38
 
30
- Dynamoid depends on the aws-sdk, and this is tested on the current version of aws-sdk (~> 3), rails (>= 4).
31
- Hence the configuration as needed for aws to work will be dealt with by aws setup.
39
+ Dynamoid depends on the aws-sdk, and this is tested on the current
40
+ version of aws-sdk (~> 3), rails (>= 4). Hence the configuration as
41
+ needed for aws to work will be dealt with by aws setup.
32
42
 
33
43
  ### AWS SDK Version Compatibility
34
44
 
@@ -51,45 +61,73 @@ For example, to configure AWS access:
51
61
  Create `config/initializers/aws.rb` as follows:
52
62
 
53
63
  ```ruby
64
+ Aws.config.update({
65
+ region: 'us-west-2',
66
+ credentials: Aws::Credentials.new('REPLACE_WITH_ACCESS_KEY_ID', 'REPLACE_WITH_SECRET_ACCESS_KEY'),
67
+ })
68
+ ```
54
69
 
55
- Aws.config.update({
56
- region: 'us-west-2',
57
- credentials: Aws::Credentials.new('REPLACE_WITH_ACCESS_KEY_ID', 'REPLACE_WITH_SECRET_ACCESS_KEY'),
58
- })
70
+ Alternatively, if you don't want Aws connection settings to be
71
+ overwritten for you entire project, you can specify connection settings
72
+ for Dynamoid only, by setting those in the `Dynamoid.configure` clause:
59
73
 
74
+ ```ruby
75
+ require 'dynamoid'
76
+ Dynamoid.configure do |config|
77
+ config.access_key = 'REPLACE_WITH_ACCESS_KEY_ID'
78
+ config.secret_key = 'REPLACE_WITH_SECRET_ACCESS_KEY'
79
+ config.region = 'us-west-2'
80
+ end
60
81
  ```
61
82
 
62
- Alternatively, if you don't want Aws connection settings to be overwritten for you entire project, you can specify connection settings for Dynamoid only, by setting those in the `Dynamoid.configure` clause:
83
+ Additionally, if you would like to pass in pre-configured AWS credentials
84
+ (e.g. you have an IAM role credential, you configure your credentials
85
+ elsewhere in your project, etc.), you may do so:
63
86
 
64
87
  ```ruby
65
- require 'dynamoid'
66
- Dynamoid.configure do |config|
67
- config.access_key = 'REPLACE_WITH_ACCESS_KEY_ID'
68
- config.secret_key = 'REPLACE_WITH_SECRET_ACCESS_KEY'
69
- config.region = 'us-west-2'
70
- end
88
+ require 'dynamoid'
89
+
90
+ credentials = Aws::AssumeRoleCredentials.new(
91
+ region: region,
92
+ access_key_id: key,
93
+ secret_access_key: secret,
94
+ role_arn: role_arn,
95
+ role_session_name: 'our-session'
96
+ )
97
+
98
+ Dynamoid.configure do |config|
99
+ config.region = 'us-west-2',
100
+ config.credentials = credentials
101
+ end
71
102
  ```
72
103
 
73
104
  For a full list of the DDB regions, you can go
74
105
  [here](http://docs.aws.amazon.com/general/latest/gr/rande.html#ddb_region).
75
106
 
76
- Then you need to initialize Dynamoid config to get it going. Put code similar to this somewhere (a Rails initializer would be a great place for this if you're using Rails):
107
+ Then you need to initialize Dynamoid config to get it going. Put code
108
+ similar to this somewhere (a Rails initializer would be a great place
109
+ for this if you're using Rails):
77
110
 
78
111
  ```ruby
79
- require 'dynamoid'
80
- Dynamoid.configure do |config|
81
- config.namespace = 'dynamoid_app_development' # To namespace tables created by Dynamoid from other tables you might have. Set to nil to avoid namespacing.
82
- config.endpoint = 'http://localhost:3000' # [Optional]. If provided, it communicates with the DB listening at the endpoint. This is useful for testing with [Amazon Local DB] (http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html).
83
- end
112
+ require 'dynamoid'
113
+ Dynamoid.configure do |config|
114
+ # To namespace tables created by Dynamoid from other tables you might have.
115
+ # Set to nil to avoid namespacing.
116
+ config.namespace = 'dynamoid_app_development'
117
+
118
+ # [Optional]. If provided, it communicates with the DB listening at the endpoint.
119
+ # This is useful for testing with [DynamoDB Local] (http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html).
120
+ config.endpoint = 'http://localhost:3000'
121
+ end
84
122
  ```
85
123
 
86
124
  ### Ruby & Rails Compatibility
87
125
 
88
126
  Dynamoid supports Ruby >= 2.3 and Rails >= 4.2.
89
127
 
90
- Its compatibility is tested against following Ruby versions: 2.3.8,
91
- 2.4.5, 2.5.3 and 2.6.1, JRuby versions 9.1.17.0 and 9.2.6.0 and
92
- against Rails versions: 4.2.x, 5.0.x, 5.1.x and 5.2.x.
128
+ Its compatibility is tested against following Ruby versions: 2.3, 2.4,
129
+ 2.5 and 2.6, JRuby 9.2.8.0 and against Rails versions: 4.2, 5.0, 5.1,
130
+ 5.2 and 6.0.
93
131
 
94
132
  ## Setup
95
133
 
@@ -99,12 +137,15 @@ You *must* include `Dynamoid::Document` in every Dynamoid model.
99
137
  class User
100
138
  include Dynamoid::Document
101
139
 
140
+ # fields declaration
102
141
  end
103
142
  ```
104
143
 
105
144
  ### Table
106
145
 
107
- Dynamoid has some sensible defaults for you when you create a new table, including the table name and the primary key column. But you can change those if you like on table creation.
146
+ Dynamoid has some sensible defaults for you when you create a new table,
147
+ including the table name and the primary key column. But you can change
148
+ those if you like on table creation.
108
149
 
109
150
  ```ruby
110
151
  class User
@@ -114,29 +155,85 @@ class User
114
155
  end
115
156
  ```
116
157
 
117
- These fields will not change an existing table: so specifying a new read_capacity and write_capacity here only works correctly for entirely new tables. Similarly, while Dynamoid will look for a table named `awesome_users` in your namespace, it won't change any existing tables to use that name; and if it does find a table with the correct name, it won't change its hash key, which it expects will be `user_id`. If this table doesn't exist yet, however, Dynamoid will create it with these options.
158
+ These fields will not change an existing table: so specifying a new
159
+ read_capacity and write_capacity here only works correctly for entirely
160
+ new tables. Similarly, while Dynamoid will look for a table named
161
+ `awesome_users` in your namespace, it won't change any existing tables
162
+ to use that name; and if it does find a table with the correct name, it
163
+ won't change its hash key, which it expects will be `user_id`. If this
164
+ table doesn't exist yet, however, Dynamoid will create it with these
165
+ options.
118
166
 
119
- ### Fields
167
+ There is a basic support of DynamoDB's [Time To Live (TTL)
168
+ mechanism](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html).
169
+ If you declare a field as TTL field - it will be initialised if doesn't
170
+ have value yet. Default value is current time + specified seconds.
171
+
172
+ ```ruby
173
+ class User
174
+ include Dynamoid::Document
175
+
176
+ table expires: { field: :ttl, after: 60 }
177
+
178
+ field :ttl, :integer
179
+ end
180
+ ```
120
181
 
121
- You'll have to define all the fields on the model and the data type of each field. Every field on the object must be included here; if you miss any they'll be completely bypassed during DynamoDB's initialization and will not appear on the model objects.
182
+ Field used to store expiration time (e.g. `ttl`) should be declared
183
+ explicitly and should have numeric type (`integer`, `number`) only.
184
+ `datetime` type is also possible but only if it's stored as number
185
+ (there is a way to store time as a string also).
122
186
 
123
- By default, fields are assumed to be of type `:string`. Other built-in types are
124
- `:integer`, `:number`, `:set`, `:array`, `:map`, `:datetime`, `date`, `:boolean`, `:raw` and `:serialized`.
125
- `array` and `map` match List and Map DynamoDB types respectively.
126
- `raw` type means you can store Ruby Array, Hash, String and numbers.
127
- If built-in types do not suit you, you can use a custom field type represented by an arbitrary class, provided that the class supports a compatible serialization interface.
128
- The primary use case for using a custom field type is to represent your business logic with high-level types, while ensuring portability or backward-compatibility of the serialized representation.
187
+ It's also possible to override a global option `Dynamoid::Config.timestamps`
188
+ on a table level:
189
+
190
+ ```ruby
191
+ table timestamps: false
192
+ ```
193
+
194
+ This option controls generation of timestamp fields
195
+ `created_at`/`updated_at`.
196
+
197
+ It's also possible to override table capacity mode configured globally
198
+ with table level option `capacity_mode`. Valid values are
199
+ `:provisioned`, `:on_demand` and `nil`:
200
+
201
+ ```ruby
202
+ table capacity_mode: :on_demand
203
+ ```
204
+
205
+ If table capacity mode is on-demand, another related table-level options
206
+ `read_capacity` and `write_capacity` will be ignored.
207
+
208
+ ### Fields
209
+
210
+ You'll have to define all the fields on the model and the data type of
211
+ each field. Every field on the object must be included here; if you miss
212
+ any they'll be completely bypassed during DynamoDB's initialization and
213
+ will not appear on the model objects.
214
+
215
+ By default, fields are assumed to be of type `string`. Other built-in
216
+ types are `integer`, `number`, `set`, `array`, `map`, `datetime`,
217
+ `date`, `boolean`, `binary`, `raw` and `serialized`. `array` and
218
+ `map` match List and Map DynamoDB types respectively. `raw` type means
219
+ you can store Ruby Array, Hash, String and numbers. If built-in types do
220
+ not suit you, you can use a custom field type represented by an
221
+ arbitrary class, provided that the class supports a compatible
222
+ serialization interface. The primary use case for using a custom field
223
+ type is to represent your business logic with high-level types, while
224
+ ensuring portability or backward-compatibility of the serialized
225
+ representation.
129
226
 
130
227
  #### Note on boolean type
131
228
 
132
229
  The boolean fields are stored as DynamoDB boolean values by default.
133
230
  Dynamoid can store boolean values as strings as well - `'t'` and `'f'`.
134
- So if you want to change default format of boolean field you can easily
135
- achieve this with `store_as_native_boolean` field option:
231
+ So if you want to change the default format of boolean field you can
232
+ easily achieve this with `store_as_native_boolean` field option:
136
233
 
137
234
  ```ruby
138
235
  class Document
139
- include DynamoId::Document
236
+ include Dynamoid::Document
140
237
 
141
238
  field :active, :boolean, store_as_native_boolean: false
142
239
  end
@@ -144,11 +241,13 @@ end
144
241
 
145
242
  #### Note on date type
146
243
 
147
- By default date fields are persisted as days count since 1 January 1970 like UNIX time. If you prefer dates to be stored as ISO-8601 formatted strings instead then set `store_as_string` to `true`
244
+ By default date fields are persisted as days count since 1 January 1970
245
+ like UNIX time. If you prefer dates to be stored as ISO-8601 formatted
246
+ strings instead then set `store_as_string` to `true`
148
247
 
149
248
  ```ruby
150
249
  class Document
151
- include DynamoId::Document
250
+ include Dynamoid::Document
152
251
 
153
252
  field :sent_on, :date, store_as_string: true
154
253
  end
@@ -156,51 +255,58 @@ end
156
255
 
157
256
  #### Note on datetime type
158
257
 
159
- By default datetime fields are persisted as UNIX timestamps with millisecond precision in DynamoDB. If you prefer datetimes to be stored as ISO-8601 formatted strings instead then set `store_as_string` to `true`
258
+ By default datetime fields are persisted as UNIX timestamps with
259
+ millisecond precision in DynamoDB. If you prefer datetimes to be stored
260
+ as ISO-8601 formatted strings instead then set `store_as_string` to
261
+ `true`
160
262
 
161
263
  ```ruby
162
264
  class Document
163
- include DynamoId::Document
265
+ include Dynamoid::Document
164
266
 
165
267
  field :sent_at, :datetime, store_as_string: true
166
268
  end
167
269
  ```
168
270
 
169
- **WARNING:** Fields in numeric format are stored with nanoseconds as a fraction part and precision could be lost.
170
- That's why `datetime` field in numeric format shouldn't be used as a range key.
271
+ **WARNING:** Fields in numeric format are stored with nanoseconds as a
272
+ fraction part and precision could be lost. That's why `datetime` field
273
+ in numeric format shouldn't be used as a range key.
171
274
 
172
- You have two options if you need to use a `datetime` field as a range key:
275
+ You have two options if you need to use a `datetime` field as a range
276
+ key:
173
277
  * string format
174
- * store `datetime` values without milliseconds e.g. cut them
175
- manually with `change` method - `Time.now.change(usec: 0)`
278
+ * store `datetime` values without milliseconds (e.g. cut
279
+ them manually with `change` method - `Time.now.change(usec: 0)`
176
280
 
177
281
  #### Note on set type
178
282
 
179
283
  `Dynamoid`'s type `set` is stored as DynamoDB's Set attribute type.
180
- DynamoDB supports only Set of strings, numbers and binary.
181
- Moreover Set *must* contain elements of the same type only.
284
+ DynamoDB supports only Set of strings, numbers and binary. Moreover Set
285
+ *must* contain elements of the same type only.
182
286
 
183
- In order to use some other `Dynamoid`'s types you can specify `of` option
184
- to declare the type of set elements.
287
+ In order to use some other `Dynamoid`'s types you can specify `of`
288
+ option to declare the type of set elements.
185
289
 
186
290
  As a result of that DynamoDB limitation, in Dynamoid only the following
187
291
  scalar types are supported (note: does not support `boolean`):
188
- `integer`, `number`, `date`, `datetime`, `serializable` and custom types.
292
+ `integer`, `number`, `date`, `datetime`, `serializable` and custom
293
+ types.
189
294
 
190
295
  ```ruby
191
296
  class Document
192
- include DynamoId::Document
297
+ include Dynamoid::Document
193
298
 
194
299
  field :tags, :set, of: :integer
195
300
  end
196
301
  ```
197
302
 
198
- It's possible to specify field options like `store_as_string` for `datetime` field
199
- or `serializer` for `serializable` field for `set` elements type:
303
+ It's possible to specify field options like `store_as_string` for
304
+ `datetime` field or `serializer` for `serializable` field for `set`
305
+ elements type:
200
306
 
201
307
  ```ruby
202
308
  class Document
203
- include DynamoId::Document
309
+ include Dynamoid::Document
204
310
 
205
311
  field :values, :set, of: { serialized: { serializer: JSON } }
206
312
  field :dates, :set, of: { date: { store_as_string: true } }
@@ -209,12 +315,14 @@ end
209
315
  ```
210
316
 
211
317
  DynamoDB doesn't allow empty strings in fields configured as `set`.
212
- Abiding by this restriction, when `Dynamoid` saves a document it removes all empty strings in set fields.
318
+ Abiding by this restriction, when `Dynamoid` saves a document it removes
319
+ all empty strings in set fields.
213
320
 
214
321
  #### Note on array type
215
322
 
216
323
  `Dynamoid`'s type `array` is stored as DynamoDB's List attribute type.
217
- It can contain elements of different types (in contrast to Set attribute type).
324
+ It can contain elements of different types (in contrast to Set attribute
325
+ type).
218
326
 
219
327
  If you need to store in array field elements of `datetime`, `date`,
220
328
  `serializable` or some custom type, which DynamoDB doesn't support
@@ -222,7 +330,7 @@ natively, you should specify element type with `of` option:
222
330
 
223
331
  ```ruby
224
332
  class Document
225
- include DynamoId::Document
333
+ include Dynamoid::Document
226
334
 
227
335
  field :dates, :array, of: :date
228
336
  end
@@ -230,7 +338,8 @@ end
230
338
 
231
339
  #### Magic Columns
232
340
 
233
- You get magic columns of `id` (`string`), `created_at` (`datetime`), and `updated_at` (`datetime`) for free.
341
+ You get magic columns of `id` (`string`), `created_at` (`datetime`), and
342
+ `updated_at` (`datetime`) for free.
234
343
 
235
344
  ```ruby
236
345
  class User
@@ -248,11 +357,12 @@ end
248
357
 
249
358
  #### Default Values
250
359
 
251
- You can optionally set a default value on a field using either a plain value or a lambda:
360
+ You can optionally set a default value on a field using either a plain
361
+ value or a lambda:
252
362
 
253
363
  ```ruby
254
- field :actions_taken, :integer, default: 0
255
- field :joined_at, :datetime, default: -> { Time.now }
364
+ field :actions_taken, :integer, default: 0
365
+ field :joined_at, :datetime, default: -> { Time.now }
256
366
  ```
257
367
 
258
368
  #### Custom Types
@@ -260,64 +370,68 @@ You can optionally set a default value on a field using either a plain value or
260
370
  To use a custom type for a field, suppose you have a `Money` type.
261
371
 
262
372
  ```ruby
263
- class Money
264
- # ... your business logic ...
373
+ class Money
374
+ # ... your business logic ...
265
375
 
266
- def dynamoid_dump
267
- 'serialized representation as a string'
268
- end
376
+ def dynamoid_dump
377
+ 'serialized representation as a string'
378
+ end
269
379
 
270
- def self.dynamoid_load(serialized_str)
271
- # parse serialized representation and return a Money instance
272
- Money.new(1.23)
273
- end
380
+ def self.dynamoid_load(serialized_str)
381
+ # parse serialized representation and return a Money instance
382
+ Money.new(1.23)
274
383
  end
384
+ end
275
385
 
276
- class User
277
- include Dynamoid::Document
386
+ class User
387
+ include Dynamoid::Document
278
388
 
279
- field :balance, Money
280
- end
389
+ field :balance, Money
390
+ end
281
391
  ```
282
392
 
283
- If you want to use a third-party class (which does not support `#dynamoid_dump` and `.dynamoid_load`)
284
- as your field type, you can use an adapter class providing `.dynamoid_dump` and `.dynamoid_load` class methods
285
- for your third-party class. (`.dynamoid_load` can remain the same from the previous example; here we just
286
- add a level of indirection for serializing.) Example:
393
+ If you want to use a third-party class (which does not support
394
+ `#dynamoid_dump` and `.dynamoid_load`) as your field type, you can use
395
+ an adapter class providing `.dynamoid_dump` and `.dynamoid_load` class
396
+ methods for your third-party class. `.dynamoid_load` can remain the same
397
+ from the previous example; here we just add a level of indirection for
398
+ serializing. Example:
287
399
 
288
400
  ```ruby
289
- # Third-party Money class
290
- class Money; end
401
+ # Third-party Money class
402
+ class Money; end
291
403
 
292
- class MoneyAdapter
293
- def self.dynamoid_load(money_serialized_str)
294
- Money.new(1.23)
295
- end
404
+ class MoneyAdapter
405
+ def self.dynamoid_load(money_serialized_str)
406
+ Money.new(1.23)
407
+ end
296
408
 
297
- def self.dynamoid_dump(money_obj)
298
- money_obj.value.to_s
299
- end
409
+ def self.dynamoid_dump(money_obj)
410
+ money_obj.value.to_s
300
411
  end
412
+ end
301
413
 
302
- class User
303
- include Dynamoid::Document
414
+ class User
415
+ include Dynamoid::Document
304
416
 
305
- field :balance, MoneyAdapter
306
- end
417
+ field :balance, MoneyAdapter
418
+ end
307
419
  ```
308
420
 
309
- Lastly, you can control the data type of your custom-class-backed field at the DynamoDB level.
310
- This is especially important if you want to use your custom field as a numeric range or for
311
- number-oriented queries. By default custom fields are persisted as a string attribute, but
312
- your custom class can override this with a `.dynamoid_field_type` class method, which would
313
- return either `:string` or `:number`.
421
+ Lastly, you can control the data type of your custom-class-backed field
422
+ at the DynamoDB level. This is especially important if you want to use
423
+ your custom field as a numeric range or for number-oriented queries. By
424
+ default custom fields are persisted as a string attribute, but your
425
+ custom class can override this with a `.dynamoid_field_type` class
426
+ method, which would return either `:string` or `:number`.
314
427
 
315
- DynamoDB may support some other attribute types that are not yet supported by Dynamoid.
428
+ DynamoDB may support some other attribute types that are not yet
429
+ supported by Dynamoid.
316
430
 
317
431
  ### Sort key
318
432
 
319
- Along with partition key table may have a sort key. In order to declare it in a model
320
- `range` class method should be used:
433
+ Along with partition key table may have a sort key. In order to declare
434
+ it in a model `range` class method should be used:
321
435
 
322
436
  ```ruby
323
437
  class Post
@@ -331,9 +445,15 @@ Second argument, type, is optional. Default type is `string`.
331
445
 
332
446
  ### Associations
333
447
 
334
- Just like in ActiveRecord (or your other favorite ORM), Dynamoid uses associations to create links between models.
448
+ Just like in ActiveRecord (or your other favorite ORM), Dynamoid uses
449
+ associations to create links between models.
335
450
 
336
- The only supported associations (so far) are `has_many`, `has_one`, `has_and_belongs_to_many`, and `belongs_to`. Associations are very simple to create: just specify the type, the name, and then any options you'd like to pass to the association. If there's an inverse association either inferred or specified directly, Dynamoid will update both objects to point at each other.
451
+ The only supported associations (so far) are `has_many`, `has_one`,
452
+ `has_and_belongs_to_many`, and `belongs_to`. Associations are very
453
+ simple to create: just specify the type, the name, and then any options
454
+ you'd like to pass to the association. If there's an inverse association
455
+ either inferred or specified directly, Dynamoid will update both objects
456
+ to point at each other.
337
457
 
338
458
  ```ruby
339
459
  class User
@@ -348,7 +468,6 @@ class User
348
468
  belongs_to :group, foreign_key: :group_id
349
469
  has_one :role
350
470
  has_and_belongs_to_many :friends, inverse_of: :friending_users
351
-
352
471
  end
353
472
 
354
473
  class Address
@@ -357,11 +476,18 @@ class Address
357
476
  # ...
358
477
 
359
478
  belongs_to :user # Automatically links up with the user model
360
-
361
479
  end
362
480
  ```
363
481
 
364
- Contrary to what you'd expect, association information is always contained on the object specifying the association, even if it seems like the association has a foreign key. This is a side effect of DynamoDB's structure: it's very difficult to find foreign keys without an index. Usually you won't find this to be a problem, but it does mean that association methods that build new models will not work correctly -- for example, `user.addresses.new` returns an address that is not associated to the user. We'll be correcting this ~soon~ maybe someday, if we get a pull request.
482
+ Contrary to what you'd expect, association information is always
483
+ contained on the object specifying the association, even if it seems
484
+ like the association has a foreign key. This is a side effect of
485
+ DynamoDB's structure: it's very difficult to find foreign keys without
486
+ an index. Usually you won't find this to be a problem, but it does mean
487
+ that association methods that build new models will not work correctly -
488
+ for example, `user.addresses.new` returns an address that is not
489
+ associated to the user. We'll be correcting this ~soon~ maybe someday,
490
+ if we get a pull request.
365
491
 
366
492
  ### Validations
367
493
 
@@ -378,9 +504,12 @@ class User
378
504
  end
379
505
  ```
380
506
 
381
- To see more usage and examples of ActiveModel validations, check out the [ActiveModel validation documentation](http://api.rubyonrails.org/classes/ActiveModel/Validations.html).
507
+ To see more usage and examples of ActiveModel validations, check out the
508
+ [ActiveModel validation
509
+ documentation](http://api.rubyonrails.org/classes/ActiveModel/Validations.html).
382
510
 
383
- If you want to bypass model validation, pass `validate: false` to `save` call:
511
+ If you want to bypass model validation, pass `validate: false` to `save`
512
+ call:
384
513
 
385
514
  ```ruby
386
515
  model.save(validate: false)
@@ -388,7 +517,9 @@ model.save(validate: false)
388
517
 
389
518
  ### Callbacks
390
519
 
391
- Dynamoid also employs ActiveModel callbacks. Right now, callbacks are defined on ```save```, ```update```, ```destroy```, which allows you to do ```before_``` or ```after_``` any of those.
520
+ Dynamoid also employs ActiveModel callbacks. Right now, callbacks are
521
+ defined on `save`, `update`, `destroy`, which allows you to do `before_`
522
+ or `after_` any of those.
392
523
 
393
524
  ```ruby
394
525
  class User
@@ -404,7 +535,8 @@ end
404
535
 
405
536
  ### STI
406
537
 
407
- Dynamoid supports STI (Single Table Inheritance) like Active Record does. You need just specify `type` field in a base class. Example:
538
+ Dynamoid supports STI (Single Table Inheritance) like Active Record
539
+ does. You need just specify `type` field in a base class. Example:
408
540
 
409
541
  ```ruby
410
542
  class Animal
@@ -421,12 +553,13 @@ end
421
553
  cat = Cat.create(name: 'Morgan')
422
554
  animal = Animal.find(cat.id)
423
555
  animal.class
424
- #=> Cat
556
+ #=> Cat
425
557
  ```
426
558
 
427
- If you already have DynamoDB tables and `type` field already exists and has its own semantic it leads to conflict.
428
- It's possible to tell Dynamoid to use another field (even not existing)
429
- instead of `type` one with `inheritance_field` table option:
559
+ If you already have DynamoDB tables and `type` field already exists and
560
+ has its own semantic it leads to conflict. It's possible to tell
561
+ Dynamoid to use another field (even not existing) instead of `type` one
562
+ with `inheritance_field` table option:
430
563
 
431
564
  ```ruby
432
565
  class Car
@@ -443,8 +576,9 @@ c.my_new_type
443
576
 
444
577
  ### Type casting
445
578
 
446
- Dynamid supports type casting and tryes to do it in the most convinient way.
447
- Values for all fields (except custom type) are coerced to declared field types.
579
+ Dynamid supports type casting and tries to do it in the most convenient
580
+ way. Values for all fields (except custom type) are coerced to declared
581
+ field types.
448
582
 
449
583
  Some obvious rules are used, e.g.:
450
584
 
@@ -468,18 +602,29 @@ document.integer_field = true
468
602
  # => 1
469
603
  ```
470
604
 
471
- If time zone isn't specified for `datetime` value - application time zone is used.
605
+ If time zone isn't specified for `datetime` value - application time
606
+ zone is used.
472
607
 
473
608
  To access field value before type casting following method could be
474
- used: `attributes_before_type_cast` and `read_attribute_before_type_cast`.
609
+ used: `attributes_before_type_cast` and
610
+ `read_attribute_before_type_cast`.
475
611
 
476
- There is `<name>_before_type_cast` method for every field in a model as well.
612
+ There is `<name>_before_type_cast` method for every field in a model as
613
+ well.
614
+
615
+ ### Dirty API
616
+
617
+ Dynamoid supports Dirty API which equivalents to [Rails 5.2
618
+ `ActiveModel::Dirty`](https://api.rubyonrails.org/v5.2/classes/ActiveModel/Dirty.html).
619
+ There is only one limitation - change in place of field isn't detected
620
+ automatically.
477
621
 
478
622
  ## Usage
479
623
 
480
624
  ### Object Creation
481
625
 
482
- Dynamoid's syntax is generally very similar to ActiveRecord's. Making new objects is simple:
626
+ Dynamoid's syntax is generally very similar to ActiveRecord's. Making
627
+ new objects is simple:
483
628
 
484
629
  ```ruby
485
630
  u = User.new(name: 'Josh')
@@ -487,13 +632,15 @@ u.email = 'josh@joshsymonds.com'
487
632
  u.save
488
633
  ```
489
634
 
490
- Save forces persistence to the datastore: a unique ID is also assigned, but it is a string and not an auto-incrementing number.
635
+ Save forces persistence to the datastore: a unique ID is also assigned,
636
+ but it is a string and not an auto-incrementing number.
491
637
 
492
638
  ```ruby
493
639
  u.id # => '3a9f7216-4726-4aea-9fbc-8554ae9292cb'
494
640
  ```
495
641
 
496
- To use associations, you use association methods very similar to ActiveRecord's:
642
+ To use associations, you use association methods very similar to
643
+ ActiveRecord's:
497
644
 
498
645
  ```ruby
499
646
  address = u.addresses.create
@@ -520,7 +667,8 @@ Querying can be done in one of three ways:
520
667
 
521
668
  ```ruby
522
669
  Address.find(address.id) # Find directly by ID.
523
- Address.where(city: 'Chicago').all # Find by any number of matching criteria... though presently only "where" is supported.
670
+ Address.where(city: 'Chicago').all # Find by any number of matching criteria...
671
+ # Though presently only "where" is supported.
524
672
  Address.find_by_city('Chicago') # The same as above, but using ActiveRecord's older syntax.
525
673
  ```
526
674
 
@@ -530,11 +678,16 @@ And you can also query on associations:
530
678
  u.addresses.where(city: 'Chicago').all
531
679
  ```
532
680
 
533
- But keep in mind Dynamoid -- and document-based storage systems in general -- are not drop-in replacements for existing relational databases. The above query does not efficiently perform a conditional join, but instead finds all the user's addresses and naively filters them in Ruby. For large associations this is a performance hit compared to relational database engines.
681
+ But keep in mind Dynamoid - and document-based storage systems in
682
+ general - are not drop-in replacements for existing relational
683
+ databases. The above query does not efficiently perform a conditional
684
+ join, but instead finds all the user's addresses and naively filters
685
+ them in Ruby. For large associations this is a performance hit compared
686
+ to relational database engines.
534
687
 
535
688
  **WARNING:** There is a limitation of conditions passed to `where`
536
689
  method. Only one condition for some particular field could be specified.
537
- The last one only will be applyed and others will be ignored. E.g. in
690
+ The last one only will be applied and others will be ignored. E.g. in
538
691
  examples:
539
692
 
540
693
  ```ruby
@@ -544,64 +697,102 @@ User.where(name: 'Mike').where('name.begins_with': 'Ed')
544
697
 
545
698
  the first one will be ignored and the last one will be used.
546
699
 
700
+ **Warning:** There is a caveat with filtering documents by `nil` value
701
+ attribute. By default Dynamoid ignores attributes with `nil` value and
702
+ doesn't store them in a DynamoDB document. This behavior could be
703
+ changed with `store_attribute_with_nil_value` config option.
704
+
705
+ If Dynamoid ignores `nil` value attributes `null`/`not_null` operators
706
+ should be used in query:
707
+
708
+ ```ruby
709
+ Address.where('postcode.null': true)
710
+ Address.where('postcode.not_null': true)
711
+ ```
712
+
713
+ If Dynamoid keeps `nil` value attributes `eq`/`ne` operators should be
714
+ used instead:
715
+
716
+ ```ruby
717
+ Address.where('postcode': nil)
718
+ Address.where('postcode.ne': nil)
719
+ ```
720
+
547
721
  #### Limits
548
722
 
549
723
  There are three types of limits that you can query with:
550
724
 
551
- 1. `record_limit` - The number of evaluated records that are returned by the query.
552
- 2. `scan_limit` - The number of scanned records that DynamoDB will look at before returning.
553
- 3. `batch_size` - The number of records requested to DynamoDB per underlying request, good for large queries!
725
+ 1. `record_limit` - The number of evaluated records that are returned by
726
+ the query.
727
+ 2. `scan_limit` - The number of scanned records that DynamoDB will look
728
+ at before returning.
729
+ 3. `batch_size` - The number of records requested to DynamoDB per
730
+ underlying request, good for large queries!
554
731
 
555
- Using these in various combinations results in the underlying requests to be made in the smallest size possible and
556
- the query returns once `record_limit` or `scan_limit` is satisfied. It will attempt to batch whenever possible.
732
+ Using these in various combinations results in the underlying requests
733
+ to be made in the smallest size possible and the query returns once
734
+ `record_limit` or `scan_limit` is satisfied. It will attempt to batch
735
+ whenever possible.
557
736
 
558
- You can thus limit the number of evaluated records, or select a record from which to start in order to support pagination.
737
+ You can thus limit the number of evaluated records, or select a record
738
+ from which to start in order to support pagination.
559
739
 
560
740
  ```ruby
561
741
  Address.record_limit(5).start(address) # Only 5 addresses starting at `address`
562
742
  ```
563
- Where `address` is an instance of the model or a hash `{the_model_hash_key: 'value', the_model_range_key: 'value'}`:
564
- Keep in mind that if you are passing a hash to `.start()` you need to explicitly define all required keys in it including range keys, depending on table or secondary indexes signatures, otherwise you'll get an `Aws::DynamoDB::Errors::ValidationException` either for `Exclusive Start Key must have same size as table's key schema` or `The provided starting key is invalid`
565
-
566
- If you are potentially running over a large data set and this is especially true when using certain filters, you may
567
- want to consider limiting the number of scanned records (the number of records DynamoDB infrastructure looks through
568
- when evaluating data to return):
743
+ Where `address` is an instance of the model or a hash
744
+ `{the_model_hash_key: 'value', the_model_range_key: 'value'}`. Keep in
745
+ mind that if you are passing a hash to `.start()` you need to explicitly
746
+ define all required keys in it including range keys, depending on table
747
+ or secondary indexes signatures, otherwise you'll get an
748
+ `Aws::DynamoDB::Errors::ValidationException` either for `Exclusive Start
749
+ Key must have same size as table's key schema` or `The provided starting
750
+ key is invalid`
751
+
752
+ If you are potentially running over a large data set and this is
753
+ especially true when using certain filters, you may want to consider
754
+ limiting the number of scanned records (the number of records DynamoDB
755
+ infrastructure looks through when evaluating data to return):
569
756
 
570
757
  ```ruby
571
758
  Address.scan_limit(5).start(address) # Only scan at most 5 records and return what's found starting from `address`
572
759
  ```
573
760
 
574
- For large queries that return many rows, Dynamoid can use AWS' support for requesting documents in batches:
761
+ For large queries that return many rows, Dynamoid can use AWS' support
762
+ for requesting documents in batches:
575
763
 
576
764
  ```ruby
577
765
  # Do some maintenance on the entire table without flooding DynamoDB
578
- Address.all(batch_size: 100).each { |address| address.do_some_work; sleep(0.01) }
766
+ Address.batch(100).each { |address| address.do_some_work; sleep(0.01) }
579
767
  Address.record_limit(10_000).batch(100).each { … } # Batch specified as part of a chain
580
768
  ```
581
769
 
582
- The implication of batches is that the underlying requests are done in the batch sizes to make the request and responses
583
- more manageable. Note that this batching is for `Query` and `Scans` and not `BatchGetItem` commands.
770
+ The implication of batches is that the underlying requests are done in
771
+ the batch sizes to make the request and responses more manageable. Note
772
+ that this batching is for `Query` and `Scans` and not `BatchGetItem`
773
+ commands.
584
774
 
585
775
  #### DynamoDB pagination
586
776
 
587
- At times it can be useful to rely on DynamoDB [low-level pagination](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.Pagination)
588
- instead of fixed pages sizes. Each page results in a single Query or Scan call
589
- to DyanmoDB, but returns an unknown number of records.
777
+ At times it can be useful to rely on DynamoDB [low-level
778
+ pagination](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.Pagination)
779
+ instead of fixed pages sizes. Each page results in a single Query or
780
+ Scan call to DynamoDB, but returns an unknown number of records.
590
781
 
591
- Access to the native DynamoDB pages can be obtained via the `find_by_pages`
592
- method, which yields arrays of records.
782
+ Access to the native DynamoDB pages can be obtained via the
783
+ `find_by_pages` method, which yields arrays of records.
593
784
 
594
785
  ```ruby
595
786
  Address.find_by_pages do |addresses, metadata|
596
787
  end
597
788
  ```
598
789
 
599
- Each yielded pages returns page metadata as the second argument, which is a hash
600
- including a key `:last_evaluated_key`. The value of this key can be used for
601
- the `start` method to fetch the next page of records.
790
+ Each yielded pages returns page metadata as the second argument, which
791
+ is a hash including a key `:last_evaluated_key`. The value of this key
792
+ can be used for the `start` method to fetch the next page of records.
602
793
 
603
- This way it can be used for instance to implement efficiently
604
- pagination in web-application:
794
+ This way it can be used for instance to implement efficiently pagination
795
+ in web-applications:
605
796
 
606
797
  ```ruby
607
798
  class UserController < ApplicationController
@@ -620,8 +811,9 @@ end
620
811
 
621
812
  #### Sort Conditions and Filters
622
813
 
623
- You are able to optimize query with condition for sort key. Following operators are available: `gt`, `lt`, `gte`, `lte`,
624
- `begins_with`, `between` as well as equality:
814
+ You are able to optimize query with condition for sort key. Following
815
+ operators are available: `gt`, `lt`, `gte`, `lte`, `begins_with`,
816
+ `between` as well as equality:
625
817
 
626
818
  ```ruby
627
819
  Address.where(latitude: 10212)
@@ -633,18 +825,57 @@ Address.where('city.begins_with': 'Lon')
633
825
  Address.where('latitude.between': [10212, 20000])
634
826
  ```
635
827
 
636
- You are able to filter results on the DynamoDB side and specify conditions for non-key fields.
637
- Following operators are available: `in`, `contains`, `not_contains`:
828
+ You are able to filter results on the DynamoDB side and specify
829
+ conditions for non-key fields. Following additional operators are
830
+ available: `in`, `contains`, `not_contains`, `null`, `not_null`:
638
831
 
639
832
  ```ruby
640
833
  Address.where('city.in': ['London', 'Edenburg', 'Birmingham'])
641
834
  Address.where('city.contains': ['on'])
642
835
  Address.where('city.not_contains': ['ing'])
836
+ Address.where('postcode.null': false)
837
+ Address.where('postcode.not_null': true)
838
+ ```
839
+
840
+ **WARNING:** Please take into account that `NULL` and `NOT_NULL`
841
+ operators check attribute presence in a document, not value. So if
842
+ attribute `postcode`'s value is `NULL`, `NULL` operator will return
843
+ false because attribute exists even if has `NULL` value.
844
+
845
+ #### Selecting some specific fields only
846
+
847
+ It could be done with `project` method:
848
+
849
+ ```ruby
850
+ class User
851
+ include Dynamoid::Document
852
+ field :name
853
+ end
854
+
855
+ User.create(name: 'Alex')
856
+ user = User.project(:name).first
857
+
858
+ user.id # => nil
859
+ user.name # => 'Alex'
860
+ user.created_at # => nil
861
+ ```
862
+
863
+ Returned models with have filled specified fields only.
864
+
865
+ Several fields could be specified:
866
+
867
+ ```ruby
868
+ user = User.project(:name, :created_at)
643
869
  ```
644
870
 
645
871
  ### Consistent Reads
646
872
 
647
- Querying supports consistent reading. By default, DynamoDB reads are eventually consistent: if you do a write and then a read immediately afterwards, the results of the previous write may not be reflected. If you need to do a consistent read (that is, you need to read the results of a write immediately) you can do so, but keep in mind that consistent reads are twice as expensive as regular reads for DynamoDB.
873
+ Querying supports consistent reading. By default, DynamoDB reads are
874
+ eventually consistent: if you do a write and then a read immediately
875
+ afterwards, the results of the previous write may not be reflected. If
876
+ you need to do a consistent read (that is, you need to read the results
877
+ of a write immediately) you can do so, but keep in mind that consistent
878
+ reads are twice as expensive as regular reads for DynamoDB.
648
879
 
649
880
  ```ruby
650
881
  Address.find(address.id, consistent_read: true) # Find an address, ensure the read is consistent.
@@ -653,21 +884,25 @@ Address.where(city: 'Chicago').consistent.all # Find all addresses where the
653
884
 
654
885
  ### Range Finding
655
886
 
656
- If you have a range index, Dynamoid provides a number of additional other convenience methods to make your life a little easier:
887
+ If you have a range index, Dynamoid provides a number of additional
888
+ other convenience methods to make your life a little easier:
657
889
 
658
890
  ```ruby
659
891
  User.where("created_at.gt": DateTime.now - 1.day).all
660
892
  User.where("created_at.lt": DateTime.now - 1.day).all
661
893
  ```
662
894
 
663
- It also supports `gte` and `lte`. Turning those into symbols and allowing a Rails SQL-style string syntax is in the works. You can only have one range argument per query, because of DynamoDB's inherent limitations, so use it sensibly!
895
+ It also supports `gte` and `lte`. Turning those into symbols and
896
+ allowing a Rails SQL-style string syntax is in the works. You can only
897
+ have one range argument per query, because of DynamoDB inherent
898
+ limitations, so use it sensibly!
664
899
 
665
900
 
666
901
  ### Updating
667
902
 
668
903
  In order to update document you can use high level methods
669
- `#update_attributes`, `#update_attribute` and `.update`.
670
- They run validation and collbacks.
904
+ `#update_attributes`, `#update_attribute` and `.update`. They run
905
+ validation and callbacks.
671
906
 
672
907
  ```ruby
673
908
  Address.find(id).update_attributes(city: 'Chicago')
@@ -677,18 +912,18 @@ Address.update(id, { city: 'Chicago' }, if: { deliverable: true })
677
912
  ```
678
913
 
679
914
  There are also some low level methods `#update`, `.update_fields` and
680
- `.upsert`. They don't run validation and callbacks (except `#update` - it
681
- runs `update` callbacks). All of them support conditional updates.
915
+ `.upsert`. They don't run validation and callbacks (except `#update` -
916
+ it runs `update` callbacks). All of them support conditional updates.
682
917
  `#upsert` will create new document if document with specified `id`
683
918
  doesn't exist.
684
919
 
685
920
  ```ruby
686
- Adderess.find(id).update do |i|
921
+ Address.find(id).update do |i|
687
922
  i.set city: 'Chicago'
688
923
  i.add latitude: 100
689
924
  i.delete set_of_numbers: 10
690
925
  end
691
- Adderess.find(id).update(if: { deliverable: true }) do |i|
926
+ Address.find(id).update(if: { deliverable: true }) do |i|
692
927
  i.set city: 'Chicago'
693
928
  end
694
929
  Address.update_fields(id, city: 'Chicago')
@@ -699,8 +934,8 @@ Address.upsert(id, { city: 'Chicago' }, if: { deliverable: true })
699
934
 
700
935
  ### Deleting
701
936
 
702
- In order to delete some items `delete_all` method should be used.
703
- Any callback wont be called. Items delete in efficient way in batch.
937
+ In order to delete some items `delete_all` method should be used. Any
938
+ callback won't be called. Items delete in efficient way in batch.
704
939
 
705
940
  ```ruby
706
941
  Address.where(city: 'London').delete_all
@@ -721,17 +956,24 @@ class User
721
956
  end
722
957
  ```
723
958
 
724
- There are following options:
959
+ There are the following options:
725
960
  * `hash_key` - is used as hash key of an index,
726
961
  * `range_key` - is used as range key of an index,
727
- * `projected_attributes` - list of fields to store in an index or has a predefiled value `:keys_only`, `:all`; `:keys_only` is a default,
728
- * `name` - an index will be created with this name when a table is created; by default name is generated and contains table name and keys names,
729
- * `read_capacity` - is used when table creates and used as an index capacity; by default equals `Dynamoid::Config.read_capacity`,
730
- * `write_capacity` - is used when table creates and used as an index capacity; by default equals `Dynamoid::Config.write_capacity`
962
+ * `projected_attributes` - list of fields to store in an index or has a
963
+ predefined value `:keys_only`, `:all`; `:keys_only` is a default,
964
+ * `name` - an index will be created with this name when a table is
965
+ created; by default name is generated and contains table name and keys
966
+ names,
967
+ * `read_capacity` - is used when table created and used as an index
968
+ capacity; by default equals `Dynamoid::Config.read_capacity`,
969
+ * `write_capacity` - is used when table created and used as an index
970
+ capacity; by default equals `Dynamoid::Config.write_capacity`
731
971
 
732
972
  The only mandatory option is `name`.
733
973
 
734
- **WARNING:** In order to use global secondary index in `Document.where` implicitly you need to have all the attributes of the original table in the index and declare it with option `projected_attributes: :all`:
974
+ **WARNING:** In order to use global secondary index in `Document.where`
975
+ implicitly you need to have all the attributes of the original table in
976
+ the index and declare it with option `projected_attributes: :all`:
735
977
 
736
978
  ```ruby
737
979
  class User
@@ -741,13 +983,16 @@ class User
741
983
  end
742
984
  ```
743
985
 
744
- There is only one implicit way to query Global and Local Secondary Indexes (GSI/LSI).
986
+ There is only one implicit way to query Global and Local Secondary
987
+ Indexes (GSI/LSI).
745
988
 
746
989
  #### Implicit
747
990
 
748
- The second way implicitly uses your GSI through the `where` clauses and deduces the index based on the query fields
749
- provided. Another added benefit is that it is built into query chaining so you can use all the methods used in normal
750
- querying. The explicit way from above would be rewritten as follows:
991
+ The second way implicitly uses your GSI through the `where` clauses and
992
+ deduces the index based on the query fields provided. Another added
993
+ benefit is that it is built into query chaining so you can use all the
994
+ methods used in normal querying. The explicit way from above would be
995
+ rewritten as follows:
751
996
 
752
997
  ```ruby
753
998
  where(dynamo_primary_key_column_name => dynamo_primary_key_value,
@@ -755,57 +1000,118 @@ where(dynamo_primary_key_column_name => dynamo_primary_key_value,
755
1000
  .scan_index_forward(false)
756
1001
  ```
757
1002
 
758
- The only caveat with this method is that because it is also used for general querying, it WILL NOT use a GSI unless it
759
- explicitly has defined `projected_attributes: :all` on the GSI in your model. This is because GSIs that do not have all
760
- attributes projected will only contain the index keys and therefore will not return objects with fully resolved field
761
- values. It currently opts to provide the complete results rather than partial results unless you've explicitly looked up
762
- the data.
1003
+ The only caveat with this method is that because it is also used for
1004
+ general querying, it WILL NOT use a GSI unless it explicitly has defined
1005
+ `projected_attributes: :all` on the GSI in your model. This is because
1006
+ GSIs that do not have all attributes projected will only contain the
1007
+ index keys and therefore will not return objects with fully resolved
1008
+ field values. It currently opts to provide the complete results rather
1009
+ than partial results unless you've explicitly looked up the data.
763
1010
 
764
- *Future TODO could involve implementing `select` in chaining as well as resolving the fields with a second query against
765
- the table since a query against GSI then a query on base table is still likely faster than scan on the base table*
1011
+ *Future TODO could involve implementing `select` in chaining as well as
1012
+ resolving the fields with a second query against the table since a query
1013
+ against GSI then a query on base table is still likely faster than scan
1014
+ on the base table*
766
1015
 
767
1016
  ## Configuration
768
1017
 
769
1018
  Listed below are all configuration options.
770
1019
 
771
- * `adapter` - usefull only for the gem developers to switch to a new adapter. Default and the only available value is `aws_sdk_v3`
772
- * `namespace` - prefix for table names, default is `dynamoid_#{application_name}_#{environment}` for Rails application and `dynamoid` otherwise
773
- * `logger` - by default it's a `Rails.logger` in Rails application and `stdout` otherwise. You can disable logging by setting `nil` or `false` values. Set `true` value to use defaults
774
- * `access_key` - DynamoDb custom credentials for AWS, override global AWS credentials if they present
775
- * `secret_key` - DynamoDb custom credentials for AWS, override global AWS credentials if they present
776
- * `region` - DynamoDb custom credentials for AWS, override global AWS credentials if they present
777
- * `batch_size` - when you try to load multiple items at once with `batch_get_item` call Dynamoid loads them not with one api call but piece by piece. Default is 100 items
778
- * `read_capacity` - is used at table or indices creation. Default is 100 (units)
779
- * `write_capacity` - is used at table or indices creation. Default is 20 (units)
1020
+ * `adapter` - useful only for the gem developers to switch to a new
1021
+ adapter. Default and the only available value is `aws_sdk_v3`
1022
+ * `namespace` - prefix for table names, default is
1023
+ `dynamoid_#{application_name}_#{environment}` for Rails application
1024
+ and `dynamoid` otherwise
1025
+ * `logger` - by default it's a `Rails.logger` in Rails application and
1026
+ `stdout` otherwise. You can disable logging by setting `nil` or
1027
+ `false` values. Set `true` value to use defaults
1028
+ * `access_key` - DynamoDb custom access key for AWS credentials, override global
1029
+ AWS credentials if they're present
1030
+ * `secret_key` - DynamoDb custom secret key for AWS credentials, override global
1031
+ AWS credentials if they're present
1032
+ * `credentials` - DynamoDb custom pre-configured credentials, override global
1033
+ AWS credentials if they're present
1034
+ * `region` - DynamoDb custom credentials for AWS, override global AWS
1035
+ credentials if they're present
1036
+ * `batch_size` - when you try to load multiple items at once with
1037
+ * `batch_get_item` call Dynamoid loads them not with one api call but
1038
+ piece by piece. Default is 100 items
1039
+ * `capacity_mode` - used at a table creation and means whether a table
1040
+ read/write capacity mode will be on-demand or provisioned. Allowed
1041
+ values are `:on_demand` and `:provisioned`. Default value is `nil` which
1042
+ means provisioned mode will be used.
1043
+ * `read_capacity` - is used at table or indices creation. Default is 100
1044
+ (units)
1045
+ * `write_capacity` - is used at table or indices creation. Default is 20
1046
+ (units)
780
1047
  * `warn_on_scan` - log warnings when scan table. Default is `true`
781
- * `endpoint` - if provided, it communicates with the DynamoDB listening at the endpoint. This is useful for testing with [Amazon Local DB]
782
- * `identity_map` - ensures that each object gets loaded only once by keeping every loaded object in a map. Looks up objects using the map when referring to them. Isn't thread safe. Default is `false`.
1048
+ * `endpoint` - if provided, it communicates with the DynamoDB listening
1049
+ at the endpoint. This is useful for testing with
1050
+ [DynamoDB Local](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html)
1051
+ * `identity_map` - ensures that each object gets loaded only once by
1052
+ keeping every loaded object in a map. Looks up objects using the map
1053
+ when referring to them. Isn't thread safe. Default is `false`.
783
1054
  `Use Dynamoid::Middleware::IdentityMap` to clear identity map for each HTTP request
784
- * `timestamps` - by default Dynamoid sets `created_at` and `updated_at` fields for model at creation and updating. You can disable this behavior by setting `false` value
785
- * `sync_retry_max_times` - when Dynamoid creates or deletes table synchronously it checks for completion specified times. Default is 60 (times). It's a bit over 2 minutes by default
786
- * `sync_retry_wait_seconds` - time to wait between retries. Default is 2 (seconds)
787
- * `convert_big_decimal` - if `true` then Dynamoid converts numbers stored in `Hash` in `raw` field to float. Default is `false`
788
- * `models_dir` - `dynamoid:create_tables` rake task loads DynamoDb models from this directory. Default is `./app/models`.
789
- * `application_timezone` - Dynamoid converts all `datetime` fields to specified time zone when loads data from the storage.
790
- Acceptable values - `:utc`, `:local` (to use system time zone) and time zone name e.g. `Eastern Time (US & Canada)`. Default is `utc`
791
- * `dynamodb_timezone` - When a datetime field is stored in string format Dynamoid converts it to specified time zone when saves a value to the storage.
792
- Acceptable values - `:utc`, `:local` (to use system time zone) and time zone name e.g. `Eastern Time (US & Canada)`. Default is `utc`
793
- * `store_datetime_as_string` - if `true` then Dynamoid stores :datetime fields in ISO 8601 string format. Default is `false`
794
- * `store_date_as_string` - if `true` then Dynamoid stores :date fields in ISO 8601 string format. Default is `false`
795
- * `store_boolean_as_native` - if `true` Dynamoid stores boolean fields as native DynamoDB
796
- boolean values. Otherwise boolean fields are stored as string values
797
- `'t'` and `'f'`. Default is true
798
- * `backoff` - is a hash: key is a backoff strategy (symbol), value is parameters for the strategy. Is used in batch operations. Default id `nil`
799
- * `backoff_strategies`: is a hash and contains all available strategies. Default is { constant: ..., exponential: ...}
800
- * `http_continue_timeout`: The number of seconds to wait for a 100-continue HTTP response before sending the request body. Default option value is `nil`. If not specified effected value is `1`
801
- * `http_idle_timeout`: The number of seconds an HTTP connection is allowed to sit idble before it is considered stale. Default option value is `nil`. If not specified effected value is `5`
802
- * `http_open_timeout`: The number of seconds to wait when opening a HTTP session. Default option value is `nil`. If not specified effected value is `15`
803
- * `http_read_timeout`:The number of seconds to wait for HTTP response data. Default option value is `nil`. If not specified effected value is `60`
1055
+ * `timestamps` - by default Dynamoid sets `created_at` and `updated_at`
1056
+ fields for model creation and updating. You can disable this
1057
+ behavior by setting `false` value
1058
+ * `sync_retry_max_times` - when Dynamoid creates or deletes table
1059
+ synchronously it checks for completion specified times. Default is 60
1060
+ (times). It's a bit over 2 minutes by default
1061
+ * `sync_retry_wait_seconds` - time to wait between retries. Default is 2
1062
+ (seconds)
1063
+ * `convert_big_decimal` - if `true` then Dynamoid converts numbers
1064
+ stored in `Hash` in `raw` field to float. Default is `false`
1065
+ * `store_attribute_with_nil_value` - if `true` Dynamoid keeps attribute
1066
+ with `nil` value in a document. Otherwise Dynamoid removes it while
1067
+ saving a document. Default is `nil` which equals behaviour with `false`
1068
+ value.
1069
+ * `models_dir` - `dynamoid:create_tables` rake task loads DynamoDb
1070
+ models from this directory. Default is `./app/models`.
1071
+ * `application_timezone` - Dynamoid converts all `datetime` fields to
1072
+ specified time zone when loads data from the storage.
1073
+ Acceptable values - `:utc`, `:local` (to use system time zone) and
1074
+ time zone name e.g. `Eastern Time (US & Canada)`. Default is `utc`
1075
+ * `dynamodb_timezone` - When a datetime field is stored in string format
1076
+ Dynamoid converts it to specified time zone when saves a value to the
1077
+ storage. Acceptable values - `:utc`, `:local` (to use system time
1078
+ zone) and time zone name e.g. `Eastern Time (US & Canada)`. Default is
1079
+ `utc`
1080
+ * `store_datetime_as_string` - if `true` then Dynamoid stores :datetime
1081
+ fields in ISO 8601 string format. Default is `false`
1082
+ * `store_date_as_string` - if `true` then Dynamoid stores :date fields
1083
+ in ISO 8601 string format. Default is `false`
1084
+ * `store_boolean_as_native` - if `true` Dynamoid stores boolean fields
1085
+ as native DynamoDB boolean values. Otherwise boolean fields are stored
1086
+ as string values `'t'` and `'f'`. Default is true
1087
+ * `backoff` - is a hash: key is a backoff strategy (symbol), value is
1088
+ parameters for the strategy. Is used in batch operations. Default id
1089
+ `nil`
1090
+ * `backoff_strategies`: is a hash and contains all available strategies.
1091
+ Default is { constant: ..., exponential: ...}
1092
+ * `log_formatter`: overrides default AWS SDK formatter. There are
1093
+ several canned formatters: `Aws::Log::Formatter.default`,
1094
+ `Aws::Log::Formatter.colored` and `Aws::Log::Formatter.short`. Please
1095
+ look into `Aws::Log::Formatter` AWS SDK documentation in order to
1096
+ provide own formatter.
1097
+ * `http_continue_timeout`: The number of seconds to wait for a
1098
+ 100-continue HTTP response before sending the request body. Default
1099
+ option value is `nil`. If not specified effected value is `1`
1100
+ * `http_idle_timeout`: The number of seconds an HTTP connection is
1101
+ allowed to sit idle before it is considered stale. Default option
1102
+ value is `nil`. If not specified effected value is `5`
1103
+ * `http_open_timeout`: The number of seconds to wait when opening a HTTP
1104
+ session. Default option value is `nil`. If not specified effected
1105
+ value is `15`
1106
+ * `http_read_timeout`:The number of seconds to wait for HTTP response
1107
+ data. Default option value is `nil`. If not specified effected value
1108
+ is `60`
804
1109
 
805
1110
 
806
1111
  ## Concurrency
807
1112
 
808
- Dynamoid supports basic, ActiveRecord-like optimistic locking on save operations. Simply add a `lock_version` column to your table like so:
1113
+ Dynamoid supports basic, ActiveRecord-like optimistic locking on save
1114
+ operations. Simply add a `lock_version` column to your table like so:
809
1115
 
810
1116
  ```ruby
811
1117
  class MyTable
@@ -817,23 +1123,38 @@ class MyTable
817
1123
  end
818
1124
  ```
819
1125
 
820
- In this example, all saves to `MyTable` will raise an `Dynamoid::Errors::StaleObjectError` if a concurrent process loaded, edited, and saved the same row. Your code should trap this exception, reload the row (so that it will pick up the newest values), and try the save again.
1126
+ In this example, all saves to `MyTable` will raise an
1127
+ `Dynamoid::Errors::StaleObjectError` if a concurrent process loaded,
1128
+ edited, and saved the same row. Your code should trap this exception,
1129
+ reload the row (so that it will pick up the newest values), and try the
1130
+ save again.
821
1131
 
822
- Calls to `update` and `update!` also increment the `lock_version`, however they do not check the existing value. This guarantees that a update operation will raise an exception in a concurrent save operation, however a save operation will never cause an update to fail. Thus, `update` is useful & safe only for doing atomic operations (e.g. increment a value, add/remove from a set, etc), but should not be used in a read-modify-write pattern.
1132
+ Calls to `update` and `update!` also increment the `lock_version`,
1133
+ however, they do not check the existing value. This guarantees that a
1134
+ update operation will raise an exception in a concurrent save operation,
1135
+ however a save operation will never cause an update to fail. Thus,
1136
+ `update` is useful & safe only for doing atomic operations (e.g.
1137
+ increment a value, add/remove from a set, etc), but should not be used
1138
+ in a read-modify-write pattern.
823
1139
 
824
1140
 
825
1141
  ### Backoff strategies
826
1142
 
827
1143
 
828
- You can use several methods that run efficiently in batch mode like `.find_all` and `.import`. It affects `Query` and `Scan` operations as well.
1144
+ You can use several methods that run efficiently in batch mode like
1145
+ `.find_all` and `.import`. It affects `Query` and `Scan` operations as
1146
+ well.
829
1147
 
830
- The backoff strategy will be used when, for any reason, some items could not be processed as part of a batch mode command.
831
- Operations will be re-run to process these items.
1148
+ The backoff strategy will be used when, for any reason, some items could
1149
+ not be processed as part of a batch mode command. Operations will be
1150
+ re-run to process these items.
832
1151
 
833
- Exponential backoff is the recommended way to handle throughput limits exceeding and throttling on the table.
1152
+ Exponential backoff is the recommended way to handle throughput limits
1153
+ exceeding and throttling on the table.
834
1154
 
835
- There are two built-in strategies - constant delay and truncated binary exponential backoff.
836
- By default no backoff is used but you can specify one of the built-in ones:
1155
+ There are two built-in strategies - constant delay and truncated binary
1156
+ exponential backoff. By default no backoff is used but you can specify
1157
+ one of the built-in ones:
837
1158
 
838
1159
  ```ruby
839
1160
  Dynamoid.configure do |config|
@@ -846,7 +1167,8 @@ end
846
1167
 
847
1168
  ```
848
1169
 
849
- You can just specify strategy without any arguments to use default presets:
1170
+ You can just specify strategy without any arguments to use default
1171
+ presets:
850
1172
 
851
1173
  ```ruby
852
1174
  Dynamoid.configure do |config|
@@ -854,7 +1176,7 @@ Dynamoid.configure do |config|
854
1176
  end
855
1177
  ```
856
1178
 
857
- You can use your own strategy in following way:
1179
+ You can use your own strategy in the following way:
858
1180
 
859
1181
  ```ruby
860
1182
  Dynamoid.configure do |config|
@@ -871,10 +1193,11 @@ end
871
1193
 
872
1194
  There are a few Rake tasks available out of the box:
873
1195
 
874
- * `rake dynamoid:create_tables`
875
- * `rake dynamoid:ping`
1196
+ * `rake dynamoid:create_tables`
1197
+ * `rake dynamoid:ping`
876
1198
 
877
- In order to use them in non-Rails application they should be required explicitly:
1199
+ In order to use them in non-Rails application they should be required
1200
+ explicitly:
878
1201
 
879
1202
  ```ruby
880
1203
  # Rakefile
@@ -883,12 +1206,14 @@ Rake::Task.define_task(:environment)
883
1206
  require 'dynamoid/tasks'
884
1207
  ```
885
1208
 
886
- The Rake tasks depend on `:environment` task so it should be declared
887
- as well.
1209
+ The Rake tasks depend on `:environment` task so it should be declared as
1210
+ well.
888
1211
 
889
1212
  ## Test Environment
890
1213
 
891
- In test environment you will most likely want to clean the database between test runs to keep tests completely isolated. This can be achieved like so
1214
+ In test environment you will most likely want to clean the database
1215
+ between test runs to keep tests completely isolated. This can be
1216
+ achieved like so
892
1217
 
893
1218
  ```ruby
894
1219
  module DynamoidReset
@@ -919,7 +1244,8 @@ RSpec.configure do |config|
919
1244
  end
920
1245
  ```
921
1246
 
922
- In Rails, you may also want to ensure you do not delete non-test data accidentally by adding the following to your test environment setup:
1247
+ In Rails, you may also want to ensure you do not delete non-test data
1248
+ accidentally by adding the following to your test environment setup:
923
1249
 
924
1250
  ```ruby
925
1251
  raise "Tests should be run in 'test' environment only" if Rails.env != 'test'
@@ -956,9 +1282,14 @@ timing (231.28 ms).
956
1282
 
957
1283
  ## Credits
958
1284
 
959
- Dynamoid borrows code, structure, and even its name very liberally from the truly amazing [Mongoid](https://github.com/mongoid/mongoid). Without Mongoid to crib from none of this would have been possible, and I hope they don't mind me reusing their very awesome ideas to make DynamoDB just as accessible to the Ruby world as MongoDB.
1285
+ Dynamoid borrows code, structure, and even its name very liberally from
1286
+ the truly amazing [Mongoid](https://github.com/mongoid/mongoid). Without
1287
+ Mongoid to crib from none of this would have been possible, and I hope
1288
+ they don't mind me reusing their very awesome ideas to make DynamoDB
1289
+ just as accessible to the Ruby world as MongoDB.
960
1290
 
961
- Also, without contributors the project wouldn't be nearly as awesome. So many thanks to:
1291
+ Also, without contributors the project wouldn't be nearly as awesome. So
1292
+ many thanks to:
962
1293
 
963
1294
  * [Logan Bowers](https://github.com/loganb)
964
1295
  * [Lane LaRue](https://github.com/luxx)
@@ -979,9 +1310,12 @@ Also, without contributors the project wouldn't be nearly as awesome. So many th
979
1310
 
980
1311
  ## Running the tests
981
1312
 
982
- Running the tests is fairly simple. You should have an instance of DynamoDB running locally. Follow these steps to setup your test environment.
1313
+ Running the tests is fairly simple. You should have an instance of
1314
+ DynamoDB running locally. Follow these steps to setup your test
1315
+ environment.
983
1316
 
984
- * First download and unpack the latest version of DynamoDB. We have a script that will do this for you if you use homebrew on a Mac.
1317
+ * First download and unpack the latest version of DynamoDB. We have a
1318
+ script that will do this for you if you use homebrew on a Mac.
985
1319
 
986
1320
  ```shell
987
1321
  bin/setup
@@ -999,13 +1333,18 @@ Running the tests is fairly simple. You should have an instance of DynamoDB runn
999
1333
  rake
1000
1334
  ```
1001
1335
 
1002
- * When you are done, remember to stop the local test instance of dynamodb
1336
+ * When you are done, remember to stop the local test instance of
1337
+ dynamodb
1003
1338
 
1004
1339
  ```shell
1005
1340
  bin/stop_dynamodblocal
1006
1341
  ```
1007
1342
 
1008
- If you want to run all the specs that travis runs, use `bundle exec wwtd`, but first you will need to setup all the rubies, for each of `%w( 2.0.0-p648 2.1.10 2.2.6 2.3.3 2.4.1 jruby-9.1.8.0 )`. When you run `bundle exec wwtd` it will take care of starting and stopping the local dynamodb instance.
1343
+ If you want to run all the specs that travis runs, use `bundle exec
1344
+ wwtd`, but first you will need to setup all the rubies, for each of `%w(
1345
+ 2.0.0-p648 2.1.10 2.2.6 2.3.3 2.4.1 jruby-9.1.8.0 )`. When you run
1346
+ `bundle exec wwtd` it will take care of starting and stopping the local
1347
+ dynamodb instance.
1009
1348
 
1010
1349
  ```shell
1011
1350
  rvm use 2.0.0-p648