0

sinatra-contrib

sinatra-content-forの0.2がrubygemsから消えた。
どこ行ってしまったのかと思ったら
https://github.com/sinatra/sinatra-contrib
最新版はsinatra-contribに移動してた。

インストールして

gem install sinatra-contrib

いつも通り使える。
require 'sinatra/content_for'

sinatra-reloaderもsinatra-contribに入ってた。

0

JSONシリアライズ方法を設定する

こういうjsonへのシリアライズ方法が書かれてないクラスを使って、to_jsonすると

#!/usr/bin/env ruby
require 'rubygems'
require 'json'

class User
attr_reader :id, :name
def initialize(id, name)
@id = id
@name = name
end
end

users = Array.new
['user1', 'user2', 'shokai'].each_with_index{|name,id|
users.push User.new(id, name)
}

puts users.to_json


クラス名が出てきてしまう
["#<User:0x007fc17b8bda50>","#<User:0x007fc17b8bda28>","#<User:0x007fc17b8bda00>"]


JSON implementation for Rubyに、to_json(*a)を定義しろって書いてあった
#!/usr/bin/env ruby
require 'rubygems'
require 'json'

class User
attr_reader :id, :name
def initialize(id, name)
@id = id
@name = name
end

## JSON作る
def to_json(*a)
{
:id => @id,
:name => @name
}.to_json(*a)
end
end

users = Array.new
['user1', 'user2', 'shokai'].each_with_index{|name,id|
users.push User.new(id, name)
}

puts users.to_json


ちゃんと出てきた
[{"id":0,"name":"user1"},{"id":1,"name":"user2"},{"id":2,"name":"shokai"}]

0

Google NewsをMacに読み上げさせる

Googleニュース等のヘッドラインをMacに読み上げさせるやつ作った。最終的にMac以外でも動くようになった。



新聞の勧誘を「インターネットでニュース見てますので」と言って断ってるんだけどよく考えたら見てなかったから、新聞の人に申し訳なくて作った。
自分でインターネットにニュース見に行くの面倒くさいし、テレビとかラジオつけると余計な番組やっててウルサイからニュースだけ届けてほしい。

というわけで3ヶ月ぐらい前に作って、そこそこ良くてずっと使ってるから書いておく。


■使い方
詳細は上のgithubのREADMEを見るとよい。


面倒だったら
git clone git://github.com/shokai/speech-news.git
cd speech-news
gem install bundler
bundle install --path vendor/bundle
でインストールできて


読み上げる。
ruby -Ku speech_news.rb --help
ruby -Ku speech_news.rb

Mac/Linuxに喋らせるで書いた、MacのsayコマンドのKyoko化(日本語化)をやっておく必要あり。



■毎日決まった時間に読み上げる
crontab -e して
0 7 * * *  cd /Users/sho/src/ruby/speech-news && ruby -Ku speech_news.rb > /dev/null 2>&1
0 12 * * * cd /Users/sho/src/ruby/speech-news && ruby -Ku speech_news.rb > /dev/null 2>&1
0 19 * * * cd /Users/sho/src/ruby/speech-news && ruby -Ku speech_news.rb > /dev/null 2>&1
こう書いておくと、7時/12時/19時にニュース読んでくれる。朝はおはようって言うし夜はこんばんはって言う。
ちゃんと書けてるかは crontab -l で確認。


■Mac以外でも読み上げる
Mac/Linuxに喋らせるに書いたGoogle翻訳の声を使うgsayコマンドも一緒に入れておいた。
-sayオプションでgsayを使うようにすれば、Mac以外でも読んでくれる。声怖いけど。
ruby -Ku speech_news.rb -say "tools/gsay"


■Googleニュース以外も読み上げたい
pluginsディレクトリの中に.rbファイルを置けばニュースソースとして自動的に読み込むようになってる。
プラグインは読み上げるべき文字列を配列で返してくれるRubyプログラムならなんでも良い。
今はgoogle.rbだけが置いてある。


自分でプラグインを書くならgoogle.rbを参考にするとか、
a.rb
# プラグインのテストです
return ['うぽって微妙に古そうな銃ばかりでてくるし',
'あれは使われなくなった銃の行き着く精神世界なのでは',
'飛行機の墓場みたいな']
のような感じで、とりあえずStringのArrayを返すやつ書いてみると動く。

2

