ActiveCohort 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/active_cohort.rb +168 -0
  3. metadata +44 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8ad4bb05ec3fab6b833e77b81f463873eb86fbe1
4
+ data.tar.gz: 3c8782baa944cebc1f36ca2a209d09d920075e61
5
+ SHA512:
6
+ metadata.gz: 9b1f88caf2d8111c3f606e33eb16f08f710f3b27ade16d688ed2411b3c9455ef4a03d26f8934463d5931cae79663c2a8f6ccc840663d8ee9ab6384380565dbbd
7
+ data.tar.gz: 28d7ae8c4984b3d15d357dbd2758573363ebb9e868c790dc6fb611a83d69b6d820aaa6dc1d18ff70b72723bf51a813d0958a39c17bf36acc913fd46656ce8bf0
@@ -0,0 +1,168 @@
1
+ # Public: Provides a cohort analysis of a set of ActiveRecord objects. Intended to be
2
+ # consumed by domain-specific classes, such AigAnalyst, OrderAnalyst, etc.
3
+ # See the constructor's documentation for information on the options hash.
4
+ #
5
+ # Examples
6
+ #
7
+ # cohort = ActiveCohort.new(some_options_hash)
8
+ # cohort.generate_report
9
+ # # => [["", "Week 0", "Week 1", "Week 2", "Week 3", "Week 4", "Week 5"],
10
+ # ["1/9", "27.0%", "8.1%", "2.7%", "0.0%", "0.0%", "0.0%"],
11
+ # ["1/16", "37.9%", "7.6%", "0.0%", "0.0%", "0.0%"],
12
+ # ["1/23", "42.2%", "3.1%", "0.0%", "0.0%"],
13
+ # ["1/30", "31.8%", "0.0%", "0.0%"],
14
+ # ["2/6", "-", "-"]]
15
+ #
16
+ # puts cohort.to_csv
17
+ # # => ,Week 0,Week 1,Week 2,Week 3,Week 4,Week 5
18
+ # 1/9,27.0%,8.1%,2.7%,0.0%,0.0%,0.0%
19
+ # 1/16,37.9%,7.6%,0.0%,0.0%,0.0%
20
+ # 1/23,42.2%,3.1%,0.0%,0.0%
21
+ # 1/30,31.8%,0.0%,0.0%
22
+ # 2/6,-,-
23
+ class ActiveCohort
24
+ attr_accessor :subject_collection, :activation_lambda
25
+ attr_writer :start_at, :interval_timestamp_field
26
+
27
+ # Public: Initialize a ActiveCohort.
28
+ #
29
+ # Required params
30
+ # subject_collection - An ActiveRecord collection of records to perform a
31
+ # cohort analysis on.
32
+ # activation_lambda - A lambda that returns a boolean indicating whether
33
+ # a given record has activated (e.g., converted,
34
+ # signed up, purchased, etc.)
35
+ # opts - A String naming the widget.
36
+ # start_at - The date at which to begin the analysis.
37
+ # Default: 30 days ago.
38
+ # interval - A string representation of the interval to run the analysis
39
+ # over (e.g, day, week, etc.) For instance, 'week' would
40
+ # result in a week-over-week analysis.
41
+ # Default: 'day'.
42
+ # interval_timestamp_field - A String representation of the timestamp
43
+ # field on the cohort records to be used to
44
+ # offset between intervals.
45
+ # Default: 'created_at'.
46
+ def initialize(subject_collection, activation_lambda, opts={})
47
+ @subject_collection = subject_collection
48
+ @activation_lambda = activation_lambda
49
+ opts.each { |k,v| instance_variable_set("@#{k}", v) }
50
+ end
51
+
52
+ def interval
53
+ @interval || 'day'
54
+ end
55
+
56
+ def interval=(interval)
57
+ unless interval.downcase.in? valid_intervals
58
+ raise "The interval \"#{interval}\" isn't valid.\n" +
59
+ "Use #{valid_intervals.join ', '}"
60
+ end
61
+ @interval = interval.downcase
62
+ end
63
+
64
+ def start_at
65
+ @start_at || 30.days.ago
66
+ end
67
+
68
+ def interval_timestamp_field
69
+ @interval_timestamp_field || 'created_at'
70
+ end
71
+
72
+ # Public: Generates a cohort report using params supplied to the instance in
73
+ # the constructor.
74
+ #
75
+ # Example
76
+ # cohort.generate_report
77
+ # # => [["", "Week 0", "Week 1", "Week 2", "Week 3", "Week 4", "Week 5"],
78
+ # ["1/9", "27.0%", "8.1%", "2.7%", "0.0%", "0.0%", "0.0%"],
79
+ # ["1/16", "37.9%", "7.6%", "0.0%", "0.0%", "0.0%"],
80
+ # ["1/23", "42.2%", "3.1%", "0.0%", "0.0%"],
81
+ # ["1/30", "31.8%", "0.0%", "0.0%"],
82
+ # ["2/6", "-", "-"]]
83
+ #
84
+ # Returns an Array of values representing the report.
85
+ def generate_report
86
+ validate_required_fields
87
+ @report = []
88
+ @report << header
89
+
90
+ (number_of_intervals - 1).times do |row|
91
+ @report << build_row(row)
92
+ end
93
+ @report
94
+ end
95
+
96
+ # Public: Outputs the cohort report in CSV format. Does not regenerate the
97
+ # report if the instance has already generated it.
98
+ #
99
+ # Example
100
+ # puts cohort.to_csv
101
+ # # => ,Week 0,Week 1,Week 2,Week 3,Week 4,Week 5
102
+ # 1/9,27.0%,8.1%,2.7%,0.0%,0.0%,0.0%
103
+ # 1/16,37.9%,7.6%,0.0%,0.0%,0.0%
104
+ # 1/23,42.2%,3.1%,0.0%,0.0%
105
+ # 1/30,31.8%,0.0%,0.0%
106
+ # 2/6,-,-
107
+ #
108
+ # Returns a String representation of the report with CSV formatting.
109
+ def to_csv(seperator=',')
110
+ report = @report || generate_report
111
+ report.map{ |row| row.join(seperator) }.join("\n")
112
+ end
113
+
114
+ private
115
+ def header
116
+ header = ['']
117
+ number_of_intervals.times do |i|
118
+ header << "#{interval.capitalize} #{i}"
119
+ end
120
+ header
121
+ end
122
+
123
+ def number_of_intervals
124
+ @interval == 'day' ? 30 : 6
125
+ end
126
+
127
+ def valid_intervals
128
+ %w(day week month)
129
+ end
130
+
131
+ def assemble_cohort(start_date, end_date)
132
+ @subject_collection.where(
133
+ @interval_timestamp_field.to_sym => start_date..end_date
134
+ )
135
+ end
136
+
137
+ def percentage_as_string(numerator, denominator)
138
+ return "-" if denominator.zero?
139
+ "#{((numerator / denominator.to_f) * 100).round(1)}%"
140
+ end
141
+
142
+ def start_date_for_cell(row, col)
143
+ row_offset = row.send(:"#{interval}")
144
+ col_offset = col.send(:"#{interval}")
145
+ (start_at + row_offset + col_offset).send(:"beginning_of_#{interval}")
146
+ end
147
+
148
+ def build_row(row)
149
+ row_values = []
150
+ row_offset = row.send(:"#{interval}")
151
+ cohort_start_date = (start_at + row_offset).send(:"beginning_of_#{interval}")
152
+ cohort_end_date = cohort_start_date.send(:"end_of_#{interval}")
153
+ cohort = assemble_cohort cohort_start_date, cohort_end_date
154
+ row_values << cohort_start_date.strftime("%-m/%-d")
155
+ (number_of_intervals - row).times do |col|
156
+ activation_start_date = start_date_for_cell(row, col)
157
+ activation_end_date = activation_start_date.send(:"end_of_#{interval}")
158
+ activated = cohort.select { |c| @activation_lambda.call(c, activation_start_date, activation_end_date) }
159
+ row_values << percentage_as_string(activated.length, cohort.length)
160
+ end
161
+ row_values
162
+ end
163
+
164
+ def validate_required_fields
165
+ raise "Missing subject_collection" unless subject_collection.present?
166
+ raise "Missing activation_lambda" unless activation_lambda.present?
167
+ end
168
+ end
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ActiveCohort
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Josh Saint Jacque
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-02-25 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Quickly product cohort reports from active record data.
14
+ email: joshsaintjacque@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/active_cohort.rb
20
+ homepage: http://rubygems.org/gems/activecohort
21
+ licenses:
22
+ - MIT
23
+ metadata: {}
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ requirements: []
39
+ rubyforge_project:
40
+ rubygems_version: 2.6.6
41
+ signing_key:
42
+ specification_version: 4
43
+ summary: Cohort reports for ActiveRecord.
44
+ test_files: []