2011年2月27日日曜日

[Rails3] whenever

whenever】はRubyとCronの橋渡しをしてくれるようなgemで、DSLで処理を記述しておくだけで crontab に書き込んでくれるという嬉しい代物です。bash経由で実行するようです。

Railsでももちろん使えて、rakeやrunnerなどを実行できますが、runnerの場合、うちの環境では bash のオプションに -l (--login) がついているとうまく動作しませんでした。

ので、手動で crontab -e して -l を削除しましたとさ、という報告でした。


2011年2月26日土曜日

Ruby on Rails と Thread

Rails2.2 の頃からスレッドセーフだとか何とか騒がれていましたがボクは知りません。知らないので、ネイティブスレッドではないRubyの Thread クラスを Rails で使っちゃおうという計画。delayed_jobではちょっと力不足だったのです。

やりたいことはただひとつ。
  • サーバで起こっている作業の途中経過をAjaxで取得したい。
コレ。簡単そうに思えましたが、ところがどっこい苦労しまくって体調崩すほどでした。誰か他にいい方法があったら教えてください。

つまりですよ。
  1. クライアントがファイルをアップロードする。
  2. サーバはレスポンスを返す。
  3. と同時に、サーバは新しく Thread.new して非同期に処理を開始し、処理をしながら進捗情報をどこかに保存しておく。
  4. クライアントは setInterval とかでサーバにリクエストを送信する。
  5. サーバは保存されている進捗情報を返す。
こんなことがしたかったんですね。

超問題になったのが「進捗情報の保存場所」。ぶっちゃけると session は使えませんでした。値が保持されるかと思ったら、スレッドの外と内でスコープが異なるようです。つまり 5 の時点で謎の値が返るわけです。

次に考えたのが app/models/ に新しくクラスを作成して、クラス変数に保持しておく、ということ。でもこれも駄目でした。development環境ではリクエストのたびにクラスがロードされるらしいので、予測不可能な段階でクラス変数が吹っ飛びます→NameError。production環境では動くかも知れないけど、さすがにそんな危ない橋は渡れません。

で、さんざん悩んだ結果、 lib/thread_manager.rb を作る、ということでした。

class ThreadManager
  @@progress
  def self.progress
    @@progress
  end
  def self.progress= value
    @@progress = value
  end
end


クラス変数に対するアクセサメソッドはマクロで定義できないようです。そんなに使う機会が無いとはいえ、あればもうちょっとスマートにできましたかね。

ともあれ、これで何とか凌げそうです。苦労はしましたが、得るものも大きかった…とは言い辛いぞ!正直Scala+Liftに乗り換えたいぞ!でもそれって、「隣の芝は…」ってやつですかね…。


2011年2月24日木曜日

ActionDispatch::Session::CookieStore RuntimeError

になって困ってたんですよね。

前回の記事】で「Flashからのセッション情報を gem で解決する」と記述しましたが、どうもこれがうまく動かないときがあるので、やっぱりRackミドルウェアとして実装することにしたのです。

まずは app/middleware/flash_session_cookie_middleware.rb:


require 'rack/utils'


class FlashSessionCookieMiddleware
  def initialize app, session_key="_session_id"
    @app = app
    @session_key = session_key
  end


  def call(env)
    if env["HTTP_USER_AGENT"] =~ /^(Adobe|Shockwave) Flash/
      req = Rack::Request.new(env)
      env["HTTP_COOKIE"] = [@session_key, req.params[@session_key]].
                            join("=").freeze unless req.params[@session_key].nil?
      env["HTTP_ACCEPT"] = "#{req.params['_http_accept']}".
                           freeze unless req.params['_http_accept'].nil?
    end


    @app.call(env)
  end
end

次は config/application.rb:

# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/app/middleware)

最後に config/initializers/session_store.rb:



MyApplication::Application.config.session_store :active_record_store, :key => '_MyApplication_session'


Rails.application.config.middleware.insert_before(
  ActionDispatch::Session::CookieStore,
  FlashSessionCookieMiddleware,
  Rails.application.config.session_options[:key]
)

