Posts WindowsでRails、ついでにHeroku
Post
Cancel

WindowsでRails、ついでにHeroku

Railsのお仲間ができるといいな、なんて思い、ここ数年で状況も変わってきたので、WindowsでRailsの開発をする方法をまとめてみました。

それから若干のRailsの基礎知識も触れます。

私が動作確認した環境

Windows 10(ただし、Mac Book Pro Late 2016(Sierra)のPrallels12上です。

まずはRubyのインストール(+MYSYS2, + Node)

現状のRubyInstallerの状況を軽くまとめると以下のようになります。

  • これまで、RubyInstaller + DevKit による環境構築が一般的でした。
    • DevKit は Native extention のインストールの際(つまりコンパイルしたりする時)に使用。
    • しかしメンテナーが忙しくてギブアップし、Ruby 2.3.3 で止まっています(既に2.4が普通・・・2017/07/11現在)。
  • なので、MSYS2 + RubyInstaller2(実はこれがすでにRubyInstallerという名前になっているみたい)に変更せざるを得ません。
    • MSYS2は前のDevKitに相当するもの。
    • 以前のhttps://rubyinstaller.org/から最新版(2017/07/11現在RubyInstaller 2.4.1-2が最新)をダウンロードできます。

      ほとんど以下ではデフォルトでインストールしていますが、デフォルトのディレクトリProgram Filesのスペースがコマンドプロンプト上で困る経験が何度かあったので、ディレクトリはProgram Filesを取ってインストールしています。

  1. Rails5.1(5.0からかも)から、Nodeが必要になった(らしい)ので、Nodeをインストールします。もし、すでにインストールしている場合は必要ないかもしれません。ただ、相当古いと何か問題が出るかもしれませんので、ついでにここで新しくする手もあると思います。https://nodejs.org/ja/download/から、インストーラーをダウンロードして、実行してインストールします。

  2. https://rubyinstaller.org/からrubyinstallerをダウンロードして、これを実行します。
    1. 最初の画面では、一番下(UTF-8を使う)のにチェックを入れます(デフォルトでは付いていませんでした)。インストールディレクトリーはC:/Ruby24-x64です。

      utf-8 checked

    2. 途中で、Command Promptが立ち上がり、1〜3のどれにする?って聞かれます(下の図参照)。

      which select

      ここでは1→2→3という順番に選択してください。MSYS2のインストールディレクトリーはC:/msys64です。結構updateには時間がかかります。ちなみに、1を選んでインストールすると、また最初の選択画面に戻りますが、ここでそのままリターンを打ち込むと終わってしまうので、必ず番号を入力してからリターンを打ち込んでください。

    3. パスなどは自動で環境変数にセットしてくれていますが、Command Promptを開いてruby -vとかで、rubyが立ち上がればそのまま作業を続ければ良いですが、そんなコマンドがないとか言われたら、Windowsを再起動してください(私は再起動した結果、下のようにバージョンが表示されました)。

      ruby version

  3. 最後にgemというライブラリ管理ツールを使って、Railsをインストールしてみます。コマンドプロント上で以下を実行します。

    1
    
    gem install rails
    

これが下のように問題なくインストールされれば、OKです。

rails install

Railsのサンプルプロジェクトを作成

1
2
rails new rails-sandbox
cd rails-sandbox

を実行して、

1
rails s

を実行し、ブラウザでhttp://localhost:3000を見ると、以下が表示されればとりあえずOKです。

ちなみに、敢えてDBの指定がない場合(つまり今回の場合)、DBはsqliteが利用されます。

application

少しばかりRailsの基礎を

Railsはルールに則ってやるのであれば、極簡単にプログラミングできますが、ルールに反したことをしようとするととたんに面倒になります。

つまり、できる限りRailsのデフォルトの手法を覚えていった方が良いだろう、ということなので、その辺を極簡単に触れます。

RESTful→routes.rb

https://www.webprofessional.jp/what-does-restful-really-mean/とか、Google先生に聞くと色々と説明しているところがあるので、RESTfulについてはここでは深入りしません。

ごく簡単にRailsに関係するところだけ触れれば、http://dokoka.com/blogsとかにGETでアクセスすると「ブログ一覧」が表示され、http://dokoka.com/blogs/1だとidが1のブログの内容を表示し、http://dokoka.com/blogs/1/editだとidが1のブログの内容を編集する画面が表示されます。

これらのURIとRailsのプログラムのマッピングを定義するのが、config/routes.rbです。これを編集していくことになります。

例えば、後にも出てきますが、blogsというテーブル(テーブル名は複数形)へのCRUD(Create, Read, Update, Delete)ならば、次の1行をroutes.rbに書き込むだけ、定義できてしまいます。

1
  resources :blogs

つまり、下のような8つのroutesが定義されたということになります(下の出力はrails routesで出力されたものです)。「Prefixって何?}とかはここではスルーしておいてください(後で少しだけ触れます)。

1
2
3
4
5
6
7
8
9
10
rails routes
         Prefix Verb   URI Pattern                Controller#Action
          blogs GET    /blogs(.:format)           blogs#index
                POST   /blogs(.:format)           blogs#create
       new_blog GET    /blogs/new(.:format)       blogs#new
      edit_blog GET    /blogs/:id/edit(.:format)  blogs#edit
           blog GET    /blogs/:id(.:format)       blogs#show
                PATCH  /blogs/:id(.:format)       blogs#update
                PUT    /blogs/:id(.:format)       blogs#update
                DELETE /blogs/:id(.:format)       blogs#destroy

MVC

