Rails の touch 時に処理をフックする

任意のレコードの updated_at のみを更新する際に ActiveRecord#touch を使うことはあると思います。

class User < ActiveRecord::Base
end

user = User.create(name: "Homu")

pp user.updated_at.iso8601(10)
# => "2019-02-18T12:36:37.2315494900Z"

# updated_at のみを更新する
user.touch

pp user.updated_at.iso8601(10)
# => "2019-02-18T12:36:37.2315494900Z"

#touch 時に処理をフックする

#touch がやっていることは『updated_at の更新』なので after_save 等でフックしたくなるんですが残念ながら #touch 時には after_save は呼ばれません。

class User < ActiveRecord::Base
    # #touch 時に after_save は呼ばれない
    after_save {
        pp "after_save"
    }
end

#touch 時に処理をフックする場合は after_touch を使用します。

class User < ActiveRecord::Base
    # touch で更新した後に after_touch が呼ばれる
    after_touch {
        pp "after_save"
        pp updated_at.iso8601(10)
    }
end

user = User.create(name: "Homu")

pp user.updated_at.iso8601(10)

user.touch

pp user.updated_at.iso8601(10)
# output:
# "2019-02-18T12:40:49.6699328380Z"
# "after_save"
# "2019-02-18T12:40:49.6741588740Z"
# "2019-02-18T12:40:49.6741588740Z"

#touch がやっていることは更新なので after_save が呼ばれて当然と思っていてハマりました。 #touch 時には、

  • after_touch
  • after_commit
  • after_rollback

のみが呼ばれます。

Rails で default_scope を複数定義すると…

Railsdefault_scope を複数定義した場合、『両方の default_scope 』が適用されます。

class Article < ActiveRecord::Base
  default_scope { where(published: true) }
  default_scope { where(rating: 'G') }
end

Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'

