ども、@kimihom です。
今回は Ruby on Rails における パラメーターの処理について。
よくある Rails の Scaffold では、送られてくるパラメーターとモデルが 1対1 で対応しているため、 Strong Parameter を使って綺麗に CRUD(Create, Read, Update, Delete) 処理ができている。ただ、実際は何かの登録フォームなどで関連するモデルを一括でフォームから登録する、という場合など多くあるかと思う。そんな時は Rails のレールから外れるから仕方なく汚いコード(特に検証のコード)を書いてしまった経験は一度や二度はあるだろう。しかし、実は Rails はそんな時でも綺麗に 書くことのできる方法を提供している。これは、データ保存を必要としないモデルを作成し、そこに ActiveModel
をインクルードするのだ。これにより、DB保存はしないけど、Rails の Model と同等の検証機能を利用できる。
ではサンプルを見ていこう。
ActiveModel な Model を作成する
今回のサンプルは、会員登録で一気に複数のモデルを一括作成するようなケースを考えてみる。このような時は User 単体の Create 処理だけじゃ物足りず、その他サービスを利用する上で重要なデータの初期化などを行うことだろう。そんな時のパラメーターと Model に渡すパラメータは複数であることが多い。
通常の Model といえば、ActiveRecord::Base
を継承したクラスを作成する。今回は各 Model は既に定義されているとして、パラメータ用のモデルActiveModel::Model
をインクルードした RegisterParam
を作成する。
今回は例えば契約情報(Contract)クラスがあり、その中に複数のユーザー(User)クラスがあるようなパターンを考えてみる。
class RegisterParam include ActiveModel::Model attr_accessor :name, :email, :email_confirm, :tel1, :tel2, :tel3, :password, :password_confirm validates :name, presence: true, length: {maximum: 255} validates :email, presence: true, length: {maximum: 255}, confirmation: true validates :password, presence: true, length: {in: 8..50}, confirmation: true validates :tel1, presence: true, numericality: true, format: { with: /\A0\d+\Z/i } validates :tel2, presence: true, numericality: true validates :tel3, presence: true, numericality: true def initialize(attributes={}) super end def contract_param { name: self.name, email: self.email, tel_number: [self.tel1, self.tel2, self.tel3].join("-") } end def user_param { email: self.email, password: self.password, password_confirmation: password_confirmation } end end
このようにすることで嬉しい点が大きく2つある。
1つ目は Rails の バリデーション機能を複数のモデルが関連した時でも利用できる、ということ。実際にコントローラ側で検証するには、validate
メソッドが利用できる。通常のバリデーションと同様に、エラーが発生した場合はメッセージを自動で生成してくれる。
2つ目は View 側で form_tag
ではなく form_for
が利用できる点だ。 form_tag
は特に何も気にせずに form をかけて手っ取り早いんだけども、データのValue 値など全てinput要素毎に書かないといけない。また、label も同様に自分で書く必要がある。form_tag
は UIの変更に非常に弱いのだ。しかし、 form_for
を使えばそれらの苦労から解放される。以下のようにコントローラを記述すればform_for
が View 側で利用できる。
どちらも、 Rails のレールに乗ることができる というのが最大のメリットである。
def new @register = RegisterParam.new end def create ActiveRecord::Base.transaction do @register = RegisterParam.new(register_param) raise ActiveRecord::RecordInvalid.new(@register) unless @register.validate @contract = Contract.create!(@register.contract_param) @user = @contract.users.create!(@register.user_param) end rescue => e # 例外処理 @error = e.message end end private def register_param params.require(:register_param).permit( :name, :password, :password_confirmation, :tel1, :tel2, :tel3, :email, :email_confirmation ) end
<%= form_for @register do |f| %> <%= f.label :name %> <%= f.text_field :name %> <!-- ..... --> <%= f.submit %> <% end %>
通常のモデル処理と同様、validate
メソッドの検証に失敗した場合は、@register
にerror
オブジェクトが入ってくるので、エラー箇所などを適切に表現することが可能だ。もちろん 今回のモデルは DB とは連携していないので、save
メソッドなどは利用できない。
ちなみにエラーメッセージを出す時のフィールド名も 通常の Model と同様、 config/locales/ja.yml
に置いておけば適切に見てくれる。その際、キーはactiverecord
ではなくactivemodel
であることに注意しよう。
activemodel: attributes: register_param: name: 氏名 tel1: 電話番号1 tel2: 電話番号2 tel3: 電話番号3 email: メールアドレス email_confirmation: メールアドレス確認
終わりに
このように複雑なパラメータ処理でも、美しいコーディングを実現することができた。 Rails のレールに乗っている時の気分の良さはたまらないものがある。しっかりと Rails を理解して書いているという満足感と美しくコードを書くことができたという誇らしさ、そしてそれがメンテンス性、テストの書きやすさにつながってくる。
初心者ほど Rails のレールを踏み外すことが多い。より良いコードを書くために、Rails のレールに乗せることを再度考えてみよう。