make dynamic routes from content - ruby-on-rails

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.

Related

Is it possible to change the name of controller in URL in Rails

I have a controller
emppedes
Its fine when i want to call this in url i see
localhost:3000/emppedes
and whole the content related to it appears.
But can i match this controller to others name in rails so that when i click in it I can get in URL like
localhost:3000/employees
without changing the controller inside but outside in url you can see that.
I mean can i code somewhere like
`emppedes will display as employees in URL.`
Is it possible. May be my question is stupid but I want to know whether it is possible or not.
In edit i have two send two different id and in controller also for update i have to send two id.
For edit my code is like
<%= link_to 'Edit', { :action => "edit",:id => #emppede.id,:ad => #id},:class=>"btn btn-success" %> |
since i have two handle two id in controller
Also for update
#id= #emppede.ad
if #emppede.save
format.html { redirect_to :action => :index, :id => #emppede.ad }
format.json { render json: #emppede, status: :created, location: #emppede }
How can i send two params :id and :ad both in rails path formate?
Since my controller is like this
def index
#id = params[:id]
#data = Emppede.where(:ad => #id)
if #data.count > 0
#data.each do |data|
#ids= data.id
end
redirect_to :action => :show, :id => #ids, :ad => #id
else
redirect_to :action => :new, :id => #id
end
end
Yes, you can just specify the controller to use for any given resource:
resources :employees, :controller => "emppedes"
Or if you prefer the Ruby 1.9 syntax:
resources :employees, controller: "emppedes"
For more details, see Rails Guide: Routing.
What you will want to do is pass in the :path option
resources :employees, :path => "emppedes"
This replace all your route references with /employees to /emppedes
See: http://guides.rubyonrails.org/routing.html, Section 4.7 Translating Paths
You can use the following in routes.rb for your queries
match '/employees/:id/:ad(.:format)' => 'emppedes#index', :via => :get, :as => :employees
resources :emppedes, :as => 'employees'
try use inflections in your initialize file
ActiveSupport::Inflector.inflections do |inflect|
inflect.irregular 'employees', 'emppedes'
end

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.

Rails using routes.rb to redirect old URLs

I have an old site built in Coldfusion, a new site built in Rails. I would like to redirect the old URLs to the new URLs. I'm not sure if routes is the way to go or not (I'm a noob). The URLs will be very similar. This should be easy but I'm not sure of the best method.
Old URL:
mysite.com/this-is-the-slug-right-here/
New URL:
mysite.com/blog/this-is-the-slug-right-here
Here is the issue, I have 3 "content types". The old site url didn't make a distinction between the content types. The new Rails site has a controller for each of the content types: Blog, Photo, Mobile Photo.
So in the example above /blog/ is the controller (content type) and this-is-the-slug-right-here is the permalink or slug for the content. Which I am getting like this:
#content = Content.where(:permalink => params[:id]).first
Should I be using routes.rb, or do I need some kind of catch-all script? Any help in getting me pointed in the right direction would be greatly appreciated.
Edit to further clarify
Here is a blog post: http://jyoseph.com/treadmill-desk-walk-this-way/
The new URL for this would be /blog/treadmill-desk-walk-this-way because it is a content type of blog.
And a photo post: http://jyoseph.com/berries/
The new URL for this would be /photos/berries because it is a content type of photo.
The content type is an attribute on the content model, stored in the attribute content_type.
Here's my routes.rb file:
resources :contents
match 'mophoblog/:id', :to => 'mophoblog#show'
match 'photos/:id', :to => 'photos#show'
match 'blog/:id', :to => 'blog#show'
root :to => "index#index"
match ':controller(/:action(/:id(.:format)))'
Got it using #mark's answer, here's what I ended up with.
in my routes.rb
match ':id' => 'contents#redirect', :via => :get, :as => :id
in my contents controller:
def redirect
#content = Content.where(:permalink => params[:id]).first
if #content.content_type.eql?('Photo')
redirect_to "/photos/#{#content.permalink}", :status => :moved_permanently
elsif #content.content_type.eql?('Blog')
redirect_to "/blog/#{#content.permalink}", :status => :moved_permanently
elsif #content.content_type.eql?('MoPhoBlog')
redirect_to "/mophoblog/#{#content.permalink}", :status => :moved_permanently
end
end
I'm sure this could be improved upon, specifically the way I am redirecting, but this solved my problem perfectly.
You can't use routes.rb to do this, however it's simple enough to set up a route, get the content type and redirect.
Something like:
routes.rb
match.resources :photos
match.resources :mobile_photos
match.resources :blog
#everything_else all resource and named routes before
match ':article_id' => 'articles#redirect', :via => :get, :as => :article_redirect
#articles_controller.rb
def redirect
#content = Content.find params[:id]
if #content.content_type.eql?('photo')
redirect_to photo_path(#content), :status => :moved_permanently
elsif #content.content_type.eql?('mobile_photo')
redirect_to mobile_photo_path(#content), :status => :moved_permanently
...
end
Now it occurs to me as I write this that you probably only want one controller for all this?
Just thought people might be interested of a solution to do a more general redirect:
redirector = lambda do |env|
path = env['action_dispatch.request.path_parameters'][:path]
redirect("/new-url-base#{path}").call(env)
end
match 'old-url-base:path' => redirector, :constraints => {:path => /.*/}
redirect() just returns a simple rack app that returns the redirect headers, so wrapping it in a lambda allows you to change the arguments to it. It'd probably be better for maintenance to wrap it up in an object instead:
match 'old-url-base:path' => Redirector.new("new-url-base", :path), :constraints => {:path => /.*/}
Rails 4 and later have support for redirect built in:
http://guides.rubyonrails.org/routing.html#redirection

How can I create a Rails route to a permalink - without the controller in the URL?

I am currently using the Permalink_fu plugin which correctly creates the following URI:
http://localhost:3000/pages/some-permalink-to-page
I now want to configure my routing so that I can drop the /pages/ part from the URI leaving me with:
http://localhost:3000/some-permalink-to-page
I tried adding the following to the bottom of my config/routes.rb file:
map.connect ':permalink', :controller => 'page', :action => 'view'
but I get the following error, when I try the new URI:
uninitialized constant PageController
Do you have any suggestions? I'm running Rails 2.2.2 and am reluctant to try edge rails just yet.
Many thanks,
Ben...
In your route, should :controller be "pages" (plural)?
Thanks Mike, I'd made a number of errors. This is how I got it working.
In the routes.rb file add the following routing, near the bottom of the page:
map.connect ':id', :controller => 'pages', :action => 'show'
The problem is then now any bad URL is going to fail badly, e.g.
http://localhost:3000/this-permalink-doesnt-exist
Will lead to a failure not a 404 error.
I fixed this by adding the following line to my pages_controller.rb show action:
def show
#page = Page.find_by_permalink(params[:id])
if #page.nil? then
render :file => 'public/404.html', :status => '404'
else
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #page }
end
end
end
Now I get the correct behaviours for all variations of the URL:
http://localhost:3000/pages/some-permalink-to-page
http://localhost:3000/some-permalink-to-page
and the if an invalid permalink is entered
http://localhost:3000/no-such-permalink
gets rendered to the default public/404.html file.
Hope that helps anyone else, and thanks again Mike.
Ben...
There is a typo in your routes.rb entry:
map.connect ':permalink', :controller => 'page', :action => 'view'
It should read:
map.connect ':permalink', :controller => 'pages', :action => 'view'
The :controller parameter is the singular name of the controller, should be 'pages'

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