cohort_scope 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +13 -0
- data/Gemfile +5 -0
- data/README.rdoc +9 -13
- data/cohort_scope.gemspec +1 -5
- data/lib/cohort_scope.rb +12 -38
- data/lib/cohort_scope/active_record_base_class_methods.rb +11 -0
- data/lib/cohort_scope/active_record_relation_instance_methods.rb +30 -0
- data/lib/cohort_scope/arel_visitors_visitor_instance_methods.rb +7 -0
- data/lib/cohort_scope/big_cohort.rb +10 -7
- data/lib/cohort_scope/cohort.rb +18 -93
- data/lib/cohort_scope/strict_cohort.rb +3 -3
- data/lib/cohort_scope/version.rb +1 -1
- data/test/helper.rb +4 -0
- data/test/test_big_cohort.rb +8 -8
- data/test/test_cohort_scope.rb +10 -89
- data/test/test_strict_cohort.rb +21 -5
- metadata +13 -42
data/CHANGELOG
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
0.3.0 / 2012-02-27
|
2
|
+
|
3
|
+
* Now my_cohort = Person.cohort({:favorite_color => 'heliotrope', :birthdate => @date_range}, :importance => [:birthdate, :favorite_color]) will return a Arel::Nodes::Node which can be combined like Person.where(my_cohort.and("gender = 'male")) - it does NOT return a "scope" like before.
|
4
|
+
|
5
|
+
* Refactor to take advantage of ARel.
|
6
|
+
|
7
|
+
0.2.0
|
8
|
+
|
9
|
+
* No longer "flattens" or "sanitizes" characteristics by turning records into integer IDs, etc. You should pass in exactly what you would pass into a normal ActiveRecord relation/scope.
|
10
|
+
|
11
|
+
0.1.0
|
12
|
+
|
13
|
+
* First version!
|
data/Gemfile
CHANGED
data/README.rdoc
CHANGED
@@ -2,16 +2,12 @@
|
|
2
2
|
|
3
3
|
Provides cohorts (in the form of ActiveRecord scopes) that dynamically widen until they contain a certain number of records.
|
4
4
|
|
5
|
-
* <tt>big_cohort</tt> widens by successively removing what it finds to be the most restrictive
|
6
|
-
* <tt>strict_cohort</tt> widens by eliminating
|
7
|
-
|
8
|
-
== Changes 0.1.x vs. 0.2.x
|
9
|
-
|
10
|
-
No longer "flattens" or "sanitizes" constraints by turning records into integer IDs, etc. You should pass in exactly what you would pass into a normal ActiveRecord relation/scope.
|
5
|
+
* <tt>big_cohort</tt> widens by successively removing what it finds to be the most restrictive characteristic until it reaches the minimum number of records
|
6
|
+
* <tt>strict_cohort</tt> widens by eliminating characteristics in order until it reaches the minimum number of records
|
11
7
|
|
12
8
|
== Real-world use
|
13
9
|
|
14
|
-
This has been at use at http://
|
10
|
+
This has been at use at http://impact.brighterplanet.com since April 2010, where it helps sift through climate data to come up with meaningful emissions calculations.
|
15
11
|
|
16
12
|
== Quick start
|
17
13
|
|
@@ -28,13 +24,13 @@ Now I need to run a calculation that ideally uses birthday and favorite color, b
|
|
28
24
|
# => [... a cohort of at least 1,000 records (otherwise it's empty),
|
29
25
|
where everybody's favorite color MAY be heliotrope
|
30
26
|
and everybody's birthday MAY be between 1980 and 1990
|
31
|
-
(at least one of those
|
27
|
+
(at least one of those characteristics will hold) ...]
|
32
28
|
|
33
|
-
What if my calculation privileges favorite color? In other words, if you can't give me a cohort of minimum size within the birthday
|
29
|
+
What if my calculation privileges favorite color? In other words, if you can't give me a cohort of minimum size within the birthday characteristic, at least give me one where everybody loves heliotrope:
|
34
30
|
|
35
|
-
|
36
|
-
|
37
|
-
|
31
|
+
ordered_characteristics = []
|
32
|
+
ordered_characteristics << [:favorite_color, 'heliotrope']
|
33
|
+
ordered_characteristics << [:birthdate, (Date.parse('1980-01-01')..Date.parse('1990-01-01'))]
|
38
34
|
|
39
35
|
Citizen.strict_cohort *favorite_color_matters_most
|
40
36
|
# => [... a cohort of at least 1,000 records (otherwise it's empty),
|
@@ -45,4 +41,4 @@ What if my calculation privileges favorite color? In other words, if you can't g
|
|
45
41
|
|
46
42
|
== Copyright
|
47
43
|
|
48
|
-
Copyright (c)
|
44
|
+
Copyright (c) 2012 Brighter Planet, Inc. See LICENSE for details.
|
data/cohort_scope.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.email = ["seamus@abshere.net"]
|
11
11
|
s.homepage = "https://github.com/seamusabshere/cohort_scope"
|
12
12
|
s.summary = %Q{Provides cohorts (in the form of ActiveRecord scopes) that dynamically widen until they contain a certain number of records.}
|
13
|
-
s.description = %Q{Provides big_cohort, which widens by finding the
|
13
|
+
s.description = %Q{Provides big_cohort, which widens by finding the characteristic that eliminates the most records and removing it. Also provides strict_cohort, which widens by eliminating characteristics in order.}
|
14
14
|
|
15
15
|
s.rubyforge_project = "cohort_scope"
|
16
16
|
|
@@ -21,8 +21,4 @@ Gem::Specification.new do |s|
|
|
21
21
|
|
22
22
|
s.add_runtime_dependency "activesupport", '>=3'
|
23
23
|
s.add_runtime_dependency "activerecord", '>=3'
|
24
|
-
s.add_development_dependency 'test-unit'
|
25
|
-
s.add_development_dependency 'mysql2'
|
26
|
-
s.add_development_dependency 'rake'
|
27
|
-
# s.add_development_dependency 'ruby-debug'
|
28
24
|
end
|
data/lib/cohort_scope.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'arel'
|
1
2
|
require 'active_record'
|
2
3
|
require 'active_support/core_ext'
|
3
4
|
|
@@ -5,6 +6,10 @@ require 'cohort_scope/cohort'
|
|
5
6
|
require 'cohort_scope/big_cohort'
|
6
7
|
require 'cohort_scope/strict_cohort'
|
7
8
|
|
9
|
+
require 'cohort_scope/active_record_base_class_methods'
|
10
|
+
require 'cohort_scope/active_record_relation_instance_methods'
|
11
|
+
require 'cohort_scope/arel_visitors_visitor_instance_methods'
|
12
|
+
|
8
13
|
module CohortScope
|
9
14
|
def self.extended(klass)
|
10
15
|
klass.class_eval do
|
@@ -14,47 +19,16 @@ module CohortScope
|
|
14
19
|
end
|
15
20
|
end
|
16
21
|
|
17
|
-
def self.conditions_for(
|
18
|
-
case
|
22
|
+
def self.conditions_for(characteristics)
|
23
|
+
case characteristics
|
19
24
|
when ::Array
|
20
|
-
|
25
|
+
characteristics.inject({}) { |memo, (k, v)| memo[k] = v; memo }
|
21
26
|
when ::Hash
|
22
|
-
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# Find the biggest scope possible by removing constraints <b>in any order</b>.
|
27
|
-
# Returns an empty scope if it can't meet the minimum scope size.
|
28
|
-
def big_cohort(constraints, options = {})
|
29
|
-
BigCohort.create self, constraints, (options[:minimum_cohort_size] || minimum_cohort_size)
|
30
|
-
end
|
31
|
-
|
32
|
-
# Find the first acceptable scope by removing constraints <b>in strict order</b>, starting with the last constraint.
|
33
|
-
# Returns an empty scope if it can't meet the minimum scope size.
|
34
|
-
#
|
35
|
-
# <tt>constraints</tt> must be key/value pairs (splat if it's an array)
|
36
|
-
#
|
37
|
-
# Note that the first constraint is implicitly required.
|
38
|
-
#
|
39
|
-
# Take this example, where favorite color is considered to be "more important" than birthdate:
|
40
|
-
#
|
41
|
-
# ordered_constraints = [ [:favorite_color, 'heliotrope'], [:birthdate, '1999-01-01'] ]
|
42
|
-
# Citizen.strict_cohort(*ordered_constraints) #=> [...]
|
43
|
-
#
|
44
|
-
# If the original constraints don't meet the minimum scope size, then the only constraint that can be removed is birthdate.
|
45
|
-
# In other words, this would never return a scope that was constrained on birthdate but not on favorite_color.
|
46
|
-
def strict_cohort(*args)
|
47
|
-
args = args.dup
|
48
|
-
options = args.last.is_a?(::Hash) ? args.pop : {}
|
49
|
-
constraints = args
|
50
|
-
StrictCohort.create self, constraints, (options[:minimum_cohort_size] || minimum_cohort_size)
|
51
|
-
end
|
52
|
-
|
53
|
-
module ActiveRecordRelationExt
|
54
|
-
def to_cohort
|
55
|
-
Cohort.new self
|
27
|
+
characteristics
|
56
28
|
end
|
57
29
|
end
|
58
30
|
end
|
59
31
|
|
60
|
-
|
32
|
+
ActiveRecord::Base.extend CohortScope::ActiveRecordBaseClassMethods
|
33
|
+
ActiveRecord::Relation.send :include, CohortScope::ActiveRecordRelationInstanceMethods
|
34
|
+
Arel::Visitors::Visitor.send :include, CohortScope::ArelVisitorsVisitorInstanceMethods
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module CohortScope
|
2
|
+
module ActiveRecordRelationInstanceMethods
|
3
|
+
# Find the biggest scope possible by removing characteristics <b>in any order</b>.
|
4
|
+
# Returns an empty scope if it can't meet the minimum scope size.
|
5
|
+
def big_cohort(characteristics, options = {})
|
6
|
+
BigCohort.new self, characteristics, (options[:minimum_cohort_size] || (klass.respond_to?(:minimum_cohort_size) ? klass.minimum_cohort_size : nil))
|
7
|
+
end
|
8
|
+
|
9
|
+
# Find the first acceptable scope by removing characteristics <b>in strict order</b>, starting with the last characteristic.
|
10
|
+
# Returns an empty scope if it can't meet the minimum scope size.
|
11
|
+
#
|
12
|
+
# <tt>characteristics</tt> must be key/value pairs (splat if it's an array)
|
13
|
+
#
|
14
|
+
# Note that the first characteristic is implicitly required.
|
15
|
+
#
|
16
|
+
# Take this example, where favorite color is considered to be "more important" than birthdate:
|
17
|
+
#
|
18
|
+
# ordered_characteristics = [ [:favorite_color, 'heliotrope'], [:birthdate, '1999-01-01'] ]
|
19
|
+
# Citizen.strict_cohort(*ordered_characteristics) #=> [...]
|
20
|
+
#
|
21
|
+
# If the original characteristics don't meet the minimum scope size, then the only characteristic that can be removed is birthdate.
|
22
|
+
# In other words, this would never return a scope that was constrained on birthdate but not on favorite_color.
|
23
|
+
def strict_cohort(*args)
|
24
|
+
args = args.dup
|
25
|
+
options = args.last.is_a?(::Hash) ? args.pop : {}
|
26
|
+
characteristics = args
|
27
|
+
StrictCohort.new self, characteristics, (options[:minimum_cohort_size] || (klass.respond_to?(:minimum_cohort_size) ? klass.minimum_cohort_size : nil))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -1,14 +1,17 @@
|
|
1
1
|
module CohortScope
|
2
2
|
class BigCohort < Cohort
|
3
|
-
# Reduce
|
3
|
+
# Reduce characteristics by removing them one by one and counting the results.
|
4
4
|
#
|
5
|
-
# The
|
6
|
-
def self.
|
7
|
-
|
8
|
-
|
9
|
-
active_record.scoped.where(conditions).count
|
5
|
+
# The characteristic whose removal leads to the highest record count is removed from the overall characteristic set.
|
6
|
+
def self.reduce_characteristics(active_record, characteristics)
|
7
|
+
if characteristics.keys.length < 2
|
8
|
+
return {}
|
10
9
|
end
|
11
|
-
|
10
|
+
most_restrictive_characteristic = characteristics.keys.max_by do |key|
|
11
|
+
conditions = CohortScope.conditions_for characteristics.except(key)
|
12
|
+
active_record.where(conditions).count
|
13
|
+
end
|
14
|
+
characteristics.except most_restrictive_characteristic
|
12
15
|
end
|
13
16
|
end
|
14
17
|
end
|
data/lib/cohort_scope/cohort.rb
CHANGED
@@ -1,105 +1,30 @@
|
|
1
|
-
require 'delegate'
|
2
|
-
|
3
1
|
module CohortScope
|
4
|
-
class Cohort < ::
|
5
|
-
|
6
|
-
# Recursively look for a scope that meets the constraints and is at least <tt>minimum_cohort_size</tt>.
|
7
|
-
def create(active_record, constraints, minimum_cohort_size)
|
8
|
-
if constraints.none? # failing base case
|
9
|
-
cohort = new active_record.scoped.where(IMPOSSIBLE_CONDITION)
|
10
|
-
cohort.count = 0
|
11
|
-
return cohort
|
12
|
-
end
|
13
|
-
|
14
|
-
constrained_scope = active_record.scoped.where CohortScope.conditions_for(constraints)
|
2
|
+
class Cohort < ::Arel::Nodes::Node
|
3
|
+
IMPOSSIBLE = '1 = 2'
|
15
4
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
else
|
21
|
-
create active_record, reduce_constraints(active_record, constraints), minimum_cohort_size
|
22
|
-
end
|
23
|
-
end
|
5
|
+
def initialize(active_record, characteristics, minimum_cohort_size)
|
6
|
+
@active_record = active_record
|
7
|
+
@characteristics = characteristics
|
8
|
+
@minimum_cohort_size = minimum_cohort_size
|
24
9
|
end
|
25
|
-
|
26
|
-
IMPOSSIBLE_CONDITION = ::Arel::Nodes::Equality.new(1,2)
|
27
10
|
|
28
|
-
def
|
29
|
-
|
30
|
-
@_ch_obj = obj
|
31
|
-
end
|
32
|
-
def __getobj__
|
33
|
-
@_ch_obj
|
34
|
-
end
|
35
|
-
def __setobj__(obj)
|
36
|
-
@_ch_obj = obj
|
11
|
+
def expr
|
12
|
+
@expr ||= resolve
|
37
13
|
end
|
14
|
+
alias :to_sql :expr
|
38
15
|
|
39
|
-
|
40
|
-
@count = int
|
41
|
-
end
|
42
|
-
|
43
|
-
def count
|
44
|
-
@count ||= super
|
45
|
-
end
|
16
|
+
private
|
46
17
|
|
47
|
-
#
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
{ :members => count }
|
54
|
-
end
|
55
|
-
|
56
|
-
def empty?
|
57
|
-
count == 0
|
58
|
-
end
|
59
|
-
|
60
|
-
def any?
|
61
|
-
return false if count == 0
|
62
|
-
return true if !block_given? and count > 0
|
63
|
-
super
|
64
|
-
end
|
65
|
-
|
66
|
-
def none?(&blk)
|
67
|
-
return true if count == 0
|
68
|
-
return false if !block_given? and count > 0
|
69
|
-
if block_given?
|
70
|
-
# sabshere 2/1/11 ActiveRecord does this for #any? but not for #none?
|
71
|
-
to_a.none? &blk
|
18
|
+
# Recursively look for a scope that meets the characteristics and is at least <tt>minimum_cohort_size</tt>.
|
19
|
+
def resolve
|
20
|
+
if @characteristics.empty?
|
21
|
+
IMPOSSIBLE
|
22
|
+
elsif (current = @active_record.where(CohortScope.conditions_for(@characteristics))).count >= @minimum_cohort_size
|
23
|
+
current.constraints.inject(:and).to_sql
|
72
24
|
else
|
73
|
-
|
25
|
+
@characteristics = self.class.reduce_characteristics(@active_record, @characteristics)
|
26
|
+
resolve
|
74
27
|
end
|
75
28
|
end
|
76
|
-
|
77
|
-
def where_value_nodes
|
78
|
-
__getobj__.instance_variable_get(:@where_values)
|
79
|
-
end
|
80
|
-
|
81
|
-
def active_record
|
82
|
-
__getobj__.klass
|
83
|
-
end
|
84
|
-
|
85
|
-
def +(other)
|
86
|
-
case other
|
87
|
-
when Cohort
|
88
|
-
combined_conditions = (where_value_nodes + other.where_value_nodes).inject(nil) do |memo, node|
|
89
|
-
if memo.nil?
|
90
|
-
node
|
91
|
-
else
|
92
|
-
memo.or(node)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
Cohort.new active_record.where(combined_conditions)
|
96
|
-
else
|
97
|
-
super
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def inspect
|
102
|
-
"#<#{self.class.name} with #{count} members>"
|
103
|
-
end
|
104
29
|
end
|
105
30
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module CohortScope
|
2
2
|
class StrictCohort < Cohort
|
3
|
-
# Reduce
|
4
|
-
def self.
|
5
|
-
|
3
|
+
# Reduce characteristics by removing the least important one.
|
4
|
+
def self.reduce_characteristics(active_record, characteristics)
|
5
|
+
characteristics[0..-2]
|
6
6
|
end
|
7
7
|
end
|
8
8
|
end
|
data/lib/cohort_scope/version.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -6,6 +6,10 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
6
6
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
7
|
require 'cohort_scope'
|
8
8
|
|
9
|
+
if ::Bundler.definition.specs['ruby-debug19'].first or ::Bundler.definition.specs['ruby-debug'].first
|
10
|
+
require 'ruby-debug'
|
11
|
+
end
|
12
|
+
|
9
13
|
class Test::Unit::TestCase
|
10
14
|
end
|
11
15
|
|
data/test/test_big_cohort.rb
CHANGED
@@ -8,24 +8,24 @@ class TestBigCohort < Test::Unit::TestCase
|
|
8
8
|
|
9
9
|
def test_001_empty
|
10
10
|
cohort = Citizen.big_cohort :favorite_color => 'heliotrope'
|
11
|
-
assert_equal 0, cohort.count
|
11
|
+
assert_equal 0, Citizen.where(cohort).count
|
12
12
|
end
|
13
13
|
|
14
14
|
def test_002_optional_minimum_cohort_size_at_runtime
|
15
15
|
cohort = Citizen.big_cohort({:favorite_color => 'heliotrope'}, :minimum_cohort_size => 0)
|
16
|
-
assert_equal 1, cohort.count
|
16
|
+
assert_equal 1, Citizen.where(cohort).count
|
17
17
|
end
|
18
18
|
|
19
19
|
def test_003_seek_cohort_of_maximum_size
|
20
20
|
cohort = Citizen.big_cohort :birthdate => @date_range, :favorite_color => 'heliotrope'
|
21
|
-
assert_equal 9, cohort.count
|
22
|
-
assert cohort.any? { |m| m.favorite_color != 'heliotrope' }
|
23
|
-
assert cohort.all? { |m| @date_range.include? m.birthdate }
|
21
|
+
assert_equal 9, Citizen.where(cohort).count
|
22
|
+
assert Citizen.where(cohort).any? { |m| m.favorite_color != 'heliotrope' }
|
23
|
+
assert Citizen.where(cohort).all? { |m| @date_range.include? m.birthdate }
|
24
24
|
end
|
25
25
|
|
26
26
|
def test_004_unsurprising_treatment_of_arrays
|
27
|
-
assert_equal 3, Citizen.big_cohort({:favorite_color => 'blue'}, :minimum_cohort_size => 0).count
|
28
|
-
assert_equal 1, Citizen.big_cohort({:favorite_color => 'heliotrope'}, :minimum_cohort_size => 0).count
|
29
|
-
assert_equal 4, Citizen.big_cohort({:favorite_color => ['heliotrope', 'blue']}, :minimum_cohort_size => 0).count
|
27
|
+
assert_equal 3, Citizen.where(Citizen.big_cohort({:favorite_color => 'blue'}, :minimum_cohort_size => 0)).count
|
28
|
+
assert_equal 1, Citizen.where(Citizen.big_cohort({:favorite_color => 'heliotrope'}, :minimum_cohort_size => 0)).count
|
29
|
+
assert_equal 4, Citizen.where(Citizen.big_cohort({:favorite_color => ['heliotrope', 'blue']}, :minimum_cohort_size => 0)).count
|
30
30
|
end
|
31
31
|
end
|
data/test/test_cohort_scope.rb
CHANGED
@@ -18,103 +18,24 @@ class TestCohortScope < Test::Unit::TestCase
|
|
18
18
|
assert Style.joins(:houses).where(:houses => { :id => [house1] }).first
|
19
19
|
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
assert_equal
|
25
|
-
# these return 2, which is too small
|
26
|
-
assert_equal 0, Style.joins(:houses).big_cohort(:houses => { :id => [house3]}).length
|
27
|
-
assert_equal 0, Style.joins(:houses).big_cohort(:houses => { :id => [house3]}, :name => 'classical revival').length
|
28
|
-
end
|
29
|
-
|
30
|
-
# should this even work in theory?
|
31
|
-
# def test_002b_simplified_joins
|
32
|
-
# assert_equal 3, Style.big_cohort(:houses => [house1]).length
|
33
|
-
# end
|
34
|
-
|
35
|
-
def test_003_redefine_any_query_method
|
36
|
-
cohort = Citizen.big_cohort(:birthdate => @date_range)
|
37
|
-
assert cohort.all? { |c| true }
|
38
|
-
assert cohort.any? { |c| true }
|
39
|
-
assert !cohort.none? { |c| true }
|
40
|
-
end
|
41
|
-
|
42
|
-
def test_004_really_run_blocks
|
43
|
-
assert_raises(RuntimeError, 'A') do
|
44
|
-
Citizen.big_cohort(:birthdate => @date_range).all? { |c| raise 'A' }
|
45
|
-
end
|
46
|
-
assert_raises(RuntimeError, 'B') do
|
47
|
-
Citizen.big_cohort(:birthdate => @date_range).any? { |c| raise 'B' }
|
48
|
-
end
|
49
|
-
assert_raises(RuntimeError, 'C') do
|
50
|
-
Citizen.big_cohort(:birthdate => @date_range).none? { |c| raise 'C' }
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def test_005_short_to_json
|
55
|
-
cohort = Citizen.big_cohort :birthdate => @date_range, :favorite_color => 'heliotrope'
|
56
|
-
assert_equal({ :members => 9 }.to_json, cohort.to_json)
|
57
|
-
end
|
58
|
-
|
59
|
-
def test_006_doesnt_mess_with_active_record_json
|
60
|
-
non_cohort = Citizen.all
|
61
|
-
assert_equal non_cohort.to_a.as_json, non_cohort.as_json
|
62
|
-
end
|
63
|
-
|
64
|
-
def test_007_doesnt_mess_with_active_record_inspect
|
65
|
-
non_cohort = Citizen.all
|
66
|
-
assert_equal non_cohort.to_a.inspect, non_cohort.inspect
|
67
|
-
end
|
68
|
-
|
69
|
-
def test_008_short_inspect
|
70
|
-
cohort = Citizen.big_cohort :birthdate => @date_range, :favorite_color => 'heliotrope'
|
71
|
-
assert_equal "#<CohortScope::BigCohort with 9 members>", cohort.inspect
|
72
|
-
end
|
73
|
-
|
74
|
-
def test_009_not_reveal_itself_in_to_hash
|
75
|
-
cohort = Citizen.big_cohort :birthdate => @date_range, :favorite_color => 'heliotrope'
|
76
|
-
assert_equal '{"c":{"members":9}}', { :c => cohort }.to_hash.to_json
|
77
|
-
end
|
78
|
-
|
79
|
-
def test_010_work_as_delegator
|
80
|
-
cohort = Citizen.big_cohort :birthdate => @date_range, :favorite_color => 'heliotrope'
|
81
|
-
assert_kind_of Citizen, cohort.last
|
82
|
-
assert_kind_of Citizen, cohort.where(:teeth => 31).first
|
21
|
+
def test_002_to_sql
|
22
|
+
assert %r{BETWEEN.*#{@date_range.first}.*#{@date_range.last}}.match(Citizen.big_cohort(:birthdate => @date_range).to_sql)
|
23
|
+
assert %r{.citizens...favorite_color. = 'heliotrope'}.match(Citizen.big_cohort({:favorite_color => 'heliotrope'}, :minimum_cohort_size => 1).to_sql)
|
24
|
+
assert_equal %{1 = 2}, Citizen.big_cohort(:favorite_color => 'osijdfosidfj').to_sql
|
83
25
|
end
|
84
26
|
|
85
27
|
def test_011_combine_scopes_with_or
|
86
28
|
nobody = Citizen.big_cohort({:favorite_color => 'oaisdjaoisjd'}, :minimum_cohort_size => 1)
|
87
|
-
assert_equal 0, nobody.count
|
29
|
+
assert_equal 0, Citizen.where(nobody).count
|
88
30
|
people_who_love_heliotrope_are_from_the_fifties = Citizen.big_cohort({:favorite_color => 'heliotrope'}, :minimum_cohort_size => 1)
|
89
|
-
assert_equal 1, people_who_love_heliotrope_are_from_the_fifties.count
|
90
|
-
assert people_who_love_heliotrope_are_from_the_fifties.none? { |c| @date_range.include? c.birthdate }
|
31
|
+
assert_equal 1, Citizen.where(people_who_love_heliotrope_are_from_the_fifties).count
|
32
|
+
assert Citizen.where(people_who_love_heliotrope_are_from_the_fifties).none? { |c| @date_range.include? c.birthdate }
|
91
33
|
their_children_are_born_in_the_eighties = Citizen.big_cohort({:birthdate => @date_range}, :minimum_cohort_size => 1)
|
92
|
-
assert_equal 9, their_children_are_born_in_the_eighties.count
|
93
|
-
everybody = (
|
94
|
-
|
95
|
-
assert_equal 10, everybody.count
|
34
|
+
assert_equal 9, Citizen.where(their_children_are_born_in_the_eighties).count
|
35
|
+
everybody = people_who_love_heliotrope_are_from_the_fifties.or(their_children_are_born_in_the_eighties).or(nobody)
|
36
|
+
assert_equal 10, Citizen.where(everybody).count
|
96
37
|
end
|
97
|
-
|
98
|
-
def test_012_to_cohort
|
99
|
-
relation = Citizen.big_cohort({:favorite_color => 'heliotrope'}, :minimum_cohort_size => 1).where(:birthdate => @date_range)
|
100
|
-
assert_equal [], relation.as_json
|
101
|
-
assert_equal({ :members => 0 }, relation.to_cohort.as_json)
|
102
|
-
end
|
103
|
-
|
104
|
-
def test_013_count_is_forced
|
105
|
-
cohort = Citizen.big_cohort(:birthdate => @date_range)
|
106
|
-
|
107
|
-
cohort.count = 0
|
108
|
-
assert !cohort.any?
|
109
|
-
assert cohort.none?
|
110
|
-
assert cohort.empty?
|
111
38
|
|
112
|
-
cohort.count = 1
|
113
|
-
assert cohort.any?
|
114
|
-
assert !cohort.none?
|
115
|
-
assert !cohort.empty?
|
116
|
-
end
|
117
|
-
|
118
39
|
private
|
119
40
|
|
120
41
|
def style
|
data/test/test_strict_cohort.rb
CHANGED
@@ -8,22 +8,38 @@ class TestStrictCohort < Test::Unit::TestCase
|
|
8
8
|
|
9
9
|
def test_001_empty
|
10
10
|
cohort = Citizen.strict_cohort
|
11
|
-
assert_equal 0, cohort.count
|
11
|
+
assert_equal 0, Citizen.where(cohort).count
|
12
12
|
end
|
13
13
|
|
14
14
|
def test_002_optional_minimum_cohort_size_at_runtime
|
15
15
|
cohort = Citizen.strict_cohort [:favorite_color, 'heliotrope'], :minimum_cohort_size => 0
|
16
|
-
assert_equal 1, cohort.count
|
16
|
+
assert_equal 1, Citizen.where(cohort).count
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
19
|
+
def test_003_seek_cohort_by_discarding_characteristics_in_order
|
20
20
|
favorite_color_matters_most = [ [:favorite_color, 'heliotrope'], [:birthdate, @date_range] ]
|
21
21
|
birthdate_matters_most = [ [:birthdate, @date_range], [:favorite_color, 'heliotrope'] ]
|
22
22
|
|
23
23
|
cohort = Citizen.strict_cohort *favorite_color_matters_most
|
24
|
-
assert_equal 0, cohort.count
|
24
|
+
assert_equal 0, Citizen.where(cohort).count
|
25
25
|
|
26
26
|
cohort = Citizen.strict_cohort *birthdate_matters_most
|
27
|
-
assert_equal 9, cohort.count
|
27
|
+
assert_equal 9, Citizen.where(cohort).count
|
28
28
|
end
|
29
|
+
|
30
|
+
# TODO: undo the annoying splat thing
|
31
|
+
# characteristics = (args.first.is_a?(::Array) and not args.first.first.is_a?(::Symbol)) ? args.first : args
|
32
|
+
# def test_004_accepts_non_splat_array
|
33
|
+
# cohort = Citizen.strict_cohort [[:favorite_color, 'heliotrope']], :minimum_cohort_size => 0
|
34
|
+
# assert_equal 1, Citizen.where(cohort).count
|
35
|
+
#
|
36
|
+
# favorite_color_matters_most = [ [:favorite_color, 'heliotrope'], [:birthdate, @date_range] ]
|
37
|
+
# birthdate_matters_most = [ [:birthdate, @date_range], [:favorite_color, 'heliotrope'] ]
|
38
|
+
#
|
39
|
+
# cohort = Citizen.strict_cohort favorite_color_matters_most
|
40
|
+
# assert_equal 0, Citizen.where(cohort).count
|
41
|
+
#
|
42
|
+
# cohort = Citizen.strict_cohort birthdate_matters_most
|
43
|
+
# assert_equal 9, Citizen.where(cohort).count
|
44
|
+
# end
|
29
45
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cohort_scope
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,11 +11,11 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2012-02-
|
14
|
+
date: 2012-02-27 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: activesupport
|
18
|
-
requirement: &
|
18
|
+
requirement: &2152961320 !ruby/object:Gem::Requirement
|
19
19
|
none: false
|
20
20
|
requirements:
|
21
21
|
- - ! '>='
|
@@ -23,10 +23,10 @@ dependencies:
|
|
23
23
|
version: '3'
|
24
24
|
type: :runtime
|
25
25
|
prerelease: false
|
26
|
-
version_requirements: *
|
26
|
+
version_requirements: *2152961320
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activerecord
|
29
|
-
requirement: &
|
29
|
+
requirement: &2152960780 !ruby/object:Gem::Requirement
|
30
30
|
none: false
|
31
31
|
requirements:
|
32
32
|
- - ! '>='
|
@@ -34,43 +34,10 @@ dependencies:
|
|
34
34
|
version: '3'
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
|
-
version_requirements: *
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
none: false
|
42
|
-
requirements:
|
43
|
-
- - ! '>='
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
version: '0'
|
46
|
-
type: :development
|
47
|
-
prerelease: false
|
48
|
-
version_requirements: *2152801900
|
49
|
-
- !ruby/object:Gem::Dependency
|
50
|
-
name: mysql2
|
51
|
-
requirement: &2152801240 !ruby/object:Gem::Requirement
|
52
|
-
none: false
|
53
|
-
requirements:
|
54
|
-
- - ! '>='
|
55
|
-
- !ruby/object:Gem::Version
|
56
|
-
version: '0'
|
57
|
-
type: :development
|
58
|
-
prerelease: false
|
59
|
-
version_requirements: *2152801240
|
60
|
-
- !ruby/object:Gem::Dependency
|
61
|
-
name: rake
|
62
|
-
requirement: &2152800640 !ruby/object:Gem::Requirement
|
63
|
-
none: false
|
64
|
-
requirements:
|
65
|
-
- - ! '>='
|
66
|
-
- !ruby/object:Gem::Version
|
67
|
-
version: '0'
|
68
|
-
type: :development
|
69
|
-
prerelease: false
|
70
|
-
version_requirements: *2152800640
|
71
|
-
description: Provides big_cohort, which widens by finding the constraint that eliminates
|
72
|
-
the most records and removing it. Also provides strict_cohort, which widens by eliminating
|
73
|
-
constraints in order.
|
37
|
+
version_requirements: *2152960780
|
38
|
+
description: Provides big_cohort, which widens by finding the characteristic that
|
39
|
+
eliminates the most records and removing it. Also provides strict_cohort, which
|
40
|
+
widens by eliminating characteristics in order.
|
74
41
|
email:
|
75
42
|
- seamus@abshere.net
|
76
43
|
executables: []
|
@@ -79,12 +46,16 @@ extra_rdoc_files: []
|
|
79
46
|
files:
|
80
47
|
- .document
|
81
48
|
- .gitignore
|
49
|
+
- CHANGELOG
|
82
50
|
- Gemfile
|
83
51
|
- LICENSE
|
84
52
|
- README.rdoc
|
85
53
|
- Rakefile
|
86
54
|
- cohort_scope.gemspec
|
87
55
|
- lib/cohort_scope.rb
|
56
|
+
- lib/cohort_scope/active_record_base_class_methods.rb
|
57
|
+
- lib/cohort_scope/active_record_relation_instance_methods.rb
|
58
|
+
- lib/cohort_scope/arel_visitors_visitor_instance_methods.rb
|
88
59
|
- lib/cohort_scope/big_cohort.rb
|
89
60
|
- lib/cohort_scope/cohort.rb
|
90
61
|
- lib/cohort_scope/strict_cohort.rb
|