#Valid Route
Purpose
valid_route ensures that your routes that are dynamically generated do not conflict.
Requirements
- rails >= 4.0.0.beta1
- valid_route expects that your model's records can be found by passing the results of Model.to_param into Model.find() and Model.exists?()
- You can implement the methods yourself (see below) or use a gem such as friendly_id to accomplish this for you.
Usage
Let's assume you want valid a Page model with a :permalink property that dynamically determines what URL the page is accessible at.
Gemfile:
gem 'valid_route'
page.rb:
class Page < ActiveRecord::Base
# rails g scaffold Page name:string permalink:string content:text
validates :permalink, :route => true
# The code below is not necessary if you're using a gem such as friendly_id to accomplish this for you.
validates_format_of :permalink, :without => /^\d/, :multiline => true
def self.find(input)
input.to_i == 0 ? find_by_permalink(input) : super(input)
end
def self.exists?(input)
input.to_i == 0 ? super(permalink: input) : super(input)
end
def to_param # overridden
self.permalink
end
end
That's it! Your routes are now protected against conflicts for all dynamic routes. Let's look at an example:
routes.rb:
MyGreatApp::Application.routes.draw do
resources :users
resources :pages
root :to => 'pages#show', :id => 'home'
match 'home' => redirect("/"), :via => :get
resources :pages, :only => [:show], :path => "/", :as => :page_permalink
end
You'll automatically be protected from overwriting routes already defined in routes.rb:
p = Page.new
p.permalink = "about"
p.valid? # => true
Resourceful routes taken:
p.permalink = "pages"
p.valid? # => false
p.permalink = "users"
p.valid? # => false
Other records for the Page model that have already been taken:
Page.exists?("home") # => false
p.permalink = "home"
p.valid? # => true
p.save # TODO: WHAT DOES THIS ACTUALLY RETURN?
p = Page.new
Page.exists?("home") # => true
p.permalink = "home"
p.valid? # => false
You can also check across multiple models:
app/constraints/page_constraint.rb:
# http://robotslacker.com/2012/01/rails-3-routes-configuration-dynamic-segments-constraints-and-scope/
class PageConstraint
def initialize
end
def matches?(request)
Page.exists?(request.path_parameters[:id])
end
end
user.rb:
class User < ActiveRecord::Base
# rails g scaffold User username:string password:string first_name:string
validates :username, :route => true
validates_format_of :username, :without => /^\d/, :multiline => true
def self.find(input)
input.to_i == 0 ? find_by_username(input) : super(input)
end
def self.exists?(input)
input.to_i == 0 ? super(username: input) : super(input)
end
def to_param
self.username
end
end
routes.rb:
Dummy::Application.routes.draw do
resources :users
resources :pages
root :to => 'pages#show', :id => 'home'
match 'contact_us' => 'pages#show', :id => 'contact', :via => :get
match 'home' => redirect("/"), :via => :get
resources :pages, :only => [:show], :path => "/", :as => :page_permalink, :constraints => PageConstraint.new
resources :users, :only => [:show], :path => "/", :as => :user_profile
end
Let's take a look:
p = Page.new
Page.exists?("home") # => false
p.permalink = "home"
p.valid? # => true
p.save
p = Page.new
Page.exists?("about") # => false
p.permalink = "about"
p.valid? # => true
p.save
u = User.new
u.username = "coolperson"
u.valid? # => true
u.save
u = User.new
u.username = "home"
u.valid? # => false
u.username = "about"
u.valid? # => false
u.username = "pages"
u.valid? # => false
p = Page.new
p.permalink = "coolperson"
p.valid? # => false
Reserving Additional Routes
You can also reserve additional routes to prevent them from being used in a particular model:
page.rb:
class Page < ActiveRecord::Base
# rails g scaffold Page name:string permalink:string content:text
validates :permalink, :route => {reserved_routes: ["reserved", "fake_page"]}
# ...
end
p = Page.new
p.permalink = "fake_page"
p.valid? # => false
Unreserving Routes
Conversely, if you want to avoid checking certain routes for validity, you can do so as well:
page.rb:
class Page < ActiveRecord::Base
# rails g scaffold Page name:string permalink:string content:text
validates :permalink, :route => {reserved_routes: ["reserved", "fake_page"], unreserved_routes: ["users"]}
# ...
end
p = Page.new
p.permalink = "users"
p.valid? # => true
Credits
Note on Patches/Pull Requests
-
Fork the project.
-
Make your feature addition or bug fix.
-
Add tests for it. This is important so I don't break it in a future version unintentionally.
-
Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
-
Send me a pull request. Bonus points for topic branches.
Copyright
Copyright © 2013 Vince Montalbano. See LICENSE for details