Passing variables to Controller via RESTful routing in Rails - ruby-on-rails

The following question is related to passing a variable from routes to the controller. I have a Ruby on Rails (v 2.3.3) app with one model, which is delivered to the user with multiple views. The current solution involves using multiple controllers which are triggered by multiple routes. For example:
ActionController::Routing::Routes.draw do |map| # defines map
map.resource :simpsons, :only => [] do |b|
b.resources :episodes, :controller => "SimpsonsEpisodes"
end
map.resource :flintstones, :only => [] do |b|
b.resources :episodes, :controller => "FlintstonesEpisodes"
end
However, for the sake of DRYness I would like these routes to operate with the same controller. In order for the controller to distinct between the routes I would like to pass along a variable via the route. For example:
map.resource :simpsons, :only => [] do |b|
b.resources :episodes, :controller => "Episodes", :type => "simpsons"
end
map.resource :flintstones, :only => [] do |b|
b.resources :episodes, :controller => "Episodes", :type => "flintstones"
end
So in the controller I could do this:
case(type)
when "simpsons" then ... do something for the Simpsons ...
when "flintstones" then ... do something for the Simpsons ...
else .... do something for all episodes ....
end
I found a way to do this with non-RESTful routing (map.with_options etc.), but I'd prefer to use RESTful routes with map.resource(s). One ugly solution might be to parse the request URI in the controller, which I'd not prefer.

Since there are no replies and I found a solution, I am going to answer this question myself. If you also have a situation, where you might have a model or a table, which has multiple entries, but for some of them you might need separate views, this might benefit you a bit.
Most likely you would like to have different indexes and for that simply use collection get:
map.resources :episodes, :collection => {:simpsons=> :get, :flintstones=> :get} do |episode|
....and so on
Simply add the methods named "simpsons" and "flintstones" in your controller. And, for the edit, view and delete methods you can use a bit of extra logic, if necessary, by determening the ID of the entry at hand. Something like #episode = Episode.find(params[:id]), if #episode.criteria == ...something... then render this or the other view.

Related

Rails weird default routing behaviour in nested namespace in resource

