Mocking/Stubbing an Application Controller method with Mocha (Using Shoulda, Rails 3) - ruby-on-rails

While writing functional tests for a controller, I came across a scenario where I have a before_filter requesting some information from the database that one of my tests requires. I'm using Factory_girl to generate test data but I want to avoid hitting the database when its not explicitly needed. I'd also like to avoid testing my before_filter method here (I plan to test it in a separate test). As I understand, mocking/stubbing is the way to accomplish this.
My question is, what is the best way to mock/stub this method in this scenario.
My before filter method looks for a site in the db based on a subdomain found in the URL and sets an instance variable to be used in the controller:
#application_controller.rb
def load_site_from_subdomain
#site = Site.first(:conditions => { :subdomain => request.subdomain })
end
My controller that uses this method as a before_filter:
# pages_controller.rb
before_filter :load_site_from_subdomain
def show
#page = #site.pages.find_by_id_or_slug(params[:id]).first
respond_to do |format|
format.html { render_themed_template }
format.xml { render :xml => #page }
end
end
As you can see, it relies on the #site variable to be set (by the before_filter). During testing however, I'd like to have the test assume that #site has been set, and that it has at least 1 associated page (found by #site.pages). I'd like to then test my load_site_from_subdomain method later.
Here is what I have in my test (using Shoulda & Mocha):
context "a GET request to the #show action" do
setup do
#page = Factory(:page)
#site = Factory.build(:site)
# stub out the #page.site method so it doesn't go
# looking in the db for the site record, this is
# used in this test to add a subdomain to the URL
# when requesting the page
#page.stubs(:site).returns(#site)
# this is where I think I should stub the load_site_from_subdomain
# method, so the #site variable will still be set
# in the controller. I'm just not sure how to do that.
#controller.stubs(:load_site_from_subdomain).returns(#site)
#request.host = "#{ #page.site.subdomain }.example.com"
get :show, :id => #page.id
end
should assign_to(:site)
should assign_to(:page)
should respond_with(:success)
end
This leaves me with an error in my test results telling me that #site is nil.
I feel like I'm going about this the wrong way. I know it would be easy to simply just Factory.create the site so it exists in the db, but as I said earlier, I'd like to reduce the db usage to help keep my tests speedy.

Try stubbing out 'Site.first' since it the the setting of the #site var that you need to stub and not the returned var from the before_filter.

The reason why your #site is nil because your load_site_from_subdomain does the value assignment for #site -- it does not return any value hence your stubbing for load_site_from_subdomain simply doesn't assign the value to #site. There are two work-arounds for this:
First way:
Change load_site_from_subdomain to just do a return value:
def load_site_from_subdomain
Site.first(:conditions => { :subdomain => request.subdomain })
end
and then remove the before_filter :load_site_from_subdomain and change your show to:
def show
#site = load_site_from_subdomain
#page = #site.pages.find_by_id_or_slug(params[:id]).first
respond_to do |format|
format.html { render_themed_template }
format.xml { render :xml => #page }
end
end
And then do the stubbing in the test:
#controller.stubs(:load_site_from_subdomain).returns(#site)
that ensure our #site is stubbed indirectly via load_site_from_subdomain
Second way
To stub the Site.first, I do not really like this approach as in functional test, we do not really care about how the model is retrieved but the behaviour of the respond. Anyway, if you feel like going this path, you could stub it out in your test:
Site.stubs(:first).returns(#site)

Related

What's the difference between using render instead of respond_with/to in a Rails API?

I am building a simple rails tutorial on how to build APIs for some students and I am building it without the respond_to and respond_with because I just want to see if I can build an api without using a gem. This is what I have and my tests pass:
controller:
class Api::V1::SuyasController < ApplicationController
def index
render json: Suya.all
end
def create
render json: Suya.create(suyas_params)
end
private
def suyas_params
params.require(:suya).permit(:meat, :spicy)
end
end
routes:
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :vendors
resources :suyas
end
end
end
tests:
require 'test_helper'
class Api::V1::SuyasControllerTest < ActionController::TestCase
test "index can get all the suyas" do
Suya.create(meat: "beef", spicy: true)
Suya.create(meat: "kidney", spicy: false)
get :index
suyas = JSON.parse(response.body)
assert_equal "beef", suyas[0]["meat"]
assert_equal true, suyas[0]["spicy"]
assert_equal "kidney", suyas[1]["meat"]
assert_equal false, suyas[1]["spicy"]
end
test "create can create a suya" do
assert_difference("Suya.count", 1) do
create_params = { suya: { meat: "beefy", spicy: true }, format: :json }
post :create, create_params
suya = JSON.parse(response.body)
assert_equal "beefy", suya["meat"]
assert_equal true, suya["spicy"]
end
end
end
What's the difference between using render vs respond_with? I can't find any answers. Is there even something that I am doing wrong? Why are there two ways to create APIs (respond_to/respond_with AND this way?)
-Jeff
render is part of Rails and it just renders whatever you say in whatever format you say. Typically a view, possibly a string, possibly a file.
A pretty low-level function that renders whatever you say making a few assumptions per conventions, like where to look for a view.
respond_to is a micro-DSL that allows you to respond differently to different formats being requested.
I. e. in a block with |format| call to format.json requires a block that will be executed on requests for JSON, otherwise will be a no-op (no operation). Also, if respond_to didn't execute any block, it responds with a generic 406 Not Acceptable (server cannot respond in any format acceptable by the client).
While it is possible to do if request.json?, it's not so readable and needs to explicitly specify when to respond with 406.
respond_with, formerly part of Rails, now (since 4.2) in a separate gem responders (for a reason), takes an object and uses it to construct a response (making a lot of assumptions, all of them can be given at controller declaration).
It makes code much shorter in typical use cases (i. e. some APIs). In not-so-typical use cases it can be customized to suit your needs. In highly unusual use cases it's of no use.
I may be overly simplifying things, it's a general overview.
There are two things :)..render and respond_to.
Render is used to create a full response and sends it back to the browser.
So render is used in respond_to ,to make your action very responsive for every call whether it can be js/ajax call,full page load(html),json(to show autosearch dropdown,tokens) or xml.So if i want my method to work and respond to every calls from client,i will use the below block in my action.
respond_to do |format|
format.html { redirect_to(person_list_url) }
format.js {render "show_person_details"}
format.xml { render :xml => #people.to_xml }
format.json { render json: #people}
end
above controller will work on every scenario,such as js/html/json and xml without getting 403 Forbidden error which we get usually get when a js call is made to action having only format.html and not format.js
HOPE IT HELPS
I think the answer is that render only allows me to respond with JSON, whereas if I use respond_to and respond_with, I can respond in more than just one manner? Is that all?

Rails 4 using controller concern to DRY update methods

I have a rails app where many of the models are editable using best_in_place, so I have a lot of controllers that look partially like this:
before_action :find_objects, except: [:new, :create]
def update
#object.update_attributes(object_params)
respond_to do |format|
format.json { respond_with_bip #object}
end
end
private
def object_params
params.require(:object).permit(:foo, :bar)
end
def find_objects
#object = Object.find(params[:id])
end
How do I move this particular repeated piece into a controller concern, given that the object being updated is going to come in with a particular name in the params hash, and object_params and find_objects should call their proper versions based on the model name? Is there some elegant meta-magic that'll sort this all out?
I think this is a case where your code could be "too DRY". You can certainly accomplish this using meta-magic, but it could make your code confusing in the long run.
If you want to do the meta-magic, one trick is to use params[:controller] to get the name of the model. For example, if you have a PostsController, then:
params[:controller] # => "posts"
params[:controller].classify # => "Post"
Taking this a step further, you could write a generic find_object like this:
def find_object
model_class = params[:controller].classify.constantize
model_instance = model_class.find(params[:id])
instance_variable_set("##{model_class.name.underscore}", model_instance)
end
But as I said at the beginning, I'm not sure I would recommend this amount of abstraction just for the sake of DRY-ing your controller code.

Rails ActiveRecord relations - avoiding writing .blank? checks

This is more a style question than anything.
When writing queries, I always find myself checking if the result of the query is blank, and it seems - I dunno, overly verbose or wrong in some way.
EX.
def some_action
#product = Product.where(:name => params[:name]).first
end
if there is no product with the name = params[:name], I get a nil value that breaks things.
I've taken to then writing something like this
def some_action
product = Product.where(:name -> params[:name])
#product = product if !product.blank?
end
Is there a more succinct way of handling nil and blank values? This becomes more of a headache when things rely on other relationships
EX.
def some_action
#order = Order.where(:id => params[:id]).first
# if order doesn't exist, I get a nil value, and I'll get an error in my app
if !#order.nil?
#products_on_sale = #order.products.where(:on_sale => true).all
end
end
Basically, is there something I haven;t yet learned that makes dealing with nil, blank and potentially view breaking instance variables more efficient?
Thanks
If its just style related, I'd look at Rails' Object#try method or perhaps consider something like andand.
Using your example, try:
def some_action
#order = Order.where(:id => params[:id]).first
#products_on_sale = #order.try(:where, {:onsale => true}).try(:all)
end
or using andand:
def some_action
#order = Order.where(:id => params[:id]).first
#products_on_sale = #order.andand.where(:onsale => true).andand.all
end
Well even if you go around "nil breaking things" in your controller, you'll still have that issue in your views. It is much easier to have one if statement in your controller and redirect view to "not found" page rather than having several ifs in your views.
Alternatively you could add this
protected
def rescue_not_found
render :template => 'application/not_found', :status => :not_found
end
to your application_controller. See more here: https://ariejan.net/2011/10/14/rails-3-customized-exception-handling

Simple respond_with in rails that avoids 204 from PUT

I want to PUT to rails and avoid getting a 204. I am using this pattern:
class SomeController < ApplicationController
respond_to :json
def update
# ...
respond_with(some_object)
end
end
However, when I do a put to update, I get a 204 back. I realize this is completely valid etc, but I explicitly want the content back. I can override it to some extent like this:
def update
respond_with(some_object) do |format|
format.json{render json: some_object}
end
end
but this seems a bit too hands-on for rails. Is there any more idiomatic way of avoiding a 204 and requesting the full content to be sent back? This is Rails 3.2.
In summary: I want maximally idiomatic rails that avoids a 204.
I made a custom responder which always returns my JSON encoded resource even on PUT/POST.
I put this file in lib/responders/json_responder.rb. Your /lib dir should be autoloaded.
module Responders::JsonResponder
protected
# simply render the resource even on POST instead of redirecting for ajax
def api_behavior(error)
if post?
display resource, :status => :created
# render resource instead of 204 no content
elsif put?
display resource, :status => :ok
else
super
end
end
end
Now, explicitly modify the controller which requires this behavior, or place it in the application controller.
class ApplicationController < ActionController::Base
protect_from_forgery
responders :json
end
You should now get JSON encoded resources back on PUT.
As a less invasive alternative, you can pass a json: option to the respond_with method invocation inside your controller update action, like this:
def update
# ...
respond_with some_object, json: some_object
end
Granted it seems a bit unDRY having to repeat the object twice in the arguments, but it'll give you what you want, the json representation of the object in the response of a PUT request, and you don't need to use the render json: way, which won't give you the benefits of responders.
However, if you have a lot of controllers with this situation, then customizing the responders, as jpfuentes2 showed in the accepted anwser, is the way to go. But for a quick single case, this alternative may be easier.
Source: https://github.com/plataformatec/responders/pull/115#issuecomment-72517532
This behavior seems intentional to fall in line with the HTTP spec, and "ideally" you should be firing off an additional GET request to see the results. However, I agree in the real world I'd rather have it return the JSON.
#jpfuentes2's solution above should do the trick (it's very similar to the pull request below), but I'm hesitant to apply anything that's patching rails internals, as it could be a real pain to upgrade between major versions, especially if you don't have tests for it (and let's face it, developers often skimp on controller tests).
References
https://github.com/rails/rails/issues/9862
https://github.com/rails/rails/pull/9887
Just to clarify, you do not need the responders gem to do this... You can just do:
config/initializers/responder_with_put_content.rb
class ResponderWithPutContent < ActionController::Responder
def api_behavior(*args, &block)
if put?
display resource, :status => :ok
else
super
end
end
end
and then either (for all updates actions to be affected):
class ApplicationController < ActionController::Base
def self.responder
ResponderWithPutContent
end
end
or in your action:
def update
foo = Foo.find(params[:id])
foo.update_attributes(params[:foo])
respond_with foo, responder: ResponderWithPutContent
end
What's wrong with simply doing:
def update
some_object = SomeObject.update()
render json: some_object
end
Not a big fan of this behavior. To get around it, I had to avoid using the respond_with method:
class SomeController < ApplicationController
respond_to :json
def update
# ...
respond_to do |format|
format.json { render(json: some_object, status: 200) }
end
end
end

Controller best practices: Multiple Methods or Multiple cases in Show

I'm frequently building controllers where i would like multiple methods
(in addition to index, edit, show, etc.). Most of the time the actions i
desire could be lumped into show as they are simple GET operations,
however I don't want to put too much logic in any one controller action.
Here is a quick example of two different ways to achieve the same
thing...
class TwitterFriendController < ApplicationController
## lump everything into show?
def show
if params[:id] == "follow"
users = current_user.following
elsif params[:id] == "follow_me"
users = current_user.users_who_follow_me
elsif params[:id] == "following_follow_me"
users = current_user.following_who_follow_me
elsif params[:id] == "following_who_do_not_follow_me"
users = current_user.following_who_do_not_follow_me
...
end
respond_with do |format|
format.json do {...}
end
end
## or split everything out into separate methods, this requires
additional routing
def following
...
end
def users_who_follow_me
...
end
def following_who_follow_me
...
end
def following_who_do_not_follow_me
...
end
end
Everything in show
a ton of logic in one method
DRY ? # lots of extra code needed for logic
Less routing
Seperate Methods
More routing
not DRY
Easy method lookup
Easier to read individual methods
So again the real question is, which one of those techniques are less
bad.
I would do something like:
FOLLOW_WHITELIST = %w[ follow follow_me following_follow_me following_who_follow_me following_who_do_not_follow_me ]
def show
if FOLLOW_WHITELIST.include? params[:id]
users = current_user.send params[:id].to_sym
end
respond_with do |format|
format.json do {...}
end
end
This will call whatever method is passed in params[:id], as long as it's in the whitelist (to prevent arbitrary code injection).
If having separate routes was a plus to you (nicer urls?), you could also dynamically generate the methods and routes with something like this:
class TwitterFriendController < ApplicationController
FOLLOW_ACTIONS = %w[ follow follow_me following_follow_me following_who_follow_me following_who_do_not_follow_me ]
FOLLOW_ACTIONS.each do |action|
define_method action do
users = current_user.send action.to_sym
respond_with do |format|
format.json do {...}
end
end
end
end
And then in routes.rb:
FOLLOW_ACTIONS.each do |action|
match action.to_sym => "controller##{action}"
end

Resources