cohort_scope 0.2.3 → 0.3.0
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/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
|