IT・技術研修ならCTC教育サービス

サイト内検索 企業情報 サイトマップ

研修コース検索

コラム

Ruby & Rails

CTC 教育サービス

 [IT研修]注目キーワード   OpenStack  OpenFlow/SDN  情報セキュリティ  Python  システムトラブルシュート 

第29回 触ってみよう!Ruby on Rails 5(前編) ~APIモード~ (藺藤卓実) 2016年9月

こんにちは。 藺藤です。

2016年6月30日、ついにRuby on Rails 5がリリースされました!(*1)  Rails 5では様々な新機能が追加されています。

Ruby & Railsコラムでは、これから何回かに渡って正式リリースされたRails 5の新機能を説明してゆきたいと思います。 今回はRails 5のAPIモードを使ってJSON-APIサーバを作成します。

目次
・Rails 5リリース
・Rails 5の導入
・APIモードでアプリケーションを作成
・Scaffoldでリソースを追加
・Jbuilderの利用

Rails 5リリース

Rails 5がリリースされました。 Rails 4のリリースが2013年の6月でしたので、Rails 5は実に3年ぶりのメジャー・リリースとなります。

すでにリリース候補版等に触ってみた方もいるかと思いますが、Rails 5では、Rails 4系と比べて様々な変更を含むバージョンになっています。 変更点を幾つか抜粋します。

・ActionCableの導入 ... WebSocketをRails-likeに扱うための新しい機能が導入されました。
・APIモードの導入 ... RailsをJSON-APIサーバとして動作させるためのモードが導入されました。
・railsコマンドの充実 ... これまでrakeコマンドで実行していたスクリプトを、railsコマンドを使って実行可能になりました。
・ApplicationRecordの導入 ... モデルの共通親クラスとしてApplicationRecordが導入されました。

より詳細はリリースノートをご覧ください。(*1)

今回は新機能であるAPIモードを使ったアプリケーションを一緒に作成してゆきましょう。 また、アプリケーションを作成する中でrailsコマンド(従来rakeコマンドで実行していたもの)を使ってみます。 その他の機能については次回コラムをお楽しみに!

Rails 5の導入

それではRails 5を導入します。 今回私が使用した環境は以下のとおりです。

  • CentOS 7.0
  • Ruby 2.3.1
  • Rails 5.0.0

さっそくRailsをインストールします。 以下のコマンドを実行してください。

gem install rails -v 5.0.0
#=> ...
#=> Done installing documentation for activesupport,  rack, ...,  after 43 seconds
#=> 12  gems installed 

インストールされるGemの数は環境により異なるかもしれません。(*2)(*3)

Railsのバージョンを確認しましょう。

rails -v
#=> Rails 5.0.0

正常にインストールできたようですね。

APIモードでアプリケーションを作成

それではAPIモードでRailsアプリケーションを作ってみましょう。 今回作成するプロジェクト名は「rails5_api」にします。

APIモードでアプリケーションを新規作成するには、いつもの「rails new」コマンドに「--api」オプションを追加します。 次のコマンドを実行してください。

rails new rails5_api --api
#=> ...
#=> Using rails 5.0.0
#=> Bundle complete! 8 Gemfile ...

Railsプロジェクトの作成に成功したようです。

モードの違いによって、デフォルトで利用されるGemが異なります。 これは、通常のRails(「--api」オプションを付けない場合)がHTMLのレンダリングを目的としているのに対して、APIモードはJSONレスポンスを返すことを目的としているためです。

試しに--apiオプションを付けずに「rails new」コマンドを実行した場合と、APIモードで「rails new」を実行した場合の違いを比較してみてください。

# 非API Railsアプリケーションを作成する
rails new rails5_html
# APIモードの場合とGemfileの差分をとってみる(出力は一部抜粋) 
diff rails5_api/Gemfile rails5_html/Gemfile
#=> > gem 'sass-rails', '~> 5.0'
#=> > gem 'uglifier', '=> 1.3.0'
#=> > gem 'jquery-rails'
#=> ...