としたんですが、どうも「ActionDispatch::Session::CookieStoreなんてミドルウェアは知らないよ」と、タイトルにもあるとおりRuntimeErrorを吐かれてサーバが起動できない。散々調べた結果、収穫はゼロ。このエラーに悩まされているのは世界中でボク一人だったようで、なんとも。

で、試しに :active_record_store から :cookie_store に戻してみたんですよね。

MyApplication::Application.config.session_store :cookie_store, :key => '_MyApplication_session'


Rails.application.config.middleware.insert_before(
  ActionDispatch::Session::CookieStore,
  FlashSessionCookieMiddleware,
  Rails.application.config.session_options[:key]
)

動きました。どうやらこいつが原因だったみたいですが…それでも :active_record_store で Uploadify なんかを使っている人は多いと思うんですよね。

新しくアプリケーションを作成したときにこのエラーが発現するのかどうかが気になるところですが、もうなんか疲れたのでこれまで、ということで。気が向いたら RailsTalk に投稿するかも知れませぬ。

2011年2月23日水曜日

Rails3とUploadify

「Railsでアップロードの進捗を表示させたいなぁ…」と思っていたところ、【Uploadify】というものが見付かりました。Flashこそ使えどjQueryのプラグインなので導入も簡単です。と思いきや手間取りまくったのでメモメモ(本当は jQuery-ui のプログレスバーを使うものがあったらいいなと思ったのだけれど、実装する勇気も知識もない)。

まず知らなかったのは、Flash経由でリクエストするとセッション情報が送られない、という事実。常識なのかも知れないけどFlashから遠い位置にいたんだから仕方ないよねっ!

で、作成しているアプリケーションは「ログインしてからでないとファイルをアップロードしちゃだめ」なので、当然セッション情報は無視できないのです。半日Google先生と相談してわかったのは、【flash_cookie_session】という、読んで字のごとくFlashからCookieとかセッション情報を送ってくれる gem があることでした。結論から言うと試したら動いたので、今回はこれを使って Uploadify を設置していきます。

Flashからのセッション情報送信をRackレベルで動作するミドルウェアとして手作業で実装するのが(検索した結果)メジャーのようでしたが、アプリケーションごとに書くのも「わざわざだなぁ」と思ったので、っていうかやってみたんだけど動かなかったので、今回は gem で解決です。

2011年2月20日日曜日

Rails 3.0.4 と csrf

Rails がセキュリティアップデートされてました。最新版は 3.0.4 です。詳しいことは「こちら」へ。

元記事にもある通り CSRF や SQL Injection など非常にヤバい単語が並べられているので則アップデートしたものの、Ajax系の動作がうまく動かなくなり(link_to :remote でGET以外のリクエストをAjax経由で送るとセッションがクリアされてしまう)、頭を抱えていたところ、はたと rails.js に原因があるのではないかと思い立って jquery-rails をアップデートしてみたところ、ビンゴだったので詳細を記録。

ちなみに CSRF はクロスサイトリクエストフォージェリ(Cross site request forgeries)の略記で、外部からの意図しないリクエストによってWebサービスの動作を改竄する攻撃手段のひとつ。らしいです、Wikipediaによると。

SQL Injection は、フォームの内容に SQL 文を埋め込むことでサーバのデータを改竄する、これも攻撃手段のひとつですね。

Railsでは app/views/layouts/application.html.erb<head> タグ内に <%= csrf_meta_tag -%> を埋め込むことでワンタイムトークンを生成し、フォームの送信時に透過的にこれを hidden パラメータとして送信することで外部からの無作為なデータの送信を防ぐ(ことを自動的にやってくれる)わけで、トークンが合致しない場合は InvalidAuthenticityToken 例外が投げられていたのですが、今回は Java や Flash などのリクエストで同じことをやろうとしたときに、なぜだか忘れましたがとにかく例外を投げるだけではよくないらしいです。ので、セッションをクリアすることにしたんだそうで。

SQL Injection については、ActiveRecordlimit に脆弱性があったらしいです。「limitなんて数字だけじゃん」とついつい思ってしまいますが、攻撃する側も防御する側もよく気付くなぁ…と感心してしまいます。そんな悠長な事態じゃないんですけど。

で、肝心の対策。

ボクは jquery-rails を使っているので、

