histomatic 0.0.1.beta.1

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/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ tmp/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2 do
5
+ watch(%r{^spec/.+_spec\.rb$}) { "spec" }
6
+ watch(%r{^lib/(.+)\.rb$}) { "spec" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
9
+
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2011 Christopher Meiklejohn.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,53 @@
1
+ # Histomatic
2
+
3
+ Quick n' dirty histograms for Rails.
4
+
5
+ ## Notice
6
+
7
+ Currently in development, and only supports the mysql2 driver.
8
+
9
+ ## Usage
10
+
11
+ Generate a histogram providing an input source, which is either an
12
+ ActiveRecord class or instance of ActiveRelation, a column as a string,
13
+ and groupings.
14
+
15
+ Currently the column must return a numeric.
16
+
17
+ ### Examples
18
+
19
+ Provide a class:
20
+
21
+ ```ruby
22
+ Histomatic.generate(
23
+ Purchase,
24
+ 'amount',
25
+ [0, 10, 20]
26
+ ).to_hash # { 0 => 2, 10 => 0, 20 => 0 }
27
+ ```
28
+
29
+ Provide an ActiveRelation:
30
+
31
+ ```ruby
32
+ Histomatic.generate(
33
+ Purchase.where(:name => 'Chris'),
34
+ 'amount',
35
+ [0, 10, 20]
36
+ ).to_hash # { 0 => 1, 10 => 0, 20 => 0 }
37
+ ```
38
+
39
+ Provide a transformation as the column:
40
+
41
+ ```ruby
42
+ Histomatic.generate(
43
+ Purchase.where(:name => 'Chris'),
44
+ 'datediff(current_date, purchases.created_at)',
45
+ [0, 10, 20]
46
+ ).to_hash # { 0 => 0, 10 => 1, 20 => 0 }
47
+ ```
48
+
49
+ ## License
50
+
51
+ Histomatic is Copyright © 2012 Christopher Meiklejohn. Histomatic is
52
+ free software, and may be redistributed under the terms specified in the
53
+ LICENSE file.
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ require 'bundler/gem_tasks'
6
+ require 'rake'
7
+ require 'rdoc/task'
8
+ require 'rake/clean'
9
+ require 'rubygems/package_task'
10
+ require 'rspec/core/rake_task'
11
+ require 'rdoc'
12
+ require 'yard'
13
+
14
+ include Rake::DSL
15
+
16
+ Bundler::GemHelper.install_tasks
17
+
18
+ RSpec::Core::RakeTask.new do |t|
19
+ t.pattern = 'spec/**/*_spec.rb'
20
+ end
21
+
22
+ YARD::Rake::YardocTask.new do |t|
23
+ t.files = ['lib/**/*.rb']
24
+ t.options = ['--no-private']
25
+ end
26
+
27
+ task :default => [:spec]
data/db/schema.rb ADDED
@@ -0,0 +1,11 @@
1
+ # encoding: UTF-8
2
+
3
+ ActiveRecord::Schema.define do
4
+ self.verbose = false
5
+
6
+ create_table :purchases, :force => true do |t|
7
+ t.string :name
8
+ t.decimal :amount
9
+ t.timestamps
10
+ end
11
+ end
@@ -0,0 +1,41 @@
1
+ # encoding: UTF-8
2
+
3
+ $:.push File.expand_path("../lib", __FILE__)
4
+ require "histomatic/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "histomatic"
8
+ s.version = Histomatic::VERSION
9
+ s.authors = ["Christopher Meiklejohn"]
10
+ s.email = ["christopher.meiklejohn@gmail.com"]
11
+ s.homepage = "http://github.com/cmeiklejohn/histomatic"
12
+ s.summary = %q{Quick 'n dirty histograms.}
13
+ s.description = %q{Quick 'n dirty histograms.}
14
+
15
+ s.rubyforge_project = "histomatic"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_runtime_dependency('rails')
23
+
24
+ s.add_development_dependency('yard')
25
+ s.add_development_dependency('rdoc')
26
+ s.add_development_dependency('redcarpet')
27
+
28
+ s.add_development_dependency('rspec')
29
+ s.add_development_dependency('rake')
30
+
31
+ s.add_development_dependency('bundler')
32
+ s.add_development_dependency('rspec')
33
+ s.add_development_dependency('rspec-rails')
34
+ s.add_development_dependency('guard')
35
+ s.add_development_dependency('guard-rspec')
36
+ s.add_development_dependency('mysql2')
37
+ s.add_development_dependency('sqlite3')
38
+ s.add_development_dependency('factory_girl')
39
+ s.add_development_dependency('factory_girl_rails')
40
+ s.add_development_dependency('diesel')
41
+ end
data/lib/histomatic.rb ADDED
@@ -0,0 +1,128 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'histomatic/version'
4
+
5
+ # {{Histomatic}}: Quick 'n dirty histograms from {{ActiveRecord}} sources.
6
+ #
7
+ module Histomatic
8
+
9
+ class << self
10
+
11
+ # Generate a histogram from a particular {{ActiveRecord}} or
12
+ # {{ActiveRelation}} source.
13
+ #
14
+ # @param [ActiveRelation]
15
+ # @param [String]
16
+ # @param [Array<Float>]
17
+ # @return [Histogram]
18
+ #
19
+ def generate(source, column, bins)
20
+ Histogram.new(source, column, bins)
21
+ end
22
+
23
+ end
24
+
25
+ class Histogram
26
+
27
+ attr_reader :source, :column, :bins
28
+
29
+ # Create a new {{Histogram}} instance.
30
+ #
31
+ def initialize(source, column, bins)
32
+ @source = source
33
+ @column = column
34
+ @bins = bins
35
+ end
36
+
37
+ # Return hash representation.
38
+ #
39
+ # @return [Hash]
40
+ #
41
+ def to_hash
42
+ serializable_hash
43
+ end
44
+
45
+ # Return json representation.
46
+ #
47
+ # @return [String]
48
+ #
49
+ def to_json
50
+ JSON.generate(to_hash)
51
+ end
52
+
53
+ # Return the result as a keys representing the lower inclusive bound
54
+ # of bin, with values pointing to the number of objects represented.
55
+ #
56
+ # @return [Hash]
57
+ #
58
+ def serializable_hash
59
+ results.each.inject(empty_bins) do |histogram, result|
60
+ histogram[result] = histogram[result] ? histogram[result] + 1 : 1; histogram
61
+ end
62
+ end
63
+
64
+ # Return SQL for generating the source data for this histogram.
65
+ #
66
+ # @return [String]
67
+ # @private
68
+ #
69
+ def to_sql
70
+ source.select(bin_sql).to_sql
71
+ end
72
+ private :to_sql
73
+
74
+ # Return results.
75
+ #
76
+ # @return [Mysql2::Results]
77
+ # @private
78
+ #
79
+ def results
80
+ ActiveRecord::Base.connection.execute(to_sql).each.map(&:first)
81
+ end
82
+ private :results
83
+
84
+ # Convert bins to SQL conditions which can be used to generate the
85
+ # histogram.
86
+ #
87
+ # @return [String]
88
+ # @private
89
+ # @todo Inefficient, however, compatible for clients that do not support subselects.
90
+ #
91
+ def bin_sql
92
+ selection = [
93
+ "CASE",
94
+ bins.each_cons(2).map do |bounds|
95
+ case_for(column, bounds.first, bounds.last)
96
+ end,
97
+ case_for(column, bins.last),
98
+ "END"
99
+ ].flatten.join(' ')
100
+ end
101
+ private :bin_sql
102
+
103
+ # Returns a case condition and implications for a particular set of
104
+ # bounds.
105
+ #
106
+ # @param [String]
107
+ # @param [Float]
108
+ # @param [Float]
109
+ # @return [String]
110
+ # @private
111
+ #
112
+ def case_for(column, lower, upper = nil)
113
+ "WHEN #{column} >= #{lower} #{" AND #{column} < #{upper} " if upper} THEN #{lower}"
114
+ end
115
+
116
+ # Generate a hash of empty bins used during output.
117
+ #
118
+ # @return [Hash]
119
+ # @private
120
+ #
121
+ def empty_bins
122
+ bins.inject({}) { |bins, bin| bins[bin] = 0; bins }
123
+ end
124
+ private :empty_bins
125
+
126
+ end
127
+
128
+ end
@@ -0,0 +1,3 @@
1
+ module Histomatic
2
+ VERSION = "0.0.1.beta.1"
3
+ end
@@ -0,0 +1,66 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ class Purchase < ActiveRecord::Base; end
6
+
7
+ module Histomatic
8
+ describe Histogram do
9
+ let(:source) { Purchase }
10
+ let(:column) { 'amount' }
11
+ let(:bins) { [0, 10, 20] }
12
+
13
+ subject { Histogram.new(source, column, bins) }
14
+
15
+ it 'initializes with a source, column and bins' do
16
+ subject.source.should == source
17
+ subject.column.should == column
18
+ subject.bins.should == bins
19
+ end
20
+
21
+ context 'with a generated histogram' do
22
+ let(:hash) { { 0 => 1, 1 => 2, 2 => 3 } }
23
+
24
+ before do
25
+ subject.stub(:serializable_hash).and_return(hash)
26
+ end
27
+
28
+ it 'can return a hash' do
29
+ subject.to_hash.should == hash
30
+ end
31
+
32
+ it 'can return json' do
33
+ JSON.should_receive(:generate).with(hash)
34
+ subject.to_json
35
+ end
36
+ end
37
+
38
+ context 'given a few purchases' do
39
+ let(:purchases) do
40
+ 10.times.map do |i|
41
+ Purchase.new(:amount => i.to_i * 10, :name => "#{i % 2 == 0 ? "Chris" : "Bob"}")
42
+ end
43
+ end
44
+
45
+ before do
46
+ purchases.map { |purchase| purchase.save! }
47
+ end
48
+
49
+ it 'generates a histogram for the amount' do
50
+ Histomatic.generate(Purchase, 'amount', [0, 10, 20]).
51
+ serializable_hash.should == { 0 => 1, 10 => 1, 20 => 8 }
52
+ end
53
+
54
+ it 'generates a histogram for the amount by user' do
55
+ Histomatic.generate(Purchase.where(:name => 'Chris'), 'amount', [0, 10, 20]).
56
+ serializable_hash.should == { 0 => 1, 10 => 0, 20 => 4 }
57
+ end
58
+
59
+ it 'generates a histogram for the day difference by user' do
60
+ Histomatic.generate(Purchase.where(:name => 'Chris'),
61
+ 'datediff(purchases.created_at, date_sub(current_date, interval 20 day))', [0, 10, 20]).
62
+ serializable_hash.should == { 0 => 0, 10 => 0, 20 => 5 }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: UTF-8
2
+
3
+ ENV["RAILS_ENV"] ||= "test"
4
+
5
+ PROJECT_ROOT = File.expand_path("../..", __FILE__)
6
+ $LOAD_PATH << File.join(PROJECT_ROOT, "lib")
7
+
8
+ require 'rails/all'
9
+ require 'rails/test_help'
10
+ Bundler.require
11
+
12
+ require 'diesel/testing'
13
+ require 'rspec/rails'
14
+
15
+ require 'histomatic'
16
+
17
+ Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
18
+
19
+ ActiveRecord::Base.establish_connection(
20
+ :adapter => "mysql2",
21
+ :database => "histomatic",
22
+ :username => "histomatic",
23
+ :password => "histomatic"
24
+ )
25
+
26
+ ActiveRecord::Base.silence do
27
+ ActiveRecord::Migration.verbose = false
28
+
29
+ load(File.dirname(__FILE__) + '/../db/schema.rb')
30
+ end
31
+
32
+ RSpec.configure do |config|
33
+ config.use_transactional_fixtures = true
34
+ config.backtrace_clean_patterns << %r{gems/}
35
+ end
metadata ADDED
@@ -0,0 +1,238 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: histomatic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.beta.1
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Christopher Meiklejohn
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: &20707620 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *20707620
25
+ - !ruby/object:Gem::Dependency
26
+ name: yard
27
+ requirement: &20705980 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *20705980
36
+ - !ruby/object:Gem::Dependency
37
+ name: rdoc
38
+ requirement: &20704460 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *20704460
47
+ - !ruby/object:Gem::Dependency
48
+ name: redcarpet
49
+ requirement: &20737720 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *20737720
58
+ - !ruby/object:Gem::Dependency
59
+ name: rspec
60
+ requirement: &20735820 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *20735820
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: &20750800 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *20750800
80
+ - !ruby/object:Gem::Dependency
81
+ name: bundler
82
+ requirement: &20748620 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *20748620
91
+ - !ruby/object:Gem::Dependency
92
+ name: rspec
93
+ requirement: &20744140 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: *20744140
102
+ - !ruby/object:Gem::Dependency
103
+ name: rspec-rails
104
+ requirement: &20757300 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: *20757300
113
+ - !ruby/object:Gem::Dependency
114
+ name: guard
115
+ requirement: &20765880 !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ! '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ type: :development
122
+ prerelease: false
123
+ version_requirements: *20765880
124
+ - !ruby/object:Gem::Dependency
125
+ name: guard-rspec
126
+ requirement: &21443240 !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: *21443240
135
+ - !ruby/object:Gem::Dependency
136
+ name: mysql2
137
+ requirement: &21440240 !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ type: :development
144
+ prerelease: false
145
+ version_requirements: *21440240
146
+ - !ruby/object:Gem::Dependency
147
+ name: sqlite3
148
+ requirement: &21449520 !ruby/object:Gem::Requirement
149
+ none: false
150
+ requirements:
151
+ - - ! '>='
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ type: :development
155
+ prerelease: false
156
+ version_requirements: *21449520
157
+ - !ruby/object:Gem::Dependency
158
+ name: factory_girl
159
+ requirement: &21447740 !ruby/object:Gem::Requirement
160
+ none: false
161
+ requirements:
162
+ - - ! '>='
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ type: :development
166
+ prerelease: false
167
+ version_requirements: *21447740
168
+ - !ruby/object:Gem::Dependency
169
+ name: factory_girl_rails
170
+ requirement: &21472720 !ruby/object:Gem::Requirement
171
+ none: false
172
+ requirements:
173
+ - - ! '>='
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ type: :development
177
+ prerelease: false
178
+ version_requirements: *21472720
179
+ - !ruby/object:Gem::Dependency
180
+ name: diesel
181
+ requirement: &21482480 !ruby/object:Gem::Requirement
182
+ none: false
183
+ requirements:
184
+ - - ! '>='
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ type: :development
188
+ prerelease: false
189
+ version_requirements: *21482480
190
+ description: Quick 'n dirty histograms.
191
+ email:
192
+ - christopher.meiklejohn@gmail.com
193
+ executables: []
194
+ extensions: []
195
+ extra_rdoc_files: []
196
+ files:
197
+ - .gitignore
198
+ - .rspec
199
+ - Gemfile
200
+ - Guardfile
201
+ - LICENSE
202
+ - README.markdown
203
+ - Rakefile
204
+ - db/schema.rb
205
+ - histomatic.gemspec
206
+ - lib/histomatic.rb
207
+ - lib/histomatic/version.rb
208
+ - spec/lib/histomatic_spec.rb
209
+ - spec/spec_helper.rb
210
+ homepage: http://github.com/cmeiklejohn/histomatic
211
+ licenses: []
212
+ post_install_message:
213
+ rdoc_options: []
214
+ require_paths:
215
+ - lib
216
+ required_ruby_version: !ruby/object:Gem::Requirement
217
+ none: false
218
+ requirements:
219
+ - - ! '>='
220
+ - !ruby/object:Gem::Version
221
+ version: '0'
222
+ segments:
223
+ - 0
224
+ hash: 4312745738876464787
225
+ required_rubygems_version: !ruby/object:Gem::Requirement
226
+ none: false
227
+ requirements:
228
+ - - ! '>'
229
+ - !ruby/object:Gem::Version
230
+ version: 1.3.1
231
+ requirements: []
232
+ rubyforge_project: histomatic
233
+ rubygems_version: 1.8.11
234
+ signing_key:
235
+ specification_version: 3
236
+ summary: Quick 'n dirty histograms.
237
+ test_files: []
238
+ has_rdoc: