AWS Developer Tools Blog
Introducing the Aws::Record Developer Preview
We are happy to announce that the aws-record
gem is now in Developer Preview and available for you to try.
What Is Aws::Record?
In version 1 of the AWS SDK for Ruby, the AWS::Record
class provided a data mapping abstraction over Amazon DynamoDB operations. As version 2 of the AWS SDK for Ruby was being developed, many of you asked for an updated version of the library.
The aws-record
gem provides a data mapping abstraction for DynamoDB built on top of the AWS SDK for Ruby version 2.
Using Aws::Record
You can download the aws-record
gem from RubyGems by including the --pre
flag in a gem installation:
gem install 'aws-record' --pre
You can also include it in your Gemfile. Do not include a version lock yet, so that bundler
can find the pre-release version:
# Gemfile gem 'aws-record'
Defining a Model
To create an aws-record model, include the Aws::Record
module in your class definition:
require 'aws-record' class Forum include Aws::Record end
This will decorate your class with helper methods you can use to create a model compatible with DynamoDB’s table schemas. You might define keys for your table:
require 'aws-record' class Forum include Aws::Record string_attr :forum_uuid, hash_key: true integer_attr :post_id, range_key: true end
When you use these helper methods, you do not need to worry about how to define these attributes and types in DynamoDB. The helper methods and marshaler classes are able to define your table and item operations for you. The aws-record
gem comes with predefined attribute types that cover a variety of potential use cases:
require 'aws-record' class Forum include Aws::Record string_attr :forum_uuid, hash_key: true integer_attr :post_id, range_key: true string_attr :author_username string_attr :post_title string_attr :post_body datetime_attr :created_at map_attr :post_metadata end
Creating a DynamoDB Table
The aws-record
gem provides a helper class for table operations, such as migrations. If we wanted to create a table for our Forum
model in DynamoDB, we would run the following migration:
require 'forum' # Depending on where you defined the class above. migration = Aws::Record::TableMigration.new(Forum) migration.create!( provisioned_throughput: { read_capacity_units: 10, write_capacity_units: 4 } ) migration.wait_until_available # Blocks until table creation is complete.
Operations with DynamoDB Items
With a model and table defined, we can perform operations that relate to items in our table. Let’s create a post:
require 'forum' require 'securerandom' uuid = SecureRandom.uuid post = Forum.new post.forum_uuid = uuid post.post_id = 1 post.author_username = "User1" post.post_title = "Hello!" post.post_body = "Hello Aws::Record" post.created_at = Time.now post.post_metadata = { this_is_a: "Post", types_supported_include: ["String", "Integer", "DateTime"], how_many_times_ive_done_this: 1 } post.save # Writes to the database.
This example shows us some of the types that are supported and serialized for you. Using the key we’ve defined, we can also find this object in our table:
my_post = Forum.find(forum_uuid: uuid, post_id: 1) my_post.post_title # => "Hello!" my_post.created_at # => #<DateTime: 2016-02-09T14:39:07-08:00 ((2457428j,81547s,0n),-28800s,2299161j)>
You can use the same approach to save changes or, as shown here, you can delete the item from the table:
my_post.delete! # => true
At this point, we know how to use Aws::Record
to perform key-value store operations powered by DynamoDB and have an introduction to the types available for use in our tables.
Querying, Scanning, and Collections
Because it is likely that you’re probably doing Query and Scan operations in addition to key-value operations, aws-record
provides support for integrating them with your model class.
When you include the Aws::Record
module, your model class is decorated with #query
and #scan
methods, which correspond to the AWS SDK for Ruby client operations. The response is wrapped in a collection enumerable for you. Consider the following basic scan operation:
Forum.scan # => #<Aws::Record::ItemCollection:0x007ffc293ec790 @search_method=:scan, @search_params={:table_name=>"Forum"}, @model=Forum, @client=#<Aws::DynamoDB::Client>>
No client call has been made yet: ItemCollection
instances are lazy, and only make client calls only when needed. Because they provide an enumerable interface, you can use any of Ruby’s enumerable methods on your collection, and your result page is saved:
resp = Forum.scan resp.take(1) # Makes a call to the underlying client. Returns a 'Forum' object. resp.take(1) # Same result, but does not repeat the client call.
Because the Aws::Record::ItemCollection
uses version 2 ofthe AWS SDK for Ruby, pagination support is built-in. So, if your operation requires multiple DynamoDB client calls due to response truncation, ItemCollection
will handle the calls required in your enumeration:
def author_posts Forum.scan.inject({}) do |acc, post| author = post.author_username if acc[author] acc[author] += 1 else acc[author] = 1 end acc end end
The same applies for queries. Your query result will also be provided as an enumerable ItemCollection
:
def posts_by_forum(uuid) Forum.query( key_condition_expression: "#A = :a", expression_attribute_names: { "#A" => "forum_uuid" }, expression_attribute_values: { ":a" => uuid } ) end
Given this functionality, you have the flexibility to mix and match Ruby’s enumerable functionality with DynamoDB filter expressions, for example, to curate your results. These two functions return the same set of responses:
def posts_by_author_in_forum(uuid, author) posts_by_forum(uuid).select do |post| post.author_username == author end end def posts_by_author_in_forum_with_filter(uuid, author) Forum.query( key_condition_expression: "#A = :a", filter_expression: "#B = :b", expression_attribute_names: { "#A" => "forum_uuid", "#B" => "author_username" }, expression_attribute_values: { ":a" => uuid, ":b" => author } ) end
Support for Secondary Indexes
Aws::Record
also supports both local and global secondary indexes. Consider this modified version of our Forum table:
require 'aws-record' class IndexedForum include Aws::Record string_attr :forum_uuid, hash_key: true integer_attr :post_id, range_key: true string_attr :author_username string_attr :post_title string_attr :post_body datetime_attr :created_at map_attr :post_metadata global_secondary_index(:author, hash_key: :author_username, projection: { projection_type: "INCLUDE", non_key_attributes: ["post_title"] } ) local_secondary_index(:by_date, range_key: :created_at, projection: { projection_type: "ALL" } ) end
You can see the table’s attributes are the same, but we’ve included a couple potentially useful indexes.
-
:author
: This uses the author name as a partition, which provides a way to search across forums by author user name without having to scan and filter. Take note of the projection, because your global secondary index results will only return the:forum_uuid
,:post_id
,:author_username
, and:post_title
. Other attributes will be missing from this projection, and you would have to hydrate your item by calling#reload!
on the item instance. -
:by_date
: This provides a way to sort and search within a forum by post creation date.
To create this table with secondary indexes, you create a migration like we did before:
require 'indexed_forum' migration = Aws::Record::TableMigration.new(IndexedForum) migration.create!( provisioned_throughput: { read_capacity_units: 10, write_capacity_units: 4 }, global_secondary_index_throughput: { author: { read_capacity_units: 5, write_capacity_units: 3 } } ) migration.wait_until_available
You can use either of these indexes with the query interface:
require 'indexed_forum' def search_by_author(author) IndexedForum.query( index_name: "author", key_condition_expression: "#A = :a", expression_attribute_names: { "#A" => "author_username" }, expression_attribute_values: { ":a" => author } ) )
Secondary indexes can be a powerful performance tool, and aws-record
can simplify the process of managing them.
Get Involved!
Please download the gem, give it a try, and let us know what you think. This project is a work in progress, so we welcome feature requests, bug reports, and information about the kinds of problems you’d like to solve by using this gem. And, as with other SDKs and tools we produce, we’d also be happy to look at contributions.
You can find the project on GitHub at https://github.com/awslabs/aws-sdk-ruby-record
Please reach out and let us know what you think!