😎
【Rails】分割代入っぽいことをしてクラス初期化を見やすくする
オブジェクトがよしなに動いてくれて記述量が減るよりも、記述量が多少増えても開発者の目に優しい方がいいよね派の藤谷です。
外部クラスの初期化に使う値を渡す際の工夫について書きます
元のコードと課題感
- CQRSアーキテクチャを踏襲し、Commandにdb更新の責務を持たせる
- ActiveModel::Attrubitesを使用してクラス内のプロパティを初期化
- クラス実行時の引数にはitem_create_paramsを渡す
class Api::V1::ItemsController < BaseController
before_action :authenticate_request
def create
item = Items::ItemCreateCommand.new(item_create_params).run
render json: item
end
private
def item_create_params
item_params = params.require(:item).permit(:name, :quantity)
item_params.merge(category_id: params[:category_id], user: current_user)
end
end
class Items::ItemCreateCommand
include CommandModule
include ActiveModel::Model
include ActiveModel::Attributes
attribute :name, :string
attribute :quantity, :integer
attribute :category_id, :string
attribute :user
validates :name, presence: true
validates :quantity, presence: true
validates :category_id, presence: true
def run
user.items.create!(
name: name,
qantity quantity,
category_id: category_id
)
end
end
上記の実装でもpost処理は成功しますが、下記の課題を感じました。
- controllerでの外部クラス実行時に
ActionController::Parameters
が渡されており、一目で引数とプロパティの関係が分からない - 外部クラスにcurrent_user(sorcery提供のオブジェクト)を渡す際にstrogn parameterに直接mergeするやり方になっていて複雑
これらの課題を解決しつつ挙動を変えないために、分割代入
での実装を解説します。
※typescriptでにた実装をそう呼ぶだけで、rubyでその呼ばれ方されてないっぽいです
values_at
配列やオブジェクトから値を順番に取り出すメソッドです。
これを使用して分割代入を再現します。
修正後のコード
- ItemCreateCommandは変更なし
- createメソッド内でvalues_atを使用して,パラメータ内容をメソッド内がスコープのプロパティに分割代入
- Commandクラスへの引数を明示的に書くことで初期化プロパティと引数が一対一で対応付いている。見やすい。
- current_userをstrong parameterにmergeせず引数で渡すことでitem_create_paramsの内容がシンプルになる
class Api::V1::ItemsController < BaseController
before_action :authenticate_request
def create
name, quantity, category_id = item_create_params.values_at(:name, :quantity, :category_id)
item = Items::ItemCreateCommand.new(
name: name,
quantity: quantity,
category_id: category_id,
user: current_user
).run
render json: item
end
private
def item_create_params
params.require(:item).permit(:name, :quantity)
end
end
class Items::ItemCreateCommand
include CommandModule
include ActiveModel::Model
include ActiveModel::Attributes
attribute :name, :string
attribute :quantity, :integer
attribute :category_id, :string
attribute :user
validates :name, presence: true
validates :quantity, presence: true
validates :category_id, presence: true
def run
user.items.create!(
name: name,
qantity quantity,
category_id: category_id
)
end
end
記述量が増えましたが、コードとしては見やすくわかりやすくなった感じが個人的にはあります。
値の渡し方とかはプロジェクトの規約で決まってくる所ですが、こんなのどうでしょう?って提案になれば嬉しいです。
Discussion