Archive for the ‘ Ruby ’ Category

delayed_job の daemon がログを吐くのを抑制

SQL (0.4ms) UPDATE `delayed_jobs` SET `locked_at` = '2013-04-14 05:23:00', `locked_by` = 'delayed_job host:hoge.lan pid:74793' WHERE ((run_at <= '2013-04-14 05:23:00' AND (locked_at IS NULL OR locked_at < '2013-04-14 01:23:00') OR locked_by = 'delayed_job host:hoge.lan pid:74793') AND failed_at IS NULL) ORDER BY priority ASC, run_at ASC LIMIT 1
SQL (0.4ms) UPDATE `delayed_jobs` SET `locked_at` = '2013-04-14 05:23:05', `locked_by` = 'delayed_job host:hoge.lan pid:74793' WHERE ((run_at <= '2013-04-14 05:23:05' AND (locked_at IS NULL OR locked_at < '2013-04-14 01:23:05') OR locked_by = 'delayed_job host:hoge.lan pid:74793') AND failed_at IS NULL) ORDER BY priority ASC, run_at ASC LIMIT 1

こんな感じのログが毎秒出力されるので、非常に精神衛生上よろしくない。
止めましょう。

module Delayed
  class Job
    class < < self
      def reserve_with_log_silencer(worker, max_run_time = Worker.max_run_time)
        silence { reserve_without_log_silencer(worker, max_run_time) }
      end
      alias_method_chain :reserve, :log_silencer
    end
  end
end

redirect_to and return と書いて良いのか?

redirect_to root_url unless logged_in?

みたいなコードを初めのうちはつい書いてしまいハマる。
正しくは、以下のように return しなければならない。

unless logged_in?
  redirect_to root_url
  return 
end

しかし1行で書けていたものが4行になってしまうのはいかにも不格好である。

あちこちでコードを読んでいるとたまにこんなのを見かける。

redirect_to root_url and return unless logged_in?

これは見た目もかっこいいし意味も判り易くて cool だ。

ところで、 and return ってなんだ?

これは何の事はない、 redirect_to の戻り値がたまたま true なのを利用して、 and で受けて次の文を書いているだけである。
redirect_to が万が一 false や nil を返してきたら return は実行されない。
ちなみに redirect_to の実装を見ると、少なくとも現時点では false や nil にならないことは確実だが、将来に渡って保証されているわけではないので気持ち悪い。

and の代わりに true/false に関わらず確実に実行する接続詞として ; があるので、以下のように書いてみる。

redirect_to root_url; return unless logged_in?

残念ながらこれはうまくいかない。 unless より ; のほうが優先順位が高いので、

redirect_to root_url
return unless logged_in?

という意味になってしまう。

仕方なくカッコでくくる。

(redirect_to root_url; return) unless logged_in?

これは正しく動くが、かっこわるいよねえ。カッコ付けててかっこ悪いってやつ?

さらに他のコードを読み漁ると、こんな書き方もある。

return redirect_to(root_url) unless logged_in?

and return 記法に比べればcoolさは劣るが、比較的スマートな書き方だ。何の意味も無い値を return することについては一瞬不安になるが、 action の戻り値を気にしなければならなくなるような仕様変更は将来的にも有り得ないと思われるので、問題ないだろう。

というわけで、結論としては return redirect_to 記法をオススメ、というところで筆を置くつもりだったのだが、何やら公式っぽいドキュメントに、「and return」って書くといいよ! と記述されてるので、いいのかもしれんね。
(だったら redirect_to の実装の末尾に true の一行を足しておきたいなあ)

2つ以上の情報源がある際の truncate

以下のコードをお好きな model なり helper なりに。

  def smart_truncate(str, len)
    return str.truncate(len) if str.is_a?(String)
    raise ArgumentError.new(:str) unless str.is_a?(Array)
    num = str.length - 1
    return str[0].truncate(len) if num == 0
    len -= str.last.length
    (0...num).to_a.reverse.each do |i|
      each_len = len / (i+1)
      str[i] = str[i].truncate(each_len)
      len -= str[i].length
    end
    str.join("")
  end

