respond_with won't work on nested routes - ruby-on-rails

I've this code that I'm trying to get working.
class CommitRequestsController < ApplicationController
respond_to :json
def create
#commit_request = CommitRequest.new(params[:commit_request])
respond_with(repository, #commit_request)
end
private
def repository
Repository.find(params[:repository_id])
end
end
I also have this spec
CommitRequest.any_instance.stubs(:valid?).returns(false)
post(:create, {
format: "json",
repository_id: repository.id,
commit_request: {}
})
response.status.should_not eq(201)
The problem is that the spec is always failing.
It returns 201, even tho the created object isn't valid.
Removing the mock line results in the same problem,
even tho the created object is invalid (for real this time).
I'm using Rails 3.2.

Related

When testing a method call that I know happens , I get a failure error stating the method was called 0 times

I have a controller that calls the CSVCreator class when a get request is made. People is just a list of Persons that have some basic values like name and title.
def index
respond_to do |format|
format.html
format.csv { send_data CSVCreator.new(people).create }
end
My CSVCreator looks like this
class CSVCreator
HEADERS = ["Name", "Date", "Title"].freeze
def initialize(people)
#people = people
end
def generate
CSV.generate do |csv|
csv << HEADERS
#people.each do |person|
row = [person.name, person.date, person.title]
csv << row
end
end
end
end
I'm trying to figure out how I would go about testing this in Rspec? What I've tried is
it "calls CSVCreator when get request is made" do
csv_creator = double("CSVCreator")
people = double("People")
allow(csv_creator).to receive(:new).with(people)
allow(csv_creator).to receive(:create)
expect(csv_creator).to receive(:new).with(people)
expect(csv_creator).to receive(:create)
get :index, format: :csv
end
My thought process was to decouple the controller and the CSVCreator and People classes. So to test the controller, I wanted to see if it correctly calls the methods it needs to, so I created test doubles for those objects. I'm new to RSpec and testing in general, so please let me know if my approach was incorrect.
My issue is, I get a failure saying
Failure/Error: expect(csv_creator).to receive(:new).with(people)
(Double "CSVCreator").new(#<Double "People">)
expected: 1 time with arguments: (#<Double "People">)
received: 0 times
I know my class works and it creates the CSV, and that CSVCreator.new(people).create being called in the controller. So I'm curious as to why I'm receiving this failure.
Create a class method on your CSVCreator that creates an instance and calls the generate method:
class CSVCreator
# ...
def self.perform(people)
new(people).generate
end
end
def index
respond_to do |format|
format.html
format.csv { send_data CSVCreator.perform(people) }
end
end
This creates a better API so that consumers don't have to have as much knowledge about the class and it makes it way easier to set expectations:
it "calls CSVCreator when get request is made" do
expect(CSVCreator).to receive(:perform)
get :index, format: :csv
end
However stubbing in people is a bit more problematic and depends on how its actually implemented. If its a method on the controller it would be hard to stub without using any_instance which is a code smell.
i guess that the input param people is created somewhere in your controller, but i'm sure it's not your double "People", you have to stub the method :new of the People class to return an instance_double of "People", Or in case you want to make sure that CSVCreator is created with a special "People", for example in your controller you call this to get people: PeopleService.some_logic(..) -> a_special_people, then stub that method allow(PeopleService).to receive(:some_logic).and_return(instance_double_people)
beside that, you could also create an instance_double of CSVCreator instead, then stub method :new of CSVCreator to return that instance_double, now you only need to verify that instance_double has called :create method since it makes sure that CSVCreator has called :new and the new instance of CSVCreator has called :create.
it "calls CSVCreator when get request is made" do
csv_creator = instance_double(CSVCreator)
people = instance_double(People)
allow(People).to receive(:new).and_return(people)
# allow(PeopleService).to receive(:some_logic).and_return(people)
allow(CSVCreator).to receive(:new).with(people).and_return(csv_creator)
allow(csv_creator).to receive(:create)
get :index, format: :csv
expect(csv_creator).to have_received(:create)
end

Argument error; wrong number of arguments given

New to Ruby on Rails and been cracking my head on this. I have been following this tutorial here to make this form to save records into my DB - https://human-se.github.io/rails-demos-n-deets-2020/demo-resource-create/
This is my controller:
class ViewaddparamController < ActionController::Base
def view_add_param
newParam = ViewaddparamController.new
respond_to do |format|
format.html{ render :viewaddparam, locals: { newParam: newParam } }
end
end
def add_param
# new object from params
mlParam = ViewaddparamController.new(params.require(:Viewaddparam_Controller).permit(:name, :machine_model, :parameter_1, :parameter_2, :parameter_3, :data_train_set, :start_date, :end_date))
# respond_to block
respond_to do |format|
format.html do
if mlParam.save
# success message
flash[:success] = "ML parameters saved successfully"
# redirect to index
redirect_to model_url
else
# error message
flash.now[:error] = "Error: parameters could not be saved"
# render new
render :viewaddparam, locals: { newParam: newParam }
end
end
end
end
end
My route:
get 'viewparam', to: 'viewaddparam#view_add_param'
post 'add_param', to: 'viewaddparam#add_param', as: 'add_param'
My view:
<%= form_with model: newParam, url: add_param_path, method: :post, local: true, scope: :Viewaddparam_Controller do |f| %>
...
I kept getting this error whenever I try to submit the form
ArgumentError in ViewaddparamController#add_param
Wrong number of arguments (given 1, expected 0)
The error highlighted at my controller class, line 11.
What am I doing wrong here? I looked through the tutorial over and over but I can't see the fault here.
Thanks in advance.
It seems that you’re treating ViewaddparamController as both the controller – which in Rails terms is what responds to user requests - and the data model.
There’s often a one-to-one correlation to controllers and models, especially if you’re following what’s known as the RESTful pattern. So if your model was a Product, you might set it up in routes using a resources directive:
resources :products
That would set the app up to expect to use a ProductsController. And, using a similar coding style to your example, that would look a little like:
class ProductsController < ApplicationController
def new
product = Product.new
render :new, locals: { product: product }
end
def create
product = Product.new(params.require(:product).permit(…))
# etc
end
# etc
end
So you can see from this example that controller and model are related, but are named differently.
By using your controller name where you should be using the model, you’re calling #new on the controller, which is not normally something you need to do – controller instances are managed by the Rails app framework. And their initialiser doesn’t take any arguments, so Ruby complains that an initialiser that takes 0 arguments is being given 1.

Rspec rails-controller-testing gem's assigns doesn't work

I'm using Rails 5.2 with rspec-rails 3.7 and rails-controller-testing gems.
I have a controller that filters results on the index action (yeah, bad practice, legacy code, etc). The problem is I need to test that when I do GET work_orders/index params: { keywords: 'some word' }, the instance variable #work_orders returns filtered results, but if I use assigns(:work_orders) it returns nil. I even tested this assigning the last WorkOrder to that variable, but it still doesn't show the variable in the hash.
work_orders_controller.rb
def index
... some code ...
#work_orders = WorkOrder.last
respond_to do |format|
format.html
end
end
spec/controllers/work_orders_controller_spec.rb
require 'rails_helper'
describe WorkOrdersController do
describe 'GET index' do
it 'collects work orders filtered by courier_ot in #work_orders' do
get :index
expect(assigns(:work_orders)).to be_an_instance_of WorkOrder
end
end
end
That is the simplest example I tested and it doesn't work. The assigns call returns nil, and if I use byebug to inspect the hash, it only has this: {"marked_for_same_origin_verification"=>true}.
The real test should use get :index, params: { keywords: 'asdf' } and test that it gets the filtered work orders.
Any help would be appreciated. Thanks.

How can I test a Rails RESTful API post request with xml as input?

I am adding a RESTful API to the Rails Tutorial sample app. The controller actions should take XML as input and respond with XML. I am trying to follow a TDD approach but have been unable to find a definitive method of making a post request. Here are my tests, as best as I've written them so far:
it "should increment the Relationship count" do
expect do
# valid_xml = "<xml><:followed_id>#{other_user.id}</:followed_id></xml>"
valid_xml = { followed_id: other_user.id }.to_xml
post :create, relationship: valid_xml, format: 'xml', content_type: 'application/xml'
end.to change(Relationship, :count).by(1)
end
it "should respond with success" do
# valid_xml = "<xml><:followed_id>#{other_user.id}</:followed_id></xml>"
valid_xml = { followed_id: other_user.id }.to_xml
post :create, relationship: valid_xml, format: :xml, content_type: 'application/xml'
expect(response).to be_success
end
This particular test is verifying that posting XML will create a new Relationship. As you can see, I have tried several ways to specify that the input is XML. Here is the controller action:
class RelationshipsController < ApplicationController
before_action :signed_in_user
respond_to :html, :js, :xml
def create
# Problem is that the xml is not being read as such; it's a string
relationship = params[:relationship]
puts relationship
puts relationship[:followed_id]
#user = User.find(relationship[:followed_id])
current_user.follow!(#user)
respond_with #user
end
For the given tests, the controller prints out relationship as XML but errors on the following line, where it attempts to get the value of key :followed_id. The error is TypeError: no implicit conversion of Symbol into Integer for both tests.
I assume that this means that the type of relationship is a string, not a hash, and Ruby thinks that the bracket is supposed to be an index. Does anyone know where I am going wrong, either in the test or the controller action?
Are there any better ways to test a RESTful API or gems I should use?
The XML parameters parser was removed from core in Rails 4.0. This was due to several vulnerabilities and the fact that most API´s worth using today are JSON or moving towards JSON.
The removed functionality is available from the actionpack-xml_parser gem.

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

Resources