Hatena::ブログ(Diary)

Lazy Technology

2007-06-18

[][]AR#==

ActiveRecordの==はどう動くのか気になったので調べてみた。

例えばSNSで日記にユーザからコメントがついた場合、新着に表示する時にはまず日記を書いたユーザとコメントを書いたユーザが別かどうかを判定しなきゃいけない。


早速ソースを読む。

def ==(comparison_object)
  comparison_object.equal?(self) ||
    (comparison_object.instance_of?(self.class) && 
      comparison_object.id == id && 
      !comparison_object.new_record?)
end

つまり、

と言う事。


#IDが一緒ならTrue
user1 = User.find(1)
user2 = User.find(1)
user1 == user2  #=> true

#IDが違えばfalse
user3 = User.find(2)
user1 == user3 #=> false

#オブジェクトIDが一緒ならtrue
user4 = user1
user4 == user1 #=> true

user4.id = 3
user4 == user1 #=> true
user1.id #=> 3


#新規レコードならIDが一緒でもfalse
user_new1 = User.new
user1 == user_new1 #=> false

user_new1.id = 1
user1 == user_new1 #=> false

#新規レコード同士でIDが一緒でもfalse
user_new2 = User.new
user_new2.id = 1
user_new1 == user_new2 #=>false


たぶん問題は出ないと思う。

よく考えられてるなぁ。

2007-06-15

[] HTTP POST ≒ request.post?

例えば以下のようにlink_toメソッドを呼び出してリンクを作成すると、HTTP的にはPOST扱いとなる。

link_to("削除", {}, :confirm => "削除する?", :delete)

HTTPリクエストは以下のように発行される。

POST / HTTP/1.1
(中略)
_method=delete

で、このリクエストをコントローラで判断してみる。

  request.post? #=> false
  request.delete? #=> true

request.post?がfalseになるのはかなり違和感がある。

requestはHTTP Requestを表現するオブジェクトなのだから個人的にはrequest.post?はHTTP POSTだったら常にtrueであるべきとは思うんだけどな。*1


まぁたぶんRESTとかActiveResourceとかに関連してくる話なのだと思う。

DHHがRailsConf2006で語っていた気がするけどまだ詳細は押さえていなかったり。

[] Developmentモードのログを日毎にローテートする

RubyのLoggerは簡易的ながらもローテーションする事ができるのでそれを使えば簡単にローテーションを実現できる。

config/environments/development.rbに以下を追記すると日毎にローテーションするようになる。

config.logger = Logger.new(config.log_path, 'daily')

期間毎のローテーションのパラメータは以下の3種類。第二引数に指定する。

'daily'
'weekly'
'monthly'

参考

no title

20070617追記

ローテーションしているとログファイルの所有者がmongrelの動作アカウントになる事に注意。

ユーザアカウントmongrelアカウントが違う場合、script/consoleで何か実行するとパーミッションエラーが出てしまう。

*1:method.post? method.delete?とかだったらいいんだけど…。

2007-06-12

[]ActiveSupportに年月表示用のメソッドを追加する方法

以下をenvironment.rbに追加する。

ActiveSupport::CoreExtensions::Time::Conversions.class_eval do
  def to_yymm
    strftime("%y/%m")
  end
end

MySQLの場合はDateTime型がRubyのTime型にマッピングされるからTime型を拡張している。


YYMM形式で表示したいシステムは結構多いと思うなぁ。


ついでにデフォルト時刻表示フォーマットを変える方法も載せておく。

ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.update :default => "%y/%m/%d %H:%M"
ActiveSupport::CoreExtensions::Date::Conversions::DATE_FORMATS.update :default => "%y/%m/%d"

2007-06-09

[][] date_selectヘルパーの注意点

date_selectヘルパー引数で渡したインスタンス名とメソッド名から年月日の選択状態を自動的に判断してくれて便利なのだけれど、インスタンスがローカル変数だと値を読み取ってくれず、常に最新の日付になってしまうと言う問題がある。

  date_select(object_name, attribute_name)

上記のようなコードの場合、object_nameじゃなくて@object_nameに目的のインスタンスが入ってなきゃダメだった。

date_selectに限らず全般的にそうなのかも。スコープが原因なのかなぁ。

基本的に編集画面で表示されるインスタンスは一つだろうからそんなに多発する問題ではないと思うけれど、ちょっと不便と言うかドキュメントに明記しておいて欲しいなと言うか。


理由を詳しく知りたいと思ってactionpack\lib\action_view\helpers\date_helper.rbを調べるも、363行目付近の以下のコードで挫折。

        def date_or_time_select(options)
          defaults = { :discard_type => true }
          options  = defaults.merge(options)
          datetime = value(object)
          datetime ||= Time.now unless options[:include_blank]

datetimeオブジェクトが選択状態を決定するための変数なんだけど、datetime = value(object)っていうコードが何をしているのかが不明。valueが何なのか分からない。。。


同じように疑問に思ってる人もいるみたい。凄い昔だけど…。