使い方は、省略可能な文字列のセットを配列にしたものと、最終的に納めたい文字数。例えばこんな感じだ。
smart_truncate([subject, content, author, url], 140)
最後の要素は省略されずに必ず全て表示される。

動作の結果は以下のとおり。

  describe "smart_truncate" do
    it { smart_truncate("abcde", 4).should == "a..." }
    it { smart_truncate(["abcde"], 4).should == "a..." }
    it { smart_truncate(["123456","78"], 8).should == "12345678" }
    it { smart_truncate(["123456","78"], 7).should == "12...78" }
    it { smart_truncate(["123456","789012", "3456"], 16).should == "1234567890123456" }
    it { smart_truncate(["123456","789012", "3456"], 15).should == "12345678...3456" }
    it { smart_truncate(["123456","789012", "3456"], 14).should == "12...78...3456" }
    it { smart_truncate(["123456","789012", "3456"], 13).should == "12...7...3456" }
    it { smart_truncate(["12345" ,"789012", "3456"], 13).should == "123457...3456" }
  end

Rails で 華麗に sql union する

model の中にこんなのを書く
[crayon lang=”ruby”]
class User < ActiveRecord::Base module UnionHack def union(*relations) from '((' + relations.map { |r| r.ast.to_sql }.join(') UNION (') + ')) AS ' + self.table_name end end extend UnionHack end [/crayon] そんで、以下のように使える。 [crayon lang="ruby"] User.union(@group1.users, @group2.users).limit(20) [/crayon] 参考: http://coderwall.com/p/9hohaa

resources を nest するときは shallow を使うと幸せになれる

既にみんな使ってると思うけど、日本語の解説が見あたらなかったので一応書いた。

[crayon lang=”ruby”]
# config/routes.rb

resources :group do
resources :user
end
[/crayon]

こんな風にすると、新規に User を作る時の URL が /groups/:group_id/users/new となって、大変美しい。
ところが、いざ User が生成された後にその User を show するには /groups/:group_id/users/:id などとしなければならなくなる。
これは冗長である。 User の id が group に寄らず unique であるなら、 /users/:id で参照出来て然るべきだ。

そこで、shallow です。

[crayon lang=”ruby”]
# config/routes.rb

resources :group, shallow: true do
resources :user
end
[/crayon]

こうすると、なんと、

# rake routes
 group_user_index GET    /group/:group_id/user(.:format)          user#index
                  POST   /group/:group_id/user(.:format)          user#create
   new_group_user GET    /group/:group_id/user/new(.:format)      user#new
        edit_user GET    /user/:id/edit(.:format)                 user#edit
             user GET    /user/:id(.:format)                      user#show
                  PUT    /user/:id(.:format)                      user#update
                  DELETE /user/:id(.:format)                      user#destroy
      group_index GET    /group(.:format)                         group#index
                  POST   /group(.:format)                         group#create
        new_group GET    /group/new(.:format)                     group#new
       edit_group GET    /group/:id/edit(.:format)                group#edit
            group GET    /group/:id(.:format)                     group#show
                  PUT    /group/:id(.:format)                     group#update
                  DELETE /group/:id(.:format)                     group#destroy

な、なんて美しいんだ。。!

何が起きているかというと、user_id を指定しない action である index, new, create の3つは group_id を必要とし、それ以外の action では user_id のみを指定すればよい、ということに、たったの “shallow: true” だけでなってしまったのである。

もちろん、以下のように action を拡張した場合

# config/routes.rb

resources :group, shallow: true do
    resources :user do
      get :search, on: :collection
      post :follow, on: :member
    end
end

には、

# rake routes
(抜粋)
search_group_user_index GET    /group/:group_id/user/search(.:format)   user#search
            follow_user POST   /user/:id/follow(.:format)               user#follow

そうですそうです、そうして欲しかったんですよ、という状態にちゃんとなっている。

