randumb 0.3.0 → 0.3.1

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