Lugo Labs

Rewrite Medium in Ruby on Rails, Part 1: The Setup

Demo
Rewrite Medium in Ruby on Rails

I love Medium's interface, clean, tidy, devoid of any bells and whistles, making you concentrate on what you're reading, or writing. When you highlight parts of a text, a little iPhone-like popup appears that allows you to format the selected text: make it bold, italic, a header, etc.. Let's see how we can rewrite Medium in Ruby on Rails. (You can check a live demo of this post at our Blogr app)

Install Medium Editor

For this exercise we will add a blog to an existing Rails app. We'll use the awesome Medium Editor to turn our form into a Medium-like form. We'll use the manual installation for this exercise, but you can use bower or other methods explained in Medium Editor's github page.

Let's download the files from their latest release and copy the JavaScript file into our vendor/assets/javascripts, then add it to our application.js manifest:

js
//= require medium-editor

Let's also grab the editor's stylesheets (medium-editor.css and require medium-editor-theme-default.css and ) and copy them into vendor/assets/stylesheets folder; then add them to our application.css manifest. Medium Editor comes with several themes, but we'll use the default one here.

The Model

Time now to write our Post model that will hold the posts we write. We'll create a simplified version of a blog, and not worry about advanced blogging features, such as versions, etc.. We want to show drafts and public posts as Medium does, so we attach a published_at date to the model.

sh
bundle exec rails g model post title body:text published_at:datetime

And run the migration:

sh
bundle exec rake db:migrate

Setting up the controller

The controller is also simple:

sh
bundle exec rails g controller index edit --skip-javascripts --skip-stylesheets

We're adding the blog to an existing Rails app, so let's move the controller into a new folder, blogr, and namespace it:

ruby
# app/controller/blogr/posts_controller

class Blogr::PostsController < ApplicationController
  def index
  end

  def new
  end

  def edit
  end

  def public
  end

  def publish
  end

  def unpublish
  end

  def create
  end

  def update
  end

  def destroy
  end
end

Apart from the standards Rails' CRUD actions, we also added three new actions:

  • public - shows the published posts
  • publish - publish a post
  • unpublish - unpublish a post

The new routes will also be namespaced:

ruby
# config/routes

namespace :blogr do
  resources :posts do
    get :public, :on => :collection
    member do
      patch :publish
      patch :unpublish
    end
  end
  root 'posts#index'
end

This allows us to have a working URL /blogr where our new blog will live.

The Layout

Let's start with the layout page, that lives in app/views/layouts/blogr/posts.html.erb:

html
<!DOCTYPE html>
<html>
<head>
  <title>Blogr</title>
  <%= stylesheet_link_tag "application", media: "all" %>
  <%= csrf_meta_tags %>
</head>
<body>

  <header class="blogr-header">
    <%= link_to blogr_root_path, class: 'blogr-logo' do %>
      blogr
    <% end %>
  </header>


  <article id="posts-cont" class="blogr-cont">
    <%= yield %>
  </article>

  <%= javascript_include_tag "application" %>

</body>
</html>

Our blog has its own layout, which allows us to style it differently to the rest of our app, quite common process nowadays. The layout's elements have all classes in them, which we'll then use to add a bit of flair to our blog later.

The Views

The index.html.erb page should show a list of drafts and published posts, just as Medium does.

html
<section class="posts-title cf">
  <h1>Your posts</h1>
  <%= link_to 'Write a post', new_blogr_post_path(format: 'html'), remote: true %>
</section>

<nav class="posts-nav">
  <ul>
    <li><%= link_to_posts 'Drafts', :index %></li>
    <li><%= link_to_posts 'Public', :public %></li>
  </ul>
</nav>

<section class="blogr-posts">
  <ul>
  <% @posts.each do |post| %>
    <li class="summary">
      <h3><%= link_to post.title.html_safe, edit_blogr_post_path(post) %></h3>
      <div class="desc">
        <span>Last edited <%= time_ago_in_words post.updated_at %> ago</span>
        <span class="dot">.</span>
        <%= link_to 'Edit', edit_blogr_post_path(post) %>
        <span class="dot">.</span>
        <%= link_to 'Delete', blogr_post_path(post), method: :delete, data: { confirm: 'Are you sure?' } %>

        <span class="dot">.</span>
        <%= link_to_published post %>
      </div>
    </li>
  <% end %>
  </ul>

  <%= no_posts_meessage %>
</section>

The view has a title, navigation, and lists of posts. The link_to_posts helper renders a span or an a tag, based on the current navigated page (stored on an instance variable @current_blogr_tab we'll set later inside the PostsController).

ruby
# app/helpers/blogr/posts_helper.rb

def link_to_posts(name, action)
  if @current_blogr_tab == action
    content_tag :span, "#{name} #{@posts.size}"
  else
    link_to name, controller: :posts, action: action
  end
end

The link_to_published shows the publish or unpublish link, depending on the post being published or not.

ruby
# app/helpers/blogr/posts_helper.rb

def link_to_published(post)
  if post.published_at?
    link_to 'Unpublish', unpublish_blogr_post_path(post), method: :patch, data: { confirm: 'Are you sure?' }
  else
    link_to 'Publish', publish_blogr_post_path(post), method: :patch, data: { confirm: 'Are you sure?' }
  end
end

And to end we use a no_posts_message method to display a message when no posts are available:

ruby
# app/helpers/blogr/posts_helper.rb

def no_posts_message
  if @posts.size == 0
    content_tag :div, "You don't have any posts here yet."
  end
end

The controller again

Time now to write the controller actions index, which would show the draft posts, and public, to show the published posts.

ruby
# app/controller/blogr/posts_controller

def index
  @current_blogr_tab = :index
  @posts = Post.drafts
end

def public
  @current_blogr_tab = :public
  @posts = Post.published
  render :index
end

Both actions set the @current_blogr_tab instance variable, we saw earlier, that tells the helper which tab is selected. The index action is showing the drafts, which can be fetched in the model like this:

ruby
# app/models/post.rb

def self.drafts
  where('published_at IS NULL')
end

It checks that published_at is NULL. The public action does the opposite, when it calls the published method in the model:

ruby
# app/models/post.rb

def self.published
  where('published_at NOTNULL')
end

Conclusion

Here ends the first part of the Rewrite Medium in Ruby on Rails series, which helps us to add a blog of the Medium's style to our existing application. In part 2 we'll see how to use the Medium Editor we downloaded earlier, to create the blog posts. You can check the finalized demo app at Blogr.

Happy coding!