How to test controllers with nested routes using Rspec? - ruby-on-rails

I have 2 controllers that I created using scaffold generator of rails. I wanted them to be nested in a folder called "demo" and so ran
rails g scaffold demo/flows
rails g scaffold demo/nodes
Then I decided to nest nodes inside flows, and changed my routes file like so:
namespace :demo do
resources :flows do
resources :nodes
end
end
But this change resulted on the rspec tests for nodes breaking with ActionController::Routing errors.
15) Demo::NodesController DELETE destroy redirects to the demo_nodes list
Failure/Error: delete :destroy, :id => "1"
ActionController::RoutingError:
No route matches {:id=>"1", :controller=>"demo/nodes", :action=>"destroy"}
The problem is rspec is looking at the wrong route. It's supposed to look for "demo/flows/1/nodes". It also needs a mock model for flow, but I am not sure how to provide that. Here is my sample code from generated rspec file:
def mock_node(stubs={})
#mock_node ||= mock_model(Demo::Node, stubs).as_null_object
end
describe "GET index" do
it "assigns all demo_nodes as #demo_nodes" do
Demo::Node.stub(:all) { [mock_node] }
get :index
assigns(:demo_nodes).should eq([mock_node])
end
end
Can someone help me understand how I need to provide the flow model?

You have two different questions going on here, so you may want to split them up since your second question has nothing to do with the title of this post. I would recommend using FactoryGirl for creating mock models https://github.com/thoughtbot/factory_girl
Your route error is coming from the fact that your nested routes require id's after each one of them like this:
/demo/flows/:flow_id/nodes/:id
When you do the delete on the object, you need to pass in the flow ID or else it won't know what route you are talking about.
delete :destroy, :id => "1", :flow_id => "1"
In the future, the easiest way to check what it expects is to run rake routes and compare the output for that route with what you params you are passing in.
demo_flow_node /demo/flows/:flow_id/nodes/:id(.:format) {:action=>"destroy", :controller=>"demo/flows"}

Related

Rails getting routes name in Route.rb with controller actions

