text.ssig33.com

created at 2010-11-03 17:32:43 UTC

このサイトを Sinatra で書き直した。 もともと Rails で書かれていたのだが、この規模のサイトに使うには Rails はいかにも重量級すぎるので。 Sinatra ならば 1 ファイルで完結する。

Sinatra で開発する時に、 O/R マッピングには Sequel とか DataMapper とかが使われることが多い印象があるのだが、今回は ActiveRecord3 を使ってみた。 ActiveRecord3 はメソッドチェインでクエリを組み立てることが出来て非常に便利だし、 Rails3 でこのサイトを作った時のコードを使い回せる。

従来では「Rails を使わない理由」の中で最も大きなものに「ActiveRecord と密接に接合しており他の DBI を使いたくても使えない」というものがあったと思うが(つまりそれだけクソだった)、 ActiveRecord3 は Ruby の DBI の中で現状最も優れたものの一つであると思う。 Sinatra と組合せて使用した場合も非常に強力である。

小規模なアプリケーションを開発する場合、 Sinatra + ActiveRecord3 というのは現状では最も強力な組合せの一つなのではなかろうか。

以下このサイトのコード

require "sinatra"
require "active_record"
require "bluecloth"
require "haml"
require "cgi"
require "rack/csrf"
require "logger"
require 'kconv'

ActiveRecord::Base.establish_connection(
  :adapter => 'sqlite3',
  :database => 'production.sqlite3'
)

ActiveRecord::Base.logger = Logger.new("./database.log")

class Post < ActiveRecord::Base
  def html
    BlueCloth.new(self.body).to_html rescue "<pre>#{self.body}</pre>"
  end

  def title
    self.body.split("\n").first.chomp
  end
end

#Post.auto_migrate!

configure do
  set :app_file, __FILE__
  use Rack::Session::Cookie, :secret => 'fsdjkfhsjkhr23f8qdwriuef9oooqdr4hn8or'
  use Rack::Csrf, :raise => true
end

mime_type :atom, 'application/atom+xml'

get '/feed' do
  @posts = Post.order("created_at desc").limit(200)
  content_type :atom
  erb :feed
end

get '/' do
  @post = Post.order("created_at desc").limit(1).first
  @next = Post.order("created_at desc").where("created_at < ?", @post.created_at).limit(1).first
  haml :post
end

get "/:id" do
  begin
    @post = Post.find_by_name params[:id]
    @next = Post.order("created_at desc").where("created_at < ?", @post.created_at).limit(1).first
    haml :post
  rescue
    redirect '/'
  end
end

get '/post/new' do
  id = (Post.order("created_at desc").limit(0).first.name.to_i+1).to_s rescue 1
  redirect "/#{id}/edit"
end

get '/:id/edit' do
  @post = Post.find_by_name params[:id] rescue @post = Post.new
  @post = Post.new unless @post
  haml :edit
end

post '/post/update' do
  raise if Digest::MD5.hexdigest(params[:password]) != "#{パスワードをハッシュ化したもの}"
  @post = Post.find_or_create_by_name params[:id]
  @post.body = params[:text]
  @post.save
  redirect "/#{params[:id]}"
end

post '/post/destroy' do
  raise if Digest::MD5.hexdigest(params[:password]) != "#{パスワードをハッシュ化したもの}"
  Post.find_or_create_by_name(params[:id]).destroy
  redirect "/"
end


helpers do
  def h(str)
    CGI.escapeHTML str.to_s
  end

  def sanitize(str)
    str.to_s#todo atode tsukuru
  end

  def title
    if request.path_info == "/"
      return "text.ssig33.com"
    else
      return "text.ssig33.com - #{@post.title}"
    end
  end

  def ref str
    str.toutf16.unpack( "n*" ).map {|n| (n < 128 ? '%c' : '&#%d;') % n}.join
  end
end

__END__
@@ post
!!!
%html
  %meta{:name => "viewport", :content => "width=320, initial-scale=1.0, maximum-scale=1.0, user-scalable=no /"}
  %title=h title
  %link{:href => "http://ssig33.com/common.css", :media => "screen", :rel => "stylesheet", :type => "text/css"}
  %link{:rel => "alternate", :type => "application/atom+xml", :title => "Feed", :href => "http://text.ssig33.com/feed"}
  %meta{:name => "viewport", :content => "width=320, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"}
  %div#all
    %h3
      %a{:href => "/"}text.ssig33.com
    %div#post.autopagerize_page_element
      %p
        created at
        %a{:href => "/#{@post.name}"}=h @post.created_at.getutc
      %div
        ~sanitize @post.html
    %div#pager
      %p
        %br
        - if @next
          %a{:href => "/#{@next.name}", :rel => "next"}=h("next: #{@next.title}")

@@ edit
!!!
%html
  %title text.ssig33.com
  %link{:href => "http://ssig33.com/common.css", :media => "screen", :rel => "stylesheet", :type => "text/css"}
  %meta{:name => "viewport", :content => "width=320, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"}
  %div#all
    %h3
      %a{:href => "/"}text.ssig33.com
    %div#post
      %p
        created_at
        %a{:href => "/#{@post.name}"}=h @post.created_at.getutc
      %div
        %p Edit
        %form{"accept-charset" => "UTF-8", :action => "/post/update", :method => "post"}
          =Rack::Csrf.csrf_tag(env)
          %input{:id => "id", :name => "id", :type => "hidden", :value => "#{params[:id]}"}
          %p
            %textarea{:cols => "80", :id => "text", :name => "text", :rows => "30"}=@post.body
          %p 
            %input{:id => "password", :name => "password", :type => "password"}
          %p
            %input{:name => "commit", :type => "submit", :value => "Save changes"}
      %div
        %p Destroy
        %form{"accept-charset" => "UTF-8", :action => "/post/destroy", :method => "post"}
          =Rack::Csrf.csrf_tag(env)
          %input{:id => "id", :name => "id", :type => "hidden", :value => @post.name}
          <p><input id="password" name="password" type="password" /></p>
          <p><input name="commit" type="submit" value="Save changes" /></p>

@@ feed
<?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="ja-JP" xmlns="http://www.w3.org/2005/Atom">
  <id>http://text.ssig33.com/</id>
  <link rel="alternate" type="text/html" href="http://text.ssig33.com/"/>
  <link rel="self" type="application/atom+xml" href="http://text.ssig33.com/feed"/>
  <title>text.ssig33.com</title>
  <subtitle>text</subtitle>
  <updated><%=@posts.first.updated_at.getutc.iso8601 %></updated>

  <author>
    <name>ssig33</name>
  </author>
  <% @posts.each do |p| %>
  <entry>
    <id>http://text.ssig33.com/<%=h p.name %></id>
    <published><%=p.created_at.getutc.iso8601 %></published>
    <updated><%=p.updated_at.getutc.iso8601 %></updated>

    <link rel="alternate" type="text/html" href="http://text.ssig33.com/<%= p.name %>"/>
    <title><%=p.created_at %></title>
    <content type="html"><%=ref h(p.html) %></content>
    <author>
      <name>ssig33</name>
    </author>
  </entry>
  <% end %>
</feed>

あとデータベースのスキーマ

create_table "posts", :force => true do |t|
  t.text     "body"
  t.datetime "created_at"
  t.datetime "updated_at"
  t.string   "name"
end
add_index "posts", ["name"], :name => "index_posts_on_key", :unique => true