なんとな~くしあわせ?の日記

「そしてそれゆえ、知識そのものが力である」 (Nam et ipsa scientia potestas est.) 〜 フランシス・ベーコン

JRubyに挑戦2

f:id:panzer-jagdironscrap1:20170920235632p:plain

もとの記事は JRuby - FreeStyleWiki

とりあえずJRuby+Hanami+HerokuでREST APIを動かすところまでできたので記事を書いておく。

REST APIを作ってみる

最初の目的であるREST APIを作る

  • HanamiによるAPIの作成手順

ほとんどコレの内容を見て作成した。使用するSQLについてはいろいろ変えている。

speakerdeck.com

作りたいもの
  • APIの場所は /rest/employees にしたい*1

Hanami - ドキュメント

新規アプリの追加
> jruby -S bundle exec hanami generate app api

これで apps/api という階層ができる。Railsと違って複数のアプリが作成できるようだ。

新規Action/Viewの追加
> jruby -S bundle exec hanami generate action api employees#list

これで employees というActionができる。ルートテーブルにもいろいろ追加された。


ここまででできていること

  • Hanamiの全体設定としてのアプリのURL(マウント)設定
  • config/environment.rbApi アプリケーションのマウント場所が書いてある

つまり、http://{ホスト名}/api 以下にアプリが配置されるということ

Hanami.configure do
  mount Api::Application, at: '/api'
...
  • Apiアプリの設定ファイル
  • '''apps/api/application.rb''' に '''Api''' アプリケーションの設定が書いてある

デフォルトではhtmlを返す設定になっているので、API用に以下を追記する

module Api
  class Application < Hanami::Application
    configure do

      # 以下を追加
      default_request_format :json
      default_response_format :json
      body_parsers :json
      ...
  • employeesのためのコントローラー
  • apps/api/controllers/employees/list.rb に employeesを返すためのModuleができている

これの面白いところはコントローラーがメソッドごとに分割されているところだろう

module Api::Controllers::Employees
  class List
    include Api::Action
    accept:json # 追加

    def call(params)
    end
  end
end
  • employeesのためのビュー
  • apps/api/views/employees/list.rb に オブジェクトをJSONにして返すためのModuleができている

これもメソッドごとに分割されている

module Api::Views::Employees
  class List
    include Api::View
    # 以下を追加
    layout false 

    def render
      "[]"
    end
  end
end
新規Entity/Repositoryの追加

ここでモデルと言わずE/RになってるのはRailsと違うところなのだと思う。

  • employeeのモデルクラス作成

モデルは単数形になることに注意

> jruby -S bundle exec hanami generate model employee
  • 元となる理想のテーブル定義(MySQL)
CREATE TABLE employees (
    emp_no      INT             NOT NULL,  -- UNSIGNED AUTO_INCREMENT??
    birth_date  DATE            NOT NULL,
    first_name  VARCHAR(14)    NOT NULL,
    last_name   VARCHAR(16)    NOT NULL,
    gender      VARCHAR(1)     NOT NULL,  -- Enumeration of either 'M' or 'F'
    hire_date   DATE            NOT NULL,
    PRIMARY KEY (emp_no)                   -- Index built automatically on primary-key column
                                           -- INDEX (first_name)
                                           -- INDEX (last_name)
);
  • 作成されたmigrationファイルを改変する
  • db/migrations/YYYYMMDD000000_create_employees.rb
Hanami::Model.migration do
  change do
    create_table :employees do
      primary_key :emp_no, Integer, null: false
      column :birth_date, Date, null: false
      column :first_name, String, null: false, size: 14
      column :last_name, String, null: false, size: 16
      column :gender, String, null: false, size: 1
      column :hire_date, Date, null: false
    end
  end
end

Hanamiのマイグレーションファイルの書き方は、ここに書いてある

そして、実は生のSQLでも実行できる

Hanami::Model.migration do
  up do
    execute %{
      INSERT INTO `employees` VALUES (10001,'1953-09-02','Georgi','Facello','M','1986-06-26'),
      (10002,'1964-06-02','Bezalel','Simmel','F','1985-11-21'),
      (10003,'1959-12-03','Parto','Bamford','M','1986-08-28'),
      ...
    }
  end
end
employeeのDBのテーブルを作成

以下の手順でエラーが出たときは、おそらくWindows/Unix間のパスの問題なので .env.development を見る

  • .env.development
  • windowsの場合、migrate実行時は"jdbc:sqlite:db\\[sqliteのdbファイル名] にする必要がある
DATABASE_URL="jdbc:sqlite:db\\generic_dao_jruby_development.sqlite"
  • 開発環境はSQLite3で動く、ゼロから始める場合は以下のコマンドでdbファイルを作成
> jruby -S bundle exec hanami db create
  • その後以下のコマンドでSQLを実行
> jruby -S bundle exec hanami db migrate
JSONを返す部分を追記

とりあえず、以下の追記でサーバを再起動すればJSONが出力されるはず

  • apps/api/controllers/employees/list.rb
   class List
      include Api::Action
      accept :json
 +    expose :employees
  
      def call(params)
 +      @employees = EmployeeRepository.new.all
      end
    end
  end
  • apps/api/views/employees/list.rb
 module Api::Views::Employees
   class List
     include Api::View
      layout false
  
      def render
 -      "[]"
 +      _raw JSON.dump(employees.map{|employee| employee.to_h })
      end
    end
  end
hanami-modelについての見解

さて、ここまででサーバが起動できるはず

> jruby -S bundle exec hanami s

http://localhost:2300/employee にアクセスするとJSONが返ってくる*2

Hanamiのすごいところは、ここまででO/Rをマッピングするコードを書いていないところだ。詳しくはhanami-modelのREADMEを見れば良いと思うが、model/README.md at master · hanami/model · GitHub。これは結構すごいと思う。

つまりはDB側でカラムの更新があり、usersテーブルにbirthdayというカラムを追加したとしても特にmodelの側でコードの変更は無いということだ。

Javaのものと比べるとすると、例えばMyBatisだとmapper.xmlの中にDBのカラムとObjectのフィールド要素をマッピングするファイルが必要になる。例えばHibernateだとクラスのフィールドに対してアノテーションを付けまくる必要がある。

*1:本当は間に/v1をはさみたい

*2:何かデータは入れておいてね