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:
//= 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.
bundle exec rails g model post title body:text published_at:datetime
And run the migration:
bundle exec rake db:migrate
Setting up the controller
The controller is also simple:
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:
# 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:
# 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:
<!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.
<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
).
# 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.
# 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:
# 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.
# 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:
# 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:
# 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!