I have this in my router.rb:
namespace :me do
namespace :bills do
get :consumption_invoice, :format => :pdf
end
end
and also
resources :groups do
member do
namespace :bills do
get :consumption_invoice, :format => :pdf
end
end
end
The first one gives me:
me_bills_consumption_invoice GET /me/bills/consumption_invoice(.:format) me/bills#consumption_invoice {:format=>:pdf}
consumption_invoice_bills_group GET /groups/:id/bills/consumption_invoice(.:format) bills/groups#consumption_invoice {:format=>:pdf}
In the group, the controller called is bills/groups#consumption_invoice instead of groups/bills#consumption_invoice as I'd expect.
Why?
Thanks
EDIT
After some reflexion, here's what I'd like to achieve:
/me/bills/consumption_invoice => :controller => :bills, :action => :consumption_invoice
# and
/groups/:id/bills/consumption_invoice => :controller => :bills, :action => :consumption_invoice
Idealy, I'd like to have both those rules in the :me namespace and the :groups resource blocks for making it cleaner to read.
And I'd like to be able to add more actions easily:
/me/bills/subscription_invoice => :controller => :bills, :action => :subscription_invoice
which is why I wanted to create a block :bills in it.
I've been trying so many possibilities around, can't I achieve that?
The way paths are constructed is different than the way the controller is determined.
Controllers can be organized under a namespace. The namespace is a folder that controllers can sit in. Although the order of resources and namespaces affects the order of the parts of the path, it won't affect the way that namespaces are treated as a controller grouping. The resource is :groups so the controller is named "groups" and handles actions made against the group model. The namespace is :bills so the controller is contained within a folder called "bills".
In a similar fashion, if you nested two resources like:
resources :groups do
resources :users
end
The path for a user action would be /groups/:id/users but the controller would just be named "users".
If you really want this action to be handled by a bills controller in a groups folder, then you could customize the behavior with something like:
resources :groups do
member do
scope module: 'groups' do
get 'bills/consumption_invoice', :format => :pdf, controller: 'bills'
end
end
end
Additional Info for Edit in the Question
If you just want your two paths to reach the bills#consumption_invoice action and you don't want your bills controller to live in a folder called me, you could consider something like this:
get 'me/bills/consumption_invoice', :format => :pdf, to: 'bills#consumption_invoice'
resources :groups do
member do
get 'bills/consumption_invoice', :format => :pdf, controller: 'bills'
end
end
Rails will then expect that your consumption_invoice view lives in a bills folder.
The Rails Routing from the Outside In (http://guides.rubyonrails.org/routing.html) can provide additional details on all the routing options.

rails routing aliases

I have a model called Spaces which has different types of places... such as Bars, Restaurants, etc. It has the same columns, same, model, controller, etc. no fancy STI, I just have one field called Space_type which I would like to determine an aliased route.
Instead of domain.com/spaces/12345 it would be /bars/12345 or /clubs/12345
Currently I have:
resources :spaces do
collection do
get :update_availables
get :update_search
get :autocomplete
end
member do
post :publish
post :scrape
end
resources :photos do
collection do
put :sort
end
end
resources :reviews
end
Also, Is there a way I can do this so that anytime I use the space_url it can figure out which one to use?
The routes are not a way to interact with your model directly. So, as long as you write a standard route, you can make things work. For instance, to make /bars/12345 and /clubs/12345 for your spaces_controller (or whatever the name of the controller is) , you can create routes like :
scope :path => '/bars', :controller => :spaces do
get '/:id' => :show_bars, :as => 'bar'
end
scope :path => '/clubs', :controller => :spaces do
get '/:id' => :show_clubs, :as => 'clubs'
end
# routes.rb
match "/:space_type/:id", :to => "spaces#show", :as => :space_type
# linking
link_to "My space", space_type_path(#space.space_type, #space.id)
which will generate this urls: /bars/123, /clubs/1 ... any space_type you have
And it looks like STI wold do this job little cleaner ;)
UPD
Also you can add constraints to prevent some collisions:
match "/:space_type/:id", :to => "spaces#show", :as => :space_type, :constraints => { :space_type => /bars|clubs|hotels/ }
And yes - it is good idea to put this rout in the bottom of all other routes
You can also wrap it as a helper (and rewrite your default space_url):
module SpacesHelper
def mod_space_url(space, *attrs)
# I don't know if you need to pluralize your space_type: space.space_type.pluralize
space_type_url(space.space_type, space.id, attrs)
end
end

Setting up restful routes as a total newb

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.

Rails 3 routing - what's best practice?

I'm trying out Rails, and I've stumbled across an issue with my routing.
I have a controller named "Account" (singular), which should handle various settings for the currently logged in user.
class AccountController < ApplicationController
def index
end
def settings
end
def email_settings
end
end
How would I set-up the routes for this in a proper manner? At the moment I have:
match 'account(/:action)', :to => 'account', :as => 'account'
This however does not automagically produce methods like account_settings_path but only account_path
Is there any better practice of doing this? Remember the Account controller doesn't represent a controller for an ActiveModel.
If this is in fact the best practice, how would I generate links in my views for the actions? url_to :controller => :account, :action => :email_settings ?
Thanks!
To get named URLs to use in your views, you need to specify each route to be named in routes.rb.
match 'account', :to => 'account#index'
match 'account/settings', :to => 'account#settings'
match 'account/email_settings', :to => 'account#email_settings'
Or
scope :account, :path => 'account', :name_prefix => :account do
match '', :to => :index, :as => :index
match 'settings', :to => :settings
match 'email_settings', :to => :email_settings
end
Either works the same, it's just a matter of choice. But I do think the first method is the cleanest even if it isn't as DRY.
You could also define it as a collection on the resource:
resources :login do
collection { get :reminder, :test }
end
Of course, this also defines the default CRUD actions as well. I'm currently only using two of those for my not-an-actual-model controller, but I don't think/expect there will be any problem with the extra routes.

Ruby on Rails conditional routing

I have a site listing many jobs, but I also want each account to be able to access its jobs in one place. Thus, I am using these routes:
map.resources :jobs
map.resource :account, :has_many => :jobs
This gets me URLs like localhost/jobs/ and localhost/account/jobs. However, both seem to render JobsController::index. How can I either make a conditional in the index action (how do I access whether account/jobs or just jobs was specified in the URL?) or change the account route to render a different action? What's the proper way to do this?
You can use a block when creating your routes, and then pass a :controller parameter, like so
map.resource :account do |account|
# If you have a special controller 'AccountJobsController'
account.resources :jobs, :controller => "account_jobs"
end
It may be cleaner for you to put your controllers into a directory structure, and then you can reference them in a nested way. For example:
map.resource :account do |account|
account.resources :jobs, :controller => "accounts/jobs"
end
If you use the above snippet, you should then create a controller in app/controllers/accounts/jobs_controller.rb, which is defined like so:
class Account::JobsController < ApplicationController
##
## etc.
##
end
You can always use rake routes to check which routes have been generated and which controllers they'll use.
Adding a requirement to the resource definition allows you to pass extra parameters
map.resources :jobs
map.resource :account, :has_many => :jobs, :requirements => {:account => true}
Then params[:account] will be set if the routing url was 'http://www.mysite.tld/account/jobs' and unset if it it was 'http://www.mysite.tld/jobs'
As with all other restful routing the action depends on the context.
GET requests without an id route to index.
GET requests with an id route to show
POST requests route to create
PUT requests route to update
DELETE requests route to destroy.
If you run "rake routes" you should see something like this
account_jobs GET /accounts/:account_id/jobs/:job_id {:controller => 'jobs', :action => 'index'}
This means when your action is called via the /account/jobs route you should have an :account_id parameter. You can then do your logic switch based on the existence of this param:
if params[:account_id].nil?
...
else
...
end

Resources