Rails のベストプラクティスに従っているかチェックするために rails_best_practices を導入

Rails の使い方が間違っていないかどうかを、人が目視でチェックするのは、 無駄ではないけど時間がもったいない。

もしツールでチェックできるとしたら、ツールでやった方がいい。 設計とか、他にもっとレビューするべきところはあるのだから。

そこで rails_best_practices という gem の出番。

この gem は、Rails Best Practices というサイトで公開されているベストプラクティスに、 Rails アプリが従っているかをチェックしてくれる。

gem install rails_best_practices

でインストールしたら、Rails プロジェクトのルートディレクトリで

rails_best_practices -f html .

を実行。すると rails_best_practices_output.html が出力される。 こんな感じのやつ。

f:id:griefworker:20140716205020p:plain

あとは警告を1つずつ潰していけばいい。

Vim で ^M を一括削除

Rails でジェネレーターを実行したら、ファイルの末尾に余分な改行コード ^M が追加されてしまったので、Vim でまとめて削除する方法をメモしとく。

コマンドモードに切り替えて

:%s/^M//g

を実行すればいい。 なお、^MCtrl-V Ctrl-M を押して入力する。

牛亭

肉が食べたい気分だったので、 六本松駅から歩いて15分、大濠公園すぐ側にある『牛亭』に行ってみた。 この店は人気らしく、ランチは予約しないとすぐ満席になってしまうみたい。 そうとも知らず予約なしで行ったら、運よくカウンター席が開いていた。 危なかったな。

f:id:griefworker:20140705114645j:plain

A ランチ(ポーク炭火焼+ハンバーグ)と手造りハンバーグステーキがよく注文されているみたいで、 どちらにするか迷ったけど、今回はハンバーグを注文。

f:id:griefworker:20140705115727j:plain

ハンバーグは鉄板いっぱいに広がっていて、予想以上のボリューム。 ソースはデミグラスソース。やっぱりハンバーグはデミグラスでしょ。 ソースをよく絡めて食べると旨い。

f:id:griefworker:20140705114947j:plain

サラダはコールスローみたいだった。

f:id:griefworker:20140705115743j:plain

ライスとパンを選べたので、ライスを選択。 肉にはライスが一番合う。

あと、アフターのコーヒーがついて、1200 円(税抜)。 増税の影響か、値段が上がっていて少々お高い印象を受けた。 以前は 1050 円(税込)だったらしいので、200 円以上の値上がり。 1080 円だと良かったのに。 まぁ、ハンバーグはボリュームあったし、コーヒーも付いていたから、 そう考えると 1200 円(税抜)でも納得だな。

関連ランキング:ステーキ | 六本松駅唐人町駅大濠公園駅

Grape で API を複数ファイルに分けて定義する

Grape では 1 ファイルに API をずらっと書けるけど、 規模が大きくなってくると見通しが悪くなるので、複数ファイルに分割したくなる。

Grape では API が他の API をマウントできるので、 その機能を使ってリソースごとにファイルを分割できる。

以下、サンプル。ヘルパーも別ファイルに切り出してみた。

posts.rb
# coding: utf-8
module API
  class Posts < Grape::API
    resource :posts do
      desc "投稿をすべて取得"
      get do
        # posts はヘルパーメソッド
        posts
      end

      desc "投稿を1件取得"
      get ":id" do
        posts.find(params[:id])
      end
    end
  end
end
helpers.rb
# coding: utf-8
module API
  module APIHelpers
    def posts
      Post.order(:id)
    end
  end
end
api.rb
# coding: utf-8
require_relative "helpers"
require_relative "posts"

module API
  class API < Grape::API
    format :json
    default_format :json
    prefix "api"
    version "v1", using: :path

    helpers APIHelpers
    mount Posts
  end
end

gem をオフラインでインストールする方法

あらかじめ Web に繋がったマシンで .gem ファイルをダウンロードしておけば

gem install --local <gem ファイル名>

でインストールできる。 ただし、依存する gem がインストールされていないと失敗するから、それらをすべて手動でインストールする必要があるため苦行。

Rails アプリで使う gem をまとめてオフラインインストールしたい場合、あらかじめ

bundle package

でインストールしている gem を vendor/cache に出力。 そして、まだ bundle install していないプロジェクトに vendor/cache をコピーして

bundle install --path vendor/bundle --local

を実行できれば、まとめてインストールできる。 依存している gem をいちいち手動で入れる必要がないので、こちらは楽。

paranoia を使った論理削除でコールバックを実行しない

