※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

このアイデアはIRC、暗い路地、汗まみれのダンスホール、法廷で語りつくされたのを知っています。

こんな風に: SQLを書く代わりにRubyで書いてください。カーテンの後ろでSQLを生成してください。

例えばこれ:

User.detect { |u| u.name == 'Jericho' && u.age == 22 }

またはこれ:

User.select { |u| [1, 2, 3, 4].include? u.id }

さもなくばこれ:

User.select { |u| u.name =~ 'rick' }.sort_by(&:age)

本当にすごいでしょう?

誤解しないでください。私たちは既にRubyでクエリを書くことが出来ます。 ez-where や Squirrel その他もろもろがありますが。しかしこれらは Ruby ぽい DSLs クエリ で Ruby ではありません。私は単に大好きな Enumerable を使いたいだけです。 古風だと呼んでください。

Ambitious の仕事

Erlang の Mnesia データベースは私が欲しいものに似ています。: 素の Erlang で書かれたあなたのクエリは構文木によって Mnesiaクエリ を翻訳されます。 よく出来た仕組みですが、聞いてください。: Ruby にも構文木があり、ParseTree のおかげで簡単に手に入れられます。

それでは、Ambition の始まりです。

$ sudo gem install ambition -y

コンソールを使って ActiveRecord のロギングをハックして楽しんでください:

$ script/console
>> ActiveRecord::Base.logger = Logger.new(STDOUT)
=> #<Logger:0x2814134 ...>
>> require 'ambition'
=> []

バックグラウンドで実行された SQL によって ActiveRecord モデルが使われるいくつかの例:

User.first
"SELECT * FROM users LIMIT 1" 
User.select { |m| m.name != 'macgyver' }
"SELECT * FROM users WHERE users.`name` <> 'macgyver'" 
User.select { |u| u.email =~ /chris/ }.first
"SELECT * FROM users WHERE (users.`email` REGEXP 'chris') LIMIT 1" 
User.select { |u| u.karma > 20 }.sort_by(&:karma).first(5)
"SELECT * FROM users WHERE (users.`karma` > 20) 
ORDER BY users.karma LIMIT 5" 
User.select { |u| u.email =~ 'ch%' }.size
"SELECT count(*) AS count_all FROM users WHERE (users.`email` LIKE 'ch%')" 
User.sort_by { |u| [ u.email, -u.created_at ] }
"SELECT * FROM users ORDER BY users.email,users.created_at DESC" 
User.detect { |u| u.email =~ 'chris%' && u.profile.blog == 'Err' }
"SELECT users.`id` AS t0_r0 ... FROM users 
LEFT OUTER JOIN profiles ON profiles.user_id = users.id 
WHERE ((users.`email` LIKE 'chris%' AND profiles.blog = 'Err')) 
LIMIT 1" 

など。README にたくさんの例をリストで確認できます。

Kicking Around Data

A good thing to keep in mind is that queries aren’t actually run until the data they represent is requested. Usually this is done with what I call a kicker method. You can call them that, too.

Kicker methods are guys like detect, each, each_with_index, map, and first (with no argument). Methods like select, sort_by, and first (with an argument) are not kicker methods and return a Query object without running any SQL.

As such, you can garner some information from a Query object:

>> user = User.select { |u| u.name == 'Dio' }
=> (Query object: call #to_sql or #to_hash to inspect...)
>> user.to_sql
=> "SELECT * FROM users WHERE users.`name` = 'Dio'" 
>> user.to_hash
=> {:conditions=>"users.`name` = 'Dio'"}
>> user.first  # => SQL is run
=> #<User:0x36896e4 ...>

Note the to_hash—Ambition doesn’t actually run any SQL, it just hands this hash to ActiveRecord::Base#find.

Anyway, kickers have useful implications for Rails apps. Take this controller:

class BandsController < ApplicationController
  def index
    @bands = Band.sort_by(&:name)
  end
end

Since no kicker method is called, @bands is just a Query object—no SQL run. The SQL is only run once we call each in our view:

Rocktastic Bands

    <% @bands.each do |band| %>
  • <%= band %>
  • <% end %>
    
a) キャッシュの断片 と b) クエリの再利用をしたいと言うでしょう。 Standard stuff.

Two birds, one stone:

Rocktastic Bands

<% cache do %>
    <% @bands.each do |band| %>
  • <%= band %>
  •   <% end %>
    
<% end %>

Rails がキャッシュを見つけたなら、SQLは何も実行されません。 滑らかにしてください。(キャッシュしないでください?)

The Catch

This is pretty new, so watch the sharp edges. While we aren’t good at executing arbitrary Ruby inside the block, we can handle variables.

Practically speaking, instead of writing:

User.select { |u| u.created_at = 2.days.ago }.first

Write:

date = 2.days.ago
User.select { |u| u.created_at = date }.first

インスタンス変数と単純なメソッド呼び出しはきちんと動いています。今後のリリースで完全に Ruby をサポートするつもりです。(誰かがパッチを提供してくれると早く実現するかもしれません)

Big Dreams

理想的には、データベースにおける Rack のようなものになったと思います。 Query DataMapper や Sequel や ActiveRecord は Ruby の素の jane Enumerable API を使っています。 Hey, maybe we can thrown an OODB or two into the mix?

容疑者としていつも名前が挙がること:

Report feature requests, shortcomings, & bugs at Lighthouse. SVN’s at svn://errtheblog.com/svn/projects/ambition Code at http://projects.require.errtheblog.com/browser/ambition RDoc at Rock

ここ数日私は Cheat で実行していて、動くようになっています。 @sheets = Sheet.sort_by(&:title) のような恐怖がないかソースをチェックしてください。

ambition がちょっとわかってきたら次のスペースを見てください。

Update: Oh yeah, KirinDave came up with the name Ambition. Thanks.

Update 2: Okay, added some stuff tonight: You can now do cross-table sort_bys:

User.sort_by { |u| [ u.profile.name, u.ideas.karma ] }

これは以前動いていませんでした(笑)。 私はバックエンドで a COUNT する any? や all? や empty? といったクエリサポートを追加しました。狂った状態を与えてください。3つともすべて kickers です。(?)

entries と to_a kickers も加えられました。 User.to_a は User.find(:all)と同じです。 万能 kicker としての仕事、 too—User.select { |u| u.name == ‘kicker’ }.to_a など。

最後に, slice は [] の別名です。 PJ へ感謝してください。

新しい gem はりリース済みです。私は破壊的で建設的ないくつかの空の仕様を加えました。危険な考えであることはわかっています。またお会いしましょう。