Aws::Record を使用して DynamoDB のテーブルを Rails から操作するチュートリアル

DynamoDB テーブルとクライアントのクラスをマッピングする [Aws::Record](https://github.com/aws/aws-sdk-ruby-record) を使用して DynamoDB を Ruby on Rails から操作する方法をチュートリアル風にメモっとく。

Java や C# 向けには高レベルプログラミングインターフェイスが公式に用意されている。しかし、公式な Ruby 向けの高レベルプログラミングインターフェイスの Aws::Record は2016年8月にリリースされたばかりで、開発者ガイドにも詳細な記載はない。（AWS SDK for Ruby バージョン1には似たような機能が搭載されていたが、AWS SDK for Ruby バージョン2には搭載されていない。）この記事では Aws::Record と Ruby on Rails を使用して Amazon DynamoDB 入門ガイドの [Ruby および DynamoDB](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/gettingstartedguide/GettingStarted.Ruby.html) のチュートリアルを再実装してみることにする。

# 事前準備

Ruby と Ruby on Rails をインストールしておくこと。この記事では Ruby 2.3.1 と Ruby on Rails 5.0.0.1 を対象とする。

DynamoDB Local もインストールして起動しておく。OS X であれば [OS X に DynamoDB Local を Homebrew でインストールして brew services で起動する](http://qiita.com/takuya0301/items/1718d6b9e3a3c31aefcf)を参考にしてほしい。

# Rails プロジェクトを作成する

Rails プロジェクトを適当な名前で作成する。以降はプロジェクトディレクトリを作業ディレクトリとする。

```shell-session
$ rails new dynamodb-tutorial
$ cd dynamodb-tutorial
```

# Rails 用の AWS SDK for Ruby と Aws::Record をインストールする

Gemfile に `aws-sdk-rails` と `aws-record` を追加する。

```diff:Gemfile
@@ -26,6 +26,10 @@ gem 'jbuilder', '~> 2.5'
 # gem 'redis', '~> 3.0'
 # Use ActiveModel has_secure_password
 # gem 'bcrypt', '~> 3.1.7'
+# Use AWS SDK for Ruby
+gem 'aws-sdk-rails'
+# Use Aws::Record as a data mapping abstraction for Amazon DynamoDB
+gem 'aws-record'
 
 # Use Capistrano for deployment
 # gem 'capistrano-rails', group: :development
```

`bundle` を実行してインストールする。

```shell-session
$ bundle
```

# AWS SDK for Ruby を設定する

AWS SDK for Ruby 用に環境別設定ファイルを記述する。なお、アクセスキー ID が接続先データベースに1対1に対応するので、DynamoDB JavaScript Shell などで DynamoDB を閲覧するときはアクセスキー ID を一致させる。シークレットアクセスキーとリージョンの設定も必須。DynamoDB JavaScript Shell から閲覧するのであればリージョンは us-west-2 に設定する。また、開発やテストでは DynamoDB Local を使用するのでエンドポイントを `http://localhost:8000` に設定する。

```yaml:config/aws.yml
development:
  access_key_id: DynamodbTutorialDevelopment
  secret_access_key: DynamodbTutorialDevelopmentSecret
  region: us-west-2
  endpoint: http://localhost:8000

test:
  access_key_id: DynamodbTutorialTest
  secret_access_key: DynamodbTutorialTestSecret
  region: us-west-2
  endpoint: http://localhost:8000

production:
  access_key_id: <%= ENV["AWS_ACCESS_KEY_ID"] %>
  secret_access_key: <%= ENV["AWS_SECRET_ACCESS_KEY"] %>
  region: <%= ENV["AWS_REGION"] %>
```

```rb:config/initializers/aws.rb
# Be sure to restart your server when you modify this file.

# Configure AWS SDK for Ruby.
Aws.config.update(Rails.application.config_for(:aws).symbolize_keys)
```

この設定の詳細は [Rails で AWS SDK for Ruby の Aws.config を環境別に設定する](http://qiita.com/takuya0301/items/9a5d7a4539ad56cc5d56)を見てほしい。

# Step 1: テーブルを作成する

ここからは Amazon DynamoDB 入門ガイドの [Ruby および DynamoDB](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/gettingstartedguide/GettingStarted.Ruby.html) のチュートリアルに沿って実装していく。

まずは Movies という名前のテーブルを作成する。この記事では Rails のマイグレーションを使用してテーブルを作成することにする。

## Movie モデルを作成する

まずは下記のように Movie モデルを作成する。Aws::Record はモデルからマイグレーションに必要な情報を生成するためだ。

```rb:app/models/movie.rb
class Movie
  include Aws::Record

  integer_attr :year,  hash_key: true
  string_attr  :title, range_key: true
  map_attr     :info
end
```

## マイグレーションファイルを作成する

次にマイグレーションファイルを生成する。

```shell-session
$ rails g migration create_movies
```

ファイルの内容を下記のように書き換える。`provisioned_throughput` は DynamoDB Local では無視される。本番環境の DynamoDB のマイグレーションでも使用することを考えると最小値に設定しておくのが無難かな。

マイグレーションのロールバックはできないように `down` メソッドで `ActiveRecord::IrreversibleMigration` 例外を投げるようにしている。テーブルの削除については記事の終わりの方を見てほしい。

```rb:20161105093705_create_movies.rb
class CreateMovies < ActiveRecord::Migration[5.0]
  def up
    migration = Aws::Record::TableMigration.new(Movie)
    migration.create!(
      provisioned_throughput: {
        read_capacity_units: 1,
        write_capacity_units: 1
      }
    )
    migration.wait_until_available
  end

  def down
    raise ActiveRecord::IrreversibleMigration
  end
end
```

## マイグレーションを実行する

マイグレーションのコマンドを実行する。

```shell-session
$ rails db:migrate
```

なお、このプロジェクトでは Rails のマイグレーションを管理しているテーブルは SQLite データベースに作成される。DynamoDB のみをデータベースとして使用するプロジェクトの場合は Rails のマイグレーションを管理しているテーブルをどうするか考えなければならない。Rails のマイグレーションを使用しないというのもひとつの解決策かと思う。

## Movie テーブルの存在を確認する

Rails コンソールで Movie テーブルの存在を確認する。

```shell-session
$ rails c
>  Movie.table_exists?
[Aws::DynamoDB::Client 200 0.211896 0 retries] describe_table(table_name:"Movie")  

=> true
> exit
```

# Step 2: サンプルデータをロードする

映画のサンプルデータを DynamoDB にロードする。この記事では Rails のシード機能を使用する。

## サンプルデータファイルをダウンロードする

サンプルデータをダウンロードして `db/moviedata.json` として保存する。

```shell-session
$ curl -O http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/gettingstartedguide/samples/moviedata.zip
$ unzip moviedata.zip
$ mv moviedata.json db
$ rm moviedata.zip
```

## Movie テーブルにサンプルデータをロードする

`db/moviedata.json` の内容を DynamoDB に保存するコードをシードファイルに記述する。

```rb:db/seeds.rb
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
#
# Examples:
#
#   movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
#   Character.create(name: 'Luke', movie: movies.first)

# Load movies
file = File.read('db/moviedata.json')
movies = JSON.parse(file)
movies.each do |movie|
  Movie.new(movie).save!
end
```

シードファイルを実行してサンプルデータをロードする。この実行には数十秒程度の時間がかかるので少し待とう。

```shell-session
$ rails db:seed
```

## サンプルデータのロードを確認する

Rails コンソールでサンプルデータの存在を確認する。

```shell-session
$ rails c
> Movie.find(year: 2013, title: 'Rush')
[Aws::DynamoDB::Client 200 0.051161 0 retries] get_item(table_name:"Movie",key:{"year"=>{n:"2013"},"title"=>{s:"Rush"}})  

=> #<Movie:0x007fe09d5a1268 @data=#<Aws::Record::ItemData:0x007fe09d58add8 @data={:year=>#<BigDecimal:7fe09d5a2410,'0.2013E4',9(18)>, :title=>"Rush", :info=>{"actors"=>["Daniel Bruhl", "Chris Hemsworth", "Olivia Wilde"], "release_date"=>"2013-09-02T00:00:00Z", "plot"=>"A re-creation of the merciless 1970s rivalry between Formula One rivals James Hunt and Niki Lauda.", "genres"=>["Action", "Biography", "Drama", "Sport"], "image_url"=>"http://ia.media-imdb.com/images/M/MV5BMTQyMDE0MTY0OV5BMl5BanBnXkFtZTcwMjI2OTI0OQ@@._V1_SX400_.jpg", "directors"=>["Ron Howard"], "rating"=>#<BigDecimal:7fe09d5a16f0,'0.83E1',18(18)>, "rank"=>#<BigDecimal:7fe09d5a15b0,'0.2E1',9(18)>, "running_time_secs"=>#<BigDecimal:7fe09d5a1470,'0.738E4',9(18)>}}, @clean_copies={:year=>2013, :title=>"Rush", :info=>{"actors"=>["Daniel Bruhl", "Chris Hemsworth", "Olivia Wilde"], "release_date"=>"2013-09-02T00:00:00Z", "plot"=>"A re-creation of the merciless 1970s rivalry between Formula One rivals James Hunt and Niki Lauda.", "genres"=>["Action", "Biography", "Drama", "Sport"], "image_url"=>"http://ia.media-imdb.com/images/M/MV5BMTQyMDE0MTY0OV5BMl5BanBnXkFtZTcwMjI2OTI0OQ@@._V1_SX400_.jpg", "directors"=>["Ron Howard"], "rating"=>#<BigDecimal:7fe09d589e60,'0.83E1',18(18)>, "rank"=>#<BigDecimal:7fe09d589d98,'0.2E1',9(18)>, "running_time_secs"=>#<BigDecimal:7fe09d589cd0,'0.738E4',9(18)>}}, @dirty_flags={}, @model_attributes=#<Aws::Record::ModelAttributes:0x007fe09ac7f3d0 @model_class=Aws::Record::Attributes, @attributes={:year=>#<Aws::Record::Attribute:0x007fe09ac4de98 @name=:year, @database_name="year", @dynamodb_type="N", @marshaler=#<Aws::Record::Marshalers::IntegerMarshaler:0x007fe09ac572b8>, @persist_nil=nil>, :title=>#<Aws::Record::Attribute:0x007fe09ac45e28 @name=:title, @database_name="title", @dynamodb_type="S", @marshaler=#<Aws::Record::Marshalers::StringMarshaler:0x007fe09ac45ec8>, @persist_nil=nil>, :info=>#<Aws::Record::Attribute:0x007fe09d2c8de8 @name=:info, @database_name="info", @dynamodb_type="M", @marshaler=#<Aws::Record::Marshalers::MapMarshaler:0x007fe09d2c8ed8>, @persist_nil=nil>}, @storage_attributes={"year"=>:year, "title"=>:title, "info"=>:info}>, @track_mutations=true>>
```

# Step 3: 項目を作成・取得・更新・削除する

Aws::Record を使用して項目の作成・読み込み・更新・削除を Rails コンソールから実行してみる。Rails でスキャフォルドした Web インターフェイスから操作できるようにしようかと考えたのだけど、複合プライマリキーだったり、全件表示するようなデータでもなかったり、info が複雑だったりするので諦めた。元のチュートリアルもそこまでやってないしね。

## 新しい項目を作成する

新しい項目は ActiveRecord のように作成できる。

```shell-session
$ rails c
> movie = Movie.new(year: 2015, title: 'The Big New Movie', info: { plot: 'Nothing happens at all.', rating: 0 })
> movie.save!
[Aws::DynamoDB::Client 200 0.18404 0 retries] put_item(table_name:"Movie",item:{"year"=>{n:"2015"},"title"=>{s:"The Big New Movie"},"info"=>{m:{"plot"=>{s:"Nothing happens at all."},"rating"=>{n:"0"}}}},condition_expression:"attribute_not_exists(#H) and attribute_not_exists(#R)",expression_attribute_names:{"#H"=>"year","#R"=>"title"})  

=> #<struct Aws::DynamoDB::Types::PutItemOutput attributes=nil, consumed_capacity=nil, item_collection_metrics=nil>
```

## 項目を取得する

項目の取得ではプライマリキーをハッシュ形式で指定する。Movie モデルのように複合プライマリキーの場合はパーティションキー（ハッシュキー）とソートキー（レンジキー）の両方を指定する必要がある。

```shell-session
$ rails c
> Movie.find(year: 2015, title: 'The Big New Movie')
[Aws::DynamoDB::Client 200 0.081038 0 retries] get_item(table_name:"Movie",key:{"year"=>{n:"2015"},"title"=>{s:"The Big New Movie"}})  

=> #<Movie:0x007fe09d4726a8 @data=#<Aws::Record::ItemData:0x007fe09d4725e0 @data={:year=>#<BigDecimal:7fe09d473210,'0.2015E4',9(18)>, :title=>"The Big New Movie", :info=>{"rating"=>#<BigDecimal:7fe09d472bf8,'0.0',9(18)>, "plot"=>"Nothing happens at all."}}, @clean_copies={:year=>2015, :title=>"The Big New Movie", :info=>{"rating"=>#<BigDecimal:7fe09d472018,'0.0',9(18)>, "plot"=>"Nothing happens at all."}}, @dirty_flags={}, @model_attributes=#<Aws::Record::ModelAttributes:0x007fe09d2632b8 @model_class=Aws::Record::Attributes, @attributes={:year=>#<Aws::Record::Attribute:0x007fe09d2390a8 @name=:year, @database_name="year", @dynamodb_type="N", @marshaler=#<Aws::Record::Marshalers::IntegerMarshaler:0x007fe09d242b58>, @persist_nil=nil>, :title=>#<Aws::Record::Attribute:0x007fe09ab7c618 @name=:title, @database_name="title", @dynamodb_type="S", @marshaler=#<Aws::Record::Marshalers::StringMarshaler:0x007fe09ab7c6b8>, @persist_nil=nil>, :info=>#<Aws::Record::Attribute:0x007fe09ab6e8d8 @name=:info, @database_name="info", @dynamodb_type="M", @marshaler=#<Aws::Record::Marshalers::MapMarshaler:0x007fe09ab6e978>, @persist_nil=nil>}, @storage_attributes={"year"=>:year, "title"=>:title, "info"=>:info}>, @track_mutations=true>>
```

## 項目を更新する

項目の更新ではプライマリキーと変更する属性をハッシュ形式で指定する。

```shell-session
$ rails c
> Movie.update(year: 2015, title: 'The Big New Movie', info: { plot: 'Everything happens all at once.', rating: 5.5, actors: ["Larry", "Moe", "Curly"] })
[Aws::DynamoDB::Client 200 0.042996 0 retries] update_item(table_name:"Movie",key:{"year"=>{n:"2015"},"title"=>{s:"The Big New Movie"}},update_expression:"SET #UE_A = :ue_a",expression_attribute_names:{"#UE_A"=>"info"},expression_attribute_values:{":ue_a"=>{m:{"plot"=>{s:"Everything happens all at once."},"rating"=>{n:"5.5"},"actors"=>{l:[{s:"Larry"},{s:"Moe"},{s:"Curly"}]}}}})  

=> #<struct Aws::DynamoDB::Types::UpdateItemOutput attributes=nil, consumed_capacity=nil, item_collection_metrics=nil>
```

また下記のように項目を取得してから編集して保存してもいい。

```shell-session
$ rails c
> movie = Movie.find(year: 2015, title: 'The Big New Movie')
[Aws::DynamoDB::Client 200 0.102433 0 retries] get_item(table_name:"Movie",key:{"year"=>{n:"2015"},"title"=>{s:"The Big New Movie"}})  

=> #<Movie:0x007fe09ac256a0 @data=#<Aws::Record::ItemData:0x007fe09ac25628 @data={:year=>#<BigDecimal:7fe09ac262d0,'0.2015E4',9(18)>, :title=>"The Big New Movie", :info=>{"rating"=>#<BigDecimal:7fe09ac25f10,'0.55E1',18(18)>, "plot"=>"Everything happens all at once.", "actors"=>["Larry", "Moe", "Curly"]}}, @clean_copies={:year=>2015, :title=>"The Big New Movie", :info=>{"rating"=>#<BigDecimal:7fe09ac24f70,'0.55E1',18(18)>, "plot"=>"Everything happens all at once.", "actors"=>["Larry", "Moe", "Curly"]}}, @dirty_flags={}, @model_attributes=#<Aws::Record::ModelAttributes:0x007fe09d2632b8 @model_class=Aws::Record::Attributes, @attributes={:year=>#<Aws::Record::Attribute:0x007fe09d2390a8 @name=:year, @database_name="year", @dynamodb_type="N", @marshaler=#<Aws::Record::Marshalers::IntegerMarshaler:0x007fe09d242b58>, @persist_nil=nil>, :title=>#<Aws::Record::Attribute:0x007fe09ab7c618 @name=:title, @database_name="title", @dynamodb_type="S", @marshaler=#<Aws::Record::Marshalers::StringMarshaler:0x007fe09ab7c6b8>, @persist_nil=nil>, :info=>#<Aws::Record::Attribute:0x007fe09ab6e8d8 @name=:info, @database_name="info", @dynamodb_type="M", @marshaler=#<Aws::Record::Marshalers::MapMarshaler:0x007fe09ab6e978>, @persist_nil=nil>}, @storage_attributes={"year"=>:year, "title"=>:title, "info"=>:info}>, @track_mutations=true>>
> movie.info['rating'] += 1
=> #<BigDecimal:7fe09ab3baa0,'0.65E1',18(27)>
> movie.save!
[Aws::DynamoDB::Client 200 0.029221 0 retries] update_item(table_name:"Movie",key:{"year"=>{n:"2015"},"title"=>{s:"The Big New Movie"}},update_expression:"SET #UE_A = :ue_a",expression_attribute_names:{"#UE_A"=>"info"},expression_attribute_values:{":ue_a"=>{m:{"rating"=>{n:"6.5"},"plot"=>{s:"Everything happens all at once."},"actors"=>{l:[{s:"Larry"},{s:"Moe"},{s:"Curly"}]}}}})  

=> #<struct Aws::DynamoDB::Types::UpdateItemOutput attributes=nil, consumed_capacity=nil, item_collection_metrics=nil>
```

## 項目を削除する

項目の削除ではプライマリキーを指定した項目を作成して削除する。削除対象のプライマリキーが判明していればこの方法でいい。

```shell-session
> movie = Movie.new(year: 2015, title: 'The Big New Movie')
=> #<Movie:0x007fd6a23a9eb0 @data=#<Aws::Record::ItemData:0x007fd6a23a9e60 @data={:year=>2015, :title=>"The Big New Movie"}, @clean_copies={}, @dirty_flags={}, @model_attributes=#<Aws::Record::ModelAttributes:0x007fd6a217a1a8 @model_class=Aws::Record::Attributes, @attributes={:year=>#<Aws::Record::Attribute:0x007fd6a20d35b0 @name=:year, @database_name="year", @dynamodb_type="N", @marshaler=#<Aws::Record::Marshalers::IntegerMarshaler:0x007fd69fe628a0>, @persist_nil=nil>, :title=>#<Aws::Record::Attribute:0x007fd6a20c10b8 @name=:title, @database_name="title", @dynamodb_type="S", @marshaler=#<Aws::Record::Marshalers::StringMarshaler:0x007fd6a20c1158>, @persist_nil=nil>, :info=>#<Aws::Record::Attribute:0x007fd6a20b09c0 @name=:info, @database_name="info", @dynamodb_type="M", @marshaler=#<Aws::Record::Marshalers::MapMarshaler:0x007fd6a20b0a60>, @persist_nil=nil>}, @storage_attributes={"year"=>:year, "title"=>:title, "info"=>:info}>, @track_mutations=true>>
> movie.delete!
[Aws::DynamoDB::Client 200 0.032924 0 retries] delete_item(table_name:"Movie",key:{"year"=>{n:"2015"},"title"=>{s:"The Big New Movie"}})  

=> true
```

また下記のように項目を取得してから削除してもいい。読み取りと書き込みの2つのリクエストが発生するので前述の方法より無駄がある。

```shell-session
$ rails c
> movie = Movie.find(year: 2015, title: 'The Big New Movie')
[Aws::DynamoDB::Client 200 0.062304 0 retries] get_item(table_name:"Movie",key:{"year"=>{n:"2015"},"title"=>{s:"The Big New Movie"}})  

=> #<Movie:0x007fe09d041980 @data=#<Aws::Record::ItemData:0x007fe09d041868 @data={:year=>#<BigDecimal:7fe09d042b78,'0.2015E4',9(18)>, :title=>"The Big New Movie", :info=>{"rating"=>#<BigDecimal:7fe09d042498,'0.65E1',18(18)>, "plot"=>"Everything happens all at once.", "actors"=>["Larry", "Moe", "Curly"]}}, @clean_copies={:year=>2015, :title=>"The Big New Movie", :info=>{"rating"=>#<BigDecimal:7fe09d040c88,'0.65E1',18(18)>, "plot"=>"Everything happens all at once.", "actors"=>["Larry", "Moe", "Curly"]}}, @dirty_flags={}, @model_attributes=#<Aws::Record::ModelAttributes:0x007fe09d2632b8 @model_class=Aws::Record::Attributes, @attributes={:year=>#<Aws::Record::Attribute:0x007fe09d2390a8 @name=:year, @database_name="year", @dynamodb_type="N", @marshaler=#<Aws::Record::Marshalers::IntegerMarshaler:0x007fe09d242b58>, @persist_nil=nil>, :title=>#<Aws::Record::Attribute:0x007fe09ab7c618 @name=:title, @database_name="title", @dynamodb_type="S", @marshaler=#<Aws::Record::Marshalers::StringMarshaler:0x007fe09ab7c6b8>, @persist_nil=nil>, :info=>#<Aws::Record::Attribute:0x007fe09ab6e8d8 @name=:info, @database_name="info", @dynamodb_type="M", @marshaler=#<Aws::Record::Marshalers::MapMarshaler:0x007fe09ab6e978>, @persist_nil=nil>}, @storage_attributes={"year"=>:year, "title"=>:title, "info"=>:info}>, @track_mutations=true>>
> movie.delete!
[Aws::DynamoDB::Client 200 0.027339 0 retries] delete_item(table_name:"Movie",key:{"year"=>{n:"2015"},"title"=>{s:"The Big New Movie"}})  

=> true
```

# Step 4: データをクエリおよびスキャンする

DynamoDB からデータを取得するときに使用するクエリとスキャンを Rails コンソールから実行してみる。

## クエリで1年間にリリースされたすべての映画を取得する

1985年にリリースされたすべての映画を取得してみる。年を表現するパーティションキーが1985である項目をクエリで取得する。現時点での Aws::Record のクエリは AWS SDK for Ruby のクエリをテーブル名を補完して呼び出しているだけなので引数は同じフォーマットになる。

```shell-session
$ rails c
> movies = Movie.query(
    key_condition_expression: '#yr = :yyyy',
    expression_attribute_names: { '#yr' => 'year' },
    expression_attribute_values: { ':yyyy' => 1985 })
=> #<Aws::Record::ItemCollection:0x007fe09ae542f0 @search_method=:query, @search_params={:key_condition_expression=>"#yr = :yyyy", :expression_attribute_names=>{"#yr"=>"year"}, :expression_attribute_values=>{":yyyy"=>1985}, :table_name=>"Movie"}, @model=Movie, @client=#<Aws::DynamoDB::Client>>
> movies.each do |movie|
    puts "#{movie.year.to_i} #{movie.title}"
  end
[Aws::DynamoDB::Client 200 0.134007 0 retries] query(key_condition_expression:"#yr = :yyyy",expression_attribute_names:{"#yr"=>"year"},expression_attribute_values:{":yyyy"=>{n:"1985"}},table_name:"Movie")  

1985 A Nightmare on Elm Street Part 2: Freddy's Revenge
1985 A Room with a View
1985 A View to a Kill
.
.
.
1985 The Return of the Living Dead
1985 Weird Science
1985 Witness
=> nil
```

## クエリで1年間にリリースされた特定のタイトルを持つすべての映画を取得する

1992年にリリースされたすべての映画から、文字 A から文字 L までで始まる映画を取得してみる。題名を表現するソートキーで範囲指定している。

```shell-session
$ rails c
> movies = Movie.query(
    projection_expression: '#yr, title, info.genres, info.actors[0]',
    key_condition_expression: '#yr = :yyyy and title between :letter1 and :letter2',
    expression_attribute_names: { '#yr' => 'year' },
    expression_attribute_values: { ':yyyy' => 1992, ':letter1' => 'A', ':letter2' => 'L' })
> movies.each do |movie|
    print "#{movie.year.to_i}: #{movie.title} ... "

    movie.info['genres'].each do |gen|
      print gen + ' '
    end

    print " ... #{movie.info['actors'][0]}\n"
  end
1992: A Few Good Men ... Crime Drama Mystery Thriller  ... Tom Cruise
1992: A League of Their Own ... Comedy Drama Sport  ... Tom Hanks
1992: A River Runs Through It ... Drama  ... Craig Sheffer
.
.
.
1992: Howards End ... Drama Romance  ... Anthony Hopkins
1992: Jennifer Eight ... Crime Drama Mystery Thriller  ... Andy Garcia
1992: Juice ... Crime Drama Thriller  ... Omar Epps
=> nil
```

## スキャンとフィルターで1950年代の映画を取得する

1950年代、つまり1950年から1959年までにリリースされた映画を取得してみる。パーティションキーについての範囲指定になるためクエリが使えないのでスキャンとフィルターを使用している。また、このスキャンの結果セットは 1 MB を超えるためページ分割が発生するのだが、次のページを取得する処理を Aws::Record が自動的にしてくれている。

```shell-session
$ rails c
> movies = Movie.scan(
    projection_expression: '#yr, title, info.rating',
    filter_expression: '#yr between :start_yr and :end_yr',
    expression_attribute_names: { '#yr' => 'year' },
    expression_attribute_values: { ':start_yr' => 1950, ':end_yr' => 1959 })
=> #<Aws::Record::ItemCollection:0x007fd69ff708a0 @search_method=:scan, @search_params={:projection_expression=>"#yr, title, info.rating", :filter_expression=>"#yr between :start_yr and :end_yr", :expression_attribute_names=>{"#yr"=>"year"}, :expression_attribute_values=>{":start_yr"=>1950, ":end_yr"=>1959}, :table_name=>"Movie"}, @model=Movie, @client=#<Aws::DynamoDB::Client>>
> movies.each do |movie|
    puts "#{movie.year.to_i}: #{movie.title} ... #{movie.info['rating'].to_f}"
  end
[Aws::DynamoDB::Client 200 0.296593 0 retries] scan(projection_expression:"#yr, title, info.rating",filter_expression:"#yr between :start_yr and :end_yr",expression_attribute_names:{"#yr"=>"year"},expression_attribute_values:{":start_yr"=>{n:"1950"},":end_yr"=>{n:"1959"}},table_name:"Movie")  

1952: High Noon ... 8.2
1952: Singin' in the Rain ... 8.4
1952: The Member of the Wedding ... 6.8
.
.
.
1951: The Day the Earth Stood Still ... 7.9
[Aws::DynamoDB::Client 200 0.124166 0 retries] scan(projection_expression:"#yr, title, info.rating",filter_expression:"#yr between :start_yr and :end_yr",expression_attribute_names:{"#yr"=>"year"},expression_attribute_values:{":start_yr"=>{n:"1950"},":end_yr"=>{n:"1959"}},table_name:"Movie",exclusive_start_key:{"title"=>{s:"Iron Man 2"},"year"=>{n:"2010.0"}})  

1955: East of Eden ... 8.0
.
.
.
1950: Rashomon ... 8.4
1950: Sunset Blvd. ... 8.6
1950: Tea for Two ... 6.4
=> nil
```

# Step 5: テーブルを削除する

最後に Movies テーブルを削除する。これは Rails のマイグレーションのロールバックを使用して実装する。

# マイグレーションファイルを修正する

`down` メソッドの内容を下記のように修正する。

```diff:20161105093705_create_movies.rb
@@ -11,6 +11,7 @@ class CreateMovies < ActiveRecord::Migration[5.0]
   end
 
   def down
-    raise ActiveRecord::IrreversibleMigration
+    migration = Aws::Record::TableMigration.new(Movie)
+    migration.delete!
   end
 end
```

## マイグレーションのロールバックを実行する

マイグレーションのロールバックを実行する。

```shell-session
$ rails db:rollback
```

## Movie テーブルが存在しないことを確認する

Rails コンソールで Movie テーブルが存在しないことを確認する。

```shell-session
$ rails c
>  Movie.table_exists?
[Aws::DynamoDB::Client 400 0.06534 0 retries] describe_table(table_name:"Movie") Aws::DynamoDB::Errors::ResourceNotFoundException Cannot do operations on a non-existent table

=> false
> exit
```

---

Aws::Record を使用して DynamoDB のテーブルを Ruby on Rails から操作する方法をチュートリアル風に説明した。AWS SDK for Ruby で DynamoDB を操作するよりは便利なんじゃないかな。ぜひ DynamoDB を使用した Rails アプリケーションに挑戦してみてね。

# 関連記事

[OS X に DynamoDB Local を Homebrew でインストールして brew services で起動する](http://qiita.com/takuya0301/items/1718d6b9e3a3c31aefcf)
[Rails で AWS SDK for Ruby の Aws.config を環境別に設定する](http://qiita.com/takuya0301/items/9a5d7a4539ad56cc5d56)

# 参考文献

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/gettingstartedguide/GettingStarted.Ruby.html
https://aws.amazon.com/jp/blogs/developer/general-availability-release-of-the-aws-record-gem/
http://docs.aws.amazon.com/awssdkrubyrecord/api/index.html