Reputation: 8461
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
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
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
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