RSpec tests for custom username routes are failing - ruby-on-rails

I have a constrained route that matches usernames like this:
controller :users, :path => '/:username', :as => :user, :constrain => { :username => /^_?[a-z]_?(?:[a-z0-9]_?)*$/i } do
# lots of nested routes go here
end
When I go to write RSpec tests for this (versus using user_id like normal), all the tests are failing because it "can't find the route" even though it works fine on the server.
describe "for an invalid request" do
it "should render a 404 if an associated photo is not found" do
# give it a bad photo id
xhr :post, :destroy, :id => "999999", :photo_id => "999999", :username => #photo_owner.username
# not found
response.status.should == not_found
end
end
This test was working fine when I was using the user_id in my routes prior to switching to usernames:
resources :users do
# nested routes
end
and
xhr :post, :destroy, :id => "999999", :photo_id => "999999", :user_id => #photo_owner.id
So what am I doing wrong and what has changed?
My server console shows this which means I should have all of the parameters passed in properly:
Processing by TagsController#destroy as JS
Parameters: {"constrain"=>{"username"=>/^_?[a-z]_?(?:[a-z0-9]_?)*$/i}, "username"=>"rubynewb", "photo_id"=>"2004-the-title-of-the-photo-here", "id"=>"1797"}

Use :constraints => {...} in your route definition.
You have one too many parameters being passed...
"constrain"=>{"username"=>/^_?[a-z]_?(?:[a-z0-9]_?)*$/i}
Rails doesn't recognize :constrain, therefore it and it's contents are passed along as a parameter instead of being processed by the Rails router.

Related

Rails Route "as"