$ sudo gem update jquery-rails
$ rails generate jquery:install

こうして jquery.js, jquery.min.js, rails.js を上書きして事無きを得ました。 rails.js だけでいいのかなと思ったけれど、それでまた動かなかったら嫌なので jquery ライブラリも上書きしてしまいますた。多分バージョンが違うだけだと思うけど(「だけ」ってレベルじゃねーですけど)、jQueryで本格的に開発してる人にはオススメしません。あでも、jsUnitとか使ってるのかしら。いいのかしら。

そんなわけでした。

--

2011年2月14日月曜日

[Rails3] [will_paginate] Array インスタンスを WillPaginate::Collection に変換

SomeKindOfActiveRecordModel.paginate からではなくて、何らかの読み出しで Array オブジェクトになってしまった SomeKindOfActiveRecordModel を動的に WillPaginate::Collection 型(ActiveRecord::Base.paginateで返ってくるやつ)に変換するには、Array#paginateを単に呼び出してやればいいらしい。

ちゃんとスーパークラスをRailsプラグインらしくバリバリ拡張してくれているのは大助かりですね。
--

2011年2月13日日曜日

デザイン変えてみたのだよ

もう随分長いこと更新してなかったので、ということが理由になるのかどうかは知らんけど、ブログのデザインを変更してみた。

CSS3も本腰入れて勉強してるけど、結局(正式にサポートすると発表された)IE9が出るまでは冒険的に実装する気にはなれないんだな。そんな言い訳をしつつBloggerのテンプレートはAwesomeより拝借。

自作曲の設置がHTML5だけで出来たら楽しいっていうか楽そうだなー。

最近はコーディングが楽しいけれど、コーディング === 言葉を書き下すことだと本気で考えている文系脳なので、そっちが忙しいときはどうしてもこっちの更新をしなくなってしまうのではないかという考察。あと、はてなに移行したい。かも。

ただ、そういったスピンサイクルに慣れてきたので今後は読書録でもしていこうかな、と。

自分で翻訳した本を完全に理解してないのはおかしい。絶対におかしい。

--

2011年1月18日火曜日

本日の将棋Part2・BonanzaGAKOI!

Bonanzaってやたら大駒切るよね。あの棋風はほんと独特だと思う。

やぽー。今回は二本だて。「U549」新メンバーのふゆさんとの対局になりました。徐々に体を慣らしていきたい…。


この棋譜へのコメントは「 こちら 」までどうぞ。

押してねっ→BlogPeople「趣味の世界」ブログランキング

本日の将棋・悪役

ごめんなさい!

と、わかる人にだけわかる「ごめんなさい」を書いておいてやっふー。そんなに久方ぶりでもない将棋の時間だよ。

一人暮らしも順調に面倒くさくなってきて、洗い物?なにそれおいしいの状態。おいしくないよネー。食べたら致死だよネー。

将棋そのものは久しぶりではないとはいえ、所属しているサークル「U549」のメンバーとはものすごい久しぶりの対局になりました。コトの顛末は以下からどうぞ…。


この棋譜へのコメントは「 こちら 」までどうぞ。

押してねっ→BlogPeople「趣味の世界」ブログランキング

2011年1月16日日曜日

meta_where を使った検索

検索というかなんというか。

u1 = User.new :name => "user1"
u1.comments << Comment.create :message => "hoge hoge"
u1.comments << Comment.create :message => "hoge moge"
u1.save


u2 = User.new :name => "user2"
u2.comments << Comment.create :message => "hoge fuga"
u2.save


u3 = User.create :name => "user3"

こんな感じのデータがあったとして、

User.search("user") # => [u1, u2, u3]
User.search("hoge") # => [u1, u2]

というような結果を求めるとき、 User.search の実装は以下のようになる。

scope :search, lambda {|query|
  includes(:comments).group("users.id").where(:name.like => "%#{query}%" | :comments => {:message.like => "%#{query}%"})
}

Comment に複数の属性があるときは :comments => [{:message.like => "%#{query}%"} | {:updated_at.lt => Date.today}] のように渡せばいい。ゴチャゴチャするけど。

