randumb 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|