How to set Rails routes on nested resources? - ruby-on-rails

This may seem redundant because similar question has been asked here and here, but I haven't found a solution yet.
I am running an RSpec to test :update for api. When I run RSpec, it shows No Route Matches on my first test. All I need is to have the testing to have_http_status(401) for unauthenticated user. Rails can't figure out the routing.
Here is what the error says:
Failures:
1) PostsController unauthenticated user PUT update returns http unauthenticated
Failure/Error: put :update, topic_id: my_topic.id, post_id: my_post.id, post: {title: my_post.title, body: my_post.body}
ActionController::UrlGenerationError:
No route matches {:action=>"update", :controller=>"posts", :post=>{:title=>"Xdiwbu zuitsom prubmlhd oxmgtkb swphb ukije salhvk.", :body=>"Pjlb ywlzqv igdesqmw oqjgy mrwpye ujierxtn owqxbvt. Wzxu sjcikthg xare tcawzx tedmiqwf lewab. Twkeoun mos ophta fvae krmnsqe. Jxefyo ncd agj ieyanvt uehazwnk mtsi fbsm."}, :post_id=>"1", :topic_id=>"1"}
# ./spec/api/v1/controllers/posts_controller_spec.rb:11:in `block (3 levels) in <top (required)>'
Here is the RSpec (spec/api/v1/controllers/posts_controller_spec.rb)
require 'rails_helper'
RSpec.describe Api::PostsController, type: :controller do
let(:my_user) { create(:user) }
let(:my_topic) { create(:topic) }
let(:my_post) { create(:post, topic: my_topic, user: my_user) }
context "unauthenticated user" do
it "PUT update returns http unauthenticated" do
put :update, topic_id: my_topic.id, post_id: my_post.id, post: {title: my_post.title, body: my_post.body}
expect(response).to have_http_status(401)
end
...
Here are the routes:
namespace :api do
namespace :v1 do
resources :users, only: [:index, :show, :create, :update]
resources :topics, except: [:edit, :new] do
resources :posts, only: [:update, :create, :destroy]
end
end
end
And here is the first part of the test:
class Api::V1::PostsController < Api::V1::BaseController
before_action :authenticate_user, except: [:index, :show]
before_action :authorize_user, except: [:index, :show]
def update
post = Post.find(params[:id])
if post.update_attributes(post_params)
render json: post.to_json, status: 200
else
render json: {error: "Post update failed", status: 400}, status: 400
end
end
...
No matter what I change the RSpec, I can't get it to match the routes. Would you guys mind helping?
Thanks!!

2 things stand out to me.
1: The controller itself is namespaced under Api::V1. The controller in the spec however, is namespaced under just Api. This should be updated to match.
2: If you run rake routes, you'll notice a line like this:
PUT /api/v1/topics/:topic_id/posts/:id(.:format) api/v1/posts#update.
It's important to note the names being given after the : in that message. In here, it's stating that the ID of the topic should be supplied to the controller as topic_id, and the ID of the post should be supplied as just id. If you modify your put statement to be something more like put :update, topic_id: my_topic.id, id: my_post.id, post: {title: my_post.title, body: my_post.body}, it should work.

Related

No route matches {:action=>"/microposts", :controller=>"microposts", :params=>{:micropost=>{:content=>"Lorem ipsum"}}}