ちなみに includes ではなく joins を使うと、検索結果にコメントの設定されていない u3 がヒットせず、 group を付けないと、 "hoge" の検索結果が [u1, u1, u2] になってしまうので、 users.id でグループ化する必要がある。らしい。

押してねっ→BlogPeople「趣味の世界」ブログランキング

本日の将棋・ひさびさに

「相振り中飛車で攻め潰す本」買ったんですけど、全然読めてないです。

棋譜こめったーも中途半端な出来栄えのような、サーバが不安定というか、落ち着いたら誰でも使えるようにしていきたいですねぇ。


この棋譜へのコメントは「 こちら 」までどうぞ。

2011年1月12日水曜日

[Rails3] meta_whereのコンディションとdelayed_jobのハマりどころ

都合上「何にでもヒットする meta_where のコンディション」が必要になったので、作りかた。

MetaWhere::Condition.new :id, nil, :like

これでおk。

catch_all = MetaWhere::Condition.new :id, nil, :like
Books.where(catch_all | :title.matches % "相振り中飛車で攻")

こんな感じで使える。場合によっては便利…?

それからdelayed_job、これはいろいろと fork されているみたいだけど、今回は有名な https://github.com/collectiveidea/delayed_job こちらを採用させてもらいました。

Mail.delay.deliver

とメソッドチェインするだけで遅延してくれるという優れ物ですが、実用までのメモと自分用のメモ。

$ vi Gemfile
gem "delayed_job"


$ vi config/initializers/delayed_job_config.rb

Delayed::Worker.destroy_failed_jobs = false


$ bundle
$ rails generate delayed_job
$ rake db:migrate
$ ./script/delayed_job start

これで使えるようになる。なお production 環境で使う場合は

$ rake db:migrate RAILS_ENV=production
$ RAILS_ENV=production ./script/delayed_job start

こんな感じ。

で、ハマりどころは、

  • コントローラやProcインスタンスに対しては使えない。適宜 SomethingHelper::should_be_delayed などを定義して使う。
  • delay を呼び出すコードの周り(どの辺までなのかは知らん)が変わったら delayed_job デーモンを再起動すること。

このふたつ。見事にハマりました。

ま、リソースが足りなきゃいくら遅延しても意味ないけどね。

お名前.comのサーバがまた落ちている…。

押してねっ→BlogPeople「趣味の世界」ブログランキング

[Rails3] ActiveRelation

やたら忘れるのでメモ。

RailsCastsの http://media.railscasts.com/videos/239_active_record_relation_walkthrough.mov この動画がとても参考になった。それにしてもRails使いはMacの人が多いなー。やっぱり故郷がMacだと必然的にそうなるのかしら。

えーそれで、リレーションを作成するには

ActiveRecord::Relation.new User, User.arel_table

とかするみたい。引数はモデルと Arel テーブル。この Arel テーブルには事前に定義した条件があってもいいような悪いような。

で、メソッドチェインできるのは

  1. where
  2. having
  3. select
  4. group
  5. order
  6. limit
  7. offset
  8. joins
  9. includes
  10. lock
  11. readonly
  12. from

の12個。

チェインを繋げるとデフォルトで(というか普通に) AND になるようだけど、 OR にする方法ないのかな…。Arel使えってことか。

押してねっ→BlogPeople「趣味の世界」ブログランキング

2011年1月11日火曜日

[Rails3] link_to do

link_to にブロックが渡せるなんてどこに書いてあったんだ…知らなかった。

例えば Rails で

    画像
リンク文字列

のようなレイアウトにしたい場合にブロックが使える。例えばHaml:

= link_to @user do
  = image_tag "#{@user.name}.png"
  %br
  = @user.name

マクロとレキシカルスコープがあればRuby最強なのになと思う信者でした。

押してねっ→BlogPeople「趣味の世界」ブログランキング

[Rails3] prototype_legacy_helper

Rails3になってUJS(UnobtrusiveJavaScript)が推奨されるようになり、 rails.js がコアとして動作するようになったのは時代に乗ってる感じでいいのだけれど、いかんせんドキュメントもサンプルも少ないのが現状なので、以前のヘルパも使いたくなってくるのが人情というもの。

