Rails アンチパターン - 継続的な大災害(Continual Catastrophe)
引き続きRails AntiPatternsという本を読んでいます。
https://www.amazon.co.jp/dp/B004C04QE0/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1
前回は6 Using Third-Party Codeの1つめ「Recutting the Gem」についてまとめました。
今回は10 Building for Failureの1つめ「Continual Catastrophe」についてまとめていきます。
Continual Catastrophe
サンプルとして以下のようなスクリプトを挙げる。
cd /data/tmp/ rm-rf*
/data/tmp/
がない場合に、ディレクトリ移動せず2行目が実行されたらどうなるか。ディレクトリが存在しない場合そこで終了するのが望ましい。(bashには-eや&&演算子がある)
Solution: Fail Fast
以下のようなコードがあるとする。
class Portfolio < ApplicationRecord def self.close_all! all.each do |portfolio| unless portfolio.photos.empty? raise "Can't close a portfolio with photos." end portfolio.close! end end end
これは途中でエラーになった場合に中途半端にcloseされてしまうので、初めにチェックするようにする。
class Portfolio < ApplicationRecord def self.close_all! all.each do |portfolio| unless portfolio.photos.empty? raise "Can't close a portfolio with photos." end end all.each do |portfolio| portfolio.close! end end end
更に早くチェックする方法として、UIからclose_allできないようにする,コントローラでリクエストを弾くなどがあげられる。
Fail Fastのメリットは
- 大災害を防げる
- はじめにチェックが宣言的に書かれているのでわかりやすい
- よくわからんところでぬるぽがでるのはうんざりだ!
たとえばARのfind_byとかも、レコードあり前提であればfind_by!で適切なエラーで失敗させるのがわかりやすいかなと思います。(ただし、例外を制御フローに組み込むのは絶対NG)createとかも同様ですね。(controllerのcreate, update内は別)
まとめ
その通りだなと思っていました。(それ以上の感想がない…。)
Rails アンチパターン - gemを使わない(Recutting the Gem)
引き続きRails AntiPatternsという本を読んでいます。
https://www.amazon.co.jp/dp/B004C04QE0/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1
前回は5 Servicesの1つめ「Fire and Forget」についてまとめました。
今回は6 Using Third-Party Codeの1つめ「Recutting the Gem」についてまとめていきます。
Recutting the Gem
いくつかのRailsアプリケーションに関わっていると、共通のパターンまたは機能を実装していることがあります。 その機能を必要としている人は世界にたくさんいるはずです。
「だから、gem作りましょう」という話ではないんですよね笑 Solution見ます。
Solution: Look for a Gem First
- コードを書く前にgemを探しましょうという話
- 同じような実装を何回もやっていたとしたら、それはgemが使えるかもしれないというサイン
自分も新しいアプリや新しい機能をRailsで実装するときは、めちゃくちゃググりまくってgemや実装、アーキテクチャ等参考にできないか調べているような感じがします。 探し方にもコツが有るのですが、それは次の章で書いてくれているらしいのでそのときに触れようと思います。
まとめ
「ぼくのかんがえたさいきょうのRails開発」みたいなやつどっかでまとめたくなった!
Rails アンチパターン - ファイア・アンド・フォーゲット(Fire and Forget)
引き続きRails AntiPatternsという本を読んでいます。
https://www.amazon.co.jp/dp/B004C04QE0/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1
前回は4 Controllersの2つめ「Fat Controller」についてまとめました。 waterlow2013.hatenablog.com
今回は5 Servicesの1つめ「Fire and Forget」についてまとめていきます。 ここで言うServicesとは外部サービスのことで、サービスクラスやアプリケーションサービスのことではありません。
Fire and Forget
外部サービスを利用するとき、レスポンスの扱いとしては以下のようなものがある。
- レスポンスをチェックし、エラーごとに適切な処理を行う。
- 成功、エラーのみチェックする。
- 何もチェックしない。
今回のアンチパターンは3つ目のケース。コントローラで直接呼んでいるとなるとユーザに500エラーを返すことになってしまう。
Solution: Know What Exceptions to Look Out For
発生するエラーを列挙しておき、適切にハンドリングする。たとえばHTTPクライアントなら以下のようなエラーがある。
HTTP_ERRORS = [Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError]
rescue => e
しないのは、Timeout::Error
がRuntimeError
を継承していないためらしいが、今ならRuntimeError
を継承しているので、雑にやりたければrescue => e
でもいいと思う。
Message in a Bottle
config.action_mailer.raise_delivery_errors
の設定に気をつける- エラーには大きく分けて2種類ある。クライアント側とサーバ側
- クライアント側のエラー(メールアドレスがおかしい等)はユーザに即通知する
- サーバ側のエラーはスタッフに通知する等して、何か対応を入れる。
You Don’t Know What You Don’t Know
- rescueしすぎない
- 少なめのrescueから初めて必要なものを追加する
- エラーログサービス(NewRelic, errbit等)も重要
- エラーハンドリングの詳しくはchapter10を
まとめ
特に違和感を感じたところはなかったです!外部サービスを叩くものは常にエラーが起こりうるという扱いをするのが自然なんだなと思いました。
あとは早めにchapter10を読みたい。
Rails アンチパターン - ファットコントローラー(Fat Controller)
引き続きRails AntiPatternsという本を読んでいます。
https://www.amazon.co.jp/dp/B004C04QE0/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1
前回は2 Modelsの1つめ「Voyeuristic Models」についてまとめました。
今回は4 Controllersの2つめ「Fat Controller」についてまとめていきます。 Solutionが2つあるのですが、今回は1つ目について書きます。
Fat Controller
コントローラーからビジネスロジックを削除し、それをモデルに適切に配置します。
コールバック、セッター、データベースのデフォルトなど、Active Recordによって提供される機能は、このタスクの重要なツールセットです。
Solution: Use Active Record Callbacks and Setters
サンプルコード
class ArticlesController < ApplicationController def create @article = Article.new(params[:article]) @article.reporter_id = current_user.id begin Article.transaction do @version = @article.create_version!(params[:version], current_user) end rescue ActiveRecord::RecordNotSaved, ActiveRecord::RecordInvalid render :action => :new and return false end end redirect_to article_path(@article) end end
コントローラで手続き型っぽく書いてしまっているというざっくりとした理由の他に、以下のような具体的理由があります。
- transactionをコントローラで呼んでいる
- saveが使われていない
- transactionを貼らないといけない?
- 例外を制御フローに使っている
例外的
状況ではない- GOTO文と可読性が変わらない
目標
class ArticlesController < ApplicationController def create @article = Article.new(article_params) if @article.save redirect_to article_path(@article) else render :action => :new end end end
ActiveRecordのsaveメソッドの戻り値で判断します。scaffoldで作られたものと同じですね。
create_version!メソッドを消す必要があるので、見ていきましょう。
def create_version!(attributes, user) if self.versions.empty? return create_first_version!(attributes, user) end # mark old related links as not current if self.current_version.relateds.any? self.current_version.relateds.each do |rel| rel.update_attribute(:current, false) end end version = self.versions.build(attributes) version.article_id = self.id version.written_at = Time.now version.writer_id = user.id version.version = self.current_verison.version + 1 self.save! self.update_attribute(:current_version_id, version.id) version end def create_first_version!(attributes, user) version = self.versions.build(attributes) version.written_at = Time.now version.writer_id = user.id version.state ||= "Raw" version.version = 1 self.save! self.update_attribute(:current_version_id, version.id) version end
必要に応じて他のメソッドversionモデルも見ていきます。
消せるものを消す。
version.written_at = Time.now
はcreated_atとupdated_atで代用できる。version.state ||= "Raw"
はDBのデフォルト値を使う(必要ならgemをつかってもいい)version.article_id = self.id
はself.versions.build
で勝手に入る。
def create_version!(attributes, user) if self.versions.empty? return create_first_version!(attributes, user) end # mark old related links as not current if self.current_version.relateds.any? self.current_version.relateds.each do |rel| rel.update_attribute(:current, false) end end version = self.versions.build(attributes) version.writer_id = user.id version.version = self.current_verison.version + 1 self.save! self.update_attribute(:current_version_id, version.id) version end def create_first_version!(attributes, user) version = self.versions.build(attributes) version.writer_id = user.id version.version = 1 self.save! self.update_attribute(:current_version_id, version.id) version end
コールバックをつかう
コールバックにするのは以下の3つ。
version.version
の設定current_version.relateds
のupdateself.update_attribute(:current_version_id, version.id)
version.version
の設定
class Version < ApplicationRecord before_validation :set_version_number, on: :create validates :version, presence: true private def set_version_number self.version = (article.current_version ? article.current_version.version : 0) + 1 end end
self.versions.empty?
はself.current_version
の存在確認でOK。create_first_version!
メソッドが必要なくなるので。以下のようになる。
def create_version!(attributes, user) # mark old related links as not current if self.current_version && self.current_version.relateds.any? self.current_version.relateds.each do |rel| rel.update_attribute(:current, false) end end version = self.versions.build(attributes) version.writer_id = user.id self.save! self.update_attribute(:current_version_id, version.id) version end
* current_version.relateds
のupdate
class Version < ApplicationRecord before_validation :set_version_number, on: :create before_create :mark_related_links_not_current, if: :current_version validates :version, presence: true private def current_version article.current_version end def set_version_number self.version = (current_version ? current_version.version : 0) + 1 end def mark_related_links_not_current current_version.relateds.each do |rel| rel.update_attribute(:current, false) end end end
途中細かなリファクタリングをはさんでいますが、最後の結果だけ書いています。
def create_version!(attributes, user) version = self.versions.build(attributes) version.writer_id = user.id self.save! self.update_attribute(:current_version_id, version.id) version end
だいぶスッキリしてきました!
self.update_attribute(:current_version_id, version.id)
class Version < ApplicationRecord before_validation :set_version_number, on: :create before_create :mark_related_links_not_current, if: :current_version after_create :set_current_version_on_article validates :version, presence: true private def set_current_version_on_article article.update_attribute :current_version_id, self.id end def current_version article.current_version end def set_version_number self.version = (current_version ? current_version.version : 0) + 1 end def mark_related_links_not_current current_version.relateds.each do |rel| rel.update_attribute(:current, false) end end end
メインのデータ更新以外は全てコールバックに、という流れですね。
create_version!
消すまではもう一息という感じです。
def create_version!(attributes, user) version = self.versions.build(attributes) version.writer_id = user.id self.save! version end
create_version!
を消す
ここでcreate_version!
を消します。
create_version!
のversion
に関するコードはコントローラに移します。
class ArticlesController < ApplicationController def create @article = Article.new(params[:article]) @article.reporter_id = current_user.id @version = self.versions.build(attributes) @version.writer_id = current_user.id if @article.save redirect_to article_path(@article) else render action: :new end end end
これで目標とする形にかなり近づきました。
その先
この書籍ではnested_attributesを使う方法が書かれていました。最近のRailsバージョンだとaccept_nseted_attributes_for
を使うのかと思いますが、実はこの機能、頃合いを見て消される方針のようです。
https://github.com/rails/rails/pull/26976#discussion_r87855694
代替としては、自分が知っているのはFormオブジェクト使うやり方があるのですが、だいぶ本に載っている内容からはそれるので今回はパスします。 Formオブジェクトの作り方はいろいろあると思いますが、パーフェクトRailsあたりに載っていた気がしますし、有料ですがgorailsにも。
まとめ
fat controller解消は何かパターンがあるわけではなく、リファクタリングとコールバックの積み重ね、最終的にどうしようもない所だけFormオブジェクトにするという泥臭い感じになるのだなと思いました。ただこうゆうことをサクッとできないとfat controllerと付き合うしか無いと思うので、この辺できるようにしておきたいです。
scaffoldに近づけるのが理想的という話はめちゃくちゃ同意できました!
[2017-05-07 20:50追記] 2つ目の解決策であるMove to a Presenterはactive_presenterというgemを使うのですが、今はメンテされていない様子です。内容としては上で言うFormオブジェクトっぽいものを宣言的にくためのgemのようでした。こちらに関しては深入りしないことにします。
Rails アンチパターン - 覗き見モデルクラス(Voyeuristic Models)
はじめに
引き続きRails AntiPatternsという本を読んでいます。
https://www.amazon.co.jp/dp/B004C04QE0/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1
前回は2 Domain Modelingの1つめ「Authorization Astronaut」についてまとめました。
今回は1 Modelsの1つめ「Voyeuristic Models」についてまとめていきます。
Voyeuristic Models
Ruby on Rails、MVC、およびオブジェクト指向プログラミングが提供する構造を認識していない場合などに、オブジェクト指向の基本的な原則を破ってしまうことがあります。またActiveRecordなどの便利な機能に乗りすぎてしまう場合もあります。
ここでは、MVCとオブジェクト指向プログラミングの教えに違反するいくつかのサンプルを見ていきます。
Solution: Follow the Law of Demeter(デルメルの法則に従う)
サンプルで見ていこうと思います。以下のようなモデルがあるとします。
class Address < ApplicationRecord belongs_to :customer end class Customer < ApplicationRecord has_one :address has_many :invoices end class Invoice < ApplicationRecord belongs_to :customer end
顧客は住所を1つ、請求を複数持ちます。請求の住所行を表示するビューは、次のようになります。
<%= @invoice.customer.name %> <%= @invoice.customer.address.street %> <%= @invoice.customer.address.city %>, <%= @invoice.customer.address.state %> <%= @invoice.customer.address.zip_code %>
適切なカプセル化を考えると、@invoiceはcustomerを経由してaddressにアクセスするべきではありません。たとえば、顧客が請求先住所と配送先住所の両方を持つようにアプリケーションを変更すると、これらのオブジェクトを通過して通りを取得するコード内のすべての場所が壊れ、変更する必要があるためです。
幸いにも、ActiveRecordには、delegateというメソッドがあります。このデリゲートメソッドを使用すると、addressオブジェクトのことを知ることなく顧客の住所を表示できます。
class Address < ApplicationRecord belongs_to :customer end class Customer < ApplicationRecord has_one :address has_many :invoices delegate :street, :city, :state, :zip_code, to: :address end class Invoice < ApplicationRecord belongs_to :customer delegate :name, :street, :city, :state, :zip_code, to: :customer, prefix: true end
viewのコードは以下のようになります。
<%= @invoice.customer_name %> <%= @invoice.customer_street %> <%= @invoice.customer_city %>, <%= @invoice.customer_state %> <%= @invoice.customer_zip_code %>
これやったほうがいいとは思うのですが、関連するオブジェクトをそのまま取って出すだけなら最初のままでもいい気がします。ただ悩ましいところではある…。これちょっと修正したいときに.gsub
とかviewに書いてしまう事があって、それを助長する気がしています。そしてDRYでない子0ドが生まれていくという。なので1回これに従ってみるのもいいかなと思えてきました。
Solution: Push All find() Calls into Finders on the Model(生のクエリメソッドは全てモデルに書く)
これもサンプルを見ます。
<html> <body> <ul> <% User.order(:last_name).each do |user| -%> <li><%= user.last_name %> <%= user.first_name %></li> <% end %> </ul> </body> </html>
これはビューに直接sql呼び出しが書いている状態であり、重複するロジックがそれぞれの場所で生まれてしまう可能性があります。
ひとまずコントローラに移します。
class UsersController < ApplicationController def index @users = User.order(:last_name) end end
<html> <body> <ul> <% @users.each do |user| -%> <li><%= user.last_name %> <%= user.first_name %></li> <% end %> </ul> </body> </html>
これでモデルのロジックはなくなりましたが、コントローラにロジックの記載が残っています。これをモデルに移します。
class User < ApplicationRecord scope :ordered, -> { order(:last_name) } end
class UsersController < ApplicationController def index @users = User.ordered end end
Solution: Keep Finders on Their Own Model (生のクエリメソッドは自身のクラスにしか書かない)
検索コールをRailsアプリケーションのコントローラレイヤーからモデル上のカスタムファインダに移動することは、メインテナンス可能なソフトウェアを作成する正しい方向への強力なステップです。しかし、一般的な間違いは、適切な責任の委任を無視して、それらの呼び出しを最も近いモデルに移動することです。
耳が痛い話だ!!!
class UsersController < ApplicationController def index @user = User.find(params[:id]) @memberships = @user.memberships.where(active: true). limit(5). order("last_active_on DESC") end end
今まで学んだことに基づいて、そのスコープチェーンをUserモデルのメソッドに徹底的に移動します。UsersControllerを扱っているので、これが最良の場所のようだと思います。
class UsersController < ApplicationController def index @user = User.find(params[:id]) @recent_active_memberships = @user.find_recent_active_memberships end end
class User < ApplicationRecord has_many :memberships def find_recent_active_memberships memberships.where(active: true). limit(5). order('last_active_on DESC') end end
これは間違いなく改善です。 しかし、もう少しすることができます。Userモデルはメンバーシップモデルの実装について、active、last_active_onというカラムのことを知ってしまっています。これは、まだメソッドを十分にプッシュしていないという手がかりです。
(途中経過を省き最終型のみ載せます)
class User < ApplicationRecord has_many :memberships def find_recent_active_memberships memberships.only_active.order_by_activity.limit(5) end end class Membership < ApplicationRecord belongs_to :user scope :only_active, where(active: true) scope :order_by_activity, order('last_active_on DESC') end
scopeにしたことによりロジックを組み合わせて使うことができます。
scopeにしたことが良いのと、limit(5)がUserモデルにあるのがすごくいい感じがしています。
このアプローチには、読みやすさとシンプルさの問題だけでなく、デメテルの法律の乱用などの短所もあります。
このアプローチを使用するかどうかは、あなた次第です。多くの高度なリファクタリングと同様に、これは簡単な答えがなく、好みにも大きくよるものです。
まとめ
正直ここまで徹底してやったことがなく、これを都度やっていくコストがどれくらいなのかわかりません。ただ、毎回コントローラに書いていってしまうと共通化、scope化するのがどんどん難しくなっていくので、ある程度の規模ではやるべきなのかなと思っています。モデル50個からとか?
とりあえず、次自分で何か書くときにはこれ徹底してみようと思います。
Rails アンチパターン - 使われない権限メソッド(Authorization Astronaut)
はじめに
最近Rails AntiPatternsという本を読んでいました。
https://www.amazon.co.jp/dp/B004C04QE0/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1
幾つかの項目を読んだ所なかなかおもしろそうだったので一冊通して読もうと思っています。
翻訳版はないので、ざっくり訳しつつ自分の感想を書いていこうと思います。
2010年に発売されたもので、サンプルコードやgem等は古いので適宜読み替える必要があります。
また、サンプルコードは一部変更して掲載しています。
Authorization Astronaut
権限ロジックは仕様に基づいて、または将来の要件を予期してプログラムされます。その結果、一般的なユーザー認証では、次のようなユーザー・モデルが作られます。
class User < ApplicationRecord has_many :user_roles has_many :roles, through: :user_roles def has_role?(role) roles.exists?(name: role) end def can_post? has_roles?(%w(admin editor associate_editor research_writer)) end def can_review_posts? has_roles?(%w(admin editor associate_editor)) end def can_edit_content? has_roles?(%w(admin editor associate_editor)) end def can_edit_post?(post) self == post.user || has_roles?(%w(admin editor associate_editor)) end end
can_*というメソッドは破滅への道です。いつ使われるかわからず、インタフェースも曖昧で一貫性がないです。今後も必要になる前から権限ロジックだけ先に追加されるようになります。 また、admin, editor等の権限名がハードコーディングされていて、変更に弱い状態です。
自分もこんな感じのをモデル見たことがあり、ああーなるほどという感じでした。モデルとコントローラを別の人で開発した場合にこんな感じになりがちな印象です。
ハードコーディングやめて定数にすればという人もいますが、本質的には変わらないと思います笑
ユーザにはロールをつけることができます。
class Role < ApplicationRecord has_many :user_roles has_many :users, through: :user_roles validates :name, uniqueness: true, presence: true def name=(value) write_attribute(:name, value.downcase) end def self.[](name) find_by(name: name.to_s) end def add_user(user) users << user end def delete_user(user) users.delete(user) end end
Roleモデルにはいくつかの問題があります。管理ユーザーがロールを追加または削除できるようにする計画はありません。 また、Role.[]というメソッドは識別子の変更を吸収するのに使われるかもしれませんが、その意図で書かれていないため、機能しません。どこでも使われない可能性もあります。
add_user、delete_userも良いインタフェースではありません。
あるあるだw
要するに、これら2つのモデルは独立して書かれました。アプリケーションの仕様が固まる前に、将来何が必要になるかを見越して書かれています。これは認証ロジック実装でかなり発生するようです。仕様を固めている間に、認証ロジックが開発者が事前に開始できるものとして認識されるためです。
これは誤った仮定で、過剰に設計されたコードにつながり、あいまいで矛盾したインターフェイスを提供し、正しく使用されないか、まったく使用されなくなってしまいます。
とはいえ、チームの文化とかいろんな要因で、やりたいことを理解する前(仕様が固まる前とは違う)にロジック部分だけ作ってしまい、こうゆうコードが生まれるのはたまに見かけます。
Solution: Simplify with Simple Flags
前のセクションで説明した複雑さの問題に対処するために、次のようにモデルをリファクタリングすることができます。
class User < ApplicationRecord end
class AddColumnToUsers < ActiveRecord::Migration[5.1] def change add_column :users, :admin, :boolean, null: false, default: false add_column :users, :editor, :boolean, null: false, default: false add_column :users, :writer, :boolean, null: false, default: false end end
https://github.com/waterlow/authorization_astronaut/pull/2
これによって
Role
モデルが削除でき、User
モデルにはadmin?
、editor?
などのメソッドが使えるようになります。フレームワークの機能をを効果的につかい、「コードが無い」という一番きれいな状態になります。今後1つ2つのロール追加くらいならばこの方法でも問題は無いでしょう。
なかなか衝撃的でした。3つあったテーブルを一つにしてしまって、しかもUser
モデルには何も書かないというシンプルさ…。仕事だとこう言う選択は非常に勇気が要りますが、シンプルさを保つ事ができる非常に良い方法だと感じました。
それ以上にロールが追加される場合は、テーブルを増やしましょう。ただし、多対多用の中間テーブルは作らず、
has_many
で完結させます。
class User < ApplicationRecord has_many :roles Role::TYPES.each do |role_type| define_method("#{role_type}?") do roles.exists?(name: role_type) end end end
class Role < ApplicationRecord TYPES = %w(admin editor writer guest) validates :name, inclusion: { in: TYPES } end
https://github.com/waterlow/authorization_astronaut/pull/3
これは、不必要なコードを排除し、過剰に設計されていないため、ユーザーの役割を扱う方法について疑問のない一貫したインターフェイスを提供するため、成功しています。
やっぱり前者のadmin?
メソッドにこだわっていて、Railsの機能が提供してくれるものが一番シンプルだという考え方ですかね。私もこの辺は同意です。自前で書くよりはよっぽどいい。あと、DBの外部キー制約が使えなさそうだけど、モデルでバリデーション入れてるからいいよねという気持ちだろうと思います。
ここで概説した概念は、ユーザーの役割にのみ適用されるものではありません。また、アプリケーションドメインをモデル化し、モデルによって提供されるインタフェースを定義するときに、他の多くの状況にも適用できます。以下の簡単なガイドラインは、オーバーエンジニアリングを止め、定義されていない仕様とアプリケーションの変更の両方に直面する簡単なインターフェイスを提供するのに役立ちます。
•コードを書くときにアプリケーションの要件を超えて構築しないでください。
•具体的な要件がない場合は、コードを記述しないでください。
•すぐにモデルを作ろうとしないでください。付加的なモデルの追加を避けるために、ブーリアンや非正規化などの単純な方法がよく使用されます。
•データの追加、削除、または管理のためのユーザーインターフェイスがない場合、モデルは必要ありません。 ハッシュまたは可能な値の配列で作成された非正規化列で問題ありません。
最後のはシリアライズのことをいっているのかなと思います。UIがない場合はモデル作らなくてもシリアライズで問題ないという考えは、以前ActiveRecord serialize / store の甘い誘惑を断ち切ろうという記事を読んで以来やらないほうがいいと思っていましたが、認可のようなアプリのメイン機能とは異なる場面では使ってもいいかなと思いました。
また、UIがない場合にモデルを追加しないという決断は驚きでした。
個人的な意見としては、空のモデルを作ってもいいかなとは思ったのですが、モデルを作ってしまう時点で将来の要求とはずれてしまうかもしれませんし、やはりモデルを作らないで済むのが一番拡張性の高い状態かなと思いました。
まとめ
仕事でこうゆうリファクタリングをぶっこんでいくのはなかなか難しそうですが、必要になるまで作らないという視点は常に持っておきたいです。
【LT】表参道.rbでActiveRecordのテーブル名の決め方等について発表してきました。
はじめに
ActiveJobのキュー名の設定をどうするかを考えていた過程で、ActiveRecordのテーブル名の決め方を調べていました。色々と面白いところがあったので表参道.rbで発表してきました。(ただのテーブル名あてゲーです。)
基本はシンプルに「親のテーブル名を見に行く」だけだった
こんな風にabstract_class
が複数入っていると難しく見えがちですが、AdminUser
が読まれたときにテーブル名がadmin_users
になり、そのサブクラスのtable_name
は、指定しない場合はすべてadmin_users
となります。
abstract_class
はレコードを作れないということだけで、テーブル名の決め方には関係ありませんでした。
まとめ
普段深く知らないまま使っている機能について、ソースを読んで理解を固めていくのは非常にいい感じでした。