comparison 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: baea273a06f0a0abb98d762a042fa9839ffd1c0e
4
+ data.tar.gz: 0b801fed057465fd098120a14e9cf931851c9198
5
+ SHA512:
6
+ metadata.gz: 6511bc82b1f7fde3f6c13f4d2ae1ad56622d08f3ec65a15a670c8fa370684996ea395d36455763f4894460ce8ca1920ff2f37ff7db4f473ef9305944e27fc3b7
7
+ data.tar.gz: ccd9104172486a78800166080e0ba1268008b41bc36ae34524bc491f3576fc30562446110a48b05933b6cffbe051b33f431b47b6be7ba2b8e6dc45ffff950c4b
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2016 John Parker
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # Comparison
2
+
3
+ I have often found myself implementing reporting features that compare two
4
+ numbers to each other. For example, a report that compares the outcome for a
5
+ quarter to the outcome of the same quarter in the prior year. Frequently I end
6
+ up displaying both the raw difference between the two numbers, the percentage
7
+ difference, and maybe a simple visual indicator such as an arrow (pointing up
8
+ or down). Sometimes these comparisons require special handling for Infinity and
9
+ NaN when one or both numbers are zero.
10
+
11
+ I've tackled this task enough times and in enough applications that I felt it
12
+ would simplify my life to extract and package the code for future re-use.
13
+
14
+ ## Usage
15
+ The library has three components: the Comparison class for performing the
16
+ actual math, the Presenter class for decorating the Comparison with
17
+ view-friendly output, and a helper module for using the Presenter with the
18
+ view.
19
+
20
+ `#compare` helper takes the two numbers to be compared and yields the
21
+ Comparison presenter to a block.
22
+
23
+ `#difference` provides the absolute difference between the two numbers,
24
+ literally `m - n`.
25
+
26
+ `#percentage` provides the percentage difference between the two numbers. Under
27
+ the hood it uses `ActionView::Helpers::NumberHelper#number_to_percentage` to
28
+ format the percentage. Options are passed through to that method.
29
+
30
+ `#arrow` returns an HTML character entity for an arrow (up, down, or, for no
31
+ change, an empty string).
32
+
33
+ ```erb
34
+ <%= compare m, n do |cmp| %>
35
+ <td><%= number_to_currency m %></td>
36
+ <td><%= number_to_currency n %></td>
37
+ <td><%= cmp.difference %></td>
38
+ <td>
39
+ <%= cmp.arrow %>
40
+ <%= cmp.percentage precision: 1 %>
41
+ </td>
42
+ <% end %>
43
+ ```
44
+
45
+ | Period | Outcome | LY | Diff | Pct |
46
+ | ------- | ------: | ------: | -------: | ------------: |
47
+ | Q4 2016 | $200.00 | $50.00 | +$150.00 | &uarr;+300.0% |
48
+ | Q3 2016 | $125.00 | $100.00 | +$25.00 | &uarr;+25.0% |
49
+ | Q2 2016 | $100.00 | $125.00 | -$25.00 | &darr;-20.0% |
50
+ | Q1 2016 | $100.00 | $0.00 | +$100.00 | &uarr; |
51
+ | Q4 2015 | $75.00 | $75.00 | $0.00 | 0.0% |
52
+ | Q3 2015 | $0.00 | $0.00 | $0.00 | 0.0% |
53
+
54
+
55
+ ## Configuration
56
+ Comparison uses I18n to configure the output of some of the Presenter methods.
57
+ Default implementations are provided where it makes sense. You can provide your
58
+ own implementations by adding translations to your application.
59
+
60
+ ```yml
61
+ en:
62
+ comparison:
63
+ classes:
64
+ positive: 'comparison positive'
65
+ negative: 'comparison negative'
66
+ nochange: 'comparison nochange'
67
+ css:
68
+ positive_html: 'color: #3c763d; background-color: #dff0d8;'
69
+ negative_html: 'color: #a94442; background-color: #f2dede;'
70
+ nochange_html: 'color: #777777;'
71
+ icons:
72
+ positive_html: '<span class="glyphicon glyphicon-arrow-up"></span>'
73
+ negative_html: '<span class="glyphicon glyphicon-arrow-down"></span>'
74
+ nochange_html: '<span class="glyphicon glyphicon-minus"></span>'
75
+ arrows:
76
+ positive_html: '&uarr;'
77
+ negative_html: '&darr;'
78
+ nochange_html: ''
79
+ ```
80
+
81
+ ## Installation
82
+ Add this line to your application's Gemfile:
83
+
84
+ ```ruby
85
+ gem 'comparison'
86
+ ```
87
+
88
+ And then execute:
89
+ ```bash
90
+ $ bundle
91
+ ```
92
+
93
+ Or install it yourself as:
94
+ ```bash
95
+ $ gem install comparison
96
+ ```
97
+
98
+ ## Contributing
99
+ Open an GitHub issue for problems and suggestions. This library is in its
100
+ infancy, so use it at your own risk.
101
+
102
+ ## License
103
+ The gem is available as open source under the terms of the
104
+ [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Comparison'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+ load 'rails/tasks/statistics.rake'
20
+
21
+
22
+
23
+ require 'bundler/gem_tasks'
24
+
25
+ require 'rake/testtask'
26
+
27
+ Rake::TestTask.new(:test) do |t|
28
+ t.libs << 'lib'
29
+ t.libs << 'test'
30
+ t.pattern = 'test/**/*_test.rb'
31
+ t.verbose = false
32
+ end
33
+
34
+
35
+ task default: :test
@@ -0,0 +1,13 @@
1
+ module Comparison
2
+ module ApplicationHelper
3
+ ##
4
+ # Returns a Presenter for a Comparator for +m+ and +n+.
5
+ #
6
+ # If a block is given, the Presenter is yielded to the block.
7
+ def compare(m, n)
8
+ comparison = Presenter.new Comparator.new m, n
9
+ yield comparison if block_given?
10
+ comparison
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ en:
2
+ comparison:
3
+ # classes:
4
+ # positive: 'comparison positive'
5
+ # negative: 'comparison negative'
6
+ # nochange: 'comparison nochange'
7
+ css:
8
+ positive_html: ''
9
+ negative_html: ''
10
+ nochange_html: ''
11
+ # icons:
12
+ # positive_html: '<span class="glyphicon glyphicon-arrow-up"></span>'
13
+ # negative_html: '<span class="glyphicon glyphicon-arrow-down"></span>'
14
+ # nochange_html: '<span class="glyphicon glyphicon-minus"></span>'
15
+ arrows:
16
+ positive_html: '&uarr;'
17
+ negative_html: '&darr;'
18
+ nochange_html: ''
data/lib/comparison.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'comparison/engine'
2
+ require 'comparison/comparator'
3
+ require 'comparison/presenter'
4
+
5
+ module Comparison
6
+ end
@@ -0,0 +1,39 @@
1
+ require 'bigdecimal'
2
+ require 'forwardable'
3
+
4
+ module Comparison
5
+ class Comparator
6
+ extend Forwardable
7
+
8
+ ##
9
+ # Instantiates a new Comparator to compare two numbers, +m+ and +n+.
10
+ def initialize(m, n)
11
+ @m = m.to_d
12
+ @n = n.to_d
13
+ end
14
+
15
+ attr_reader :m, :n
16
+
17
+ delegate %i[infinite? nan? negative? positive? zero?] => :relative
18
+
19
+ ##
20
+ # Returns the difference between +@m+ and +@n+.
21
+ def absolute
22
+ @absolute ||= m - n
23
+ end
24
+
25
+ alias_method :difference, :absolute
26
+
27
+ ##
28
+ # Returns the percentage difference of +@m+ to +@n+.
29
+ def relative
30
+ @relative ||= if n.negative?
31
+ (1 - m / n) * 100
32
+ else
33
+ (m / n - 1) * 100
34
+ end
35
+ end
36
+
37
+ alias_method :percentage, :relative
38
+ end
39
+ end
@@ -0,0 +1,9 @@
1
+ module Comparison
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Comparison
4
+
5
+ initializer 'comparison.view_helpers' do
6
+ ActionView::Base.send :include, ApplicationHelper
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,191 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'delegate'
4
+ require 'forwardable'
5
+
6
+ module Comparison
7
+ class Presenter < DelegateClass(Comparator)
8
+ extend Forwardable
9
+ include ActionView::Helpers::TranslationHelper
10
+
11
+ ARROWS = { up: '&uarr;', down: '&darr;', none: '' }
12
+
13
+ # TODO: This shouldn't necessarily return a currency representation.
14
+
15
+ ##
16
+ # Returns `Comparator#absolute` presented as currency.
17
+ def difference(**options)
18
+ if positive?
19
+ number_to_currency absolute, format: '+%u%n', **options
20
+ else
21
+ number_to_currency absolute, **options
22
+ end
23
+ end
24
+
25
+ ##
26
+ # Returns `Comparator#relative` formatted as a percentage.
27
+ #
28
+ # If the relative percentage evaluates to Infinity or -Infinity, +nil+ is
29
+ # returned. If it evaluates to NaN, 0 is returned.
30
+ def percentage(delimiter: ',', precision: 0, **options)
31
+ case
32
+ when nan? || zero?
33
+ number_to_percentage 0, precision: precision, **options
34
+ when infinite?
35
+ # TODO: Return nil, or lookup an optional representation in I18n?
36
+ nil
37
+ when positive?
38
+ number_to_percentage relative, delimiter: delimiter,
39
+ precision: precision, format: '+%n%', **options
40
+ else
41
+ number_to_percentage relative, delimiter: delimiter,
42
+ precision: precision, **options
43
+ end
44
+ end
45
+
46
+ alias_method :change, :percentage
47
+ deprecate :change
48
+
49
+ delegate %i[number_to_currency number_to_percentage] => :'ActiveSupport::NumberHelper'
50
+
51
+ ##
52
+ # Returns the I18n translation for `comparison.icons`. (See also `#arrow`.)
53
+ #
54
+ # This method is intended to display a graphical representation of the
55
+ # comparison. Typically this would be an arrow pointing up or down.
56
+ #
57
+ # No default implementation is included. You must provide the translations
58
+ # yourself or you will get missing translations.
59
+ #
60
+ # For example, to generate up and down arrows using Glyphicons included
61
+ # with Bootstrap, you could add the following translations to your
62
+ # application:
63
+ #
64
+ # en:
65
+ # comparison:
66
+ # icons:
67
+ # positive_html: '<span class="glyphicon glyphicon-arrow-up"></span>'
68
+ # negative_html: '<span class="glyphicon glyphicon-arrow-down"></span>'
69
+ # nochange_html: '<span class="glyphicon glyphicon-minus"></span>'
70
+ def icon
71
+ case
72
+ when positive?
73
+ t 'comparison.icons.positive_html'
74
+ when negative?
75
+ t 'comparison.icons.negative_html'
76
+ else
77
+ t 'comparison.icons.nochange_html'
78
+ end
79
+ end
80
+
81
+ ##
82
+ # Returns the I18n translation for `comparison.icons`. (See also `#icon`.)
83
+ #
84
+ # This method is intended to display a graphical representation of the
85
+ # comparison. Typically this would be an arrow pointing up or down.
86
+ #
87
+ # The default implementation is as follows:
88
+ #
89
+ # en:
90
+ # comparison:
91
+ # arrows:
92
+ # positive_html: '&uarr;'
93
+ # negative_html: '&darr;'
94
+ # nochange_html: ''
95
+ #
96
+ # For example, to generate up and down arrows using Glyphicons included
97
+ # with Bootstrap, you could add the following translations to your
98
+ # application:
99
+ #
100
+ # `#arrows` and its sister method `#icon` perform roughly identical tasks
101
+ # with roughly identical intentions. The difference between the two methods
102
+ # is in the context in which they are intended to be used.
103
+ #
104
+ # `#arrows` is meant to be used from view contexts with limited
105
+ # functionality such as an HTML email. As such, the translations you
106
+ # specify should be simple enough, like HTML character entities, to work
107
+ # within said view context.
108
+ #
109
+ # `#icons` is meant to be used from full-featured view contexts. As such,
110
+ # `#icons` is the one to use to generate HTML tags.
111
+ def arrow
112
+ case
113
+ when positive?
114
+ t 'comparison.arrows.positive_html', default: ARROWS[:up]
115
+ when negative?
116
+ t 'comparison.arrows.negative_html', default: ARROWS[:down]
117
+ else
118
+ t 'comparison.arrows.nochange_html', default: ARROWS[:none]
119
+ end
120
+ end
121
+
122
+ ##
123
+ # Returns the I18n translation for `comparison.classes`. (See also `#css`.)
124
+ #
125
+ # Use these translations to specify CSS classes for tags that contain
126
+ # comparison data. For example:
127
+ #
128
+ # en:
129
+ # comparison:
130
+ # classes:
131
+ # positive: 'comparison positive'
132
+ # negative: 'comparison negative'
133
+ # nochange: 'comparison nochange'
134
+ #
135
+ # .comparison.positive {
136
+ # color: #3c763d;
137
+ # background-color: #dff0d8;
138
+ # }
139
+ # .comparison.negative {
140
+ # color: #a94442;
141
+ # background-color: #f2dede;
142
+ # }
143
+ # .comparison.nochange {
144
+ # color: #777777;
145
+ # }
146
+ #
147
+ # content_tag cmp.difference, class: cmp.classes
148
+ def classes
149
+ case
150
+ when positive?
151
+ t 'comparison.classes.positive'
152
+ when negative?
153
+ t 'comparison.classes.negative'
154
+ else
155
+ t 'comparison.classes.nochange'
156
+ end
157
+ end
158
+
159
+ ##
160
+ # Returns the I18n translation for `comparison.css`. (See also `#classes`.)
161
+ #
162
+ # Use these translations to specify raw CSS style rules to be used when
163
+ # formatting comparison data. For example:
164
+ #
165
+ # en:
166
+ # comparison:
167
+ # css:
168
+ # positive: 'color: #3c763d; background-color: #dff0d8;'
169
+ # negative: 'color: #a94442; background-color: #f2dede;'
170
+ # nochange: 'color: #777777;'
171
+ #
172
+ # content_tag cmp.difference, style: cmp.css
173
+ #
174
+ # `#css` and its sister method `#classes` perform very similar tasks. Use
175
+ # `#css` when you need to embed the CSS style rules in an HTML tag using
176
+ # the style attribute. Use `#classes` when you want have the CSS style
177
+ # rules defined in a class and want to add that class to the HTML tag.
178
+ def css
179
+ case
180
+ when positive?
181
+ t 'comparison.css.positive', default: ''
182
+ when negative?
183
+ t 'comparison.css.negative', default: ''
184
+ else
185
+ t 'comparison.css.nochange', default: ''
186
+ end
187
+ end
188
+
189
+ alias_method :style, :css
190
+ end
191
+ end
@@ -0,0 +1,3 @@
1
+ module Comparison
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :comparison do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: comparison
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - John Parker
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-09-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '6.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '4.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '6.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: minitest-focus
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: pry-rails
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ description: Helpers for displaying details of comparing two numbers.
62
+ email:
63
+ - jparker@urgetopunt.com
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - MIT-LICENSE
69
+ - README.md
70
+ - Rakefile
71
+ - app/helpers/comparison/application_helper.rb
72
+ - config/locales/en.yml
73
+ - lib/comparison.rb
74
+ - lib/comparison/comparator.rb
75
+ - lib/comparison/engine.rb
76
+ - lib/comparison/presenter.rb
77
+ - lib/comparison/version.rb
78
+ - lib/tasks/comparison_tasks.rake
79
+ homepage: https://github.com/jparker/comparison
80
+ licenses:
81
+ - MIT
82
+ metadata: {}
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubyforge_project:
99
+ rubygems_version: 2.6.6
100
+ signing_key:
101
+ specification_version: 4
102
+ summary: Helpers for displaying details of comparing two numbers.
103
+ test_files: []