例えば「link_toヘルパに :remote をつけて特定のtext_fieldの内容を送信」という動作をさせるにはどうすればいいのか、いくら検索してもさっぱりわからなかった。Creative Commonsにオライリーから出ているRails3本の中身が載っているらしいけれど、やっぱり英語…ということで腰が引けてしまう。

そこで、過去の遺産を使うには prototype_legacy_helper が便利らしい。

$ rails plugin install https://github.com/rails/prototype_legacy_helper

これで link_to_remote などといった旧来のヘルパを扱うことができるようになる。上記「特定の text_field の中身を送信」は link_to_remote:with オプションでばっちり。

...
<%= text_field_tag :address_zipcode %>
<%= link_to_remote "AutoComplete", :url => find_zipcode_path, :with => "'zipcode='+$('address_zipcode').value" %>
...

:with オプションについてはネットにいろいろと情報があるけど、個人的に今回初めて使ったので解説を載っけておく。

ようするに "" で括られたJavaScriptらしい。で、JavaScriptの文字列でなければならず、 :with => "'key1=value1&key2=value2'" と指定すると、 params[:key1] == value1 などになる。 & で区切ってハッシュにしてくれるので、いちいちデータをシリアライズしなくてもいいみたい。

DOMオブジェクトを選択して中身を取り出すのはお馴染 $ 関数に value メソッドだけど、あくまで「JavaScriptの文字列」として渡す = '' で括る必要があるので、少し面倒くさい。そのサンプルは上のコードにある通りで、 "'zipcode='+..." とクォーテーションがいかにも読み辛いけど、Emacsの正規表現に比べたらマシと思えばマシ…かも知れない。

押してねっ→BlogPeople「趣味の世界」ブログランキング

2011年1月10日月曜日

Rails3 で大量の一括Insert

ができるらしい。

https://github.com/zdennis/activerecord-import

プラグインではないようなので v0.2.4 をダウンロードしてコピー。Wikiに載っているような使い方もできるけれど、個人的には

columns = [:first_name, :family_name]
values   = [["John", "Smith"], ["Jane", "Due"], ["Gonbe", "Nanashi"]]
Person.import columns, values

のような使い方のほうがメモリ効率がよさげかなと。

押してねっ→BlogPeople「趣味の世界」ブログランキング

[Ruby][Rails] CamelCase と snake_case

結構どこを探してもなかったんよね。
ヘルパーのAPIドキュメントにも無かったっぽいし。

つまりリフレクションしたいときとかにクラス名(CamelCase)をメソッド名(snake_case)に変換して __send__ したい場合があるじゃないのよさ。そんなときに一発変換できないかなと思って探してみたら、こんなメソッドが出てきました。

"CamelCase".underscore     # -> "camel_case"
"snake_case".camelize      # -> "SnakeCase"
"snake_case".pluralize     # -> "snake_cases"
"snake_cases".singularlize # -> "snake_case"
"snake_cases".classify     #  = "snake_cases".singularlize.camelize

classify メソッドなんてものもあった。で、こういうことがわかると検索したときに「あ、こんなに情報あるんじゃん」という事態になってしまう何かの法則。

あると便利だと思うので、Rails3からバラして使うのも良しですね。

押してねっ→BlogPeople「趣味の世界」ブログランキング

2011年1月4日火曜日

Ubuntuで動画を携帯向けにエンコードする

まず、Ubuntuに入っているffmpegのバージョンは古い、というより本家ffmpeg1日に1コミット以上という凄まじい早さで開発が進んでいるので、とりあえずSVNリポジトリからチェックアウトして、コーデックもろもろもインストールし、ffmpegのビルド&インストールも済んでいるものとします。

参考URLはこちら: http://ubuntu.futene.net/

変換は make で行おうと思ったのですが、ファイル名にスペースがあると面倒なことになりそうなので、Rakeを使うことにしました。といっても、ルールの定義をするだけな上に基本的なことも理解していないので、特に clean タスクの定義はぞんざいです。まぁ自分用なので、気に入らない方はスルーでおk。

さて、ffmpegへ渡すコマンドラインですが、ffmpegの取る引数はもう現代の魔法ですね。ボクも意味はほとんどわかりませんが、ネットの海から持ってきた情報をかき集めただけのものです。こんな感じ。

