Arrayの前後を取得したいとき
こういうのあるじゃないですか。jQueryの話じゃないですよ。
食べログの写真一覧みたいなのをイメージしてください。画像ごとにURLがあって、真ん中が今の画像、一個右の画像を押すと画像スライダーみたいな挙動なんだけどページが変わる。(理想的には画像のあたりだけが)
これどう実装しますか?
少し勘がいい人ならKaminariを使えば実装できんじゃない?って思うかもしれませんが、たぶんできない気がしますね。っていうのは、これは3枚目の画像がimages?page=3&per=1とかのURLじゃ困るわけで。一時的な挙動としてはいいかもしれませんが、画像へのリンクとかつくろうと思うとめんどくさそうだしページの内容的にURLが頻繁に変わるのはおかしいし。
で、いろいろ考えた上、効率的にベストじゃないと思うんですがいったんソートされたidの配列を受け取って、そこから前後のidを見つければいいかなと思ったんですね。ベストじゃないと思うのは、なんかうまくやればidの配列を作らなくてもいい気がするんですがそれは思いつかなかった。誰か教えてください。
要件をまとめると
array = [1,2,3,4,5,6,7,8,9,10] # 取得する件数が5件、中央が5なら [3,4,5,6,7] # 取得する件数が5件、中央が9なら [6,7,8,9,10] # 取得する件数が4件、中央が3なら [1,2,3,4]
これができてれば次のようにできますね。
ids = @restaurant.photos.published.order(:published_at).pluck(:id) @photo = @restaurant.photos.published.find(params[:photo_id]) @list_photos = Photo.where_with_order(id: find_prev_and_next_ids(ids, @photo.id))
ちなみにwhere_with_orderみたいなメソッドはRailsにはないですが現実問題けっこう使うんで自分で用意してください。
問題は、Arrayの中心部と幅を指定すれば要素が返ってくるのって、メソッドありそうでないんですよね。
しょうがないので作りました。
class ArrayPrevNext attr_accessor :center def initialize(array, center) @array = array @center = center end def prev_and_next(n) return [@center] if n == 1 if n.odd? # n = 2a + 1 として表現 a = (n - 1) / 2 if center_index < a x = a*2 @array[0..x] elsif last_index - a < center_index @array[(last_index - 2*a)..last_index] else @array[(center_index - a)..(center_index + a)] end else # n = 2a a = n / 2 if center_index <= a @array[0..(n - 1)] elsif last_index - a < center_index @array[(last_index - (2*a - 1))..last_index] else @array[(center_index - a)..(center_index + a - 1)] end end end def prev(n) first = [center_index - n, 0].max last = center_index - 1 return nil unless first < last @array[first..last] end def next(n) first = center_index + 1 last = center_index + n @array[first..last] end def center_index @array.index(@center) end def last_index @array.length - 1 end end
使い方は
@photo = @restaurant.photos.published.find(params[:photo_id]) ids = @restaurant.photos.published.order(:published_at).pluck(:id) pn = ArrayPrevNext.new(ids, @photo.id) @list = Photo.where_with_order(ids: obj.prev_and_next(9))
これで前後4件と自身を含んだ9件が返ってくる。
これは常に中心から前後が等しくなるように、偶数の場合は右が1個小さくなるように取ってくるけどその辺が設定で変えられるぐらいがいいかも。