ライセンスの作り方(後編)

前提知識

engineて何?

目次

  • 第三過程(engine作成)
    • 雛型生成
    • dbと機能を生成
    • jsonファイルの作成
    • ダミーアプリケーションの組み込み
    • 実装
    • ダミーアプリケーションでの確認
  • 第四過程(gem)

第三過程(engine作成)

雛型生成

engineの雛型はrailsコマンドが作ってくれる。

rails plugin new xxx

実例

クリエイティブコモンズ3.0をcreative_commons_v30_licensesというengine名で作ってみる。

C:\Sites>rails plugin new creative_commons_v30_licenses --mountable -T --dummy-path=spec/dummy
このengineを組み込むと、engineの機能は以下のurlでアクセスできる

dbと機能を生成

engineが作った雛型にもrailsコマンドが含まれている。これでモデルとコントローラを作成する。

ぺったんRでは、どちらもAttribute(attributes)という名前で作成することになっている。

実例

creative_commons_v30_licensesに、モデルとコントローラを作る。

C:\Sites>cd creative_commons_v30_licenses
C:\Sites\creative_commons_v30_licenses>rails g model Attribute
C:\Sites\creative_commons_v30_licenses>rails g controller attributes

jsonファイルの作成

前編で作成したjsonデータをファイルとしてengine内に配置する。dbディレクトリにengine名.jsonの規則で配置する。

実例

creative_commons_v30_licensesの場合

db/creative_commons_v30_licenses.json

ダミーアプリケーションの組み込み

spec/dummyにengineを組み込む想定の環境を構築する。それはつまり、ぺったんRの環境。擬似環境でそれを作り出すのは大変だから、たぶんぺったんRをコピーする方が速い。しかし、ただコピーするだけでは動かない。dummy以下を一旦バックアップコピーしておいて、ぺったんRを上書きコピーしちゃう。その後、両者を見比べながら差異を埋めていく。

実例

creative_commons_v30_licensesの場合

Railsルートのconfig.ruとRakefile、config/boot.rbは上書きダメだから、dummyのバックアップから戻してくる。

config/application.rbはengine読み込みがたりないので以下を追加。

Bundler.require
require "creative_commons_v30_licenses"

gemの依存関係はspec/dummyではなく、engine本体で記述。GemfileとGemfile.lockをengineルートに移動。spec/dummy直下の方が優先順位が高いので、必ず移動する。Gemfileをいじったのでgem更新。

C:\Sites\creative_commons_v30_licenses\spec\dummy>cp Gemfile ..\..\
C:\Sites\creative_commons_v30_licenses\spec\dummy>bundle update

ApplicationControllerの書き換え。ぺったんR本体の事前処理を読み込むようにする。これをしないとログイン中のアカウントはわからなくなる。

module TestSpeechBalloon
  class ApplicationController < ::ApplicationController
  end
end

Dummyの置換。ぺったんR本体のアプリケーション名が変わっているので再定義し直す。

module Dummy
end

Pettanr = Dummy

ただし、development.rbのTestLayout部分は置換すると面倒なので、そのまま。

git対応

spec/dummy以下を削除した時点でspec/dummyをgit管理から無視するように.gitignoreを編集する。ダミーアプリケーション丸ごと管理するのは無理がある。

実装

前提知識(再掲)

ライセンス付与までの流れ

  • 絵師が原画を投稿する → OriginalPicturesCreate が起動
  • ぺったんRが投稿された原画の詳細ページを表示する → OriginalPicturesShow テンプレート
  • 絵師がライセンス付与ボタンを選択する → OriginalPictureLicenseGroupsNew が起動
  • ぺったんRがライセンスグループ選択ページを表示する
  • 絵師がライセンスグループを選択する → OriginalPictureLicenseGroupsCreate が起動
  • ぺったんRがライセンス選択ページを表示する → engineのNew テンプレート
  • 絵師がライセンスを選択して必要項目を入力して送信する → engineのCreate が起動
  • ぺったんRが確認ページを表示する → ResourcePicturesNew
  • 絵師が確認する → ResourcePicturesCreate
  • ぺったんRが素材を作成する

