DEV Community

Ruby Getters and Setters

K-Sato on November 14, 2018

Table of contents What is a getter method What is a setter method What are accessors? References What is a getter method? ...
Collapse
 
phallstrom profile image
Philip Hallstrom • Edited

Another neat trick is you can mark these as private so you can use them internally without worrying about them leaking out by throwing a private into the class.

This can be useful if you don't like remembering when to use @year vs year or perhaps you want to be prepared to abstract it later.

require "date"
class Movie
  attr_accessor :name, :year
  private :year, :year=

  def initialize(name, year)
    @name = name
    @year = year
  end

  def age
    Date.today.year - year
  end
end
obj1 = Movie.new("Forrest Gump", 1994)
obj1.year #=> NoMethodError: private method `year' called for #<Movie:....>
obj1.year = 2018 #=> NoMethodError: private method `year=' called for #<Movie:....>
obj1.age #=> 24
Collapse
 
tadman profile image
Scott Tadman

It seems odd to declare these as private since you can always refer to them by their @ name internally.

Collapse
 
phallstrom profile image
Philip Hallstrom

It can be odd, but it can also be useful as it allows you to more easily override it later without having to go find/replace all instances of @year in your class.

Or, if a class inherits from it and needs to change how year is generated it's easier it doesn't have to worry about parent class using @year.

If that makes sense.

Thread Thread
 
tadman profile image
Scott Tadman

It just seems inconsistent to use year and self.year = ... in the code where one's bare and the other's necessarily prefixed, instead of @year consistently. This plus the way that @year is by default "private".

One of the major goals of object-oriented design is to properly contain any changes like that, so replacing @year with something else is at least contained to the one file or scope.

Subclass concerns are valid, though in Ruby it's generally understood you need to play nice with your parent's properties and avoid really getting in there and wrecking around.

Collapse
 
k_penguin_sato profile image
K-Sato • Edited

Thanks for sharing !!

Collapse
 
tadman profile image
Scott Tadman • Edited

These are also called "accessors" (read) and "mutators" (write) in other languages, but the principle is the same. Useful terminology for those coming to Ruby from places where those terms are used instead.

Ruby's way of declaring them as x= type methods is fairly unique and makes for some extremely concise code since there's no need for getX / setX pairs, it's just x and x=.

Another thing worth mentioning is if you have a "setter" or attr_writer you can't use that without prefixing it with some kind of object, even self.

For example:

class Example
  attr_accessor :test

  def assign!
    test = :assigned
  end
end

example = Example.new
example.assign!
example.test
# => nil

That's because in the code test = :assigned creates a variable named test, it doesn't call the test= method. To use those you must do self.test = :assigned inside the context of that method or example.test = :assigned by using some kind of variable for reference.

This leads to a lot of confusion in places like ActiveRecord where assigning to the auto-generated attributes "doesn't work".

Collapse
 
nkroker profile image
Nikhil Kumar Tyagi

I have a question how to generate this getter dynamically, for example you get a string argument and you have to generate a setter method for it which sets an instance variable which gets triggered when someone assign a value to it

class Movie
  def generate_accessors(name, value)
    generate_setter(name, value)
    generate_getter(name, value)
  end

  def generate_getter(name, value)
    self.class.send(:define_method, name) do
      value
    end
  end

  def generate_setter(name, value)
    # Here we need to generate that setter dynamically
    # def name=(name, value) #setter method
    #   instance_variable_set("@#{name}", value)
    # end
  end
end

obj1 = Movie.new
obj1.generate_accessors('foo', 'bar')
obj1.foo                         # ==> 'bar'
obj1 .foo = 'something'  # ==> This should trigger that setter
Enter fullscreen mode Exit fullscreen mode
Collapse
 
k_penguin_sato profile image
K-Sato

Thank you👍!

Collapse
 
pauldupont1120 profile image
Paul Dupont

I sincerely appreciate it. I just want to let you know that the article is fantastic since this knowledge is truly helpful to me bluey