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 CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- OTI0OTBlMWNhNTc3MWQxYzU5MDM0YzAwNmM5NWI3OTFmNzhlYzA0Nw==
5
- data.tar.gz: !binary |-
6
- YzNlMmUzYTA3ZDRmZDI4ZmE5YTIyMzFiMGNlMTA2ZDNiMjRjZDFjNQ==
7
- !binary "U0hBNTEy":
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
@@ -1,4 +1,5 @@
1
1
  require 'randumb/version'
2
+ require 'randumb/syntax'
2
3
  require 'randumb/relation'
3
4
 
4
5
  # Mix it in
@@ -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
- random_weighted(nil, max_items)
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
- the_scope = if ::ActiveRecord::VERSION::MAJOR == 3 && ::ActiveRecord::VERSION::MINOR < 2
26
- # AR 3.0.0 support
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
- if max_items && (!relation.limit_value || relation.limit_value > max_items)
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 ? the_scope.to_a : the_scope.first
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 find_all_by_id isn't retained in the result set
62
- records = the_scope.find_all_by_id(ids).shuffle!
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, "random_weighted: not possible when using .uniq and the postgres/postgis db adapter"
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
- [ id_results[ rand(id_results.count) ]['id'] ]
135
+ rand_index = rng.rand(id_results.count)
136
+ [id_results[rand_index]["id"]]
132
137
  else
133
- # ActiveRecord 4 requires .to_ary
134
- arr = id_results.respond_to?(:to_ary) ? id_results.to_ary : id_results
135
- arr.shuffle![0,max_ids].collect!{ |h| h['id'] }
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
- relation.random(max_items)
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 random_weighted(ranking_column, max_items = nil)
149
- relation.random_weighted(ranking_column, max_items)
175
+ def order_by_rand(opts = {})
176
+ where(nil).order_by_rand(opts)
150
177
  end
151
178
 
152
- def random_by_id_shuffle(max_items = nil)
153
- relation.random_by_id_shuffle(max_items)
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
@@ -1,3 +1,3 @@
1
1
  module Randumb
2
- VERSION = "0.4.0"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -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
@@ -1,4 +1,4 @@
1
- FactoryGirl.define do
1
+ FactoryBot.define do
2
2
  factory :artist do
3
3
  name { Faker::Lorem.words(3).join(' ') }
4
4
  views { Random.rand(50) }
@@ -1,13 +1,17 @@
1
1
  $:.unshift '.'; require File.dirname(__FILE__) + '/test_helper'
2
2
 
3
- class RandumbTest < Test::Unit::TestCase
4
-
5
- def assert_equal_for_both_methods(expected, obj, params = nil)
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"
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 = FactoryGirl.create(:artist, :name => "High On Fire", :views => 1)
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 = FactoryGirl.create(:artist, :name => "Fiona Apple", :views => 3)
47
- @magnetic_fields = FactoryGirl.create(:artist, :name => "The Magnetic Fields", :views => 2)
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
- assert_raise (ActiveModel::MissingAttributeError) {artists.first.views}
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
- assert_raise (ActiveModel::MissingAttributeError) {artists.first.views}
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 = FactoryGirl.create(:album, :name => "Tidal", :artist => @fiona_apple)
96
- @extraordinary_machine = FactoryGirl.create(:album, :name => "Extraordinary Machine", :artist => @fiona_apple)
97
- @sixty_nine_love_songs = FactoryGirl.create(:album, :name => "69 Love Songs", :artist => @magnetic_fields)
98
- @snakes_for_the_divine = FactoryGirl.create(:album, :name => "Snakes For the Divine", :artist => @high_on_fire)
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
- FactoryGirl.create(:album, :name => "When The Pawn", :artist => @fiona_apple)
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
- FactoryGirl.create(:album, :name => "When The Pawn", :artist => @fiona_apple)
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
- assert_not_nil Artist.uniq.random
148
- assert_not_nil Artist.uniq.random_by_id_shuffle
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 = FactoryGirl.create(:artist, :name => "Hum", :views => 3)
161
- @minutemen = FactoryGirl.create(:artist, :name => "Minutemen", :views => 2)
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
@@ -1,33 +1,29 @@
1
1
  $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
2
2
  require 'rubygems'
3
- require 'test/unit'
3
+ require "minitest/autorun"
4
4
  require 'shoulda'
5
- require 'factory_girl'
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::Unit::TestCase
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
@@ -1,10 +1,10 @@
1
1
  $:.unshift '.'; require File.dirname(__FILE__) + '/test_helper'
2
2
 
3
- class WeightedTest < Test::Unit::TestCase
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.random_weighted(:blah)
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.random_weighted(:name)
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.random_weighted(:name)
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 = FactoryGirl.create(:artist, :name => 'Spiritualized')
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| FactoryGirl.create(:artist, :views => 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
- assert(result.size == 1)
133
+ assert_equal 1, result.length
107
134
  result.first.views
108
135
  end
109
- end
110
136
 
111
- def assert_hits_per_views
112
- hits_per_views = Hash.new
113
- @view_counts.each { |views| hits_per_views[views] = 0 }
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.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: 2013-07-20 00:00:00.000000000 Z
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.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.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.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.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: 1.3.7
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: 1.3.7
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: factory_girl
140
+ name: factory_bot
85
141
  requirement: !ruby/object:Gem::Requirement
86
142
  requirements:
87
- - - ~>
143
+ - - ">="
88
144
  - !ruby/object:Gem::Version
89
- version: '3.0'
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: '3.0'
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
- rubyforge_project:
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