0

i'm creating a new Person class in a Person.rb file. When i use ruby Person.rb in my terminal it shows a NoMethodError:

Person.rb:12:in `<main>': undefined method `name=' for #<Person:0x00000001abb6d8> (NoMethodError)
Did you mean?  name

this is the code i wrote:

class Person
    attr :name, :age, :gender
end

person = Person.new

person.name = "John"
person.age = 55
person.gender = "male"

puts(person.name, person.age, person.gender)

any suggestion?

tronux7
  • 13
  • 6

2 Answers2

0

Some modifications you need to do:

please use attr_accessor :name, :age, :gender in your ruby class

and then try:

 puts person.name
 puts person.age
 puts person.gender

It should work for you..

Dev.rb
  • 487
  • 6
  • 14
  • it worked! can you also briefly explain the reason why? i'm new to ruby and i posted this question because i think there is no general answer around regarding this. P.S: i added an arrow up to your answer but it doesn't show the +1 because i'm a new user (i suppose) – tronux7 May 18 '19 at 17:20
  • `attr_accessor` is a Getter and Setter method in Ruby. what you wrote was wrong syntax. And the `puts` method does not need parenthesis. For more information, please refer `https://apidock.com/ruby/Module/attr_accessor` – Dev.rb May 18 '19 at 17:25
  • "what you wrote was wrong syntax" – That is wrong. If it was wrong syntax, it would terminate with a `SyntaxError` before even running. – Jörg W Mittag May 18 '19 at 17:32
  • @tronux: you're welcome.. this SO post will help you most for this ruby concept: https://stackoverflow.com/questions/4370960/what-is-attr-accessor-in-ruby – Dev.rb May 18 '19 at 17:32
0

If you look at the documentation of Module#attr, you will see that there are three "overloads" for using it:

attr(name, …)nil

attr(name, true)nil

attr(name, false)nil

The documentation then further explains the differences between the three:

The first form is equivalent to attr_reader. The second form is equivalent to attr_accessor(name) but deprecated. The last form is equivalent to attr_reader(name) but deprecated.

So, the first and third forms only generate readers, not writers. The second form generates a reader and a writer, but it only takes a single name formal parameter, you cannot use it to generate multiple attributes.

Also, forms two and three are deprecated and all three forms are non-idiomatic.

If you want to fix your code using the non-idiomatic Module#attr, you need to do this:

attr :name  , true
attr :age   , true
attr :gender, true

You can simplify this a little using something like

%i[name age gender].each do |attribute|
  attr attribute, true
end

However, as mentioned in the documentation, this form is deprecated, and as I mentioned earlier, Module#attr is non-idiomatic.

The idiomatic way is to use the Module#attr_* family of methods, i.e. Module#attr_reader, Module#attr_writer, and Module#attr_accessor:

attr_accessor :name, :age, :gender

Note that since your Person class doesn't actually have any behavior, you could simply replace it with a Struct:

Person = Struct.new(:name, :age, :gender)

However, note that accessors in general and writers in particular are not good object-oriented design. Objects should do something, they should have behavior. Your Person objects don't have any behavior, they just contain data. Also, an object should be valid after it is created, but your Person objects are empty when they are created. And lastly, if at all possible, objects should be immutable.

We can make sure that the object is valid after creation by adding an initializer:

class Person
  attr_accessor :name, :age, :gender

  def initialize(name, age, gender)
    self.name, self.age, self.gender = name, age, gender
  end
end

person = Person.new('John', 55, 'male')

Note: Struct will create an initializer for us.

If we want to make the object immutable, we need to remove the writers (or make them private). Unfortunately, Struct won't do that for us:

class Person
  attr_reader :name, :age, :gender

  private

  attr_writer :name, :age, :gender

  def initialize(name, age, gender)
    self.name, self.age, self.gender = name, age, gender
  end
end

person = Person.new('John', 55, 'male')

Now, person is fully initialized after creating it, and it can no longer be changed.

Community
  • 1
  • 1
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • thank you for the exaustive explanation! P.S: i ack many of the hints you wrote about object oriented design. I'm just playing around with ruby – tronux7 May 18 '19 at 22:04