I'm a real beginner of rails.
Can I get multiple routes from one controller + many actions?
For example,
resources :something
get "something#index", "something#show", "something#update"...etc.
I'm just curious if there is a command to get route name from the actions.
For example, in a controller named "pledges",
class PledgesController < ApplicationController
def home
end
def abc
end
def defg
end
def hijk
end
end
Can any commands get "pledges#home", "pledges#abc", "pledges#defg","pledges#hijk" ?
To add custom, "non-RESTful" routes to a resource, you could do the following:
resources :pledges do
collection do
get :foo
end
member do
put :bar
end
end
collection-defined routes will produce results against Pledge as a whole – think the index route.
member-defined routes will produce results against an instance of Pledge – think the show route.
This would produce the following routes for you:
foo_pledges GET /pledges/foo(.:format pledges#foo
bar_pledge PUT /pledges/:id/bar(.:format) pledges#bar
pledges GET /pledges(.:format) pledges#index
POST /pledges(.:format) pledges#create
new_pledge GET /pledges/new(.:format) pledges#new
edit_pledge GET /pledges/:id/edit(.:format) pledges#edit
pledge GET /pledges/:id(.:format) pledges#show
PATCH /pledges/:id(.:format) pledges#update
PUT /pledges/:id(.:format) pledges#update
DELETE /pledges/:id(.:format) pledges#destroy
You will have to define all of the custom actions, if there are not restful (but I would highly recommend that you follow the rest conventions). For example:
get 'pledges' => 'abc'
post 'pledges' => 'defg'
put 'pledges' => 'hijk

Devise: path helper for a resource within two scopes

I'm trying to achieve the following:
I have two entry points to the application (/admin and /sales)
I have generated a resource (rails generate scaffold) which is called customers. Admin and sales users will need to access this resource from both routes. eg. /admin/customers and /sales/customers.
This is working so far, here comes the actual problem. Although I'm logged in as a sales user, the path helpers (for example new_customer_path) all point to /admin/*
My routes.rb file:
authenticated :user, ->(u) { u.role == 'admin' } do
scope '/admin' do
resources :customers
root 'default#index', as: :admin_authenticated_root
end
end
authenticated :user, ->(u) { u.role.start_with? 'sales' } do
scope '/sales' do
resources :customers
root 'default#index', as: :sales_authenticated_root
end
end
With this I end up with the following routes.
customers GET /admin/customers(.:format) customers#index
POST /admin/customers(.:format) customers#create
new_customer GET /admin/customers/new(.:format) customers#new
edit_customer GET /admin/customers/:id/edit(.:format) customers#edit
customer GET /admin/customers/:id(.:format) customers#show
PATCH /admin/customers/:id(.:format) customers#update
PUT /admin/customers/:id(.:format) customers#update
DELETE /admin/customers/:id(.:format) customers#destroy
GET /sales/customers(.:format) customers#index
POST /sales/customers(.:format) customers#create
GET /sales/customers/new(.:format) customers#new
GET /sales/customers/:id/edit(.:format) customers#edit
GET /sales/customers/:id(.:format) customers#show
PATCH /sales/customers/:id(.:format) customers#update
PUT /sales/customers/:id(.:format) customers#update
DELETE /sales/customers/:id(.:format) customers#destroy
I really need to find a proper solution, since updating all the code generated by the scaffold generator doesn't seem feasible to me.
There must be a better way than different path helpers for each scope. I don't want to do something like this (in every generated file):
`send("#{current_user.role}_customers_path")`
I was under the impression, that the admin routes would not even be loaded when I'm logged in as a sales user, but I just started out with working with devise, so I have only very little knowledge about it.
Edit:
I guess I could just create a helper and override the path helpers rails is providing:
def customers_path
if current_user.role == 'sales'
sales_customers_path
end
end
Try this:
scope '/admin', as: :admin do
resources :customers
root 'default#index', as: :authenticated_root
end
and the same for /sales scope.
UPDATE: Oh, I see it now, you actually don't want 2 different paths, but instead generic helper that would give different values depending on current_user value. Sorry, routes don't work this way, they get loaded (all of them!) at the application boot time (that's why you can't "delete" some routes depending on your constraint during the request) and first matched one from top to bottom is taken.
Yes, your solution in EDIT section is one way to go, but I'd rather used my custom helper for that, because otherwise it would be great surprise for newcomer in your project why the same url_helper produces different urls, until he finally stumbles to your overriden helper.

Action could not be found in Rspec test for nested route

I am trying to get a handle on how nested routes work with Rspec. I have one of these:
class SupportController < ResourceController
# stuff happens
def support_override
customer = Customer.find_by_id(params[:id])
customer.override( params[:override_key] )
redirect_to("support")
end
end
We have a route:
resources :support do
member do
# loads of paths
get 'support_override/:override_key' => 'support#support_override'
end
end
And the route passes a test:
it "should route GET support/1/support_override/ABCDEF to suport#support_override" do
{ get: '/support/1/support_override/ABCDEF'}.should route_to(controller: 'support', action: 'support_override', id: '1', override_key: 'ABCDEF' )
end
However when I try to test the logic in rspec:
describe SupportController do
# various levels of context and FactoryGirl calls
it "can reach override url" do
get :support_override, { :id=> #customer.id, :override_key="123" }
response.should redirect_to("support")
end
end
I get the following response:
Failure/Error: Unable to find matching line from backtrace
AbstractController::ActionNotFound:
The action 'support_override' could not be found for SupportController
I have no doubt that the problem is with my understanding of how rspec works with nested routes, but I can't see any way to figure out what path Rspec is actually seeking and consequently it's hard to know what I need to change and I'm having trouble locating the relevant documentation.
Is there a way to find what path is being created by the test or can anyone offer guidance on how exactly the path creation works in this situation?
Since, you haven't shared the complete SupportController code, I cannot pin-point exact error. BUT there are two possibilities:
You have defined support_override under private/protected by mistake.
You have closed the class SupportController before support_override method definition, by mistake
Your action must always be public so that its accessible.

RSpec controller testing with non-RESTful route redirects

I'm trying to test some basic aspects of a controller that is reached via a nonstandard set of routes. I can't seem to connect to the appropriate controller/action in my LessonsController, which is reached via redirected routes that are meant to appear to lead to the CoursesController.
When I run my specs, I either get a routing error or the response comes back empty and I'm not sure how to parse it for useful nuggets.
# app/controllers/lessons_controller.rb
def index
... set some instance vars ...
end
# The CoursesController has index and show methods of its own which aren't used here
# app/config/routes.rb
...
get 'courses/:course_name' => redirect('/courses/%{course_name}/lessons'), :as => "course"
get 'courses/:course_name/lessons' => 'lessons#index', :as => "lessons"
...
# spec/controllers/courses_controller_spec.rb
describe CoursesController do
it "test some instance vars" do
get :show, :course_name => Course.first.title_url
assigns(:some_ivar).should_not be_empty
end
end
The error:
AbstractController::ActionNotFound: The action 'course' could not be found for CoursesController
RSpec attempt #2:
# spec/controllers/courses_controller_spec.rb
...
get :course, :course_name => Course.first.title_url
...
The attempt #2 error:
NoMethodError: undefined method `empty?' for nil:NilClass
If I run similar trial-and-error approaches by instead starting with the lessons_controller_spec.rb file (e.g. trying get :index there), I get similar errors. There is no direct route set up for lessons#index, only the redirects.
The response object in my second example is enormous (though the body is empty) so I won't include it unless someone thinks it's useful.
I'm definitely regretting the non-RESTful architecture, but given what it is, is there any idea how to get the controller spec to target the appropriate action inside the LessonsController?
Rails 3.2.12, RSpec 2.14.4, Capybara 2.0.2
Short answer: No.
Actually there are two types of get available in tests.
One type is for controller testing. This get can only accept argument as "action", say :index, :show etc. So you can only use it within current controller test. (Doc here: http://api.rubyonrails.org/classes/ActionController/TestCase/Behavior.html#method-i-get)
The other type is for integration testing. This get can accept any path as argument. http://api.rubyonrails.org/classes/ActionDispatch/Integration/RequestHelpers.html#method-i-get
The two types have same name get but usage is different.
What in your question is controller testing. So you are use the first one. You can only reach actions inside CoursesController. That's why you meet error.
I strongly recommend you to revise the routes right now. It's not about RESTful or not, but your routes break conversion all the day. What's the point the path is lesson, but controller is course? And why you write a Course controller when there is no route for him?

Rails route parent ID name and CanCan issue

I'm trying to get a simple route working
/agenda_items/5/feed
To do this, I have the following route setup
resources :agenda_items do
member do
get "/feed", to: "comments#feed"
end
end
In each of my controllers, I'm using CanCan to handle the authentication and it works fine, however on this one action I'm having an issue, which I'm pretty sure is down to railsnaming generation. When I runrake routes`, the route above is produced as
feed_agenda_item /agenda_items/:id/feed(.:format) agenda_items/:id#feed
As far as I can tell, CanCan is expecting the :id parameter, to actually be :agenda_item_id so as a result, my parent resource isn't being loaded.
Is there any way I can get rails to change this so that CanCan will work without me having to manually load and authorize the resource, or is there a way I can get CanCan to change what it's looking for on certain actions?
The problem is that your routes are wrong. You try to create a member action for agenda items which routes to the comments controller. If you want a feed of all the commments from a single agenda item you should do something like this:
resources :agenda_items do
resources :comments do
collection do
get :feed
end
end
end
You should now get the following when running rake routes:
feed_agenda_item_comments /agenda_items/:agenda_item_id/feed(.:format) comments#feed

Resources