diff options
Diffstat (limited to 'core/retriever.rb')
-rw-r--r-- | core/retriever.rb | 411 |
1 files changed, 1 insertions, 410 deletions
diff --git a/core/retriever.rb b/core/retriever.rb index 1d9224b9..538a35ef 100644 --- a/core/retriever.rb +++ b/core/retriever.rb @@ -7,413 +7,4 @@ # ハッシュテーブルを保存し、後から検索できるようにする miquire :lib, 'weakstorage' - -module Retriever - - # モデルクラス。 - # と同時に、このクラスのインスタンスはレコードを表す - class Model - include Comparable - - def self.inherited(subclass) - subclass.instance_eval do - @storage = WeakStorage.new(Integer, subclass) end end - - def initialize(args) - type_strict args => Hash - @value = args.dup - validate - self.class.store_datum(self) - end - - # 新しいオブジェクトを生成します - # 既にそのカラムのインスタンスが存在すればそちらを返します - # また、引数のハッシュ値はmergeされます。 - def self.generate(args, count=-1) - return args if args.is_a?(self) - return self.findbyid(args, count) if not(args.is_a? Hash) - result = self.findbyid(args[:id], count) - return result.merge(args) if result - self.new(args) - end - - def self.rewind(args) - type_strict args => Hash - result_strict(:merge){ new_ifnecessary(args) }.merge(args) - end - - # まだそのレコードのインスタンスがない場合、それを生成して返します。 - def self.new_ifnecessary(hash) - type_strict hash => tcor(self, Hash) - result_strict(self) do - if hash.is_a?(self) - hash - elsif hash[:id] and hash[:id] != 0 - atomic{ - @storage[hash[:id].to_i] or self.new(hash) } - else - raise ArgumentError.new("incorrect type #{hash.class} #{hash.inspect}") end end end - - # - # インスタンスメソッド - # - - # データをマージする。 - # selfにあってotherにもあるカラムはotherの内容で上書きされる。 - # 上書き後、データはDataSourceに保存される - def merge(other) - @value.update(other.to_hash) - validate - self.class.store_datum(self) - end - - def id - @value[:id] - end - - def eql?(other) - other.is_a?(self.class) and other.id == self.id end - - def hash - self.id.to_i end - - def <=>(other) - if other.is_a?(Retriever) - id - other.id - elsif other.respond_to?(:[]) and other[:id] - id - other[:id] - else - id - other end end - - def ==(other) - if other.is_a?(Retriever) - id == other.id - elsif other.respond_to?(:[]) and other[:id] - id == other[:id] - else - id == other end end - - def to_hash - @value.dup - end - - # カラムの生の内容を返す - def fetch(key) - @value[key.to_sym] end - alias [] fetch - - # 速い順にcount個のRetrieverだけに問い合わせて返す - def get(key, count=1) - result = @value[key.to_sym] - column = self.class.keys.assoc(key.to_sym) - if column and result - type = column[1] - if type.is_a? Symbol - Retriever::cast_func(type).call(result) - elsif not result.is_a?(Model) - result = type.findbyid(result, count) - if result - return @value[key.to_sym] = result end end end - result end - - - # カラムに別の値を格納する。 - # 格納後、データはDataSourceに保存される - def []=(key, value) - @value[key.to_sym] = value - self.class.store_datum(self) - value end - - # カラムと型が違うものがある場合、例外を発生させる。 - def validate - raise RuntimeError, "argument is #{@value}, not Hash" if not @value.is_a?(Hash) - self.class.keys.each{ |column| - key, type, required = *column - begin - Model.cast(self.fetch(key), type, required) - rescue InvalidTypeError=>e - estr = e.to_s + "\nin #{self.fetch(key).inspect} of #{key}" - warn estr - warn @value.inspect - raise InvalidTypeError, estr end } end - - # キーとして定義されていない値を全て除外した配列を生成して返す。 - # また、Modelを子に含んでいる場合、それを外部キーに変換する。 - def filtering - datum = self.to_hash - result = Hash.new - self.class.keys.each{ |column| - key, type = *column - begin - result[key] = Model.cast(datum[key], type) - rescue InvalidTypeError=>e - raise InvalidTypeError, e.to_s + "\nin #{datum.inspect} of #{key}" end } - result end - - # - # クラスメソッド - # - - # モデルのキーを定義します。 - # これを継承した実際のモデルから呼び出されることを想定しています - def self.keys=(keys) - @keys = keys end - - def self.keys - @keys end - - # srcが正常にModel化できるかどうかを返します。 - def self.valid?(src) - return src.is_a?(self) if not src.is_a?(Hash) - not self.get_error(src) end - - # srcがModel化できない理由を返します。 - def self.get_error(src) - self.keys.each{ |column| - key, type, required = *column - begin - Model.cast(src[key], type, required) - rescue InvalidTypeError=>e - return e.to_s + "\nin key '#{key}' value '#{src[key]}'" end } - false end - - # DataSourceのチェーンに、 _retriever_ を登録します - def self.add_data_retriever(retriever) - retriever.keys = self.keys - retrievers_add(retriever) - retriever end - - # 特定のIDを持つオブジェクトを各データソースに問い合わせて返します。 - # 何れのデータソースもそれを見つけられなかった場合、nilを返します。 - def self.findbyid(id, count=-1) - return findbyid_ary(id, count) if id.is_a? Array - raise if id.is_a? Model - result = nil - catch(:found){ - rs = self.retrievers - count = rs.length + count + 1 if(count <= -1) - rs = rs.slice(0, [count, 1].max) - rs.each{ |retriever| - detection = retriever.findbyid_timer(id) - if detection - result = detection - throw :found end } } - self.retrievers_reorder - if result.is_a? Retriever::Model - result - elsif result.is_a? Hash - self.new_ifnecessary(result) end - rescue => e - error e - abort end - - def self.findbyid_ary(ids, count=-1) - result = [] - remain = ids.clone - ids.freeze - catch(:found){ - rs = self.retrievers - count = rs.length + count + 1 if count <= -1 - rs.slice(0, [count, 1].max).each{ |retriever| - detection = retriever.findbyid_timer(remain) - if detection - detection = detection.compact.map(&method(:new_ifnecessary)) - result.concat(detection) - remain -= detection.map{ |x| x[:id].to_i } - throw :found if remain.empty? end } } - self.retrievers_reorder - container_class.new(result.sort_by{ |user| ids.index(user[:id].to_i) || 1.0/0 }) end - - def self.selectby(key, value, count=-1) - key = key.to_sym - result = [] - rs = self.retrievers - count = rs.length + count + 1 if(count <= -1) - rs = rs.slice(0, [count, 1].max) - rs.each{ |retriever| - detection = retriever.selectby_timer(key, value) - result += detection if detection } - self.retrievers_reorder - result.uniq.map{ |node| - if node.is_a? Hash - self.new_ifnecessary(node) - elsif node.is_a? Model - node - else - self.findbyid(node) end } end - - # - # プライベートクラスメソッド - # - - # データを一件保存します。 - # 保存は、全てのデータソースに対して行われます - def self.store_datum(datum) - atomic{ - @storage[datum[:id].to_i] = result_strict(self){ datum } } - return datum if datum[:system] - converted = datum.filtering - self.retrievers.each{ |retriever| - retriever.store_datum(converted) } - datum - end - - # 値を、そのカラムの型にキャストします。 - # キャスト出来ない場合はInvalidTypeError例外を投げます - def self.cast(value, type, required=false) - if value.nil? - raise InvalidTypeError, 'it is required value'+[value, type, required].inspect if required - nil - elsif type.is_a?(Symbol) - begin - result = (value and Retriever::cast_func(type).call(value)) - if required and not result - raise InvalidTypeError, 'it is required value, but returned nil from cast function' end - result - rescue InvalidTypeError - raise InvalidTypeError, "#{value.inspect} is not #{type}" end - elsif type.is_a?(Array) - if value.respond_to?(:map) - value.map{|v| cast(v, type.first, required)} - elsif not value - nil - else - raise InvalidTypeError, 'invalid type' end - elsif value.is_a?(type) - raise InvalidTypeError, 'invalid type' if required and not value.id - value.id - elsif self.cast(value, type.keys.assoc(:id)[1], true) - value end end - - # メモリキャッシュオブジェクトを返す - def self.memory_class - Memory end - - def self.container_class - Array end - - # DataSourceの配列を返します。 - def self.retrievers - atomic{ - @retrievers = [memory_class.new(@storage)] if not defined? @retrievers } - @retrievers - end - - def self.retrievers_add(retriever) - self.retrievers << retriever end - - #DataSourceの配列を、最後の取得が早かった順番に並び替えます - def self.retrievers_reorder - atomic{ - @retrievers = self.retrievers.sort_by{ |r| r.time } } - end - - end - - # データの保存/復元を実際に担当するデータソース。 - # データソースをモデルにModel::add_data_retrieverにて幾つでも参加させることが出来る。 - module DataSource - attr_accessor :keys - - # idをもつデータを返す。 - # もし返せない場合は、nilを返す - def findbyid(id) - nil - end - - # 取得できたらそのRetrieverのインスタンスをキーにして実行されるDeferredを返す - def idof(id) - Thread.new{ findbyid(id) } end - alias [] idof - - # keyがvalueのオブジェクトを配列で返す。 - # マッチしない場合は空の配列を返す。Arrayオブジェクト以外は返してはならない。 - def selectby(key, value) - [] - end - - # データの保存 - # データ一件保存する。保存に成功したか否かを返す。 - def store_datum(datum) - false - end - - def findbyid_timer(id) - st = Process.times.utime - result = findbyid(id) - @time = Process.times.utime - st if result - result - end - - def selectby_timer(key, value) - st = Process.times.utime - result = selectby(key, value) - @time = Process.times.utime - st if not result.empty? - result - end - - def time - defined?(@time) ? @time : 0.0 - end - - def inspect - self.class.to_s - end - end - - @@cast = { - :int => lambda{ |v| begin v.to_i; rescue NoMethodError then raise InvalidTypeError end }, - :bool => lambda{ |v| !!(v and not v == 'false') }, - :string => lambda{ |v| begin v.to_s; rescue NoMethodError then raise InvalidTypeError end }, - :time => lambda{ |v| - if not v then - nil - elsif v.is_a? String then - Time.parse(v) - else - Time.at(v) - end - } - } - - def self.cast_func(type) - @@cast[type] - end - - class RetrieverError < StandardError - end - - class InvalidTypeError < RetrieverError - end - - class Model::Memory - include Retriever::DataSource - - def initialize(storage) - @storage = storage end - - # def children - # @children ||= Hash.new{ |h, k| h[k] = Set.new } end - - def findbyid(id) - if id.is_a? Array or id.is_a? Set - id.map{ |i| @storage[i.to_i] } - else - @storage[id.to_i] end - end - - # def selectby(key, value) - # if key == :replyto - # children[value.to_i].to_a - # else - # [] end end - - # データの保存 - # def store_datum(datum) - # @storage[datum[:id]] = datum - # @children[datum[:replyto].to_i].push(datum[:id].to_i) if datum[:replyto] - # true - # end - end - -end +miquire :lib, 'retriever' |