-
-
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? |
In blog_post model:
attr_reader :slug, :data
I'm getting an error when I go to http://localhost:3000/blog
Missing template blog/index, application/index with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :jbuilder, :coffee]}. Searched in: * "/Users/acandael/Sites/mysite/app/views"
in my views/blog folder I created an .md file called '2014-04-24-my-first-post.md'
what does the directive layout 'site' in blog_controller.rb mean?
ok, when I go to http://localhost:3000/blog/2014-04-24.md I got the blogpost rendering. The only issue I have now, is that no css is rendered. When I look in the page source, I see that the page source is missing the header section.
Added examples of a BlogHelper
and a site layout to show how I got headers, comments, etc in too.
what does the directive layout 'site' in blog_controller.rb mean?
@acandael That is just my example layout. Your's may be "application".
Thanks @felixbuenemann
Thanks for this @metaskills . Could you list a few of the other dependencies in the gemfile, such as Rails and Ruby versions?
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.
File.basename has a second param to strip the file ending, no need for sub.