Skip to content

Instantly share code, notes, and snippets.

@metaskills
Last active June 4, 2022 00:46
Show Gist options
  • Save metaskills/11071934 to your computer and use it in GitHub Desktop.
Save metaskills/11071934 to your computer and use it in GitHub Desktop.
# In lib/tasks/blog.rake
namespace :blog do
desc "Generate a new blog post with a title. Ex: rake blog:post['My New Post']"
task :post, [:title] => [:environment] do |t, args|
date = Time.now.strftime("%G-%m-%d")
title = args[:title].to_s.strip
if title.blank?
fail 'Please supply a title.'
else
slug = title.split(' ').map{ |s| s.downcase }.join('-')
file = Rails.root.join 'app', 'views', 'blog', "#{date}-#{slug}.md"
File.open(file,'w') { |f| f.write("---\ntitle: #{title}\n---\n\n\n") }
end
end
end
# In app/controllers/blog_controller.rb
class BlogController < ApplicationController
layout 'site'
before_filter :find_post, only: [:show]
def index
@posts = BlogPost.all
end
def show
if stale?(@post, public: true)
render text: @post.html, layout: 'site'
end
end
private
def find_post
@post = BlogPost.find params[:id]
redirect_to blog_index_path unless @post
end
end
# app/helpers/blog_helper.rb
module BlogHelper
def blog_post?
controller_name == 'blog' && action_name == 'show'
end
end
# In app/models/blog_post.rb
class BlogPost
include ActionView::Helpers::TextHelper
attr_reader :slug
class << self
def all
all_slugs.map{ |slug| new(slug) }.sort
end
def find(slug)
all.detect { |post| post.slug == slug }
end
def directory
Rails.root.join 'app', 'views', 'blog'
end
private
def all_slugs
@all_slugs ||= Dir.glob("#{directory}/*.md").map { |f| File.basename(f,'.md') }
end
end
def initialize(slug)
@slug = slug
@data = {}
parse_file
end
def title
@data['title'] || slug.sub(/\d{4}-\d{2}-\d{2}-/, '').titleize
end
def date
Date.parse(slug)
end
def date_formatted
day_format = ActiveSupport::Inflector.ordinalize(date.day)
date.strftime "%B #{day_format}, %G"
end
def html
Rails.cache.fetch("#{cache_key}/html") { to_html }
end
def excerpt
first_ptag = Nokogiri::HTML(html).css('p:first').text.squish
truncate strip_tags(first_ptag), length: 200, separator: ' '
end
def path
"/blog/#{slug}"
end
def inspect
"<#{self.class.name} date: #{date.iso8601}, title: #{title.inspect}, slug: #{slug.inspect}>"
end
def <=> other
other.date <=> date
end
def updated_at
File.mtime(file_path)
end
def cache_key
ActiveSupport::Cache.expand_cache_key ['blog', slug, updated_at.to_i]
end
private
def parse_file
@markdown = Tilt::ErubisTemplate.new do
fdata = file_data
if fdata =~ /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
@data = SafeYAML.load($1)
$POSTMATCH
else
fdata
end
end.render(scope, post: self)
end
def file_path
self.class.directory.join "#{slug}.md"
end
def file_data
Rails.cache.fetch("#{cache_key}/markdown") { File.read(file_path) }
end
def markdown
@markdown
end
def to_html
Jekyll::Converters::Markdown::RedcarpetParser.new({
'highlighter' => 'rouge',
'redcarpet' => {
'extensions' => ["no_intra_emphasis", "fenced_code_blocks", "autolink", "strikethrough", "lax_spacing", "superscript", "with_toc_data"]
}
}).convert(markdown)
end
def scope
ApplicationController.helpers.clone.tap do |h|
h.singleton_class.send :include, Rails.application.routes.url_helpers
end
end
end
gem 'jekyll', '~> 2.0.0.alpha'
gem 'nokogiri'
gem 'redcarpet'
gem 'rouge'
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #999988; font-style: italic } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #000000; font-weight: bold } /* Keyword */
.highlight .o { color: #000000; font-weight: bold } /* Operator */
.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */
.highlight .cp { color: #999999; font-weight: bold; font-style: italic } /* Comment.Preproc */
.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */
.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #999999 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #aaaaaa } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #000000; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #000000; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #000000; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */
.highlight .kr { color: #000000; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #009999 } /* Literal.Number */
.highlight .s { color: #d01040 } /* Literal.String */
.highlight .na { color: #008080 } /* Name.Attribute */
.highlight .nb { color: #0086B3 } /* Name.Builtin */
.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */
.highlight .no { color: #008080 } /* Name.Constant */
.highlight .nd { color: #3c5d5d; font-weight: bold } /* Name.Decorator */
.highlight .ni { color: #800080 } /* Name.Entity */
.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */
.highlight .nl { color: #990000; font-weight: bold } /* Name.Label */
.highlight .nn { color: #555555 } /* Name.Namespace */
.highlight .nt { color: #000080 } /* Name.Tag */
.highlight .nv { color: #008080 } /* Name.Variable */
.highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mf { color: #009999 } /* Literal.Number.Float */
.highlight .mh { color: #009999 } /* Literal.Number.Hex */
.highlight .mi { color: #009999 } /* Literal.Number.Integer */
.highlight .mo { color: #009999 } /* Literal.Number.Oct */
.highlight .sb { color: #d01040 } /* Literal.String.Backtick */
.highlight .sc { color: #d01040 } /* Literal.String.Char */
.highlight .sd { color: #d01040 } /* Literal.String.Doc */
.highlight .s2 { color: #d01040 } /* Literal.String.Double */
.highlight .se { color: #d01040 } /* Literal.String.Escape */
.highlight .sh { color: #d01040 } /* Literal.String.Heredoc */
.highlight .si { color: #d01040 } /* Literal.String.Interpol */
.highlight .sx { color: #d01040 } /* Literal.String.Other */
.highlight .sr { color: #009926 } /* Literal.String.Regex */
.highlight .s1 { color: #d01040 } /* Literal.String.Single */
.highlight .ss { color: #990073 } /* Literal.String.Symbol */
.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */
.highlight .vc { color: #008080 } /* Name.Variable.Class */
.highlight .vg { color: #008080 } /* Name.Variable.Global */
.highlight .vi { color: #008080 } /* Name.Variable.Instance */
.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */
# config/routes.rb
resources :blog, only: [:index, :show]
# app/views/layouts/site.html.haml
.container
= render partial: 'blog/header' if blog_post?
~ yield :layout
= render partial: 'blog/call_to_action' if blog_post?
= render partial: 'blog/comments' if blog_post? && Rails.env.production?
@metaskills
Copy link
Author

@equivalentideas
Copy link

Thanks for this @metaskills . Could you list a few of the other dependencies in the gemfile, such as Rails and Ruby versions?

@michaelkeenan
Copy link

Beware that this gist uses %G for the year when formatting dates, but %G is a weird thing that displays the week year, not the current year, so it'll give the wrong result for a few days each year (as described here). (What's the week year? It's the year that most of the current week is in. If it's Monday on December 31, you're in a a week that is mostly in the next year, so it'll return the next year, not the current year.) It's a strange, hard-to-detect bug. The solution is to use %Y rather than %G.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment