How to handle unpredictable routes? - ruby-on-rails

Assume the following paths to be legitimate and resolving:
http://test.local/wizards/home
http://test.local/wizards/wizardfest2012/dates
http://test.local/dragons/
http://test.local/dragons/blog/stop-slaying-us
http://test.local/
This is (if you couldn't tell) for a CMS that includes a blog, so the slugs would be generated by the user. I have some routes to process first for reserved namespaces (admin, for example).
I assume that the user generated routes need to be routed to a Page controller - but, I don't think pragmatically adding a line to routes.rb is efficient. My question then, is how do I process the first part of the params (in this case, wizards and dragons) to get the correct information from the model?
Here's one of my ideas - split (somehow) the first part of the slug (again, wizards and dragons and pass the rest of the slug (for example, /wizardfest2012/dates) to the model to fetch the associated content.
Any thoughts on the most efficient way to do this?

I am not sure whether I understand what you want to achieve, but maybe this is what you want:
constraints :camp => /wizards|dragons/ do
match ':camp/home' => "pages#home"
match ':camp/blog/:title' => "pages#blog"
# ...and all the routes with known components
match ':camp/*other' => "pages#other"
end
You may create a before_filter which will recognize the params[:camp] and prepare the necessary models or whatever is needed.
The other action will receive the string "wizardfest2012/dates" as params[:other]. I hope that it was what you needed.
The "Rails Routing from the Outside In" guide may be worth reading, unless you have already read it.

Related

Rails route only if resource is present

I want to have a rails route that triggers if the given id is present, but falls back to further route matching if not.
Specifically, I want to have promo codes at the root level of my site. So, you can go to foo.com/save87 or foo.com/xmasspecial, and it'll treat it as a promo code, but if you go to a promo code that's not there (foo.com/badcode), that route will not match and rails will continue down the route list.
In an ideal world, I'd do something like this in my routes.rb file:
get '/:promo_id' => 'promos#show, constraints => lambda { Promo.exists?(promo_id) }
I know that the above code, without the constraints, would work as a catch-all for foo.com/*, and would sorta work if I put it as the last line in the routes file. Unfortunately, that would result in foo.com/badcode a 'promocode not found' error, rather than a normal 'route not found' error.
So, is there a way to accomplish what I'm trying to accomplish? This is in Rails 3, for reference.
Edit: To clarify a bit-
I want a wildcard url as described above so that our promocode urls are short and memorable (foo.com/save87 instead of foo.com/promo_codes/save87)
I'd prefer to have the option of having other routes after this one. I may, at some point, need another wildcard url at the root level- for example, if I want vanity urls for another resource in my system. For example, if I sell a dozen varieties of widgets, I might want foo.com/widget_deluxe, foo.com/widget_extreme, etc in addition to my promo code urls. I'd have to make sure that there's no collision between promo codes and widget varieties, but that's easily handled elsewhere.
In an ideal world, I'd do something like this in my routes.rb file:
No way. In Rails World, this functionality should go inside controller.
In controller you can do something like
def show
if Promo.exists?(promo_id)
#do something
else
raise ActionController::RoutingError.new('Not Found')
end
end
Update
With routes, you can do something like this
constraints(lambda { |req| Promo.exists?(req.params["promo_id"]) }) do
get '/:promo_id' => 'promos#show
end
Please keep in mind that this constraints will query the database for every request with a url matching the pattern /:promo_id (e.q. /users, /faq). To avoid unnecessary database queries that decrease your website performance, you should add this rule as far as possible to the end of your routes.rb.
Using this routing logic, every request to your application would do an extra search for a promo code before it moved on to the rest of the routes. I recommend looking at your business case and consider doing a Promo controller. If you must do routes, something like this would work but I would put it at the end so that it goes to your regular routes first.
get '*', to: 'promos#show'

Intricate Rails 3 Routing Configuration - Can routes.rb Access Initializers' Code?

I'm converting a bunch of landing pages written in php in order to add them to my RoR-based site (that's been live for more than 2 years now). These landing pages are divided into several versions, but unfortunately there is no consistency as far as URL names go. My problem is that the php pages I'm converting already have a high page rank, therefore I'd like to keep their URLs exactly the way it was.
I'm not sure how to set my routes.rb so that example.com/* will always go to my homepage; however, when (* == 'name-of-one-of-the-landing-pages') Rails will route to a separate controller, where a specific action will determine which page to render, based on an Initializer and the params hash, all this while the URL is, as mentioned, identical to what it was prior to the php-to-RoR conversion, namely www.example.com/name_of_landing_page, rather than www.example.com/*controller_name*/name_of_landing_page.
I know of the :path property that enables one to exclude the controller name from the path if passed an empty string (i.e. resources :examples, :path => ''), but that doesn't quite solve the entire problem.
I was thinking about writing an initializer that would hold a hash of all relevant landing pages, and using constraints in routes.rb to check against it, but I'm not sure if this kind of implementation is possible and how to go about it. A code example would be much appreciated.
Is there some kind of syntax for routes.rb that would enable me to do so, or perhaps a better solution?
To answer the first question: in routes.rb, inside the do/end block you will actually be in the context of ActionDispatch::Routing::Mapper, so no you won't. But, right after that block, you are back to the top level of your application and will have access to whatever variables you initialized inside your initializers, however, that code might be better suited to go in application.rb.
The only thing you should be doing in routes.rb is defining routes.
You could also handle the request for the legacy pages in rack
def call(env)
request = Rack::Request.new(env)
return [200, {"Location" => request.url("http://www.example.com")} if request.host == "www.oldpage.com"
end
More info here: http://railscasts.com/episodes/222-rack-in-rails-3
I don't understand why complex things involved. Setting such should be very simple in route.
Suppose your have a controller to handle static pages named "PagesController"
get 'name-of-one-of-the-landing-pages-a', to: 'pages#a'
get 'name-of-one-of-the-landing-pages-b', to: 'pages#b'
There is no need to add controller names in the path. You can control all of them.

Drupal-like routing system in Rails

I am trying to find a best-practice to allow users to define the route to their pages in Rails, by writing them in a text field when submitting posts, like with the Path module in Drupal (yes, we are porting a Drupal site to Rails)
So, I need to
define a new, named route on article submission (eg http://www.domain.com/a-day-in-annas-life)
change the existing route on article edit, if they define a new one, by doing a 301 redirect from the old route to the new one
How can I best achieve this?
Okay, I found a way, but if it's best practice or not, I cant say.
I am using custom restrictor's like this:
class CharitiesRestrictor
def self.matches?(request)
slug = request.path_parameters[:path]
!Charity.find_by_name(slug).nil?
end
end
constraints CharitiesRestrictor do
match '*path' => 'charities#show_by_slug', :constraints => CharitiesRestrictor.new
end
When I create a block like this for each of my model/controller pairs that should be able to respond to permalinks, I can have them all have a chance to act on a permalink. However, this also means that they all get called in series, which is not necessarily ideal.

Rails 2 Trailing Route Parameter

I'm building an article/blog website where any article may show up within a series of URL paths.
/section/article
/section/page/article
/section/page/page2/article
This works, but what if I wanted to have the page number for that article (/page/123) be bound to those URLs
/section/article/page/123/
/section/page/article/page/123
/section/page/page2/article/page/123
This would mean that I would have to create a specific route for each different url?
/:section/:page/:sub_page/:article/page/:page
This would mean that I would create dozens of URL routing paramters.
Is there anyway in rails to say that all urls may have a /page/NUMBER suffix at the end of the URL and still route normally (that is assign the NUMBER to a parameter and continue to goto the page normally)?
Route globbing, described at http://guides.rubyonrails.org/routing.html#route-globbing, might work in this situation. For example, your route might read map.connect '/:section/*page_subpage_path/page/:number', :controller => 'articles', :action => 'show'
This exact code might not work as intended, but this method might be a good direction to try. Good luck :)
If you want to create routes that are as customized as that you normally need to create a large number of routes to accommodate them. The general format is /resource/:id when using map.resource, anything other than that is left to you to specify.
Given that the Rails routes.rb file is executable ruby you can often define your routes programmatically by repeating patterns or doing combinations on arrays if required.

Why do I need to work harder to make my Rails application fit into a RESTful architecture?

I started a Rails project recently and decided to use RESTful controllers. I created controllers for my key entities (such as Country) and added index, new, edit, create, show, update and delete. I added my map.resources :country to my routes file and life was good.
After development progressed a little, I started to encounter problems. I sometimes needed extra actions in my controller. First there was the search action that returned the options for my fancy autocompleting search box. Then came the need to display the countries in two different ways in different places in the application (the data displayed was different too, so it wasn't just two views) - I added the index_full action. Then I wanted to show a country by name in the URL, not by id so I added the show_by_name action.
What do you do when you need actions beyond the standard index, new, edit, create, show, update, delete in a RESTful controller in Rails? Do I need to add (and maintain) manual routes in the routes.rb file (which is a pain), do they go in a different controller, do I become unRESTful or am I missing something fundamental?
I guess I am asking, do I need to work harder and add actions into my routes.rb file for the privilege of being RESTful? If I wasn't using map.resources to add the REST goodies, the standard :controller/:action, :controller/:action/:id routes would handle pretty much everything automatically.
I would treat search as a special case of index. Both actions return a collection of resources. The request parameters should specify things like page, limit, sort order, and search query.
For example:
/resources/index # normal index
/resources/index?query=foo # search for 'foo'
And in resources_controller:
before_filter :do_some_preprocessing_on_parameters
def index
#resources = Resource.find_by_param(#preprocessed_params)
end
As for index_full and search_by_name, you might look at splitting your current controller into two. There's a smell about what you've described.
Having said that, you're absolutely right that there's no point in forcing your app to user restful routes when it doesn't deliver anything over /:controller/:action/:id. To make the decision, look how frequently you're using the restful resource route helpers in forms and links. If you're not using them, I wouldn't bother with it.
If I go beyond the standard CRUD actions with my models, I normally just add the methods as required. Searching is something I add to many controllers, but not every one, so I add it and maintain the routes normally:
map.resources :events, :collection => { :search => :get }
Moving these actions to an entirely separate controller might keep some of your controllers RESTful, but I find that keeping them in context is far more useful.
REST does not specify that you can't have additional views. No real world application is going to be able use only the supplied actions; this is why you can add your own actions.
REST is about being able to make stateless calls to the server. Your search action is stateless each time as the data so far is supplied back, correct? Your alternate display action is also stateless, just a different view.
As to if they should be manual routes or a new controller, that depends on how distinct the activity is. Your alternate view, if it provides a full set of CRUD (create, read, update, delete) operations would do well to be in a new controller. If you only have an alternate view to the data, I would just add an alternate view action.
In other words, it doesn't sound like your application is failing to be RESTful, it is more an issue of realizing that the automatically generated feature set is a starting point, not a conclusion.
In my opinion they may have gone a bit off the rails here. What happened to DRY?
I'm just getting back into Rails not having done much development with it since beta and I'm still waiting for the light-bulb to come on here. I'm still giving it a chance but if it hasn't happened for me by the end of my current project I'll probably just drop-back to the old standard routes and define the methods as I actually need them for the next one.
I won't go on to explain more about REST since I think that has been answered in this question, however I will talk a little bit about the default route.
My main problem with the default route is that if you have multiple sites using the same Rails app it can look horrible.
For example there may be controllers that you don't want people to be able to see on one app:
http://example1.somesite.com/example_2/foo/bar/1
compare this to
/:controller/:action/:id
This would go to the controller example_2/foo, action bar and id 1
I consider this to be the main flaw of Rails' default route and this is something that RESTful routes (with subdomain extensions) or only named routes (map.connect 'foo' ... ) can fix.
To remain RESTful in your design, you need to rethink what you call a resource.
In your example a show action for a search controller, (search resource) is the direction to remain restful.
In mine, I have a dashboard controller (show) and controllers for single fields of in-place ecditors (show and update)

Resources