SinatraっぽいWAFを作る、46行で

“Tamago”というWeb Application Frameworkを作った。
https://github.com/shokai/tamagoに置いてある。


昨日学校に行く前にメシを食いながらHerokuやSqaleやらPaaSについて調べていたら、SinatraやRailsではなくRackを直接使ってPaaSで動かしている人たちが何人かいた。
sqale使ってみた – komagataとか。


で、電車の中でふとWAFを作ってみたらどうかと思って作ってみた。
最終的に学校に着く頃にこんな風に書けるのができてた。
GETやらPOSTで指定したパスへのアクセスを受け取って、Hamlのテンプレートが使えたりするDSLが使える。

get '/' do
haml :index
end

post '/' do
Time.now.to_s
end

get '/env' do
ENV.keys.sort.map{|k|
"#{k}=#{ENV[k]}"
}.join("\n")
end
まんまSinatraの書き方で、何の面白みもない・・・



■Rackとは?
RubyにはWeb Application Framework(以下WAF)にRails、Sinatra、Merb、Padrinoやら色々なのがある。サーバーもThin、Mongorel、Webrick、Passenger+Apacheとか色々ある。これらのWAFとサーバーのうち、どれとどれを組み合わせても動くようにするために、RackというWAFとサーバーの接続方法が決められている。

すごくおおざっぱに言うと、callというクラスメソッドを持ったクラスを宣言して、call内でRack::Requestを受信してRack::Responseを返すようにするだけでいいらしい。



■Rackアプリ最小構成
これがRackを直接使う最小構成になる。
この例ではTamago.callの中でGETやPOSTの判別とかPathとか判別してHTMLを作ってしまっているが、適当にDSLとか決めたりして書けるように拡張したらWAFと呼んでいいだろう。

tamago.rb
require 'rubygems'
require 'rack'
require 'uri'

class Tamago
def self.call(env)
req = Rack::Request.new(env)
body = case req.request_method
when 'GET'
"<html><body><h1>#{URI.decode req.path_info}</h1></body></html>"
when 'POST'
req.params.keys.map{|k| "#{k}=#{req.params[k]}"}.join("\n")
end
Rack::Response.new { |r|
r.status = 200
r['Content-Type'] = 'text/html;charset=utf-8'
r.write body
}.finish
end
end


rackupの設定ファイルを書く
config.ru
require 'tamago'
run Tamago


起動する
rackup config.ru -p 8080

GETアクセスしたらURLのパスの部分がh1タグで囲まれたページがでてくる。



■Sinatraっぽいのを作る
HTTPリクエストをどう処理するかをDSLで書けて、Hamlのテンプレートが使えるようにする。

まずconfig.ru
require 'rubygems'
require File.dirname(__FILE__)+'/tamago'
require File.dirname(__FILE__)+'/app'

run Tamago::Application

WAF本体はこうなった。
本体をTamago::Applicationクラスにして、Viewを作る部分とかはまた別のクラスに分けた。
tamago.rb
require 'rubygems'
require 'rack'
require 'haml'

class Object
def method_missing(name, *args, &block)
case name
when :get, :post, :head, :delete
path = args[0]
Tamago.procs["[#{name.to_s.upcase}] #{path}"] = block
when :haml
Tamago::View.render args[0]
end
end
end

class Tamago
def self.procs
@@procs ||= Hash.new
end

class View
def self.render(template)
template = case true
when template.kind_of?(File)
template.read
when template.kind_of?(Symbol)
File.open("#{ENV['PWD']}/views/#{template}.haml").read
end
raise ArgumentError, 'Argument must be instance of File, String or Symbol.' unless template.kind_of? String
Haml::Engine.new(template).render
end
end

class Application
def self.call(env)
@request = Rack::Request.new(env)
body = Tamago.procs["[#{@request.request_method}] #{@request.path_info}"].call(self)
Rack::Response.new { |r|
r.status = 200
r['Content-Type'] = 'text/html;charset=utf-8'
r.write body
}.finish
end
end
end
app.rbのgetやpostは、config.ruからrequireされた時点で関数として呼ばれる。Object.method_missingを定義しておいてgetやpostを横取りし、blockの中身をHashに保存しておく。
Tamago.callでRack::Requestが来たら、Hashに保存しておいたblockを実行する。


最後にapp.rb
get '/' do
haml :index
end

post '/' do
Time.now.to_s
end

