I'm trying to think of a better way to write these routes in my routes.rb file:
Rails.application.routes.draw do
root 'home#index'
s = /section1|section2|section3|section4/
y = /\d{4}/
m = /\d{1,2}/
resources :articles, except: [:index, :show]
# The slug will replace the default params[:id] of the following resources:
resources :articles, only: [:index, :show], path: '/:section/:year/:month', as: :my_articles, constraints: {:section => s, :year => y, month: m}
get ':section/:year/:month', to: 'articles#by_month', as: :month, constraints: {section: s, year: y, month: m}
get ':section/:year', to: 'articles#by_year', as: :year, constraints: {section: s, year: y}
get ':section', to: 'articles#by_section', as: :section, constraints: {section: s}
end
This enables me to search articles by section, by year+section, by month+section+year. It also lets me add a nice slug to URLs.
One of the problems is that the constraints are common among all these routes. I'm wondering if there's a more clever way to go about writing this. Someone suggested I use concerns but I haven't figured out a way to do that.
Not a game changer, but I'm curious as to how I can write this properly.
2 Answers 2
Got it:
Rails.application.routes.draw do
root 'home#index'
resources :articles, except: [:index, :show]
constraints section: /section1|section2|section3|section4/ do
constraints year: /\d{4}/ do
constraints month: /\d{1,2}/ do
resources :articles, only: [:index, :show], path: '/:section/:year/:month', as: :my_articles
get ':section/:year/:month', to: 'articles#by_month', as: :month
end
get ':section/:year', to: 'articles#by_year', as: :year
end
get ':section', to: 'articles#by_section', as: :section
end
I feel this is much cleaner :)
I would not use routes constraints at all here and keep restful controller for simplicity and easy maintanance.
class ArticlesController < ApplicationController
def index
@articles = Article.by_section(params[:section])
.by_year(params[:year])
.by_month(params[:month])
end
end
then defined scopes in model to allow missing params:
class Article < AR::Base
scope :by_section, ->(section) { section.present? ? where(section: section) : all }
scope :by_year, ->(year) { year.present? ? where(year: year) : all }
scope :by_month, ->(month) { month.present? ? where(month: month) : all }
end
handle it via single route:
get '/:section(/:year(/:month))', to: 'articles#index'
and finally in views you could do:
<%= link_to 'In section 1', articles_path(section: 1) %>
<%= link_to 'In section 1 in 2014', articles_path(section: 1, year: 2014) %>
You may want to handle cases when user manipulate URL and i.e. put a string in place of year - it will probably result in error from db.
Personally I think it's perfectly fine to punish users manipulating URLs by hand with error page (500). Using constraints it would error anyways, but with 404 instead.
If you feel you really need to ensure that user is allowed to i.e. view only articles from selected sections you can add another scope:
scope :in_allowed_sections, -> { where(section: ['section 1', 'section 2']}
Given malicious user manipulating URL by hand this would result in valid SQL query:
where section in ('section 1', 'section 2') and section = 'section 3'
and user will receive a proper page (200) but without any articles displayed.
-
\$\begingroup\$ I tried this method and it turns up being just about as complex as the first method. Thanks for the suggestion though! \$\endgroup\$DaniG2k– DaniG2k2014年12月03日 11:42:33 +00:00Commented Dec 3, 2014 at 11:42