最近 default_scope を使い始めましたけど便利ですね。 みんなもどんどん使いましょう(白目。

参照

https://api.rubyonrails.org/classes/ActiveRecord/Scoping/Default/ClassMethods.html#method-i-default_scope

Ruby でメソッド呼び出しの末尾に空の , があってもエラーにならない

来年はいっぱいアウトプットするぞ〜〜〜といいながら全然やってませんがわたしは元気です。
ところで Ruby って以下のコードみたいに meth(1, 2, ) っていう呼び出しはエラーにならないんですね。
普通に動いてて驚きました。

def meth a, b
    a + b
end

p meth(1, 2, )
# => 3

Ruby には知らないことが多いなあ。

2018年振り返り

2018年振り返り ということで今年やった事振り返りでも。
今年も今日で最後。

Ruby にパッチいっぱい投げた

Ruby にパッチいっぱい投げました。
その結果、Ruby 2.6 ではパッチが4つ取り込まれました、わーい。
どんなパッチを投げたのかはこちらを参照。

その他、bugs.ruby に投げたのは以下のようになります。

Hash#===Array#=== はほしいんじゃがなあ。
しばらくは Ruby にお世話になると思うので、来年も思いついたらパッチ投げたり修正できるバグ直したりとか貢献していきたいし、使い勝手をよくしていきたい。

Rails

生きるために Rails を始めました。
Rails 自体は全く触ったことがないわけではないけどほぼ何も知識がない状態で1年近く Rails やってました。
1年近く Rails やってわかってきたかというとぶっちゃけまだまだわからないことの方が多い。
けど、Rails の調べ方や書き方、コンセプトなんかは最近やっとわかってきたようには感じる。
あと最近は ActiveRecord のコード読みまくっているのでだいたいわかった…わけはなく、むしろなにもわからない…ActiveRecord なにもわからない…。
Rails に限っていえば偏った知識ばかり学んでいるので来年はもうちょいそのあたりアウトプットしていきたい。

rubygems

rubygems に関してはいろいろとつくりたいものはあるんだけど、あんまりつくれなかった感じ。
それでも今年前半はいろいろとつくっていた。

binding-debug は割と使う機会が多いのでもうちょい使い勝手よくしたいんですけどねえ。
元々 Ruby でおもしろライブラリつくるのが中心だったので来年はもうちょい役に立たないおもしろライブラリつくっていきたい。
iolite の v2 も開発せな…。

勉強会にがんばっていった

今年後半はあまり行けなかったんですが、前半はちょいちょい勉強会に行って LT してました。
後半に勉強会にあんまり行かなかった理由として『勉強会に行くなら LT をする』という目的をキメているんですが、あんまり LT 出来るような勉強会がなかったのもありましたね。
そもそも明確な目的とかがないとあんまり外に出たり人と会いたくないのはありますし…。
まあ、あとは単純に忙しくてあんまり余裕がなかったのもありますが。
地域.rb とかはテーマとかが合えばまた LT とかしに行きたいですねえ。
以下、今年つくったスライド

こうしてみるとスライド自体はかなり書いていますね。
来年も LT したいけどここまで書ける余裕はなさそう…。

買ってよかったもの

今年は開発環境回りとかで購入したものが多かったので買ってよかったものをいくつか上げます

4Kディスプレイ 27インチ + 31.5インチ

今年はディスプレイをめっちゃ買った記憶があるんですが、最終的には 4K 27インチ + 31.5インチに落ち着きました。
使うまでは 4K って言われても『画質が綺麗なんやなー』っていうぐらいの印象しかなかったんですが、実際使ってみると解像度が高いのでめっちゃ画面を広く使えてめっちゃよいです。
わたしとかは GUI マンなので何個もウィンドウを開いて開発しているので、1つのディスプレイにいくつもウィンドウが置けるのはだいぶよいですね。
ただ、Type-C が使えないのはちょっと不便かなーと最近思うようになりました。
流石にしばらくは新しいディスプレイは買わないと思いますが…。
ちなみにディスプレイはメルカリで買ったんですが、どっちも定価の1万円以上安く買えてよかったです。
あとモニタアームは HP シングルモニターアーム を使っています。
よさ過ぎて3つ買いました。
これ、Amazonベーシック モニターアーム シングルとものは同じなんですが HP の方が4000円近く安く買えるのでおすすめです。
モニタアームだと机を広く使えたり、ちょっとした微調整とかしやすいのがめっちゃいいですね。

デジタルメモ帳

タブレットでメモ取りたいなーでもその為にタブレット買うのもなーと思っていたらデジタルメモ帳っていうのを見つけたので試しに買ってみました。
実際に使ってみると書捨てのメモとかがササッと書けるので思ったよりも便利です。
脳内でまとまらないことも図にしてまとめてみたりとか。
タブレットと違って雑に扱えるのもいいですね。
ただ、10インチだとちょっと小さいので15インチぐらい大きいのがほしいんですけど、あんまり安いの売ってないんですよねえ。

関 兼次 ステーキナイフ

肉を切る時に便利。

来年目標

  • ブログを書いたり LT してアウトプットしたい
  • Ruby に貢献したい
  • Ruby 以外も書きたい

来年はもうちょいブログや LT とかでアウトプット出来るような年にしたいですねえ。
それではよいお年をー

Ruby でワンライナーでズンドコキヨシ

元ネタ

元ネタが2年以上前でめちゃくちゃ今更なんですが、やってみた。
脳内で考えたコードがワンライナーでそのまま動作したのでちょっとうれしかった

Ruby でズンドコキヨシ

puts (1..).lazy.map { %w(ズン ドコ).sample }.each_cons(5).map(&method(:p)).find(&%w(ズン ズン ズン ズン ドコ).method(:==)).join
puts "キ・ヨ・シ!"
# =>
# ["ズン", "ドコ", "ズン", "ドコ", "ドコ"]
# ["ドコ", "ズン", "ドコ", "ドコ", "ズン"]
# ["ズン", "ドコ", "ドコ", "ズン", "ズン"]
# ["ドコ", "ドコ", "ズン", "ズン", "ドコ"]
# ["ドコ", "ズン", "ズン", "ドコ", "ズン"]
# ["ズン", "ズン", "ドコ", "ズン", "ズン"]
# ["ズン", "ドコ", "ズン", "ズン", "ドコ"]
# ["ドコ", "ズン", "ズン", "ドコ", "ズン"]
# ["ズン", "ズン", "ドコ", "ズン", "ズン"]
# ["ズン", "ドコ", "ズン", "ズン", "ドコ"]
# ["ドコ", "ズン", "ズン", "ドコ", "ドコ"]
# ["ズン", "ズン", "ドコ", "ドコ", "ズン"]
# ["ズン", "ドコ", "ドコ", "ズン", "ズン"]
# ["ドコ", "ドコ", "ズン", "ズン", "ズン"]
# ["ドコ", "ズン", "ズン", "ズン", "ズン"]
# ["ズン", "ズン", "ズン", "ズン", "ドコ"]
# ズンズンズンズンドコ
# キ・ヨ・シ!

ワンライナーで書いているのでちょっと複雑かもしれませんが、やっていることは至極単純で

(1..)                           # Ruby 2.6 から入った (1..) で無限配列を生成
.lazy                           # 遅延処理するようにする
.map { %w(ズン ドコ).sample }   # ランダムな "ズン" "ドコ" な配列に変換
.each_cons(5)                   # 無限リストを5個ずつの配列に分割
.map(&method(:p))               # この時点で一旦出力
.find(&%w(ズン ズン ズン ズン ドコ).method(:==))  # ["ズン", "ズン", "ズン", "ズン", "ドコ"] の組み合わせを探す
.join                           # 結果を結合

無限リスト + #lazy を使って遅延処理しつつ、#each_cons(5) で分割して ["ズン", "ズン", "ズン", "ズン", "ドコ"] という並びがあれば終了する、っていう処理です。
やってることは割と力技。
ちなみに map(&%w(ズン ドコ).method(:sample)) と書きたかったんですが、#sample が引数を受け取ってしまうのでダメでした。

Ruby 2.6 に投げたパッチが取り込まれた話

先日 Ruby 2.6 がリリースされましたが、それにわたしが投げたパッチが4つ含まれています。
と、いうことで具体的にどんなパッチを投げてどんな機能が取り込まれたのかを簡単に書いてみようかと

Refinements で定義した #to_proc が &hoge 時に呼ばれないのを緩和する提案

表題そのままなんですが、次のように Refinements で to_proc を定義した場合、&hogeto_proc が呼ばれなかったのを呼ばれるようにしたパッチです。

using Module.new {
    # Refinements で to_proc を定義
    refine String do
        def to_proc
            proc { |it| it.send self }
        end
    end
}

p "upcase".to_proc.call "homu"
# => HOMU

# "upcase".to_proc が呼ばれるようになった
p %w(homu mami mado).map &"upcase"
# => ["HOMU", "MAMI", "MADO"]

このパッチを投げたのは Ruby 2.5 のリリース直前で、ドサクサに紛れて取り込まれないかなーという気持ちで投げました。
まあ流石に直前だと無理だったので Ruby 2.5 には入りませんでしたが、パッチ自体は特に問題なく取り込まれて Ruby 2.6 で正式に使えるようになりました。
ここから怒涛の Refinements パッチが始まります。

Proposal: Enable refinements to #public_send

先程の to_proc のパッチが取り込まれてからだいぶ時間が経って、11月取り込まれたパッチになります。
前回からだいぶ間が空いているんですが、この間にもいくつかパッチを投げており…。
matz に『日本語よりも英語で上げたほうがいいよー』とツッコまれたのでこのあたりからがんばって英語でパッチを投げるようにしました。
パッチも diff ファイルを直接添付していたんですが、CI を回したかったので github 経由で pull request を投げるようにしています。
パッチ自体は以下のように『public_send でも Refinements を有効にする』と言うような内容です。

using Module.new {
    # Refinements でメソッドを定義
    refine String do
        def twice
            self + self
        end
    end
}

# public_send 経由でもメソッドが呼ばれるようになった
p "homu".public_send(:twice)
# => "hogehoge"

まあ #send も Refinements が有効になっているから #public_send も有効になってもいいよねーという気持ちで提案しました。
提案したものの(Refinements 的な意味で)議論されて取り込まれるまでに時間がかかるかなー、と心配していたんですが割とあっさり入ったのでちょっと面食らいました。
感触としては Ruby の方針としてはこのあたりはそこまで厳密に Refinements を制限する、という感じではなさそうな感じなんですかね。

Proposal: Enable refinements to #respond_to?

これも先程の #public_send と同じで『#public_send で Refinements を有効にする』というパッチになります。

using Module.new {
    # Refinements でメソッドを定義
    refine String do
        def twice
            self + self
        end
    end
}

p "homu".respond_to?(:twice)
# => true

こちらも #public_send と同じタイミングで取り込まれました。

Ruby で定義したメソッドに&:hogeを渡しても refinements が有効にならない

これは今回投げた中では一番大変なパッチでした。
このパッチを投げた経緯はこちらを参照してもらうと詳しく書いてあります。

パッチ自体は9月ぐらいに書いてすぐに投げたんですが、その後特に進展がなく、先月頭ぐらいにバグ認定されて、今月の頭に取り込まれたんですが、テストで落ちてたみたいで一旦 revert されて…という経緯があります。
Ruby 2.6 に絶対に入れたかったのでテストで落ちていた箇所を直してもう一度パッチを投げたんですが、そしたら別の場所でテストが失敗してしまったらしくて…。
けど、原因を調べてみたら実際はこのパッチとは全然関係ない箇所で、しょうがないのでこっちで原因を調べてみたら実は Ruby のバグを踏んでいてそれを報告して…。

というのがここ1〜2週間のハイライトになります。
まさか trunk で Ruby のバグを踏んでいるとは思わなくて実はかなり悩んでいました。
まあ結果的にこの不具合が修正されたので Ruby 2.6 ではいい感じに Refinements が使えるようになると思います。

using Module.new {
    # Refinements でメソッドを定義
    refine String do
        def twice
            self + self
        end
    end
}

def meth &block
    block.call "homu"
end

# Ruby で定義したメソッドのブロック引数でも有効になった
p meth &:twice

これが今まで動かなかったんですよねー。
何が嬉しいかって ActiveRecord のリレーションで定義されている map のブロック引数とかでも Refinements が有効になったのが大きいかなーと思います。
ってか、それで使えなくて困っていたのでパッチ書いて投げました。

所感

と、言う感じで Ruby 2.6 で取り込まれたパッチをいくつか紹介しました。
改めて見ると全部 Refinements のパッチですね、これ。
Refinements はもっと使い勝手よくしたいんですよねえ。

ちなみに今回取り込まれたパッチは4つでしたが、実際にはこの倍近くのパッチを投げています。
本当は #method#method_missing で Refinements が有効になるパッチも投げていたんですが、残念ながら Ruby 2.6 には間に合いませんでした。
このあたりが Ruby 2.7 で取り込まれるとだいぶ Refinements が使いやすくなるかなーと思います。
Ruby 2.7 で入るといいですねえ。
そろそろ Ruby のパッチを書くのにも慣れてきたので、何か思いついたらまたパッチを投げていきたいですねー。
パッチを書くよりも取り込まれるまでが大変ですが…。

今日は Ruby 2.6 がリリースされる日!!

今日は年に一度の Ruby がリリースされる日ですね!!!
クリスマス?知らないですねえ…。

と、言うことで Ruby 2.6 が無事にリリースされました。やったー!

そんなわけで気になった機能をいくつか上げてみようかと思います。
ちなみに Ruby 2.6 にパッチを10個ぐらい投げた結果 4つ取り込まれました。わーい。

Kernel#thenKernel#yield_selfエイリアスとして追加

Kernel#yield_selfエイリアスとして Kernel#then が新しく追加されました。
yield_selfthen と短く書けるやったー、と思う一方 then って名前はどうなんですかね…という思いもあり…。
まあ文字数が半分以下になるのは喜ばしいこと。

Enumerable#filter Enumerable#filter! が追加

Enumerable#selectエイリアスとして Enumerable#filter が追加されました。
これで同機能の Enumerable の絞り込みメソッドが

  • Enumerable#select
  • Enumerable#find_all
  • Enumerable#filter

の3つに増えました。
知らない人から見るとますます混乱しそうな気もしますが…。
Ruby をやり始めた頃は『なんで filter がないんだよ』と思っていた思い出。

Proc#<<Proc#>> が追加

Proc にはいわゆる関数合成を行う演算子が新しく追加されました。

f = proc{|x| x + 2}
g = proc{|x| x * 3}
p (f << g).call(3) # -> 11; identical to f(g(3))
p (f >> g).call(3) # -> 15; identical to g(f(3))

f << gf(g()) になり、f >> gg(f()) になります。
また、Method にも同等の演算子が追加されています。

f = proc{|x| x + 2}
g = proc{|x| x * 3}

p (1.method(:+) << 2.method(:*)).call(3)  # => 7  (1 + (2 * 3))
p (1.method(:+) >> 2.method(:*)).call(3)  # => 8  ((1 + 3) * 2)

# Proc と組み合わせたり
p (1.method(:+) << g).call(3)   # => 10  ((3 * 3) + 1)
p (f >> 2.method(:+)).call(3)   # => 7   ((3 + 2) + 2)

Proc#<<Proc#>> ともに『#call が定義されている』オブジェクトを渡すことが出来ます。

class G
    def call x
        x * 3
    end
end

f = proc{|x| x + 2}
g = G.new
p (f << g).call(3)  # => 11  f(g(3))

所感

と、いう感じで少しですが Ruby 2.6 の機能を取り上げてみました。
他には終端なし Range が追加されたり #to_h にブロック引数が追加されたりなどなど。
気になる方はリリースノートを見てみるといいともいます。
さて、Ruby 2.6 がリリースされたということは Ruby 2.7 の開発が始まったことになります。
Ruby 2.6 ではパッチを10個投げた結果 4つ取り込まれました。
Ruby 2.7 でもまだまだ追加したい機能がたくさんあるので、Ruby 2.7 ではもう少しがんばってパッチを投げていきたいと思います。
あと直せる範囲であればバグ修正とかも手伝えるといいですねえ…。
そんな感じで来年も Ruby 2.7 を楽しみにしています!!