$ ffmpeg -i infile.avi -qmin 1 -qmax 51 -pix_fmt yuv420p -vcodec libx264 -coder 0 -vpre medium -bufsize 1000 -g 60 -vlevel 12 -qns -qpel -mbd 2 -s 320x240 -r 29.97 -b 512k -maxrate 5000 -acodec libfaac -ac 2 -ar 44100 -ab 96k -f 3g2 outfile.3g2

コーデックには一応フリーのものを使っています。で、これを2パスエンコードしようということで、Rakefileの登場なわけですね。

SRC = FileList["*mp4"]
DST = SRC.ext("3g2")
INSTALL_DIR = "/media/********************/Video/"


FFMPEG = "ffmpeg"
UNRECOGNIZED_OPTIONS = "-hq -bitexact -vprofile baseline -4mv -trell -aic -me full -fixaspect -muxvb 192 -muxab 64"
OPTIONS = "-qmin 1 -qmax 51 -pix_fmt yuv420p -vcodec libx264 -coder 0 -vpre medium -bufsize 1000 -g 60 -vlevel 12 -qns -qpel -mbd 2 -s 320x240 -r 29.97 -b 512k -maxrate 5000 -acodec libfaac -ac 2 -ar 44100 -ab 96k -f 3g2 -threads 8 -y"


task :default => DST


task :clean do
  DST.each do |file|
    if File.exists? file
      sh "rm '#{file}'"
    end
  end
  sh "rm -f *log"
  sh "rm -f *mbtree"
end


task :install => :default do
  raise RuntimeError, "マウントされてませんよ" if not File.exists? INSTALL_DIR
  DST.each{ |file| sh "cp '#{file}' #{INSTALL_DIR}" }
end


rule ".3g2" => ".mp4" do |t|
  sh "#{FFMPEG} -i '#{t.source}' -pass 1 #{OPTIONS} '#{t.name}'"
  sh "#{FFMPEG} -i '#{t.source}' -pass 2 #{OPTIONS} '#{t.name}'"
end

ほんとシンプルですが、 rake install とすると特定のディレクトリへコピーするようにしました。携帯への転送がWindowsじゃないと行えないので、NTFSパーティションに書き込むための措置です。それが見えなかったら RuntimeError 投げます。

というわけで自分用でした。

※転送しても見れなかったらごめんなさい。

押してねっ→BlogPeople「趣味の世界」ブログランキング

2011年1月2日日曜日

Windowsで再生できるのにUbuntuで再生できないDVD

知り合いの家に「ニャーン」じゃなくて「ニャァア)〜()ン」となく不思議な猫ちゃんがいるんだけど、しかも2匹いるんだけど、なんであんな色っぽいというか、(いや色っぽくはないな、雄だし)変な、というか、ツッコミたくなる鳴き声になっちゃったのでしょうね。甘え上手…? 違うと思う!

あいむふぁいんあんじゅー? 今日はDVD関連のお話ですよ。

レンタル屋さんで「おおかみかくし」を借りてきたら、「動画プレイヤーで開く」を選択しても再生できないでやんすよ。ちなみにUbuntu 10.10のお話。これは結局CSSという方式で暗号化されているからで、再生もろもろには辿り着けたのだけれども、それを忘れて何度も同じことをGoogle先生に尋ねてしまいそうなのでメモメモ。

まず「Ubuntu Tweak」をインスコしまする。そして medibuntu をどうにかして有効にして、ついでに HandBrake も有効化しておきます(※HandBrakeはDVD→MP4動画な変換ソフトちゃんです)。

すると Synaptics から libdvdcss2handbrake-gtk が見えるようになっているはずなので、そのふたつをインスコローリング。結論から言うと libdvdcss2 だけあれば Totem だろうが mplayer だろうが再生できるようにはなるんですが(もちろん各種コーデックのインストールも忘れずに)、HandBrakeも libdvdcss2 を自動的に参照して読めるようになってくれるようなので、お察しください

それでは簡単リッピング作業じゃないじゃないじゃない、動画鑑賞をお楽しみくださいNE☆

押してねっ→BlogPeople「趣味の世界」ブログランキング