【Ruby】sendメソッドとattr_accessorで属性を動的に読み書きする
おはようございます。ずみーです。
Rubyでメソッドの呼び出しはインスタンス名.メソッド名
ですが、インスタンス名
や.メソッド名
の部分をパラメータにして動的に呼び出したいことってありますよね。
結論:sendメソッドで実現可能
使い方はこんな感じです。
#インスタンスobjとメソッド名methodを受け取ってメソッドを呼び出す関数 def execute_method(obj, method) obj.send(method) end
また、インスタンスobj
にattr_accesor
で属性が定義してある場合、属性への値の書き込みは以下のようにやります。
#クラス class ClassName attr_accessor :attr1, :attr2 end #インスタンスobjと属性名attrと値valを受け取って、属性値を設定する関数 def set_attr(obj, attr, val) obj.send("#{attr}=", val) end #呼び出しかた instance = new ClassName set_attr(instance, "attr1", "val1") set_attr(instance, "attr2", "val2")
どこで使う?
私はFormオブジェクトパターンでの単体テストで使いました。
単体テストの行数を少しでも減らすため、インスタンスと属性を受け取って、「presence: true」のバリデーションをテストするヘルパー関数を定義しました。
モデル
Formオブジェクトパターン(参考記事)で定義したモデルクラスです。
saveメソッドで複数テーブルにレコード作成できるようにしています。
app/models/transaction.rb
class Transaction include ActiveModel::Model attr_accessor :user, :item, :token, :postal_code, :prefecture_id, :city, :addresses, :building, :phone_number # belongs_to_active_hash :prefecture POSTAL_CODE_REGEX = /\A\d{3}[-]\d{4}\z/.freeze PHONE_NUMBER_REGEX = /\A\d{1,11}\z/.freeze with_options presence: true do validates :user validates :item validates :token, presence: { message: 'を正しく入力してください' } validates :postal_code, format: { with: POSTAL_CODE_REGEX, message: 'はハイフン付きで000-0000の形式で入力してください' } validates :prefecture_id, numericality: { other_than: 0, message: 'を選択してください' } validates :city validates :addresses validates :phone_number, format: { with: PHONE_NUMBER_REGEX, message: 'は11桁以内の半角数字で入力してください' } end def save order = Order.create(user: user, item: item) Postal.create(order_id: order.id, postal_code: postal_code, prefecture_id: prefecture_id, city: city, addresses: addresses, building: building, phone_number: phone_number) end end
単体テスト用ヘルパーメソッド(RSpec使用)
インスタンスと属性と属性の日本語訳をパラメータとして受け取り、属性をnilにしてバリデーションをテストするヘルパー関数がこちらです。
上で書いた通り、send
メソッドを使って、obj
のcol
属性にnil
を代入し、バリデーションをテストしています。
今回の環境ではデフォルトのロケールが:ja
だったため、エラーメッセージは"#{nihongo}を入力してください"
となります(nihongoは属性名の日本語訳)。日本語訳の取得方法はこちらの記事にの追記に書きました。
spec/support/basic_validation_support.rb
module BasicValidationSupport def not_nil_validation_dynamic(obj, col, nihongo) obj.send("#{col}=", nil) obj.valid? expect(obj.errors.full_messages).to include("#{nihongo}を入力してください") end end
RSpecでヘルパーメソッドを有効化する方法についてはこちらをご覧ください。
単体テストコード
FactoryBotとFakerを使って書いています。 属性がnilなことが原因でinvalidになるテストパターンで、テストしたい属性を一つずつ上のヘルパーメソッドに渡して、動的にテストしています。
spec/models/transaction_spec.rb
require 'rails_helper' RSpec.describe Transaction, type: :model do before do @transaction = FactoryBot.build(:transaction) end describe '購入手続きをする(注文レコードと住所レコードを新規作成する)' do #省略 context '購入手続きができないとき' do #省略 context '属性の欠損が原因のとき' do columns = %w[user item postal_code prefecture_id city addresses phone_number] columns.each do |col| nihongo = Transaction.human_attribute_name(col) it "#{nihongo}が無ければ登録できない" do not_nil_validation_dynamic(@transaction, col, nihongo) end end end #省略 end end end
実行結果
Transaction 購入手続きをする(注文レコードと住所レコードを新規作成する) 購入手続きができるとき #省略 購入者が無ければ登録できない 商品が無ければ登録できない 郵便番号が無ければ登録できない 都道府県が無ければ登録できない 市区町村が無ければ登録できない 番地が無ければ登録できない 電話番号が無ければ登録できない Finished in 0.11757 seconds (files took 2.36 seconds to load) 9 examples, 0 failures