dynamoid_advanced_where 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +100 -0
  3. data/.gitignore +12 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +19 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +5 -0
  8. data/Appraisals +8 -0
  9. data/Gemfile +9 -0
  10. data/Gemfile.lock +119 -0
  11. data/README.md +375 -0
  12. data/Rakefile +6 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/dynamoid_advanced_where.gemspec +41 -0
  16. data/gemfiles/.bundle/config +2 -0
  17. data/gemfiles/dynamoid_3.4.gemfile +8 -0
  18. data/gemfiles/dynamoid_3.4.gemfile.lock +118 -0
  19. data/gemfiles/dynamoid_latest.gemfile +8 -0
  20. data/gemfiles/dynamoid_latest.gemfile.lock +118 -0
  21. data/lib/dynamoid_advanced_where.rb +8 -0
  22. data/lib/dynamoid_advanced_where/batched_updater.rb +229 -0
  23. data/lib/dynamoid_advanced_where/filter_builder.rb +136 -0
  24. data/lib/dynamoid_advanced_where/integrations/model.rb +34 -0
  25. data/lib/dynamoid_advanced_where/nodes.rb +15 -0
  26. data/lib/dynamoid_advanced_where/nodes/and_node.rb +43 -0
  27. data/lib/dynamoid_advanced_where/nodes/base_node.rb +18 -0
  28. data/lib/dynamoid_advanced_where/nodes/equality_node.rb +37 -0
  29. data/lib/dynamoid_advanced_where/nodes/exists_node.rb +44 -0
  30. data/lib/dynamoid_advanced_where/nodes/field_node.rb +186 -0
  31. data/lib/dynamoid_advanced_where/nodes/greater_than_node.rb +25 -0
  32. data/lib/dynamoid_advanced_where/nodes/includes.rb +29 -0
  33. data/lib/dynamoid_advanced_where/nodes/less_than_node.rb +27 -0
  34. data/lib/dynamoid_advanced_where/nodes/literal_node.rb +28 -0
  35. data/lib/dynamoid_advanced_where/nodes/not.rb +35 -0
  36. data/lib/dynamoid_advanced_where/nodes/null_node.rb +25 -0
  37. data/lib/dynamoid_advanced_where/nodes/operation_node.rb +44 -0
  38. data/lib/dynamoid_advanced_where/nodes/or_node.rb +41 -0
  39. data/lib/dynamoid_advanced_where/nodes/root_node.rb +47 -0
  40. data/lib/dynamoid_advanced_where/nodes/subfield.rb +17 -0
  41. data/lib/dynamoid_advanced_where/query_builder.rb +47 -0
  42. data/lib/dynamoid_advanced_where/query_materializer.rb +73 -0
  43. data/lib/dynamoid_advanced_where/version.rb +3 -0
  44. metadata +216 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 92ed86764764bc4cb565a2499dcbb686dcc82c043e2e6739d5e9a43a508096e7