「activerecord」や「actionmailer」等は共通ですが、SCSSを扱うためのGem「sass-rails」やJavaScript(CoffeeScript)を扱うためのGem「coffee-rails」「uglifier」「jquery-rails」等はAPIモードではGemfileに含まれません。 これはAPIモードの目的から当然かと思います。

Scaffoldでリソースを追加

アプリケーションを作成できたので、次にリソースを追加してみましょう。 いつも通り、ScaffoldでBookリソースを追加します。

cd rails5_api
rails g scaffold book title author_name published_on:date
#=>         active_record
#=> create    db/migrate/20160712014902_create_books.rb
#=> create    app/models/book.rb 
#=>           test_unit 
#=> create      test/models/book_test.rb 
#=> create      test/fixtures/books.yml 
#=>         resource_route
#=> create    resources :books
#=>         scaffold_controller 
#=> create    app/controllers/books_controller.rb
#=>            test_unit
#=> create      test/controllers/books_controller_test.rb

非APIモードでのScaffold同様に、モデルやコントローラが作成され、booksに対するルーティングが追加されました。 しかし、app/views/booksに相当するものが作られません。 (これについては、後ほど<Jbuilderの利用>の節で再度触れます。)

APIモードでは、コントローラ内でrenderメソッドを使ってデータを返すのが最も基本的な使い方となります。 次のようなものです。

render  json: @books
render  json: @book

実際にbooks_controller.rbを見てみましょう。

[apps/controllers/]
class BooksController < ApplicationController
before_action :set_book, only: [:show, :update, :destroy]
def index
  @books = Book.all
  render json: @books
end
def show
  render json: @book
end
  (...)
end

render json: ○○ により、○○に応じてJavaScript Object又はArrayに変換されます。

登録・更新・削除の各処理では処理が失敗することがあります。(もちろん、アプリケーションによってはindexやshowアクションが失敗するケースもあるでしょう。) このような場合の例としてupdate アクションを見てみましょう。

class BooksController < ApplicationController
  (...)
  def update
    if @book.update(book_params) # book_paramsはparams[:book]のようなもの
    render json: @book
    else
    render json: @book.errors, status: :unprocessable_entity
    end
  end
  (...)
end

更新処理に失敗した場合、render メソッドのオプション引数として、ステータス(status)を付与しています。 このように書くことで、クライアントに対して処理が失敗したこと(HTTPステータスコード 422、Unprocessable Entity)を示し、またエラーの詳細内容をボディレスポンスに埋め込むことができます。

HTMLの場合、ともすればステータスコードをあまり意識しないかもしれませんが、APIの場合にはステータスコードは非常に重要な情報となります。 ぜひ、適切なステータスコードを返すように実装したいですね。 Railsでは、ステータスの指定方法として、数値(200, 404, 500等のHTTPステータスコード)、またはRuby のSymbol(:ok, :not_found, :internal_server_error等)を利用できます。 個人的には、意味を汲み取りやすいため、Symbolの利用をお勧めしたいと思います。

ステータス及びステータスコードの指定に関してはRails Guidesに一覧表があるので参考になるでしょう。(*4)

さて、今の段階ではまだ表示させるデータがありません。 データベースを作成し、テストデータを追加しましょう。 以下のコマンドを実行してください。(*5)

# データベースの作成 
rails db:create
#=> Created database 'db/development.sqlite3'
#=> Created database 'db/test.sqlite3'
# テーブルの作成 
rails db:migrate
#=> == 20160712014902 CreateBooks: migrating =====
#=> -- create_table(:books)
#=>    ->  0.0037s
#=> == 20160712014902 CreateBooks: migrated (0.0047s)  =====
# テストデータ(fixturesファイル)のロード 
rails db:fixtures:load

これでテーブルの作成及びテストデータの用意ができました。

続いてサーバを起動します。

rails server
#=> Booting Puma
#=> (...)

ブラウザでアクセスしてみましょう。 パスは/booksを指定します。

fig01

テストデータなのであまり面白いものではないですが、きちんとJSON形式のデータを取得できていることを確かめられました。 showアクションを確かめるには/books/:id (上の例では/books/298486374)に対してアクセスしてみてください。

