Breaking News

Default Placeholder Default Placeholder

オブジェクト指向言語で使うのが避けられないのがインスタンスを作成する `new`ですよね。今回はRuby on Railsでインスタンスを作るときに使えるテクニックをご紹介します。

環境

  • Ruby on Rails: 6.0.0
  • Ruby: 2.6.3

不正なアトリビュートがないようにチェック

今回紹介したいのはActiveModel::Modelの不正アトリビュートチェック機能です。

`new`するときに引数を付与して初期データをアトリビュートに入れ込むことは多々あるかと思います。その時、最初に作った時はいいのですが、アトリビュートの数が多すぎて、本来入るべきではないアトリビュートが入っていないかわかってないときにこの手法を使うと便利です。

サンプルプログラム

ここで簡単なサンプルプログラムを作って説明します。

module Sample
  class SampleModel
    include ActiveModel::Model
    FIELDS = %i[
      aaa
      bbb
      ccc
      ddd
    ]
    attr_accessor(*FIELDS)
    def self.init_with_data(data:)
      new(
        aaa: data[:aaa],
        bbb: data[:bbb],
        ccc: data[:ccc],
        ddd: data[:ddd],
      )
    end
  end
end

Rails cで機能確認

init_with_dataで正しくインスタンスが作成できている場合は下記のようになります。

$ rails c
> data = { aaa: 1, bbb: 2, ccc: 3, ddd: 4 }
=> {:aaa=>1, :bbb=>2, :ccc=>3, :ddd=>4}
> Sample::SampleModel.init_with_data(data: data)
=> #<Sample::SampleModel:0x00007febed273770 @aaa=1, @bbb=2, @ccc=3, @ddd=4>

ちゃんとインスタンスができていますね。

ちょっと変更

さてここで、 init_with_datanewするときの引数に余分な物を入れてやってみましょう。

module Sample
  class SampleModel
    include ActiveModel::Model
    FIELDS = %i[
      aaa
      bbb
      ccc
      ddd
    ]
    attr_accessor(*FIELDS)
    def self.init_with_data(data:)
      new(
        aaa: data[:aaa],
        bbb: data[:bbb],
        ccc: data[:ccc],
        ddd: data[:ddd],
        zzz: data[:zzz]
      )
    end
  end
end

newzzzを追加しました。これで先ほどと同じようにやってみましょう。

$ rails c
> data = { aaa: 1, bbb: 2, ccc: 3, ddd: 4, zzz: 26 }
=> {:aaa=>1, :bbb=>2, :ccc=>3, :ddd=>4, :zzz=>26}
> Sample::SampleModel.init_with_data(data: data)
Traceback (most recent call last):
        3: from (irb):37
        2: from app/models/sample/sample_model.rb:15:in `init_with_data'
        1: from app/models/sample/sample_model.rb:15:in `new'
ActiveModel::UnknownAttributeError (unknown attribute 'zzz' for Sample::SampleModel.)

怒られてしまいました。エラーを見てみましょう。

ActiveModel::UnknownAttributeError

`zzz`なんてアトリビュート知らないよ、って言われていますね。

attr_accessor定義していないものはダメだよってことですね。

zzzをこのクラスのインスタンスで使いたかったらちゃんと `attr_accessor`で登録して使わなければなりません。

では `zzz`を登録してみましょう。

module Sample
  class SampleModel
    include ActiveModel::Model
    FIELDS = %i[
      aaa
      bbb
      ccc
      ddd
      zzz ⬅︎ ここ
    ]
    attr_accessor(*FIELDS)
    def self.init_with_data(data:)
      new(
        aaa: data[:aaa],
        bbb: data[:bbb],
        ccc: data[:ccc],
        ddd: data[:ddd],
        zzz: data[:zzz]
      )
    end
  end
end

また rails cで確認

$ rails c
> data = { aaa: 1, bbb: 2, ccc: 3, ddd: 4, zzz: 26 }
=> {:aaa=>1, :bbb=>2, :ccc=>3, :ddd=>4, :zzz=>26}
> Sample::SampleModel.init_with_data(data: data)
=> #<Sample::SampleModel:0x00007febebd44a20 @aaa=1, @bbb=2, @ccc=3, @ddd=4, @zzz=26>

今回は怒られずに `zzz`が入りました。

これの何がいいの?

さて、この有用性はなんなんでしょうか?直接 new すればいいじゃない、と思いますよね。しかしこのように直接 new するのではなく一回Factoryメソッドを通じてインスタンスを作るときにはこのようにするのが良しとされています。理由は2つです。

1. インスタンス作成時の引数の中身がわかりやすくなる

例えば、今回のように dataのように、中にいろんなデータが格納されている変数を引数にすることありますよね。

`data`の中には極端な話、100個も200個もデータを入れることができます。ActiveModel::Modelのこのチェック機能を使うことで、その変数を使ってインスタンスを作成する際に何が使われているかのチェックがしやすくなります。

2. 余分なアトリビュートを作らない

余分なアトリビュートを作ろうとするとエラーで落ちるのでいやでも気づきます。これで無駄コードを減らすことができます(^^)

以上、RailsのActiveModel::Modelのアトリビュートチェック機能の紹介でした。最後までお読みいただきありがとうございました。