事前準備として以下の作業がある

  • 管理者がライセンスをインポートし、ライセンスグループ(LicenseGroups)とライセンス(Licenses)のマスターデータをぺったんRシステムに登録する。

ライセンス付与に関係する処理は、OriginalPicturesOriginalPictureLicenseGroups、engine、ResourcePictures。この内、ライセンス作成者が作成する部分はengine(ライセンス選択)だけ。そのライセンス選択はRailsのengineとして作成する。

以上のことから、ライセンス作成とは、ライセンスのマスターデータ作成とライセンス付与処理のengine作成のことだと言える。

前提知識(コントローラ編)

先ほどの前提知識を読むと、engineのコントローラがすべきことは以下のことだとわかる。

前提知識(モデル編)

engineのモデルは、フォームで入力されたライセンス固有の情報を検証するためにある。固有情報の他には、全ライセンスに共通した必須項目である著作者名とライセンスidを検証する。また、素材モデルはライセンス固有の情報をカラム別には受け取れないので、jsonテキストに変換する機能を実装するためにある。

ResourcePictureModelの作成に必要なデータで補充が効かないもの。

  • license_id
    • フォームで選択したライセンスのid
  • artist_name
    • フォームで入力した著作者名
  • credit
    • 検証が終わったライセンス固有情報をjsonテキストに変換したもの。

どっちつかずな項目にロゴ画像のidがある。ライセンスのマスターデータから取ってこれる情報だから素材モデルにカラムはない。しかし、現実はロゴを取るだけにライセンスを引っ張ってくるのはアレなので、creditに含んでいる。ライセンスに「ロゴは必須だ」と言い切れないから半端になっている。

テーブルの実装

ライセンス固有の情報をdbのテーブルに落としてMigrationファイルを編集する。さらに以下の必須項目を追加する。

  • license_id
  • artist_name

実例

creative_commons_v30_licensesの場合

前編でデータ構造を以下のように決めた。

  • 作品のタイトル
    • caption
  • クレジット用のURL
    • artist_url
  • 元になった画像
    • source_url
  • 追加的許諾
    • more_permission_url

すべてテキストの入力フィールドで、制限はない。

class CreateCreativeCommonsV30LicensesAttributes < ActiveRecord::Migration
  def change
    create_table :creative_commons_v30_licenses_attributes do |t|
      t.column :license_id, :integer, :null => false, :default => 0
      t.column :artist_name, :string, :null => false, :default => 'unknown'
      t.column :caption, :string 
      t.column :artist_url, :string 
      t.column :source_url, :string 
      t.column :more_permission_url, :string 

      t.timestamps
    end
  end
end
こうなった。

検証モデルの実装

  • 入力データが正しいかを検証する
    • 必須カラム
      • ライセンスidは実在するか
      • 著作者名は必須入力を満たしているか
    • ライセンス固有の情報
  • 必要に応じてデフォルト値の補充機能を実装する
  • クレジットデータ変換では以下の項目を含める
    • system_picture_id ← ロゴ画像のid

実例

creative_commons_v30_licensesの場合

  • 入力データが正しいかを検証する
    • ライセンスidは実在するか
    • 著作者名は必須入力を満たしているか
    • テーブル実装の実例で確認した通り、作品のタイトル等、ライセンス固有の情報には検証すべき条件がない
  • デフォルト値の補充
    • 著作者名は絵師名が入る
  • クレジットデータ変換では以下の項目を含める
    • system_picture_id ← ロゴ画像のid
    • caption ← self.caption(作品のタイトル)
    • artist_url ← self.artist_url (クレジット用のURL)
    • source_url ← self.source_url (元になった画像)
    • more_permission_url ← self.more_permission_url(追加的許諾)

結果、こうなった。

module CreativeCommonsV30Licenses
  class Attribute < ActiveRecord::Base
    belongs_to :license
    
    validates :license_id, :presence => true, :numericality => true, :existence => true
    validates :artist_name, :presence => true
    
    def supply_default ar
      self.artist_name = ar.name
    end
    
    def credit
      {
        :system_picture_id => self.license.system_picture_id,
        :caption => self.caption, 
        :artist_url => self.artist_url, 
        :source_url => self.source_url, 
        :more_permission_url => self.more_permission_url
      }.to_json.to_s
    end
    
  end
