Lax_Sam
Lax_Sam

Reputation: 1139

Correct way to define class method and property in Ruby class

I am trying to write a Person class in Ruby which has some methods and properties. The below is how I have implemented it now.

class Person
  attr_accessor :name, :gender, :mother

  def initialize(name, gender, mother)
    @name = name
    @gender = gender
    @mother = mother
    @spouse = nil
  end

  def add_spouse(spouse)
    if spouse
      @spouse = spouse
      spouse.spouse = self
    end
  end
  
  def father(self)
    return [self.mother.spouse]
  end
end

I want to access the methods like this:

m = Person.new('mom', 'Female')
d = Person.new('dad', 'Male')

d.add_spouse(m)

p = Person.new('Jack', 'Male', m)

If I want to get the father of p, then I want to get it like this: p.father.

So is the above implementation correct?

I am getting the below error if I run the code:

person.rb:18: syntax error, unexpected `self', expecting ')'
  def father(self)
person.rb:21: syntax error, unexpected `end', expecting end-of-input

If I remove self from def father(self), then I am getting this error:

person.rb:14:in `add_spouse': undefined method `spouse=' for #<Person:0x00005570928382d8> (NoMethodError)

My error was here: attr_accessor :spouse. I had to add this and to access it I had to do puts(p.father[0].name)

Is there a better way to implement the above class with similar other properties like father, children, brothers etc?

Upvotes: 2

Views: 1762

Answers (2)

benjessop
benjessop

Reputation: 1959

In Ruby, everything is an object, well almost everything excluding blocks, etc. Ruby uses classes as definitions for Objects and they establish attributes and methods.

Your code sets the attributes for name, gender and mother and has methods for add_spouse and father. So there's an obvious mix for father being interpreted as both attribute and method. From a naming convention perspective we could rename that method collect_father_relation or something similar, which will process the logic to present the Object.

You also do not need to explicitly write return as you have in the father method, Ruby does that for you.

I would reconfigure the father method to the below:

def collect_father_relation
  @mother&.spouse
end

You call the @mother instance_variable, defined the instance, and only call spouse if @mother is present (as it can be nil) with the &. operator.

You should also prepare the initialize function for a nil mother being passed:

def initialize(name, gender, mother = nil)

This will accept a mother parameter, but also not raise an ArgumentError if one is not passed.

Finally, you will have to add an attr_accessor for :spouse as you will want to explicitly reference spouse for the spouse Person object as you are setting the relation in spouse.spouse = self

Other comments:

Let's make a guard clause for the add_spouse method (as we have above) to explicitly explain what we are doing, rather than the ambiguous if statement.

  def add_spouse(spouse)
    return if spouse.nil?
    
    @spouse = spouse
    spouse.spouse = self
  end

Upvotes: 1

fidato
fidato

Reputation: 719

Have a look -

class Person
  attr_accessor :name, :gender, :mother, :spouse

  def initialize(name, gender, mother)
    @name = name
    @gender = gender
    @mother = mother
    @spouse = nil
  end

  def add_spouse(spouse)
    if spouse
      @spouse = spouse
      @spouse.spouse = self
    end
  end
  
  def father()
    return [self.mother.spouse]
  end
end

output -

m = Person.new('mom', 'Female', nil)
d = Person.new('dad', 'Male', nil)
d.add_spouse(m)
p = Person.new('Jack', 'Male', m)

As written in a comment, it's more a question for https://codereview.stackexchange.com/

Upvotes: 1

Related Questions