4
votes

In Rails 4 is there another cleaner way to achieve routes, such as:

/blog/2014/8/blog-post-title
/blog/2014/8
/blog/2014
/blog/2014/8/tags/tag-1,tag-2/page/4
/blog/new OR /blog_posts/new

I've tried the following using FriendlyId (as well as acts-as-taggable for tag param and kaminari for page param):

blog_post.rb

 extend FriendlyId
 friendly_id :slug_candidates, use: [:slugged, :finders]

 def to_param
   "#{year}/#{month}/#{title.parameterize}"
 end

 def slug_candidates
 [
    :title,
    [:title, :month, :year]
 ]
 end

 def year
   created_at.localtime.year
 end

 def month
   created_at.localtime.month
 end

routes.rb

  match '/blog_posts/new', to: 'blog_posts#new', via: 'get'
  match '/blog_posts/:id/edit', to: 'blog_posts#edit', via: 'get'
  match '/blog_posts/:id/edit', to: 'blog_posts#update', via: 'post'
  match '/blog_posts/:id/delete', to: 'blog_posts#destroy', via: 'destroy'
  match '/blog(/page/:page)(/tags/:tags)(/:year)(/:month)', to: 'blog_posts#index', via: 'get'
  match '/blog/:year/:month/:title', to: 'blog_posts#show', via: 'get'
  resources 'blog', controller: :blog_posts, as: :blog_posts

Used resources so can have path and url helpers as normal.

This works (minus update yet), but feels very ugly. Is there a better way?

1
you don't need the new, edit, update, destroy routes which are included in resources...knotito
I need those so it is /blog_posts/action instead of /blog/action, otherwise it will interpret the action as a year parameter e.g. year="new". Maybe there is another approach just in respect to those route definitions?TODOMyName

1 Answers

2
votes

Friendly_ID

I think your major problem is that you're trying to keep your /:year/:month/:tags in a single set of params - you'd be much better suited to sending them separately, and building the resource as you need:

#config/routes.rb
scope "/blog" do
   resources :year, controller :blog_posts, only: :show, path: "" do
      resources :month, controller : blog_posts, only: :show, path: "" do
         resources :title, controller: blog_posts, only: :show, path: ""
      end
   end
end
resources :blog_posts, path: :blog -> domain.com/blog/new

This looks unruly, but will hopefully provide a structure of routing whereby you'll be able to send specific requests to your Rails app (domain.com/blog/...), and have them handled by the blog_posts#show action

Here's how I'd take care of that:

#app/controllers/blog_posts_controller.rb
Class BlogPostsController < ApplicationController

   def show
      case true
         when params[:year].present?
           @posts = Post.where "created_at >= ? and created_at < ?", params[:year]
         when params[:month].present?
           @posts = Post.where "created_at >= ? and created_at < ?", params[:month]
         when params[:id].present?
           @posts = Post.find params[:id]
      end
   end

end

#app/views/blog_posts/show.html.erb
<% if @posts %>
  <% @posts.each do |post| %>
    <%= link_to post.title, blog_post_path(post) %>
  <% end %>
<% end %>

<% if @post %>
   <%= link_to post.title, blog_post_path(post) %>
<% end %>

--

Slugs

To create the slugs, you'd then just be able to use the title for friendly_id:

#app/models/blog_post.rb
Class BlogPost < ActiveRecord::Base
   extend FriendlyId
   friendly_id :title, use: [:slugged, :finders]
end

This might not work out of the box (in fact, it probably won't), but what I'm trying to demonstrate is that I think you'd be better splitting your handling of the params / year / month / title into your controller