要素が足りない場合に効率的に追加するEnumerable
Ruby on Railsでコード書いてて、以下の様なケースがありました。
Hogeからnameを取得し、limit件数に満たなければ、さらにFooからtitleを取得して追加する。
limit = 10 elems = Hoge.where(:status => 1) .first(limit) .pluck(:name) if elems.count < limit elems += Foo.where(:flag => true) .first(limit - elems.count) .pluck(:title) end
※ Hoge と Foo は ActiveRecord::Base を継承しています。
場合によってはSQLのUnionを使うようにして解決できますが、わりと面倒(ソート順のカラムを合わせたりなどなど)だったり。
いろいろとコードがめんどくさい感じになっていて不便なので、クラスを作りました。
作ったクラス
class MultiEnumerable include Enumerable def initialize(*args) if args.last.is_a? Hash @collections = args[0...-1] @options = args.last else @collections = args @options = {} end end def each &block cnt = 0 fst = @options[:first] @collections.each do |c| f = fst ? fst - cnt : nil break if not f.nil? and f <= 0 if c.is_a? Proc if c.arity == 0 c = c.call else c = c.call(f) end end c = c.first(f) if f c.each do |i| block.call(i) end cnt += c.count end end end
使い方はこんな感じです。
mul = MultiEnumerable.new( -> { [1,2,3] }, -> (c) { c ? [4,5,6][0...c] : [1,2,3] }, # この場合、ここは実行されない {:first => 2} ) mul.each do |item|↲ # item => 1, 2 の2回、ここが実行されます end mul = MultiEnumerable.new( -> { [1,2,3] }, -> (c) { c ? [4,5,6][0...c] : [1,2,3] }, # この場合、ここは c => 1 で実行される {:first => 4} ) mul.each do |item|↲ # item => 1, 2, 3, 4 の4回、ここが実行されます end
最初のコードを置き換えるとこうなります
MultiEnumerable.new( -> (c) { Hoge.where(:status => 1).first(c).pluck(:name) }, -> (c) { Foo.where(:flag => true).first(c).pluck(:title) }, {:first => 10} )
簡潔になりましたね。
まとめ
車輪の再発明だと悲しい。