あなたのコードはどこへ?RailsアプリでCRUD以外のロジックを置く場所の決め方
Railsチュートリアルを終えて、いざ自分のアプリを作り始めると、混乱が始まります。「CRUD以外の一般的なロジックはどこに書けばいいの?」「Twitterからフォロワーを取得する処理はMVCのどこに収まるの?」——そんな疑問です。2人に聞けば4つの答えが返ってくる上、フォーラムのスレッドは気の利いた人たちが何時間も罵り合う場と化すこともあります。いずれにせよ、頭痛が残るだけです。
夢にまで見たアプリを作るには、何らかの汎用的なRails外のロジックがどうしても必要になります。では、コードをどこに置けば、シンプルさを保ったまま済むのでしょうか?
まず手軽に試せる場所
既存のActiveRecordモデルに関連していそうなロジックなら、まずそのモデルの中に入れてしまいます。たとえばGameモデルがあり、CSVファイルから大量のゲームデータを取り込みたいとしましょう。そんなときは、そのメソッドをGameクラスに直接実装します。
class Game < ActiveRecord::Base
def self.parse_from_csv(csv_string)
games = []
CSV.parse(csv_string, quote_char: "'") do |row|
games << Game.from_csv_row(row) if (row[0] == 'G')
end
games
end
def self.from_csv_row(row)
Game.new({
dgs_game_id: row[1],
opponent_name: row[2],
created_at: row[4],
updated_at: row[4],
})
end
endメソッドに必要な情報はすべて手元に揃っていますし、テストも簡単です。新しいコントリビューターがこのロジックを探すとき、真っ先に見るのもおそらくここでしょう。
しかし、このモデルに追加と変更を重ねていくと、クラスは肥大化して複雑になっていきます。モデル内のさまざまな部分が予想外の形で干渉し合うようになるのです。変更を重ねるほど、変更しづらいコードになっていく。
そうなったら、そのコードをActiveRecordに依存しないモデルへ切り出すリファクタリングを検討するタイミングです。
ActiveRecordに依存しないモデル
Railsアプリの中にあるからといって、すべてのクラスがActiveRecordやAction系のライブラリを継承する必要はありません。
プレーンなRubyオブジェクトとして独自のコードを書き、それをRailsアプリで使うことができます。こうしたオブジェクトでも「モデル」と呼べます。問題領域の一部をモデリングしているからです。違うのは、データを保存するActiveRecordのデータベースを持たないという点だけです。
実際、私が次にあのゲームCSVパーサーを触ったときには、Gameクラスが少し大きくなりすぎていました。そこでパーサーのロジックを独立したGameCSVParserクラスへ移動したのです。
コミット全体は省略しますが、ActiveRecordに依存しないクラスは次のような形になります。
class GameCSVParser
def initialize(csv_string)
@rows = CSV.parse(csv_string, quote_char: "'")
end
def games
game_rows.map { |row| Game.new(game_attributes(row)) }
end
private
def game_rows
@rows.select { |row| is_game_row?(row) }
end
def game_attributes(row)
{
dgs_game_id: row[1],
opponent_name: row[2],
created_at: row[4],
updated_at: row[4],
}
end
def is_game_row?(row)
row[0] == 'G'
end
end追加しようとしているロジックが特定のActiveRecordモデルと結びつかないと感じたら、私は迷わず新しいプレーンなRubyオブジェクトを作ります。あるいは、アプリにはまだ存在しない「何か」の一部であるべきコードだと思えたときも同様です。それ以外の場合は、リファクタリングを通じて自然に生まれることがほとんどです。
プレーンなRubyオブジェクトなら、何でも書けます。しかし「何でも書ける」と分かっても、方向性は見えてきません。どんなメソッドが必要? 新しいオブジェクト同士はどう連携する?
実は多くのRailsアプリで使われるプレーンなRubyオブジェクトには、共通のカテゴリーがあります。これらのカテゴリーはパターンであり、それに従って書けば他の開発者にも伝わるコードになります。いくつかは聞いたことがあるかもしれません。
サービスオブジェクト、プレゼンター、ジョブ
サービスオブジェクト、プレゼンター、ジョブには特別な魔法はありません。決まった、誰もが認識できる振る舞いをする、ただのプレーンなRubyオブジェクトです。
たとえばResqueのジョブは、performメソッドと@queueを持つプレーンなRubyクラスです。
class FetchGamesForPlayer
@queue = :default
def self.perform(player_id)
player = Player.scoped_by_id(player_id).ready_for_fetching.first
player && player.fetch_new_games!
end
endジョブが実行されるときに呼ばれるのがperformです。
プレゼンターは、ビューの中でしか意味をなさないコードを持つプレーンなRubyオブジェクトです。
class UserPresenter
def show_related_users?
@user.related.count > 3
end
endRailsのビューヘルパーをincludeしたり、複数のオブジェクトをまとめて1つの統一されたオブジェクトとしてビューに渡したりすることもあります。
サービスオブジェクトは、実行したいプロセス(処理の流れ)を表すプレーンなRubyオブジェクトです。たとえば「投稿にコメントを書く」処理は、次のような複数のステップを含みます。
- コメントを保存する。
- 投稿の作者に通知メールを送る。
サービスオブジェクトなら、この両方を担い、そのロジックをコントローラーの外に出しておけます。
サービスオブジェクトについては、実例豊富な優れた解説記事も多数公開されています。
単純な処理なら、わざわざサービスオブジェクトは作りません。しかしコントローラーが肥大化し始めたら、余分なロジックを置く絶好の場所になります。
こうしたパターンを使えば、自分のビジネスロジックを整理できます。どれもただのRubyオブジェクトですが、共通の趣を持ち、名前が付いていて、他の開発者と言葉を交わせるオブジェクトなのです。
では、どこから始める?
Rails外のビジネスロジックの置き場所はたくさんあって、選ぶのが難しいこともあります。そこで、私が実際に使っている判断基準を紹介します。
- ロジックが既存のクラス(ActiveRecordモデルであっても)と主に関係しているなら、そのクラスに入れる。
- 既存のどのクラスにも当てはまらないなら、ロジックを入れるための新しいプレーンなRubyクラスを作る。
- まだ存在しない「何か」の一部であるべきだと感じられるなら、そのための新しいプレーンなRubyクラスを作る。
- 後日コードを見返したとき、モデルが複雑になりすぎている、またはそのモデルの中でコードの意味が通らなくなっていたら、独立したプレーンなRubyクラスへリファクタリングする。
- ビューの中でしか意味をなさないコードなら、ヘルパーに追加するか、プレゼンターを作る。
- HTTPリクエスト中に実行する必要がないコード、バックグラウンドで動かすべきコードは、ジョブに入れる。
- 複数のモデルや処理の段階を扱っていて、コントローラーが理解しづらくなっているなら、サービスオブジェクトに切り出す。
あなたはどうしていますか? あなたのコードはどこに行きますか? ここで挙げた以外に役立っているパターンはありますか? ぜひコメントで教えてください。
まだ自分なりの基準がないなら、私のやり方を試してみてください。自分に合うかどうか確かめてみましょう。完璧なコードの書き方など存在しませんが、行き詰まったとき、こうした基準があると一歩を踏み出しやすくなります。
-
【保存版】「凝集度」と「結合度」でRubyコードはここまで変わる!基本のOOP原則を実例で解説
オブジェクト指向設計において最も重要な原則のひとつが、凝集度(Cohesion)と結合度(Coupling)です。この2つの概念を理解すると、コードベース内のすべてのクラスを特定のレンズを通して評価できるようになり、クラス設計の堅牢さや改善すべき箇所が明確に見えてきます。 凝集度と結合度とは何か? 凝集度とは、クラス内にあるメソッド同士の関係性を表す概念です。各メソッドが同じインスタンス変数や引数を使い、共通の目的に向かって協力し合っているか?それとも、メソッドごとに役割がバラバラで、互いに無関係に感じられるでしょうか? 結合度とは、あるクラスが他のクラスにどれほど依存しているか、システムの他
-
Ruby MRIのソースコードを読んで内部構造を学ぶ方法
Rubyを使い込んでいると、その裏側でどのように動作しているのか気になったことはありませんか? Rubyの内部に深く踏み込む方法の一つが、それを実現しているソースコードを読むことです。C言語がわからなくても、興味深い発見はたくさんあります。 ソースコードはRubyのGitHubリポジトリで公開されています。 理想を言えば、クラス名やメソッド名を簡単に検索できるCodequeryのようなツールを使うのがおすすめです。 コアクラスを探検する 調査のほとんどはルートフォルダで行うことになります。ここには、object.cにあるObjectや、array.cにあるArrayなど、すべてのコアクラスのソ