paranoia を使った論理削除では、destroy したとき deleted_at が設定されるだけでなく、コールバックも実行される。

そのため

class User < ActiveRecord::Base
  acts_as_paranoid

  has_many :items, dependent: :destroy
end

上記の User で destroy を実行すると items が削除される。

論理削除でコールバックが実行されるのが今回都合が悪かったので、paranoiaソースコードを見ながら、destroy を上書きして対処してみた。

class User < ActiveRecord::Base
  acts_as_paranoid

  has_many :items, dependent: :destroy

  def destroy
    # paranoia で定義されているメソッドを呼び出して、
    # deleted_at に時間をセット
    result = touch_paranoia_column(true)
    result ? self : false
  end
end

その場しのぎ感が強いので、他の方法を考えたほうがいいかも。dependent オプションを外すとか。とりあえずメモしておく。

Apartment を使った Rails アプリを Heroku にデプロイできない

マルチテナント用の gem である apartment を使っている Rails アプリを、 Heroku に push すると下記のエラーが発生した。

Preparing app for Rails asset pipeline
       Running: rake assets:precompile
       rake aborted!
       Gem::LoadError: Specified 'sqlite3' for database adapter, but the gem is not loaded. Add `gem 'sqlite3'` to your Gemfile (and ensure its version is at the minimum required by ActiveRecord).
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.0/lib/active_record/connection_adapters/connection_specification.rb:190:in `rescue in spec'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.0/lib/active_record/connection_adapters/connection_specification.rb:187:in `spec'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.0/lib/active_record/connection_handling.rb:50:in `establish_connection'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/apartment-0.24.3/lib/apartment/adapters/abstract_adapter.rb:82:in `block in process_excluded_models'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/apartment-0.24.3/lib/apartment/adapters/abstract_adapter.rb:81:in `each'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/apartment-0.24.3/lib/apartment/adapters/abstract_adapter.rb:81:in `process_excluded_models'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/apartment-0.24.3/lib/apartment/database.rb:18:in `init'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/apartment-0.24.3/lib/apartment/railtie.rb:31:in `block in <class:Railtie>'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/callbacks.rb:438:in `instance_exec'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/callbacks.rb:438:in `block in make_lambda'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/callbacks.rb:184:in `call'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/callbacks.rb:184:in `block in simple'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/callbacks.rb:185:in `call'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/callbacks.rb:185:in `block in simple'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/callbacks.rb:86:in `call'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/callbacks.rb:86:in `run_callbacks'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.0/lib/action_dispatch/middleware/reloader.rb:83:in `prepare!'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.0/lib/action_dispatch/middleware/reloader.rb:55:in `prepare!'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/railties-4.1.0/lib/rails/application/finisher.rb:52:in `block in <module:Finisher>'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/railties-4.1.0/lib/rails/initializable.rb:30:in `instance_exec'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/railties-4.1.0/lib/rails/initializable.rb:30:in `run'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/railties-4.1.0/lib/rails/initializable.rb:55:in `block in run_initializers'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/railties-4.1.0/lib/rails/initializable.rb:54:in `run_initializers'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/railties-4.1.0/lib/rails/application.rb:288:in `initialize!'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/config/environment.rb:5:in `<top (required)>'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `require'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `block in require'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/dependencies.rb:232:in `load_dependency'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `require'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/railties-4.1.0/lib/rails/application.rb:264:in `require_environment!'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/railties-4.1.0/lib/rails/application.rb:367:in `block in run_tasks_blocks'
       /tmp/build_4f21ff1d-af6a-46cb-ba2e-935e4be9cdd8/vendor/bundle/ruby/2.0.0/gems/sprockets-rails-2.1.3/lib/sprockets/rails/task.rb:55:in `block (2 levels) in define'
       Tasks: TOP => environment
       (See full trace by running task with --trace)
 !
 !     Precompiling assets failed.
 !

 !     Push rejected, failed to compile Ruby app

Rails 初期化時に apartment の初期化を行っており、 database.yml に書かれている production 環境のアダプタを読み込もうとしてエラーが発生していた。 SQLite のアダプタを読み込もうとしたのが原因みたい。

Heroku に push したとき、 Heroku Postgres を使うように database.yml が自動で書き換わるんだけど、 Rails 初期化のタイミングではまだ書き換わっていなかったようだ。

database.yml で

production:
  adapter: postgresql
  host: HerokuPostgresホスト名
  port: 5432
  username: ユーザー名
  password: パスワード
  database: データベース名

という風に、SQLite ではなく PostgreSQL のアダプタを使うように書いておけば回避できた。