Techioz Blog

Railsモデルクラスでのcattr_accessorの使用法を理解する

概要

私はRubyとRailsの初心者です。 Rails アプリで Wicked Wizard gem を使用しようとしていますが、コード内で見つかった cattr_accessor について理解するのに助けが必要です。

# migration
create_table "pets", force: :cascade do |t|
  t.string   "name"
  t.string   "colour"
  t.string   "owner_name"
  t.text     "identifying_characteristics"
  t.text     "special_instructions"
  t.datetime "created_at"
  t.datetime "updated_at"
  t.string   "email"
  t.string   "password"
end

# model
class Pet < ActiveRecord::Base
  has_many :pet_photos

  cattr_accessor :form_steps do
    %w(identity characteristics instructions)
  end

  attr_accessor :form_step

  validates :email, presence: true
  validates :name, :owner_name, presence: true, if: -> { required_for_step?(:identity) }
  validates :identifying_characteristics, :colour, presence: true, if: -> { required_for_step?(:characteristics) }
  validates :special_instructions, presence: true, if: -> { required_for_step?(:instructions) }

  def required_for_step?(step)
    return true if form_step.nil?
    return true if self.form_steps.index(step.to_s) <= self.form_steps.index(form_step)
  end
end

# controller
class Pet::StepsController < ApplicationController
  include Wicked::Wizard
  steps *Pet.form_steps

  def show
    @pet = Pet.find(params[:pet_id])
    render_wizard
  end

  def update
    @pet = Pet.find(params[:pet_id])
    @pet.update(pet_params(step))

    if params[:images]
      params[:images].each do |image|
        @pet.pet_photos.create(image: image)
      end
    end

    render_wizard @pet
  end

  private

  def pet_params(step)
    permitted_attributes = case step
                           when "identity"
                             [:name, :owner_name]
                           when "characteristics"
                             [:colour, :identifying_characteristics]
                           when "instructions"
                             [:special_instructions]
                           end

    params.require(:pet).permit(permitted_attributes).merge(form_step: step)
  end
end

# routes
PetThing::Application.routes.draw do
  resources :pets, only: [:new, :create, :index, :destroy] do
    resources :steps, only: [:show, :update], controller: 'pet/steps'
  end

  root to: 'pets#index'
end

さて、私の質問は次のとおりです。

  1. cattr_accessor と attr_accessor の違いは何ですか?
       cattr_accessor :form_steps do
         %w(identity characteristics instructions)
       end
       attr_accessor :form_step
  1. なぜ 2 つの異なるシンボル (:form_steps 、:form_step) がそれぞれ cattr_accessor メソッドと attr_accessor メソッドのメソッド パラメーターとして使用されるのですか?

  2. ブロックが cattr_accessor メソッドにパラメータとして渡されるのはなぜですか?

解決策

まず、このメソッドは廃止されるか、移動されます。 Rails 4 と Rails のどちらのバージョンを使用していますか?

cattr_accessor > クラス Class のスーパークラスである Module の mattr_accessor(*sYSms, &blk) を置き換えます。 attr_accessor を使用することをお勧めします。これは、クラスの属性を設定する単なるメソッドです。クラスのゲッターまたはセッターのように機能しますが、インスタンス (メモリ内) に対してのみ機能し、属性はどこにでも保存されます。

cattr_accessor は attr_* メソッドに似ていますが、クラス レベルのものです。予想外のことの 1 つは、クラスとすべてのインスタンス間で共有される値であるバッキング @@form_steps を使用していることです。

クラス属性のクラス アクセサーとインスタンス アクセサーの両方を定義します。

module HairColors
  mattr_accessor :hair_colors
end

class Person
  include HairColors
end

Person.hair_colors = [:brown, :black, :blonde, :red]
Person.hair_colors     # => [:brown, :black, :blonde, :red]
Person.new.hair_colors # => [:brown, :black, :blonde, :red]

サブクラスが値を変更すると、親クラスの値も変更されます。同様に、親クラスが値を変更すると、サブクラスの値も変更されます。

class Male < Person
end

Male.hair_colors << :blue
Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]

インスタンス ライター メソッドをオプトアウトするには、instance_writer: false を渡します。インスタンス リーダー メソッドをオプトアウトするには、instance_reader: false を渡します。

module HairColors
  mattr_accessor :hair_colors, instance_writer: false, instance_reader:             false
end

class Person
  include HairColors
end

Person.new.hair_colors = [:brown]  # => NoMethodError
Person.new.hair_colors             # => NoMethodError

または、instance_accessor: false を渡して、両方のインスタンス メソッドをオプトアウトします。

module HairColors
  mattr_accessor :hair_colors, instance_accessor: false
end

class Person
  include HairColors
end

Person.new.hair_colors = [:brown]  # => NoMethodError
Person.new.hair_colors             # => NoMethodError

また、ブロックを渡して属性をデフォルト値で設定することもできます。

module HairColors
  mattr_accessor :hair_colors do
[:brown, :black, :blonde, :red]
  end
end

class Person
  include HairColors
end

Person.class_variable_get("@@hair_colors") #=> [:brown, :black, :blonde, :red]
  1. クラス変数はクラス間を移動する傾向があります。 @@form_steps クラス変数は、継承ツリーを通じて公開できます。

  2. このクラスを持つ属性を設定します。フォームステップ

p = Pet.new
p.form_steps = "var"

そして、現在のインスタンス Pet 内で form_steps 属性を使用します (required_for_step メソッドで行うのと同様)。