randumb 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml 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