У меня есть база фильмов с атрибутами. Я хотел бы вернуть запрошенную партию этих фильмов в случайном порядке в шаблон с нумерацией страниц. Я использую will_paginate. Я попробовал следующее:
## MoviesController
movies = Movie.get_movies(query_string) # a method in Movie model that takes in
# a query_string and fetches movies
# with user-set params
@movies = movies.order('random()').page(params[:page]).per_page(16)
Это работает хорошо, за исключением того, что фильмы повторяются от страницы к странице. Решения этой проблемы опубликованы здесьи здесь. Эти ссылки объясняют, что из-за того, что начальное число random()сбрасывается со страницы на страницу, согласованности нет, и OFFSETстановится бесполезным. Они предлагают отличные решения для пользователей MySQL, поскольку их функция rand(n)принимает начальное значение n. Однако Postgres этого не делает. Вы должны объявить setseed(n)в SELECTперед выдачей random()в ORDER.
Итак, я попробовал установить начальное число с помощью postgres:
@movies = movies.select('setseed(.5)').order('random()').page(params[:page]).per_page(16)
Любопытно, что возвращались объекты Movie без каких-либо атрибутов.Из шаблона было получено следующее:
ActiveModel::MissingAttributeError in Movies#action
отсутствует атрибут: some_movie_attribute
Я отладил это, и как только стек достиг action_controller/metal, @movies содержал:
[#, #, #, #, #, #, #, #, #, #
Это количество объектов Movie (18) соответствует количество фильмов, возвращаемых по запросу.
Я также попробовал следующее, чтобы увидеть, является ли setseed(n)проблемой, удалив метод случайного порядка:
@movies = movies.select('setseed(.5)').page(params[:page]).per_page(16)
Это вернуло те же объекты Movie без атрибутов, что и изложенные выше. Таким образом, похоже, что setseed(n)действительно является проблемой.
Я пробовал несколько обходных путей, например:
# MoviesController
movies = Movie.get_movies(query_string)
shuf_movs = movies.shuffle ## effectively turns shuf_movs into an array
@movies = shuf_movs.paginate(:page => params[:page], :per_page => 16)
Это также возвращало страницы с повторяющимися фильмами. Я думал, это потому, что для нумерации страниц нужно, чтобы объекты были упорядочены something, и вы не можете установить семена в Array#shuffle. Поэтому я попытался написать свой собственный код рандомизации, который временно сохранял бы идентификатор для упорядочения в объектах Movie. (Извините за неаккуратный код):
# Movie model
attr_accessor :rand_id
# MoviesController
movies = get_movies(query_string)
movies_count = movies.count
r = Random.new
nums = []
rand_movs = []
id = 1
while nums.count != movies_count
num = r.rand(0..movies_count - 1)
if !(nums.include?(num))
movie = movies[num]
movie.rand_id = id
rand_movs << movie
nums << num
id += 1
end
end
@movies = rand_movs.sort_by { |a| a.rand_id }.paginate(:page => params[:page], :per_page => 16)
Это по-прежнему производило повторяющиеся фильмы на разных страницах. В этот момент я понимаю, что will_paginate не принимает то, что вы отсортировали до вызова paginate. Итак, я попробовал это:
@movies = rand_movs.paginate(:order => 'rand_id', :page => params[:page], :per_page => 16)
Это все еще повторяет записи. :order -> 'rand_id'игнорируется, потому что :orderимеет значение только тогда, когда вы имеете дело с объектами ActiveRecord, а не с массивами.
Так что setseed(n)кажется моей единственной надеждой на получение рандомизированных неповторяющихся записей с помощью will_paginate. Любые идеи?
Спасибо!