Ruby on Rails will paginate routes - ruby-on-rails

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)

Related

How can I route to different controllers with the same URL format in Rails?

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.

What is the method for this route

In routes.rb I have
map.resources :groups, :collection => { :find_or_create => :get, :search => :get } do |group|
group.resources :recruitment_periods, :controller => 'groups/recruitment_periods' do |period|
period.resources :recruits, :controller => 'groups/recruitment_periods/recruits'
if I wanted to redirect to the show action of a specific group,recruit_period, recruit what would the path be?
i.e. redirect_to groups_recruitment_period_recruit_path(x,y,z)
I think you almost had it. It looks like it should be:
redirect_to group_recruitment_period_recruit_path(x,y,z)
No plural on group since you know which one.

will_paginate route only works on page > 1

I have the following routes defined:
map.resources :categories, :has_many => :downloads
map.resources :downloads, :member => {:go => :get}, :collection => {:tag => :get}
map.connect '/downlods/page/:page', :controller => 'downloads', :action => 'index'
map.connect '/categories/:category_id/downloads/page/:page', :controller => 'downloads', :action => 'index'
For some reason, the first page that the will_paginate helper is called on causes links with ?page=2 to be rendered, while subsequent pages have links with /downloads/page/2. Do you know what might be causing this?
If you simply declare a route with map.connect, it can be hit and miss as to how it's routed if you do something like:
link_to("Next", :page => 2)
What you might want to do is name the route and then use it that way:
map.downloads_paginated '/downloads/page/:page', :controller => 'downloads', :action => 'index'
Then you use the route by name:
link_to("Next", downloads_paginated_path(2))
These are much more reliable.
As a note, you have '/downlods' in your path instead of '/downloads' but I'm not sure that'd be causing the trouble described.

make dynamic routes from content

I am trying to setup dnamic routes in my rails application.
i.e.
I have a acts model that has a name attribute.
name:string.
What I a trying to do is use that name as my url.
in my route if have
map.connect 'blacktie/:id', :controller => 'acts', :action => 'show', :id => 3
That takes me to http://0.0.0.0:3000/blacktie
I know that i can do something along the lines of
def map.controller_actions(controller, actions)
actions.each do |action|
self.send("#{controller}_#{action}", "#{controller}/#{action}", :controller => controller, :action => action)
end
Just not sure if it is even possible.
Add the following to the bottom of your config/routes.rb
map.connect '*url', :controller => 'acts', :action => 'show_page'
Then define the following in app/controllers/acts_controller.rb
def show_page
url = params[:url]
if Array === url
url = url.join('/')
else
url = url.to_s
end
# you now have the path in url
#act = Acts.find_by_name(url)
if #act
render :action => :show
else
redirect_to some_error_page, :status => 404
end
end
A few gotchas with the above approach.
The route is a catch all. You will be trapping everything that doesn't match a route above it. So make sure it's last and make sure you are ready to handle 404s and the like.
The :url param is an array or a string depending on the route coming in. For example /blacktie/night will be an array with a value of ['blacktie', 'night']. That's why I joined them with in the beginning of show_page. So your find_by_name function could be really smart and allow for nested acts and the such.
Hope this helps.
OR...
Add this to routes (at the bottom):
map.connect ':name', :controller => "acts", :action => "show_page",
:requirements => {:name => /[\w|-]*/}
This tells rails to send anything matching the requirements to your handler. So your show_page would be like the following:
def show_page
#act = Acts.find_by_name(params[:name])
if #act
render :action => :show
else
redirect_to some_error_page, :status => 404
end
end
This gets rid of the some of the gotchas but gives you less options for nesting and the like.

How can I change the link format in will_paginate for page_cache in Ruby on Rails?

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.

Resources