4
+ data.tar.gz: ab2b862945f23e4289b7231346ad81a457fba65c222b0c1b4bd0f219f4ca2d8c
5
+ SHA512:
6
+ metadata.gz: 1ea844c390551ebfb5813ac4c6c4f66a5acd31699fbe964e10fa08b912d464f46ff1685397f29c0dc1aed951432ffa03e1040b9d82cc25fdc692941447688f6c
7
+ data.tar.gz: 964bcf57c3c19b94853d711b3bdfda1141a6ee113f9532d86452435372959beeb9c58ca81940c34520e03a32c4d9af2f3e5d91f4991bc027d6a8a94dd23dfce4
@@ -0,0 +1,100 @@
1
+ version: 2.0
2
+
3
+ workflows:
4
+ version: 2
5
+ build:
6
+ jobs:
7
+ - "ruby-2.5"
8
+ - "ruby-2.6"
9
+ - "ruby-2.7"
10
+
11
+ jobs:
12
+ "ruby-2.5":
13
+ docker:
14
+ - image: ruby:2.5
15
+ - image: amazon/dynamodb-local
16
+ environment:
17
+ MAX_HEAP_SIZE: 1024m
18
+ HEAP_NEWSIZE: 512m
19
+
20
+ steps:
21
+ - checkout
22
+ - restore_cache:
23
+ keys:
24
+ - v1-dependencies-{{ checksum "Gemfile.lock" }}-{{ checksum "Appraisals" }}
25
+ # fallback to using the latest cache if no exact match is found
26
+ - v1-dependencies-
27
+
28
+ - run:
29
+ name: install dependencies
30
+ command: |
31
+ gem update bundler
32
+ bundle install
33
+ bundle exec appraisal install
34
+ - save_cache:
35
+ paths:
36
+ - ./vendor/bundle
37
+ key: v1-dependencies-{{ checksum "Gemfile.lock" }}-{{ checksum "Appraisals" }}
38
+ - run: bundle exec appraisal rspec --format progress
39
+
40
+ "ruby-2.6":
41
+ docker:
42
+ - image: ruby:2.6
43
+ - image: amazon/dynamodb-local
44
+ environment:
45
+ MAX_HEAP_SIZE: 1024m
46
+ HEAP_NEWSIZE: 512m
47
+
48
+ steps:
49
+ - checkout
50
+ - restore_cache:
51
+ keys:
52
+ - v1-dependencies-{{ checksum "Gemfile.lock" }}-{{ checksum "Appraisals" }}
53
+ # fallback to using the latest cache if no exact match is found
54
+ - v1-dependencies-
55
+
56
+ - run:
57
+ name: install dependencies
58
+ command: |
59
+ gem update bundler
60
+ bundle install
61
+ bundle exec appraisal install
62
+ - save_cache:
63
+ paths:
64
+ - ./vendor/bundle
65
+ key: v1-dependencies-{{ checksum "Gemfile.lock" }}-{{ checksum "Appraisals" }}
66
+ - run: bundle exec appraisal rspec --format progress
67
+
68
+ "ruby-2.7":
69
+ docker:
70
+ - image: ruby:2.7
71
+ - image: amazon/dynamodb-local
72
+ environment:
73
+ MAX_HEAP_SIZE: 1024m
74
+ HEAP_NEWSIZE: 512m
75
+
76
+ steps:
77
+ - checkout
78
+ - restore_cache:
79
+ keys:
80
+ - v1-dependencies-{{ checksum "Gemfile.lock" }}-{{ checksum "Appraisals" }}
81
+ # fallback to using the latest cache if no exact match is found
82
+ - v1-dependencies-
83
+
84
+ - run:
85
+ name: install dependencies
86
+ command: |
87
+ gem update bundler
88
+ bundle install
89
+ bundle exec appraisal install
90
+ - save_cache:
91
+ paths:
92
+ - ./vendor/bundle
93
+ key: v1-dependencies-{{ checksum "Gemfile.lock" }}-{{ checksum "Appraisals" }}
94
+ - run: bundle exec appraisal rspec --format progress
95
+
96
+
97
+
98
+
99
+
100
+
@@ -0,0 +1,12 @@
1
+ tags
2
+ /.bundle/
3
+ /.yardoc
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,19 @@
1
+ AllCops:
2
+ Exclude:
3
+ - bin/*
4
+ - spec/**/*.rb
5
+ - vendor/**/*
6
+
7
+ Style/Documentation:
8
+ Enabled: false
9
+
10
+ Style/TrailingCommaInArguments:
11
+ Enabled: false
12
+
13
+ Metrics/LineLength:
14
+ Exclude:
15
+ - 'lib/protobufs/**/*.rb'
16
+
17
+ Metrics/BlockLength:
18
+ Exclude:
19
+ - 'spec/**/*_spec.rb'
@@ -0,0 +1 @@
1
+ 2.6.3
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.0
5
+ before_install: gem install bundler -v 1.16.1
@@ -0,0 +1,8 @@
1
+
2
+ appraise "dynamoid-3.4" do
3
+ gem "dynamoid", "~> 3.4.0"
4
+ end
5
+
6
+ appraise "dynamoid-latest" do
7
+ gem "dynamoid", "~> 3.0"
8
+ end
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in dynamoid-advanced-where.gemspec
6
+ gemspec
7
+
8
+ gem 'pry'
9
+
@@ -0,0 +1,119 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ dynamoid_advanced_where (1.0.0)
5
+ dynamoid (>= 3.2, < 4)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (6.0.2.1)
11
+ activesupport (= 6.0.2.1)
12
+ activesupport (6.0.2.1)
13
+ concurrent-ruby (~> 1.0, >= 1.0.2)
14
+ i18n (>= 0.7, < 2)
15
+ minitest (~> 5.1)
16
+ tzinfo (~> 1.1)
17
+ zeitwerk (~> 2.2)
18
+ appraisal (2.2.0)
19
+ bundler
20
+ rake
21
+ thor (>= 0.14.0)
22
+ ast (2.4.0)
23
+ aws-eventstream (1.0.3)
24
+ aws-partitions (1.270.0)
25
+ aws-sdk-core (3.89.1)
26
+ aws-eventstream (~> 1.0, >= 1.0.2)
27
+ aws-partitions (~> 1, >= 1.239.0)
28
+ aws-sigv4 (~> 1.1)
29
+ jmespath (~> 1.0)
30
+ aws-sdk-dynamodb (1.41.0)
31
+ aws-sdk-core (~> 3, >= 3.71.0)
32
+ aws-sigv4 (~> 1.1)
33
+ aws-sigv4 (1.1.0)
34
+ aws-eventstream (~> 1.0, >= 1.0.2)
35
+ bundler-audit (0.6.1)
36
+ bundler (>= 1.2.0, < 3)
37
+ thor (~> 0.18)
38
+ childprocess (3.0.0)
39
+ coderay (1.1.2)
40
+ colorize (0.8.1)
41
+ concurrent-ruby (1.1.5)
42
+ diff-lcs (1.3)
43
+ dynamoid (3.4.1)
44
+ activemodel (>= 4)
45
+ aws-sdk-dynamodb (~> 1)
46
+ concurrent-ruby (>= 1.0)
47
+ null-logger
48
+ fasterer (0.8.2)
49
+ colorize (~> 0.7)
50
+ ruby_parser (>= 3.14.1)
51
+ i18n (1.8.2)
52
+ concurrent-ruby (~> 1.0)
53
+ iniparse (1.4.4)
54
+ jaro_winkler (1.5.4)
55
+ jmespath (1.4.0)
56
+ method_source (0.9.2)
57
+ minitest (5.14.0)
58
+ null-logger (0.1.5)
59
+ overcommit (0.52.1)
60
+ childprocess (>= 0.6.3, < 4)
61
+ iniparse (~> 1.4)
62
+ parallel (1.19.1)
63
+ parser (2.7.0.2)
64
+ ast (~> 2.4.0)
65
+ pry (0.12.2)
66
+ coderay (~> 1.1.0)
67
+ method_source (~> 0.9.0)
68
+ rainbow (3.0.0)
69
+ rake (10.5.0)
70
+ rspec (3.9.0)
71
+ rspec-core (~> 3.9.0)
72
+ rspec-expectations (~> 3.9.0)
73
+ rspec-mocks (~> 3.9.0)
74
+ rspec-core (3.9.1)
75
+ rspec-support (~> 3.9.1)
76
+ rspec-expectations (3.9.0)
77
+ diff-lcs (>= 1.2.0, < 2.0)
78
+ rspec-support (~> 3.9.0)
79
+ rspec-mocks (3.9.1)
80
+ diff-lcs (>= 1.2.0, < 2.0)
81
+ rspec-support (~> 3.9.0)
82
+ rspec-support (3.9.2)
83
+ rubocop (0.79.0)
84
+ jaro_winkler (~> 1.5.1)
85
+ parallel (~> 1.10)
86
+ parser (>= 2.7.0.1)
87
+ rainbow (>= 2.2.2, < 4.0)
88
+ ruby-progressbar (~> 1.7)
89
+ unicode-display_width (>= 1.4.0, < 1.7)
90
+ ruby-progressbar (1.10.1)
91
+ ruby_parser (3.14.2)
92
+ sexp_processor (~> 4.9)
93
+ sexp_processor (4.14.0)
94
+ thor (0.20.3)
95
+ thread_safe (0.3.6)
96
+ tzinfo (1.2.6)
97
+ thread_safe (~> 0.1)
98
+ unicode-display_width (1.6.1)
99
+ zeitwerk (2.2.2)
100
+
101
+ PLATFORMS
102
+ ruby
103
+ x86_64-darwin-17
104
+ x86_64-darwin-18
105
+
106
+ DEPENDENCIES
107
+ appraisal
108
+ bundler (>= 1.16)
109
+ bundler-audit
110
+ dynamoid_advanced_where!
111
+ fasterer
112
+ overcommit
113
+ pry
114
+ rake (~> 10.0)
115
+ rspec (~> 3.0)
116
+ rubocop
117
+
118
+ BUNDLED WITH
119
+ 2.1.4
@@ -0,0 +1,375 @@
1
+ # Dynamoid Advanced Where (DAW)
2
+
3
+ Dynamoid Advanced where provides a more advanced query structure for selecting,
4
+ and updating records. This is very much a work in progress and functionality is
5
+ being added as it is needed.
6
+
7
+ This gem is tested against:
8
+ * MRI 2.5, 2.6, and 2.7
9
+ * Dynamoid 3.4
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'dynamoid_advanced_where'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ ## Upgrading
24
+ From pre 1.0
25
+
26
+ New where block format:
27
+ ```
28
+ # Previously you had to do this to get access to certain scoped variables
29
+ local = getValue(123)
30
+ Model.where do
31
+ field == local
32
+ end
33
+
34
+ # This is annoying, the new search block has deprecated the argument-less block, and now should be called
35
+ # with a single argument
36
+
37
+ Model.where do |r|
38
+ r.field == getValue(123)
39
+ end
40
+ ```
41
+
42
+ Existence checks have been changed:
43
+ ```ruby
44
+ # Old
45
+ Model.where{|r| r.field }
46
+
47
+ # New
48
+ Model.where{|r| r.field.exists? }
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ The HellowWorld usage for this app is basic search and retrieval. You can
54
+ invoke DAW by calling `where` on a Dynamoid::Document (No relations yet) using
55
+ a new block form.
56
+
57
+ ```ruby
58
+ class Foo
59
+ include Dynamoid::Document
60
+
61
+ field :bar
62
+ field :baz
63
+ end
64
+
65
+ # Returns all records with `bar` equal to 'hello'
66
+ Foo.where{|r| r.bar == 'hello' }.all
67
+
68
+ # Advanced boolean logic is also supported
69
+
70
+ # Returns all records with `bar` equal to 'hello' and `baz` equal to 'dude'
71
+ x = Foo.where{|r| (r.baz == 'dude') & (r.bar == 'hello') }.all
72
+ ```
73
+
74
+ **Note:** Those `()` are required, you do remember your [operator precedence](https://ruby-doc.org/core-2.2.0/doc/syntax/precedence_rdoc.html)
75
+ right?
76
+
77
+ ## Filtering
78
+ Filter can be applied to Queries (Searches by hash key), Scans, and update
79
+ actions provided by this gem. Not all persistence actions make sense at the end
80
+ of a filtering query, such as `create`.
81
+
82
+ ### Field Existence
83
+ Checks to see if a field is defined. See [attribute_exists](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html)
84
+
85
+ Valid on field types: `any`
86
+
87
+ #### Example
88
+ `where{|r| r.foo }` or `where{|r| r.foo.exists! }`
89
+
90
+ ### Value Equality
91
+ The equality of a field can be tested using `==` and not equals tested using `!=`
92
+
93
+ Valid on field types: `string`
94
+
95
+ #### Example
96
+ `where{|r| r.foo == 'bar' }` and `where{|r| r.foo != 'bar' }`
97
+
98
+ ### Less than
99
+ The less than for a field can be tested using `<`
100
+
101
+ Valid on field types: `numeric`, and `datetime` (only when stored as a number)
102
+
103
+ #### Example
104
+ `where{|r| r.foo < 123 }` and `where{|r| r.foo < Date.today }`
105
+
106
+ ### Includes
107
+ This operator may be used to check if:
108
+
109
+ * A string contains another substring
110
+ * A set of String or Integers contain a given value
111
+
112
+ Valid on field types: `string`, or `set` of `String` / `Integer`
113
+
114
+ #### Example
115
+ `where{|r| r.foo.includes?(123) }` and `where{|r| r.foo.includes?('foo') }`
116
+
117
+ ### Working with Map and Raw types
118
+ When it comes to map and raw attribute types, DAW takes the approach of
119
+ trusting you, since the exact format is not explicitly defined or enforced.
120
+ You may specify the path to the value, as well as the value type and it will
121
+ behave like any other top level attribute.
122
+
123
+ ```
124
+ where do |r|
125
+ (r.ratings.dig(:verified_reviews, :review_count, type: :number) > 100) &
126
+ (r.ratings.dig(:verified_reviews, :average_review, type: :number) > 4) &
127
+ (r.metadata.dig(:keywords, type: :set, of: :string).includes?('foo'))
128
+ end
129
+ ```
130
+
131
+ If you have a nested array, you may access the elements by index by passing an integer into the `dig` command.
132
+
133
+ #### Custom Classes
134
+ The subfield dig works with CustomClasses if the classes store their data as a hash.
135
+
136
+ **Example**
137
+
138
+ ```ruby
139
+ CustomAttribute = Struct.new(:sub_field_a) do
140
+ def self.dynamoid_dump(item)
141
+ item.to_h
142
+ end
143
+
144
+ def self.dynamoid_load(data)
145
+ new(**data.transform_keys(&:to_sym))
146
+ end
147
+ end
148
+
149
+ class Foo
150
+ include Dynamoid::Document
151
+ field :bar, CustomAttribute
152
+ end
153
+
154
+ x = Foo.create(bar: CustomAttribute.new('b'))
155
+ Foo.where{|r| r.bar.dig(:sub_field_a, type: string).inclues?('b') }.all
156
+ # => [x]
157
+ ```
158
+
159
+ ### Boolean Operators
160
+
161
+ | Logical Operator | Behavior | Example
162
+ | ------------- | ------------- | --------
163
+ | `&` | and | <code>where{&#124;r&#124; (r.foo == 'bar') & (r.baz == 'nitch') }</code>
164
+ | <code>&#124;</code> | or | <code> where{&#124;r&#124; (r.foo == 'bar') &#124; (r.baz == 'nitch') } </code>
165
+ | `!` | negation | <code>where{&#124;r&#124; !( (r.foo == 'bar') & (r.baz == 'nitch')) }</code>
166
+
167
+ ## Retrieving Records
168
+ Retrieving a pre-filtered set of records is a fairly obvious use case for the
169
+ filtering abilities provided by DAW. Only a subset of what you may expect is
170
+ provided, but enumerable is mixed in, and each provides an Enumerator.
171
+
172
+ Provided methods
173
+ * `all`
174
+ * `first`
175
+ * `each` (and related enumerable methods)
176
+
177
+ ### Scan vs Query
178
+ DAW will automatically preform a query when it determines it is possible,
179
+ however if a query is determined to not be appropriate, a scan will be conduced
180
+ instead. When ever possible, query do not scan. See the DynamoDB docs for why.
181
+
182
+ DAW will also extract filters on the range key whenever possible. In order to
183
+ filter on a range key to be used for a query, it must be one of the allowed
184
+ range key filters and at the top level of filters.
185
+
186
+
187
+ **NOTE:** Global Secondary Indices are not yet supported
188
+
189
+ #### How a query-able filter is identified
190
+ A scan will be performed when the search is not done via the hash key, with
191
+ exact equality. DAW will examine the boolean logic to determine if a key
192
+ condition may be extracted. For example, a query will be performed in the
193
+ following examples:
194
+
195
+ * `where{|r| r.id == '123' }`
196
+ * `where{|r| (r.id == '123') & (r.bar == 'baz') }`
197
+
198
+ But it will not be performed in these scenarios
199
+
200
+ * `where{|r| r.id != '123' }`
201
+ * `where{|r| !(r.id == '123') }`
202
+ * <code>where{ (r.id == '123') &#124; (r.bar == 'baz') }</code>
203
+
204
+ ## Combination of Filters
205
+ Multiple DAW filters can be combined. This will provides the ability to compose
206
+ filtering conditions to keep your code more readable and DRY.
207
+
208
+ ### Combining conditions with AND
209
+ ```ruby
210
+ class Foo
211
+ include Dynamoid::Document
212
+
213
+ field :bar
214
+ field :baz
215
+ end
216
+
217
+ filter1 = Foo.where{|r| r.bar == 'abcd' }
218
+ filter2 = Foo.where{|r| r.baz == 'dude' }
219
+
220
+ # All of these produce the same results
221
+ combination1 = filter1.where(filter2)
222
+ combination2 = filter1.and(filter2)
223
+ combination3 = filter1.where{|r| r.baz == 'dude' }
224
+ ```
225
+
226
+ ## Mutating Records
227
+ DAW provides the ability to modify records only if they meet the criteria defined
228
+ by the where block.
229
+
230
+ Changes are also provided in batch form, so you may change multiple values with a single call.
231
+ There may also be singleton methods provided for easy of use.
232
+
233
+ ## Batch Updates
234
+
235
+ ```ruby
236
+ Model.where{ conditions }.batch_update
237
+ .set_values(field_name1: 'value', field_name2: 123)
238
+ .append_to(arr_field_name: [1,2,3], set_field_name: %w[a b c])
239
+ .apply(hash_key, range_key)
240
+ ```
241
+
242
+ Like all conditional updates it will return the full record with the new data
243
+ if it successfully updates. If it fails to update, it will return nil.
244
+
245
+ If the specified hash key, or hash/range key combination is not already present
246
+ it will be inserted with the desired mutations (if possible).
247
+
248
+ ### Setting a single field
249
+ The batch updated method `set_values(attr_name: new_attr_value, other_atter: val)`
250
+
251
+ #### Shortcut Method
252
+ You map perform an upsert using the `.upsert` method. This method performs a
253
+ simple set on the provided hash and range key.
254
+
255
+ For example, consider the following example for conditionally updating a string
256
+ field.
257
+
258
+ ```ruby
259
+ class Foo
260
+ include Dynamoid::Document
261
+ field :a_string
262
+ field :a_number, number
263
+ end
264
+
265
+ item = Foo.create(a_number: 5, a_string: 'bar')
266
+
267
+ Foo.where{|r| r.a_number > 5 }.upsert(item.id, a_string: 'dude')
268
+
269
+ item.reload.a_string # => 'bar'
270
+
271
+ Foo.where{|r| r.a_number > 4 }.upsert(item.id, a_string: 'wassup')
272
+
273
+ item.reload.a_string # => 'wassup'
274
+ ```
275
+
276
+ `upsert` can also create a record if an existing one is not found, if the hash
277
+ key can be specified. By requiring the hash key be set, you can prevent an insert
278
+ and force an update to occur.
279
+
280
+ **Note:** Upsert must be called with the hash as the first parameter, and
281
+ the range key as the second parameter if required for the model.
282
+
283
+ *Note:** Upsert will return nil if no records were found that matched the provided
284
+ parameters
285
+
286
+ ### Appending values to a List or Set
287
+ You can append a set of values to an existing set or array by using the
288
+
289
+ ```ruby
290
+ append_to(
291
+ array_field: [1,2,3],
292
+ set_field: %w[foo bar],
293
+ )
294
+ ```
295
+
296
+ If the fields are unset, it will still apply the changes to am empty array.
297
+
298
+ ### Increment / Decrement a value
299
+
300
+ You may increment or decrement a numeric value by using `increment` or `decrement`
301
+
302
+ ```ruby
303
+ increment(:field_one, :field_two)
304
+ ```
305
+
306
+ ```ruby
307
+ decrement(:field_one, :field_two)
308
+ ```
309
+
310
+ You may also provide an optional `by:` config to increment by more than one.
311
+
312
+ ```ruby
313
+ increment(:field_one, :field_two, by: 3)
314
+ ```
315
+
316
+ ```ruby
317
+ decrement(:field_one, :field_two, by: 3)
318
+ ```
319
+
320
+ If the value of the field is currently unset, it will initialize to zero
321
+
322
+ ## Development
323
+
324
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
325
+
326
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
327
+
328
+ ### TODO:
329
+
330
+ #### Known issues
331
+ * If you specify multiple term nodes for a query it will generate an invalid
332
+ query
333
+ * No support for [custom types](https://github.com/Dynamoid/Dynamoid#custom-types)
334
+
335
+ #### Enhancements
336
+ * Support Global Secondary Index
337
+ * Conditions:
338
+ * Equality
339
+ * Partially implemented
340
+ * Not Equals
341
+ * less than
342
+ * Implemented for numerics, datetimes, dates stored as integer
343
+ * less than or equal to
344
+ * greater than
345
+ * greater than or equal to
346
+ * between
347
+ * in
348
+ * attribute_not_exists
349
+ * attribute_type
350
+ * begins with
351
+ * contains
352
+ * size
353
+ * Query enhancements
354
+ * Range key conditions:
355
+ * equality
356
+ * less than
357
+ * less than or equal to
358
+ * greater than
359
+ * greater than or equal to
360
+ * between
361
+ * begins with
362
+ * convert to bulk query if multiple hash key terms are specified
363
+ * Item mutation [Docs](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.SET)
364
+ * Update (without insert)
365
+ * Upserting
366
+ * Increment / Decrement number
367
+ * Append item(s) to list
368
+ * Prepend item(s) to list
369
+ * Adding nested map attribute (low priority)
370
+ * Set value if not set
371
+ * Remove attributes
372
+
373
+ ## Contributing
374
+
375
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/dynamoid-advanced-where.