さらに深く resources を nest した場合にも、 shallow:true は1つだけで大丈夫。

# config/routes.rb

resources :group, shallow:true do
  resources :user do
    resources :entry
  end
end

この場合、entries#new は /user/:id/entries/new となり、 entries#show は /entries/:id となる。

shallow の挙動は、resources の nest 問題について一度もハマってみたことの無い人にはさっぱり理解出来ないような一見複雑な動作だが、物凄くよく考え抜かれている。Rails のこういうところが結構好きなのだな、僕は。

最後に、おそらく多くの方が form でつまずくと思うので、その時はこれを読みましょう: http://stackoverflow.com/a/9944554/683157

MySQLで緯度経度からの半径で円内検索する

Rails3 では、model に

  scope :origin, ->(lat,lng){
    factor=Math.cos(lat/180*Math::PI)
    select([table_name+".*", sanitize_sql_array(["111.11*GLength(LineString( point(?,?), point(x(geom)*?,y(geom)) )) AS distance", lng*factor, lat, factor])])
  }

としておいて、

  Location.origin(lat,lng).order("distance")

とか、

  Location.origin(lat,lng).where("'distance' < ?", dist)

とか。

計算式は何をしてるかというと、まず、中心地点の緯度から、その地点における緯度と経度の長さの比を算出。(緯度1度あたりの距離は地球上どこでも同じだが、経度1度あたりの距離は赤道から離れるほど小さくなる。東京では0.8倍くらい)これをfactorに保存。
GLength(LineString(point,point)) で三平方の定理を計算してくれるので、経度を補正してこれに入れ、出てきたものに111.11km(赤道における1度の長さ)を掛ける。

glyph icon helper for twitter-bootstrap with Rails


module ApplicationHelper
# ==== Examples
# glyph(:share_alt)
# # => <i class="icon-share-alt"></i>
# glyph(:lock, :white)
# # => <i class="icon-lock icon-white"></i>
def glyph(*names)
content_tag :i, nil, class: names.map{|name| "icon-#{name.to_s.gsub('_','-')}" }
end
end

has_many の要素を update_attributes する際に uniqueness validation が効かない

class Man < ActiveRecord::Base
  has_many :cards
  accepts_nested_attributes_for :cards
end

class Card < ActiveRecord::Base
  belongs_to :man
  validates :number, uniqueness:{ scope: :suite }
end

m = Man.new
m.update_attributes({
  cards_attributes: [
    { suite: "spade", number: "4" }, #(1)
    { suite: "spade", number: "4" }, #(2)
    { suite: "club", number: "6" },  #(3)
  ]
})

Card の validation が走る時点で、 #(1) はDB上に存在しないので、#(2) の validation がまんまと通ってしまう。

update_attributes を使わずに、
m.cards << Card.new(suite: "spade", number: "4") などと1つずつ入れればもちろんOKなのだが。。

Model.find(array) で順序が狂う

rails 覚えたての頃に書いた以下のようなコード

def items(array)
array.map{ |i| Item.find(i) }
end

を、以下のように書き換えてハマる。

def items(array)
Item.find(array)
end

タイトルに答えを書いちゃったけど、find(array) は array の順序を保存しない。
array が sort されてれば大丈夫かと思ったが、 mysql では OK だが postgresql ではダメだった。
幸い僕のケースでは array が sort 済みのものだったので、 find(array, :order=>:id) で丸く収まったが、array が sort されてない場合には厄介そうだ。

list_tag で Array をリストに展開

あったらいいのに、ということで作った。ApplicationHelper とかに入れればOK.
array がnilや空のときには ul そのものが出ないのがポイント。

  # ex:
  #  < %= list_tag(:ul, ["menu1", "menu2"], :class=>"menu") %>
  # => 
  def list_tag(wrapper, array, *opt)
    return if array.to_a == []
    content_tag(wrapper, *opt) do
      array.inject(""){|s,i|
        s < < content_tag(:li, i)
      }.html_safe
    end
  end