Testing Rails 4 Controller - ruby-on-rails

I'm having some trouble understanding what my be wrong with my test, but I keep getting No Route Match when testing the update method for a controller. Submiting a form through a browser works, though.
My Routes file:
namespace :merchant do
resources :users
get '/signup', to: "users#new"
end
My Controller:
def update
respond_to do |format|
if #merchant_user.update(user_params)
format.html { redirect_to #merchant_user, notice: 'User was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'show' }
format.json { render json: #merchant_user.errors, status: :unprocessable_entity }
end
end
end
My Test:
test "should update user" do
user = users(:jon)
user.first_name="Jonas"
put :update, :merchant_user =>user.attributes
assert_response :success
end
Result:
1) Error:
Merchant::UsersControllerTest#test_should_update_user:
ActionController::UrlGenerationError: No route matches {:merchant_user=> {"id"=>"846114006", "email"=>"jon.sims#whatever.com", "first_name"=>"Jonas", "last_name"=>"Sims", "password_digest"=>"$2a$10$LVbV7pkd7li8sobYEauoS.4JVA2ZHzAXgPFbyiojYqgcDBHUE9bXW", "type"=>"Merchant::User", "created_at"=>"2013-07-11 22:59:41 UTC", "updated_at"=>"2013-07-11 22:59:41 UTC"}, :controller=>"merchant/users", :action=>"update"}
test/controllers/merchant/users_controller_test.rb:45:in `block in <class:UsersControllerTest>'
Any hint?

You need to pass in the id of the user for the test to run correctly.
Try this:
put :update, id: user.id, merchant_user: {}
You're seeing this error because the router expects resource/:id, but your are not passing the id.
Users#update is a member action, which requires an id. The router expects a user id param: /users/:id/update. Without the id the method has no way of finding the user you want to update.

From the way your routes look it seems like it's expecting an id parameter.
If you do rake routes from the command line it will probably show a route that looks like
/merchant/users/:id/update
If you pass in the id like this put :update, id: user.id, merchant_user: user.attributes it should work.

Related

Rails: No route matches [POST] nested resources

In the routes.rb I have this nested resource
# OBSERVATIVE SESSIONS
resources :observative_sessions do
# OBSERVATIONS
resources :observations
end
In observations_controller.rb
def new
#observative_session = ObservativeSession.find(params[:observative_session_id])
#observation = Observation.new
#observation.observative_session_id = #observative_session.id
end
def create
#observative_session = ObservativeSession.find(params[:observative_session_id])
#observation = #observative_session.observations.build(observation_params)
#observation.user_id = current_user.id
respond_to do |format|
if #observation.save
format.html { redirect_to [#observative_session, #observation], notice: 'Observation was successfully created.' }
format.json { render :show, status: :created, location: #observation }
else
format.html { render :new }
format.json { render json: #observation.errors, status: :unprocessable_entity }
end
end
end
And in observations_controller_test.rb I set up both observation and observative session. The test of new works just fine.
class ObservationsControllerTest < ActionDispatch::IntegrationTest
include Devise::Test::IntegrationHelpers
setup do
#observative_session = observative_sessions(:one)
#observation = observations(:two)
sign_in users(:admin_user)
end
test "should get new" do
get new_observative_session_observation_path(#observative_session)
assert_response :success
end
test "should create observation" do
assert_difference('Observation.count') do
post observative_session_observation_path(#observative_session, #observation), params: { observation: { start_time: #observation.start_time, description: #observation.description, rating: #observation.rating, notes: #observation.notes, celestial_body_name: #observation.celestial_body_name, telescope_name: #observation.telescope_name, binocular_name: #observation.binocular_name, eyepiece_name: #observation.eyepiece_name, filter_name: #observation.filter_name, user_id: #observation.user_id, observative_session_id: #observation.observative_session_id }}
end
But this is the error I get in the creation test
test_should_create_observation
ActionController::RoutingError: No route matches [POST] "/observative_sessions/980190962/observations/298486374"
I can't understand what I'm doing wrong.
Thanks for your help.
When you say POST observation_session_observation_path(#observation_session, #observation) you are telling it to post to a url the has both :observation_session_id and an :id in the params, where the id is that of #obseravtion. However, POST paths for create actions don’t take that last id param (ostensibly you are creating a new record with that action).
Try dropping #observation from your path helper (and make sure you are using the correct create path: observation_session_observations_path(#observation_session).
You can do rake routes to see your routes in your terminal, or localhost:3000/rails/info/routes to see it in the browser.
I also see in your new action you are assigning the observation_session_id manually. I recommend you either do what you do later and call #obervation_session.observations.build, or Observation.new(observation_session: #observation_session). You should avoid setting ids like that.

Update Test On Controller Breaks in Rails 4.1.12

I'm upgrading a Rails site from 4.0 to 4.1.12 and quite a few of my Rspec controller tests are now broken. For example this test broke with the upgrade:
it "update action should render edit template" do
#user = create(:user)
#user.name = "" # model requires name
#controller.stubs(:current_user).returns(#user)
put :update, id: #user
expect(response).to render_template(:edit)
end
I'm getting "No route matches {:action=>"show", :controller=>"accounts", :id=>nil} missing required keys: [:id]". It seems like the update_attributes method is ignoring my model validations all of a sudden.
Controller code:
def update
#user = current_user
if #user.update_attributes(params[:user])
redirect_to user_path(#user), :notice => "Your profile has been updated."
else
render :action => 'edit'
end
end
routes.rb
resources :users do
member do
get 'accounting'
end
collection do
post 'send_password'
end
end
I'm sure I've missed something in the upgrade process but I don't see anything in the docs that's telling me what that is.
So just to put down what I would do - rather than changing the #user object in the test you need to send in the changes you want to make to the user object as params of the request. This way the controller would receive it in the params[] hash - otherwise you aren't really testing what the controller method is doing.
it "with invalid data; update action should render edit template" do
#user = create(:user)
#controller.stubs(:current_user).returns(#user)
patch :update, id: #user, user: { name: '' }
expect(response).to render_template(:edit)
end

Why can rails not find a route created by a helper?

I created a member route in rails 4:
resources :line_items do
post 'decrement', on: :member
end
and gave it a matching method in the line_items controller:
def decrement
#cart = current_cart
#line_item = #cart.line_items.find_by_id(params[:id])
#line_item.decrement_quantity
respond_to do |format|
if #line_item.save
format.html { redirect_to shop_path, notice: 'Line item was successfully updated.' }
format.js {#current_item = #line_item}
format.json { head :ok }
else
format.html { render action: "edit" }
format.js {#current_item = #line_item}
format.json { render json: #line_item.errors, status: :unprocessable_entity }
end
end
end
But when I try to make a button:
<%= button_to 'X', decrement_line_item_path(item) %>
I get this error:
No route matches [POST] "/carts/25"
What gives?
Your error message is:
No route matches [POST] "/carts/25"
But I was expecting the route to be: /line_items/25/decrement, not /carts/25
Are your line_items routes already nested under carts? If so, you could unnest the decrement action like so:
# in config/routes.rb
resources :carts do
resources :line_items
end
resources :line_items, only: [] do
post 'decrement', on: :member
end
resources :line_items do
post 'decrement', on: :member
end
Upon CONTROLLER='line_items' rake routes, here is the following output
decrement_line_item POST /line_items/:id/decrement(.:format) line_items#decrement
line_items GET /line_items(.:format) line_items#index
POST /line_items(.:format) line_items#create
new_line_item GET /line_items/new(.:format) line_items#new
edit_line_item GET /line_items/:id/edit(.:format) line_items#edit
line_item GET /line_items/:id(.:format) line_items#show
PUT /line_items/:id(.:format) line_items#update
DELETE /line_items/:id(.:format) line_items#destroy
So in your html button part, you need add the POST method.
<%= button_to 'X', decrement_line_item_path(item), method: :post %>

Rails 3 routes 'match' syntax

I want to route
[PUT] http:// foo /data/base_states/?id=8
to my controller, preferably with this style of definition:
`match '/data/base_state:id' => 'base_state#create_or_update', via: [:put]`
How?
It seems there are many different ways to define routes, with documentation somewhat lacking. Would be interested in any other suggested syntax (that works) as well.
No idea why the suggested routes aren't working, this is my routes.rb and controller ...
config/routes.rb
EntwineBe::Application.routes.draw do
namespace :data, defaults: {format: :json} do
resources :event_categories
resources :state_categories
resources :base_events
resources :base_states
match '/block/:bock' => 'block#show', via: [:get] # curiously, this WORKS AS EXPECTED with GET http://foo/data/block/foo
match '/base_state/:id' => 'base_states#create_or_update', via: [:put]
end
end
app/controllers/data/base_states_controller.rb
class Data::BaseStatesController < ApplicationController
before_action :set_base_state, only: [:show, :edit, :update, :destroy]
def index
#base_states = BaseState.all
end
def show
end
def new
#base_state = BaseState.new
end
def edit
end
def create # typical REST is that this is PUT
#base_state = BaseState.new(base_state_params)
respond_to do |format|
if #base_state.save
format.html { redirect_to #base_state, notice: 'Base state was successfully created.' }
format.json { render action: 'show', status: :created, location: data_base_state_url(#base_state) }
else
format.html { render action: 'new' }
format.json { render json: #base_state.errors, status: :unprocessable_entity }
end
end
end
def create_or_update
logger.info "WTF"
end
def destroy
#base_state.destroy
respond_to do |format|
format.html { redirect_to base_states_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_base_state
#base_state = BaseState.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def base_state_params
params.require(:base_state).permit(:foo, :bar)
end
end
Relevant output of 'rake routes'
data GET /data/block/:block(.:format) data/code_block#show {:format=>:json}
PUT /data/base_state/:id(.:format) data/base_states#create_or_update {:format=>:json}
PUT /data/base_state:id(.:format) base_state#create_or_update {:format=>:json}
example call
$.ajax({'type':'PUT', 'url':'/data/base_state?id=8'})
$.ajax({'type':'PUT', 'url':'/data/base_state/?id=8'})
$.ajax({'type':'PUT', 'url':'/data/base_state?id=8.json'}) // not that expect it to need / I want to have .json
$.ajax({'type':'PUT', 'url':'/data/base_state/?id=8.json'}) // not that expect it to need / I want to have .json
--> 404!
Add defaults: {format: :json} to your route.
match '/data/base_state:id' => 'base_state#create_or_update', via: [:put], defaults: {format: :json}
put '/data/base_state:id' => 'base_state#create_or_update', defaults: { format: :json }
put "data/base_state:id(.:format)" => 'base_state#create_or_update'
Hope I'm not too late with this!
If your route is /data/base_state/:id, then the url you use to view the matching resource with id 8 say is /data/base_state/8.json.
As spas correctly points out, if you set the correct datatype in the ajax call, dataType: 'json', then you do not need to add the .json at the end of the URL

Rails 3.2 redirection skips Create action

I have a Rails 3 blog-style application, where I've got an admin namespace for backend purposes and a controllers/admin subfolder containing the respective posts_controller.rb.
So the page's root url is set to "admin/posts#index", and post creation works fine, except when I configure the routes file to redirect the user to root_url if he types "/admin/articles".
This is my routes file:
BlogDos::Application.routes.draw do
# Index
root to: "admin/posts#index"
# If I uncomment these two lines below, the post#create function doesn't work. When I
# submit the "new post" form, the controller just skips the function entirelly and
# redirects me to admin/posts#index without creating the new post.
# match "admin/posts" => redirect("/")
# match "admin/posts/" => redirect("/")
namespace :admin do
resources :cpanel
resources :posts do
resources :comments, :only => [:create, :destroy]
end
root to: "cpanel#index"
end
..
end
And this is my posts_controller.rb
def create
#usuario = current_user
#post = #usuario .posts.create(params[:post])
respond_to do |format|
if #post.save
format.html { redirect_to article_dir_path(#post.year, #post.month, #post.slug), notice: 'Article was successfully created.' }
format.json { render json: article_dir_path(#post.year, #post.month, #post.slug), status: :created, location: article_dir_path(#post.year, #post.month, #post.slug) }
else
format.html { render action: "new" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
Strangely, this only happens with the Create action, If i edit an article and update it, everything works fine.
I have sorted out almost everything from looking at Rails tutorials and QA websites, except this little problem, I'm sure it is something rather simple, but i'm new to Rails and not very familiar with its routing mechanics yet.
The form that creates the post submits to
/admin/posts
If you redirect that route to the index page, the controller action is never called.

Resources