• 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
 
21st Wed

acts_as_bits-0.2

変更点

  • (1) Rails2 の dirty column に対応
  • (2) 一括設定機能を追加

   1  class User < ActiveRecord::Base
   2    acts_as_bits :flags, %w( admin create read update delete )
   3  end
   4  
   5  user = User.new
   6  user.admin = true  # (1)
   7  user.changed?      # => true
   8  
   9  user.flags = true  # (2)
  10  user.flags         # => "11111"

インストール

   1  % gem install maiha-acts_as_bits --source=http://gems.github.com

posted by Png maiha on Wed 21 Jan 2009 at 19:56

個人的には git が色んな点で svn を上回っていると感じているが、 まだひとつだけ svn にしかない機能が残っている。

   1  % svn info
   2  URL: http://wota.jp/svn/rails/plugins/trunk/dsl_accessor
   3  ...

あれ?この git ってどこのレポジトリのだっけ? てのを速攻で知りたいときに git info がないのを恨めしく思う。 もちろん .git/config を見ればいいのだが、

   1  % cat .git/config
   2  ...
   3  [remote "origin"]
   4          url = git@github.com:maiha/mjs.git
   5          fetch = +refs/heads/*:refs/remotes/origin/*
   6  

svn info に比べると只でさえ type 数が多い上に、 サブディレクトリにいるときの type 数は分析不能になるので、 やっぱり辛い。 中身も長過ぎて url を見つけ出すのも大変だし、 最近では明け方には氷点下近くまで冷え込んでしまって、 コンビニ行くのも一苦労な昨今、 たっきーに

「git config すればええがな」 (声・略礼服の長老)

と言われたので、そうしました。

   1  % git config --list
   2  user.email=maiha@wota.jp
   3  user.name=maiha
   4  ...

--list は長いので、get で url だけ登録。

   1  % vi ~/.gitconfig
   2  [alias]
   3      url = config --get remote.origin.url
   4  
   5  % git url
   6  git@github.com:maiha/mjs.git

くぅ〜ん♪

でも、git info もやっぱり欲しいです

posted by Png maiha on Tue 13 Jan 2009 at 06:25 with 1 comment

init してgithub に first commit した最初のレポジトリだけ、なぜか git pull が動かない。

   1  % git pull
   2  ...
   3     branch.master.remote = <nickname>
   4     branch.master.merge = <remote-ref>
   5     remote.<nickname>.url = <url>
   6     remote.<nickname>.fetch = <refspec>

github の指示通りに実行したのにこの仕打ちはむごいよ。 確かに git pull origin を付ければ動作するのだが、 新たに clone したレポジトリだと git pull だけでも動作するのが、 なんか悔しい。

   1  % git pull origin
   2  Already up-to-date.

それでも type 数の節約を優先させるために仕方なく、 「first commit後は必ず一度 clone しなおす」 という生活にすっかり慣れきって、いよいよ冬が降るという話題も出るほど厳しい冬となってきた昨今、 たっきーに

「alias すればええがな」 (声・略礼服の長老(すべらない話/小藪))

と言われたのでそうしました。

   1  % vi ~/.gitconfig
   2  [alias] 
   3          up = pull origin
   4  % git up
   5  Already up-to-date.

くぅ〜ん♪

posted by Png maiha on Tue 13 Jan 2009 at 03:28

svn 時代

% svn st
% svn co ...

git に乗り換えた現在

% git status
% git checkout ...

status まで我慢我慢と言ってきたのにすまないが、 checkout はもう我慢できん! この田岡茂一、そうそう気の長い・・・とかそういうの。

alias 設定

そんな田岡さんにお薦めしたいのがこちら、 設定ファイル(.git/config or ~/.gitconfig)に

[alias] 
  st = status
  ci = commit
  co = checkout

こんな風に alias 定義しておくだけで

% git st
% git ci
% git co ...

くぅ〜ん♪

posted by Png maiha on Fri 26 Sep 2008 at 10:09

git でファイルの復活

  • コミット後に勢いよくリファクタリングするも途中で挫折
  • p なデバッグコードを埋め込んでいくも修正箇所を忘れた

なんてとき、指定した一部のファイルだけをコミット後の綺麗な状態に戻したいときがある。

svn の場合

svn は楽だった。消して update するだけでよかったから。 具体的には、モデルを全部戻したくなった場合は

% rm -rf app/models
% svn update

で楽に復旧できる。

git の場合

でも、git では fetch しても削除ファイルは華麗にスルーされて困っていた。 git は超便利だが、ここの一点で svn の方が使いやすいと思っていた。 が、checkout が所望のコマンドだと知った。 (Rails勉強会++)

% rm -rf app/models
% ls app/models
ls: cannot access app/models: No such file or directory
% git checkout app/models
% ls app/models
user.rb

あぁ、もうgitでいいです。 クゥ~ン♪

posted by Png maiha on Thu 28 Aug 2008 at 05:47 with 2 comments

   1  class Parts < ActiveRecord::Base # STI
   2  end
   3  
   4  class Parts::Image < Parts
   5  end
   6  
   7  part = Parts::Image.create!
   8  p part[:type]

で何が返るのかの話。

before 2.1

   1  part[:type]  # => "Image"

after 2.1

   1  part[:type]  # => "Parts::Image"

互換性

2.1 では AR::Base.sti_name メソッドが返す値を利用しており、 AR::Base.store_full_sti_class で制御できる。

   1  Parts::Image.store_full_sti_class  # => true
   2  Parts::Image.sti_name              # => "Parts::Image"
   3  Parts::Image.create[:type]         # => "Parts::Image"
   4  
   5  Parts::Image.store_full_sti_class = false
   6  Parts::Image.sti_name              # => "Image"
   7  Parts::Image.create[:type]         # => "Image"

応用

2.1 のSTIを利用するとナベアツな世界を簡単に作成できる。

   1  class Person < ActiveRecord::Base; end
   2  class Nabe < Person; end
   3  class Aho < Person; end
   4  def Person.sti_name
   5    Time.now.day % 3 == 0 ? "Aho" : "Nabe"
   6  end
   7  
   8  (1..3).map{ Nabe.create[:type] }
   9  => ["Nabe", "Nabe", "Nabe"]
  10  
  11  # Stub
  12  class Time
  13    def day
  14      3
  15    end
  16  end
  17  
  18  (1..3).map{ Nabe.create[:type] }
  19  => ["Aho", "Aho", "Aho"]

posted by Png maiha on Fri 8 Aug 2008 at 13:35

あるフィルタが定義されているかどうかを知りたいとき、 Controller.find_filter は非常に便利で 1.x 時代から一部で重宝されていたが、 このたび Rails2.1 ではなくなってしまった。 これは obsoleted というより内部のクラス構成が変わってしまったことに起因する。

before 2.1

   1  >> ApplicationController.filter_chain.class
   2  => Array

after 2.1

   1  >> ApplicationController.filter_chain.class
   2  => ActionController::Filters::FilterChain

Controller.filter_chain は定義されたフィルタが格納されたオブジェクトであるが、これが 2.1 からは専用のFilterChainクラスに進化した。 これにより、以下のような数々のフィルタ操作メソッド

  • find_filter
  • append_filter_to_chain
  • prepend_filter_to_chain
  • ...

を直接コントローラに定義する(2.1以前)のではなく、 同クラスに持たせることができるようになった。 これは、名前空間的にも責務の明瞭化としても進化と言える。

2.1 でのフィルタの検索

従って、今後は Controller クラスに直接問い合わせるのでなく、 フィルタを管理する FilterChain インスタンスに尋ねることになる。

   1  >> ApplicationController.filter_chain.find(:login_required)
   2  => #<ActionController::Filters::BeforeFilter:0x2621914 ...

互換性

しかしながら、未だに1.2ユーザも多いプラグインなどでは後方互換性が必要である。 色んな対応策が考えられるが、 ここでは、2.1 側に涙を飲んでもらって、 せっかく奇麗にした所を悪いが find_filter を追加させてもらう。

   1  ActionController::Base.class_eval do
   2    unless respond_to?(:find_filter)
   3      def self.find_filter(name)
   4        filter_chain.find(name)
   5      end
   6    end

(vendor/plugins/xxx/init.rb)

posted by Png maiha on Thu 7 Aug 2008 at 10:49

これまでの流れ

  1. Method#to_source を作るには Ruby を改造するしかない
  2. それは敷居が高すぎる
  3. MIME定義部分は block で妥協しよう

   1      def show
   2        @user = User.find(params[:id])
   3      end
   4  
   5      show.xml {
   6        render :text => @user.to_xml
   7      }

def と block の併用は、respond_to と同程度にキモイと気付いた

結論

そうだ!アクション定義も block にしよう!

   1      show {
   2        @user = User.find(params[:id])
   3      }
   4  
   5      show.xml {
   6        render :text => @user.to_xml
   7      }

SexyActions plugin

http://github.com/maiha/sexy_actions

動作要件

  • Rails2.x

Rails2.x の scaffold が生成する不細工なコード

   1  class UsersController < ApplicationController
   2    def index
   3      @users = User.find(:all)
   4  
   5      respond_to do |format|
   6        format.html # index.html.erb
   7        format.xml  { render :xml => @users }
   8      end
   9    end
  10  
  11    def show
  12      @user = User.find(params[:id])
  13  
  14      respond_to do |format|
  15        format.html # show.html.erb
  16        format.xml  { render :xml => @user }
  17      end
  18    end
  19  
  20    ...

俺達のセクシーなコード

   1  class UsersController < ApplicationController
   2    include SexyActions
   3  
   4    index      { @users = User.find(:all) }
   5    index.html # index.html.erb
   6    index.xml  { render :xml => @users }
   7  
   8    show      { @user = User.find(params[:id]) }
   9    show.html # show.html.erb
  10    show.xml  { render :xml => @user }
  11  
  12    ...

動作

  • 各アクションの定義は method_missing 経由で得られるproxyオブジェクトが行う
  • 表記の制約により、定義時のコンテキストはコントローラクラスになる
  • 従ってinstanceだけでなくclassに対してもアクション名と同名のメソッドが定義される

まとめ

  • 可読性が上がった (定義内容が1行の場合の美しさは半端ない)
  • 再利用性が上がった (あるMIMEの動作だけ再定義することができる)

致命的な問題

  • new アクションが定義できない事に気付いた><
posted by Png maiha on Tue 29 Jul 2008 at 17:30 with 1 comment

ActionController::Base#respond_to

ActionController の respond_to

  • MIMEタイプに応じて出力を切り替える素晴らしい手段を提供しつつも
  • 使う気をなくさせる表記法(実装)を採用する

というセンスがあるのかないのかわからない機能になっている。

   1    def show
   2      @person = Person.find(params[:id])
   3  
   4      respond_to do |format|
   5        format.html
   6        format.xml { render :xml => @person.to_xml }
   7      end
   8    end

誰がこんな面倒な記述を各アクションに書いていきたいと思うだろうか。

問題

具体的には以下の問題点が挙げられる。

  • やりたい事に対して無駄にコード量が多い
  • アクションが肥大化し可読性が低い
  • case文と同じで再利用性が低い

提案

上記の問題点を解消するために、 以下のような新しい表記法(実装)を提案する。

   1    def show
   2      @person = Person.find(params[:id])
   3    end
   4  
   5    def show.xml
   6      render :xml => @person.to_xml
   7    end

可読性は一目瞭然であり、 さきほどのサンプルと比べて以下のメリットを有する。

  1. アクションロジックと描画ロジックの峻別
  2. MIME単位での定義による可読性と再利用性
  3. 特異メソッド定義と拡張子の直感的親和性

特に3番目の、 "show.xml" のレスポンスを直感的に定義(記述)できる点は Rubyならではの機能と言える。

実装方針

   1  class ApplicationController < ActionController::Base
   2    class << self
   3      def method_missing(action, *arguments)
   4        if action_methods.include?(action.to_s)
   5          mime_respond_proxy_for(action)
   6        else
   7          super
   8        end
   9      end
  10  
  11      def mime_respond_proxy_for(action)
  12        @mime_responds[action] ||= MimeRespondProxy.new
  13      end
  14    end

  1. コントローラクラスの method_missing で、"def show.xml" の "show" 部分の未定義変数への参照をトラップ
  2. 定義済みのアクションであれば、Proxyオブジェクトを返す
  3. 以下、"def show.xml" 等の定義は Proxy オブジェクトへの(特異)メソッド追加となる
  4. default_render で、現在のアクションに応じた Proxy オブジェクトを取得し、指定されたmimeタイプの描画処理を行う
  5. コンテキストを合わせるために、aProxy.xml のメソッド定義の内容を実行中のコントローラに定義する
  6. singleton method bound for a different object (imkk)

反省会

  • singleton 以前に、他のメソッド定義を違うクラスに再定義できない

今後の選択肢

  • ブロック定義にすれば引き回せるよ (→ "def show.xml" と書ける心地よさが重要)
  • UnboundMethodを任意のクラス(Object)にbindさせてYO!
  • Method#to_sourceが欲しい
  • Method#modulizeでもいい
  • JavaScriptProxyみたいにする(helperとmethodとcontextをパピコ)
posted by Png maiha on Mon 28 Jul 2008 at 06:48 with 3 comments