I've looked around for a while now, but I'm not sure how best to describe my request to google, so thought I'd ask here ;)
In rails, I know that when you nest restful routes, you generally get something like:
http://localhost/categories/1/articles/2
If you want something more meaningful, you can use slugs or friendly_id to get something like
http://localhost/categories/all-your-needs/articles/rock-out-with-this-article
(assuming you have unique names).
My question is, how can I remove the controller from the url rewriter so you get something like:
http://localhost/all-your-needs/rock-out-with-this-article
Is this possible?
Yes it is. You can use something like this:
Rails 2:
map.show_article ':category/:article', :controller => "articles", :action => "show"
Edit:
Ok. Here you have the urls for the other REST actions:
map.edit_article ':category/:article/edit', :controller => "articles", :action => "edit".
For update add :conditions => { :method => :post } to the previous one.
For delete, use the first one with :conditions => { :method => :delete }.
For new and create, you can use:
map.new_article ':category/new', :controller => "articles", :action => "new"
and for create the same but with :conditions => { :method => :post }. I hope I've been able to help you!
Related
I have two different models that can show up in a category. In my routes.rb, I would like to have something like this:
ActionController::Routing::Routes.draw do |map|
map.top_list ':category/:foo', :controller => 'foo', :action => 'show'
map.top_list ':category/:bar', :controller => 'bar', :action => 'show'
end
This works fine when I load a URL like "/some-category-name/some-foo-name", where "some-foo-name" can be loaded by the FooController like so:
class FooController < ApplicationController
def show
#foo = Foo.find_by_url! params[:foo]
end
end
But when I try to request a Bar, like "/some-category-name/some-bar-name", I get a "ActiveRecord::RecordNotFound in FooController#show". I know that I can solve this problem by requiring that all Foo names start with "foo-" and all Bar names start with "bar-", then defining routes like this:
ActionController::Routing::Routes.draw do |map|
map.top_list ':category/:foo', :controller => 'foo', :action => 'show', :requirements => { :name => /^foo-/ }
map.top_list ':category/:bar', :controller => 'bar', :action => 'show', :requirements => { :name => /^bar-/ }
end
But forcing this restriction on names is quite suboptimal. I found the following, which looks like it might work for me: Different routes but using the same controller for model subclasses in Rails. However, I don't quite follow the example, so I don't know if this would solve my problem. It is also not great that my Foo and BarController would have to inherit from CategoryController.
One thought that occurred to me is that I could try to look up the Foo, then fall back to the BarController if that fails. I can easily do this in the FooController and redirect to the BarController, but this is not really OK, since all the requests for Bars will be logged as FooController#show in the Rails log. However, if I could somehow configure the routes to call a method to determine what to route to based on the basename of the URL, I could get the behaviour that I need; e.g.
ActionController::Routing::Routes.draw do |map|
is_a_foo = Proc.new {|name| Foo.find_by_url! name && true }
map.top_list ':category/:foo', :controller => 'foo', :action => 'show', :requirements => { :name => is_a_foo }
map.top_list ':category/:bar', :controller => 'bar', :action => 'show'
end
Is there any way to do this in bog-standard Rails 2?
I ended up writing a little Rails plugin that allows me to write a route like this:
map.with_options :category => /[-A-Za-z0-9]+/ do |m|
# Since both foos and bars can appear under category, we define a route for foo that only matches when
# the foo can be found in the database, then fall back to the bar route
m.foo ':category/:foo', :controller => 'foo', :action => 'show', :conditions => {
:url => { :find_by_url => Foo }
}
m.bar ':category/:bar', :controller => 'bar', :action => 'show'
end
Until I get permission to open source the code, I have to leave the implementation as an exercise for the reader, but here's how I got there:
Monkey-patching Rails: Extending Routes #2
Under the hood: route recognition in Rails
The Complete Guide to Rails Plugins: Part II
I'll update this answer with a github link when I can. :)
This seems wrong since you are using the same route to two controllers. Rails will choose one only as you noticed.
It would be nicer to use something like this:
map.top_list ':category/foo/:foo', :controller => 'foo', :action => 'show', :requirements => { :name => is_a_foo }
map.top_list ':category/bar/:bar', :controller => 'bar', :action => 'show'
Since in provided routes you give two params each time. It does not matter that you name them differently. It just matches both.
I have a custom route (if I am doing that correctly, this is the first time I have done this) that looks like this:
map.connect 'purchases/type/:type', :controller => 'purchases', :action => 'index'
so I want to create a link_to that would use that url /purchases/type/(somenumber)
or I am completely open for a better way to do it.
Edit:
I am trying to use a category (type) to filter on the index. So if I click the link that would be /purchases/type/1 that would show all the items from type 1. I dont want this in the show, and I could do it with /purchases/?type=1, but im trying to make the urls look better.
Untested but I believe this is what you want...
map.purchase_type 'purchases/type/:type', :controller => 'purchases', :action => 'index'
Then
link_to 'foo', purchase_type_path(:type => 'your_type')
Good luck.
Based on http://www.tutorialspoint.com/ruby-on-rails-2.1/rails-routes.htm (section "named routes"), I'd try the following:
map.purchases_for_type 'purchases/type/:type', :controller => 'purchases', :action => 'index'
And I assume you'd then call it with link_to 'link text', purchases_for_type(#type_param)
For reference, I'll include the Rails3 way to do it:
match '/purchases/type/:type' => 'purchases#index', :as => "purchases_for_type", :via => "get"
Or better yet (RESTful):
match '/type/:type/purchases' => 'purchases#index', :as => "purchases_for_type", :via => "get"
You'd then call it with link_to 'link text', purchases_for_type(#type_param)
I am trying to set up my routes for the will paginate plugin so I don't have ?page=1 at the end of the url, and so I can later try to use page caching.
I've been browsing around online and I found a few tutorials explain to use map.connect, however, I am having trouble getting it to work with my application.
Here's an example url: http://localhost:3000/profile/1/browse?page=1
Here's the routes code I've got so far:
map.connect '/profile/:id/browse/:page',
:controller => 'profiles',
:action => 'browse',
:id => /\d+/,
:page => /\d+/
This doesn't work. Does anyone have any advice?
I thought map.connect was pattern matching, but maybe I am missing something.
Thank you,
Looking at your route, you might be crossing two different things. What are you paginating? If it's profiles, there's no need to supply an id. Let's assume you're trying to paginate profiles. Your route would look like this:
map.connect '/profiles/browse/:page',
:controller => 'profiles',
:action => 'index',
:page => /\d+/
And your controller action would look like this:
def index
#profiles = Profile.paginate :page => params[:page]
end
If you are trying to nest something within profiles, say browsing a profile's pictures, you'll need to do it more like this:
map.connect '/profiles/:id/browse/:page',
:controller => 'profiles',
:action => 'index',
:id => /\d+/,
:page => /\d+/
with your controller like so:
def index
#profile = Profile.find(params[:id])
#pictures = #profile.pictures.paginate :page => params[:page]
end
Let me know if this works.
UPDATE:
You listed in the comments that /profile/1/ is referring to the user's own profile. First, this is dangerous because you don't want people to change what profile the app thinks they are, just by changing that id number by hand. Rely on whatever current_user method your authentication gives you.
However, using your current setup as the example, this is what it would look like:
map.connect '/profiles/:id/browse/:page',
:controller => 'profiles',
:action => 'browse',
:id => /\d+/,
:page => /\d+/
with your controller like so:
def browse
#profile = Profile.find(params[:id])
#profiles = Profile.paginate :page => params[:page]
end
Let me know if you still have questions.
UPDATE 2
In order to get a nice link_to with this, change the route to a named route:
map.profile_browse '/profiles/:id/browse/:page',
:controller => 'profiles',
:action => 'browse',
:id => /\d+/,
:page => /\d+/
Now you can call link_to like so:
link_to profile_browse_path(:id => 1, :page => 10)
I am looking to do something similar a wordpress slug where I have a URL like this while maintaining RESTful routing:
http://foo.com/blog/2009/12/04/article-title
The reason I am interested in keep RESTFUL routing is that I am unable to use many plugins because I am using custom routes.
I have already done the RESTful appearance with:
map.connect '/blog/:year/:mon/:day/:slug',
:controller => 'posts', :action => 'show',
:year => /\d{4}/, :month => /\d{2}/,
:day => /\d{2}/, :slug => /.+/,
:requirements => { :year => /\d{4}/, :month => /\d{2}/, :day => /\d{2}/, :slug => /.+/ }
In order to write the links, I had to write custom link_to helpers to generate the proper URLs. I really would like to make this RESTful and have the link_to post_path( #post ) yield the URL above and the link_to edit_post_path(#post) ...article-title/edit
I also have :has_many => [:comments] and I would that to work as well. The link_to that I have tried looks like this:
'posts', :action => 'show', :year => recent_post.datetime.year.to_s,
:month => sprintf('%.2d', recent_post.datetime.mon.to_i),
:day => sprintf('%.2d', recent_post.datetime.mday.to_i),
:slug => recent_post.slug %>
and yields this (which isn't what I want):
http://foo.com/posts/show?day=30&month=11&slug=welcome-to-support-skydivers&year=2009
I'm not sure what I am doing wrong. Is it even possible to accomplish this?
I think it's not working because you're not using a custom route. I do this all of the time. I simply setup a simple custom route:
map.present_page '/blog/:year/:month/:day/:slug',
:controller => 'posts', :action => 'show'
Then you should be able to do:
present_page_path(:year => 2009,
:month => "December",
:day => "13",
:slug => "just-an-example")
The reason you're getting a query string is most likely because rails isn't making the connection to your route for whatever reason. Using a named route explicitly tells rails to use that route. Let me know if that solves it for you!
Here's how I went about this...
First, I'm not trying to use the route-generated url method. Also, I'm not going to the same extent as you in terms of checking formatting of the date parameters. Since I'm auto-generating the datestamps and the URL creation, I'm not concerned about format validity, I'm simply formatting a ActiveSupport::TimeWithZone object.
Let's start with the relevant route:
map.post_by_date 'content/:year/:month/:day/:slug',
:controller => 'posts',
:action => 'show_by_date_slug'
Since I didn't want to worry about argument formatting, or repetition, I created a helper method and included the helper in the relevant controller:
def pubdate_slug_url(post)
year = post.published_on.strftime('%Y')
month = post.published_on.strftime('%m')
day = post.published_on.strftime('%d')
url = "/" + ["content", year, month, day, post.slug].join("/")
return url
end
Finally, in my view, I simply call the method, passing in my Post object:
<h2><%= link_to post.headline, pubdate_slug_url(post) %></h2>
I end up with a url like: http://wallscorp.us/content/2009/12/06/links
Cheers.
I want to use page_cache with will_paginate.
There are good information on this page below.
http://railsenvy.com/2007/2/28/rails-caching-tutorial#pagination
http://railslab.newrelic.com/2009/02/05/episode-5-advanced-page-caching
I wrote routes.rb looks like:
map.connect '/products/page/:page', :controller => 'products', :action => 'index'
But, links of url are not changed to '/products/page/:page' which are in will_paginate helper.
They are still 'products?page=2'
How can i change url format is in will_paginate?
Is that route declared above any RESTful resources routes? That is, your route file should look like the following:
map.connnect '/products/page/:page', :controller => 'products', :action => 'index'
map.resources :products, :except => [:index]
If your routes look correct, you could try monkey-patching the way will_paginate generates the page links. It does so in WillPaginate::ViewHelpers#url_for(page). It's some fairly complex logic in order to handle some tricky edge cases, but you could write a new version that tried the simple version for your products first:
# in lib/cache_paginated_projects.rb
WillPaginate::ViewHelpers.class_eval do
old_url_for = method(:url_for)
define_method(:url_for) do |page|
if #template.params[:controller].to_s == 'products' && #template.params[:action].to_s == 'index'
#template.url_for :page => page
else
old_url_for.bind(self).call(page)
end
end
end
this works for me
app/helpers/custom_link_renderer.rb
class CustomLinkRenderer < WillPaginate::LinkRenderer
def page_link(page, text, attributes = {})
#template.link_to text, "#{#template.url_for(#url_params)}/page/#{page}", attributes
end
end
add this line to config/environment.rb file
WillPaginate::ViewHelpers.pagination_options[:renderer] = 'CustomLinkRenderer'
A little addition to the current answers, I had to spend hours to figure it out.
If you have some more complex routes, like for example including filtering in my case, make sure that the "higher level" routes come first (and not just that they are above the RESTful one), otherwise will_paginate picks up the first usable one and sticks the extra params at the end of the URL in a non-pretty way.
So in my case I ended up with this:
map.connect "wallpapers/:filter/page/:page", :controller => "wallpapers", :action => "index", :requirements => {:page => /\d+/, :filter => /(popular|featured)/ }
map.connect "wallpapers/page/:page", :controller => "wallpapers", :action => "index", :requirements => {:page => /\d+/ }
map.resources :wallpapers
So now I get pretty URLs like: wallpapers/popular/page/2 instead of wallpapers/page/2?filter=popular
Do this:
map.products_with_pages "/products/page/:page", :controller => "products", :action => "index"
You can even do it with a has_many ie: products has_many :items
map.resources :products do |product|
map.items_with_pages "/products/:id/page/:page", :controller => "products", :action => "show"
end
Then your controller could look like
def show
product = Product.find(params[:id])
#items = product.items.paginate :per_page => 5, :page => params[:page]
end
Which would give you a url like: http://domain.com/products/123/page/3 where 123 is the product id and 3 is the page id. You could also use permalinks and have the 123 id changed to a more seo friendly word.