randumb 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +6 -14
- data/lib/randumb.rb +1 -0
- data/lib/randumb/relation.rb +31 -45
- data/lib/randumb/syntax.rb +80 -0
- data/lib/randumb/version.rb +1 -1
- data/test/randumb_test.rb +23 -5
- data/test/test_helper.rb +0 -6
- data/test/weighted_test.rb +13 -13
- metadata +16 -15
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
metadata.gz: !binary |-
|
9
|
-
OTkzMmIzZmQ1ZDg0YjhjYmFiODhhNDRhZWFkN2RkNzdjYTQ4NTE5ZDgzOTIx
|
10
|
-
NTY4NTk4YjVhOTZmMTFhNWJlZjM5Zjg0OGE3YzMyNmFmM2QwZDA1ZDRjYTY3
|
11
|
-
YTRmYzVkNjExY2UwMTc4MDBhZWFmZjFjNzkzYTgwMzI2MjQzYjM=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
OWJjZjUzODMzOWY2MWYwZWU1YWQzZDAxMWNkOWYyM2E3ZTQyZTNmMmE4ODI4
|
14
|
-
ZjY4NjE5ZjBjMzkyNDFmZTFhZWJlMGQ4YWNlOTg2YjdiNzA3M2NmNzhhNGZj
|
15
|
-
ZDUzMTZjNjgwYTI3ZWYzNjE0MDdkMWNmNjNlOWY4MGQ0ZWU2ZWY=
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fd1f3d0262772918cb40ffc6f8c57bf5e841ea69
|
4
|
+
data.tar.gz: 4f05d46fcee8600f14f287718adeb346b7832cf2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0a713af87ffb2e625ee646dd9308876fbddb9c034d43eca825d6520bef18f8c6255d36771eafba17b770950fa4bdf17905558d4c81f1d5f542b95c12d9f714b8
|
7
|
+
data.tar.gz: 1c8bfdab9e93a9bb3241a82115460e5d42c320aa1ac52484d2615314b076e8e7ad81618bd0d4021f83b091ba0aebbad90d110221e5d6b1b291d19e191b4517fb
|
data/lib/randumb.rb
CHANGED
data/lib/randumb/relation.rb
CHANGED
@@ -9,18 +9,19 @@ module Randumb
|
|
9
9
|
|
10
10
|
# If the max_items argument is omitted, one random entity will be returned.
|
11
11
|
# If you provide the integer argument, you will get back an array of records.
|
12
|
-
def random(max_items = nil)
|
13
|
-
random_weighted(nil, max_items)
|
12
|
+
def random(max_items = nil, opts={})
|
13
|
+
random_weighted(nil, max_items, opts)
|
14
14
|
end
|
15
15
|
|
16
16
|
# If ranking_column is provided, that named column wil be multiplied
|
17
17
|
# by a random number to determine probability of order. The ranking column must be numeric.
|
18
|
-
def random_weighted(ranking_column, max_items = nil)
|
18
|
+
def random_weighted(ranking_column, max_items = nil, opts={})
|
19
19
|
relation = clone
|
20
|
-
return random_by_id_shuffle(max_items) if is_randumb_postges_case?(relation, ranking_column)
|
20
|
+
return random_by_id_shuffle(max_items, opts) if is_randumb_postges_case?(relation, ranking_column)
|
21
21
|
raise_unless_valid_ranking_column(ranking_column)
|
22
|
+
|
22
23
|
# get clause for current db type
|
23
|
-
order_clause = random_order_clause(ranking_column)
|
24
|
+
order_clause = Randumb::Syntax.random_order_clause(ranking_column, opts.merge(connection: connection, table_name: table_name))
|
24
25
|
|
25
26
|
the_scope = if ::ActiveRecord::VERSION::MAJOR == 3 && ::ActiveRecord::VERSION::MINOR < 2
|
26
27
|
# AR 3.0.0 support
|
@@ -45,11 +46,11 @@ module Randumb
|
|
45
46
|
# This was my first implementation, adding it as an option for people to use
|
46
47
|
# and to fall back on for pesky DB one off situations...
|
47
48
|
# https://github.com/spilliton/randumb/issues/7
|
48
|
-
def random_by_id_shuffle(max_items = nil)
|
49
|
+
def random_by_id_shuffle(max_items = nil, opts={})
|
49
50
|
return_first_record = max_items.nil? # see return switch at end
|
50
51
|
max_items ||= 1
|
51
52
|
relation = clone
|
52
|
-
ids = fetch_random_ids(relation, max_items)
|
53
|
+
ids = fetch_random_ids(relation, max_items, opts)
|
53
54
|
|
54
55
|
# build new scope for final query
|
55
56
|
the_scope = klass.includes(includes_values)
|
@@ -58,8 +59,9 @@ module Randumb
|
|
58
59
|
the_scope = the_scope.select(select_values) unless select_values.empty?
|
59
60
|
|
60
61
|
# get the records and shuffle since the order of the ids
|
61
|
-
# passed to
|
62
|
-
|
62
|
+
# passed to where() isn't retained in the result set
|
63
|
+
rng = random_number_generator(opts)
|
64
|
+
records = the_scope.where(:id => ids).shuffle!(:random => rng)
|
63
65
|
|
64
66
|
# return first record if method was called without parameters
|
65
67
|
return_first_record ? records.first : records
|
@@ -88,36 +90,9 @@ module Randumb
|
|
88
90
|
end
|
89
91
|
end
|
90
92
|
|
91
|
-
# sligtly different for each DB
|
92
|
-
def random_syntax
|
93
|
-
if connection.adapter_name =~ /(sqlite|postgres|postgis)/i
|
94
|
-
"RANDOM()"
|
95
|
-
elsif connection.adapter_name =~ /mysql/i
|
96
|
-
"RAND()"
|
97
|
-
else
|
98
|
-
raise Exception, "ActiveRecord adapter: '#{connection.adapter_name}' not supported by randumb. Send a pull request or open a ticket: https://github.com/spilliton/randumb"
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
# builds the order clause to be appended in where clause
|
103
|
-
def random_order_clause(ranking_column)
|
104
|
-
if ranking_column.nil?
|
105
|
-
random_syntax
|
106
|
-
else
|
107
|
-
if connection.adapter_name =~ /sqlite/i
|
108
|
-
# computer multiplication is faster than division I was once taught...so translate here
|
109
|
-
max_int = 9223372036854775807.0
|
110
|
-
multiplier = 1.0 / max_int
|
111
|
-
"(#{ranking_column} * ABS(#{random_syntax} * #{multiplier}) ) DESC"
|
112
|
-
else
|
113
|
-
"(#{ranking_column} * #{random_syntax}) DESC"
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
93
|
# Returns all matching ids from the db, shuffles them,
|
119
94
|
# then returns an array containing at most max_ids
|
120
|
-
def fetch_random_ids(relation, max_ids)
|
95
|
+
def fetch_random_ids(relation, max_ids, opts={})
|
121
96
|
# clear these for our id only query
|
122
97
|
relation.select_values = []
|
123
98
|
relation.includes_values = []
|
@@ -127,12 +102,23 @@ module Randumb
|
|
127
102
|
|
128
103
|
id_results = connection.select_all(id_only_relation.to_sql)
|
129
104
|
|
105
|
+
|
106
|
+
rng = random_number_generator(opts)
|
130
107
|
if max_ids == 1 && id_results.count > 0
|
131
|
-
|
108
|
+
rand_index = rng.rand(id_results.count)
|
109
|
+
[ id_results[ rand_index ]['id'] ]
|
132
110
|
else
|
133
111
|
# ActiveRecord 4 requires .to_ary
|
134
112
|
arr = id_results.respond_to?(:to_ary) ? id_results.to_ary : id_results
|
135
|
-
arr.shuffle![0,max_ids].collect!{ |h| h['id'] }
|
113
|
+
arr.shuffle!(:random => rng)[0,max_ids].collect!{ |h| h['id'] }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def random_number_generator(opts={})
|
118
|
+
if seed = opts[:seed]
|
119
|
+
Random.new(seed)
|
120
|
+
else
|
121
|
+
Random.new
|
136
122
|
end
|
137
123
|
end
|
138
124
|
|
@@ -141,16 +127,16 @@ module Randumb
|
|
141
127
|
|
142
128
|
# Class methods
|
143
129
|
module Base
|
144
|
-
def random(max_items = nil)
|
145
|
-
relation.random(max_items)
|
130
|
+
def random(max_items = nil, opts = {})
|
131
|
+
relation.random(max_items, opts)
|
146
132
|
end
|
147
133
|
|
148
|
-
def random_weighted(ranking_column, max_items = nil)
|
149
|
-
relation.random_weighted(ranking_column, max_items)
|
134
|
+
def random_weighted(ranking_column, max_items = nil, opts = {})
|
135
|
+
relation.random_weighted(ranking_column, max_items, opts)
|
150
136
|
end
|
151
137
|
|
152
|
-
def random_by_id_shuffle(max_items = nil)
|
153
|
-
relation.random_by_id_shuffle(max_items)
|
138
|
+
def random_by_id_shuffle(max_items = nil, opts = {})
|
139
|
+
relation.random_by_id_shuffle(max_items, opts)
|
154
140
|
end
|
155
141
|
end
|
156
142
|
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Randumb
|
2
|
+
class Syntax
|
3
|
+
class << self
|
4
|
+
|
5
|
+
# builds the order clause to be appended in where clause
|
6
|
+
def random_order_clause(ranking_column, opts={})
|
7
|
+
if ranking_column.nil?
|
8
|
+
random_for(opts)
|
9
|
+
else
|
10
|
+
connection = opts[:connection]
|
11
|
+
if connection.adapter_name =~ /sqlite/i
|
12
|
+
# computer multiplication is faster than division I was once taught...so translate here
|
13
|
+
max_int = 9223372036854775807.0
|
14
|
+
multiplier = 1.0 / max_int
|
15
|
+
"(#{ranking_column} * ABS(#{random_for(opts)} * #{multiplier}) ) DESC"
|
16
|
+
else
|
17
|
+
"(#{ranking_column} * #{random_for(opts)}) DESC"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# sligtly different for each DB
|
25
|
+
def random_for(opts)
|
26
|
+
connection = opts[:connection]
|
27
|
+
adapter_name = connection.adapter_name
|
28
|
+
if adapter_name =~ /(sqlite)/i
|
29
|
+
random_for_sqlite(opts)
|
30
|
+
elsif adapter_name =~ /(postgres|postgis)/i
|
31
|
+
random_for_postgres(opts)
|
32
|
+
elsif adapter_name =~ /mysql/i
|
33
|
+
random_for_mysql(opts)
|
34
|
+
else
|
35
|
+
raise Exception, "ActiveRecord adapter: '#{adapter_name}' not supported by randumb. Send a pull request or open a ticket: https://github.com/spilliton/randumb"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def random_for_sqlite(opts)
|
40
|
+
if seed = opts[:seed]
|
41
|
+
table_name = opts[:table_name]
|
42
|
+
# SQLLite does not support a random seed. However, pseudo-randomness
|
43
|
+
# can be achieved by sorting on a hash of the id field (generated by
|
44
|
+
# multiplying the id by the random seed and ignoring everything before
|
45
|
+
# the decimal).
|
46
|
+
# See http://stackoverflow.com/questions/2171578/seeding-sqlite-random
|
47
|
+
seed_value = Random.new(seed.to_i).rand
|
48
|
+
"(SUBSTR(#{table_name}.id * #{seed_value}, LENGTH(#{table_name}.id) + 2))"
|
49
|
+
else
|
50
|
+
"RANDOM()"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def random_for_postgres(opts)
|
55
|
+
# Postgres random seeding requires executing an extra "SELECT SETSEED(value)" on the connection.
|
56
|
+
# See http://www.postgresql.org/docs/8.3/static/sql-set.html:
|
57
|
+
#
|
58
|
+
# Sets the internal seed for the random number generator (the
|
59
|
+
# function random). Allowed values are floating-point numbers
|
60
|
+
# between 0 and 1, which are then multiplied by 2^(31)-1.
|
61
|
+
#
|
62
|
+
if seed = opts[:seed]
|
63
|
+
connection = opts[:connection]
|
64
|
+
seed_value = Random.new(seed.to_i).rand # map integer seed to a value: 0 <= value < 1
|
65
|
+
connection.execute "SELECT SETSEED(#{seed_value})"
|
66
|
+
end
|
67
|
+
"RANDOM()"
|
68
|
+
end
|
69
|
+
|
70
|
+
def random_for_mysql(opts)
|
71
|
+
if seed = opts[:seed]
|
72
|
+
"RAND(#{seed.to_i})"
|
73
|
+
else
|
74
|
+
"RAND()"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/randumb/version.rb
CHANGED
data/test/randumb_test.rb
CHANGED
@@ -2,12 +2,11 @@ $:.unshift '.'; require File.dirname(__FILE__) + '/test_helper'
|
|
2
2
|
|
3
3
|
class RandumbTest < Test::Unit::TestCase
|
4
4
|
|
5
|
-
def assert_equal_for_both_methods(expected, obj, params
|
6
|
-
assert_equal expected, obj.send(:random, params), "when calling random"
|
7
|
-
assert_equal expected, obj.send(:random_by_id_shuffle, params), "when calling random_by_id_shuffle"
|
5
|
+
def assert_equal_for_both_methods(expected, obj, *params)
|
6
|
+
assert_equal expected, obj.send(:random, *params), "when calling random"
|
7
|
+
assert_equal expected, obj.send(:random_by_id_shuffle, *params), "when calling random_by_id_shuffle"
|
8
8
|
end
|
9
9
|
|
10
|
-
|
11
10
|
should "should return empty when no record in table" do
|
12
11
|
assert_equal 0, Artist.count
|
13
12
|
|
@@ -190,6 +189,25 @@ class RandumbTest < Test::Unit::TestCase
|
|
190
189
|
assert order1_found
|
191
190
|
assert order2_found
|
192
191
|
end
|
193
|
-
end
|
194
192
|
|
193
|
+
context "using seed" do
|
194
|
+
setup do
|
195
|
+
@seed = 123
|
196
|
+
end
|
197
|
+
|
198
|
+
should "always return the same order using default method" do
|
199
|
+
seeded_order = Artist.random(2, seed: @seed)
|
200
|
+
10.times do
|
201
|
+
assert_equal seeded_order, Artist.random(2, seed: @seed)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
should "always return the same order using shuffle method" do
|
206
|
+
seeded_order = Artist.random_by_id_shuffle(2, seed: @seed)
|
207
|
+
10.times do
|
208
|
+
assert_equal seeded_order, Artist.random_by_id_shuffle(2, seed: @seed)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
195
213
|
end
|
data/test/test_helper.rb
CHANGED
@@ -6,16 +6,10 @@ require 'factory_girl'
|
|
6
6
|
require 'faker'
|
7
7
|
require 'active_record'
|
8
8
|
require 'active_support/dependencies'
|
9
|
-
require 'active_support/core_ext/logger'
|
10
|
-
# require 'active_record/fixtures'
|
11
9
|
require 'randumb'
|
12
10
|
|
13
11
|
MODELS_PATH = File.join(File.dirname(__FILE__), 'models')
|
14
12
|
|
15
|
-
|
16
|
-
ActiveRecord::Base.logger = Logger.new(STDERR)
|
17
|
-
ActiveRecord::Base.logger.level = Logger::WARN
|
18
|
-
|
19
13
|
config = YAML::load(File.open(File.expand_path("../databases.yml", __FILE__)))
|
20
14
|
version = ActiveRecord::VERSION::STRING
|
21
15
|
driver = (ENV["DB"] or "sqlite3").downcase
|
data/test/weighted_test.rb
CHANGED
@@ -107,20 +107,20 @@ class WeightedTest < Test::Unit::TestCase
|
|
107
107
|
result.first.views
|
108
108
|
end
|
109
109
|
end
|
110
|
+
end
|
110
111
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
end
|
112
|
+
def assert_hits_per_views
|
113
|
+
hits_per_views = Hash.new
|
114
|
+
@view_counts.each { |views| hits_per_views[views] = 0 }
|
115
|
+
|
116
|
+
1000.times do
|
117
|
+
hits_per_views[yield] += 1
|
118
|
+
end
|
119
|
+
last_count = 0
|
120
|
+
@view_counts.each do |views|
|
121
|
+
hits = hits_per_views[views]
|
122
|
+
assert(hits >= last_count, "#{hits} > #{last_count} : There were an unexpected number of visits: #{hits_per_views.to_yaml}")
|
123
|
+
last_count = hits
|
124
124
|
end
|
125
125
|
end
|
126
126
|
|
metadata
CHANGED
@@ -1,55 +1,55 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: randumb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zachary Kloepping
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-11-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - '>='
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activesupport
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - '>='
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: 3.0.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - '>='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 3.0.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: activerecord
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - '>='
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: 3.0.0
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - '>='
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 3.0.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
@@ -70,14 +70,14 @@ dependencies:
|
|
70
70
|
name: shoulda
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - '>='
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- -
|
80
|
+
- - '>='
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
@@ -98,14 +98,14 @@ dependencies:
|
|
98
98
|
name: faker
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- -
|
101
|
+
- - '>='
|
102
102
|
- !ruby/object:Gem::Version
|
103
103
|
version: '0'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- -
|
108
|
+
- - '>='
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
111
|
description:
|
@@ -115,6 +115,7 @@ extensions: []
|
|
115
115
|
extra_rdoc_files: []
|
116
116
|
files:
|
117
117
|
- lib/randumb/relation.rb
|
118
|
+
- lib/randumb/syntax.rb
|
118
119
|
- lib/randumb/version.rb
|
119
120
|
- lib/randumb.rb
|
120
121
|
- test/models/album.rb
|
@@ -133,17 +134,17 @@ require_paths:
|
|
133
134
|
- lib
|
134
135
|
required_ruby_version: !ruby/object:Gem::Requirement
|
135
136
|
requirements:
|
136
|
-
- -
|
137
|
+
- - '>='
|
137
138
|
- !ruby/object:Gem::Version
|
138
139
|
version: '0'
|
139
140
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
141
|
requirements:
|
141
|
-
- -
|
142
|
+
- - '>='
|
142
143
|
- !ruby/object:Gem::Version
|
143
144
|
version: '0'
|
144
145
|
requirements: []
|
145
146
|
rubyforge_project:
|
146
|
-
rubygems_version: 2.
|
147
|
+
rubygems_version: 2.1.10
|
147
148
|
signing_key:
|
148
149
|
specification_version: 4
|
149
150
|
summary: Adds the ability to pull random records from ActiveRecord
|