Question

I've got a RESTful setup for the routes in a Rails app using text permalinks as the ID for resources.

In addition, there are a few special named routes as well which overlap with the named resource e.g.:

# bunch of special URLs for one off views to be exposed, not RESTful
map.connect '/products/specials', :controller => 'products', :action => 'specials'
map.connect '/products/new-in-stock', :controller => 'products', :action => 'new_in_stock'

# the real resource where the products are exposed at
map.resources :products

The Product model is using permalink_fu to generate permalinks based on the name, and ProductsController does a lookup on the permalink field when accessing. That all works fine.

However when creating new Product records in the database, I want to validate that the generated permalink does not overlap with a special URL.

If a user tries to create a product named specials or new-in-stock or even a normal Rails RESTful resource method like new or edit, I want the controller to lookup the routing configuration, set errors on the model object, fail validation for the new record, and not save it.

I could hard code a list of known illegal permalink names, but it seems messy to do it that way. I'd prefer to hook into the routing to do it automatically.

(controller and model names changed to protect the innocent and make it easier to answer, the actual setup is more complicated than this example)

Was it helpful?

Solution

Well, this works, but I'm not sure how pretty it is. Main issue is mixing controller/routing logic into the model. Basically, you can add a custom validation on the model to check it. This is using undocumented routing methods, so I'm not sure how stable it'll be going forward. Anyone got better ideas?

class Product < ActiveRecord::Base
  #... other logic and stuff here...

  validate :generated_permalink_is_not_reserved

  def generated_permalink_is_not_reserved
    create_unique_permalink # permalink_fu method to set up permalink
    #TODO feels really ugly having controller/routing logic in the model. Maybe extract this out and inject it somehow so the model doesn't depend on routing
    unless ActionController::Routing::Routes.recognize_path("/products/#{permalink}", :method => :get) == {:controller => 'products', :id => permalink, :action => 'show'}
      errors.add(:name, "is reserved")
    end
  end
end

OTHER TIPS

You can use a route that would not otherwise exist. This way it won't make any difference if someone chooses a reserved word for a title or not.

map.product_view '/product_view/:permalink', :controller => 'products', :action => 'view'

And in your views:

product_view_path(:permalink => @product.permalink)

It's a better practice to manage URIs explicitly yourself for reasons like this, and to avoid accidentally exposing routes you don't want to.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top