2
\$\begingroup\$

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.

Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Nov 20, 2014 at 11:56
\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

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 :)

answered Nov 20, 2014 at 13:55
\$\endgroup\$
1
\$\begingroup\$

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.

answered Nov 28, 2014 at 17:36
\$\endgroup\$
1
  • \$\begingroup\$ I tried this method and it turns up being just about as complex as the first method. Thanks for the suggestion though! \$\endgroup\$ Commented Dec 3, 2014 at 11:42

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.