DataMapper 0.10.2 Reading (その 4: dm-core/core_ext/)
このディレクトリに配置されたコードは,ruby の標準クラス (モジュール) を拡張するためのもの.具体的には以下の 3 つが拡張されている.
- Enumerable
- Kernel
- Symbol
それぞれは小さなコードだが,影響範囲は広い.
Enumerable
empty?/one?/first/size メソッドを定義している.標準メソッドについては こちら を参照されたし.
Enumerable の規約に従い,全て each により定義されている.これらの内,ここでは one? メソッドを取り上げようと思う.one? メソッドは文字通り「1 つだけ?」かどうかを判定するわけだが,この判定基準にブロックを用いる.
module Enumerable def one? return one? { |entry| entry } unless block_given? matches = 0 each do |entry| matches += 1 if yield(entry) return false if matches > 1 end matches == 1 end
与えられたブロックで各要素を評価し,true を返した要素の個数をカウントしていく.カウントが 1 であれば true を返すし,そうでなければ false を返す.
Kernel
Kernel では,DataMapper モジュールの repository メソッドを呼び出すためのメソッドが定義されている.
module Kernel private def repository(*args, &block) DataMapper.repository(*args, &block) end end
ここで定義されているということは,self.repository の形であれば (self 以外に対して .repository としなければ) どこでも呼び出せる.
簡単な例を示すと,以下のような感じ.
module Kernel private def hoge puts "hogeっていうな" end end hoge # => hogeっていうな class A; end a = A.new a.hoge # => NoMethodError class A def call_hoge hoge end end a.call_hoge # => hogeっていうな
Symbol
コードは短いが,検索に広く影響する条件用の Symbol を定義している.
class Symbol (DataMapper::Query::Conditions::Comparison.slugs | [ :not, :asc, :desc ]).each do |sym| class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{sym} #{"warn \"explicit use of '#{sym}' operator is deprecated (#{caller[0]})\"" if sym == :eql || sym == :in} DataMapper::Query::Operator.new(self, #{sym.inspect}) end RUBY end end
例えば,
class MyModel include DataMapper::Resource property :id, Serial property :name, String end
というモデルを定義したとき,
m = MyModel.all(:name.like => 'Krd%')
という呼び出しの ".like" にあたる部分を定義していることになる.
この定義により,".like" と呼び出すことで,条件演算を表す Operator インスタンスが得られる.
なお,"Comparison.slugs" については後ほど.ここでは「条件記述用に定義された Symbol を配列で返す」ぐらいの認識でよいかと.
Pocket WiFi
Pocket WiFi なるものを購入してみた.
思ってたよりも小さい.スゲーつるつるしてる.
プランは「新にねん」で「スーパーライトデータ」にした.これだと本体価格は 15,580 円になる.「にねん M」だと 5,980 円になるが,差額の 9,600 円は 24ヶ月で分割され,月々の基本料金に上乗せされる.まぁ,つまるところ,2 年間使い続ければ差は無くなる.2 年より多く使うのであれば,多少は「新にねん」の方がお得になる.
これからいろいろ持ち運んでみる予定.
追記:2010/02/02
http://speedtest.goo.ne.jp/ で計測してみたら 0.51 Mbps だった...あれ?
DataMapper 0.10.2 Reading (その 3: dm-core/)
今回からは dm-core/ 以下のコードを見ていきます.
配置確認
うーん,かなり変更されている...
dm-core/ adapters/ abstract_adapter.rb data_objects_adapter.rb in_memory_adapter.rb mysql_adapter.rb oracle_adapter.rb postgres_adapter.rb sqlite3_adapter.rb sqlserver_adapter.rb yaml_adapter.rb associations/ many_to_many.rb many_to_one.rb one_to_many.rb one_to_one.rb relationship.rb core_ext/ enumerable.rb kernel.rb symbol.rb model/ descendant_set.rb hook.rb is.rb property.rb relationship.rb scope.rb query/ conditions/ comparison.rb operation.rb direction.rb operator.rb path.rb sort.rb spec/ adapter_shared_spec.rb data_objects_adapter_shared_spec.rb support/ chainable.rb deprecate.rb equalizer.rb logger.rb naming_conventions.rb types/ boolean.rb discriminator.rb object.rb paranoid_boolean.rb paranoid_datetime.rb serial.rb text.rb adapters.rb collection.rb identity_map.rb migrations.rb model.rb property.rb property_set.rb query.rb repository.rb resource.rb transaction.rb type.rb version.rb
DataMapper 0.10.2 Reading (その 2: dm-core.rb)
やはり,最初はここから.
0.9.x に比べて,格段にモジュール化が進み,非常にシンプルになっている.
最初の方
ひたすら require して,必要なコードを読み込んでいる.所々 TODO があるので,また変わるんだろうなぁ...
DataMapper.root
module DataMapper def self.root @root ||= Pathname(__FILE__).dirname.parent.expand_path.freeze end
dm-core パッケージのディレクトリパスを設定する.確かに freeze しておくのは手だなぁ.
DataMapper.setup
なんかシンプルになってるし...
module DataMapper def self.setup(*args) adapter = args.first unless adapter.kind_of?(Adapters::AbstractAdapter) adapter = Adapters.new(*args) end Repository.adapters[adapter.name] = adapter end
args は可変長引数なので Array インスタンスである.setup メソッドの呼び出し方が
DataMapper.setup(:default, 'mysql://localhost/dm_test') DataMapper.setup(:default, { :adapter => 'postgres', :database => 'localhost/dm_test', ... })
であることから,ローカル変数 adapter はコンテキスト (Repository) を識別するための Symbol が入る.adapter が AbstractAdapter 派生のインスタンスでなければ (←通常は Symbol なのでこのパターン),引数 args から新しい Adapter を作成する (MysqlAdapter 等).Adapters.new メソッド内部では,以前紹介した DataMapper.setup メソッドのように uri/Hash から Adapter インスタンスを作成しているが,このコードがまたきれいにまとめられていて,とても読みやすい (Adapters についてはまた今度).
最後は name を key として Adapter インスタンスが Repository.adapters に蓄えられる.adapter.name は,setup メソッドの第 1 引数に渡した Symbol である.
ところで,adapter.kind_of? で AbstractAdapter かどうかを見ているということは,setup メソッドの引数に Adapter インスタンスが渡せるということか?
DataMapper.repository
このメソッドはあまり変わっていない.ただ,コードが整理され,すっきりしてる.
module DataMapper def self.repository(name = nil) context = Repository.context current_repository = if name assert_kind_of 'name', name, Symbol context.detect { |repository| repository.name == name } else name = Repository.default_name context.last end current_repository ||= Repository.new(name) if block_given? current_repository.scope { |*block_args| yield(*block_args) } else current_repository end end
基本的な流れは以下の通り.
- context (コンテキストスタック) を取得
- 引数に name が指定されていれば,context から名前の一致する Repository インスタンスを取得
- 引数に name が指定されていなければ,:default を name とし,context から末尾の Repository インスタンスを取得
- context から Repository インスタンスがとれなかった (nil だった) 場合は,新しい Repository インスタンスを生成
- block が渡されていれば,block 渡して Repository#scope を呼び出す
- block が渡されていなければ,そのまま Repository インスタンスを返す
block 有りの呼び出しは,以下のようにコンテキストを明示して呼び出す際に用いる.
p = DataMapper.repository(:hoge) { Person.first }
block 無しの呼び出しは,主にモデルメソッドの内部におけるコンテキスト取得に用いられる.
Repository.context メソッドは,スレッドローカルから Repository インスタンスの配列 (コンテキストスタック) を取り出す.
module DataMapper class Repository def self.context Thread.current[:dm_repository_contexts] ||= [] end
Repository#scope メソッドは,Repository.context の返すコンテキストスタックに self を積み,渡された block を実行する.
module DataMapper class Repository def scope context = Repository.context context << self begin yield self ensure context.pop end end
これは,yield で実行されるブロックの内部において,モデルメソッドの呼び出しで context 取得が行われた際に,正しい context を返すための処置である.