aimen alt
aimen alt

Reputation: 102

rspec undefined method `title='

I am getting this error "rspec undefined method `title=' when I run bundle exec rspec. Keep note that in the spec file its called "title" not "title=" ive searched the entire spec file to see if i could find a typo but with no luck. this is the error it gives me:

    Book
  title
    should capitalize the first letter (FAILED - 1)

Failures:

  1) Book title should capitalize the first letter
     Failure/Error: @book.title = "inferno"
     NoMethodError:
       undefined method `title=' for #<Book:0x007fec2b140450>

and this is the part on the spec file where it stops at:

require '08_book_titles'

describe Book do
  before do
    @book = Book.new
  end

  describe "title" do
    it "should capitalize the first letter" do
        @book.title = "inferno"
        expect(@book.title).to eq("Inferno")
    end

to my understanding after trying things out, it is confusing the title in "@book.title = "inferno"" to be the "title=" (i changed the title to another word and it puts out that word with the equals at the end of it). I am unsure as to why it adds an equal to the end of of the method.

(i commented out all my own code to make sure that the error is coming from the spec file)

Upvotes: 0

Views: 1128

Answers (1)

Sebasti&#225;n Palma
Sebasti&#225;n Palma

Reputation: 33420

tl;dr: In the "should capitalize the first letter" example, you set the book's title, but the class doesn't implement that method.


NoMethodError: undefined method `title=' for #<Book:0x007fec2b140450>

Is self explanatory, but also might be confusing. Any NoMethodError means the method that's trying to be accessed on the receiver hasn't be defined.

Think in a very small class, Car, with one instance method to return the total of wheels:

class Car
  def initialize
    @wheels = '4 big wheels'
  end
  def wheels
    @wheels
  end
end

yaris = Car.new
p yaris.wheels # "4 big wheels"
p yaris.doors # `<main>': undefined method `doors' for #<Car:0x0000558d18cfbd68 (NoMethodError)

Accessing the wheels method returns what's expected, but trying to access the doors method throws a NoMethodError. There's no method doors defined in the Car class.

That's for both method in the example, known as "getter" methods, they return values you might need in some point on the class implementation. But as there are "getter" methods, also are "setter" methods, they defined with the = symbol.

In the example class, we define within the initialize method, the @wheels instance variable, and then through the wheels method, we access them. But what about if you want to re-define the wheels - perhaps you crashed the car and now it has just 3 wheels, so, you need a method to set the value of @wheels.

Let's try setting up them:

yaris = Car.new
p yaris.wheels # "4 big wheels"
yaris.wheels = '3 working wheels' # undefined method `wheels=' for #<Car:0x000055755ad933c8 @wheels="4 big wheels"> (NoMethodError)

Now you can't, as there's no wheels= method, you can't change the value of @wheels that way. So, is isn't, define it:

...
  def wheels=(value)
    @wheels = value
  end
end

yaris = Car.new
p yaris.wheels # "4 working wheels"
yaris.wheels = '3 working wheels'
p yaris.wheels # "3 working wheels"

That's it, you can get and set the wheels from any Car instance now.

Most of the time, you don't see methods like the wheels= one. As Ruby has the ability to let you define attribute accessors for your classes. You could define your class with an attr_reader :wheels, which would give you the value of @wheels:

class Car
  attr_reader :wheels
  def initialize
    @wheels = '4 big wheels'
  end
end

yaris = Car.new
p yaris.wheels # "4 working wheels"

And it works the same way, but, again, you can not define the value of @wheels, as you have just a read attribute, if you want to, then you could add the attr_reader's brother, an attr_writer:

class Car
  attr_reader :wheels
  attr_writer :wheels
  def initialize
    @wheels = '4 big wheels'
  end
end

Now it's done, you can read and set the @wheels value, but why bothering on writing both reader and writer attributes one by one. You can use the attr_accessor, which implements both wheels and wheels= method on your class, on the specific attribute you pass as argument.

class Car
  attr_accessor :wheels
  ...
end

p Car.instance_methods(false) # [:wheels=, :wheels]

Why such a boring answer?, because if you're able to know where do these methods come from, you'll be able to know why they're or they're not where they're supposed to be. No matter what framework, DSL, any other you're using, it's Ruby.

On Module#instance_methods.

Upvotes: 4

Related Questions