ここでも細かいMVCの概念は触れませんが、Railsではそれぞれ何が担当しているかをに触れてみます。

  1. Model

    ここでいうModelとは、テーブルに対応したクラスを言います。つまりテーブルの1レコードに対してModelの1インスタンス、という感じです。先ほどテーブル名は複数形と言いましたが、Model名はテーブル名の単数形になります。

    ファイル名はapp/models/blog.rb、中に定義するのはBlogという大文字で始まるクラス名となります。

    Railsでは通常DBにアクセスするのに、SQLは発行せず、このModelを通じてDBにアクセスすることになります。また、サーバ側のバリデート(必須とか最大文字数とかのチェック)などもこのModelに書き込みます。

  2. Controller

    コントローラは(本来は違うようですが)ビジネスロジックを書くところで、上記で触れたconfig/routes.rbで定義しているController#Actionを書くところになります。つまり、あるURIが呼ばれたら、コントローラのメソッド(Action)が呼ばれるということになるわけです。

    そして次のView使って、htmlを作成し、それをブラウザーに返します。もちろん、JSONとかも返すことが可能です。

    ファイル名はModelと同じ単数形_controller.rbで、app/controllers/blog_controller.rbで、クラス名はBlogControllerとなります

  3. View

    ERBというテンプレートライブラリをデフォルトでは使うようになっています。これはデフォルトではControllerの名前の複数形(ディレクトリ名)とメソッド名(ファイル名)との組み合わせで決まります。

    例えば、blog_controller.rbのindexというメソッド(ブログ一覧)の場合はapp/views/blogs/index.html.erbというファイル名になります

    最低<% %>とかにif文とかを書き、<%= ** %>は**(これは変数だったりメソッドだったりします)をテンプレート内に出力します。

railsコマンド

Rail4まではrailsコマンドとRakeコマンドがあったのですが、Rails5からはrailsコマンドに統一されました。先ほどrails routesでルーティング一覧を出力しましたが、このようなものを少しずつ覚えていく必要があるでしょう。

Gemfile, bundleコマンド

Rubyではgemというパッケージツールがあって、簡単に世界中で開発したライブラリが利用できますが、RailsではGemfileというファイルに必要なライブラリを書いておき、コマンドラインで

>bundle install

を実行すると、それらをインストールすることが出来ます。

development、production、test

Railsではdevelopment、production、testの3つの環境があって、データベースなども分けるのが普通です。デフォルト(何も指定していない場合)はdevelopmentになります。リリースするときなどはproductionの環境にします(後にHerokuにdeployした場合は自動でproductionになります)。

これにはいくつかの意味があります。いくつかをピックアップすると

  • 開発の時に間違って本番サーバのレコードを触ってしまわないようにする
  • javascriptやCSSをuglify(コード等を短くする)するのに時間がかかるので、開発環境ではuglifyしないが、少しでもレスポンスがよくするために本番サーバではuflifyする
  • 開発中はRailsを再起動しないでもコードの変更が反映された方が良いが、それだとレスポンスが若干遅くなるので本番サーバでは反映されないようにする

などです。

そして、モード毎に環境などを変えたい場合はconfig/environments/development.rbconfig/environments/production.rbなど別々に書き込んでおくことによりそれを実現します。あとでその例が最後のあたりに出てきます。

migrationファイル

DBのテーブルなどはmigrationファイルを通して作成します。db/migrate/20170711102832_create_blogs.rbみたいなファイルですが、これは手作業で作成することも可能ですが、通常はコマンドで作成することが多いと思います。このすぐ後にscaffoldで作成しますが、

