【问题标题】:Rails 3, will_paginate, random, repeating records, Postgres, setseed failureRails 3,will_paginate,随机,重复记录,Postgres,setseed 失败
【发布时间】:2012-04-28 21:29:09
【问题描述】:

我有一个带有属性的电影数据库。我想以随机顺序将查询的一批电影返回到带有分页的模板。我正在使用 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 对象完全没有属性。以下是从模板中提出的:

电影中的 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)

这也返回了包含重复电影的页面。我认为这是因为分页需要按 something 对对象进行排序,而您不能在 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 实现随机非重复记录的唯一希望。有什么想法吗?

谢谢!

【问题讨论】:

  • select 指定了查询中的列,而您没有指定,只指定了种子。不确定它是否会起作用,但试试这个:.select('*, setseed(1)')

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


【解决方案1】:

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

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

关于Array#shuffle 没有播种,它使用Kernel.rand,所以你可以使用Kernel.srand 播种

【讨论】:

  • 谢谢,弗雷德里克。这成功了。另外,感谢您的提醒:用内核播种 Array#shuffle。
  • 我正在努力实现同样的目标。这给了我每次重新加载时相同的随机顺序,但是我如何为每个访问该网站的用户获得一个新的随机顺序?
  • 您希望为每个用户传递不同的种子
【解决方案2】:

尝试将字段数组传递给select

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

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

【讨论】:

  • 非常感谢。我试过movies.select(['setseed(.5)', '*']).order('random()').page(params[:page]).per_page(16) 像你建议的那样传递一个数组,而是用'*'来调用所有电影字段(我的模板调用了一堆属性),渲染模板(万岁!)。但是现在有了种子集,记录不会随机排序 - 无论传入什么种子,它们都是按 id 排序的。:-(
  • .select,至少在我测试过的 Rails/ActiveRecord 5.0.7.2 和 6.1.4.1(可能还有一些早期版本)中,过滤掉了 setseed 的使用,但 @987654321 @ 效果很好(因为它直接使用.execute
猜你喜欢
  • 1970-01-01
  • 2011-07-17
  • 1970-01-01
  • 2013-07-14
  • 1970-01-01
  • 2018-06-06
  • 2011-04-08
  • 2015-10-08
  • 2013-01-17
相关资源
最近更新 更多