I have this code snippet it has 3 types of "as"
Can anybody explain about 3 different "as"
as: 'drivers_confirmation'
:as -> 'user_registration'
as :user do
get 'drivers/confirmation' => "root#index", as: 'drivers_confirmation'
namespace :api do
get 'describe/models'
get 'status' => 'status#index'
as :user do
put 'v1/users' => 'v1/registrations#update', :as => 'user_registration'
# post "login" => "v1/sessions#create"
# get "currentUser" => "v1/sessions#current"
end
as :driver do
get "v1/drivers/session" => "v1/drivers/sessions#current"
end
end
as: 'drivers_confirmation' and :as => 'user_registration' are the same.
It's the old and new ruby hash syntax. They are there to be able to use url helpers in your views (drivers_confirmation_path and user_registration_path)
as :user do is probably from Devise, those routes are scoped to specific kinds of users.
Those methods expect a hash to be passed to it in that context. What you're describing are three ways to write a Ruby hash.
The form as: 'drivers_confirmation' is the "newer" and more preferred (from what I've seen) approach to creating hashes, so long as the key you're using can be directly converted to a symbol.
The form :as => 'user_registration' is the older style of writing a hash.
The form as :user is a method which is likely expecting a symbol to be passed to it as opposed to it being any form of hash.

POST to route not working, despite route displaying in "rake routes"

I have the following route, that appears in the list when I perform "rake routes":
page_upload_image POST /pages/:page_id/upload_image(.:format) pages#upload_image
I also have the following in my routes file:
resources :pages do
post :sort, :on => :collection
post :upload_image, :on => :collection
end
I'm then using JavaScript to capture and POST values pulled from the following:
%form.new-page-image{:class => "hide", :action => "/pages/#{#page.id}/upload_image"}
%input.page_image{:type => "file", :name => "page[image]", :multiple => "true"}
I have the following for my upload_image action in my pages controller:
def upload_image
image = params[:page][:image]
uploader = PageImageUploader.new
uploader.store!(image)
render json: uploader.to_json
end
For some reason, even though the route exists, when I post to it, I get the following:
ActionController::RoutingError (No route matches [POST] "/pages/1/upload_image"):
I'm wondering why a route, that appears legitimate (shows up properly when executing rake routes) would return this error when I try to post to it.
Try changing your routes to this instead, as it looks like you want a member route instead of a collection route:
resources :pages do
post :sort, :on => :collection
post :upload_image, :on => :member
end
I would also advise against using interpolation in the route, as it can quickly get long and messy. Instead try to name your routes using as: and call them using the names instead.

How to add a custom action to the controller in Rails 3

I want to add another action to my controller, and I can't figure out how.
I found this on RailsCasts, and on most StackOverflow topics:
# routes.rb
resources :items, :collection => {:schedule => :post, :save_scheduling => :put}
# items_controller.rb
...
def schedule
end
def save_scheduling
end
# items index view:
<%= link_to 'Schedule', schedule_item_path(item) %>
But it gives me the error:
undefined method `schedule_item_path' for #<#<Class:0x6287b50>:0x62730c0>
Not sure where I should go from here.
A nicer way to write
resources :items, :collection => {:schedule => :post, :save_scheduling => :put}
is
resources :items do
collection do
post :schedule
put :save_scheduling
end
end
This is going to create URLs like
/items/schedule
/items/save_scheduling
Because you're passing an item into your schedule_... route method, you likely want member routes instead of collection routes.
resources :items do
member do
post :schedule
put :save_scheduling
end
end
This is going to create URLs like
/items/:id/schedule
/items/:id/save_scheduling
Now a route method schedule_item_path accepting an Item instance will be available. The final issue is, your link_to as it stands is going to generate a GET request, not a POST request as your route requires. You need to specify this as a :method option.
link_to("Title here", schedule_item_path(item), method: :post, ...)
Recommended Reading: http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to
Ref Rails Routing from the Outside In
Following should work
resources :items do
collection do
post 'schedule'
put 'save_scheduling'
end
end
You can write routes.rb like this:
match "items/schedule" => "items#schedule", :via => :post, :as => :schedule_item
match "items/save_scheduling" => "items#save_scheduling", :via => :put, :as => :save_scheduling_item
And the link_to helper can not send post verb in Rails 3.
You can see the Rails Routing from the Outside In

Routing error on Ruby on Rails 3 voting system

All I'm trying to add is a button that, when pressed, increments an "accuracy" value by 1. I've looked at many, many, StackOverflow solutions but none of them have worked for me so far. I'm working with a colleague's code, and if it helps, most of it is taken from a Ruby on Rails textbook ("Learn Rails by Example") and may be hacked together.
my view looks like:
<%= link_to 'Accurate2', :action => :vote_up, :id => #post.id%>
my code looks like:
def vote_up
#in posts_controller.rb
#post = Post.find(params[:id])
#post.rate_it( 1, current_user.id ) #with "acts_as_rateable" plugin
#post.save
my routing looks like:
resources :users do
resources :comments
resources :posts
end
resources :posts do
resources :comments
match "vote_up", :on => :collection
match "vote_down", :on => :collection
end
the error I receive is:
http://localhost:3000/posts/vote_up?id=1
CanCan::AccessDenied in PostsController#vote_up
You are not authorized to access this page.
Parameters:
{"id"=>"1"}
You're using the CanCan gem which apparently doesn't give you the necessary authorization. Check this for details:
https://github.com/ryanb/cancan/wiki/defining-abilities
Also, as a sidenote, I would recommend changing these:
match "vote_up", :on => :collection
match "vote_down", :on => :collection
to:
member do
post "vote_up"
post "vote_down"
end
and making the appropriate changes in the view:
<%= button_to 'Vote up', {:action => :vote_up, :id => #post.id} %>
The reason is that actions like these shouldn't be done with GET requests since the user may accidentally submit them multiple times by refreshing the page etc.

Rails Functional Test of Arbitrary or Custom URLs

I have a RESTful resource in my Rails app called "Photo". I'm using Paperclip to serve different "styles" of my photos (for thumbnails and the like), and I'm using a custom route to RESTfully access those styles:
map.connect "photos/:id/style/*style", :controller => "photos", :action => "show"
That's working fine, but I want to write a test to make sure it stays that way.
I already have a functional test to call the Photo controller's show action (generated by scaffold in fact):
test "should show photo" do
get :show, :id => photos(:one).to_param
assert_response :success
end
That tests the execution of the action at the URL "/photo/1". Now I want to test the execution of the URL "/photo/1/style/foo". Unfortunately, I can't seem to get ActionController::TestCase to hit that URL; the get method always wants an action/id and won't accept a URL suffix.
How do I go about testing a custom URL?
Update
While checking on #fernyb's answer I found this snippet in the same rdoc
In tests you can simply pass the URL or named route to get or post.
def send_to_jail
get '/jail'
assert_response :success
assert_template "jail/front"
end
However, when I actually try that, I get an error message:
test "should get photo" do
get "/photos/1/style/original"
assert_equal( "image/jpeg", #response.content_type )
end
ActionController::RoutingError: No route matches {:action=>"/photos/1/style/original", :controller=>"photos"}
I wonder if I'm doing something wrong.
Use assert_routing to test routes:
assert_routing("/photos/10/style", :controller => "photos", :action => "show", :id => "10", :style => [])
assert_routing("/photos/10/style/cool", :controller => "photos", :action => "show", :id => "10", :style => ["cool"])
assert_routing("/photos/10/style/cool/and/awesome", :controller => "photos", :action => "show", :id => "10", :style => ["cool", "and", "awesome"])
In your integration test you can then do:
test "get photos" do
get "/photos/10/style/cool"
assert_response :success
end
From the Rails API documentation:
Route globbing
Specifying *[string] as part of a
rule like:
map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
will glob all remaining parts of the
route that were not recognized
earlier. The globbed values are in
params[:path] as an array of path
segments.
So it looks like you need to pass the :path arguments, to test the action correctly.

Resources