0
votes

I'm really hoping someone can help this Rails n00b with this issue. I've been researching, trying, crashing(-and-burning) over the past few days on how to implement the standard /YYYY/MM/Title-Slug URL structure for a blog I'm putting together. I've discovered and successfully implemented Friendly_Id to handle the sluggification (along with history tracking), but for the life of me I can't get the Year/Month part of the routing problem resolved.

Before I forget: I'm using Rails 4.2.3 and Ruby 2.2.1p85 (because, yes, I leveraged a bunch of stuff from RailsTutorial.org) :-)

To minimize confusion (or collateral damage), I've scaffolded a super-simple blog app to try to get it all working:

$ rails new blog
[...]
$ cd blog
# (Add friendly_id to Gemfile & install)
$ rails generate friendly_id
$ rails generate scaffold post title content slug:string:uniq
[...]
$ rake db:migrate

Made the following changes to post.rb:

class Post < ActiveRecord::Base
  extend FriendlyId
  friendly_id :title, use: :slugged

  def year
    created_at.localtime.year
  end

  def month
    created_at.localtime.strftime("%m")
  end

end

posts_controller.rb:

class PostsController < ApplicationController
  before_action :set_post, only: [:show, :edit, :update, :destroy]

  def index
    @posts = Post.order('created_at DESC').all
  end

  def show
  end

  def new
    @post = Post.new
  end

  def edit
  end

  def create
    @post = Post.new(post_params)

    respond_to do |format|
      if @post.save
        format.html { redirect_to @post, notice: 'Post was successfully created.' }
        format.json { render :show, status: :created, location: @post }
      else
        format.html { render :new }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  def update
    respond_to do |format|
      if @post.update(post_params)
        format.html { redirect_to @post, notice: 'Post was successfully updated.' }
        format.json { render :show, status: :ok, location: @post }
      else
        format.html { render :edit }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  def destroy
    @post.destroy
      respond_to do |format|
        format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.' }
        format.json { head :no_content }
      end
    end

  private

    def set_post
      @post = Post.friendly.find(params[:id])
    end

    def post_params
      params.require(:post).permit(:title, :content, :published_at, :slug)
    end
end

index.html.erb

<p id="notice"><%= notice %></p>

<h1>Listing Posts</h1>

<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Content</th>
      <th>Slug</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.title %></td>
        <td><%= post.content %></td>
        <td><%= post.slug %></td>
        <td><%= link_to 'Show', post_date_path(post) %></td>
        <td><%= link_to 'Edit', edit_post_path(post) %></td>
        <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Post', new_post_path %>

routes.rb:

Rails.application.routes.draw do

  get '/posts', to: 'posts#index', as: :posts_path
  get '/posts/:year', to: 'posts#index', as: :posts_year,
    constraints: { year: /\d{4}/ }
  get '/posts/:year/:month', to: 'posts#index', as: :posts_month,
    constraints: { year: /\d{4}/, month: /\d{1,2}/ }
  get '/posts/:year/:month/:slug', to: 'posts#show', as: :post_date,
    constraints: { year: /\d{4}/, month: /\d{1,2}/, slug: /[a-z0-9\-]+/ }
  resources :posts
end

These changes are mostly from updating the Rails3 code from this Stackoverflow Q&A as that has gotten me the furthest out of other options I've discovered. I'm currently running into the following Controller Exception:

Showing […]/app/views/posts/index.html.erb where line #24 raised:

No route matches {:action=>"show", :controller=>"posts", :month=>nil, :slug=>nil, :year=>#<Post id: 23, title: "test", content: "", slug: "test-4", created_at: "2015-09-01 21:05:48", updated_at: "2015-09-01 21:05:48">} missing required keys: [:month, :slug, :year]

Other solutions that have failed in other slightly soul-crushing ways:

  • "Rails 4 Blog /:year/:month/:title with clean routing" (see comments for link) - this appears to not work because of a 4.1.2 bug that appears to never having been fixed)
  • "Rails 4.1.2 - to_param escapes slashes (and breaks app)" (see comments for link) - this may work, but I wasn't able to translate the answer for my purposes
  • "Friendly_Id slugs with ids or dates separated by slashes" (see comments for link)

To be clear: I'm not wedded to this approach - I'm more than happy to go an entirely different way. I would just like my final blog to function as:

  • http://www.example.com/blog/ (for the index)
  • http://www.example.com/2015/ (for an index of 2015 posts)
  • http://www.example.com/2015/09/ (for an index of posts from Sept'15)
  • http://www.example.com/2015/09/pleeze-help-me (for an individual post)

Many thanks in advance!

EDIT

In going down some additional rabbit holes to get a solution to this, I'm wondering if using URL rewriting would be the only? approach for this issue. My gut says that it's pounding a round peg into a square hole (especially given that the blog isn't live yet, so there's no chance that there are links out in the wild pointing to the current URL structure), but I'm failing to find a better alternative.

I've found two options that might help with the rewrite approach: refraction (see comments for link) and rack-rewrite (see comments for link)

Does anyone have any input on this alternative approach and/or these plugins?

Thanks!

PS - There appears to be an update to SO permissions that now require at least 10 reputation to post more than 2 links, so I had to remove all the links in order to post this edit. I've moved them to the comments so I can get the edit saved.

1
@mysmallidea - Yes I did check that out, but as the original answerer noted, his approach doesn't work as he proposed. I clicked through to their chat session where they were ping-ponging trying to get a workable solution before it dead-ended. Do you have any insights on how to get that approach to work?Brian M.

1 Answers

0
votes

I've got a partially-working solution that can be viewed in a new question I've just created. The index and individual posts render as desired, but I'm running into issues when creating & editing posts and I don't have a solution for yearly indexes e.g., http://www.example.com/2015/ and monthly indexes e.g., http://www.example.com/2015/09/.

Additional insight on resolving these outstanding issues at the new question would be really appreciated!

A full, working implementation can be found in the accepted answer of this subsequent question.