APIモードの使い方はここまで見ていた通りで、従来のRailsとそれほど変わりません。 異なる点としては、ApplicationControllerの継承元がActionController::APIに変わっている点が上げられます。(非APIモードの場合は、ActionController::Baseを継承します。) ActionController::APIは、非APIモードで利用されるActionController::BaseからAPIサーバの構築には不要なものを取り除き、APIとしての機能に特化したものだそうです。 

全てのコントローラに共通する機能(処理)を追加したい場合には、これまで通り、ApplicationControllerに対してincludeします。

Jbuilderの利用

ここまででBookリソースを作成し、ブラウザで表示できることを確かめました。 APIモードでは、デフォルトではビューを作成せず、コントローラ内でrenderメソッドを呼び出すことで描画内容を決めます。 シンプルなAPIであればこれだけで十分かもしれませんが、よりリッチなビューが必要な場合にはJbuilderを使ってビューを作成するとよいでしょう。 

ここからはJbuilderの利用方法を見ていきます。

Jbuilderを利用するために、まずはGemfileを編集します。 編集するのはGemfileの次の行です。

[Gemfile]
# gem 'jbuilder'

この行がコメントアウトされていますので、コメントを解除します。

[Gemfile]
(...) 
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5' # この一行をアンコメント
(...) 

編集が終わったら、「bundle install」しておきます。(私の場合は既にインストール済みでした。)

これでJbuilderを使う準備ができました。 リソース「Shop」を作成します。

rails g scaffold shop name address
#=>         active_record
#=> create    db/migrate/20160712021309_create_shops.rb
#=> create    app/models/shop.rb
#=>           test_unit
#=> create      test/models/shop_test.rb
#=> create      test/fixtures/shops.yml
#=>         resource_route
#=> create    resources :shops
#=>         scaffold_controller
#=> create    app/controllers/shops_controller.rb
#=>           test_unit
#=> create      test/controllers/shops_controller_test.rb
#=>           jbuilder
#=> create      app/views/shops
#=> create      app/views/shops/index.json.jbuilder
#=> create      app/views/shops/show.json.jbuilder
#=> create      app/views/shops/_shop.json.jbuilder

Gemfileを編集しJbuilderを使うように変更したため、今度はindexとshowアクションに対するビューが作成されました。 これらはそれぞれリソースが単数か複数かに応じて適切に部分テンプレート_shop.json.jbuilderを呼び出します。

また、Jbuilderを使わない場合と比べて、shops_controllerの内容も少し変更されています。 今回はJbuilderを利用するので、コントローラ内でrender json: ○○を行いません。 createアクションやupdateアクションが成功した場合、show.json.jbuilderを利用してモデルデータを返します。 このあたりはぜひ、実際にソースコードを見てみてください。 もちろん、独自にcreateやupdate用のビューを追加することもできます。

少しだけJbuilderを使ってみましょう。 今回は管理者向け・非管理者向けに、それぞれ異なるJSONデータを返却するケースを想定してみます。

まず、先ほど生成したapp/views/shops/show.json.jbuilderをコピーして、新たにapp/views/shops/show_admin.json.jbuilderを作成してください。 また、同様に_shops.json.jbuilderについてもコピーを作成してください。

cp app/views/shops/show.json.jbuilder  app/views/shops/show_admin.json.jbuilder
cp app/views/shops/_shop.json.jbuilder  app/views/shops/_shop_admin.json.jbuilder

コピーが作成できたらビューを編集し、show.json.jbuilderは_shop.json.jbuilderを、show_admin.json.jbuilderは_shop_admin.json.jbuilderをそれぞれ内部的に呼び出すようにします。 このためには、show_admin.json.jbuilderを次のように編集します。

[app/views/shops/show_admin.json.jbuilder]
json.partial! "shops/shop_admin", shop: @shop  # ビューを変更
[app/views/shops/show.json.jbuilder]
json.partial! "shops/shop", shop: @shop  # こちらは変更無し

