module Randumb::ActiveRecord::Relation

Public Instance Methods

order_by_rand(opts = {}) click to toggle source
# File lib/randumb/relation.rb, line 66
def order_by_rand(opts = {})
  if opts.is_a?(Hash)
    build_order_scope(opts)
  else
    raise ArgumentError.new(
      "order_by_rand() expects a hash of options.  If you need to limit "\
      "results simply add a limit to your scope ex: Artist.order_by_rand.limit(1)"
    )
  end
end
order_by_rand_weighted(ranking_column, opts={}) click to toggle source
# File lib/randumb/relation.rb, line 77
def order_by_rand_weighted(ranking_column, opts={})
  raise_unless_valid_ranking_column(ranking_column)
  is_randumb_postges_case?(self, ranking_column)
  build_order_scope(opts, ranking_column)
end
random(max_items = nil, opts={}) click to toggle source

If the max_items argument is omitted, one random entity will be returned. If you provide the integer argument, you will get back an array of records.

# File lib/randumb/relation.rb, line 12
def random(max_items = nil, opts={})
  ActiveSupport::Deprecation.warn "The random() method will be depricated in randumb 1.0 in favor of the order_by_rand scope."
  relation = clone
  return random_by_id_shuffle(max_items, opts) if is_randumb_postges_case?(relation)
  scope = relation.order_by_rand(opts)

  scope = scope.limit(max_items) if override_limit?(max_items, relation)

  # return first record if method was called without parameters
  max_items ? scope.to_a : scope.first
end
random_by_id_shuffle(max_items = nil, opts={}) click to toggle source

This was my first implementation, adding it as an option for people to use and to fall back on for pesky DB one off situations…

https://github.com/spilliton/randumb/issues/7
# File lib/randumb/relation.rb, line 45
def random_by_id_shuffle(max_items = nil, opts={})
  return_first_record = max_items.nil? # see return switch at end
  max_items ||= 1
  relation = clone
  ids = fetch_random_ids(relation, max_items, opts)

  # build new scope for final query
  the_scope = klass.includes(includes_values)

  # specifying empty selects caused bug in rails 3.0.0/3.0.1
  the_scope = the_scope.select(select_values) unless select_values.empty?

  # get the records and shuffle since the order of the ids
  # passed to where() isn't retained in the result set
  rng = random_number_generator(opts)
  records = the_scope.where(:id => ids).to_a.shuffle!(:random => rng)

  # return first record if method was called without parameters
  return_first_record ? records.first : records
end
random_weighted(ranking_column, max_items = nil, opts={}) click to toggle source

If ranking_column is provided, that named column wil be multiplied by a random number to determine probability of order. The ranking column must be numeric.

# File lib/randumb/relation.rb, line 26
def random_weighted(ranking_column, max_items = nil, opts={})
  ActiveSupport::Deprecation.warn "The random_weighted() method will be depricated in randumb 1.0 in favor of the order_by_rand_weighted scope."
  relation = clone
  return random_by_id_shuffle(max_items, opts) if is_randumb_postges_case?(relation, ranking_column)
  raise_unless_valid_ranking_column(ranking_column)

  scope = relation.order_by_rand_weighted(ranking_column, opts)

  # override the limit if they are requesting multiple records
  scope = scope.limit(max_items) if override_limit?(max_items, relation)

  # return first record if method was called without parameters
  max_items ? scope.to_a : scope.first
end

Private Instance Methods

build_order_scope(options, ranking_column=nil) click to toggle source
# File lib/randumb/relation.rb, line 85
def build_order_scope(options, ranking_column=nil)
  opts = options.reverse_merge(connection: connection, table_name: table_name)

  order_clause = if ranking_column
    Randumb::Syntax.random_weighted_order_clause(ranking_column, opts)
  else
    Randumb::Syntax.random_order_clause(opts)
  end

  # keep prior orders and append random
  all_orders = (arel.orders + [order_clause])
  # override all previous orders
  reorder(all_orders)
end
fetch_random_ids(relation, max_ids, opts = {}) click to toggle source

Returns all matching ids from the db, shuffles them, then returns an array containing at most max_ids

# File lib/randumb/relation.rb, line 123
def fetch_random_ids(relation, max_ids, opts = {})
  # clear these for our id only query
  relation.select_values = []
  relation.includes_values = []

  # do original query but only for id field
  id_only_relation = relation.select("#{table_name}.id")

  id_results = connection.select_all(id_only_relation.to_sql)

  rng = random_number_generator(opts)
  if max_ids == 1 && id_results.count > 0
    rand_index = rng.rand(id_results.count)
    [id_results[rand_index]["id"]]
  else
    # ActiveRecord 4 requires .to_a
    arr = id_results.respond_to?(:to_a) ? id_results.to_a : id_results
    arr.shuffle!(random: rng)[0, max_ids].collect! { |h| h["id"] }
  end
end
is_randumb_postges_case?(relation, ranking_column=nil) click to toggle source

postgres won't let you do an order_by when also doing a distinct let's just use the in-memory option in this case

# File lib/randumb/relation.rb, line 102
def is_randumb_postges_case?(relation, ranking_column=nil)
  if relation.respond_to?(:uniq_value) && relation.uniq_value && connection.adapter_name =~ /(postgres|postgis)/i
    if ranking_column
      raise Exception, "order_by_rand_weighted: not possible when using .uniq and the postgres/postgis db adapter"
    else
      return true
    end
  end
end
override_limit?(max_items, relation) click to toggle source
# File lib/randumb/relation.rb, line 152
def override_limit?(max_items, relation)
  max_items && (!relation.limit_value || relation.limit_value > max_items)
end
raise_unless_valid_ranking_column(ranking_column) click to toggle source

columns used for ranking must be a numeric type b/c they are multiplied

# File lib/randumb/relation.rb, line 113
def raise_unless_valid_ranking_column(ranking_column)
  if ranking_column
    column_data = @klass.columns_hash[ranking_column.to_s]
    raise ArgumentError.new("random_weighted: #{ranking_column} is not a column on #{@klass.table_name}!") unless column_data
    raise ArgumentError.new("random_weighted: #{ranking_column} is not a numeric column on #{@klass.table_name}!") unless [:integer, :float].include?(column_data.type)
  end
end
random_number_generator(opts={}) click to toggle source
# File lib/randumb/relation.rb, line 144
def random_number_generator(opts={})
  if seed = opts[:seed]
    Random.new(seed)
  else
    Random.new
  end
end