# 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 |
.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 */ |
# 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? |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Show comment Hide comment
felixbuenemann
commented
Apr 22, 2014
File.basename has a second param to strip the file ending, no need for sub. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Show comment Hide comment
jnillo
commented
Apr 24, 2014
In blog_post model: attr_reader :slug, :data |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Show comment Hide comment
acandael
Apr 24, 2014
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'
acandael
commented
Apr 24, 2014
I'm getting an error when I go to http://localhost:3000/blog
in my views/blog folder I created an .md file called '2014-04-24-my-first-post.md' |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Show comment Hide comment
acandael
commented
Apr 25, 2014
what does the directive layout 'site' in blog_controller.rb mean? |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Show comment Hide comment
acandael
Apr 25, 2014
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.
acandael
commented
Apr 25, 2014
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. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Show comment Hide comment
metaskills
May 9, 2014
Added examples of a BlogHelper
and a site layout to show how I got headers, comments, etc in too.
Added examples of a |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Show comment Hide comment
metaskills
May 9, 2014
what does the directive layout 'site' in blog_controller.rb mean?
@acandael That is just my example layout. Your's may be "application".
@acandael That is just my example layout. Your's may be "application". |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Show comment Hide comment
Thanks @felixbuenemann |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Show comment Hide comment
equivalentideas
Jun 19, 2014
Thanks for this @metaskills . Could you list a few of the other dependencies in the gemfile, such as Rails and Ruby versions?
equivalentideas
commented
Jun 19, 2014
Thanks for this @metaskills . Could you list a few of the other dependencies in the gemfile, such as Rails and Ruby versions? |
File.basename has a second param to strip the file ending, no need for sub.