RSpec code example basics with CRUD method calls - ruby-on-rails

After generate rspec:install in a Rails 3 project any new scaffolding will include some default specs. I'm confused about the get, post, put & delete methods and what they're actually being called on?
specifically, in this example, the line delete :destroy, :id => "1" is being called on what exactly? the controller? but the controller doesn't have a 'delete' method...though it does have destroy. but calling 'delete' on it shouldn't do anything so passing :destroy as an argument is meaningless... how does this work?
Here's a portion of the generated specs for a resources_controller. I've left out, but the same thing exists for put :update and post :create and get :edit, :show, :new & :index
#app/controllers/resources_controller.rb
describe ResourcesController do
def mock_resource(stubs={})
#mock_resource ||= mock_model(Resource, stubs).as_null_object
end
...
describe "DELETE destroy" do
it "destroys the requested resource" do
Resource.stub(:find).with("37") { mock_resource }
mock_resource.should_receive(:destroy)
delete :destroy, :id => "37"
end
it "redirects to the resources list" do
Resource.stub(:find) { mock_resource }
delete :destroy, :id => "1"
response.should redirect_to(resources_url)
end
end
end

get, post, put, and delete are the HTTP verbs used in the request. See: http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods
And yes, the following argument is the action being called on your controller, :update, :create, etc.

When you write controller specs, RSpec includes the ControllerExampleGroup module, which "extends ActionController::TestCase::Behavior to work with RSpec.".
ActionController::TestCase::Behavior is where these methods are defined.

Related

Rspec no route matches polymorphic

