Techioz Blog

特定のレコードを含むランダムな ActiveRecord

概要

条件を満たし、一部のレコードを除外するランダムなレコードを 1 つ返すモデルのクラス関数を作成したいと考えています。 「ランダム記事セクション」を作ろうと考えています。

私の関数は次のようにしたいのですが

Article.randomArticle([1, 5, 10]) # array of article ids to exclude

いくつかの疑似コード:

ids_to_exclude = [1,2,3]

loop do
  returned_article = Article.where(published: true).sample
  break unless ids_to_exclude.include?(returned_article.id)
do

解決策

DB 固有のオプションを見てみましょう。

class Article
  # ...
  def self.random(limit: 10)
    scope = Article.where(published: true)
    # postgres, sqlite
    scope.limit(limit).order('RANDOM()')
    # mysql
    scope.limit(limit).order('RAND()')
  end
end

Article.random は、データベースに 10 個のランダムなレコードを取得するよう要求します。 それでは、いくつかのレコードを除外するオプションを追加する方法を見てみましょう。

class Article
  # ...
  def self.random(limit: 10, except: nil)
    scope = Article.where(published: true)
    if except
      scope = scope.where.not(id: except)
    end 
    scope.limit(limit).order('RANDOM()')
  end
end

これで、Article.random(例外: [1,2,3]) は、ID が [1,2,3] ではない 10 個のレコードを取得します。

これは、rails の .where がチェーン可能なスコープを返すためです。例えば:

> User.where(email: '[email protected]').where.not(id: 1)
User Load (0.7ms)  SELECT "users".* FROM "users" WHERE "users"."email" = $1 AND ("users"."id" != $2)  [["email", "[email protected]"], ["id", 1]]
=> #<ActiveRecord::Relation []>

ここでスコープを渡すこともできます。

# cause everyone hates Bob
Article.random(except: Article.where(author: 'Bob'))

ここで DB 固有のソリューションが適切な選択である理由については、「Rails Quick Tips - Random Records」を参照してください。