3 日坊主日記 - date_select , actionpack-controller_name-untaint.patch

2007-06-08

[]ちょっと便利なObject#blank?メソッド

ActiveSupportの拡張。

Stringでnilもしくは空文字列を判断する制御文がシンプルになる。

  #before
  if str && !(str.empty?)
  #after
  unless str.blank?

実装はこちらを。

no title

参考

no title

2007-06-07

[]ActiveHeartはどのようにカラム名の日本語化を実現しているか

Base.set_field_namesメソッドでは定義したカラム名と日本語名の対照用ハッシュ(@field_names)を作成する。

そして、ActiveRecord::Base#human_attribute_name(column_name)と言うメソッドをaliasにより独自メソッドに置き換え、引数のカラム名が@field_namesに含まれていればハッシュから返す。なければオリジナルのメソッドを呼び出す。


と言うものだった。

以下のコードがすべてかな。

    def set_field_names(field_names = {})
      @field_names = HashWithIndifferentAccess.new unless @field_names
      @field_names.update(field_names)
    end
    alias_method :_human_attribute_name, :human_attribute_name
    def human_attribute_name(attribute_key_name)
      if @field_names && @field_names[attribute_key_name]
        @field_names[attribute_key_name]
      else
        _human_attribute_name(attribute_key_name)
      end
    end

SpecialGenerationではlocalize/[table_name].ymlに同じような感じに書くのがDRYじゃないなぁ、なんて思ったり。

plugin-suiteみたいなのできないかなー。

参考

404 Not Found

no title

2007-06-04

[][]Railsではアクション内にConst変数を宣言することができない?

以下のようなコードをアクションに書いたらシンタックスエラーになってしまった。

  Item = Struct.new("Item", :value, :name)
  case type
  when 'select'
    item.new(0, "選択して下さい")
  when 'search'
    item.new(0, "すべて")
  else
    item.new(0, "すべて")
  end

エラーの出力内容は以下の通り。

SyntaxError (./script/../config/../app/controllers/hoge_controller.rb:55: dynamic constant assignment


定数は一度宣言してから代入しようとはしてないし…そもそも宣言でエラーだし。

グーグル先生に聞いてみるとRails Tracで同様の問題が報告されていた。

no title


回答の肝となる部分を引用。

Ruby doesn't allow assigning class constants within method bodies, hence you get the "dynamic constant assignment" exception. It's not exclusive to Struct nor to Rails:

http://dev.rubyonrails.org/ticket/4287

つまり、メソッドの中ではConst変数は宣言できないと言う事らしい。

def e
  Hoge = "aa"
end
#=>
SyntaxError: compile error
(irb):2: dynamic constant assignment
Hoge = "aa"
      ^
        from (irb):3

なるほど…。

と言うわけで、アクションメソッドの外にStructのサブクラスの宣言を移したら無事に動いた。


結論として、Railsではアクション内にConst変数を宣言することができない、のではなくRubyではメソッド内でConst変数を宣言することができない、と言う事なのね。

参考

Page not found – The Real Adam

2007-06-01

[][]ARのWhere句を構築してくれるEz-Where pluginの次期版が良さげ

ARではWhere句を構築するのに結構手間がかかるので、いくつかpluginが公開されている。

はてなダイアリー


とは言うものの、どれも機能的には貧弱に見えたのでこれだったら我慢して直で書いちゃおうかな、と思っていたらez_where_twoと言うEz-Where pluginの次期版を発見した。

no title


公式ブログで言及されているわけでもないので、チーム内や作者だけが使っている秘密のブツなのかも。公開すればいいのに。

READMEを読んでみるとかなり良さげだったので試してみる事に。

ruby script/plugin install http://opensvn.csie.org/ezra/rails/ez_where_two/

READMEのサンプルコードを若干修正したら動いた。

ANDとORの優先順位を認識し、括弧付けしてくれるのが素敵だ。

cond = Caboose::EZ::Condition.new
cond += cc { name == 'fab' } + cc { login =~ 'loob%' } # AND - AND
cond -= cc { age < 20 } | cc { login_count < 10 }      # AND NOT - OR
cond |= cc { login == 'admin' }                        # OR - 
cond += { :color => 'red' }
cond.to_sql
=> ["(((((name = ?) AND (login LIKE ?))) AND NOT ((age < ?) OR (login_count < ?))) 
OR (login = ?)) AND (color = ?)", "fab", "loob%", 20, 10, "admin", "red"]

個人的に高評価だったは、Where Pluginと違ってnewしただけの状態でto_sqlを呼ぶとnilが返る点。

cond = Caboose::EZ::Condition.new
cond.to_sql #=> nil

検索画面などで動的に検索項目を切り替える場合、何も入力しなかった場合は全件ヒットするのが一般的なUIだと思う。

:conditionsにnilを渡すと全件ヒットするのでこの方が都合が良い。


他にもREADMEに色々書いてあるので参考にしよう。

もうちょっと使い込んでみてまた再評価したい。

参考

no title

Application Error