Skip to content

Commit a74b629

Browse files
authored
Merge pull request #846 from ckhsponge/transaction-update-block
TransactionWrite update_fields() with a block
2 parents 41f015a + 883ca94 commit a74b629

File tree

9 files changed

+864
-76
lines changed

9 files changed

+864
-76
lines changed

README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ DynamoDB is not like other document-based databases you might know, and
2424
is very different indeed from relational databases. It sacrifices
2525
anything beyond the simplest relational queries and transactional
2626
support to provide a fast, cost-efficient, and highly durable storage
27-
solution. If your database requires complicated relational queries and
28-
transaction support, then this modest Gem cannot provide them for you,
27+
solution. If your database requires complicated relational queries
28+
then this modest Gem cannot provide them for you
2929
and neither can DynamoDB. In those cases you would do better to look
3030
elsewhere for your database needs.
3131

@@ -1187,6 +1187,19 @@ Dynamoid::TransactionWrite.execute do |txn|
11871187
# sets the name and title for a user
11881188
# The user is found by id (that equals 1)
11891189
txn.update_fields(User, '1', name: 'bob', title: 'mister')
1190+
1191+
# sets the name, increments a count and deletes a field
1192+
txn.update_fields(User, 1) do |t|
1193+
t.set(name: 'bob')
1194+
t.add(article_count: 1)
1195+
t.delete(:title)
1196+
end
1197+
1198+
# adds to a set of integers and deletes from a set of strings
1199+
txn.update_fields(User, 2) do |t|
1200+
t.add(friend_ids: [1, 2])
1201+
t.delete(child_names: ['bebe'])
1202+
end
11901203
end
11911204
```
11921205

lib/dynamoid/errors.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,16 @@ class InvalidQuery < Error; end
8686

8787
class UnsupportedKeyType < Error; end
8888

89-
class UnknownAttribute < Error; end
89+
class UnknownAttribute < Error
90+
attr_reader :model_class, :attribute_name
91+
92+
def initialize(model_class, attribute_name)
93+
super("Attribute #{attribute_name} does not exist in #{model_class}")
94+
95+
@model_class = model_class
96+
@attribute_name = attribute_name
97+
end
98+
end
9099

91100
class SubclassNotFound < Error; end
92101

lib/dynamoid/fields.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ def write_attribute(name, value)
291291
old_value = read_attribute(name)
292292

293293
unless attribute_is_present_on_model?(name)
294-
raise Dynamoid::Errors::UnknownAttribute, "Attribute #{name} is not part of the model"
294+
raise Dynamoid::Errors::UnknownAttribute.new(self.class, name)
295295
end
296296

297297
if association = @associations[name]

lib/dynamoid/persistence.rb

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -620,8 +620,7 @@ def update_attribute(attribute, value)
620620
# attributes. Supports following operations: +add+, +delete+ and +set+.
621621
#
622622
# Operation +add+ just adds a value for numeric attributes and join
623-
# collections if attribute is a collection (one of +array+, +set+ or
624-
# +map+).
623+
# collections if attribute is a set.
625624
#
626625
# user.update! do |t|
627626
# t.add(age: 1, followers_count: 5)
@@ -646,7 +645,7 @@ def update_attribute(attribute, value)
646645
# {parameter}[https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LegacyConditionalParameters.AttributeUpdates.html]
647646
# of +UpdateItem+ operation.
648647
#
649-
# It's an atomic operation. So adding or deleting elements in a collection
648+
# It's atomic operations. So adding or deleting elements in a collection
650649
# or incrementing or decrementing a numeric field is atomic and does not
651650
# interfere with other write requests.
652651
#
@@ -716,8 +715,7 @@ def update!(conditions = {})
716715
# attributes. Supports following operations: +add+, +delete+ and +set+.
717716
#
718717
# Operation +add+ just adds a value for numeric attributes and join
719-
# collections if attribute is a collection (one of +array+, +set+ or
720-
# +map+).
718+
# collections if attribute is a set.
721719
#
722720
# user.update do |t|
723721
# t.add(age: 1, followers_count: 5)

lib/dynamoid/persistence/update_validations.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ module UpdateValidations
77
def self.validate_attributes_exist(model_class, attributes)
88
model_attributes = model_class.attributes.keys
99

10-
attributes.each_key do |attr_name|
11-
unless model_attributes.include?(attr_name)
12-
raise Dynamoid::Errors::UnknownAttribute, "Attribute #{attr_name} does not exist in #{model_class}"
10+
attributes.each_key do |name|
11+
unless model_attributes.include?(name)
12+
raise Dynamoid::Errors::UnknownAttribute.new(model_class, name)
1313
end
1414
end
1515
end

lib/dynamoid/transaction_write.rb

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
require 'dynamoid/transaction_write/update_fields'
99
require 'dynamoid/transaction_write/update_attributes'
1010
require 'dynamoid/transaction_write/upsert'
11+
require 'dynamoid/transaction_write/item_updater'
1112

1213
module Dynamoid
1314
# The class +TransactionWrite+ provides means to perform multiple modifying
@@ -345,6 +346,52 @@ def upsert(model_class, hash_key, range_key = nil, attributes) # rubocop:disable
345346
# t.update_fields(User, '1', 'Tylor', age: 26)
346347
# end
347348
#
349+
# Updates can also be performed in a block.
350+
#
351+
# Dynamoid::TransactionWrite.execute do |t|
352+
# t.update_fields(User, 1) do |u|
353+
# u.add(article_count: 1)
354+
# u.delete(favorite_colors: 'green')
355+
# u.set(age: 27, last_name: 'Tylor')
356+
# end
357+
# end
358+
#
359+
# Operation +add+ just adds a value for numeric attributes and join
360+
# collections if attribute is a set.
361+
#
362+
# t.update_fields(User, 1) do |u|
363+
# u.add(age: 1, followers_count: 5)
364+
# u.add(hobbies: ['skying', 'climbing'])
365+
# end
366+
#
367+
# Operation +delete+ is applied to collection attribute types and
368+
# substructs one collection from another.
369+
#
370+
# t.update_fields(User, 1) do |u|
371+
# u.delete(hobbies: ['skying'])
372+
# end
373+
#
374+
# Operation +set+ just changes an attribute value:
375+
#
376+
# t.update_fields(User, 1) do |u|
377+
# u.set(age: 21)
378+
# end
379+
#
380+
# Operation +remove+ removes one or more attributes from an item.
381+
#
382+
# t.update_fields(User, 1) do |u|
383+
# u.remove(:age)
384+
# end
385+
#
386+
# All the operations work like +ADD+, +DELETE+, +REMOVE+, and +SET+ actions supported
387+
# by +UpdateExpression+
388+
# {parameter}[https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html]
389+
# of +UpdateItem+ operation.
390+
#
391+
# It's atomic operations. So adding or deleting elements in a collection
392+
# or incrementing or decrementing a numeric field is atomic and does not
393+
# interfere with other write requests.
394+
#
348395
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
349396
# attributes is not declared in the model class.
350397
#
@@ -353,8 +400,14 @@ def upsert(model_class, hash_key, range_key = nil, attributes) # rubocop:disable
353400
# @param range_key [Scalar value] range key value (optional)
354401
# @param attributes [Hash]
355402
# @return [nil]
356-
def update_fields(model_class, hash_key, range_key = nil, attributes) # rubocop:disable Style/OptionalArguments
357-
action = Dynamoid::TransactionWrite::UpdateFields.new(model_class, hash_key, range_key, attributes)
403+
def update_fields(model_class, hash_key, range_key = nil, attributes = nil, &block)
404+
# given no attributes, but there may be a block
405+
if range_key.is_a?(Hash) && !attributes
406+
attributes = range_key
407+
range_key = nil
408+
end
409+
410+
action = Dynamoid::TransactionWrite::UpdateFields.new(model_class, hash_key, range_key, attributes, &block)
358411
register_action action
359412
end
360413

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# frozen_string_literal: true
2+
3+
module Dynamoid
4+
class TransactionWrite
5+
class ItemUpdater
6+
attr_reader :attributes_to_set, :attributes_to_add, :attributes_to_delete, :attributes_to_remove
7+
8+
def initialize(model_class)
9+
@model_class = model_class
10+
11+
@attributes_to_set = {}
12+
@attributes_to_add = {}
13+
@attributes_to_delete = {}
14+
@attributes_to_remove = []
15+
end
16+
17+
def empty?
18+
[@attributes_to_set, @attributes_to_add, @attributes_to_delete, @attributes_to_remove].all?(&:empty?)
19+
end
20+
21+
def set(attributes)
22+
validate_attribute_names!(attributes.keys)
23+
@attributes_to_set.merge!(attributes)
24+
end
25+
26+
# adds to array of fields for use in REMOVE update expression
27+
def remove(*names)
28+
validate_attribute_names!(names)
29+
@attributes_to_remove += names
30+
end
31+
32+
# increments a number or adds to a set, starts at 0 or [] if it doesn't yet exist
33+
def add(attributes)
34+
validate_attribute_names!(attributes.keys)
35+
@attributes_to_add.merge!(attributes)
36+
end
37+
38+
# deletes a value or values from a set
39+
def delete(attributes)
40+
validate_attribute_names!(attributes.keys)
41+
@attributes_to_delete.merge!(attributes)
42+
end
43+
44+
private
45+
46+
def validate_attribute_names!(names)
47+
names.each do |name|
48+
unless @model_class.attributes[name]
49+
raise Dynamoid::Errors::UnknownAttribute.new(@model_class, name)
50+
end
51+
end
52+
end
53+
end
54+
end
55+
end

0 commit comments

Comments
 (0)