histomatic 0.0.1.beta.1

Sign up to get free protection for your applications and to get access to all the features.
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: