My config/routes.rb file...
Rails.application.routes.draw do
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :hotels do
resources :rooms
end
end
end
My app/controllers/api/v1/hotels_controller.rb
module Api
module V1
class HotelsController < ApplicationController
respond_to :json
skip_before_filter :verify_authenticity_token
def index
@hotels = Hotel.all
respond_with ({hotels: @hotels}.as_json)
#respond_with(@hotels)
end
def show
@hotel = Hotel.find(params[:id])
respond_with (@hotel)
end
def create
@hotel = Hotel.new(user_params)
if @hotel.save
respond_with (@hotel) #LINE 21
end
end
private
def user_params
params.require(:hotel).permit(:name, :rating)
end
end
end
end
When I go to POST through Postman, my data saves just fine, but I get this NoMethodError. Why is this? The issue seems to be occurring at line 21, which is the respond_with(@hotel) line. Should it not just be responding with json ouput for the newly created hotel, via the show method?
(1.1ms) COMMIT
Completed 500 Internal Server Error in 76ms
NoMethodError (undefined method `hotel_url' for #<Api::V1::HotelsController:0x0000010332df58>):
app/controllers/api/v1/hotels_controller.rb:21:in `create'
Rendered /Users/.rvm/gems/ruby-2.0.0-p451@railstutorial_rails_4_0/gems/actionpack-4.1.0/lib/action_dispatch/middleware/templates/rescues/_source.erb (1.0ms)
Rendered /Users/.rvm/gems/ruby-2.0.0-p451@railstutorial_rails_4_0/gems/actionpack-4.1.0/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (1.7ms)
Rendered /Users/.rvm/gems/ruby-2.0.0-p451@railstutorial_rails_4_0/gems/actionpack-4.1.0/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (1.4ms)
Rendered /Users/.rvm/gems/ruby-2.0.0-p451@railstutorial_rails_4_0/gems/actionpack-4.1.0/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (31.5ms)
Because your route is in the API + v1 namespace, you actually need to redirect to the api_v1_hotel_url(@hotel)
after you successfully create your resource. Of course, this is an API and there is no real redirecting, but the default Rails responder doesn't know that. It also doesn't know about your routing namespaces.
With just the default responder, you would have to do
respond_with :api, :v1, @hotel
So that Rails will build a URL that exists. Alternatively, you can create a custom responder that remove the :location
option. Here is the default responder: http://api.rubyonrails.org/files/actionpack/lib/action_controller/metal/responder_rb.html
Reading through the source code for that class is very helpful in understanding respond_with
. For example, you don't need to use if record.save
before you use respond_with
with this Responder. Rails will check if the record saved successfully for you and render a 422 with errors if it failed to save.
Anyway, you can see that the responder sets up a lot of variables in it's initializer:
def initialize(controller, resources, options={})
@controller = controller
@request = @controller.request
@format = @controller.formats.first
@resource = resources.last
@resources = resources
@options = options
@action = options.delete(:action)
@default_response = options.delete(:default_response)
end
If you subclassed this responder, you could make something like this:
class CustomResponder < ActionController::Responder
def initialize(*)
super
@options[:location] = nil
end
end
You can set a controller's responder using responder=
:
class AnyController < ActionController::Base
self.responder = CustomResponder
# ...
end
To be clear, let me recap:
respond_with
, Rails will try to infer what route to redirect to after a successful create. Imagine you had a web UI where you can create hotels. After a hotel is created, you will be redirected to that hotel's show
page in the standard Rails flow. That is what Rails is trying to do here.hotel_url
- a route which does not exist!api_v1_hotel_url
nil
, since you don't actually need to redirect anywhere with a simple JSON response. Custom responders can also be useful in many other ways. Check out the source code. If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With