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.
- data/lib/randumb/relation.rb +81 -75
- data/lib/randumb/version.rb +1 -1
- data/test/randumb_test.rb +8 -6
- data/test/weighted_test.rb +11 -8
- metadata +10 -10
data/lib/randumb/relation.rb
CHANGED
@@ -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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
68
|
+
return true
|
28
69
|
end
|
29
70
|
end
|
71
|
+
end
|
30
72
|
|
31
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
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
|
-
#
|
77
|
-
|
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
|
-
|
110
|
-
|
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
|
-
|
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
|
data/lib/randumb/version.rb
CHANGED
data/test/randumb_test.rb
CHANGED
@@ -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
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
data/test/weighted_test.rb
CHANGED
@@ -20,15 +20,18 @@ class WeightedTest < Test::Unit::TestCase
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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.
|
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:
|
13
|
+
date: 2013-01-19 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
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:
|
23
|
+
version: "0"
|
24
24
|
type: :runtime
|
25
25
|
version_requirements: *id001
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
|
-
name:
|
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:
|
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:
|
45
|
+
version: 3.0.0
|
46
46
|
type: :runtime
|
47
47
|
version_requirements: *id003
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
|
-
name:
|
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:
|
56
|
+
version: 1.3.5
|
57
57
|
type: :development
|
58
58
|
version_requirements: *id004
|
59
59
|
- !ruby/object:Gem::Dependency
|