I am trying to test my polymorphic comments create action but I always get no route matches error in rspec.
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
#comment = #commentable.comments.new(comment_params)
#comment.user_id = current_user.id
#comment.save
redirect_to :back, notice: "Your comment was successfully posted."
end
private
def comment_params
params.require(:comment).permit(:body)
end
end
Rspec
describe "POST #create" do
context "with valid attributes" do
before do
#project = FactoryGirl.create(:project)
#comment_attr = FactoryGirl.build(:comment).attributes
end
it "creates a new comment" do
expect{
post :create, params: { project_id: #project, comment: #comment_attr }
}.to change(Comment, :count).by(1)
end
end
end
I am using this approach for testing create action in my another controllers and there is everything good, but here from some reason is throws error. I think my error is in line where I pass params to post create action but I do not see error.
UPDATE
resources :projects do
resources :comments, module: :projects
resources :tasks do
resources :comments, module: :tasks
end
end
UPDATE 2
Failure/Error: post :create, params: { project_id: #project,
commentable: #project, comment: #comment_attr }
ActionController::UrlGenerationError:
No route matches {:action=>"create", :comment=>{"id"=>nil, "commentable_type"=>nil, "commentable_id"=>nil, "user_id"=>nil,
"body"=>"MyText", "created_at"=>nil, "updated_at"=>nil,
"attachment"=>nil}, :commentable=>#, :controller=>"comments", :project_id=>#}
I think that your controller namespace does not match the routes you define. The controller is defined to not be nested (CommentsController) whereas the corresponding routes are nested and inside a module projects as well. Nesting the routes will have no influence on the controller ActionDispatch is looking for. But defining a module for the route will lead to rails expecting a controller inside the module namespace. In your case that would be Projects::CommentsController and Tasks::CommentsController. Please see "2.6 Controller Namespaces and Routing" of "Rails Routing from the Outside In" for further details.
I added your routes to a brand new rails project and ran rails routes. The output is:
Prefix Verb URI Pattern Controller#Action
project_comments GET /projects/:project_id/comments(.:format) projects/comments#index
POST /projects/:project_id/comments(.:format) projects/comments#create
...
You can therefor either remove the module definition from your routes
resources :projects do
resources :comments
resources :tasks do
resources :comments
end
end
or nest the controllers inside in a project/task namespace. Given that you want polymorphism for your comments, I would advise using the first option.

Rspec controller action not found

test is failing becasue it says the action does not exist, when it clearly does. Is it becasue it is a nested route? Any thoughts?
Update:
I moved resources :orders outside of the nested route and tests passed. So it has something to do with it being nested.
OrderController
def index
if current_printer
#orders = Order.all
#printer = Printer.find(params[:printer_id])
end
if current_user
#orders = Order.where(user_id: params[:user_id])
end
end
OrdersController Spec
require 'rails_helper'
RSpec.describe OrdersController, :type => :controller do
describe "unauthorized user" do
before :each do
# This simulates an anonymous user
login_with_user nil
binding.pry
end
it "should be redirected back to new user session" do
get :index
expect( response ).to redirect_to( new_user_session_path )
end
end
end
Routes
resources :users, only: [:index, :show] do
resources :orders
end
Error
Failures:
1) OrdersController unauthorized user should be redirected back to new user session
Failure/Error: get :index
ActionController::UrlGenerationError:
No route matches {:action=>"index", :controller=>"orders"}
When testing controllers that have nested routes you must pass in a hash of the url params.
for example my routes looked like this
user_orders GET /users/:user_id/orders(.:format) orders#index
so in my test I passed in a hash with user_id
get :index, { user_id: 1 }
Tests passing :)

Rails: Point several nested routes to one customer controller action

How do you point different nested routes to one controller action?
A user can be a member of several groups like company, project, group ect. for which It can request to join, leave or be removed by an admin.
I want to access the remove action for several models and destroy the belongs_to record in the profile model
I already have a polymorphic model that takes requests from a profile to a model( e.g. company) and upon acceptance of the request the profile will belong to the model. once the request is accepted the request recored is destroyed. I feel that the remove action that will destroy the relationship between the profile and the model should be part of the requests_controller, but I guess could be part of the profile_controller.
What I'm thinking I need to end up with is either
/_model_/:id/profile/:id/remove
/company/:id/profile/:id/remove
but how do I get this to point the remove action in my requests controller
or
/_model_/:id/requests/remove
/company/:id/request/remove
I am using the following code in my routes
resources :companies do
resource :requests do
put 'remove', :on => :member
end
resources :requests do
put 'accept', :on => :member
end
end
This is producing a double up of the routes
remove_company_requests PUT /companies/:company_id/requests/remove(.:format)
company_requests POST /companies/:company_id/requests(.:format)
new_company_requests GET /companies/:company_id/requests/new(.:format)
edit_company_requests GET /companies/:company_id/requests/edit(.:format)
GET /companies/:company_id/requests(.:format)
PUT /companies/:company_id/requests(.:format)
DELETE /companies/:company_id/requests(.:format)
accept_company_request PUT /companies/:company_id/requests/:id/accept(.:format)
GET /companies/:company_id/requests(.:format)
POST /companies/:company_id/requests(.:format)
new_company_request GET /companies/:company_id/requests/new(.:format)
edit_company_request GET /companies/:company_id/requests/:id/edit(.:format)
company_request GET /companies/:company_id/requests/:id(.:format)
PUT /companies/:company_id/requests/:id(.:format)
DELETE /companies/:company_id/requests/:id(.:format)
As
My I suggest that you create a new controller to handle this? The advantage is that you can map the route to this controller on any models you want the "remove association" on.
For example:
# RemoveController.rb
class RemoveController < ApplicationController
def destroy
# inplement the logic for deletion. You can use refection to implement
# this function only once for all the applied associations.
end
end
# routes.rb
resources :companies do
resource :requests do
resource :remove, :controller => :remove, :only => [:destroy]
end
end
The above routes would generate:
company_requests_remove DELETE /companies/:company_id/requests/remove(.:format) remove#destroy
You can nest the above line for the remove controller on any nested routes you want and they will all point back to the RemoteController's destroy object, only with different parameters to help you implement the destroy action.
Edit: to add create for specific relationship that you don't want to duplicate you can do this:
# routes.rb
resources :companies do
resource :requests do
resource :remove, :controller => :relationship, :only => [:destroy]
resource :create, :controller => :relationship, :only => [:create]
end
end
company_requests_remove DELETE /companies/:company_id/requests/remove(.:format) relationship#destroy
company_requests_create POST /companies/:company_id/requests/create(.:format) relationship#create
But I think you might need to be careful about breaking the convention of create in the respective controller. I'm not sure if there are any downside to this. The remove part since is only removing association and not the records itself, it doesn't seem to break the convention.
Try
puts 'remove', :on => :member, :controller => :requests, :action => :remove

how to TDD deleting a user in devise

I setup Devise so I can write controller specs with this.
Then I setup Devise so users cannot delete their accounts.
Now I want to write a spec to make sure the controller is unable to call the destroy action on the Devise user. How do I write this?
In my controller the Devise part looks like this
devise_for :users, skip: :registrations do
resource :registration,
only: [:new, :create, :edit, :update],
path: 'users',
path_names: { new: 'sign_up' },
controller: 'devise/registrations',
as: :user_registration do
get :cancel
end
end
In my spec I'm trying to do the following but it doesn't work. I'm not even sure I'm writing it right. I think the page I'm trying to access is wrong.
describe UsersController do
login_user # from devise controller helper
it "does not allow deleting of user" do
get :users, :method => :delete
# assert here user was not deleted
end
end
I think what you really want to test is whether or not the route exists for the registrations#destroy action. If there is no route, then the action will not be called since it can't be routed to the controller.
For a destroy action, we need to try to route a DELETE action to the users path. so, something like this might do the trick:
{ :delete=> "/users" }.should_not be_routable
Test syntax pulled from a similar answer here:
Rails RSpec Routing: Testing actions in :except do NOT route
Your mixing your http verbs for one thing. You should be doing
delete :destroy, id: #user
Your going to have to get #user from somewhere, I have it set by controller macros personally.
Then you can either check the response header for unsuccessful, or more easily
#user.should exist
I would put the following in my controller spec when testing this kind of thing (although i'd use FactoryGirl to create my test user):
it "does not allow deletion of a user" do
user = User.create!([insert valid args here])
expect {
delete :destroy, id: user
}.not_to change(User, :count)
end

Rails routing: override the action name in a resource/member block

I know how to do this with match but I really want to do it in the resource block. Here's what I have (simplified):
resources :stories do
member do
'put' collaborator
'delete' collaborator
end
end
I am looking for a way to allow the same action name in the URL but have different entry points in the controller. At the moment I've got in my controller:
# PUT /stories/1/collaborator.json
def add_collaborator
...
end
# DELETE /stories/1/collaborator.json
def remove_collaborator
...
end
So I tried this:
resources :stories do
member do
'put' collaborator, :action => 'add_collaborator'
'delete' collaborator, :action => 'remove_collaborator'
end
end
but this didn't seem to work when I wrote an rspec unit test:
describe "PUT /stories/1/collaborator.json" do
it "adds a collaborator to the story" do
story = FactoryGirl.create :story
collaborator = FactoryGirl.create :user
xhr :put, :collaborator, :id => story.id, :collaborator_id => collaborator.id
# shoulds go here...
end
end
I get this error:
Finished in 0.23594 seconds
5 examples, 1 failure, 1 pending
Failed examples:
rspec ./spec/controllers/stories_controller_spec.rb:78 # StoriesController PUT
/stories/1/collaborator.json adds a collaborator to the story
I'm assuming this error is because the way I'm trying to define my routes is incorrect...any suggestions?
Is the following better?
resources :stories do
member do
put 'collaborator' => 'controller_name#add_collaborator'
delete 'collaborator' => 'controller_name#remove_collaborator'
end
end
You should also check your routes by launching in a terminal:
$ rake routes > routes.txt
And opening the generated routes.txt file to see what routes are generated from your routes.rb file.

Resources