understanding the usage of cattr_accessor in Rails Model Class

1.3k Views Asked by At

I am a newbie to Ruby and Rails. In my Rails app, I am trying to use Wicked Wizard gem and need some help in Understanding about the cattr_accessor which I have come across in the code

# 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

Now my Questions are:

1) what is the difference between the cattr_accessor and attr_accessor?

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

2) why two different symbols(:form_steps , :form_step) are used as method parameters for cattr_accessor and attr_accessor methods respectively?

3) why is a block passed as a parameter to cattr_accessor method?

1

There are 1 best solutions below

0
On

First of all, this method is deprecated or moved. Which version do you use, Rails 4 or Rails ?

cattr_accessor > replaced for mattr_accessor(*syms, &blk) for Module which is the superclass of the class Class. I recomend to use attr_accessor, which is just a method to set attributes for the class, it works like a getter or a setter for the class but only works for an instance (in memory), the attribute is saved anywhere.

cattr_accessor is like the attr_* methods, but for the class level. One thing you wouldn't expect is because it uses a backing @@form_steps, the value shared between the class and all instances.

Api docs:

Defines both class and instance accessors for class attributes.

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]

If a subclass changes the value then that would also change the value for parent class. Similarly if parent class changes the value then that would change the value of subclasses too.

class Male < Person
end

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

To opt out of the instance writer method, pass instance_writer: false. To opt out of the instance reader method, pass 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

Or pass instance_accessor: false, to opt out both instance methods.

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

Also you can pass a block to set up the attribute with a default value.

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)

  • attr_accessor: is a ruby method that makes a getter and a setter
  • cattr_accessor:is a ruby method that makes a getter and a setter for the class (quite strange).

2) Class variable have a tendency to wander from class to class. @@form_steps class variable can be exposed through inheritance tree.

3) To set which attributes will have this class. form_steps

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

and use the form_steps attribute inside your current instance Pet (like you do in the method required_for_step?).