Using Rails 3.0.7, I'm creating an API for our app, and I have this setup:
routes.rb
namespace :api do
namespace :v1 do
match "connect" => "users#login", :via => :post
match "disconnect" => "users#logout", :via => :post
resources :users
match "users/:id/foos" => "foos#list", :via => :get
match "users/:id" => "users#update", :via => :put
match "foos/:id/bars" => "bars#list_by_foo", :via => :get
match "foos/:id" => "foos#show", :via => :get, :constraints => { :id => /\d+/ }
match "bars/:id" => "bars#show", :via => :get
end
end
# other routes here e.g.
match "users/find" => "users#find_by_name", :via => :get
match "users" => "users#create", :via => :post
And then I have my regular app/controllers/application_controller.rb and app/controllers/users_controller.rb files as well as my app/controllers/api/v1/application_controller.rb and app/controllers/api/v1/users_controller.rb files that are defined like the following:
class Api::V1::ApplicationController < ApplicationController
before_filter :verify_access
def verify_access
# some code here
end
end
class Api::V1::UsersController < Api::V1::ApplicationController
skip_before_filter, :except => [:show, :update, :delete]
end
And before everything seemed to be working right until I overrode a method that is shared by both UsersController and Api::V1::UsersController -- and now it seems like everything is pointing to UsersController even though I'm accessing through the api/v1/users route.
I'm at my wit's end trying to figure it out. Any suggestions? Thanks. PS - feel free to comment with whatever conventions I'm ignoring that I shouldn't be or other things I might have messed up :)
skip_before_filter also general takes a symbol parameter for the before filter than you wish to skip. Controller names should not have to be unique as long as the proper scoping/namespacing is applied.
example
api/users_controller
admin/users_controller
users_controller
then the code per controller
class Api::V1::UsersController < Api::V1::BaseController
end
class Admin:UsersController < Admin::BaseController
end
class UsersController < ApplicationController
end
Then the routes
MyApp::Application.routes.draw do
scope :module => "api" do
namespace :v1 do
resources :users
end
end
namespace :admin do
resources :users
end
resources :users
end
Rails is a bit confusing, but I had a similar problem. Here's some steps you can take to make sure you're not missing any small code issues. (this eventually led me to discover a syntax bug in the namespaced controller).
run bundle exec rake routes to generate a list of what route links to what controller and action. If this is good, then move to step 2. If not, fix your routes file and try again. (many good tutorials on this, so I won't go into detail)
Go into the rails console, and just load the controller class. If it doesn't work, you may have discovered a bug in syntax. Here's what happened on console when I tried to load the Api::V2::CampaignsController.
irb> Api::V2::CampaignsController
=> CampaignsController
Note: Rails is directing all requests to the wrong controller (based on Rails' fancy logic to load controller classes). It should goto Api::V2::CampaignsController, but instead it is loading CampaignsController.
You can also verify it in the console with:
> app.get '/api/v2/campaigns.json'
> app.controller.class
=> CampaignsController
# This is not the expected controller.
This ended up being a syntax problem in a class I was extending from the Api::V2::CampaignsController.
It was a bit mind-boggling, but hope this helps someone else.
Related
I have a problem with the devise gem, I have this controller.
class AdminController < ApplicationController
before_action :authenticate_user!
def index
end
def per
end
def po
end
end
When redirect to sign_in form , shows nothing
sign_in form
These are my routes:
match 'po' => 'admin#po', :via => :get
match 'per' => 'admin#per', :via => :get
match 'admin' => 'admin#index', :via => :get
match 'admin/index' => 'admin#index', :via => :get
match 'admin/per' => 'admin#per', :via => :get
match 'admin/po' => 'admin#po', :via => :get
devise_for :users, :controllers => { :omniauth_callbacks => "callbacks" }
root 'home#index'
I have three templates: application, admin and home
I overwrite the default route after log in
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
#before_action :authenticate_user!
def after_sign_in_path_for(resource)
#request.env['omniauth.origin'] || stored_location_for(resource) || admin_path
admin_path
end
end
My last gem installed:
gem 'bootstrap-sass'
You need to run the generator for Devise views which will copy the necessary files in your views folder:
Run:
rails g devise:views
There is more information on configuring the Devise views here
Your problem probably isn't with Devise, it looks systemic to me.
#config/routes.rb
namespace :admin do
root "application#index" #->
resources :model_controller, path: "", only: :index do #-> url.com/admin/...
collection do
get :po #-> you shouldn't really have this
get :per #-> you shouldn't really have this
end
end
end
devise_for :users, :controllers => { :omniauth_callbacks => "callbacks" }
This will give you the following:
#app/controllers/admin/application_controller.rb
class Admin::ApplicationController < ApplicationController
before_action :authenticate_user!
def index
# do something here
end
end
This gives you the ability to create a custom "dashboard" type page for your admin area, from which you'll be able to use controllers bound to models.
Your po and per actions really shouldn't be there - they are not part of the CRUD system
In regards to your Devise views, the other answers are correct in that you would be best to generate the Devise views in your app:
rails generate devise:views
This won't solve your problem (hence why I downvoted the other answers). It will simply put the views in your app. It will do nothing apart from put code in a different place.
You will need to debug the issue:
Check the action you're seeing at /users/sign_in
Check the code in the <body> tags (which you haven't shown)
If the HTML is there, there will be some other issue preventing it from loading
If there is no HTML, it will likely mean a problem with the core of Devise
What I would recommend you do is the following:
Generate your views
From your screenshot, show us the contents of the <body> tag
Screenshot your console log (this will show any errors)
Update your question with the above
This will give you a much clearer perspective on what the potential issue will be, and allow other community members to better define the solution.
(Rails 3.0.7)
My routes.rb has this:
namespace :admin do
namespace :campus_hub do
resources :billing_subscriptions, {
:except => [:destroy, :new, :create]
} do
member do
post :add_addon
end
end
end
end
rake routes shows this route:
add_addon_admin_campus_hub_billing_subscription POST /admin/campus_hub/billing_subscriptions/:id/add_addon(.:format) {:action=>"add_addon", :controller=>"admin/campus_hub/billing_subscriptions"}
My controller (Admin::CampusHub::BillingSubscriptionsController) has the method add_addon.
I do a POST that looks like this in the logs:
Started POST "/admin/campus_hub/billing_subscriptions/50059f5be628f83b13000012/add_addon" for 33.33.33.1 at Tue Jul 17 20:21:17 +0200 2012
And I get this error:
AbstractController::ActionNotFound (The action '50059f5be628f83b13000012' could not be found for Admin::CampusHub::BillingSubscriptionsController)
I'm totally baffled. The POST request I make matches the route exactly. Why does it think that the ID is the action? Hope I'm just missing something obvious!
I'm guessing from the rails version that you're having a similar problem to this: Routing error with Rails 3 with members which is a bug in rails 3, note comment: Routing error with Rails 3 with members
You'd need to replace:
member do
post :add_addon
end
With something like this:
match "add_addon" => "billing_subscriptions#add_addon", :as => :add_addon, :via => :post
You'd get a slightly swapped path like this: admin_campus_hub_billing_subscription_add_addon_path but it should work in rails 3 and 4.
All together it's like this:
namespace :admin do
namespace :campus_hub do
resources :billing_subscriptions, {
:except => [:destroy, :new, :create]
} do
match "add_addon" => "billing_subscriptions#add_addon", :as => :add_addon, :via => :post
end
end
end
Note that the full rake routes looks like this:
admin_campus_hub_billing_subscription_add_addon POST /admin/campus_hub/billing_subscriptions/:billing_subscription_id/add_addon(.:format) admin/campus_hub/billing_subscriptions#add_addon
It used to be that you could load Typus routes exactly where you needed them by placing
Typus::Routes.draw(map)
at the appropriate point in your routes.rb file. It seems that this is no longer supported and that they're always loaded after all of the application routes. This causes problems with catchall routes which must be defined last. Does anyone know how to control the load order for typus now? Is there a way to get them defined before any of the app routes rather than after? Thanks!
I got around it by leaving my catch-all routes at the end of my apps routes.rb BUT excluding it from matching for Typus urls:
# A catch all route
match '*path' => 'content#show', :constraints => lambda{|request|
!request.path.starts_with?("/admin") # excluded if typus will be taking it...
}
This may or may now work for you...
I'm looking for the same answer.
At the moment I have resorted to copying the contents from typus's config/routes.rb and placing it into my routes.rb file, before the catchall route.
It's a horrible, hackish solution, but it's solving my immediate problem.
Example:
# TODO: KLUDGE: MANUALLY BRING THE TYPUS ROUTES IN
# Typus used to provide :
# Typus::Routes.draw(map)
# But that is no longer the case.
scope "admin", :module => :admin, :as => "admin" do
match "/" => "dashboard#show", :as => "dashboard"
match "user_guide" => "base#user_guide"
if Typus.authentication == :session
resource :session, :only => [:new, :create, :destroy], :controller => :session
resources :account, :only => [:new, :create, :show, :forgot_password] do
collection do
get :forgot_password
post :send_password
end
end
end
Typus.models.map { |i| i.to_resource }.each do |resource|
match "#{resource}(/:action(/:id(.:format)))", :controller => resource
end
Typus.resources.map { |i| i.underscore }.each do |resource|
match "#{resource}(/:action(/:id(.:format)))", :controller => resource
end
end
# END KLUDGE
# Catch all to the state page handler
match '/:page' => 'pages#show', :as => 'page'
I'm getting the following error:
Unknown action
No action responded to show. Actions: activate, destroy, index, org_deals, search, and suspend
Controller:
class Admin::HomepagesController < Admin::ApplicationController
def org_deals
#organization = Organization.find(:all)
end
Routes:
map.root :controller => 'main'
map.admin '/admin', :controller => 'admin/main'
map.namespace :admin do |admin|
admin.resources :organizations, :collection => {:search => :get}, :member => {:suspend => :get, :activate => :get}
To note: This is a controller inside of a controller.
Any idea why this is defaulting to show?
Update:
I updated what the routes syntax is. Read that article, and tried quite a few variations but its still adamantly looking for a show.
Firstly, it looks like your routes file has the wrong syntax. If you are trying to establish routes for nested resources, you'd do it like so:
map.resources :admin
admin.resources :organizations
end
This would give you paths such as:
/admin/
/admin/1
/admin/1/organizations
/admin/1/organizations/1
The mapping from route to controller/action is done via a Rails convention, where HTTP verbs are assigned in ways that are useful for the typical CRUD operations. For example:
/admin/1/organizations/1
would map to several actions in the OrganizationsController, distinguished by the type of verb:
/admin/1/organizations/1 # GET -> :action => :show
/admin/1/organizations/1 # PUT -> :action => :update
/admin/1/organizations/1 # DELETE -> :action => :destroy
"Show" is one of the seven standard resourceful actions that Rails gives you by default. You can exclude "show" with the directive :except => :show, or specify only the resourceful actions you want with :only => :update, for example.
I recommend you look at Rails Routing from the Outside In, which is a great introduction to this topic.
EDIT
I see I ignored the namespacing in my answer, sorry. How about this:
map.namespace(:admin) do |admin|
admin.resources :homepages, :member => { :org_deals => :get }
end
This will generate your org_deals action as a GET with an id parameter (for the organization). You also get a show route, along with the six other resourceful routes. rake routes shows this:
org_deals_admin_homepage GET /admin/homepages/:id/org_deals(.:format) {:controller=>"admin/homepages", :action=>"org_deals"}
Of course your homepages_controller.rb has to live in app/controllers/admin/
EDIT redux
Actually, you want organizations in the path, I'll bet, in which case:
map.namespace(:admin) do |admin|
admin.resources :organizations, :controller => :homepages, :member => { :org_deals => :get }
end
which gives you:
org_deals_admin_organization GET /admin/organizations/:id/org_deals(.:format) {:controller=>"admin/homepages", :action=>"org_deals"}
By specifying admin.resources ... you are telling Rails you want the seven default different routes in your application. If you do not want them, and only want the ones you specify, do not use .resources. Show is called because that's the default route called for a GET request with a path such as /admin/id when you have the default resources.
I'm reading these two pages
resources
Adding more RESTful actions
The Rails Guides page shows
map.resources :photos, :new => { :upload => :post }
And its corresponding URL
/photos/upload
This looks wonderful.
My routes.rb shows this
map.resources :users, :new => { :signup => :get, :register => :post }
When I do: [~/my_app]$ rake routes
I see the two new routes added
signup_new_user GET /users/new/signup(.:format)
register_new_user POST /users/new/register(.:format)
Note the inclusion of /new! I don't want that. I just want /users/signup and /users/register (as described in the Rails Routing Guide).
Any help?
When you expose a controller as a resource, following actions are automatically added:
show
index
new
create
edit
update
destroy
These actions can be categorized in to two groups:
:member actions
The URL for the member action has the id of the target resource. E.g:
users/1/edit
users/1
You can think of :member action as an instance method on a class. It always applies on an existing resource.
Default member actions: show, edit, update, destroy
:collection actions
The URL for the :collection action does not contain the id of the target resource. E.g:
users/login
users/register
You can think of :collection action as a static method on a class.
Default collection actions: index, new, create
In your case you need two new actions for registration. These actions belong to :collection type( as you do not have the id of the user while submitting these actions). Your route can be as follows:
map.resources :users, :collection => { :signup => :get, :register => :post }
The URL for the actions are as follows:
users/signup
users/register
If you want to remove a standard action generated by Rails use :except/:only options:
map.resources :foo, :only => :show
map.resources :foo, :except => [:destroy, :show]
Edit 1
I usually treat the confirmation action as a :member action. In this case params[id] will contain the confirmation code.
Route configuration:
map.resources :users, :member => { :confirm => :get}
URL
/users/xab3454a/confirm
confirm_user_path(:id => #user.confirmation_code) # returns the URL above
Controller
class UsersController < ApplicationController
def confirm
# assuming you have an attribute called `confirmation_code` in `users` table
# and you have added a uniq index on the column!!
if User.find_by_confirmation_code(params[id])
# success
else
# error
end
end
end
This can be taken as just another syntax -- something good to know may be.
Syntax 1:
resources :users do
member do
get 'signup'
post 'register'
end
end
Rake Route Output will include
signup_users GET /users/signup(.:format) {:action=>"signup", :controller=>"users"}
register_users POST /users/register(.:format) {:action=>"register", :controller=>"use
rs"}
Syntax 2:
If you have only one collection route
resources :users do
get 'signup', :on => :collection
end
If i'm understanding your question right, you just want to rename the urls of the new and create actions.
This would be done like so:
map.resources :users, :path_names => {:new => 'signup', :create => 'register'}
If you really would like to add new routes with corresponding controller actions, then Damiens answer is the way to go.
The new option allows you to create new routes for creating new objects. That's why they're prefixed with that term.
What you're looking for is the :collection option.
map.resources :users, :collection => { :signup => :get, :register => :post }
Which will create the /users/signup and /users/register urls.