1
rails g model モデル名 フィールド:型:(unique|index) 以降必要文だけのフィールド(スペース区切り)`

のような方法でも作成できます(この場合Modelも作成されます)。

このMigrationを通じてDBを管理するメリットの1つは、開発中はフィールド等の変更や追加が良く怒りますが、変更するたびにmigrationファイルを作成しておくことにより、元に戻したりすることが簡単にできるということです。また、SQLを直に発行する必要がなくなることもメリットかもしれません。

先ほどのrails-sandboxにPDFを作成する機能の追加

scaffoldとmigrateの実行

RailsにはScaffoldというのがあって、先ほど極簡単に触れたように通常Railsでは次の

  1. テーブルのmigrationファイル
  2. Controller
  3. View
  4. Model
  5. Routes

あたりを作成する必要があるのですが、このscaffoldを使うと、これらのファイルを一気に作成してくれます。もちろん、その作られたファイルをそれだけで使えるアプリになるわけじゃありませんが、とりあえず、scaffoldでひな形を作った方が楽な場合もあるでしょう。

1
2
> rails g scaffold Blog title:string content:text posted_date:date
> rais db:migrate

上記を実行するとブログを登録したりすることが可能になったりします。もちろん、素っ気ないものですが・・・。

フィールドは僅かに3つです。タイトルとブログの内容、そして、リリース日(公開日)です。リリース日自体の実装はしません(長くなりそうなので)。

ちなみに型はSQLのようには詳しく書きません。stringを指定するとMySQLあたりだとデフォルトでvarchar 64とかになります。

一度migrationファイルを作っておいて、中身に後からファイルの中身を編集してvarcharの長さを長くしたり、デフォルトの値を設定したり、not null属性を入れたりすることが出来ます。

一度db:migrateしても、db:rollbackして、それからmigrationファイルを変更して、再度db:migrateとかすることも可能です。

scaffoldで出来たもの

実際できるものは、以下のようなものです。

DBのMigration File

ファイル名:rails-sandbox/db/migrate/20170711102832_create_blogs.rb

  1. title: varchar64
  2. content: text
  3. posted_date: date

というフィールド(今はデフォルトのsqliteなので、型的にはそれに近いもの)を追加するスクリプトファイル。

Railsはserial(sequence)なIDというフィールドとcreated_atとupdated_atというフィールドが自動で追加されます。

実際にはrails db:migrateを実行すると、DBにテーブルが作成されます。

※ファイル名の前についているのは日時等なので、状況により変わります。

Controller

ファイル名:rails-sandbox/app/controllers/blog_controller.rb

コメントを取って、表示すると以下のような内容のファイルが出来ています。これらは、ブログレコードを管理するためのコントローラになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class BlogsController < ApplicationController
  before_action :set_blog, only: [:show, :edit, :update, :destroy]

  def index
    @blogs = Blog.all
  end
  def show
  end
  def new
    @blog = Blog.new
  end
  def edit
  end
  def create
    @blog = Blog.new(blog_params)
    respond_to do |format|
      if @blog.save
        format.html { redirect_to @blog, notice: 'Blog was successfully created.' }
        format.json { render :show, status: :created, location: @blog }
      else
        format.html { render :new }
        format.json { render json: @blog.errors, status: :unprocessable_entity }
      end
    end
  end
  def update
    respond_to do |format|
      if @blog.update(blog_params)
        format.html { redirect_to @blog, notice: 'Blog was successfully updated.' }
        format.json { render :show, status: :ok, location: @blog }
      else
        format.html { render :edit }
        format.json { render json: @blog.errors, status: :unprocessable_entity }
      end
    end
  end
  def destroy
    @blog.destroy
    respond_to do |format|
      format.html { redirect_to blogs_url, notice: 'Blog was successfully destroyed.' }
      format.json { head :no_content }
    end
  end
  private
    def set_blog
      @blog = Blog.find(params[:id])
    end

    def blog_params
      params.require(:blog).permit(:title, :content, :posted_date)
    end
end

次のような、8つのpublicなメソッドと2つのprivateなメソッドが定義されています。

controllerの中にすでに定義されているメソッド

  1. public
    1. index

      これはレコードの一覧を表示させるメソッド。

    2. show

      1つのレコードの値を表示させるメソッド。

    3. new

      新しいレコードを登録するためのフォームを表示するメソッド(登録するのは次の次のメソッド)。

    4. edit

      レコードの編集するためのフォームを表示するメソッド(値はフィールドにセットされています)。

    5. create

      newで開いたフォームの値を元にレコードを作成するメソッド。

    6. update

      editで表示されたフォームに対して何らかの値を修正して、それらの変更を更新するメソッド。

    7. destroy

      レコードをIDを指定して削除するメソッド。

  2. private
    1. set_blog

      編集の時(edit)やデータの表示(show)等にidからレコードを拾ってBlogモデルクラスのインスタンスにレコードをセットするメソッド。

    2. blog_params

      Strong Parameterといって、リクエストの値の中にシステムが要求している以外のフィールドとかないかチェックしているメソッド(今回は詳細は避けます)。

View

いわゆるテンプレートです。jbuilderという拡張子のものはjsonを返すためのものなので、ここでは触れません。残り、次のようなファイルが出来ています。

  1. _form.html.erb

    edit.html.erbとnew.html.erbからインクルードされている、htmlのformが書かれている(後述)。

  2. index.html.erb

    レコード(Blog)の一覧表示するもの。下の図は、1つだけレコードを作成した場合の一覧(といっても1レコードですが)が表示されているキャプチャ。 blog_list

  3. new.html.erb

    ほとんど、_form.html.erbを読み込んでいるだけ。 blog_new

  4. edit.html.erb

    ほとんど、_form.html.erbを読み込んでいるだけ。ただし、先ほどのset_blogでDBの値がセットされています。

    blog_edit

  5. show.html.erb

    レコードの値の表示するもの。

    blog_show

_form.html.erbの中身を少し眺めてみると・・・。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<%= form_with(model: blog, local: true) do |form| %>
  <% if blog.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(blog.errors.count, "error") %> prohibited this blog from being saved:</h2>

      <ul>
      <% blog.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :title %>
    <%= form.text_field :title, id: :blog_title %>
  </div>

  <div class="field">
    <%= form.label :content %>
    <%= form.text_area :content, id: :blog_content %>
  </div>

  <div class="field">
    <%= form.label :posted_date %>
    <%= form.date_select :posted_date, id: :blog_posted_date %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>
  1. <% 〜 %>は、この間にERB(テンプレートの名前)のif文やforeach文、或いはレコードの値を拾って出力するとかできます。
  2. <%= 〜 %>は「=」があるところだけが前項と違いますが、これはhtml内にそこの返値が書き込まれます。つまり<% print hoge() %> = <%= hoge() %>という感じです。
  3. form_withは、Rails5.1から(確か)導入されたもので、以前はform_for(条件によってはform_tag)を使っていたものです。実際のテンプレートでは以下のようになって、URLとメソッド名が明示され、クロスサイトリクエストフォージェリ対策に使われるトークンも自動で負荷されています。
    1
    2
    3
    4
    
     <form action="/blogs" accept-charset="UTF-8" method="post">
       <input name="utf8" type="hidden" value="&#x2713;" />
       <input type="hidden" name="authenticity_token"
         value="nGj+gwfEMKCgbW7jOE/aRLRo+RxbAPDJqQyYlGA7mUf9XdFbz6INXahjVZ6kQUdMFi+5lq4FLr+5TrcVxIdVQA==" />
    
  4. テーブルに紐付いたレコードの場合は(Railsではこれが普通)、括弧の中にテーブルに対応するモデル名を括弧に入れ、それとは関係ありませんが、local: trueが入っています。後者はデフォルトでremote: true(AJaxでデータのやりとりを行う)なので、これを明示的に否定しています(もちろん、remote: trueにする場合は若干手を加える必要がありますが、ここでは省略します)。
  5. 3〜12行目は入力errorを表示するところ。ここでいうエラーとはModelでValidationを定義すると、そのValidationに引っかかった場合、それに対応するメッセージが表示されます。例えば「必須です」みたいな感じです。

    pluralizeは第1引数の数(ここではエラーの数)が2以上の場合、文字列errorを複数にしてくれるActionView::Helpers::TextHelperのメソッドで、ここは単に「3 erros prohibited this blog from being saved:」と表示するもの。

  6. <%= form.label :title %><%= form.text_field :title, id: :blog_title %>は、実際のHTMLでは
    1
    2
    
    <label for="blog_title">Title</label>
      <input id="blog_title" type="text" name="blog[title]" />
    

    のようにIDやname等が自動で付いています。ただしこのままでは英語のままですが、これは後に変更します。 残りの2つのフィールドはhttp://api.rubyonrails.org/あたりに行って、form_withとかで検索して(左上に検索フィールドがある)調べてみてくださいw

  7. <%= form.submit %>で、デフォルトではサブミットボタンが「Create Blog(新規作成の時)」と表示されています。

Model

ファイル名:rails-sandbox/app/models/blog.rb

ActiveRecordというRailsのORマッパーの中心がこのModelです。テーブルの1レコードが、Modelの1インスタンスというイメージです(もちろん、多対多の定義等もできて相当高機能ですが、ここでは触れません)。

今回はblogというテーブルを作成したので(上でrails g scaffold Blog title:string content:text posted_date:dateというコマンド参照)、BlogというクラスがModelになります。

>
1
2
class Blog < ApplicationRecord
end

わずかに2行です。このBlogクラスを使って、保存したり、検索したり、削除したり出来ます(不思議なことに)。

さて、少し前にValidation(値検証)を入れてみましょう。Modelに書き込みます。

>
1
2
3
4
class Blog < ApplicationRecord
  validates :title, presence: true, length: { maximum: 20} 
  validates :content, presence: true, length: { maximum: 500 }
end

見たまんまですが、titleが必須・文字数最大20文字まで、contentが必須・最大500文字まで、という感じです。

この制限の中で、titleを21文字を入力し、contentは何も書き込まず「Create Blog」ボタンをクリックすると、下のような画面が表示されます(まだメッセージとか、英語ばかりですね。少々お待ちをw)。

validation_en

Routes

ファイル名:rails-sandbox/config/routes.rb

scaffoldで作成されたファイルの最後はroutes.rbです。

>
1
2
3
Rails.application.routes.draw do
  resources :blogs
end

これもたった3行です。実際にはresources :blogs1行ですね。

rails-sandboxディレクトリでrails routesというコマンドをたたくと(一度上でやりました)

1
2
3
4
5
6
7
8
9
10
C:\Users\chikkun\rails-sandbox>rails routes
   Prefix Verb   URI Pattern               Controller#Action
    blogs GET    /blogs(.:format)          blogs#index
          POST   /blogs(.:format)          blogs#create
 new_blog GET    /blogs/new(.:format)      blogs#new
edit_blog GET    /blogs/:id/edit(.:format) blogs#edit
     blog GET    /blogs/:id(.:format)      blogs#show
          PATCH  /blogs/:id(.:format)      blogs#update
          PUT    /blogs/:id(.:format)      blogs#update
          DELETE /blogs/:id(.:format)      blogs#destroy

詳細は避けますがresources :blogsで上記のようなURLとコントローラのメソッドとのマッピングが定義されています。

  1. /blosgというURLをGETメソッドでアクセスすると、blogs_controllerのindexメソッドが実行されます。
  2. /blosgというURLにPOSTメソッドでアクセスすると(POSTなので、基本Formのsubmitやbutton等からしかアクセス出来ません)、blogs_controllerのcreateメソッドが実行されます。
  3. /blosg/レコードのID(数字)/editというURLをGETメソッドでアクセスすると、blogs_controllerのeditメソッドが実行されま。
  4. /blosg/レコードのID(数字)というURLをPUT(or PATCH)メソッドでアクセスすると(これもフォームからが基本になります)、blogs_controllerのupdateメソッドが実行されま。

ちなみに、レコードのCRUDとかとは関係ないURLを定義したいときなどはどうするかというと、いろいろな書き方がありますが、例えば次のような書き方で定義できます。

>
1
  get "blogs/download", as: "blogs_download", to: "blogs#download"

これだと、例えば、blogs/download/にGETでアクセするとblogs_controllerのdownloadメソッドが実行され、ブログの全データをダウンロードするというようなことも定義できます。

少しばかり日本語化

日本語の保存等はできますが、表示が英語ばかりです。これを変更してみます。もちろん、フォームの中身を日本語にしてしまうという手もありますが、i18nに対応させて、日本語化します。

ja.ymlの作成(というかダウンロード)

https://github.com/svenfuchs/rails-i18n/tree/master/rails/localeに行って、ja.ymlをダウンロードしてきて、rails-sandbox/config/localesに置きます。

localeの指定(Rails)

次にrails-sandbox/config/application.rbの中に

>
1
2
3
4
5
6
module RailsSandbox
  class Application < Rails::Application
    config.load_defaults 5.1
    config.i18n.default_locale = :ja# ←これ
  end
end

を書き込みます。

これにより、エラーメッセージの一部とsubmit button等が日本語になります。

validation_partial_jap

ja.ymlへの追加

1でダウンロードしたja.ymlに次の文言を書き込みます。8行目から12行目までのattributes以下です。

>
1
2
3
4
5
6
7
8
9
10
11
12
ja:
  activerecord:
    errors:
      messages:
        record_invalid: "バリデーションに失敗しました: %{errors}"
        restrict_dependent_destroy:
          has_one: "%{record}が存在しているので削除できません"
    attributes:
      blog:
        title: タイトル
        content: ブログ内容
        posted_date: 投稿日

これにより、titleとかcontentが日本語化されます。

validation_almost_jap

ただ、残念ながらページタイトルや「Back」、1行目のエラーメッセージが英語です。

さらにja.ymlに項目追加

以下の2行目〜10行目、そして22行目〜32行目を追加しました。

>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
ja:
  blogs:
    index:
      title: ブログ一覧
    show:
      title: ブログ表示
    edit:
      title: ブログ編集
    new:
      title: ブログ新規作成
  activerecord:
    errors:
      messages:
        record_invalid: "バリデーションに失敗しました: %{errors}"
        restrict_dependent_destroy:
          has_one: "%{record}が存在しているので削除できません"
    attributes:
      blog:
        title: タイトル
        content: ブログ内容
        posted_date: リリース日
  global:
    message:
      back: 戻る
      validation_error: "入力したデータに不正な形式のものが見つかりました。"
      new: 新規作成
      edit: 編集
      show: 表示
      destroy: 削除
      list: 一覧
      blog: ブログ
      confirm: "マジっすか?"

2行目〜10行目は、各コントローラとメソッド、それに対応するテンプレートで使えるキーになります。

例えば、

>
1
2
3
4
ja:
  blog:
    index:
      title: ブログ一覧

は、blog_controllerのindexメソッドのviewのなかでt('.title')とやると「ブログ一覧」に置き換わります。titleの前にドットが必要です

エラーが起こったりするのはYAMLのインデントの問題のことが多いので、おかしなエラーが起こったらインデントがしっかりついているか、タブを使っていないかなどご確認ください。

それから、22行目〜32行目までは、いろいろなテンプレートから参照できるものを定義しています(キーはご自由に)。

この場合は、例えば、edit.html.erbでは

>
1
2
3
4
5
6
7
8
<h1>
<%= t('.title')%><!--ここ-->
</h1>

<%= render 'form', blog: @blog %><%# ここでフォームを読み込んでいる(ファイル名はアンスコが先頭に付きます!) %>

<%= link_to t('global.message.show'), @blog %> |
<%= link_to t('global.message.back'), blogs_path %>

のように、t('global.message.show')のように、yamlの階層に合わせてドットでつないで利用します。ちなみにt()はtranslate(翻訳)の頭文字のメソッド(短いので、書くのが簡単w)。

edit_jap

英語でも変えたいのであれば、もちろん、en.yamlに書くことになります(フランスだったらfr.haml)。

RailsでPDFを作成し表示

とりあえずレコード追加

databaseにレコードがないとPDF化するどころじゃないので、まずはレコードを追加するrails-sandbox/db/seeds.rbにスクリプトを書き込みます。

>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Blog.delete_all#とりあえずレコードを全削除
#リリース日付を今日から1日ずつ、後にしていく(適当)
today = Date.today

# 「ああああ」とか「かいいいいいい」とかタイトルとブログ内容をフィールドにセットしてレコードを10個作る
# 時々BRタグと改行を入れる

('あ' .. 'こ').each do |a|
  today = today + 1.day
  c_n = rand(150) + 30
  cons = a * c_n
  r = rand(10) + 20
  (1..c_n).each do |n|
    cons.insert(n, "<br />\n") if n % r == 0
  end
  Blog.create(title: a * (rand(10) + 5), content: cons, posted_date: today)
end

これを書き込んで、コマンドプロンプト上で次を実行します。

>rails db:seed

これでレコードができます。randとかを駆使して、適当なレコードを作成しています(contentにbrタグがありますが、Railsではエスケープされてしまうのですが、このあとBootstrapを使ってデザインも変えるので、少し後でエスケープされないようにテンプレートを変更します)。

ちなみに、RailsではBlog.create(…)

1
Blog.create(title: a * (rand(10) + 5), content: cons, posted_date: today)

で新しいレコードを即保存します(Blog.newでは即保存はせずに、その後saveメソッドで保存することも出来ます)。

デザインもちょっとは

今回はPDFにしようと思っているのはブログ一覧なので、index.html.erbだけ少しデザインしてみます。

私的にはBootstrapを使って、簡単にデザインしたいので、またGemfileに次のものを書き込んでbundle installを行います。

>gem 'bootstrap-sass'

これでBootstrapを使える環境にはなりましたが、しっかり使えるには

  • rails-sandbox/app/assets/stylesheets/application.css
  • rails-sandbox/app/assets/javascripts/application.js

を書き換える必要があります。

まずは

rails-sandbox/app/assets/stylesheets/application.css

rails-sandbox/app/assets/stylesheets/application.scss

にリネームします(拡張子cssをscssに)。

中身を

> *= require_tree . *= require_self */

> * require_tree . * require_self */

にします(要するに、意味のないただのコメントにします→Railsでは「=」があると意味を持っているので)。

そして、以下を追加します(コメントの下)。

>// Bootstrap Sass @import "bootstrap-sprockets"; @import "bootstrap";

次に、

rails-sandbox/app/assets/javascripts/application.js

の内容に次を書き込みます。

>//= require bootstrap-sprockets

もう1つ。このままでBoostrapは使えますが、レイアウト的に変な感じになるので、まずはRailsの外側の

rails-sandbox/app/views/layouts/application.html.erb

のbodyのところにBootstrapのcontainerのDIVを加え、

>
1
2
3
4
5
  <body>
     <div class="container-fluid">
       <%= yield %>
     </div>
  </body>

そして、ブログ一覧の

rails-sandbox/app/views/blogs/index.html.erb

をBootstrapデザインに変更します。下の6行目あたりから13行目あたりです。

>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<p id="notice"><%= notice %></p>

<h1><%= t('.title') %>
</h1>
      <table class="table table-bordered table-striped">
    <thead>
	<tr>
	  <th class="col-md-1"><%= t('activerecord.attributes.blog.title') %></th>
	  <th class="col-md-7"><%= t('activerecord.attributes.blog.content') %></th>
	  <th class="col-md-1"><%= t('activerecord.attributes.blog.posted_date') %></th>
	  <th class="col-md-3" colspan="3"></th>
	</tr>
    </thead>
      <tbody>
       <% @blogs.each do |blog| %>
	<tr style='page-break-inside: avoid;'>
          <td><%= blog.title %></td>
          <td><%= blog.content.html_safe %></td>
          <td><%= blog.posted_date %></td>
          <td><%= link_to t('global.message.show'), blog %></td>
          <td><%= link_to t('global.message.edit'), edit_blog_path(blog) %></td>
          <td><%= link_to t('global.message.destroy'), blog, method: :delete, data: { confirm: t('global.message.confirm') } %></td>
	</tr>
	<% end %>
      </tbody>
    </table>
<br>

<%= link_to t('global.message.new') + t('global.message.blog'), new_blog_path %>
ちなみにERBというテンプレートは他のファイルを読み込めるので、ヘッダとかフッタとかが書いてある外側(layout)とその中のViewとかを分けるのがデフォルトになっています。
先ほどブログ内容にbrタグがあってこれがエスケープされちゃう件に触れましたが、これは18行目の.html_safeメソッドを実行することにより回避しています。

まあ、デザイン的にまだまだだけれど、とりあえずこれで手打ち。

bootstrap_index

PDF変換のGemを追加

ようやく、Railsに機能を追加するために、rails-sandbox/Gemfileに書き込みます。ここではhtmlをPDFにコンバートして、ブラウザ上に表示するためのライブラリです。

実際には、Gemでインストールするのはwkhtmltopdfというコマンドラインツールのラッパーで、単にRails上からそのコマンドラインツールを実行してPDFファイルを作るわけですが、そのコマンドラインツール自体が必要になります。

ただ、MacやLinuxだとその実行ファイルもgemでインストールできるのですが、そして、どうやらWindowsでも出来るようなことがネット上で散見されるのですが、私の環境の場合はうまくいきませんでした(何故かwindowsのgemでインストールしているのに、MacやLinuxじゃないと動かないと言われてしまうバイナリがインストールされてしまいました)。

したがいまして、Windows用のwkhtmltopdfをインストールして、これを使うことにします。

なので、https://wkhtmltopdf.org/downloads.htmlにいって、インストーラー(Windows (MSVC))をダウンロードして、これをインストールします。ただデフォルトのインストー先のc:/Program Filesとかスペースがあるディレクトリは何かとトラブることもあるので、C:/wkhtmltopdfにインストールします。

次にorails-sandbox/Gemfileに次の1行を書き込みます。

>
1
gem 'wicked_pdf'

そして、rails-sandoboxをカレントディレクトリにして、コマンドプロンプト上で

>bundle install

を実行します。

次に、rails-sandbox/config/initializers/wicked_pdf.rbというファイルを作成し、中に次の内容を書き込みます。

>
1
2
3
4
WickedPdf.config = {
  exe_path: 'C:/wkhtmltopdf/bin/wkhtmltopdf.exe',
  layout: 'pdf.html',
}

exe_pathは、もちろん、上記でインストールした先を書き込みます(ディレクトリ名にスペースがある場合はエスケープとかする必要があるかも)。

これで準備は出来ましたが、PDFをこのwkhtmltopdfで作成するには、html的にどのようなCSS的にデザインするかと言うことと、wkhtmltopfd的にPDFのサイズをA4とかA3とかどれくらいの大きさにするかを決めなくてはなりません。

wkhtmltopfd的にはA3で作成し、ただ、htmlのCSS的にはA4で作成すれば、A3の左上にA4があるような感じのPDFになるでしょうし、反対に、wkhtmltopfd的にはA4で作成し、CSS的にはA3で作成すると、実際のPDFは縮小されたりします。もちろん、通常はサイズや向き(portlait or landscape)一致させることになると思います。

それではCSS的にA3とかをどうするかというと、Tsutomu Kawamuraさんのhttps://github.com/cognitom/paper-cssを利用させて頂きます(ここでは詳細は省略します)。

nodeのモジュール管理ツールのnpmをコマンドプロンプト上で以下のように実行します。

>npm install paper-css

で、インストールされます。

そして、ruby-sandbox/config/application.rb

>config.assets.paths << config.root.join("node_modules")

を書き込みます。

pdf用のcssとlayout.erbを作成

htmlとは別のPDF専用のCSSとlayoutを使い、通常のものは使わず、別途作成してそれを使います。

ちなみに、通常は現在layoutはrails-sandbox/app/views/layouts/application.html.erb、そのlayout中からCSSをrails-sandbox/app/assets/stylesheets/application.scssに指定しています。

今回はrails-sandbox/app/views/layouts/pdf.html.erb

>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=Edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>RailsSandbox</title>
    <%= csrf_meta_tags %>
    <%= wicked_pdf_stylesheet_link_tag "application.pdf" -%>
  </head>

  <body class="A4 landscape sheet">
     <div class="container-fluid">
       <%= yield %>
     </div>
  </body>
</html>

を書き込みます。9行目にCSSの指定が書いてあり、それをこの後作成します。wicked_pdf_stylesheet_link_tagは通常のCSSの指定では、wkhtmltopdfが読み込めないので、wicked_pdfが用意してくれているヘルパーメソッドを利用しています。また12行目のbodyタグのクラスが先ほどのpaper.cssで定義されているものです。

そして、最後に rails-sandbox/app/assets/stylesheets/application.pdf.scss ファイルに

>
1
2
3
4
// Bootstrap Sass
@import "bootstrap-sprockets";
@import "bootstrap";
@import "paper-css/paper.css"

を書き込んで保存します。

pdf用(index)のviewとcontrollerのメソッドを作成(+routes.rb)

さて、先ほどbootstrapでデザインしたものをPDF化します。

PDFとそうでないものを比べるために、これまでのindexはそのままにして、別途index_pdfというメソッドを作成します。またそれに伴いデフォルトじゃないroutingを追加することになるのでruby-sandbox/config/routes.rbにそのroutingを追加します。

もともとのindexに拡張しpdfがあったら、PDFを返すようにする方法もありますが、routes.rbにも触れたいので、敢えて別の方法をとっています。

rails-sandbox/app/views/blogs/index.pdf.erbを以下の内容で作成します。ただ、index.html.erbと基本的に同じでも良いのですが、リンクとかあっても意味がないので、それらを削ることだけします(それに伴いテーブルの幅を若干調整)。そうそう、あと1つは、タイトルのi18nでメソッドがindexでないので(index_pdfだから)、t('.title')という書き方が出来ないので、それも変更しました(1行目)。

>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<h1><%= t('blogs.index.title') %>(PDF)</h1>

    <table class="table table-bordered table-striped">
    <thead>
	<tr>
	  <th class="col-md-2"><%= t('activerecord.attributes.blog.title') %></th>
	  <th class="col-md-9"><%= t('activerecord.attributes.blog.content') %></th>
	  <th class="col-md-1"><%= t('activerecord.attributes.blog.posted_date') %></th>
	</tr>
    </thead>
      <tbody>
       <% @blogs.each do |blog| %>
	<tr style='page-break-inside: avoid;'>
          <td><%= blog.title %></td>
          <td><%= blog.content.html_safe %></td>
          <td><%= blog.posted_date %></td>
	</tr>
	<% end %>
      </tbody>
    </table>
<br>

次にcontrollerにindex_pdfというメソッドを追加します。

>
1
2
3
4
    def index_pdf
      @blogs = Blog.all
      render pdf: "file_name" , layout: 'layouts/pdf.html.erb', template: 'blogs/index.pdf.erb', orientation: 'Landscape', page_size: 'A4', encoding: 'UTF-8'
    end

wicked_pdfをインストイールしているので、render pdfが使えるようになっています。上記のそのオプションのtemplateはすぐ上で作ったものを、layoutはpdf用のcssとlayout.erbを作成で作成したものを指定し(これは通常html等と同じで、デフォルトとは違うものを利用する場合はここで指定する)、残りはwkhtmltopdfに渡すものになります。

ただ、このままではindex_pdfを実行するURLが定義されていません。つまり、rails-sandbox/config/routes.rbは以下のものしか定義されていません。

>
1
  resources :blogs

です。これはたったの1行ですが、これだけで、blogsというレコードに対するcontrollerの中にすでに定義されているメソッドで簡単に紹介した全てのメソッドに対するroutes(URLとcontrollerのメソッドのマッピング)が定義されています。ただもちろん、index_pdfは含まれていません。

そこで次のようなものをrails-sandbox/config/routes.rbに書き込みます。

> post "blogs/index_pdf", as: "blogs_index_pdf", to: "blogs#index_pdf"

上の意味を左から追うと、POSTでURLのblogs/index_pdfにアクセスするものは、blogs_index_pdfという「名前付きヘルパー(viewの中等で利用できるもので、ただ最後に_pathを付ける必要がある)」として定義され、アクセスされるとblogs_controllerのindex_pdfメソッドが実行される、という感じの意味になります。

1
rails routes

というコマンドを叩くと、以下のようにroutingの状況が出力されます。

1
2
3
4
5
6
7
8
9
10
         Prefix Verb   URI Pattern                Controller#Action
          blogs GET    /blogs(.:format)           blogs#index
                POST   /blogs(.:format)           blogs#create
       new_blog GET    /blogs/new(.:format)       blogs#new
      edit_blog GET    /blogs/:id/edit(.:format)  blogs#edit
           blog GET    /blogs/:id(.:format)       blogs#show
                PATCH  /blogs/:id(.:format)       blogs#update
                PUT    /blogs/:id(.:format)       blogs#update
                DELETE /blogs/:id(.:format)       blogs#destroy
blogs_index_pdf POST   /blogs/index_pdf(.:format) blogs#index_pdf

一番下の行に出てます。

ここではGETじゃなく、POSTとして定義しましたが、これはGETだと/blogs/index_pdfというURLが上にある/blogs/:idつまり/blogs/3のようなものと勘違いされ、index_pdfというidのblogレコードはないなどと、Railsに怒られてしまいます。なので、とりあえずPOSTで定義がぶつからないようにしているわけです。

これにともない、ブラウザでURLを入力してアクセスしてもGETになってしまうので、viewの中にリンクを作成します。 rails-sandbox/app/views/blogs/index.html.erb

><%= button_to "PDFで閲覧", blogs_index_pdf_path, method: :post %>

上記を書き込みます。先に触れましたが、blogs_index_pdfという名前付きヘルパーを利用しています(_pathを付ける必要があります:再掲)。

これでブログ一覧の左上にあるボタンをクリックすると、PDFが見えるようになります。

blog_list_new.png

ふう、PDFが以下のように見えたら成功です。

blog_list_pdf.png

最後にHeroku

ユーザー登録

まずはユーザー登録が必要ですので、https://signup.heroku.com/にいって、必要事項を入力して登録すると(Pick your development language: ではRubyを選んでください)、メールが送られてきて、メール内のリンクをクリックして、登録を完了させます。その際、パスワードを登録する必要があります。もちろん、deploy時に利用するので、そのパスワードを覚えておきましょう。

Heroku CLIのインストール(herokuコマンド)

https://devcenter.heroku.com/articles/heroku-cliに行って、インストーラをダウンロードし、インストールします。この時、私はC:/Herokuにインストール先を変えただけで、後はデフォルトにしました。gitも自動でインストールされます。

ついでに、改行コードがLFだったりすると、gitがやらとwarningを出す場合があって、コマンドプロンプトで以下を実行すると、そのwarningが出なくなるかもしれません。

1
git config --global core.autoCRLF false

そして、コマンドプロンプト上でherokuとやって反応したらOKです(再起動しないとダメな場合もあるかもしれません)。最初はいきなりupdateが始まる場合もあります(私の場合)。

SqliteからPostgresSQLへ

windows内では、DBは気楽に使えるSqliteを使っていましたが(インストールする必要がないので)、Herokuでは使えません。なので、productionではPostgresSQLを、Windowsのdevelopment環境ではSqliteを利用するようにします。

rails-sandbox/Gemfile

にsqliteのところを以下のように書き換えます(sqlite3の行も書き換えてください)。

1
2
gem 'sqlite3', group: :development
gem 'pg', group: :production

です。それからdatabaseの設定があるので(rails-sandbox/config/database.yml)、これもproductionのところを以下のようにします。

1
2
3
4
5
production:
  <<: *default
  adapter: postgresql
  encoding: unicode
  pool: 5

HerokuへのPostgreSQLのインストールは少し後に行います

もう1つの問題がwkhtmltopdf

開発環境がWindowsだったので、wkhtmltopdfを別途インストールし、実行ファイルの場所を指定してました(wkhtmltopdfのインストール説明)。HerokuはWindowsサーバではないので、これをPostgreSQLの場合のように変更する必要があります。

まずはrails-sandbox/Gemfileから。下の2行目を加えます(productionの時のみインストール)。

1
2
gem 'wicked_pdf'
gem 'wkhtmltopdf-binary', group: :production

次に設定を、rails-sandbox/config/initializers/wicked_pdf.rbに書いてありましが、これを削除し

rails-sandbox/config/environments/production.rbrails-sandbox/config/environments/development.rbに分散させます。

まずはrails-sandbox/config/environments/development.rbには元々と同じ内容の以下を書き込みます。

1
2
3
4
WickedPdf.config = {
  exe_path: 'C:/wkhtmltopdf/bin/wkhtmltopdf.exe',
  layout: 'pdf.html',
}

そして、rails-sandbox/config/environments/production.rbにはexe_pathがないものを

1
2
3
WickedPdf.config = {
  layout: 'pdf.html',
}

を書き込みます。binaryをGemfileに書き込んでこれを使う場合は必要がないようです。

さらにもう1つ。日本語のフォントの問題があります。

http://d.hatena.ne.jp/deeeki/20120902/heroku_wkhtmltopdf_fontsに書いてあった通りのことをします。

http://ipafont.ipa.go.jp/old/ipafont/IPAfont00303.phpをクリックして、ダウンロードされるファイルを解凍し、その中のttfファイルをrails-sandbox/.fontsディレクトリを作成して、コピーします。

これでOKのはず。ただし、ドットフォルダを作成しようとすると、エクスプローラーではエラーになってしまいますが、コマンドプロンプト上で

1
mkdir .fonts

で作成すれば大丈夫です。

Herokuでassetsコンパイルで失敗するので

assetsコンパイルで失敗するので、Gemを1つインストールするのと、rails-sandbox/config/environments/production.rbを変更および追加を行います。

まずはrails-sandbox/Gemfileに次の1行を書き込み、いつものbundle installを行います。

1
gem 'rails_12factor', group: :production

次にrails-sandbox/config/environments/production.rbに1行目は変更し、2行目は存在しないので(たぶん)追加します。

1
2
  config.assets.compile = true
  config.public_file_server.enabled  = true

これでwindowsで動けば、いよいよHerokuにdeployです

おっと、

1
bundle install

は忘れないでね。

gitに登録

Herokuにはgitでpushすることにより、deployすることになるので、まずは現在のrails-sandboxをgitリポジトリにして、commitします。

1
2
3
4
5
git config --global user.email sakai@co-machi.org
git config --global user.name sakai
git init
git add .
git commit -m 'first commit'

名前とかが登録されていないと警告されますが、ここは無視しましょう。

Herokuにアプリケーションを登録

heroku createコマンドを使ってHerokuにアプリケーションを作成します。

現在のrails-sandboxのルートディレクトリをカレントにして、コマンドプロンプト上でheroku createを実行します。

1
2
3
heroku create
Creating app... done, stark-lake-43074
https://stark-lake-43074.herokuapp.com/ | https://git.heroku.com/stark-lake-43074.git

heroku create application_nameというようにHeroku全体でユニークになるようなアプリ名を指定することも可能ですが(指定しない場合は名前が自動で付けられます)、今回は何故か.git/configにリモートリポジトリ情報が書き込まれなくて、うまくいかなかったので(私が何かとちったのかもしれませんけれど)、指定しない方法で行いました。

入力時にHerokuのメールアドレスとパスワードを求められるので入力してください。

HerokuにPostrgreSQLをインストール

下のURLでHerokuにログインして

https://dashboard.heroku.com/apps

今作成したばかりのアプリを選択して、上部のResourcesをクリック、find more addonsをクリックして、下の方にあるHeroku Postgresを選択して、右上にある「login to install」をクリックして、PostgreSQLをインストールします。

終わったら「installボタン」をクリックすると、どのアプリにインストールするか、とか聞かれますが、今登録したばかりのアプリを選んでください。それからHobby Planで良いか、とか聞かれますが、そのplanはたぶんFreeとか書いてあったので、私はそれを選びました。

そして、次のコマンドを実行します。

1
2
3
git push heroku master
heroku run rails db:migrate
heroku run rails db:seed
どうやら人によって現象が違うのですが、git pushした際、ユーザ名とパスワードを聞かれますが、これがコンソール上の場合、最初にHerokuにユーザ登録したメールアドレスとパスワードでOKなのですが、下の図のようなポップアップが出た場合は「heroku auth:token」というコマンドを打って、出てきたものをパスワードとして入れないとダメみたいです。

credential

http://web.plus-idea.net/2017/04/heroku-app-pushauth-error/を上記のことは参考にしました。

ようやく確認!

先ほどアプリを登録した際表示されたURL(私の場合「https://stark-lake-43074.herokuapp.com/」なので「https://stark-lake-43074.herokuapp.com/blogs」)をブラウザで開き、ブログ一覧が見え、PDFで閲覧をクリックするとPDFが閲覧できれば、やった〜、完成です!

以上です。

シェア
#内容発言者

非同期はなかなか手強い(特にループが絡むと!)

Rails5.1の基本(Scaffoldなし)+Vuejs+typescript