Can't figure out how Rails route helper should look - ruby-on-rails

I am working on an assignment which includes adding a feature to Typo.
rake routes shows:
admin_content /admin/content {:controller=>"admin/content", :action=>"index"}
/admin/content(/:action(/:id)) {:action=>nil, :id=>nil, :controller=>"admin/content"}
I need to create a route helper which matches the following RESTful route: /admin/content/edit/:id and an example of url is /admin/content/edit/1
But I can't figure out how to do it. I tried something like admin_content_path(edit,some_article) but it didn't work. (some_article is just an article object)
In routes.rb file:
# some other code
# Admin/XController
%w{advanced cache categories comments content profiles feedback general pages
resources sidebar textfilters themes trackbacks users settings tags redirects seo post_types }.each do |i|
match "/admin/#{i}", :to => "admin/#{i}#index", :format => false
match "/admin/#{i}(/:action(/:id))", :to => "admin/#{i}", :action => nil, :id => nil, :format => false
end
#some other code
Thanks a lot for your help!

If you are using RESTful routes, why not use the Rails default routes?
So your routes.rb would look like
namespace :admin do
resources :content
resources :advanced
resources :categories
resources :comments
...
<etc>
end
This does assume all your controllers are in the folder admin (but from your comment this seems to be the case.
If you do that, you can just use the standard route-helper: edit_admin_content_path.
If you want to do it manually, you should try adding a name to your route. E.g. as follows:
match "/admin/#{i}/:action(/:id)" => "admin/#{i}", :as => "admin_#{i}_with_action"
and then you should do something like
admin_content_with_action(:action => 'edit', :id => whatevvvva)
As a side-note: I really do not like the meta-programming in your config/routes.rb, if for whatever you really find that the default resources are not a right fit, I would advise to use methods instead (as explained here)
So for example in your config/routes.rb you would write:
def add_my_resource(resource_name)
match "/#{resource_name}", :to => "#{resource_name}#index", :format => false
match "/#{resource_name}(/:action(/:id))", :to => "#{resource_name}", :as => 'admin_#{resource_name}_with_action", :action => nil, :id => nil, :format => false
end
namespace :admin do
add_my_resource :content
add_my_resource :advanced
add_my_resource :categories
...
end
which imho is much more readable.
But my advice, unless you really-really need to avoid it, would be to use the standard resources since you do not seem to add anything special.
HTH.

Related

Rails 3: making a catch-all route easier to read and amend

I'm trying to write a catch-all route in Rails 3, but I want to reserve some terms in it. I'm specifically following the example put forth in this post, in the answer by David Burrows: Dynamic routes with Rails 3
The syntax I am using is the following:
match '*path' => 'router#routing', :constraints => lambda{|req| (req.env["REQUEST_PATH"] =~ /(users|my-stuff)/).nil? }
Now, that syntax works just fine - if a user visits a page with "user" or "my-stuff" in the path, it falls through the catch-all and goes to a specific place. If the user goes to any other URL, it goes to my routing logic.
My question is more about readability - is there a way I can match the route against something other than a regex? Is there a way to provide an array of terms to match against? Also, is there a way to match specific segments of the route, as opposed to the entire thing?
Obviously Rails has built-in routing, but this project has a requirement that for certain routes, the controller not be present in the URL. Hence, the catch-all.
Thanks for any help
Here's the updated routes file per the answer below:
class RouteConstraint
RESERVED_ROUTES = ['users', 'my-stuff']
def matches?(request)
!RESERVED_ROUTES.map {|r| request.path.include?(r)}.empty?
end
end
App::Application.routes.draw do
resources :categories
resources :sites
match '*path' => 'router#routing', :constraints => RouteConstraint.new
devise_for :users, :path_names =>{ :sign_in => 'login', :sign_out => 'logout', :registration => 'register' }
root :to => "router#routing"
end
You can use a class to specify the constraints if you want something cleaner once you have multiple routes to try:
class MyConstraint
BYPASSED_ROUTES = ['users', 'my-stuff']
def matches?(request)
BYPASSED_ROUTES.map {|r| request.path.include?(r)} .empty?
end
end
TwitterClone::Application.routes.draw do
match "*path" => "router#routing", :constraints => MyConstraint.new
end
This example is adapted from the rails routing guide.
It's taking a lambda; you can use whatever criteria you want.

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.

Can controller names in RESTful routes be optional?

With a standard map.resource routing mechanics and several nested resources the resultant routes are unnecessarily long. Consider the following route:
site.org/users/pavelshved/blogs/blogging-horror/posts/12345
It's easy to create in routes.rb, and I'm sure it follows some kind of beneficial routing logic. But it's way too long and also seems like it's not intended to be human-readable.
A nice improvement would be to drop controller names, so it looks like:
site.org/pavelshved/blogging-horror/12345
Clear, simple, short. It may become ambiguous, but in my case I'm not going to name any user "users", for instance.
I tried setting :as => '', but it yields routes like this: site.org//pavelshved//blogging-horror//12345 when generating them by standard helpers.
Is there a way to map resources in such a way, that controller names become optional?
You're looking for the :path_prefix option for resources.
map.resources :users do |user|
user.resources :blogs do |blog|
blog.resources :posts, :path_prefix => '/:user_login/:blog_title/:id'
end
end
Will produce restful routes for all blogs of this form: site.org/pavelshved/bogging-horror/posts/1234. You'll need to go to a little extra effort to use the url helpers but nothing a wrapper of your own couldn't quickly fix.
The only way to get rid of the posts part of the url is with named routes, but those require some duplication to make restful. And you'll run into the same problems when trying to use route helpers.
The simplest way to get what you want would be to create a route in addition to your RESTful routes that acts as a shorthand:
map.short_blog ':user_id/:blog_id/:id', :controller => 'posts', :action => 'show'
You'll have to change the URL bits to work with how you're filtering the name of the user and the name of their blog. But then when you want to use the shorter URL you can use all the short_blog_* magic.
Straight out of the default routes.rb:
map.connect 'products/:id', :controller => 'catalog', :action => 'view'
You could write:
map.connect ':user_id/:blog_id/:id', :controller => 'posts', :action => 'show'
But be sure to include that in the very end of the file, or it will try to match every three levels deep url to it.
Try this
map.pavelshved '/pavelshved/', :controller => :users, :action => view or
map.pavelshved '/:id', :controller => :users, :action => show do | blogs|
blogs.bloging '/:id', :controller => :blogs, :action => show do | post|
post.posting '/:id', :controller => :posts, :action => show
end
end
I hope it work :)
Google "rails shallow routes" for information about this.

Resources