RailsのN+1避けのものもう一つ作った

RailsでFoo.where(condition).n1_safeと付け加えるだけで簡単にN+1クエリを回避出来る奴
https://github.com/tompng/n1_safe

使い方

gem 'n1_safe', github: 'tompng/n1_safe'
@posts = Post.all.includes(:comments,ほげほげ)

みたくincludesする代わりに

@posts = Post.all.n1_safe
@post = Post.find(params[:id]).n1_safe

とつけ加えるだけでN+1が起こらなくなる。

@posts.each{|post|
  post.comments.each{|comment|
    comment.user.id
    comment.favs.count
  }
}

SQLはこんな感じ

Comment Load (0.8ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN (1, 2, ... 19, 20)
User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (20, 16, ... 15, 4)
 (0.5ms)  SELECT COUNT(*) AS count_all, target_id AS target_id FROM "favs" WHERE "favs"."target_id" IN (49, 73, ... 55, 67) AND "favs"."target_type" = ? GROUP BY "favs"."target_id"  [["target_type", "Comment"]]

↑可能ならgroup_by/countもやるよ 出来ない場合(has_many throughの場合など)はpreloadしてto_a.sizeで代用


前作ったsmart_jsonは依存関係を定義から読んで先にincludesする方針。
今回は呼ばれて必要になった時にあとからpreloadする方針。
n1_safeを呼んだルートとなるオブジェクトを覚えておいて、
関連モデルを呼び出した時とRelationを配列にする時に、それを起点にpreloadする。

・BasicObject便利
ActiveRecord::Associations::Preloader便利
・バージョン違い辛い
ActiveRecord::Base#reflectionsが無くなってたりreflectionsのkeyがsymbolかstringか違ったり

RailsでN+1が出ないようにas_jsonぽくする奴を作った

smart_json: RailsでN+1が出ないas_jsonぽい奴作りました。
https://github.com/tompng/smart_json
ちょくちょく直しながら作ってます。んで、実践投入しようかなと思ってるので記事書いとこうと。

smart_json?

gem 'smart_json', github: 'tompng/smart_json'
blogs.as_smart_json(posts: [:タイトル日付のみ, author: :詳細, comments: {user: :名前だけ}])

と指定してjsonを作れる、そして必要なincludesを自動でやってくれるのでN+1問題も起きない(はず)

smart_jsonが解決する問題:その1

posts.as_json(include: {comments: {include: :user}})

とかするとN+1クエリが出る、かといって

posts.includes(comments: :user).as_json(include: {comments: {include: :user}})

とするのはだるい。Don't repeat yourselfだくそ野郎

smart_jsonを使うと...

posts.as_smart_json(comments: :user)

smart_jsonが解決する問題:その2

as_jsonでonlyやmethodsに指定する内容は数通りしかないので、そのままだと

user.as_json(only: [:id, :name])
comment.as_json(only: [:id, :text], include: {user: {only: [:id, :name]}})
posts.as_json(include: {comments: {only: [:id, :text]}})
blogs.as_json(include: {owner: {only: ...}, posts: {include: {author: {only: ...}, comments: {only: ...}}}})

と重複だらけになってひどいしN+1対策でさらに長くなる。

smart_jsonでスタイルを定義しておけば...

class User
  smart_json_style(:default){as_json only: [:id, :name]}
end
class Comment
  smart_json_style(:default){as_json only: [:id, :text]}
  smart_json_style(:詳細, :user)
end
class Post
  smart_json_style(:簡易){{title: title, body: body[0..30]}}
  smart_json_style(:コメントなど含む, :簡易, :author, comments: :詳細)
end
user.as_smart_json
comment.as_smart_json(:詳細)
posts.as_smart_json(:comments)
blogs.as_smart_json(:owner, posts: :コメントなど含む)

とすっきり書ける。これまた依存関係全部includesしてくれる。

誰か実験台になってくれちょ
(あと似たようなのが既にあれば教えてほしい)

将棋倶楽部24勝手にUIパッチ

将棋倶楽部24の対戦画面のUIをちょっと派手にするパッチのようなものを作りました。
1〜2年くらい前に。データ無くさないようにとgithubに上げた次第。
https://github.com/tompng/shogipatch24

将棋倶楽部24のUIがこんな感じに変わります。

参考動画
http://www.nicovideo.jp/watch/sm19784620

どんな機能があるの?

基本的にエフェクトがついただけです。
駒移動、定跡、成、王手、囲いとか。

使い方

1. dojoXXX.jarを、どこからか入手したdojo***.jar(***はバージョン番号)に置き換える
2. build.shでコンパイル
3. OsakaDojo.jar/TokyoDojo.jarをダブルクリックすると、パッチが当たった状態で起動します。

カスタマイズしたいなら

res/の画像と音を入れ替える
自分でコード書く

うまく動かないとき、使い方がわからないとき

`whoami`さんに聞きましょう。きっと不具合を修正してpullrequest送ってくれるはずです。

具体的に何やってるのか

中身は将棋倶楽部24専用アプレットビューアです。
ただ、dojo***.jarより優先してres/dojo.jarのクラスを読み込んでるだけです。
アプレットビューアです。

Javaでイケナイ事をしたいなら

・javapで見る(もしくはもっと見やすく表示してくれるツールで)
・別のjarを準備して、読み込むjarの順番を変える(今回はこれを使った)
・クラスファイルのいじっても良さそうなところをバイナリエディタでいじる
・継承元クラス名を変えて、親と子の間に別のクラスを挟む
・実行ファイルは書き換えずに、classloaderで読み込む段階でいじる
他に便利な技を知ってる人がいたら教えてください。

日記全然続いてないなー、ネタがある時だけでいいか

Rubyな日々

最近あまりにブログを書いてないのでRubyネタを2つほど
強い.zero?Lisp in Ruby

強い.zero?

https://gist.github.com/tompng/5031911
RubyにあるFixnum#zero?メソッドを強くしてみました。

出来る事

.zero?以外に.one? .two?とかが使えます。

  30.thirty?
  1000.thousand?
  -102.5.minusonehundredtwopointfive?

0 1 2...の代わりにzero one two...を使えます

ten.times do |i|
  print "----" if i.five?
  p i*threepointonefour
end

コード中の数値を全て英語表記に変えれちゃいます。誰得

仕組み

method_missingメソッドを定義しておくと、
存在しないメソッドを呼んだ時にゴニョゴニョできます

def method_missing name,*args
  if name.to_s.match /say_(.+)/
    print "hello #{$1}!\n"
  else
    super
  end
end
say_world
=> hello world!
say_goodbye
=> hello goodbye!

Lisp in Ruby

https://github.com/tompng/rubyhogelisp

(hoge (piyo (fuga 3),(foo 4,5)),(bar 5))

このLispっぽい表記がRubyだと構文エラーにならない。
じゃあいっそLisp動かしちゃおう、と思って。

結果
Lisp do (car (cons (cons 1,(list 2,3,4)),5)) end
=> (1, 2, 3, 4)
Lisp do
  (progn\
    (define (fact :n),
      (ifelse (eq :n,0),
        1,(mult :n,(fact (sub :n,1))))),
    (fact 5)
  )
end
=> 120
Rubyのいろんなgemの機能も一応使えます
require 'sinatra'
Lisp do
  (let :count,0,
    (call (get '/'),
      (progn\
        (set :count,(add :count,1)),
        (set :cntstr,(send :count,(to_s))),
        (call (system 'say',:cntstr)),
        (add (send (call params),(to_s)),:cntstr)
      )
    )
  )
end
=> sinatraが動く!
仕組み

これまたmethod_missing使って怪しい事してます。
まずdo-end内のLispっぽい式全体を実行して構文木を取り出す。
(実行するだけで構文木が構築されるようにmethod_missingをいじってます)
その先は力技でLispっぽいインタプリタを書いて動かす。以上!

コーディング風景をリアルタイムでWeb配信する素敵ツール『ScreenX』

結構前に作った、ターミナル画面をリアルタイムで配信するツールを公開しました。
GitHub:tompng/screenx
チャット付きです。




セットアップ

RackHubを使う場合はこちらも参考に
https://gist.github.com/3547668

ビルド
$ git clone https://github.com/tompng/screenx
$ cd screenx
$ ./build.sh
サーバー起動
$ cd classes
$ nohup java ScreenX &
配信開始
$ screen -x

これで配信するスクリーンが開きます。
配信URLは http://*****:8080/screenx.html
使うポートは設定で変更できます。




怪しくも素敵な隠れ機能 Web Login

httpフォルダを開くと、3つのhtmlファイルがあります。

  • screenx.html 配信用。チャット付き。
  • sxlogin.html 配信する時用のログイン。チャット見れる。ウインドウ幅固定
  • login.html 普通にログインして作業する用。ウインドウ幅可変。

Login・・・だと・・・


なんと、ソースコードを確認した上で自己責任で次の事をすると、Webからログインする機能が使えてしまいます!

1. classes/screenx.conf の設定を変え、サーバーを再起動
EnableLogin httpsでのログインを許可します。HttpsPort,KeyStoreFile,KeyStorePasswordも設定してください
EnableHttpLogin httpでもログインできるようにします。ログインパスワード以外はPlainTextです。危険度UP
EnableCometLogin Comet(WebSocketが使えない環境での代替策)でのログインを許可します。危険度さらにUP
LoginPassword チャレンジコード風の認証に使います。
DocumentRoot ここを空にする場合、httpフォルダ内のファイルをお使いのWebサーバ(Apache等)のディレクトリにコピーしてください
2. http/sxconfig.js を編集

ポートを変えたりhttpsを使う場合に書き換えてください。
また、WebSocketの使えない環境で動かす場合はforceCometをtrueにしてください。




アーキテクチャ


サーバーでUNIXのscreenコマンドをexecしてます。
そのscreenが吐く文字列(VT100エスケープシーケンス)をWebSocketで送っているだけ。
WebSocketが無いブラウザでは代わりにCometが使われます
そして、クライアント側ではjavascriptで書いた端末エミュレータがそれを解釈して表示しています。

※screenコマンドが吐くエスケープシーケンスは種類が少ないので、
 端末エミュレータはそれだけに絞って実装しました。楽だし確実。
 ちなみにscreenの代わりにbashをexecした場合、emacsの表示は結構崩れました。
 ちょっといじったら問題無く使えるようになりました。(2012 9/3)




まとめ。

名前の由来はscreenコマンドを-x引数で起動するからScreenXです。
NHKのProjectXみたいな語感が良いね。
では、リスクを承知の上で素敵ングなTerminal Broadcastingをお楽しみ下さい。