5

我有一个带有属性的电影数据库。我想以随机顺序将查询的一批电影返回到带有分页的模板。我正在使用 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 并没有这样做。在ORDER中发出 random()之前,您必须在SELECT中声明setseed(n)

所以我尝试了postgres的方式来设置种子:

@movies = movies.select('setseed(.5)').order('random()').page(params[:page]).per_page(16)

奇怪的是,它返回了完全没有属性的 Movie 对象。以下是从模板中提出的:

电影中的 ActiveModel::MissingAttributeError#action

缺少属性:some_movie_attribute

我对此进行了调试,一旦堆栈到达 action_controller/metal,@movies 包含:

[#<Movie >, #<Movie >, #<Movie >, #<Movie >, #<Movie >, #<Movie >, #<Movie >, #<Movie >, #<Movie >, #<Movie >, #<Movie >, #<Movie >, #<Movie >, #<Movie >, #<Movie >, #<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)

这也返回了包含重复电影的页面。我认为这是因为分页需要对对象进行排序,并且不能在 Array#shuffle 中设置种子。因此,我尝试编写自己的随机化代码,该代码将临时存储要在 Movie 对象中排序的 id。(请原谅草率的代码):

# 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 实现随机非重复记录的唯一希望。有任何想法吗?

谢谢!

4

2 回答 2

19

不是 postgres 人,但......我会尝试

Movie.connection.execute "select setseed(0.5)"
Movie.where(...).order('random()').page(params[:page]).per_page(15)

关于Array#shuffle不带种子,它使用Kernel.rand,因此您可以使用Kernel.srand

于 2012-04-29T08:04:54.917 回答
1

尝试将字段数组传递给select

@movies = movies.select(['setseed(.5)', 'some_movie_attribute']).order('random()').page(params[:page]).per_page(16)

某些结果是 using some_movie_attribute,您的查询未选择该结果,因此不可用。将其添加为选择字段之一应该可以解决这个问题。

于 2012-04-29T01:37:00.097 回答