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 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