I am currently stuck on the rails tutorial from Michael Hartl (railstutorial.org), Chapter 13
and getting the following two errors:
1) Error:
MicropostsControllerTest#test_should_redirect_create_when_not_logged_in:
ActionController::UrlGenerationError: No route matches {:action=>"/microposts", :controller=>"microposts", :params=>{:micropost=>{:content=>"Lorem ipsum"}}}
test/controllers/microposts_controller_test.rb:11:in 'block (2 levels) in <class:MicropostsControllerTest>'
test/controllers/microposts_controller_test.rb:10:in 'block in <class:MicropostsControllerTest>'
2) Error:
MicropostsControllerTest#test_should_redirect_destroy_when_not_logged_in:
ActionController::UrlGenerationError: No route matches {:action=>"/microposts/499495288", :controller=>"microposts"}
test/controllers/microposts_controller_test.rb:18:in 'block (2 levels) in <class:MicropostsControllerTest>'
test/controllers/microposts_controller_test.rb:17:in 'block in <class:MicropostsControllerTest>'
As far as i know, 'action' should be something like get, post, delete etc.
But i don't know, why it says 'micropost' here.
Content of microposts_controller_test.rb:
require 'test_helper'
class MicropostsControllerTest < ActionController::TestCase
def setup
#micropost = microposts(:orange)
end
test 'should redirect create when not logged in' do
assert_no_difference 'Micropost.count' do
post microposts_path, params: {micropost: {content: 'Lorem ipsum'}}
end
assert_redirected_to login_url
end
test 'should redirect destroy when not logged in' do
assert_no_difference 'Micropost.count' do
delete micropost_path(#micropost)
end
assert_redirected_to login_url
end
end
Content of micropost_controller.rb:
class MicropostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
def create
#micropost = current_user.microposts.build(micropost_params)
if #micropost.save
flash[:success] = 'Micropost created!'
redirect_to root_url
else
render 'static_pages/home'
end
end
def destroy
end
private
def micropost_params
params.require(:micropost).permit(:content)
end
end
Content of routes.rb:
Rails.application.routes.draw do
root 'static_pages#home'
get '/help' => 'static_pages#help'
get '/about' => 'static_pages#about'
get '/contact' => 'static_pages#contact'
get '/signup' => 'users#new'
get '/login' => 'sessions#new'
post '/login' => 'sessions#create'
delete '/logout' => 'sessions#destroy'
resources :users
resources :account_activations, only: [:edit]
resources :password_resets, only: [:new, :create, :edit, :update]
resources :microposts, only: [:create, :destroy]
end
Any help is appreciated.
Thanks
Edit:
In micropost_controller_test.rb, it should've been:
class MicropostsControllerTest < ActionDispatch::IntegrationTest
instead of
class MicropostControllerTest < ActionCntroller::TestCase
The problem with the first test is that you dont have a route in the controller /microposts" on which the test is trying to hit and if you see your routes file it confirms resources :microposts, only: [:create, :destroy], so no route for that. Secondly after destroy it should redirect and in your controller method no redirection is there. So eventually test fails.
In micropost_controller_test.rb, it should've been:
class MicropostsControllerTest < ActionDispatch::IntegrationTest
instead of
class MicropostControllerTest < ActionCntroller::TestCase

Trouble with accessing controller action within nested route with RSpec

I'm working with the following route:
resources :groups, :only => [:show, :create, :update, :destroy] do
get 'viewmembers' => 'groups#view_members"
end
But when I'm trying to test in RSpec:
describe "GET #viewmembers" do
context "the group members are retrieved" do
before do
#group = FactoryGirl.create :group
..
get :viewmembers, group_id: #group.id
end
it { should respond_with :ok}
# others tests...
end
I get the following error:
Failure/Error: get 'viewmembers', group_id: #group.id
ActionController::UrlGenerationError:
No route matches {:action=>"viewmembers", :controller=>"api/v1/groups", :group_id=>"1"}
Is there a different way I should be formulating the GET request in my Rspec test?
Here is what's listed in rake routes:
GET /api/v1/groups/:group_id/viewmembers(.:format) api/v1/group#viewmembers{:format=>:json}`
If i take the get 'viewmembers'... route OUT of the nested do-end block for the group resource like so:
resources :groups, :only => [:show, :create, :update, :destroy]
get '/groups/viewmembers' => 'groups#view_members'
then the rspec test runs fine, so it has to be a problem with the way I'm formulating the request...

Rspec fails with ActionController::UrlGenerationError

Rspec fails with ActionController::UrlGenerationError with a URL I would think is valid. I've tried messing with the params of Rspec request, as well as fiddled with the routes.rb, but I'm still missing something.
The weird thing is, it works 100% as expected when testing locally with curl.
Error:
Failure/Error: get :index, {username: #user.username}
ActionController::UrlGenerationError:
No route matches {:action=>"index", :controller=>"api/v1/users/devices", :username=>"isac_mayer"}
Relevant code:
spec/api/v1/users/devices_controller_spec.rb
require 'rails_helper'
RSpec.describe Api::V1::Users::DevicesController, type: :controller do
before do
#user = FactoryGirl::create :user
#device = FactoryGirl::create :device
#user.devices << #device
#user.save!
end
describe "GET" do
it "should GET a list of devices of a specific user" do
get :index, {username: #user.username} # <= Fails here, regardless of params. (Using FriendlyId by the way)
# expect..
end
end
end
app/controllers/api/v1/users/devices_controller.rb
class Api::V1::Users::DevicesController < Api::ApiController
respond_to :json
before_action :authenticate, :check_user_approved_developer
def index
respond_with #user.devices.select(:id, :name)
end
end
config/routes.rb
namespace :api, path: '', constraints: {subdomain: 'api'}, defaults: {format: 'json'} do
namespace :v1 do
resources :checkins, only: [:create]
resources :users do
resources :approvals, only: [:create], module: :users
resources :devices, only: [:index, :show], module: :users
end
end
end
Relevant line from rake routes
api_v1_user_devices GET /v1/users/:user_id/devices(.:format) api/v1/users/devices#index {:format=>"json", :subdomain=>"api"}
The index action requires a :user_id parameter, but you haven't supplied one in the params hash. Try:
get :index, user_id: #user.id
The error message is a bit confusing, because you aren't actually supplying a URL; instead you are calling the #get method on the test controller, and passing it a list of arguments, the first one is the action (:index), and the second is the params hash.
Controller specs are unit tests for controller actions, and they expect that the request parameters are correctly specified. Routing is not the responsibility of the controller; if you want to verify that a particular URL is routed to the right controller action (since as you mention, you are using friendly-id), you may want to consider a routing spec.

rspec ActionController::UrlGenerationError for create action in namespaced admin that is nested

I'm getting this error in my controller spec for my create action
No route matches {:action=>"create", :assessment=>{:course_id=>"1", :curriculum_id=>"1"}, :controller=>"admin/assessments"}
Here is my controller spec:
it "sets the flash success" do
set_current_admin
course = Fabricate(:course)
post :create, assessment: { course_id: course.id, curriculum_id: course.curriculum.id }
expect(flash[:success]).not_to be_blank
end
The error occurs on the post :create.. line.
Here is my create action for assessments:
def create
#assessment = Assessment.new(assessment_params.merge!(course_id: Course.find(params[:course_id]).id))
if #assessment.save
flash[:success] = "You have created your assessment."
redirect_to curriculum_course_assessment_path(#assessment.course.curriculum, #assessment.course, #assessment)
else
...
end
end
And, here is my routing for the assessments:
resources :curriculums, only: [:index, :show] do
resources :courses, only: [:show] do
resources :assessments, only: [:show]
namespace :admin do
resources :assessments, only: [:index, :new, :create, :edit, :update]
end
end
end
Here is the line from my rake routes...
POST /curriculums/:curriculum_id/courses/:course_id/admin/assessments(.:format) admin/assessments#create
When I actually run the create action in the browser, it works fine, so I'm guessing that it's a problem with my spec's syntax. Any advice on this would be much appreciated. (I realize that I'm double-nesting by resources here which is not generally the best practice, but I couldn't find any other way to access the curriculums & courses params.)
You're nesting the course_id and curriculum_id parameters inside an assessment hash - in the route, they are not nested.

Rails 4 routing in rspec for testing PATCH

This is my routes.rb
resources :agreements, param: :customer_number, only: :show, as: 'agreement_from_customer_number'
resources :agreements, only: [:index, :update] do
resources :orders, only: :index
end
This is my AgreementsController
def update
...
end
And this is my rspec test for testing the update action:
before :each do
#agreement = create :agreement
end
it 'updates the correct agreement through the internal API' do
patch agreement_path(#agreement)
end
My application uses ActiveModel, so it has no database. Because of this, the createmethod in the before block simply does the following:
factories/agreement.rb
factory :agreement do
skip_create
id '1'
customer_number '1001'
agreement_id '101'
return_address 'Fakeville, USA'
return_postal_code '2013'
end
The test returns the following error:
ActionController::UrlGenerationError:
No route matches {:id=>#<Agreement:0x007f94ab7b0ce0 #id="1", #agreement_id="101", #customer_number="1001", #return_address="Fakeville, USA", #return_postal_code="2013">} missing required keys: [:id]
And if I change the test to look like this:
it 'updates the correct agreement through the internal API' do
patch agreement_path(#agreement.id)
end
I get the following error:
ActionController::UrlGenerationError:
No route matches {:controller=>"agreements", :action=>"/agreements/1"}
Help :-|
I solved this particular problem by doing the following:
patch :update, agreement: #agreement.attributes
Because the controller expects a hash of values rather than an object literal, this worked :-)

Resources