end

アプリケーションコントローラ

engine側ではぺったんRのApplicationControllerの機能が処理されない。認証フィルタが動かないと困るから、engine側ApplicationControllerを書き換えて認証処理を通す。

実例

creative_commons_v30_licensesの場合

module CreativeCommonsV30Licenses
  class ApplicationController < ::ApplicationController
  end
end

ルーティング

前提知識(コントローラ編)では、コントローラの仕事は入力フォームの表示と入力データの検証と確認した。つまり、NewとCreateがあればよい。Newは検証で引っ掛かかったときに動く。

engineはぺったんRとルートが被らないようにコンフィグファイルが独立している。engineのアクションはengine名以下のurlで表す。

engineのconfig/routes.rbを編集する。

実例

creative_commons_v30_licensesの場合

CreativeCommonsV30Licenses::Engine.routes.draw do
  resources :attributes do
    new do
      get :new
    end
    collection do
      post :create
    end
  end
end

コントローラ

Create処理のキモは

  • フォーム中のライセンス固有のデータから検証モデルのオブジェクトを生成する
  • フォーム中のデータから素材オブジェクトを生成する
  • 検証モデルを検証する
  • 素材の新規作成テンプレート(ResourcePicturesNew)をRenderingする
  • テンプレートに必要なデータを準備しておく
    • 原画
    • 原画ライセンスグループ
    • ライセンスグループ
    • ライセンス
    • 素材

オブジェクト生成以外は共通した処理。

実例

creative_commons_v30_licensesの場合

require_dependency "creative_commons_v30_licenses/application_controller"

module CreativeCommonsV30Licenses
  class AttributesController < ApplicationController
    layout 'test' if Pettanr::TestLayout
    before_filter :authenticate_user!, :only => [:new, :create]
    before_filter :authenticate_artist, :only => [:new, :create]
    
    def new
      @original_picture = OriginalPicture.show params[:original_picture_id], @artist
      @original_picture_license_group = OriginalPictureLicenseGroup.new(params[:original_picture_license_group])
      @license_group = LicenseGroup.show @original_picture_license_group.license_group_id
      @creative_commons_v30_license = CreativeCommonsV30License.new
      @creative_commons_v30_license.supply_default @artist

      respond_to do |format|
        format.html # new.html.erb
        format.js
      end
    end

    def create
      @original_picture = OriginalPicture.show params[:original_picture_id], @artist
      @original_picture_license_group = OriginalPictureLicenseGroup.new params[:original_picture_license_group]
      @license_group = LicenseGroup.show @original_picture_license_group.license_group_id
      @creative_commons_v30_license = CreativeCommonsV30Licenses::Attribute.new params[:creative_commons_v30_license]
      @license = License.show @creative_commons_v30_license.license_id
      @resource_picture = @original_picture.resource_picture || ResourcePicture.new
      @resource_picture.attributes = {
        :original_picture_id => @original_picture.id, :license_id => @license.id, 
        :artist_name => @creative_commons_v30_license.artist_name, :classname => @license_group.classname, 
        :credit => @creative_commons_v30_license.credit, :settings => @license.settings
      }
      respond_to do |format|
        if @creative_commons_v30_license.valid?
          format.html { render main_app.new_resource_picture_path }
          format.js { render :template => "resource_pictures/new" }
        else
          format.html { render action: "new" }
          format.js { render action: "new" }
        end
      end
    end

  end
end

ビュー

以下のビューを用意する。

  • Newアクション用の入力フォーム
  • 確認用の部分テンプレート_confirm.html.erb
  • クレジット表示用の部分テンプレート_credit.html.erb
New

テンプレートで得られるデータは以下。

  • 原画
  • 原画ライセンスグループ
  • ライセンスグループ

留意点は、検証モデルのオブジェクトがないこと。これはOriginalPictureLicenseGroupsCreateからはengineを見ていられない構造だから。よって、テンプレートでは必ず検証モデルを生成しておく。ただし、検証エラーでRenderingされる時だけはオブジェクトが生成されているので、このケースでは生成してはならない。

実例

