Anonymous controller in Minitest w/ Rails - ruby-on-rails-3

While converting from RSpec to Minitest I ran into a slight issue that Google has not helped with one bit, and that's figuring out how to do something like this:
describe ApplicationController do
controller do
def index
render nothing: true
end
end
it "should catch bad slugs" do
get :index, slug: "bad%20slug"
response.code.should eq("403")
end
end
with Minitest. Is there a way to create anonymous controllers like this inside of Minitest or is there documentation that could help me learn how to test controllers with minitest?

You can do something like that:
# Add at runtime an action to ApplicationController
ApplicationController.class_eval do
def any_action
render :nothing
end
end
# If disable_clear_and_finalize is set to true, Rails will not clear other routes when calling again the draw method. Look at the source code at: http://apidock.com/rails/v4.0.2/ActionDispatch/Routing/RouteSet/draw
Rails.application.routes.disable_clear_and_finalize = true
# Create a new route for our new action
Rails.application.routes.draw do
get 'any_action' => 'application#any_action'
end
# Test
class ApplicationControllerTest < ActionController::TestCase
should 'do something' do
get :any_action
assert 'something'
end
end

I don't think anonymous controllers are supported. Instead of using a DSL to create a controller, try defining a controller in your test.
class SlugTestController < ApplicationController
def index
render nothing: true
end
end
describe SlugTestController do
it "should catch bad slugs" do
get :index, slug: "bad%20slug"
response.code.must_equal "403"
end
end

Related

how to stub or mock authlogic current_user in view specs

