New Wave of Database Programming with Ruby 1.9 on Rails 2.1
-
Upload
akira-matsuda -
Category
Technology
-
view
4.751 -
download
3
description
Transcript of New Wave of Database Programming with Ruby 1.9 on Rails 2.1
New Wave of Database Programming with
Ruby 1.9 on Rails 2.1
松田 明 @ RubyKaigi 2008 LT
1
begin
2
自己紹介:名前 => 松田 明
:職業 => フリーランスの Ruby/Rails プログラマ
:仕事 => Rails
:趣味 => Rails
:仕事で書いているもの => 業務アプリが多い
3
業務アプリと言えば、DB。
4
人類のDBプログラミングの進化の歴史を振り返る
5
WEB + DBプログラミングにおけるパラダイムの遷移
古代 → 近代 → 現代
2回ぐらいの大きなパラダイムの転換
6
history
古代 → 近代 → 現代
7
古代言語e.g.
P H P8
:logic => 生SQLを文字列で 組み立てて発行
:view => 結果セットをぐるぐるループして HTML文字列を生成。
9
サンプルコード
10
省略。11
古代言語の衰退に伴って 絶滅。
12
history
古代 → 近代 → 現代
13
大きなパラダイムの転換
14
DBフレームワークの登場
• DBアクセス手段の共通化
• 設定の一元管理/コネクションのハンドリング
• RDBMS間の方言を吸収
• いわゆる O/Rマッパー
15
O/Rマッパーがもたらしたもの
RDBMSと
オブジェクト指向の
幸福な出会い
16
人間らしいDBプログラミング
17
サンプルコード
18
よくあるO/Rマッパーを使ったちょっとした業務アプリ
public interface ProductDAO { public Product find_by_id(int id); public Product find_by_name(String name); public List<Product> find_by_maker(Maker maker); public List<Product> find_by_category(Category category); public List<Product> find_by_maker_and_category(Maker maker, Category category);}
19
よくあるO/Rマッパーを使ったちょっとした業務アプリ
public interface ProductDAO { public Product find_by_id(int id); public Product find_by_name(String name); public List<Product> find_by_maker(Maker maker); public List<Product> find_by_category(Category category); public List<Product> find_by_maker_and_category(Maker maker, Category category);}
Remote InterfaceImpl
EntityBeanmock Home Interface
Serializable
CORBA
RMISetter
Getter
Compile
Deploy
DBUnit
19
よくあるO/Rマッパーを使ったちょっとした業務アプリ
public interface ProductDAO { public Product find_by_id(int id); public Product find_by_name(String name); public List<Product> find_by_maker(Maker maker); public List<Product> find_by_category(Category category); public List<Product> find_by_maker_and_category(Maker maker, Category category);}
Remote InterfaceImpl
EntityBeanDAO
Annotation
Dependency Injection
mock
DTODXO
Home Interface
Lazy LoadingOpen Session in View
Serializable
CORBA
RMISetter
Getter
Compile
Deploy
DBUnit
Bytecode Enhancement
19
よくあるO/Rマッパーを使ったちょっとした業務アプリ
public interface ProductDAO { public Product find_by_id(int id); public Product find_by_name(String name); public List<Product> find_by_maker(Maker maker); public List<Product> find_by_category(Category category); public List<Product> find_by_maker_and_category(Maker maker, Category category);}
Remote InterfaceImpl
EntityBeanDAO
Annotation
Dependency Injection
mock
DTODXO
Home Interface
Lazy LoadingOpen Session in View
Serializable
CORBA
RMISetter
Getter
Compile
Deploy
流れるようなインターフェース(笑)
DBUnit
Bytecode Enhancement
19
よくあるO/Rマッパーを使ったちょっとした業務アプリ
public interface ProductDAO { public Product find_by_id(int id); public Product find_by_name(String name); public List<Product> find_by_maker(Maker maker); public List<Product> find_by_category(Category category); public List<Product> find_by_maker_and_category(Maker maker, Category category);}
Remote InterfaceImpl
EntityBeanDAO
Annotation
Dependency Injection
mock
DTODXO
Home Interface
Lazy LoadingOpen Session in View
Serializable
CORBA
RMISetter
Getter
Compile
Deploy
流れるようなインターフェース(笑)
DBUnit
Bytecode Enhancement
XML
XML
XML
XMLXML
XML
XMLXML
XMLXML
XMLXML
XML
XMLXMLXML
XML
XML
XML
XML
19
カオス(笑)
20
人々がまだJavaを喋っていた時代の懐かしいお話
21
history
古代 → 近代 → 現代
22
次のパラダイム転換のきっかけ
23
我らが ActiveRecordの登場
24
•CoC をフル活用。「XML hell」と決別。
• modelクラスのアクセッサメソッドや単純な検索メソッドは DBのスキーマ定義から動的に生成。
•REST思想と見事な統合を遂げる。model == リソース。
• 柔軟なプラグイン機構
特徴
25
• 言語内DSLを生かした極めて可読性の高いバリデーション
• マイグレーションも Rubyスクリプトで。
• YAML形式でさくさくテストデータを記述。テストは専用DBで。
• フィルタのようなイメージで条件を重ね合わせていくwith_scope という機能=> 「Activerecord を詳しく」by 舞波氏 参照
機能
26
現代人のニーズとセンスに完璧にマッチした、変化に強い、エレガントなフレームワーク
27
DB層フレームワークの決定版
28
scaffoldで作る CRUDアプリ• 一覧
@models = Model.find(:all)
• 詳細@model = Model.find(params[:id])a
• 登録@model = Model.new(params[:model])@model.save
• 更新@model = Model.find(params[:id])@model.update_attributes(params[:model]
• 削除@model.destroy
29
コマンド一発で自動生成
30
誰でも書ける ActiveRecord
31
みんなの ActiveRecord
32
ご清聴ありが(ry
33
「15分で作るブログ」みたいなアプリ
ならね。
34
user_values = [] user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? if project user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] } else # members of the user's projects user_values += User.current.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] } end @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty? @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
if project # project specific filters @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } } @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } } unless @project.active_children.empty? @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } } end @project.all_custom_fields.select(&:is_filter?).each do |field| case field.field_format when "text" options = { :type => :text, :order => 20 } when "list" options = { :type => :list_optional, :values => field.possible_values, :order => 20} when "date" options = { :type => :date, :order => 20 } when "bool" options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 } else options = { :type => :string, :order => 20 } end @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name }) end # remove category filter if no category defined @available_filters.delete "category_id" if @available_filters["category_id"][:values].empty? end
でも実際の業務アプリになると…
35
やっぱりカオス・・・
36
実は DBアクセスは Railsの弱点
• 結局、SQL文字列を組み立てる処理を常に意識
•CoCではどうにもならない泥臭いところ
• 複雑になればなるほど肥大化
37
• 「SQLの文字列を組み立てる」
という昔ながらのダサい処理
•Ruby的オブジェクト指向
というハイセンスで華やかな世界観
この問題の根本原因
38
• 「SQLの文字列を組み立てる」
という昔ながらのダサい処理
•Ruby的オブジェクト指向
というハイセンスで華やかな世界観
この問題の根本原因
大きなギャップ
38
RDBMS操作 == SQL文を組み立てて発行することという先入観から脱却できていないから。
why?
39
RDBMS操作 != SQL文を組み立てて発行すること
actually,
40
DB操作の本質は集合演算。
41
そこで、
42
history
古代 → 近代 → 現代 → 2008年~
43
named_scope
44
named_scope とは?
• もともとは 昨秋ごろに彗星のように登場した has_finderという名前の野良 Railsプラグイン
• 今年の3月ごろ、Rails 2.1系 から正式に
Rails本体に取り込まれた
45
named_scopeの機能• 問い合わせを固まりではなく断片でとらえる
「scope」
• 「scope」に名前を付けて、DSL的にあらかじめ定義
• 複雑な条件クエリの組み立ては、定義済みの「scope」を組み合わせて動的に作ることで表現できる
46
example• modelnamed_scope :of_the_month, :conditions => {:reported_at => Date.today.beginning_of_month..Date.today.end_of_month}
• 呼び出し側@reports = Report.of_the_month
• 発行されるSQLSELECT * FROM "reports" WHERE ("reports"."reported_at" BETWEEN '2008-06-01' AND '2008-06-30')
47
『DAO』と何が違うの?
48
example(2)• modelnamed_scope :of_the_month, :conditions => {:reported_at => Date.today.beginning_of_month..Date.today.end_of_month} named_scope :written_by, Proc.new {|u| {:conditions => {:user_id => u}}}
• 呼び出し側@reports = Report.of_the_month.written_by(@current_user)
• 発行されるSQLSELECT * FROM "reports" WHERE (("reports"."user_id" = 1) AND ("reports"."reported_at" BETWEEN '2008-06-01' AND '2008-06-30'))
49
カスケードして呼び出してもSQLは1回
50
可読性高すぎ。
51
イメージ図
52
イメージ図of_the_month
52
イメージ図of_the_month written_by
52
イメージ図of_the_month written_by
これを select。
52
まさに集合演算。
53
集合がDSLで!
54
named_scopeがもたらしたものRDBMS
と
オブジェクト指向と
集合演算の
幸福な出会い55
これを実現している技術
• scopeの実体は Procのインスタンス
• チェインされた Procたちをまとめて評価 -> 実行
• Rubyの豊かな表現力を生かした、Rubyならではの実装
56
単体テストも楽々(RSpecの場合)• scopeそのものの条件テスト
describe 'written_by' do it 'Userのインスタンスを受け取って user_id で検索を実行すること' do Report.written_by(@user).proxy_options.should == {:conditions => {:user_id => 1}} end
• ロジック側から然るべきscopeが正しく呼ばれていることのテスト it ‘named_scope :written_by を「ログインユーザ」を引数にして呼び出していること’ do @written_by_scope.should_receive(:call).with(Report, users(:ito)).and_return(Report) do_get end
57
Rails 2.2に向けてますます機能拡張中!
• !付きメソッド
• 検索以外でも使えるように
• 詳細は
Rails勉強会@東京ブースにて配布中のペーパー参照。
58
Life Changing!
59
まとめ
60
RubyプログラマはRubyでものを考える
61
named_scopeは、泥臭い DB操作をキレイな Ruby語に翻訳するデバイス
62
でも、キレイな Ruby語とか言うわりには、
• デフォルト引数を与えたい場合、こう書きたいけど書けないnamed_scope :recent, Proc.new {|time || 2.weeks.ago| {:conditions => ['reported_at > ?', time]} }
• ので、こんなふうにでも書いて逃げるしか。named_scope :recent, Proc.new {|*args| {:conditions => ['reported_at > ?', (args.first || 2.weeks.ago)]} }
63
これはちょっとイケてないかも?
64
そこで、Ruby 1.9。
65
remember this?66
Ruby 1.9なら
•新しい lambda記法 -> ができた
•procにデフォルト引数がとれるようになった
67
Ruby 1.9 なら• さっきのこれが、
named_scope :recent, Proc.new {|*args| {:conditions => ['reported_at > ?', (args.first || 2.weeks.ago)]} }
• こうなる。named_scope :recent, ->(time = 2.weeks.ago) { {:conditions => ["reported_at > ?", time]} }
68
Ruby 1.9 is for you, Railers!
69
end
70