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 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
+ SHA1:
3
+ metadata.gz: fd1f3d0262772918cb40ffc6f8c57bf5e841ea69
4
+ data.tar.gz: 4f05d46fcee8600f14f287718adeb346b7832cf2
5
+ SHA512:
6
+ metadata.gz: 0a713af87ffb2e625ee646dd9308876fbddb9c034d43eca825d6520bef18f8c6255d36771eafba17b770950fa4bdf17905558d4c81f1d5f542b95c12d9f714b8
7
+ data.tar.gz: 1c8bfdab9e93a9bb3241a82115460e5d42c320aa1ac52484d2615314b076e8e7ad81618bd0d4021f83b091ba0aebbad90d110221e5d6b1b291d19e191b4517fb
@@ -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,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 find_all_by_id isn't retained in the result set
62
- records = the_scope.find_all_by_id(ids).shuffle!
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
- [ id_results[ rand(id_results.count) ]['id'] ]
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
@@ -1,3 +1,3 @@
1
1
  module Randumb
2
- VERSION = "0.4.0"
2
+ VERSION = "0.4.1"
3
3
  end
@@ -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 = 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"
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
@@ -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
@@ -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
- 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
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.0
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-07-20 00:00:00.000000000 Z
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.0.5
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