randumb 0.3.0 → 0.3.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.
@@ -4,7 +4,7 @@ require 'active_record/relation'
4
4
  module Randumb
5
5
  # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/query_methods.rb
6
6
  module ActiveRecord
7
-
7
+
8
8
  module Relation
9
9
 
10
10
  # If the max_items argument is omitted, one random entity will be returned.
@@ -17,110 +17,116 @@ module Randumb
17
17
  # by a random number to determine probability of order. The ranking column must be numeric.
18
18
  def random_weighted(ranking_column, max_items = nil)
19
19
  relation = clone
20
+ return random_by_id_shuffle(max_items) if is_randumb_postges_case?(relation, ranking_column)
21
+ raise_unless_valid_ranking_column(ranking_column)
22
+
23
+ order_clause = random_order_clause(ranking_column)
24
+ the_scope = relation.order(order_clause)
25
+
26
+ # override the limit if they are requesting multiple records
27
+ if max_items && (!relation.limit_value || relation.limit_value > max_items)
28
+ the_scope = the_scope.limit(max_items)
29
+ end
30
+
31
+ # return first record if method was called without parameters
32
+ max_items ? the_scope.all : the_scope.first
33
+ end
34
+
35
+
36
+ # This was my first implementation, adding it as an option for people to use
37
+ # and to fall back on for pesky DB one off situations...
38
+ # https://github.com/spilliton/randumb/issues/7
39
+ def random_by_id_shuffle(max_items = nil)
40
+ return_first_record = max_items.nil? # see return switch at end
41
+ max_items ||= 1
42
+ relation = clone
43
+ ids = fetch_random_ids(relation, max_items)
44
+
45
+ # build new scope for final query
46
+ the_scope = klass.includes(includes_values)
47
+
48
+ # specifying empty selects caused bug in rails 3.0.0/3.0.1
49
+ the_scope = the_scope.select(select_values) unless select_values.empty?
50
+
51
+ # get the records and shuffle since the order of the ids
52
+ # passed to find_all_by_id isn't retained in the result set
53
+ records = the_scope.find_all_by_id(ids).shuffle!
54
+
55
+ # return first record if method was called without parameters
56
+ return_first_record ? records.first : records
57
+ end
20
58
 
21
- # postgres won't let you do an order_by when also doing a distinct
22
- # let's just use the in-memory option in this case
23
- if relation.uniq_value && connection.adapter_name =~ /postgres/i
24
- if ranking_column
25
- raise Exception, "random_weighted: not possible when using .uniq and the postgres db adapter"
59
+ private
60
+
61
+ # postgres won't let you do an order_by when also doing a distinct
62
+ # let's just use the in-memory option in this case
63
+ def is_randumb_postges_case?(relation, ranking_column)
64
+ if relation.respond_to?(:uniq_value) && relation.uniq_value && connection.adapter_name =~ /(postgres|postgis)/i
65
+ if ranking_column
66
+ raise Exception, "random_weighted: not possible when using .uniq and the postgres/postgis db adapter"
26
67
  else
27
- return random_by_id_shuffle(max_items)
68
+ return true
28
69
  end
29
70
  end
71
+ end
30
72
 
31
- # ensure a valid column for ranking
73
+ # columns used for ranking must be a numeric type b/c they are multiplied
74
+ def raise_unless_valid_ranking_column(ranking_column)
32
75
  if ranking_column
33
76
  column_data = @klass.columns_hash[ranking_column.to_s]
34
77
  raise ArgumentError.new("random_weighted: #{ranking_column} is not a column on #{@klass.table_name}!") unless column_data
35
78
  raise ArgumentError.new("random_weighted: #{ranking_column} is not a numeric column on #{@klass.table_name}!") unless [:integer, :float].include?(column_data.type)
36
79
  end
80
+ end
37
81
 
38
- # choose the right syntax
39
- if connection.adapter_name =~ /(sqlite|postgres)/i
40
- rand_syntax = "RANDOM()"
82
+ # sligtly different for each DB
83
+ def random_syntax
84
+ if connection.adapter_name =~ /(sqlite|postgres|postgis)/i
85
+ "RANDOM()"
41
86
  elsif connection.adapter_name =~ /mysql/i
42
- rand_syntax = "RAND()"
87
+ "RAND()"
43
88
  else