次に_shop.json.jbuilderを次のように編集します。 元々のソースコードと比較して、created_at及びupdated_atのデータを返却しないように書き換えることにします。(一方、_shop_admin.json.jbuilderはコピーなので、元々のままです。)

[app/views/shops/_shop_admin.json.jbuilder]
json.extract! @shop, :id, :name, :address, :created_at, :updated_at  # 変更無し
json.url shop_url(@shop, format: :json)
[app/views/shops/_shop.json.jbuilder]
json.extract! @shop, :id, :name, :address # created_at及びupdated_atを削除
json.url shop_url(@shop, format: :json)

管理者向けのビュー(ファイル名がXXX_admin)と非管理者向けのビュー(ファイル名が_adminを除いたXXX)をそれぞれ別になるようにしました。

続いて、権限によって異なるビューを利用するように、アプリケーションのロジックを修正します。 shops_controller.rbを以下のように書き換えます。

[app/controllers/shops_controller.rb]
(...)
def show  # showアクションを以下のように変更する
  if params[:admin].present? and (params[:admin] == 'true')
      render :show_admin
    else
      render :show  # 元々あったコードはこの行だけ
    end
  end
(...)

showアクションに対してアクセスする際、「?admin=true」を追加することで管理者用の画面が表示できる仕組みを作りました。(もちろん、実際のアプリケーションではparamsで判定するのではなく、クライアント毎の権限チェックをするように判定条件を書き換えるべきです。)

ScaffoldでShopリソースを追加したため、ここで一度テーブルを作り直し、テストデータを入れておきましょう。

rails db:migrate:reset
rails db:fixtures:load

ブラウザで「/shops/:id.json?admin=true」(管理者ユーザ向け画面)や「/shops/:id.json」(一般ユーザ向け画面)を表示させてみてください。 レスポンスが変わること(=独自のビューを利用できていること)を確認できれば完成です。 また、curlやwgetなどのコマンドを利用してもよいでしょう。

今回はcurlコマンドを利用します。 予め、「http://localhost:3000/shops.json」にアクセスし、shopsのidを確認しておいてください。(私の場合は id = 289486374でした。)
idを確認できたら、実際にshowアクションにアクセスしてみます。 以下のようにコマンドを実行して、同様の出力となることを確かめてください。

curl http://localhost:3000/shops/289486374.json
#=> {"id":298486374,"name":"MyString","address":"MyString"}
curl http://localhost:3000/shops/289486374.json?admin=true
#=>{"id":298486374,"name":"MyString","address":"MyString","created_at":"2016-07-25T01:27:52.900Z","updated_at": "2016-0725T01:27:52.900Z"}

クエリパラメータ「?admin=true」を付与した場合と、付与しない場合でそれぞれ異なるレスポンスを得ることができました。

同様にしてindexアクションのビューを書き換えることもできますが、これは読者の方への宿題としたいと思います。

まとめ

Rails 5がリリースされました。 今回は新機能のAPIモードでアプリケーションを作成するまでの流れを説明しました。 APIの作成にRailsを使うことのメリットとして、これまで使っていたGemやプログラムを流用しやすい点が挙げられると思います。 ぜひ、試してみてください。

それでは、Enjoy Ruby!

注釈

*1 : Rails 5リリースノート:
http://weblog.rubyonrails.org/2016/6/30/Rails-5-0-final/

*2 : 余談ですが、インストール時に表示される内容を眺めていると、新たにGem「actioncable-5.0.0」もしっかりとインストールされていますね。

*3 : 現時点ではバージョン指定しなくてもRails 5.0.0がインストールされると思います。 今回はあらわにバージョンを指定しました。

*4 : Rails Guides中のレイアウト及びレンダリングのページ:
http://guides.rubyonrails.org/layouts_and_rendering.html

*5 : Rails 4までは「rake」コマンドを利用していましたが、Rails 5からは「rails」コマンドが利用できます。 ただし、従来どおりrakeコマンドでも実行できるようです。

 


 

 [IT研修]注目キーワード   OpenStack  OpenFlow/SDN  情報セキュリティ  Python  システムトラブルシュート