percent 0.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6ba54ea6f9cd4071338360bee6e073c87710f6f9
4
+ data.tar.gz: 14d795b2df55dfa1ed3eb4629061b68b533878cd
5
+ SHA512:
6
+ metadata.gz: 4924d7c67df23882936c2a1a5a2c3687128bf6b71c138d9ce6c6021a201916f000b5e0e930acf7b78f674e6ea914ce1e2822fdf79796699eaeff369e6c3f1b9d
7
+ data.tar.gz: 17bc724e07383b35fb7c6c8f8372ddfbad765a850762cf855b31b860cd27897f55428b5d77fd2f5425c6c7bf225065626ec686e7e784f4d8a925514dc5611c89
@@ -0,0 +1,9 @@
1
+ require 'percentage'
2
+ require 'percent/hooks'
3
+
4
+ module Percent
5
+ end
6
+
7
+ if defined? Rails
8
+ require 'percent/railtie'
9
+ end
@@ -0,0 +1,11 @@
1
+ module Percent
2
+ module ActiveModel
3
+ class PercentageValidator < ::ActiveModel::Validations::NumericalityValidator
4
+ def validate_each(record, attr, value)
5
+ super record, attr, (value.nil? ? value : value.to_decimal)
6
+ end
7
+ end
8
+ end
9
+ end
10
+
11
+ ActiveModel::Validations::PercentageValidator = Percent::ActiveModel::PercentageValidator
@@ -0,0 +1,21 @@
1
+ module Percent
2
+ module ActiveRecord
3
+ module MigrationExtensions
4
+ module Options
5
+ def self.without_table(accessor, options = {})
6
+ column_name = accessor.to_s + '_fraction'
7
+ options[:null] ||= false
8
+ options[:default] ||= 0
9
+ type = :decimal
10
+
11
+ [column_name, type, options]
12
+ end
13
+
14
+ def self.with_table(table_name, accessor, options = {})
15
+ options = self.without_table accessor, options
16
+ options.unshift table_name
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ module Percent
2
+ module ActiveRecord
3
+ module MigrationExtensions
4
+ module SchemaStatements
5
+ def add_percentage(table_name, accessor, options={})
6
+ *opts = Options.with_table table_name, accessor, options
7
+ add_column *opts
8
+ end
9
+
10
+ def remove_percentage(table_name, accessor, options={})
11
+ *opts = Options.with_table table_name, accessor, options
12
+ remove_column *opts
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,18 @@
1
+ module Percent
2
+ module ActiveRecord
3
+ module MigrationExtensions
4
+ module Table
5
+ def percentage(accessor, options={})
6
+ *opts = Options.without_table accessor, options
7
+ column *opts
8
+ end
9
+
10
+ def remove_percentage(accessor, options={})
11
+ *opts = Options.without_table accessor, options
12
+ remove *opts
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,71 @@
1
+ module Percent
2
+ module ActiveRecord
3
+ module Percentizable
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def percentize(*columns)
8
+ # initialize options
9
+ options = columns.extract_options!
10
+
11
+ columns.each do |column|
12
+ column_name = column.to_s
13
+
14
+ # determine attribute name
15
+ if options[:as]
16
+ attribute_name = options[:as]
17
+ elsif column_name =~ /_fraction$/
18
+ attribute_name = column_name.sub /_fraction$/, ""
19
+ else
20
+ attribute_name = column_name + '_percent'
21
+ end
22
+
23
+ # validation
24
+ unless options[:disable_validation]
25
+ allow_nil = options.fetch :allow_nil, false
26
+ numericality = options[:numericality]
27
+ fraction = options[:fraction_numericality]
28
+
29
+ if numericality || !options.key?(:numericality)
30
+ numericality = numericality_range 0, 100 unless fraction.is_a? Hash
31
+
32
+ validates attribute_name, {
33
+ allow_nil: allow_nil,
34
+ 'percent/active_model/percentage': numericality
35
+ }
36
+ end
37
+
38
+ if fraction || !options.key?(:fraction_numericality)
39
+ unless numericality.is_a?(Hash) && options.key?(:numericality)
40
+ fraction = numericality_range 0, 1
41
+ end
42
+
43
+ validates column_name, allow_nil: allow_nil, numericality: fraction
44
+ end
45
+ end
46
+
47
+ # percent attribute getter
48
+ define_method attribute_name do
49
+ value = public_send column_name
50
+ value.nil? ? value : Percentage.from_fraction(value, options)
51
+ end
52
+
53
+ # percent attribute setter
54
+ define_method "#{attribute_name}=" do |value|
55
+ unless value.nil? || value.is_a?(Percentage)
56
+ value = Percentage.from_amount value, options
57
+ end
58
+ public_send "#{column_name}=", (value.nil? ? value : value.to_d)
59
+ end
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def numericality_range(min, max)
66
+ { greater_than_or_equal_to: min, less_than_or_equal_to: max }
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,18 @@
1
+ module Percent
2
+ class Hooks
3
+ def self.init
4
+ ActiveSupport.on_load(:active_record) do
5
+ require 'percent/active_model/percentage_validator'
6
+ require 'percent/active_record/percentizable'
7
+ ::ActiveRecord::Base.send :include, Percent::ActiveRecord::Percentizable
8
+
9
+ require 'percent/active_record/migration_extensions/options'
10
+ require 'percent/active_record/migration_extensions/schema_statements'
11
+ require 'percent/active_record/migration_extensions/table'
12
+ ::ActiveRecord::Migration.send :include, Percent::ActiveRecord::MigrationExtensions::SchemaStatements
13
+ ::ActiveRecord::ConnectionAdapters::TableDefinition.send :include, Percent::ActiveRecord::MigrationExtensions::Table
14
+ ::ActiveRecord::ConnectionAdapters::Table.send :include, Percent::ActiveRecord::MigrationExtensions::Table
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ module Percent
2
+ class Railtie < ::Rails::Railtie
3
+ initializer 'percent.initialize' do
4
+ Percent::Hooks.init
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,127 @@
1
+ require 'bigdecimal'
2
+ require 'bigdecimal/util'
3
+
4
+ class Percentage < Numeric
5
+ def initialize(val = 0, options = {})
6
+ val = 0.0 if !val
7
+ val = 1.0 if val == true
8
+ val = val.to_r if val.is_a?(String) && val['/']
9
+ val = val.to_f if val.is_a?(Complex) || val.is_a?(Rational)
10
+ val = val.to_i if val.is_a?(String) && !val['.']
11
+ val = val.to_d / 100 if val.is_a?(Integer) || (val.is_a?(String) && val['%'])
12
+ val = val.value if val.is_a? self.class
13
+
14
+ @value = val.to_d
15
+ end
16
+
17
+ ###
18
+ # Attributes
19
+ ###
20
+ def value
21
+ @value ||= 0
22
+ end
23
+
24
+ ###
25
+ # Convert percentage to different number formats
26
+ ###
27
+ def to_i; (self.value * 100).to_i; end
28
+ def to_c; self.value.to_c; end
29
+ def to_d; self.value.to_d; end
30
+ def to_f; self.value.to_f; end
31
+ def to_r; self.value.to_r; end
32
+
33
+ ###
34
+ # Convert percentage fraction to percent amount
35
+ ###
36
+ def to_complex; (self.value * 100).to_c; end
37
+ def to_decimal; (self.value * 100).to_d; end
38
+ def to_float; (self.value * 100).to_f; end
39
+ def to_rational; (self.value * 100).to_r; end
40
+
41
+ ###
42
+ # String conversion methods
43
+ ###
44
+ def to_s; self.to_amount.to_s; end
45
+ def to_str; self.to_f.to_s; end
46
+ def to_string; self.to_s + '%'; end
47
+
48
+ def format(options = {})
49
+ # set defaults; all other options default to false
50
+ options[:percent_sign] = options.fetch :percent_sign, true
51
+
52
+ if options[:as_decimal]
53
+ return self.to_str
54
+ elsif options[:rounded]
55
+ string = self.to_float.round.to_s
56
+ elsif options[:no_decimal]
57
+ string = self.to_i.to_s
58
+ elsif options[:no_decimal_if_whole]
59
+ string = self.to_s
60
+ else
61
+ string = self.to_float.to_s
62
+ end
63
+
64
+ string += ' ' if options[:space_before_sign]
65
+ string += '%' if options[:percent_sign]
66
+ return string
67
+ end
68
+
69
+ ###
70
+ # Additional conversion methods
71
+ ###
72
+ def to_amount
73
+ (int = self.to_i) == (float = self.to_float) ? int : float
74
+ end
75
+
76
+ def inspect
77
+ "#<#{self.class.name}:#{self.object_id}, #{self.value.inspect}>"
78
+ end
79
+
80
+ ###
81
+ # Comparisons
82
+ ###
83
+ def == other
84
+ self.eql?(other) || self.to_f == other
85
+ end
86
+
87
+ def eql? other
88
+ self.class == other.class && self.value == other.value
89
+ end
90
+
91
+ def <=> other
92
+ self.to_f <=> other.to_f
93
+ end
94
+
95
+ ###
96
+ # Mathematical operations
97
+ ###
98
+ [:+, :-, :/, :*].each do |operator|
99
+ define_method operator do |other|
100
+ case other
101
+ when Percentage
102
+ new_value = self.value.public_send(operator, other.value)
103
+ self.class.new new_value
104
+ else
105
+ self.value.to_f.public_send(operator, other)
106
+ end
107
+ end
108
+ end
109
+
110
+ ###
111
+ # Additional initialization methods
112
+ ###
113
+ def self.from_fraction(val = 0, options = {})
114
+ val = val.to_i if val.is_a?(String) && !(val['/'] || val['%'] || val['.'])
115
+ val = val.to_d if val.is_a?(Integer)
116
+
117
+ self.new val, options
118
+ end
119
+
120
+ def self.from_amount(val = 0, options = {})
121
+ val = val.to_r if val.is_a?(String) && val['/']
122
+ val = val.to_d if val.is_a?(String)
123
+ val = val / 100 if val.is_a?(Numeric) && !val.integer?
124
+
125
+ self.new val, options
126
+ end
127
+ end
@@ -0,0 +1,25 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'percent'
3
+ s.version = '0.0.1'
4
+ s.platform = Gem::Platform::RUBY
5
+ s.license = 'MIT'
6
+ s.authors = ['Joe Kennedy']
7
+ s.email = ['joseph.stephen.kennedy@gmail.com']
8
+ s.summary = 'Percent objects and integration with Rails'
9
+ s.homepage = 'https://github.com/JoeKennedy/percent'
10
+
11
+ s.files = Dir.glob('{lib,spec}/**/*') + %w(percent.gemspec)
12
+ s.files.delete 'spec/percent.sqlite3'
13
+
14
+ s.test_files = s.files.grep(%r{^spec/})
15
+
16
+ s.require_path = 'lib'
17
+
18
+ s.add_dependency 'activesupport', '~> 4.2'
19
+
20
+ s.add_development_dependency 'activerecord', '~> 4.2'
21
+ s.add_development_dependency 'bundler', '~> 1.11'
22
+ s.add_development_dependency 'rake', '~> 10'
23
+ s.add_development_dependency 'rspec', '~> 3.0'
24
+ s.add_development_dependency 'sqlite3', '~> 1.3'
25
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ class JobTracker < ActiveRecord::Base; end
4
+
5
+ if defined? ActiveRecord
6
+ describe Percent::ActiveRecord::MigrationExtensions::SchemaStatements do
7
+ before :all do
8
+ @connection = ActiveRecord::Base.connection
9
+ @connection.send :extend, Percent::ActiveRecord::MigrationExtensions::SchemaStatements
10
+
11
+ @connection.drop_table :job_trackers if @connection.table_exists? :job_trackers
12
+ @connection.create_table :job_trackers
13
+
14
+ @options = { default: 1, null: true }
15
+
16
+ @connection.add_percentage :job_trackers, :percentage
17
+ @connection.add_percentage :job_trackers, :full_options, @options
18
+
19
+ JobTracker.reset_column_information
20
+ end
21
+
22
+ describe '#add_percentage' do
23
+ context 'default options' do
24
+ subject { JobTracker.columns_hash['percentage_fraction'] }
25
+
26
+ it 'should default to 0' do
27
+ expect(subject.default).to eql '0'
28
+ expect(JobTracker.new.public_send subject.name).to eql 0
29
+ end
30
+
31
+ it 'should not allow null values' do
32
+ expect(subject.null).to be false
33
+ end
34
+
35
+ it 'should be of type decimal' do
36
+ expect(subject.type).to eql :decimal
37
+ end
38
+ end
39
+
40
+ context 'full options' do
41
+ subject { JobTracker.columns_hash['full_options_fraction'] }
42
+
43
+ it 'should default to 1' do
44
+ expect(subject.default).to eql '1'
45
+ expect(JobTracker.new.public_send subject.name).to eql 1
46
+ end
47
+
48
+ it 'should allow null values' do
49
+ expect(subject.null).to be true
50
+ end
51
+
52
+ it 'should be of type decimal' do
53
+ expect(subject.type).to eql :decimal
54
+ end
55
+ end
56
+ end
57
+
58
+ describe '#remove_percentage' do
59
+ before :all do
60
+ @connection.remove_percentage :job_trackers, :percentage
61
+ @connection.remove_percentage :job_trackers, :full_options, @options
62
+
63
+ JobTracker.reset_column_information
64
+ end
65
+
66
+ it 'should remove percentage columns' do
67
+ expect(JobTracker.columns_hash['percentage_fraction']).to be nil
68
+ expect(JobTracker.columns_hash['full_options_fraction']).to be nil
69
+ end
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ class JobTracker < ActiveRecord::Base; end
4
+
5
+ if defined? ActiveRecord
6
+ describe Percent::ActiveRecord::MigrationExtensions::Table do
7
+ before :all do
8
+ @connection = ActiveRecord::Base.connection
9
+ @connection.send :extend, Percent::ActiveRecord::MigrationExtensions::SchemaStatements
10
+
11
+ @options = { default: 1, null: true }
12
+
13
+ @connection.drop_table :job_trackers if @connection.table_exists? :job_trackers
14
+ @connection.create_table :job_trackers do |t|
15
+ t.percentage :percentage
16
+ t.percentage :full_options, @options
17
+ end
18
+
19
+ JobTracker.reset_column_information
20
+ end
21
+
22
+ describe '#percentage' do
23
+ context 'default options' do
24
+ subject { JobTracker.columns_hash['percentage_fraction'] }
25
+
26
+ it 'should default to 0' do
27
+ expect(subject.default).to eql '0'
28
+ expect(JobTracker.new.public_send subject.name).to eql 0
29
+ end
30
+
31
+ it 'should not allow null values' do
32
+ expect(subject.null).to be false
33
+ end
34
+
35
+ it 'should be of type decimal' do
36
+ expect(subject.type).to eql :decimal
37
+ end
38
+ end
39
+
40
+ context 'full options' do
41
+ subject { JobTracker.columns_hash['full_options_fraction'] }
42
+
43
+ it 'should default to 1' do
44
+ expect(subject.default).to eql '1'
45
+ expect(JobTracker.new.public_send subject.name).to eql 1
46
+ end
47
+
48
+ it 'should allow null values' do
49
+ expect(subject.null).to be true
50
+ end
51
+
52
+ it 'should be of type decimal' do
53
+ expect(subject.type).to eql :decimal
54
+ end
55
+ end
56
+ end
57
+
58
+ describe '#remove_percentage' do
59
+ before :all do
60
+ @connection.change_table :job_trackers do |t|
61
+ t.remove_percentage :percentage
62
+ t.remove_percentage :full_options, @options
63
+ end
64
+
65
+ JobTracker.reset_column_information
66
+ end
67
+
68
+ it 'should remove percentage columns' do
69
+ expect(JobTracker.columns_hash['percentage_fraction']).to be nil
70
+ expect(JobTracker.columns_hash['full_options_fraction']).to be nil
71
+ end
72
+ end
73
+ end
74
+ end