44
89
  raise Exception, "ActiveRecord adapter: '#{connection.adapter_name}' not supported by randumb. Send a pull request or open a ticket: https://github.com/spilliton/randumb"
45
90
  end
91
+ end
46
92
 
47
- order_clause = if ranking_column.nil?
48
- rand_syntax
49
- else
50
- if connection.adapter_name =~ /sqlite/i
51
- # computer multiplication is faster than division I was once taught...so translate here
52
- max_int = 9223372036854775807.0
53
- multiplier = 1.0 / max_int
54
- "(#{ranking_column} * ABS(#{rand_syntax} * #{multiplier}) ) DESC"
55
- else
56
- "(#{ranking_column} * #{rand_syntax}) DESC"
57
- end
58
- end
59
-
60
- the_scope = relation.order(order_clause)
61
-
62
- # override the limit if they are requesting multiple records
63
- if max_items && (!relation.limit_value || relation.limit_value > max_items)
64
- the_scope = the_scope.limit(max_items)
65
- end
66
-
67
- # return first record if method was called without parameters
68
- if max_items.nil?
69
- the_scope.first
93
+ # builds the order clause to be appended in where clause
94
+ def random_order_clause(ranking_column)
95
+ if ranking_column.nil?
96
+ random_syntax
70
97
  else
71
- the_scope.all
98
+ if connection.adapter_name =~ /sqlite/i
99
+ # computer multiplication is faster than division I was once taught...so translate here
100
+ max_int = 9223372036854775807.0
101
+ multiplier = 1.0 / max_int
102
+ "(#{ranking_column} * ABS(#{random_syntax} * #{multiplier}) ) DESC"
103
+ else
104
+ "(#{ranking_column} * #{random_syntax}) DESC"
105
+ end
72
106
  end
73
107
  end
74
108
 
75
-
76
- # This was my first implementation, adding it as an option for people to use
77
- # and to fall back on for pesky DB one off situations...
78
- # https://github.com/spilliton/randumb/issues/7
79
- def random_by_id_shuffle(max_items = nil)
80
- return_first_record = max_items.nil?# see return switch at end
81
- max_items ||= 1
82
- relation = clone
83
-
84
- # store these for including on final scope
85
- original_includes = relation.includes_values
86
- original_selects = relation.select_values
87
-
109
+ # Returns all matching ids from the db, shuffles them,
110
+ # then returns an array containing at most max_ids
111
+ def fetch_random_ids(relation, max_ids)
88
112
  # clear these for our id only query
89
113
  relation.select_values = []
90
114
  relation.includes_values = []
91
-
115
+
92
116
  # do original query but only for id field
93
117
  id_only_relation = relation.select("#{table_name}.id")
94
118
  id_results = connection.select_all(id_only_relation.to_sql)
95
-
96
- # get requested number of random ids
97
- if max_items == 1 && id_results.length > 0
98
- ids = [ id_results[ rand(id_results.length) ]['id'] ]
99
- else
100
- ids = id_results.shuffle![0,max_items].collect!{ |h| h['id'] }
101
- end
102
-
103
- # build scope for final query
104
- the_scope = klass.includes(original_includes)
105
-
106
- # specifying empty selects caused bug in rails 3.0.0/3.0.1
107
- the_scope = the_scope.select(original_selects) unless original_selects.empty?
108
119
 
109
- # get the records and shuffle since the order of the ids
110
- # passed to find_all_by_id isn't retained in the result set
111
- records = the_scope.find_all_by_id(ids).shuffle!
112
-
113
- # return first record if method was called without parameters
114
- if return_first_record
115
- records.first
120
+ if max_ids == 1 && id_results.length > 0
121
+ ids = [ id_results[ rand(id_results.length) ]['id'] ]
116
122
  else
117
- records
123
+ ids = id_results.shuffle![0,max_ids].collect!{ |h| h['id'] }
118
124
  end
119
125
  end
120
126
 
121
- end
127
+ end
128
+
122
129
 
123
-
124
130
  # Class methods
125
131
  module Base
126
132
  def random(max_items = nil)
@@ -134,7 +140,7 @@ module Randumb
134
140
  def random_by_id_shuffle(max_items = nil)
135
141
  relation.random_by_id_shuffle(max_items)
