Collecting first segments of all routes in rails 3

2019-06-10 04:54发布

问题:

I'm trying to implement routes where the first segment is the profile's alias or id:

resources :profiles, :path => '' do
    ...
end

And I need to validate that alias is not already taken by first segments of other(higher) routes. What I have now is:

validates :alias, :exclusion => {:in => Rails.application.routes.routes.map{|r| r.path.spec.to_s.split('(').first.split('/').second}.compact.uniq },
                  ....

In development everything is ok. In production Rails.application.routes.routes.map... returns empty array. But only inside validation in model, if I put it somewhere in view just to test it returns array of first segments of all routes as expected. What am I doing wrong or maybe there is a better solution?

回答1:

I'd guess that you have a timing problem. Your routing table in Rails.application.routes probably hasn't been built when your model is loaded in production mode; but, in development mode, your model is probably being reloaded on each request so Rails.application.routes will be populated when your model is loaded and your validates call:

validates :alias, :exclusion => { :in => Rails.application.routes.routes.map { ... } }

is executed.

An easy solution would be to switch to a validation method:

class Model < ActiveRecord::Base
    validate :alias_isnt_a_route, :if => :alias_changed?

private

    def alias_isnt_a_route
        routes = Rails.application.routes.routes.map { ... }
        if(routes.include?(alias))
            errors.add(:alias, "Alias #{alias} is already used for a route")
    end
end

This way, you don't look at Rails.application.routes until you need to check an alias and by that time, the routes will have been loaded. You could, of course, cache the route prefix list if you wanted to.

You'll also want to add some sanity checking to your application's initialization phase. Someone in your production environment could add, say, 'pancakes' as their alias while you add a /pancakes route while developing: your validation will miss this new conflict. Something simple like this:

config.after_initialize do
    Rails.application.reload_routes! # Make sure the routes have been loaded.
    # Compare all the existing aliases against the route prefixes and raise an
    # exception if there is a conflict.
end

in your config/application.rb would be sufficient.