creative_commons_v30_licensesの場合

<% 
  unless @creative_commons_v30_license
    @creative_commons_v30_license = CreativeCommonsV30Licenses::Attribute.new
    @creative_commons_v30_license.supply_default @artist
  end
 %>
<%= form_tag('/creative_commons_v30_licenses/attributes') do %>
  <%= collection_select :creative_commons_v30_license, :license_id, @license_group.licenses.map {|l| [l.caption, l.id] }, :last, :first %>

略

<% end %>
確認用部分テンプレート

engine内で検証モデルの検証を通過した後は素材の新規作成をRenderしてぺったんRに処理を返すが、そこは入力結果の確認なので、入力されたライセンス固有の情報をengine側に表示してもらう必要がある。

テンプレートで得られるデータは以下。

  • 原画
  • 原画ライセンスグループ
  • ライセンスグループ
  • ライセンス
  • 素材
クレジット表示部分テンプレート

クレジットを表示するために部分テンプレートを用意する。実素材だけを参照できる。

実例
<div>
<%= content_tag(:a, 
  tag(:img, :src => picture.license.system_picture.url, :alt => h(picture.license.license_group.caption.to_s + '[' + picture.license.caption.to_s + ']')), 
  :href => picture.license.url )
%>
</div>

ダミーアプリケーションでの確認

ダミーアプリケーションのルーティングを書き換えてengineをマウントする。spec/dummy/config/routes.rbに以下を追加する。

  mount CreativeCommonsV30Licenses::Engine => "/creative_commons_v30_licenses"

engineのmigrateファイルをダミーアプリケーションにコピーしてMigrationする。

C:\Sites\creative_commons_v30_licenses\spec\dummy>rake creative_commons_v30_licenses:install:migrations
C:\Sites\creative_commons_v30_licenses\spec\dummy>rake db:migrate

jsonデータをインポートする。

C:\Sites\creative_commons_v30_licenses\spec\dummy>rails r "LicenseGroup.import('C:\Sites\creative_commons_v30_licenses\db\creative_commons_v30_license.json')"
これをRakeタスクでインポートさせたい。

そしてサーバ起動。

C:\Sites\creative_commons_v30_licenses\spec\dummy>rails s
ぺったんRが動いて、ライセンスのengineが動くことを確認する。

第四過程(gem)

単純に言えば.gemspecのTODO部分を編集してgem buildするだけ。

C:\Sites\creative_commons_v30_licenses>gem build XXXX.gemspec

配布

ライセンスはrubygems.orgで配布する。

テスト環境

gemの参照先が変わったので、テストファイルを作り直し。途中何か聞かれるかもしれないがデフォルト値で進む。

C:\Sites\creative_commons_v30_licenses>rails g rspec:install
C:\Sites\creative_commons_v30_licenses>rails g rspec:model Attribute
C:\Sites\creative_commons_v30_licenses>rails g rspec:controller Attribute
spec_helperをengineのspec下に配置する。下記の編集。アプリケーションの位置とサポートツールの位置を設定。
require File.expand_path("../dummy/config/environment", __FILE__)
    ENGINE_RAILS_ROOT=File.join(File.dirname(__FILE__), '../')

    # Requires supporting ruby files with custom matchers and macros, etc,
    # in spec/support/ and its subdirectories.
    Dir[File.join(ENGINE_RAILS_ROOT, "spec/support/**/*.rb")].each {|f| require f }
factories.rbもspec下に配置する。

specファイルはspec下に配置する。controllersとmodelsディレクトリを作成する。controllerは必ずcontrollers\engine名\attributes_controller_spec.rbの要領で配置する。

テスト記述の注意点

engineの名前空間に従って記述する。

attributes_controller_spec.rbでは

describe StandardLicenses::AttributesController do
モデルを参照するには
StandardLicenses::Attribute
コントローラのget、POSTでNo route matchesに悩まされたら
get :new, :use_route => :standard_licenses
のようにルートを指示してやる。

うっかりミス

テストdbにテーブルを作成していない

rake standard_licenses:install:migrations
rake db:migrate RAILS_ENV='test'
Gemfileでテスト対象のengineを読み込んでいる

gem化