136
142
  end
137
- end
143
+ end
138
144
 
139
145
 
140
146
  # These get registered as class and instance methods
@@ -157,4 +163,4 @@ module Randumb
157
163
  end
158
164
 
159
165
  end # ActiveRecord
160
- end # Randumb
166
+ end # Randumb
@@ -1,3 +1,3 @@
1
1
  module Randumb
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -139,12 +139,14 @@ class RandumbTest < Test::Unit::TestCase
139
139
  assert_equal true, albums.include?(@sixty_nine_love_songs)
140
140
  end
141
141
 
142
- should "work with uniq" do
143
- assert_equal 2, Artist.uniq.random(2).length
144
- assert_equal 2, Artist.uniq.random_by_id_shuffle(2).length
145
-
146
- assert_not_nil Artist.uniq.random
147
- assert_not_nil Artist.uniq.random_by_id_shuffle
142
+ # ActiveRecord 3.0 does not have this
143
+ if Artist.respond_to?(:uniq)
144
+ should "work with uniq" do
145
+ assert_equal 2, Artist.uniq.random(2).length
146
+ 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
149
+ end
148
150
  end
149
151
 
150
152
  end
@@ -20,15 +20,18 @@ class WeightedTest < Test::Unit::TestCase
20
20
  end
21
21
  end
22
22
 
23
- if ENV["DB"] == "postgres"
24
- should "raise exception if being called with uniq/postgres" do
25
- assert_raises(Exception) do
26
- Artist.uniq.random_weighted(:name)
23
+ # ActiveRecord 3.0 doesnt have uniq scope
24
+ if Artist.respond_to?(:uniq)
25
+ if ENV["DB"] == "postgres"
26
+ should "raise exception if being called with uniq/postgres" do
27
+ assert_raises(Exception) do
28
+ Artist.uniq.random_weighted(:name)
29
+ end
30
+ end
31
+ else
32
+ should "work with uniq if not postgres" do
33
+ assert_nil Artist.uniq.random_weighted_by_views
27
34
  end
28
- end
29
- else
30
- should "work with uniq if not postgres" do
31
- assert_nil Artist.uniq.random_weighted_by_views
32
35
  end
33
36
  end
34
37
 
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: randumb
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.3.0
5
+ version: 0.3.1
6
6
  platform: ruby
7
7
  authors:
8
8
  - Zachary Kloepping
@@ -10,21 +10,21 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2012-07-07 00:00:00 Z
13
+ date: 2013-01-19 00:00:00 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
- name: activesupport
16
+ name: rake
17
17
  prerelease: false
18
18
  requirement: &id001 !ruby/object:Gem::Requirement
19
19
  none: false
20
20
  requirements:
21
21
  - - ">="
22
22
  - !ruby/object:Gem::Version
23
- version: 3.0.0
23
+ version: "0"
24
24
  type: :runtime
25
25
  version_requirements: *id001
26
26
  - !ruby/object:Gem::Dependency
27
- name: activerecord
27
+ name: activesupport
28
28
  prerelease: false
29
29
  requirement: &id002 !ruby/object:Gem::Requirement
30
30
  none: false
@@ -35,25 +35,25 @@ dependencies:
35
35
  type: :runtime
36
36
  version_requirements: *id002
37
37
  - !ruby/object:Gem::Dependency
38
- name: rake
38
+ name: activerecord
39
39
  prerelease: false
40
40
  requirement: &id003 !ruby/object:Gem::Requirement
41
41
  none: false
42
42
  requirements:
43
43
  - - ">="
44
44
  - !ruby/object:Gem::Version
45
- version: "0"
45
+ version: 3.0.0
46
46
  type: :runtime
47
47
  version_requirements: *id003
48
48
  - !ruby/object:Gem::Dependency
49
- name: pg
49
+ name: sqlite3
50
50
  prerelease: false
51
51
  requirement: &id004 !ruby/object:Gem::Requirement
52
52
  none: false
53
53
  requirements:
54
- - - ">="
54
+ - - "="
55
55
  - !ruby/object:Gem::Version
56
- version: "0"
56
+ version: 1.3.5
57
57
  type: :development
58
58
  version_requirements: *id004
59
59
  - !ruby/object:Gem::Dependency