acts_as_decimal 1.0.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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Codegram
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.rdoc ADDED
@@ -0,0 +1,44 @@
1
+ = acts_as_decimal
2
+
3
+ A simple gem for Rails 3 to make an attribute behave like it is floating point, being stored as an integer in the database.
4
+
5
+ Add it to your Gemfile:
6
+
7
+ gem 'acts_as_decimal'
8
+
9
+ And put this in your model, let's say a Product with a :price attribute:
10
+
11
+ class Product < ActiveRecord::Base
12
+ acts_as_decimal :price # Defaults to 2 decimal floating point values, or...
13
+ acts_as_decimal :price, :decimals => 5 # ...as you wish!
14
+ end
15
+
16
+ Now you store and retrieve :price as a floating point:
17
+
18
+ product = Product.new
19
+ product.price = 12.30
20
+ product.price # => 12.30
21
+
22
+ But you still have access to the raw database integer value through :price_raw:
23
+
24
+ product.price_raw # => 1230
25
+ product.price_raw = 4309 # product.price == 43.09
26
+
27
+ Furthermore, you get some nice humanizers for free. Note that all humanizers return a string, not a float.
28
+
29
+ product = Product.new
30
+ product.price = 3900400.40
31
+ product.humanized_price # => "3.900.400,40"
32
+ product.humanized_price(:thousand_delimiters => false) # => "3900400.40"
33
+
34
+ == Note on Patches/Pull Requests
35
+
36
+ * Fork the project.
37
+ * Make your feature addition or bug fix.
38
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
39
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
40
+ * Send me a pull request. Bonus points for topic branches.
41
+
42
+ == Copyright
43
+
44
+ Copyright (c) 2010 Codegram. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "acts_as_decimal"
8
+ gem.summary = "Treat an attribute as a decimal, storing it as an integer in the database."
9
+ gem.description = "Rails 3 gem to treat an attribute as a decimal (storing and retrieving floating-point values) but storing it as an integer in the database (useful for prices and other money attributes)."
10
+ gem.email = "info@codegram.com"
11
+ gem.homepage = "http://github.com/codegram/acts_as_decimal"
12
+ gem.authors = ["Oriol Gual", "Josep Mª Bach", "Josep Jaume Rey"]
13
+
14
+ gem.add_dependency 'activerecord', '>= 3.0.0.beta4'
15
+
16
+ gem.add_development_dependency "rspec", '>= 2.0.0.beta.12'
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ # Rake RSpec2 task stuff
24
+ gem 'rspec', '>= 2.0.0.beta.12'
25
+ gem 'rspec-expectations'
26
+
27
+ require 'rspec/core/rake_task'
28
+
29
+ desc "Run the specs under spec"
30
+ RSpec::Core::RakeTask.new do |t|
31
+
32
+ end
33
+
34
+ task :default => :spec
35
+
36
+ require 'rake/rdoctask'
37
+ Rake::RDocTask.new do |rdoc|
38
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
39
+
40
+ rdoc.rdoc_dir = 'rdoc'
41
+ rdoc.title = "acts_as_decimal #{version}"
42
+ rdoc.rdoc_files.include('README*')
43
+ rdoc.rdoc_files.include('lib/**/*.rb')
44
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,61 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{acts_as_decimal}
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Oriol Gual", "Josep M\302\252 Bach", "Josep Jaume Rey"]
12
+ s.date = %q{2010-06-23}
13
+ s.description = %q{Rails 3 gem to treat an attribute as a decimal (storing and retrieving floating-point values) but storing it as an integer in the database (useful for prices and other money attributes).}
14
+ s.email = %q{info@codegram.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ ".rspec",
23
+ "LICENSE",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "acts_as_decimal.gemspec",
28
+ "lib/acts_as_decimal.rb",
29
+ "lib/acts_as_decimal/acts_as_decimal.rb",
30
+ "spec/acts_as_decimal_spec.rb",
31
+ "spec/model_builder.rb",
32
+ "spec/spec_helper.rb"
33
+ ]
34
+ s.homepage = %q{http://github.com/codegram/acts_as_decimal}
35
+ s.rdoc_options = ["--charset=UTF-8"]
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = %q{1.3.6}
38
+ s.summary = %q{Treat an attribute as a decimal, storing it as an integer in the database.}
39
+ s.test_files = [
40
+ "spec/acts_as_decimal_spec.rb",
41
+ "spec/model_builder.rb",
42
+ "spec/spec_helper.rb"
43
+ ]
44
+
45
+ if s.respond_to? :specification_version then
46
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
47
+ s.specification_version = 3
48
+
49
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
50
+ s.add_runtime_dependency(%q<activerecord>, [">= 3.0.0.beta4"])
51
+ s.add_development_dependency(%q<rspec>, [">= 2.0.0.beta.12"])
52
+ else
53
+ s.add_dependency(%q<activerecord>, [">= 3.0.0.beta4"])
54
+ s.add_dependency(%q<rspec>, [">= 2.0.0.beta.12"])
55
+ end
56
+ else
57
+ s.add_dependency(%q<activerecord>, [">= 3.0.0.beta4"])
58
+ s.add_dependency(%q<rspec>, [">= 2.0.0.beta.12"])
59
+ end
60
+ end
61
+
@@ -0,0 +1,4 @@
1
+ require 'acts_as_decimal/acts_as_decimal'
2
+ require 'active_record'
3
+
4
+ ActiveRecord::Base.send :include, ActsAsDecimal
@@ -0,0 +1,56 @@
1
+ # ActsAsDecimal
2
+ module ActsAsDecimal
3
+ require 'bigdecimal'
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ # Implements integer handling as floating numbers.
11
+ # Usage:
12
+ #
13
+ # Inside a model with a price or amount field, simply put
14
+ #
15
+ # acts_as_decimal field_name, :decimals => 2
16
+ #
17
+ def acts_as_decimal(attr_name, options = {:decimals => 2})
18
+ fields = [attr_name] unless attr_name.is_a? Array
19
+ fields.each do |field|
20
+ class_eval <<-EOC
21
+ def #{field}
22
+ (self[:#{field}].nil? ? nil : (BigDecimal.new(self[:#{field}].to_s) / BigDecimal('10').power(#{options[:decimals]})).to_f)
23
+ end
24
+ def humanized_#{field}(options = {:thousand_delimiters => true})
25
+
26
+ a = #{field}.to_s.split('.')
27
+ b = a[1].ljust(2,'0')
28
+
29
+ if options[:thousand_delimiters] == false
30
+ return a[0] + "." + b
31
+ else
32
+ groups = a[0].reverse.scan(/\\d{3}/)
33
+ rest = a[0].gsub(groups.join.reverse, '').reverse
34
+ groups << rest unless rest.empty?
35
+ return groups.join('.').reverse + "," + b
36
+ end
37
+
38
+ end
39
+ def #{field}=(decimalnum)
40
+ self[:#{field}] = (decimalnum.nil? || !decimalnum.is_a?(Numeric) ? nil : (BigDecimal.new(decimalnum.to_s) * BigDecimal('10').power(#{options[:decimals]})).to_i )
41
+ end
42
+
43
+ def #{field}_raw
44
+ self[:#{field}]
45
+ end
46
+ def #{field}_raw=(intnum)
47
+ self[:#{field}] = intnum
48
+ end
49
+ EOC
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ create_table "products" do end
4
+
5
+ define_model('Product', :name => :string, :price => :integer)
6
+
7
+ class Product < ActiveRecord::Base
8
+ acts_as_decimal :price, :decimals => 2
9
+ end
10
+
11
+ describe Product do
12
+
13
+ context "with a price of 10.30" do
14
+
15
+ before(:all) do
16
+ Product.acts_as_decimal :price
17
+ end
18
+
19
+ subject { Product.new(:price => 10.30)}
20
+
21
+ it "has a price of 10.30" do
22
+ subject.price.should == 10.30
23
+ end
24
+
25
+ it { should respond_to(:price_raw) }
26
+
27
+ it "has a price_raw of 1030" do
28
+ subject.price_raw.should == 1030
29
+ end
30
+
31
+ it "stores 1030 to the database" do
32
+ subject.attributes["price"].should == 1030
33
+ end
34
+
35
+ it "handles nil values with dignity" do
36
+ subject.price = nil
37
+
38
+ subject.price.should be_nil
39
+ subject.price_raw.should be_nil
40
+ end
41
+
42
+ end
43
+
44
+ describe "humanized helpers", "when retrieving the humanized value (as a string)" do
45
+
46
+ context "with a 3.900.400,00 price" do
47
+
48
+ subject { Product.new(:price => 3900400.00) }
49
+
50
+ it "has a humanized_price of 3.900.400,00" do
51
+ subject.humanized_price.should == "3.900.400,00"
52
+ end
53
+
54
+ it "has a humanized_price(:thousand_delimiters => false) of 3900400.00" do
55
+ subject.humanized_price(:thousand_delimiters => false).should == "3900400.00"
56
+ end
57
+
58
+ end
59
+
60
+ context "with a 20.000,00 price" do
61
+
62
+ subject { Product.new(:price => 20000.00) }
63
+
64
+ it "has a humanized_price of 20.000,00" do
65
+ subject.humanized_price.should == "20.000,00"
66
+ end
67
+
68
+ it "has a humanized_price(:thousand_delimiters => false) of 20000.00" do
69
+ subject.humanized_price(:thousand_delimiters => false).should == "20000.00"
70
+ end
71
+
72
+ end
73
+
74
+ context "with a 900,00 price" do
75
+
76
+ subject { Product.new(:price => 900.00) }
77
+
78
+ it "has a humanized_price of 900,00" do
79
+ subject.humanized_price.should == "900,00"
80
+ end
81
+
82
+ it "has a humanized_price(:thousand_delimiters => false) of 900.00" do
83
+ subject.humanized_price(:thousand_delimiters => false).should == "900.00"
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+
90
+ context "with a price of 10.30452 and specifying :decimals => 5" do
91
+
92
+ before(:all) do
93
+ Product.acts_as_decimal :price, :decimals => 5
94
+ end
95
+
96
+ subject { Product.new(:price => 10.30452) }
97
+
98
+ it "has a price of 10.30452" do
99
+ subject.price.should == 10.30452
100
+ end
101
+
102
+ it "has a price_raw of 10304542" do
103
+ subject.price_raw.should == 1030452
104
+ end
105
+
106
+ it "stores 1030452 to the database" do
107
+ subject.attributes["price"].should == 1030452
108
+ end
109
+
110
+ end
111
+
112
+ end
@@ -0,0 +1,100 @@
1
+ # This is based on Remarkable based on Shoulda model builder for Test::Unit.
2
+ #
3
+ module ModelBuilder
4
+ def self.included(base)
5
+ return unless base.name =~ /^Spec/
6
+
7
+ base.class_eval do
8
+ after(:each) do
9
+ if @defined_constants
10
+ @defined_constants.each do |class_name|
11
+ Object.send(:remove_const, class_name)
12
+ end
13
+ end
14
+
15
+ if @created_tables
16
+ @created_tables.each do |table_name|
17
+ ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS #{table_name}")
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ base.extend ClassMethods
24
+ end
25
+
26
+ def create_table(table_name, &block)
27
+ connection = ActiveRecord::Base.connection
28
+
29
+ begin
30
+ connection.execute("DROP TABLE IF EXISTS #{table_name}")
31
+ connection.create_table(table_name, &block)
32
+ @created_tables ||= []
33
+ @created_tables << table_name
34
+ connection
35
+ rescue Exception => e
36
+ connection.execute("DROP TABLE IF EXISTS #{table_name}")
37
+ raise e
38
+ end
39
+ end
40
+
41
+ def define_constant(class_name, base, &block)
42
+ class_name = class_name.to_s.camelize
43
+
44
+ klass = Class.new(base)
45
+ Object.const_set(class_name, klass)
46
+
47
+ klass.class_eval(&block) if block_given?
48
+
49
+ @defined_constants ||= []
50
+ @defined_constants << class_name
51
+
52
+ klass
53
+ end
54
+
55
+ def define_model_class(class_name, &block)
56
+ define_constant(class_name, ActiveRecord::Base, &block)
57
+ end
58
+
59
+ def define_model(name, columns = {}, &block)
60
+ class_name = name.to_s.pluralize.classify
61
+ table_name = class_name.tableize
62
+
63
+ table = columns.delete(:table) || lambda {|table|
64
+ columns.each do |name, type|
65
+ table.column name, *type
66
+ end
67
+ }
68
+
69
+ create_table(table_name, &table)
70
+
71
+ klass = define_model_class(class_name, &block)
72
+ instance = klass.new
73
+
74
+ self.class.subject { instance } if self.class.respond_to?(:subject)
75
+ instance
76
+ end
77
+
78
+ module ClassMethods
79
+ # This is a macro to run validations of boolean optionals such as :allow_nil
80
+ # and :allow_blank. This macro tests all scenarios. The specs must have a
81
+ # define_and_validate method defined.
82
+ #
83
+ def create_optional_boolean_specs(optional, base, options={})
84
+ base.describe "with #{optional} option" do
85
+ it { should define_and_validate(options.merge(optional => true)).send(optional) }
86
+ it { should define_and_validate(options.merge(optional => false)).send(optional, false) }
87
+ it { should_not define_and_validate(options.merge(optional => true)).send(optional, false) }
88
+ it { should_not define_and_validate(options.merge(optional => false)).send(optional) }
89
+ end
90
+ end
91
+
92
+ def create_message_specs(base)
93
+ base.describe "with message option" do
94
+ it { should define_and_validate(:message => 'valid_message').message('valid_message') }
95
+ it { should_not define_and_validate(:message => 'not_valid').message('valid_message') }
96
+ end
97
+ end
98
+ end
99
+
100
+ end
@@ -0,0 +1,21 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'rubygems'
5
+ require 'active_record'
6
+
7
+ require 'lib/acts_as_decimal'
8
+ require 'rspec'
9
+ require 'rspec/autorun'
10
+
11
+ RAILS_ENV = "test"
12
+ RAILS_VERSION = ENV['RAILS_VERSION'] || '3.0.0.beta4'
13
+
14
+ ActiveRecord::Base.establish_connection(
15
+ :adapter => 'sqlite3',
16
+ :database => ':memory:'
17
+ )
18
+
19
+ dir = File.dirname(__FILE__)
20
+ require File.join(dir, "model_builder")
21
+ include ModelBuilder
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acts_as_decimal
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 0
9
+ version: 1.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Oriol Gual
13
+ - "Josep M\xC2\xAA Bach"
14
+ - Josep Jaume Rey
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-06-23 00:00:00 +02:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: activerecord
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ segments:
30
+ - 3
31
+ - 0
32
+ - 0
33
+ - beta4
34
+ version: 3.0.0.beta4
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: rspec
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ segments:
45
+ - 2
46
+ - 0
47
+ - 0
48
+ - beta
49
+ - 12
50
+ version: 2.0.0.beta.12
51
+ type: :development
52
+ version_requirements: *id002
53
+ description: Rails 3 gem to treat an attribute as a decimal (storing and retrieving floating-point values) but storing it as an integer in the database (useful for prices and other money attributes).
54
+ email: info@codegram.com
55
+ executables: []
56
+
57
+ extensions: []
58
+
59
+ extra_rdoc_files:
60
+ - LICENSE
61
+ - README.rdoc
62
+ files:
63
+ - .document
64
+ - .gitignore
65
+ - .rspec
66
+ - LICENSE
67
+ - README.rdoc
68
+ - Rakefile
69
+ - VERSION
70
+ - acts_as_decimal.gemspec
71
+ - lib/acts_as_decimal.rb
72
+ - lib/acts_as_decimal/acts_as_decimal.rb
73
+ - spec/acts_as_decimal_spec.rb
74
+ - spec/model_builder.rb
75
+ - spec/spec_helper.rb
76
+ has_rdoc: true
77
+ homepage: http://github.com/codegram/acts_as_decimal
78
+ licenses: []
79
+
80
+ post_install_message:
81
+ rdoc_options:
82
+ - --charset=UTF-8
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ segments:
90
+ - 0
91
+ version: "0"
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ segments:
97
+ - 0
98
+ version: "0"
99
+ requirements: []
100
+
101
+ rubyforge_project:
102
+ rubygems_version: 1.3.6
103
+ signing_key:
104
+ specification_version: 3
105
+ summary: Treat an attribute as a decimal, storing it as an integer in the database.
106
+ test_files:
107
+ - spec/acts_as_decimal_spec.rb
108
+ - spec/model_builder.rb
109
+ - spec/spec_helper.rb