i am trying to write some views specs for my rails app, but i stumble on this error:
ActionView::Template::Error:
undefined local variable or method `current_user' for #<#<Class:0x007fa47d2612d0>:0x007fa47e267710>
Here is how i wrote my view spec :
describe "/newsletters/index.html.erb" do
include NewslettersHelper
include Authlogic::TestCase
def current_user(stubs = {})
#current_user ||= mock_model(User, stubs)
end
def user_session(stubs = {}, user_stubs = {})
#current_user_session ||= mock_model(UserSession, {:user => current_user(user_stubs)}.merge(stubs))
end
def login(session_stubs = {}, user_stubs = {})
UserSession.stub!(:find).and_return(user_session(session_stubs, user_stubs))
end
def logout
#user_session = nil
end
context "without a logged-in user" do
before(:each) do
activate_authlogic
logout()
assigns[:newsletters] = #newsletters = [ mock_model(Newsletter, :titre => "value for titre",
:sommaire => "value for sommaire", :content => "value for content") ]
end
it "renders a list of newsletters" do
# pending("find how to mock authlogic current user in views spec")
render
rendered.should have_selector("tr>td") do |row|
row.should have_content("value for titre")
end
rendered.should have_selector("tr>td") do |row|
row.should have_content("value for sommaire")
end
rendered.should have_selector("tr>td") do |row|
row.should have_content("value for content")
end
end
end
Try controller.stub(:current_user) { mock_model(User) } I think it should help
The view spec is an isolated context so you need to stub the current_user method in the view context.
view.stub(:current_user).and_return(mock_model(User))
For further reading on the view spec I suggest you the view spec page on relish
None of the answers worked for me (using rspec 3.9 here), as I was getting errors like #<#<Class:0x007fb9ca387dc8> does not implement: current_user, trying to stub the view or controller objects, so I had to do it like:
before do
controller.singleton_class.class_eval do
# Just defining methods to being stubbed later
def current_user; end
def current_account; end
helper_method :current_user, :current_account
end
allow(controller).to receive(:current_user).and_return(user)
allow(controller).to receive(:current_account).and_return(account)
end
not the prettiest solution, but it worked.

In Rails 3 how could I create a db only contact form not using scaffold?

New to rails 3
I would like to create a contact form that people fill out, its saved to the db and then a thank you page comes up.
I would like to do this without scaffold so I can learn better, and I figure that by doing it this way it would be easer to setup so that people cannot try and look at other people's entries by modifying the url.
ideally it would keep their state in the session or cookie so that they would end up on the thanks page if they came back.
Have been trying to do this for about 3 days and reading/googling tons, but between the new routes redirect_to controller stuff in rails3 havn't managed to figure it out.
Routes.rb
Contact::Application.routes.draw do
resources :contactees, :only => [:new, :create]
# to make sure crud doesn't have the routest I don't want
get 'contactees/submitted'
root :to => 'contactees#new'
contactees_controller.rb
ContacteesController < ApplicationControler
def share
end
def new
#contactee = Contactee.new
end
def create
#contactee = Contactee.new(params[:contactee])
if #contactee.save
redirect_to submitted_contactee
else
render action: "new"
end
end
end
Views
contactees
_form.html.erb
new.html.erb
submitted.html.erb
Get rid of the submitted route, you don't need it. Perhaps something like this?
def new
render :submitted if session[:contacted]
end
def create
#contactee = Contactee.new(params[:contactee])
if #contactee.save
session[:contacted] = true
render :submitted
else
render action: "new"
end
end

TDD with RSpec and Rails: testing controller actions with no view

As I continue to learn my way around TDD with RSpec 2 and Rails 3.1, I can't seem to find a solution to this problem.
I have a Users controller with a new and create action. In my UsersController spec, I have
users_controller_spec.rb
describe "POST 'create'" do
before(:each) do
#attr = Factory.attributes_for(:user)
end
it "should assign an #user variable" do
post :create, :user => #attr
assigns[:user].should_not be_nil
assigns[:user].should be_kind_of(User)
end
end
and in my UsersController,
users_controller.rb
def create
#user = User.new(params[:user])
end
This spec is failing with
1) UsersController POST 'create' should assign an #user variable
Failure/Error: post :create, :user => #attr
ActionView::MissingTemplate:
I can continue to implement application code to get this test to pass, but I feel like this test should be passing as it is.
Any suggestions?
Your create method needs to do something. Either render a template or redirect. Since you're not telling it to redirect it's assuming that you want it to render a template but when it can't find a create.html.erb file it throws an error.
You're best bet is to do either this:
def create
#user = User.new(params[:user])
redirect_to root_url
end
or this:
def create
#user = User.new(params[:user])
render :nothing => true
end
To test rendering nothing you'll want:
expect(response).to render_template(nil)
I've come across this recently myself. It seems one possibility would be to rescue the error in your test.
it "should assign an #user variable" do
begin
post :create, :user => #attr
rescue ActionView::MissingTemplate
# This is okay because(/as long as) we test the render/redirect
# in a separate spec where we don't rescue this exception.
end
assigns[:user].should_not be_nil
assigns[:user].should be_kind_of(User)
end
I don't know how "correct" this solution is. On one hand, it definitely emphasizes the "testing one thing at a time" mentality, on the other, it seems kind of ugly.
Edit
I suppose you could make some nice wrappers in your spec helper, something like
def post?(action, params = {})
post action, params
rescue ActionView::MissingTemplate
end

Why is respond_with not returning json from my model?

Why doesn't respond_with respond with the json in this case? I'm invoking the action with an explicit .json (/tasks/4e3c1163a19d461203000106/items/4e4c27dfa19d46e0e400000a.json)
In my controller --
class Tasks::TasksController < Tasks::BaseController
respond_to :html, :js, :json
def update
#task = #taskgroup.update_task(params[:id], params[:task])
#taskgroup.save
respond_with #task
end
end
When I overrode to_json and added a breakpoint, it isn't hit. The response is:
{}
If I replace respond_with with an explicit call to to_json:
respond_with #task do |format|
format.json { render json: #task.to_json }
end
The response is perfect:
{
"_id":"4e4c27dfa19d46e0e400000a",
"assigned_to":null,
"comments" [{"_id":"4e4c2fd7a19d46e127000014",
[SNIP]
It works fine in the later case, but I'd like to figure out why the first one doesn't work. This happens for other controllers and models in my app. Not sure if its a mongoid thing? (rails 3.0.9 / mongoid 2.1.8)
Here is a monkeypatch I wrote to always respond_with what you tell it to do regardless of protocol. Be warned this does break RESTful best practices and if you respond_with in a RESTful manner then it may break. However if your JSON/XML responses are separate from the main application then it is useful and your other controllers will not break.
Usage, Include this in any controller to override the respond_with functionality.
class ApiController < BaseController
include ValidResponder
end
Then anything that extends ApiController will include this functionality.
Save the following in app/lib/valid_responder.rb:
#Override restful responses.
module ValidResponder
def self.included(base)
ActionController::Responder.class_eval do
alias :old_api_behavior :api_behavior
define_method :api_behavior do |error|
if controller.class.ancestors.include?(base)
raise error unless resourceful?
display resource
else
old_api_behaviour(error)
end
end
end
end
end
For reference, the actual method source is available here: http://api.rubyonrails.org/classes/ActionController/Responder.html#method-i-api_behavior
Ok look how it goes. When you call respond_with inside the update action then (if the object is valid) it will redirect to the show action (if you do not want this default behaviour you must provide location: "other_action" to respond_with).

How do I make a route to a custom controller action in Rails 3?

I'm new to Rails, and a bit confused about routes:
I have a Devices controller:
#devices_controllers.rb
class DevicesController < ApplicationController
def index
#devices = Device.all
end
def show
#device = Device.find(params[:id])
end
def new
#device = Device.new
end
def create
#device = Device.new(params[:device])
if #device.save
flash[:notice] = "Successfully created device."
redirect_to #device
else
render :action => 'new'
end
end
def edit
#device = Device.find(params[:id])
end
def update
#device = Device.find(params[:id])
if #device.update_attributes(params[:device])
flash[:notice] = "Successfully updated device."
redirect_to #device
else
render :action => 'edit'
end
end
def destroy
#device = Device.find(params[:id])
#device.destroy
flash[:notice] = "Successfully destroyed device."
redirect_to devices_url
end
def custom_action
"Success"
end
I'd like to access the "custom_action" action via a url like this:
http://foo.bar/devices/custom_action
I've added this line to my routes.rb file:
match 'devices/custom_action' => 'devices#custom_action'
However, when I try the URL in the browser, I get this error:
ActiveRecord::RecordNotFound in DevicesController#show
Couldn't find Device with ID=custom_action
It seems to be going to #show action instead of #custom_action. If a user id is not supplied, and I go to http://foo.bar/devices/custom_action, I'd like it to go #custom_action.
I've read Rails Routing from the Outside, but still can't still seem to figure out the problem.
I think the problem may be because of the order in which you have defined your routes.
I suspect you have resources :devices in your routes.rb. In addition, I suspect you have defined your custom route after this. If you type rake routes into your console/terminal, you will see that there is already a route defined for the following pattern:
GET /devices/:id
This route is a product of resources :devices, which is taking precedence over your custom route. Referring back to the Edge Guides, specifically in 1.1. Connecting URLs to Code, it states that the request will be dispatched to the first matching route. So a simple fix would be to define your custom route before resources :devices.