get '/env' do
ENV.keys.sort.map{|k|
"#{k}=#{ENV[k]}"
}.join("
")
end


起動
rackup config.ru -port 8080


というわけでRack上で簡単なWAFを作ってみた。
この分なら、たぶんRackミドルウェアとかを作るのもそんなに難しくないと思う。今度やってみよう。
Sinatraとかでも、Sinatraで書くよりRackの方をいじったほうがスマートに書ける事もあるだろうし。

0

Sinatra+Haml+jQueryテンプレートにDataMapper版とMongoid版を追加した

去年作ったSinatra+Haml+jQuery入門のテンプレ、なにげにしょっちゅうアップデートしている。
内容は↑にも書いてあるがおみくじを引くだけの超単純なアプリだ。
新規プロジェクトを開始する時はコレをcloneして使うので、余計な物を付けない様にしている。

githubのリポジトリはここ https://github.com/shokai/sinatra-template


■使っている部品
今はこういう構成になっている

  • Ruby 1.8.7
  • Sinatra 1.3
  • Haml + sinatra-content-for
  • Sass
  • jQuery
  • foreman
  • bundler


master以外のブランチにmongoiddm-mysqlの2つがある。
それぞれDataMapper+MySQLと、Mongoid+MongoDBをバックエンドにしたやつ。


DataMapper+MySQL版はこんなファイル構成にしている。(Mongoidもほぼ同じ)
.
├── Gemfile # 必要なGEMを書いておくファイル。bundle installで一気にインストールされる。
├── Procfile # webサーバーと同時に起動するプロセスを書いておく
├── README.md
├── bin
│   ├── console.rb # DB接続してModel読み込んだ状態でIRBが起動する
│   ├── db_migrate.rb # DBを初期化するツール
│   └── db_upgrate.rb # model更新した時にDBスキーマを更新するツール
├── bootstrap.rb # これを読み込むとconfig.ymlを読んだりmodels/controllers/hellpersを一括読み込みしたりできる
├── config.ru # RackUp用の設定
├── config.yml # 設定ファイル
├── controllers # sinatraのルーティングメソッドを書いたファイルを入れるディレクトリ
│   ├── css.rb
│   └── main.rb
├── helpers # 便利関数を入れるディレクトリ
│   └── helper.rb
├── inits # DB接続とか、一番最初に実行すべき処理を書く
│   └── db.rb
├── models
│   └── omikuji.rb # DBのQueryとかを集約する
├── public
│   └── js
│   ├── jquery.js
│   └── main.js # index.hamlから読まれるJS
├── sample.config.yml # config.ymlにリネームして使う
└── views
├── index.haml # レイアウト抜きのメインコンテンツ部分
├── layout.haml # レイアウト
└── main.scss # cssのテンプレ


■DataMapperとMySQLを使う
MySQL版はこんなの(デモ)

MySQLのセットアップは以前書いた


この下に書いてある事はREADMEにも書いてある。


cloneしてくる
% git clone git://github.com/shokai/sinatra-template.git
% cd sinatra-template
% git branch -a
% git checkout -b dm-mysql remotes/origin/dm-mysql


設定
% cp sample.config.yml config.yml
config.ymlのMySQLのパスワード等を変更する


DB作る。最初のDB作る部分はログインして自分で作らないとならない。
% mysql -u your_name -p
mysql> create database sinatra_template
% ruby bin/db_migrate.rb
DB初期化するけどいいか?と訊かれるのでYesと答えよう。


起動
% gem install foreman bundler
% bundle install
% foreman start
http://localhost:8080 で起動する。


なんかすごい当たり前の話なんだけど、単純なWebアプリではなく、クローラとか色々と組み合わせたアプリを書くことが多い。
クローラとWebアプリが同じModelを読み込んで、同じ設定ファイルを読んで同じDBに接続するようにすると、綺麗に実装できる。

conosle.rbみたいな書き方をすると良い。

#!/usr/bin/env ruby
require 'rubygems'
require 'bundler/setup'
require File.dirname(__FILE__)+'/../bootstrap'
Bootstrap.init :inits, :models
これだけでDB接続してModelを読み込めるので、あとは普通に書けばいい。

設定ファイル(config.yml)の中身も、main.rbでやっているように
@title = Conf['title']
result = Conf['omikuji'].choice
みたいに取り出せる。