AWS Developer Tools Blog

Amazon DynamoDB Document API in Ruby (Part 1 – Projection Expressions)

Amazon DynamoDB launched JSON Document Support along with several improvements to the DynamoDB API. This post is part of a series where we’ll explore these features in more depth with the AWS SDK for Ruby V2. In particular, this post focuses on putting items into DynamoDB using the Ruby SDK and controlling the data we get back with projection expressions. At the end of the post, we also provide some helpful information for getting started with DynamoDB Local.

Putting JSON data into DynamoDB

DynamoDB now supports the following new data types: Maps, Lists, Booleans, and Nulls. Suppose we have a DynamoDB table for products with a hash key on an “Id” attribute. It’s easy to store such data into DynamoDB with native Ruby types:

# put a JSON item
item = {
  Id: 205, # hash key
  Title: "20-Bicycle 205",
  Description: "205 description",
  BicycleType: "Hybrid",
  Brand: "Brand-Company C",
  Price: 500,
  Gender: "B",
  Color: Set.new(["Red", "Black"]),
  ProductCategory: "Bike",
  InStock: true,
  QuantityOnHand: nil,
  NumberSold: BigDecimal.new("1E4"),
  RelatedItems: [
    341, 
    472, 
    649
  ],
  Pictures: { # JSON Map of views to url String
    FrontView: "http://example.com/products/205_front.jpg", 
    RearView: "http://example.com/products/205_rear.jpg",
    SideView: "http://example.com/products/205_left_side.jpg",
  },
  ProductReviews: { # JSON Map of stars to List of review Strings
    FiveStar: [
      "Excellent! Can't recommend it highly enough!  Buy it!",
      "Do yourself a favor and buy this."
    ],
    OneStar: [
      "Terrible product!  Do not buy this."
    ]
  }
}
dynamodb.put_item(:table_name => "ProductCatalog", :item => item)

Getting data from DynamoDB using projection expressions

Since DynamoDB now supports more interesting data types, we’ve also added projection expressions and expression attribute names to make it easier to retrieve only the attributes we want:

# get only the attributes we want with projection expressions
item = dynamodb.get_item(
  :table_name => "ProductCatalog",

  # Get the item with Id == 205
  :key => {
    :Id => 205
  },

  # for less typing, use expression attribute names to substitute
  # "ProductReviews" with "#pr" and "RelatedItems" with "#ri"
  :expression_attribute_names => {
    "#pr" => "ProductReviews",
    "#ri" => "RelatedItems",
  },

  # get Price, Color, FiveStar reviews, 0th and 2nd related items
  :projection_expression => "Price, Color, #pr.FiveStar, #ri[0], #ri[2], 
    #pr.NoStar, #ri[4]" # try projecting non-existent attributes too
).data.item

puts item["Price"].to_i
# 500

puts item["Color"].inspect
# #<Set: {"Black", "Red"}>

puts item["ProductReviews"]["FiveStar"][0]
# Excellent! Can't recommend it highly enough!  Buy it!

puts item["ProductReviews"]["FiveStar"][1]
# Do yourself a favor and buy this.

puts item["ProductReviews"]["OneStar"].inspect
# nil (because we only projected FiveStar reviews)

puts item["ProductReviews"]["NoStar"].inspect
# nil (because no NoStar reviews)

puts item["RelatedItems"]
# 0.341E3   (0th element)
# 0.649E3   (2nd element)

puts item["RelatedItems"].size
# 2 (non-existent 4th element not present)

Next Steps

As you can see, it’s easy to put and get items in DynamoDB with the AWS SDK for Ruby. In upcoming blog posts, we’ll take a closer look at expressions for filtering and updating data.

Feel free to get started on DynamoDB Local with the following code (note that it uses the credentials file approach for specifying AWS credentials):

#! /usr/bin/ruby

require "set"
require "bigdecimal"
require "aws-sdk-core"

# Configure SDK

# use credentials file at .aws/credentials
Aws.config[:credentials] = Aws::SharedCredentials.new
Aws.config[:region] = "us-west-2"

# point to DynamoDB Local, comment out this line to use real DynamoDB
Aws.config[:dynamodb] = { endpoint: "http://localhost:8000" }

dynamodb = Aws::DynamoDB::Client.new

## Create the table if it doesn't exist
begin
  dynamodb.describe_table(:table_name => "ProductCatalog")
rescue Aws::DynamoDB::Errors::ResourceNotFoundException
  dynamodb.create_table(
    :table_name => "ProductCatalog",
    :attribute_definitions => [
      {
        :attribute_name => :Id,
        :attribute_type => :N
      }
    ],
    :key_schema => [
      {
        :attribute_name => :Id,
        :key_type => :HASH
      }
    ],
    :provisioned_throughput => {
      :read_capacity_units => 1,
      :write_capacity_units => 1,
    }
  )

  # wait for table to be created
  puts "waiting for table to be created..."
  dynamodb.wait_until(:table_exists, table_name: "ProductCatalog")
  puts "table created!"
end