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 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
@@ -2,3 +2,8 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in cohort_scope.gemspec
4
4
  gemspec
5
+
6
+ # gem 'ruby-debug19'
7
+ gem 'test-unit'
8
+ gem 'mysql2'
9
+ gem 'rake'
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 constraint until it reaches the minimum number of records
6
- * <tt>strict_cohort</tt> widens by eliminating constraints in order until it reaches the minimum number of records
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://carbon.brighterplanet.com since April 2010, where it helps sift through climate data to come up with meaningful emissions calculations.
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 constraints will hold) ...]
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 constraint, at least give me one where everybody loves heliotrope:
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
- ordered_constraints = []
36
- ordered_constraints << [:favorite_color, 'heliotrope']
37
- ordered_constraints << [:birthdate, (Date.parse('1980-01-01')..Date.parse('1990-01-01'))]
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) 2011 Seamus Abshere and Andy Rossmeissl. See LICENSE for details.
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 constraint that eliminates the most records and removing it. Also provides strict_cohort, which widens by eliminating constraints in order.}
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(constraints)
18
- case constraints
22
+ def self.conditions_for(characteristics)
23
+ case characteristics
19
24
  when ::Array
20
- constraints.inject({}) { |memo, (k, v)| memo[k] = v; memo }
25
+ characteristics.inject({}) { |memo, (k, v)| memo[k] = v; memo }
21
26
  when ::Hash
22
- constraints.dup
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
- ::ActiveRecord::Relation.send :include, ::CohortScope::ActiveRecordRelationExt
32
+ ActiveRecord::Base.extend CohortScope::ActiveRecordBaseClassMethods
33
+ ActiveRecord::Relation.send :include, CohortScope::ActiveRecordRelationInstanceMethods
34
+ Arel::Visitors::Visitor.send :include, CohortScope::ArelVisitorsVisitorInstanceMethods
@@ -0,0 +1,11 @@
1
+ module CohortScope
2
+ module ActiveRecordBaseClassMethods
3
+ def big_cohort(*args)
4
+ scoped.big_cohort *args
5
+ end
6
+
7
+ def strict_cohort(*args)
8
+ scoped.strict_cohort *args
9
+ end
10
+ end
11
+ end
@@ -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
@@ -0,0 +1,7 @@
1
+ module CohortScope
2
+ module ArelVisitorsVisitorInstanceMethods
3
+ def visit_CohortScope_Cohort(o)
4
+ o.expr
5
+ end
6
+ end
7
+ end
@@ -1,14 +1,17 @@
1
1
  module CohortScope
2
2
  class BigCohort < Cohort
3
- # Reduce constraints by removing them one by one and counting the results.
3
+ # Reduce characteristics by removing them one by one and counting the results.
4
4
  #
5
- # The constraint whose removal leads to the highest record count is removed from the overall constraint set.
6
- def self.reduce_constraints(active_record, constraints)
7
- most_restrictive_constraint = constraints.keys.max_by do |key|
8
- conditions = CohortScope.conditions_for constraints.except(key)
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
- constraints.except most_restrictive_constraint
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
@@ -1,105 +1,30 @@
1
- require 'delegate'
2
-
3
1
  module CohortScope
4
- class Cohort < ::Delegator
5
- class << self
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
- if (count = constrained_scope.count) >= minimum_cohort_size
17
- cohort = new constrained_scope
18
- cohort.count = count
19
- cohort
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 initialize(obj)
29
- super
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
- def count=(int)
40
- @count = int
41
- end
42
-
43
- def count
44
- @count ||= super
45
- end
16
+ private
46
17
 
47
- # sabshere 2/1/11 overriding as_json per usual doesn't seem to work
48
- def to_json(*)
49
- as_json.to_json
50
- end
51
-
52
- def as_json(*)
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
- super
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 constraints by removing the least important one.
4
- def self.reduce_constraints(active_record, constraints)
5
- constraints[0..-2]
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
@@ -1,3 +1,3 @@
1
1
  module CohortScope
2
- VERSION = '0.2.3'
2
+ VERSION = '0.3.0'
3
3
  end
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
 
@@ -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
@@ -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
- # confusing as hell because houses have styles according to periods, which is not accurate
22
- def test_002a_complicated_cohorts_with_joins
23
- assert_equal 3, Style.joins(:houses).big_cohort(:houses => { :id => [house1]}).length
24
- assert_equal 3, Style.joins(:houses).big_cohort(:houses => { :id => [house1]}, :name => 'foooooooo').length
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 = (people_who_love_heliotrope_are_from_the_fifties + their_children_are_born_in_the_eighties + nobody)
94
- assert_kind_of CohortScope::Cohort, everybody
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
@@ -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 test_003_seek_cohort_by_discarding_constraints_in_order
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.2.3
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-22 00:00:00.000000000 Z
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: &2152803180 !ruby/object:Gem::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: *2152803180
26
+ version_requirements: *2152961320
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activerecord
29
- requirement: &2152802420 !ruby/object:Gem::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: *2152802420
38
- - !ruby/object:Gem::Dependency
39
- name: test-unit
40
- requirement: &2152801900 !ruby/object:Gem::Requirement
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