Railsを使い始めてX年経ったこともあり、自分なりの開発パターンが形成されていることに気づきました。今日はそれを恥ずかしげもなく晒してみようかと思います。
目次
Gemfile
自分の中で必須のgemたちです。その他はプロジェクトに合わせ適宜追加する感じです。
📄Gemfile
source 'https://rubygems.org' ruby "2.0.0" gem 'rails', '~> 4.0' gem 'sqlite3' gem 'sass-rails' gem 'uglifier' gem 'coffee-rails' gem 'jquery-rails' gem 'turbolinks' gem 'jbuilder' gem 'draper' gem 'ransack' group :development do gem 'rack-mini-profiler' end group :development, :test do gem 'pry-rails' gem 'pry-doc' gem 'pry-byebug' gem 'rspec-rails' end group :test do gem "shoulda-matchers" gem 'factory_girl_rails' gem 'capybara' gem 'poltergeist' gem 'launchy' end group :doc do gem 'sdoc', require: false end
パフォーマンスは絶えず確認する
rack-mini-profiler
というgemを使用して、常にパフォーマンスを確認しながら開発してます。SQLの発行数も確認できるのですごく便利。詳しくは過去の記事で説明してます。
検索はransackで
単純な検索機能であれば、ransack
を導入することで大した手間なく実現できてしまいます。
📄blogs_controller.rb
class BlogsController < ApplicationController ... # GET /blogs # GET /blogs.json def index @q = Blog.search(params[:q]) @blogs = @q.result(distinct: true) end ...
📄blogs/index.html.erb
<h1>Listing blogs</h1> <%= search_form_for @q do |f| %> <%= f.text_field :title_cont, placeholder: "Title" %> <%= f.select :author_eq, Blog.pluck(:author), include_blank: true %> <%= f.text_field :description_cont, placeholder: "Description" %> <%= f.submit %> <% end %> ...
たったこれだけで、検索機能が完成しました。
ViewModel層を作る
draper
を導入すれば、簡単にViewModel層を追加することができます。オブジェクト指向的に実装できますし、ビューやヘルパーがごちゃごちゃしなくて済みます。詳細はRailsCastが分かりやすいと思います。
📄blogs_controller.rb
class BlogsController < ApplicationController ... def index @q = Blog.search(params[:q]) @blogs = @q.result(distinct: true).decorate # BlogDecoratorを返す end ...
📄blog_decorator.rb
class BlogDecorator < Draper::Decorator delegate_all def short_description h.truncate(model.description, length: 10) end end
📄index.html.erb
... <% @blogs.each do |blog| %> <tr> <td><%= blog.title %></td> <td><%= blog.author %></td> <td><%= blog.short_description %></td> <td><%= link_to 'Show', blog %></td> <td><%= link_to 'Edit', edit_blog_path(blog) %></td> <td><%= link_to 'Destroy', blog, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> ...
(サンプルがViewModel層として、うってつけではないですね。すいません・・・)
複数モデルを更新する場合はFacadeで
単純なマスタメンテナンス機能なんかは、scaffoldと同等のコードで構わないと思います。しかし、アプリがある程度の規模になってくると、一連のトランザクションで複数モデルを更新する場合が出てくると思います。そんなときは、facadeクラスを用意します。
📄blog_create_facade.rb
class BlogCreateFacade def initialize(blog) @blog = blog end def save return false unless @blog.valid? @blog.transaction do @blog.save! ping_to_notice @blog send_new_entry @blog # その他いろいろな処理 # ... # ... end true rescue => e raise e end end
Facadeを呼び出すコントローラ側は、すごくスッキリします。
📄blogs_controller.rb
... def create @blog = Blog.new(blog_params) facade = BlogCreateFacade.new(@blog) if facade.save redirect_to @blog, notice: 'Blog was successfully created.' else render action: 'new' end end ...
J2EEでいうところのServicesですかね。
テストは rspec + shoulda-matchers で
shoulda-matchers
を使用すれば、いくらかのspecが簡単に書けます。一番役に立つのはバリデーションのspecかな。
例えば以下のようなモデルに対して・・・
📄blog.rb
class Blog < ActiveRecord::Base validates :title, presence: true validates :description, length: { maximum: 200 } end
簡単にバリデーションのspecが書けちゃいます。
📄blog_spec.rb
require 'spec_helper' describe Blog do it { should validate_presence_of(:title) } it { should ensure_length_of(:description).is_at_most(200) } end
単純なバリデーションにまで、いちいちテストを書くのかどうかは意見が分かれると思います。しかし、shoulda-matchersを使えば1行でサクッと書けるので、私はテストするようにしてます。
ヘルパーのテストは書かない
特定オブジェクトに属する複雑な表示処理は、前述のdraper
にてViewModel層に定義し、テストを書いています。よって、ヘルパーには大したコードが残らない & どうせ目で見て確認しないとダメ なので、ヘルパーのspecは書いてません。
ビューのテストも書かない
昔書いてましたが、あまりメリットが無かったのでもう書いてません。私の場合、画面の開発って、あーでもない、こーでもないって考えながら実装していくパターンが多いので、どうしてもテスト・ファーストできません。テスト・アフターで「ブログ一覧と表示されているか?」ってテストするのもアレですし、そもそも体裁を確認するために結局目視するので、書くのをやめてしまいました。
Javascriptのテストも書かない・・・
フロントエンドをJavascriptでゴリゴリするようなナウいアプリなら、当然テストを書くべきでしょう。しかし私は、今のところその手のアプリを作っていません(恥)。また、jQueryでidやクラス指定してゴソゴソするような処理は、実画面のDOM構造と密接に関連して『しまう』ので、テストビューなどを用意した自動テストは上手くハマらないと思います。キモとなる重要なJavascript処理がある場合、poltergeist
でテストするようにしてます。
書くのは models, decorators, facades, controllers, requests sepc
これだけ書いてれば98点ぐらい取れると思います。request specではcapybara
とpoltergeist
で受け入れテストしてます。Cucumberは、フィーチャーファイルを一緒に見てくれるユーザーがいないのでw使ってません。
フィクスチャではなく、factory_girl
ちょっとした規模のアプリになると、フィクスチャは破綻します。最初からfactory_girlを使用するようにしています。
📄factories.rb
FactoryGirl.define do factory :blog do title "test blog" author "Yamada Taro" description "this is a test blog.\n2nd line." end factory :masuda_blog, class: Blog do title "匿名ダイアリー" author nil description "this is a masuda blog." end end
📄blog_spec.rb
require 'spec_helper' describe Blog do ... describe "masuda?" do subject { blog.masuda? } let(:blog) { FactoryGirl.create(:masuda_blog) } it { should be_true } end end
以上
関連する記事
- Railsアプリを『浅く』パフォーマンス・チューニングしてみる(その1)
- 【実践】Railsにて検索結果をそのままCSV出力する(やや手抜きで)
- simple_formとTwitter bootstrapで作る俺流鉄板Railsアプリ(その1)
- Rails+JSフレームワークでリアルタイム掲示板を作成してみる(AngularJS編)
- Rails+JSフレームワークでリアルタイム掲示板を作成してみる(Ember.js編)