comparison 0.1.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.
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: []