randumb 0.4.0 → 0.6.0
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 +93 -64
- data/lib/randumb/syntax.rb +82 -0
- data/lib/randumb/version.rb +1 -1
- data/test/models/artist.rb +8 -2
- data/test/models/factories.rb +1 -1
- data/test/randumb_test.rb +77 -22
- data/test/test_helper.rb +14 -13
- data/test/weighted_test.rb +54 -21
- metadata +98 -28
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
|
+
SHA256:
|
3
|
+
metadata.gz: 9fd0e54ae7de8a15766df8a33d65edd4a5d1eab2cb3ba36b27c6f9541ab39a72
|
4
|
+
data.tar.gz: ed1afd68fa829e7e236d92d44fda28439492b004eda2c8d45b11b1b85f8feee3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7031236bbde306b4c508d019956514ebe0a9b93b037f0d2cc2a539ea694b55c33c05b6728cfaae952f663b2727f919b974a863ddc905b15c77e70ca99c149c91
|
7
|
+
data.tar.gz: 7431df3f579c21e3076507b85564375646db6e78135ac0420d399c62e50474bafe3a1d827bc1f174edb57f22c8a68dbbaf7eb6b8e656e845e23476dfbb4bc310
|
data/lib/randumb.rb
CHANGED
data/lib/randumb/relation.rb
CHANGED
@@ -9,47 +9,44 @@ 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
|
-
|
12
|
+
def random(max_items = nil, opts={})
|
13
|
+
ActiveSupport::Deprecation.warn "The random() method will be depricated in randumb 1.0 in favor of the order_by_rand scope."
|
14
|
+
relation = clone
|
15
|
+
return random_by_id_shuffle(max_items, opts) if is_randumb_postges_case?(relation)
|
16
|
+
scope = relation.order_by_rand(opts)
|
17
|
+
|
18
|
+
scope = scope.limit(max_items) if override_limit?(max_items, relation)
|
19
|
+
|
20
|
+
# return first record if method was called without parameters
|
21
|
+
max_items ? scope.to_a : scope.first
|
14
22
|
end
|
15
23
|
|
16
24
|
# If ranking_column is provided, that named column wil be multiplied
|
17
25
|
# by a random number to determine probability of order. The ranking column must be numeric.
|
18
|
-
def random_weighted(ranking_column, max_items = nil)
|
26
|
+
def random_weighted(ranking_column, max_items = nil, opts={})
|
27
|
+
ActiveSupport::Deprecation.warn "The random_weighted() method will be depricated in randumb 1.0 in favor of the order_by_rand_weighted scope."
|
19
28
|
relation = clone
|
20
|
-
return random_by_id_shuffle(max_items) if is_randumb_postges_case?(relation, ranking_column)
|
29
|
+
return random_by_id_shuffle(max_items, opts) if is_randumb_postges_case?(relation, ranking_column)
|
21
30
|
raise_unless_valid_ranking_column(ranking_column)
|
22
|
-
# get clause for current db type
|
23
|
-
order_clause = random_order_clause(ranking_column)
|
24
31
|
|
25
|
-
|
26
|
-
|
27
|
-
relation.order(order_clause)
|
28
|
-
else
|
29
|
-
# keep prior orders and append random
|
30
|
-
all_orders = (relation.orders + [order_clause]).join(", ")
|
31
|
-
# override all previous orders
|
32
|
-
relation.reorder(all_orders)
|
33
|
-
end
|
34
|
-
|
32
|
+
scope = relation.order_by_rand_weighted(ranking_column, opts)
|
33
|
+
|
35
34
|
# override the limit if they are requesting multiple records
|
36
|
-
|
37
|
-
the_scope = the_scope.limit(max_items)
|
38
|
-
end
|
35
|
+
scope = scope.limit(max_items) if override_limit?(max_items, relation)
|
39
36
|
|
40
37
|
# return first record if method was called without parameters
|
41
|
-
max_items ?
|
38
|
+
max_items ? scope.to_a : scope.first
|
42
39
|
end
|
43
40
|
|
44
41
|
|
45
42
|
# This was my first implementation, adding it as an option for people to use
|
46
43
|
# and to fall back on for pesky DB one off situations...
|
47
44
|
# https://github.com/spilliton/randumb/issues/7
|
48
|
-
def random_by_id_shuffle(max_items = nil)
|
45
|
+
def random_by_id_shuffle(max_items = nil, opts={})
|
49
46
|
return_first_record = max_items.nil? # see return switch at end
|
50
47
|
max_items ||= 1
|
51
48
|
relation = clone
|
52
|
-
ids = fetch_random_ids(relation, max_items)
|
49
|
+
ids = fetch_random_ids(relation, max_items, opts)
|
53
50
|
|
54
51
|
# build new scope for final query
|
55
52
|
the_scope = klass.includes(includes_values)
|
@@ -58,21 +55,54 @@ module Randumb
|
|
58
55
|
the_scope = the_scope.select(select_values) unless select_values.empty?
|
59
56
|
|
60
57
|
# get the records and shuffle since the order of the ids
|
61
|
-
# passed to
|
62
|
-
|
58
|
+
# passed to where() isn't retained in the result set
|
59
|
+
rng = random_number_generator(opts)
|
60
|
+
records = the_scope.where(:id => ids).to_a.shuffle!(:random => rng)
|
63
61
|
|
64
62
|
# return first record if method was called without parameters
|
65
63
|
return_first_record ? records.first : records
|
66
64
|
end
|
67
65
|
|
66
|
+
def order_by_rand(opts = {})
|
67
|
+
if opts.is_a?(Hash)
|
68
|
+
build_order_scope(opts)
|
69
|
+
else
|
70
|
+
raise ArgumentError.new(
|
71
|
+
"order_by_rand() expects a hash of options. If you need to limit "\
|
72
|
+
"results simply add a limit to your scope ex: Artist.order_by_rand.limit(1)"
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def order_by_rand_weighted(ranking_column, opts={})
|
78
|
+
raise_unless_valid_ranking_column(ranking_column)
|
79
|
+
is_randumb_postges_case?(self, ranking_column)
|
80
|
+
build_order_scope(opts, ranking_column)
|
81
|
+
end
|
82
|
+
|
68
83
|
private
|
69
84
|
|
85
|
+
def build_order_scope(options, ranking_column=nil)
|
86
|
+
opts = options.reverse_merge(connection: connection, table_name: table_name)
|
87
|
+
|
88
|
+
order_clause = if ranking_column
|
89
|
+
Randumb::Syntax.random_weighted_order_clause(ranking_column, opts)
|
90
|
+
else
|
91
|
+
Randumb::Syntax.random_order_clause(opts)
|
92
|
+
end
|
93
|
+
|
94
|
+
# keep prior orders and append random
|
95
|
+
all_orders = (arel.orders + [order_clause])
|
96
|
+
# override all previous orders
|
97
|
+
reorder(all_orders)
|
98
|
+
end
|
99
|
+
|
70
100
|
# postgres won't let you do an order_by when also doing a distinct
|
71
101
|
# let's just use the in-memory option in this case
|
72
|
-
def is_randumb_postges_case?(relation, ranking_column)
|
102
|
+
def is_randumb_postges_case?(relation, ranking_column=nil)
|
73
103
|
if relation.respond_to?(:uniq_value) && relation.uniq_value && connection.adapter_name =~ /(postgres|postgis)/i
|
74
104
|
if ranking_column
|
75
|
-
raise Exception, "
|
105
|
+
raise Exception, "order_by_rand_weighted: not possible when using .uniq and the postgres/postgis db adapter"
|
76
106
|
else
|
77
107
|
return true
|
78
108
|
end
|
@@ -88,36 +118,9 @@ module Randumb
|
|
88
118
|
end
|
89
119
|
end
|
90
120
|
|
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
121
|
# Returns all matching ids from the db, shuffles them,
|
119
122
|
# then returns an array containing at most max_ids
|
120
|
-
def fetch_random_ids(relation, max_ids)
|
123
|
+
def fetch_random_ids(relation, max_ids, opts = {})
|
121
124
|
# clear these for our id only query
|
122
125
|
relation.select_values = []
|
123
126
|
relation.includes_values = []
|
@@ -127,30 +130,54 @@ module Randumb
|
|
127
130
|
|
128
131
|
id_results = connection.select_all(id_only_relation.to_sql)
|
129
132
|
|
133
|
+
rng = random_number_generator(opts)
|
130
134
|
if max_ids == 1 && id_results.count > 0
|
131
|
-
|
135
|
+
rand_index = rng.rand(id_results.count)
|
136
|
+
[id_results[rand_index]["id"]]
|
132
137
|
else
|
133
|
-
# ActiveRecord 4 requires .
|
134
|
-
arr = id_results.respond_to?(:
|
135
|
-
arr.shuffle![0,max_ids].collect!{ |h| h[
|
138
|
+
# ActiveRecord 4 requires .to_a
|
139
|
+
arr = id_results.respond_to?(:to_a) ? id_results.to_a : id_results
|
140
|
+
arr.shuffle!(random: rng)[0, max_ids].collect! { |h| h["id"] }
|
136
141
|
end
|
137
142
|
end
|
138
143
|
|
144
|
+
def random_number_generator(opts={})
|
145
|
+
if seed = opts[:seed]
|
146
|
+
Random.new(seed)
|
147
|
+
else
|
148
|
+
Random.new
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def override_limit?(max_items, relation)
|
153
|
+
max_items && (!relation.limit_value || relation.limit_value > max_items)
|
154
|
+
end
|
155
|
+
|
139
156
|
end
|
140
157
|
|
141
158
|
|
142
159
|
# Class methods
|
160
|
+
# where(nil) is because:
|
161
|
+
# http://stackoverflow.com/questions/18198963/with-rails-4-model-scoped-is-deprecated-but-model-all-cant-replace-it
|
143
162
|
module Base
|
144
|
-
def random(max_items = nil)
|
145
|
-
|
163
|
+
def random(max_items = nil, opts = {})
|
164
|
+
where(nil).random(max_items, opts)
|
165
|
+
end
|
166
|
+
|
167
|
+
def random_weighted(ranking_column, max_items = nil, opts = {})
|
168
|
+
where(nil).random_weighted(ranking_column, max_items, opts)
|
169
|
+
end
|
170
|
+
|
171
|
+
def random_by_id_shuffle(max_items = nil, opts = {})
|
172
|
+
where(nil).random_by_id_shuffle(max_items, opts)
|
146
173
|
end
|
147
174
|
|
148
|
-
def
|
149
|
-
|
175
|
+
def order_by_rand(opts = {})
|
176
|
+
where(nil).order_by_rand(opts)
|
150
177
|
end
|
151
178
|
|
152
|
-
def
|
153
|
-
|
179
|
+
def order_by_rand_weighted(ranking_column, opts={})
|
180
|
+
where(nil).order_by_rand_weighted(ranking_column, opts)
|
154
181
|
end
|
155
182
|
end
|
156
183
|
|
@@ -159,6 +186,7 @@ module Randumb
|
|
159
186
|
module MethodMissingMagicks
|
160
187
|
def method_missing(symbol, *args)
|
161
188
|
if symbol.to_s =~ /^random_weighted_by_(\w+)$/
|
189
|
+
ActiveSupport::Deprecation.warn "Dynamic finders will be removed in randumb 1.0 http://guides.rubyonrails.org/active_record_querying.html#dynamic-finders"
|
162
190
|
random_weighted($1, *args)
|
163
191
|
else
|
164
192
|
super
|
@@ -167,6 +195,7 @@ module Randumb
|
|
167
195
|
|
168
196
|
def respond_to?(symbol, include_private=false)
|
169
197
|
if symbol.to_s =~ /^random_weighted_by_(\w+)$/
|
198
|
+
ActiveSupport::Deprecation.warn "Dynamic finders will be removed in randumb 1.0 http://guides.rubyonrails.org/active_record_querying.html#dynamic-finders"
|
170
199
|
true
|
171
200
|
else
|
172
201
|
super
|
@@ -0,0 +1,82 @@
|
|
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(opts={})
|
7
|
+
random_for(opts)
|
8
|
+
end
|
9
|
+
|
10
|
+
# builds the order clause to be appended in where clause
|
11
|
+
def random_weighted_order_clause(ranking_column, opts={})
|
12
|
+
connection = opts[:connection]
|
13
|
+
|
14
|
+
if connection.adapter_name =~ /sqlite/i
|
15
|
+
# computer multiplication is faster than division I was once taught...so translate here
|
16
|
+
max_int = 9223372036854775807.0
|
17
|
+
multiplier = 1.0 / max_int
|
18
|
+
"(#{ranking_column} * ABS(#{random_for(opts)} * #{multiplier}) ) DESC"
|
19
|
+
else
|
20
|
+
"(#{ranking_column} * #{random_for(opts)}) DESC"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# sligtly different for each DB
|
27
|
+
def random_for(opts)
|
28
|
+
connection = opts[:connection]
|
29
|
+
adapter_name = connection.adapter_name
|
30
|
+
if adapter_name =~ /(sqlite)/i
|
31
|
+
random_for_sqlite(opts)
|
32
|
+
elsif adapter_name =~ /(postgres|postgis)/i
|
33
|
+
random_for_postgres(opts)
|
34
|
+
elsif adapter_name =~ /mysql/i
|
35
|
+
random_for_mysql(opts)
|
36
|
+
else
|
37
|
+
raise Exception, "ActiveRecord adapter: '#{adapter_name}' not supported by randumb. Send a pull request or open a ticket: https://github.com/spilliton/randumb"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def random_for_sqlite(opts)
|
42
|
+
if seed = opts[:seed]
|
43
|
+
table_name = opts[:table_name]
|
44
|
+
# SQLLite does not support a random seed. However, pseudo-randomness
|
45
|
+
# can be achieved by sorting on a hash of the id field (generated by
|
46
|
+
# multiplying the id by the random seed and ignoring everything before
|
47
|
+
# the decimal).
|
48
|
+
# See http://stackoverflow.com/questions/2171578/seeding-sqlite-random
|
49
|
+
seed_value = Random.new(seed.to_i).rand
|
50
|
+
"(SUBSTR(#{table_name}.id * #{seed_value}, LENGTH(#{table_name}.id) + 2))"
|
51
|
+
else
|
52
|
+
"RANDOM()"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def random_for_postgres(opts)
|
57
|
+
# Postgres random seeding requires executing an extra "SELECT SETSEED(value)" on the connection.
|
58
|
+
# See http://www.postgresql.org/docs/8.3/static/sql-set.html:
|
59
|
+
#
|
60
|
+
# Sets the internal seed for the random number generator (the
|
61
|
+
# function random). Allowed values are floating-point numbers
|
62
|
+
# between 0 and 1, which are then multiplied by 2^(31)-1.
|
63
|
+
#
|
64
|
+
if seed = opts[:seed]
|
65
|
+
connection = opts[:connection]
|
66
|
+
seed_value = Random.new(seed.to_i).rand # map integer seed to a value: 0 <= value < 1
|
67
|
+
connection.execute "SELECT SETSEED(#{seed_value})"
|
68
|
+
end
|
69
|
+
"RANDOM()"
|
70
|
+
end
|
71
|
+
|
72
|
+
def random_for_mysql(opts)
|
73
|
+
if seed = opts[:seed]
|
74
|
+
"RAND(#{seed.to_i})"
|
75
|
+
else
|
76
|
+
"RAND()"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/randumb/version.rb
CHANGED
data/test/models/artist.rb
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
class Artist < ActiveRecord::Base
|
2
2
|
has_many :albums
|
3
|
-
|
3
|
+
|
4
|
+
if Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("3.2")
|
5
|
+
default_scope { where("artists.deleted_at IS NULL") }
|
6
|
+
else
|
7
|
+
default_scope where("artists.deleted_at IS NULL")
|
8
|
+
end
|
9
|
+
|
4
10
|
scope :at_least_three_views, -> { where("views >= 3") }
|
5
|
-
end
|
11
|
+
end
|
data/test/models/factories.rb
CHANGED
data/test/randumb_test.rb
CHANGED
@@ -1,13 +1,17 @@
|
|
1
1
|
$:.unshift '.'; require File.dirname(__FILE__) + '/test_helper'
|
2
2
|
|
3
|
-
class RandumbTest < Test
|
4
|
-
|
5
|
-
def assert_equal_for_both_methods(expected, obj, params
|
6
|
-
|
7
|
-
|
3
|
+
class RandumbTest < Minitest::Test
|
4
|
+
|
5
|
+
def assert_equal_for_both_methods(expected, obj, *params)
|
6
|
+
if expected.nil?
|
7
|
+
assert_nil obj.send(:random, *params), "when calling random"
|
8
|
+
assert_nil obj.send(:random_by_id_shuffle, *params), "when calling random_by_id_shuffle"
|
9
|
+
else
|
10
|
+
assert_equal expected, obj.send(:random, *params), "when calling random"
|
11
|
+
assert_equal expected, obj.send(:random_by_id_shuffle, *params), "when calling random_by_id_shuffle"
|
12
|
+
end
|
8
13
|
end
|
9
14
|
|
10
|
-
|
11
15
|
should "should return empty when no record in table" do
|
12
16
|
assert_equal 0, Artist.count
|
13
17
|
|
@@ -15,36 +19,56 @@ class RandumbTest < Test::Unit::TestCase
|
|
15
19
|
# above is equivalent to:
|
16
20
|
# assert_equal nil, Artist.random
|
17
21
|
# assert_equal nil, Artist.random_by_id_shuffle
|
22
|
+
assert_nil Artist.order_by_rand.first
|
18
23
|
|
19
24
|
assert_equal_for_both_methods [], Artist, 1
|
20
25
|
# above is equivalent to:
|
21
26
|
# assert_equal [], Artist.random(1)
|
22
27
|
# assert_equal [], Artist.random_by_id_shuffle(1)
|
28
|
+
assert_equal [], Artist.order_by_rand.limit(1).all
|
23
29
|
|
24
30
|
assert_equal_for_both_methods nil, Artist.limit(50)
|
25
31
|
end
|
26
32
|
|
33
|
+
should "raise helpful error if non-hash passed" do
|
34
|
+
error = assert_raises ArgumentError do
|
35
|
+
Artist.order_by_rand(10)
|
36
|
+
end
|
37
|
+
assert error.message.include?("order_by_rand")
|
38
|
+
end
|
39
|
+
|
27
40
|
context "1 record in the table" do
|
28
41
|
setup do
|
29
|
-
@high_on_fire =
|
42
|
+
@high_on_fire = FactoryBot.create(:artist, :name => "High On Fire", :views => 1)
|
30
43
|
end
|
31
44
|
|
32
45
|
should "select only 1 record even when you request more" do
|
33
46
|
assert_equal 1, Artist.count
|
34
47
|
|
35
48
|
assert_equal_for_both_methods @high_on_fire, Artist
|
49
|
+
assert_equal @high_on_fire, Artist.order_by_rand.first
|
50
|
+
|
36
51
|
assert_equal_for_both_methods [@high_on_fire], Artist, 1
|
37
52
|
assert_equal_for_both_methods [@high_on_fire], Artist, 30
|
53
|
+
assert_equal [@high_on_fire], Artist.limit(30).order_by_rand.all
|
38
54
|
end
|
39
55
|
|
40
56
|
should "not return a record that doesnt match where" do
|
41
57
|
assert_equal_for_both_methods nil, Artist.where(:name => "The Little Gentlemen")
|
42
58
|
end
|
43
59
|
|
60
|
+
should "respect default scope" do
|
61
|
+
@high_on_fire.deleted_at = Time.now.utc
|
62
|
+
@high_on_fire.save!
|
63
|
+
assert_equal 0, Artist.count
|
64
|
+
assert_nil Artist.order_by_rand.first
|
65
|
+
assert_equal @high_on_fire, Artist.unscoped.order_by_rand.first
|
66
|
+
end
|
67
|
+
|
44
68
|
context "3 records in table" do
|
45
69
|
setup do
|
46
|
-
@fiona_apple =
|
47
|
-
@magnetic_fields =
|
70
|
+
@fiona_apple = FactoryBot.create(:artist, :name => "Fiona Apple", :views => 3)
|
71
|
+
@magnetic_fields = FactoryBot.create(:artist, :name => "The Magnetic Fields", :views => 2)
|
48
72
|
end
|
49
73
|
|
50
74
|
should "apply randomness after other orders when using sql method" do
|
@@ -61,19 +85,27 @@ class RandumbTest < Test::Unit::TestCase
|
|
61
85
|
end
|
62
86
|
|
63
87
|
should "respect selecting certain columns" do
|
88
|
+
# this version doesn't throw for some reason...
|
89
|
+
skip_throw_missing = ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0
|
90
|
+
|
64
91
|
assert_equal 3, Artist.find(@fiona_apple.id).views
|
65
92
|
|
66
93
|
artists = Artist.select(:name).random(3)
|
67
94
|
assert_equal false, artists.first.name.nil?
|
68
|
-
|
95
|
+
assert_raises (ActiveModel::MissingAttributeError) { artists.first.views } unless skip_throw_missing
|
96
|
+
|
97
|
+
artists = Artist.select(:name).order_by_rand.limit(3)
|
98
|
+
assert_equal false, artists.first.name.nil?
|
99
|
+
assert_raises (ActiveModel::MissingAttributeError) { artists.first.views } unless skip_throw_missing
|
69
100
|
|
70
101
|
artists = Artist.select(:name).random_by_id_shuffle(3)
|
71
102
|
assert_equal false, artists.first.name.nil?
|
72
|
-
|
103
|
+
assert_raises (ActiveModel::MissingAttributeError) { artists.first.views } unless skip_throw_missing
|
73
104
|
end
|
74
105
|
|
75
106
|
should "respect scopes" do
|
76
107
|
assert_equal_for_both_methods [@fiona_apple], Artist.at_least_three_views, 3
|
108
|
+
assert_equal [@fiona_apple], Artist.at_least_three_views.order_by_rand.limit(3)
|
77
109
|
end
|
78
110
|
|
79
111
|
should "select only as many as in the db if we request more" do
|
@@ -92,10 +124,10 @@ class RandumbTest < Test::Unit::TestCase
|
|
92
124
|
|
93
125
|
context "with some albums" do
|
94
126
|
setup do
|
95
|
-
@tidal =
|
96
|
-
@extraordinary_machine =
|
97
|
-
@sixty_nine_love_songs =
|
98
|
-
@snakes_for_the_divine =
|
127
|
+
@tidal = FactoryBot.create(:album, :name => "Tidal", :artist => @fiona_apple)
|
128
|
+
@extraordinary_machine = FactoryBot.create(:album, :name => "Extraordinary Machine", :artist => @fiona_apple)
|
129
|
+
@sixty_nine_love_songs = FactoryBot.create(:album, :name => "69 Love Songs", :artist => @magnetic_fields)
|
130
|
+
@snakes_for_the_divine = FactoryBot.create(:album, :name => "Snakes For the Divine", :artist => @high_on_fire)
|
99
131
|
end
|
100
132
|
|
101
133
|
|
@@ -103,7 +135,7 @@ class RandumbTest < Test::Unit::TestCase
|
|
103
135
|
artists = Artist.includes(:albums).random(10)
|
104
136
|
fiona_apple = artists.find { |a| a.name == "Fiona Apple" }
|
105
137
|
# if I add a new album now, it shouldn't be in the albums assocation yet b/c it was already loaded
|
106
|
-
|
138
|
+
FactoryBot.create(:album, :name => "When The Pawn", :artist => @fiona_apple)
|
107
139
|
|
108
140
|
assert_equal 2, fiona_apple.albums.length
|
109
141
|
assert_equal 3, @fiona_apple.reload.albums.length
|
@@ -113,7 +145,7 @@ class RandumbTest < Test::Unit::TestCase
|
|
113
145
|
artists = Artist.includes(:albums).random_by_id_shuffle(10)
|
114
146
|
fiona_apple = artists.find { |a| a.name == "Fiona Apple" }
|
115
147
|
# if I add a new album now, it shouldn't be in the albums assocation yet b/c it was already loaded
|
116
|
-
|
148
|
+
FactoryBot.create(:album, :name => "When The Pawn", :artist => @fiona_apple)
|
117
149
|
|
118
150
|
assert_equal 2, fiona_apple.albums.length
|
119
151
|
assert_equal 3, @fiona_apple.reload.albums.length
|
@@ -144,8 +176,8 @@ class RandumbTest < Test::Unit::TestCase
|
|
144
176
|
should "work with uniq" do
|
145
177
|
assert_equal 2, Artist.uniq.random(2).length
|
146
178
|
assert_equal 2, Artist.uniq.random_by_id_shuffle(2).length
|
147
|
-
|
148
|
-
|
179
|
+
assert !Artist.uniq.random.nil?
|
180
|
+
assert !Artist.uniq.random_by_id_shuffle.nil?
|
149
181
|
end
|
150
182
|
end
|
151
183
|
|
@@ -157,8 +189,8 @@ class RandumbTest < Test::Unit::TestCase
|
|
157
189
|
|
158
190
|
context "2 records in table" do
|
159
191
|
setup do
|
160
|
-
@hum =
|
161
|
-
@minutemen =
|
192
|
+
@hum = FactoryBot.create(:artist, :name => "Hum", :views => 3)
|
193
|
+
@minutemen = FactoryBot.create(:artist, :name => "Minutemen", :views => 2)
|
162
194
|
end
|
163
195
|
|
164
196
|
should "eventually render the 2 possible orders using default method" do
|
@@ -190,6 +222,29 @@ class RandumbTest < Test::Unit::TestCase
|
|
190
222
|
assert order1_found
|
191
223
|
assert order2_found
|
192
224
|
end
|
193
|
-
end
|
194
225
|
|
226
|
+
context "using seed" do
|
227
|
+
setup do
|
228
|
+
@seed = 123
|
229
|
+
end
|
230
|
+
|
231
|
+
should "always return the same order using default method" do
|
232
|
+
seeded_order = Artist.random(2, seed: @seed)
|
233
|
+
10.times do
|
234
|
+
assert_equal seeded_order, Artist.random(2, seed: @seed)
|
235
|
+
end
|
236
|
+
|
237
|
+
10.times do
|
238
|
+
assert_equal seeded_order, Artist.order_by_rand(seed: @seed).limit(2)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
should "always return the same order using shuffle method" do
|
243
|
+
seeded_order = Artist.random_by_id_shuffle(2, seed: @seed)
|
244
|
+
10.times do
|
245
|
+
assert_equal seeded_order, Artist.random_by_id_shuffle(2, seed: @seed)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
195
250
|
end
|
data/test/test_helper.rb
CHANGED
@@ -1,33 +1,29 @@
|
|
1
1
|
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
|
2
2
|
require 'rubygems'
|
3
|
-
require
|
3
|
+
require "minitest/autorun"
|
4
4
|
require 'shoulda'
|
5
|
-
require '
|
5
|
+
require 'factory_bot'
|
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
|
22
16
|
in_memory = config[driver]["database"] == ":memory:"
|
23
|
-
|
17
|
+
|
24
18
|
# http://about.travis-ci.org/docs/user/database-setup/
|
25
19
|
commands = {
|
26
20
|
"mysql" => "mysql -e 'create database randumb_test;'",
|
27
21
|
"postgres" => "psql -c 'create database randumb_test;' -U postgres"
|
28
22
|
}
|
29
23
|
%x{#{commands[driver] || true}}
|
30
|
-
|
24
|
+
|
25
|
+
|
26
|
+
|
31
27
|
ActiveRecord::Base.establish_connection config[driver]
|
32
28
|
puts "Using #{RUBY_VERSION} AR #{version} with #{driver}"
|
33
29
|
|
@@ -38,16 +34,18 @@ ActiveRecord::Base.connection.create_table(:artists, :force => true) do |t|
|
|
38
34
|
t.float "rating"
|
39
35
|
t.datetime "created_at"
|
40
36
|
t.datetime "updated_at"
|
37
|
+
t.datetime "deleted_at"
|
41
38
|
end
|
42
|
-
|
39
|
+
|
43
40
|
ActiveRecord::Base.connection.create_table(:albums, :force => true) do |t|
|
44
41
|
t.string "name"
|
45
42
|
t.integer "views"
|
46
43
|
t.integer "artist_id"
|
47
44
|
t.datetime "created_at"
|
48
45
|
t.datetime "updated_at"
|
46
|
+
t.datetime "deleted_at"
|
49
47
|
end
|
50
|
-
|
48
|
+
|
51
49
|
# setup models for lazy load
|
52
50
|
dep = defined?(ActiveSupport::Dependencies) ? ActiveSupport::Dependencies : ::Dependencies
|
53
51
|
dep.autoload_paths.unshift MODELS_PATH
|
@@ -56,7 +54,7 @@ dep.autoload_paths.unshift MODELS_PATH
|
|
56
54
|
require 'test/models/factories'
|
57
55
|
|
58
56
|
# clear db for every test
|
59
|
-
class Test
|
57
|
+
class Minitest::Test
|
60
58
|
|
61
59
|
def setup
|
62
60
|
Artist.delete_all
|
@@ -65,3 +63,6 @@ class Test::Unit::TestCase
|
|
65
63
|
|
66
64
|
end
|
67
65
|
|
66
|
+
|
67
|
+
# Silence deprications
|
68
|
+
ActiveSupport::Deprecation.silenced = true
|
data/test/weighted_test.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
$:.unshift '.'; require File.dirname(__FILE__) + '/test_helper'
|
2
2
|
|
3
|
-
class WeightedTest < Test
|
3
|
+
class WeightedTest < Minitest::Test
|
4
4
|
|
5
5
|
should "raise exception when called with a non-existent column" do
|
6
6
|
assert_raises(ArgumentError) do
|
7
|
-
Artist.
|
7
|
+
Artist.order_by_rand_weighted(:blah)
|
8
8
|
end
|
9
9
|
assert_raises(ArgumentError) do
|
10
10
|
Artist.random_weighted_by_blah
|
@@ -13,7 +13,7 @@ class WeightedTest < Test::Unit::TestCase
|
|
13
13
|
|
14
14
|
should "raise exception when called with a non-numeric column" do
|
15
15
|
assert_raises(ArgumentError) do
|
16
|
-
Artist.
|
16
|
+
Artist.order_by_rand_weighted(:name)
|
17
17
|
end
|
18
18
|
assert_raises(ArgumentError) do
|
19
19
|
Artist.random_weighted_by_name
|
@@ -25,7 +25,7 @@ class WeightedTest < Test::Unit::TestCase
|
|
25
25
|
if ENV["DB"] == "postgres"
|
26
26
|
should "raise exception if being called with uniq/postgres" do
|
27
27
|
assert_raises(Exception) do
|
28
|
-
Artist.uniq.
|
28
|
+
Artist.uniq.order_by_rand_weighted(:views)
|
29
29
|
end
|
30
30
|
end
|
31
31
|
else
|
@@ -41,7 +41,7 @@ class WeightedTest < Test::Unit::TestCase
|
|
41
41
|
end
|
42
42
|
|
43
43
|
should "not interfere with active record dynamic methods that use method_missing" do
|
44
|
-
@artist =
|
44
|
+
@artist = FactoryBot.create(:artist, :name => 'Spiritualized')
|
45
45
|
assert_equal @artist, Artist.find_by_name('Spiritualized')
|
46
46
|
end
|
47
47
|
|
@@ -59,13 +59,26 @@ class WeightedTest < Test::Unit::TestCase
|
|
59
59
|
context "order by ranking_column" do
|
60
60
|
setup do
|
61
61
|
@view_counts = [1, 2, 3, 4, 5]
|
62
|
-
@view_counts.each { |views|
|
62
|
+
@view_counts.each { |views| FactoryBot.create(:artist, views: views) }
|
63
|
+
end
|
64
|
+
|
65
|
+
should "respect default scope" do
|
66
|
+
artist = Artist.last
|
67
|
+
artist.deleted_at = Time.now.utc
|
68
|
+
artist.save!
|
69
|
+
50.times do
|
70
|
+
assert Artist.random_weighted("views").id != artist.id, "should never pick deleted artist"
|
71
|
+
end
|
72
|
+
assert_equal 4, Artist.order_by_rand_weighted("views").all.length
|
63
73
|
end
|
64
74
|
|
65
75
|
should "order by ranking column with explicit method call" do
|
66
76
|
assert_hits_per_views do
|
67
77
|
Artist.random_weighted("views").views
|
68
78
|
end
|
79
|
+
assert_hits_per_views do
|
80
|
+
Artist.order_by_rand_weighted("views").first.views
|
81
|
+
end
|
69
82
|
end
|
70
83
|
|
71
84
|
should "order by ranking column with method_missing" do
|
@@ -80,6 +93,12 @@ class WeightedTest < Test::Unit::TestCase
|
|
80
93
|
assert(result.size == 5)
|
81
94
|
result.first.views
|
82
95
|
end
|
96
|
+
|
97
|
+
assert_hits_per_views do
|
98
|
+
result = Artist.order_by_rand_weighted("views").limit(5).all
|
99
|
+
assert(result.size == 5)
|
100
|
+
result.first.views
|
101
|
+
end
|
83
102
|
end
|
84
103
|
|
85
104
|
should "order by ranking column with method_missing using max_items" do
|
@@ -98,31 +117,45 @@ class WeightedTest < Test::Unit::TestCase
|
|
98
117
|
result.last.views
|
99
118
|
end
|
100
119
|
end
|
120
|
+
|
121
|
+
assert_raises(MiniTest::Assertion) do
|
122
|
+
assert_hits_per_views do
|
123
|
+
result = Artist.order_by_rand_weighted(:views).limit(3)
|
124
|
+
assert(result.size == 3)
|
125
|
+
result.last.views
|
126
|
+
end
|
127
|
+
end
|
101
128
|
end
|
102
129
|
|
103
130
|
should "order by ranking column with method_missing using 1 max_items" do
|
104
131
|
assert_hits_per_views do
|
105
132
|
result = Artist.random_weighted_by_views(1)
|
106
|
-
|
133
|
+
assert_equal 1, result.length
|
107
134
|
result.first.views
|
108
135
|
end
|
109
|
-
end
|
110
136
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
1000.times do
|
116
|
-
hits_per_views[yield] += 1
|
117
|
-
end
|
118
|
-
last_count = 0
|
119
|
-
@view_counts.each do |views|
|
120
|
-
hits = hits_per_views[views]
|
121
|
-
assert(hits >= last_count, "#{hits} > #{last_count} : There were an unexpected number of visits: #{hits_per_views.to_yaml}")
|
122
|
-
last_count = hits
|
137
|
+
assert_hits_per_views do
|
138
|
+
result = Artist.order_by_rand_weighted(:views).limit(1)
|
139
|
+
assert_equal 1, result.length
|
140
|
+
result.first.views
|
123
141
|
end
|
124
142
|
end
|
125
143
|
end
|
126
144
|
|
145
|
+
def assert_hits_per_views
|
146
|
+
hits_per_views = Hash.new
|
147
|
+
@view_counts.each { |views| hits_per_views[views] = 0 }
|
148
|
+
|
149
|
+
1000.times do
|
150
|
+
hits_per_views[yield] += 1
|
151
|
+
end
|
152
|
+
last_count = 0
|
153
|
+
@view_counts.each do |views|
|
154
|
+
hits = hits_per_views[views]
|
155
|
+
assert(hits >= last_count, "#{hits} > #{last_count} : There were an unexpected number of visits: #{hits_per_views.to_yaml}")
|
156
|
+
last_count = hits
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
127
160
|
|
128
|
-
end
|
161
|
+
end
|
metadata
CHANGED
@@ -1,111 +1,181 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: randumb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zachary Kloepping
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-05-26 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
|
-
version: 3.0.
|
33
|
+
version: 3.0.20
|
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
|
-
version: 3.0.
|
40
|
+
version: 3.0.20
|
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
|
-
version: 3.0.
|
47
|
+
version: 3.0.20
|
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
|
-
version: 3.0.
|
54
|
+
version: 3.0.20
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: sqlite3
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.3.6
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.3.6
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: mysql2
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
73
|
- - '='
|
60
74
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
75
|
+
version: 0.4.10
|
62
76
|
type: :development
|
63
77
|
prerelease: false
|
64
78
|
version_requirements: !ruby/object:Gem::Requirement
|
65
79
|
requirements:
|
66
80
|
- - '='
|
67
81
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
82
|
+
version: 0.4.10
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pg
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.19.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.19.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: bigdecimal
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.4.2
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.4.2
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: minitest
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
69
125
|
- !ruby/object:Gem::Dependency
|
70
126
|
name: shoulda
|
71
127
|
requirement: !ruby/object:Gem::Requirement
|
72
128
|
requirements:
|
73
|
-
- -
|
129
|
+
- - ">="
|
74
130
|
- !ruby/object:Gem::Version
|
75
131
|
version: '0'
|
76
132
|
type: :development
|
77
133
|
prerelease: false
|
78
134
|
version_requirements: !ruby/object:Gem::Requirement
|
79
135
|
requirements:
|
80
|
-
- -
|
136
|
+
- - ">="
|
81
137
|
- !ruby/object:Gem::Version
|
82
138
|
version: '0'
|
83
139
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
140
|
+
name: factory_bot
|
85
141
|
requirement: !ruby/object:Gem::Requirement
|
86
142
|
requirements:
|
87
|
-
- -
|
143
|
+
- - ">="
|
88
144
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
145
|
+
version: '0'
|
90
146
|
type: :development
|
91
147
|
prerelease: false
|
92
148
|
version_requirements: !ruby/object:Gem::Requirement
|
93
149
|
requirements:
|
94
|
-
- -
|
150
|
+
- - ">="
|
95
151
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
152
|
+
version: '0'
|
97
153
|
- !ruby/object:Gem::Dependency
|
98
154
|
name: faker
|
99
155
|
requirement: !ruby/object:Gem::Requirement
|
100
156
|
requirements:
|
101
|
-
- -
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: pry
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
102
172
|
- !ruby/object:Gem::Version
|
103
173
|
version: '0'
|
104
174
|
type: :development
|
105
175
|
prerelease: false
|
106
176
|
version_requirements: !ruby/object:Gem::Requirement
|
107
177
|
requirements:
|
108
|
-
- -
|
178
|
+
- - ">="
|
109
179
|
- !ruby/object:Gem::Version
|
110
180
|
version: '0'
|
111
181
|
description:
|
@@ -114,9 +184,10 @@ executables: []
|
|
114
184
|
extensions: []
|
115
185
|
extra_rdoc_files: []
|
116
186
|
files:
|
187
|
+
- lib/randumb.rb
|
117
188
|
- lib/randumb/relation.rb
|
189
|
+
- lib/randumb/syntax.rb
|
118
190
|
- lib/randumb/version.rb
|
119
|
-
- lib/randumb.rb
|
120
191
|
- test/models/album.rb
|
121
192
|
- test/models/artist.rb
|
122
193
|
- test/models/factories.rb
|
@@ -133,17 +204,16 @@ require_paths:
|
|
133
204
|
- lib
|
134
205
|
required_ruby_version: !ruby/object:Gem::Requirement
|
135
206
|
requirements:
|
136
|
-
- -
|
207
|
+
- - ">="
|
137
208
|
- !ruby/object:Gem::Version
|
138
209
|
version: '0'
|
139
210
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
211
|
requirements:
|
141
|
-
- -
|
212
|
+
- - ">="
|
142
213
|
- !ruby/object:Gem::Version
|
143
214
|
version: '0'
|
144
215
|
requirements: []
|
145
|
-
|
146
|
-
rubygems_version: 2.0.5
|
216
|
+
rubygems_version: 3.0.3
|
147
217
|
signing_key:
|
148
218
|
specification_version: 4
|
149
219
|
summary: Adds the ability to pull random records from ActiveRecord
|