Bitwise
Bitwise

Reputation: 8461

Rspec - Trying to understand nil:NilClass error

I'm trying to make a test pass for code that has already has been implemented. I'm new to unit testing and RSpec and for the life of me cannot figure out this error. Basically, I'm trying to verify that JSON is being sent to the proper endpoint. I think I have created all the attributes I needed to but I keep getting this same error. Here is the error and my code. it's clearly something to do with including the city from another model table but I'm not sure why it's complaining?

Here is the test.

require "rails_helper"

RSpec.describe "/api/retailers" do

describe "GET /api/retailers" do
it "Returns JSON for retailers" do

    location = Location.create!(
      city: "Portland",
      street_1: "Cherry",
      state: "Oregon",
      zip: "49490"
    )

  retailer = Retailer.create!(
    name: "Good Coffee Co.",
    description: "Hipster Good",
    image_url: "http://www.example.com/foo_bar.jpg",
  )

  get "/api/retailers.json"

  expect(response).to be_success
  json = JSON.parse(response.body)

  expect(json["name"]).to eql("Good Coffee Co.")
  expect(json["description"]).to eql("Hipster Good")
  expect(json["image_url"]).to eql("http://www.example.com/foo_bar.jpg")
  expect(json["city"]).to eql(location.city)
end
end
end

Here is my error message

/api/retailers GET /api/retailers Returns JSON for retailers
 Failure/Error: location.city

 NoMethodError:
   undefined method `city' for nil:NilClass
 # ./app/models/retailer.rb:8:in `city'

Here is my controller

class Api::RetailersController < ApiController
 def index
  @retailers = Retailer.all
  render json: @retailers, methods: [:city]
 end
end

Here is my model

class Retailer < ActiveRecord::Base
has_one :location

has_many :retailer_timeslots
has_many :timeslots, through: :retailer_timeslots

def city
 location.city
end
end

Any help would be awesome. I'm really stumped on this problem. Let me know if any other info is necessary

Upvotes: 0

Views: 280

Answers (3)

max
max

Reputation: 101811

You never actually set up the association between retailer and location. Thus when retailer#city is called location is nil.

You might want to start by changing your method to handle nil gracefully unless your business logic should not allow a Retailer without a Location:

class Retailer < ActiveRecord::Base
  has_one :location

  has_many :retailer_timeslots
  has_many :timeslots, through: :retailer_timeslots

  def city
    location.city if location
  end
end

A more elegant way is by using Module#delegate:

class Retailer < ActiveRecord::Base
  has_one :location

  has_many :retailer_timeslots
  has_many :timeslots, through: :retailer_timeslots

  delegate :city, to: :location, allow_nil: true
end

This should change the spec from broken to failing.

To fix the spec you need to setup a relation between the two:

location = Location.create!(
  city: "Portland",
  street_1: "Cherry",
  state: "Oregon",
  zip: "49490"
)

retailer = Retailer.create!(
  name: "Good Coffee Co.",
  description: "Hipster Good",
  image_url: "http://www.example.com/foo_bar.jpg",
  location: location # !!!!!
)

Upvotes: 1

x6iae
x6iae

Reputation: 4164

Your Retailer model has a relationship with location.

And you have a city method on it as well, which is looking at this location

def city
  location.city
end

Now, this is included in your controller:

render json: @retailers, methods: [:city]

Now, in your test, you created a location and a retailer:

location = Location.create!(
      city: "Portland",
      street_1: "Cherry",
      state: "Oregon",
      zip: "49490"
    )

  retailer = Retailer.create!(
    name: "Good Coffee Co.",
    description: "Hipster Good",
    image_url: "http://www.example.com/foo_bar.jpg",
  )

But these two are not related in any way.

So, when in your test, you send a get request to the index action of your controller,

get "/api/retailers.json"

This tries to get the city for the retailer, which is the method above. but remember that the method tries to get location.city

But your retailer has no location, because it has not been linked to any... So, location here will be nil

and then, when you call city on location(which is nil), you are calling city on nil

This is where the error comes from:

NoMethodError:
   undefined method `city' for nil:NilClass
 # ./app/models/retailer.rb:8:in `city'

Edit (To Fix):

Add the location created as the Retailers location in your test as follow:

location = Location.create!(
  city: "Portland",
  street_1: "Cherry",
  state: "Oregon",
  zip: "49490"
)

retailer = Retailer.create!(
  name: "Good Coffee Co.",
  description: "Hipster Good",
  image_url: "http://www.example.com/foo_bar.jpg",
  location: location # Add location as location here.
)

Upvotes: 1

Rodrigo
Rodrigo

Reputation: 4802

The city method use the location of Retailer, which wasn't set for the one you created.

To solve this, just assign the location association to Retailer:

retailer = Retailer.create!(
    name: "Good Coffee Co.",
    description: "Hipster Good",
    image_url: "http://www.example.com/foo_bar.jpg",
    location: location
  )

Upvotes: 1

Related Questions