Rails 3,will_paginate,随机,重复记录,Postgres,setseed失败

时间:2012-04-28 21:29:10

标签: ruby-on-rails ruby-on-rails-3 postgresql random will-paginate

我有一个带属性的电影数据库。我想以随机顺序将查询过的一批电影返回到带分页的模板。我正在使用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)

这很好用,除了电影在页面之间重复。已发布此问题的解决方案herehere。这些链接解释了,因为 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对象完全没有属性。以下内容来自模板:

  

Movie#action

中的ActiveModel :: MissingAttributeError      
    

缺少属性:some_movie_attribute

  

我调试了这个,一旦堆栈到达action_controller / metal,@ movie包含:

[#<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。 (请原谅邋code的代码):

# 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)

仍然会在不同的页面中制作重复的电影。在这一点上,我意识到在调用 paginate 之前,will_paginate不会接受你排序的内容。所以我尝试了这个:

@movies = rand_movs.paginate(:order => 'rand_id', :page => params[:page], :per_page => 16)

仍然重复记录。 :订单 - &gt; 'rand_id'会被忽略,因为:order 仅在您处理ActiveRecord对象而不是数组时很重要。

所以 setseed(n)似乎是我使用will_paginate实现随机非重复记录的唯一希望。有什么想法吗?

谢谢!

2 个答案:

答案 0 :(得分:18)

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

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

关于Array#shuffle没有取种子,它会使用Kernel.rand,因此您可以使用Kernel.srand

播种

答案 1 :(得分:1)

尝试将一组字段传递给select

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

某些结果是使用some_movie_attribute,而您的查询未选择该{{1}},因此无法使用。将其添加为选择字段之一应解决此问题。