has_price 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ doc
20
+ pkg
21
+ .yardoc
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Maxim Chernyak
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,86 @@
1
+ has_price
2
+ =========
3
+
4
+ Let's just say, it organizes your price breakdowns and allows for easy retrieval of price subgroups and subtotals, as well as simple serialization for your receipts.
5
+
6
+ Install
7
+ -------
8
+
9
+ Make sure [gemcutter.org](http://gemcutter.org) is in your sources.
10
+
11
+ <pre>
12
+ sudo gem install has_price
13
+ </pre>
14
+
15
+ In rails environment:
16
+ <pre>
17
+ config.gem "has_price"
18
+ </pre>
19
+
20
+ For any generic Ruby class:
21
+ <pre>
22
+ require 'has_price'
23
+ include HasPrice::HasPrice
24
+ </pre>
25
+
26
+ P.S. Usage as Rails plugin is supported too, but gem is preferred.
27
+
28
+ Organize
29
+ --------
30
+
31
+ Say you have a Product class with some attributes which price depends on. For this example assume that base_price, federal_tax, and state_tax are integer attributes existing on Product model.
32
+
33
+ <pre lang="ruby">
34
+ class Product < ActiveRecord::Base
35
+ has_many :discounts
36
+ end
37
+ </pre>
38
+
39
+ has_price provides a small DSL with two methods, `item` and `group`, to help you organize this.
40
+
41
+ <pre lang="ruby">
42
+ class Product < ActiveRecord::Base
43
+ has_many :discounts
44
+
45
+ has_price do
46
+ item base_price, "base"
47
+ group "taxes" do
48
+ item federal_tax, "federal"
49
+ item state_tax, "state"
50
+ end
51
+ group "discounts" do
52
+ discounts.each do |discount|
53
+ item discount.amount, discount.title
54
+ end
55
+ end
56
+ end
57
+ end
58
+ </pre>
59
+
60
+ What we've done just now is — built instance method `price` on products. Now you can use it as so.
61
+
62
+ <pre lang="ruby">
63
+ # Hypothetically all these numbers are coming from the above declared instance methods.
64
+
65
+ product = Product.find(1)
66
+ product.price # => Price object
67
+ product.price.total # => 500
68
+ product.price.base # => 400
69
+ product.price.taxes # => Price object
70
+ product.price.taxes.federal # => 100
71
+ product.price.taxes.total # => 200
72
+ product.discounts.total # => -100
73
+ </pre>
74
+
75
+ Serialize
76
+ ---------
77
+
78
+ Price object actually inherits from a plain old Hash. Therefore, this will work:
79
+
80
+ <pre lang="ruby">
81
+ class Receipt < ActiveRecord::Base
82
+ serialize :price, Hash
83
+ end
84
+ </pre>
85
+
86
+ Now passing the whole price breakdown into receipt is as simple as `receipt.price = product.price`.
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "has_price"
8
+ gem.summary = %Q{Provides a convenient DSL for organizing a price breakdown in a class.}
9
+ gem.description = %Q{A convenient DSL for defining complex price reader/serializer in a class and organizing a price breakdown. Price can be declared with items and groups which depend on other attributes. Price is a very simple subclass of Hash. This provides for easy serialization and flexibility in case of implementation changes. This way you can conveniently store the whole price breakdown in your serialized receipts. It also provides magic methods for convenient access, but can be fully treated as a regular Hash with some sprinkles on top.}
10
+ gem.email = "max@bitsonnet.com"
11
+ gem.homepage = "http://github.com/maxim/has_price"
12
+ gem.authors = ["Maxim Chernyak"]
13
+ gem.add_development_dependency "shoulda", ">= 0"
14
+ gem.add_development_dependency "rr"
15
+ gem.files.include %w(lib/*/**)
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
20
+ end
21
+
22
+ require 'rake/testtask'
23
+ Rake::TestTask.new(:test) do |test|
24
+ test.libs << 'lib' << 'test'
25
+ test.pattern = 'test/**/test_*.rb'
26
+ test.verbose = true
27
+ end
28
+
29
+ begin
30
+ require 'rcov/rcovtask'
31
+ Rcov::RcovTask.new do |test|
32
+ test.libs << 'test'
33
+ test.pattern = 'test/**/test_*.rb'
34
+ test.verbose = true
35
+ end
36
+ rescue LoadError
37
+ task :rcov do
38
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
39
+ end
40
+ end
41
+
42
+ task :test => :check_dependencies
43
+
44
+ task :default => :test
45
+
46
+ require 'rake/rdoctask'
47
+ Rake::RDocTask.new do |rdoc|
48
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
+
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.title = "has_price #{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
data/has_price.gemspec ADDED
@@ -0,0 +1,66 @@
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{has_price}
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 = ["Maxim Chernyak"]
12
+ s.date = %q{2009-12-02}
13
+ s.description = %q{A convenient DSL for defining complex price reader/serializer in a class and organizing a price breakdown. Price can be declared with items and groups which depend on other attributes. Price is a very simple subclass of Hash. This provides for easy serialization and flexibility in case of implementation changes. This way you can conveniently store the whole price breakdown in your serialized receipts. It also provides magic methods for convenient access, but can be fully treated as a regular Hash with some sprinkles on top.}
14
+ s.email = %q{max@bitsonnet.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "LICENSE",
22
+ "README.md",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "has_price.gemspec",
26
+ "lib/has_price.rb",
27
+ "lib/has_price/core_extensions/array.rb",
28
+ "lib/has_price/core_extensions/string.rb",
29
+ "lib/has_price/has_price.rb",
30
+ "lib/has_price/price.rb",
31
+ "lib/has_price/price_builder.rb",
32
+ "rails/init.rb",
33
+ "test/helper.rb",
34
+ "test/test_has_price.rb",
35
+ "test/test_price.rb",
36
+ "test/test_price_builder.rb"
37
+ ]
38
+ s.homepage = %q{http://github.com/maxim/has_price}
39
+ s.rdoc_options = ["--charset=UTF-8"]
40
+ s.require_paths = ["lib"]
41
+ s.rubygems_version = %q{1.3.5}
42
+ s.summary = %q{Provides a convenient DSL for organizing a price breakdown in a class.}
43
+ s.test_files = [
44
+ "test/helper.rb",
45
+ "test/test_has_price.rb",
46
+ "test/test_price.rb",
47
+ "test/test_price_builder.rb"
48
+ ]
49
+
50
+ if s.respond_to? :specification_version then
51
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
52
+ s.specification_version = 3
53
+
54
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
55
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
56
+ s.add_development_dependency(%q<rr>, [">= 0"])
57
+ else
58
+ s.add_dependency(%q<shoulda>, [">= 0"])
59
+ s.add_dependency(%q<rr>, [">= 0"])
60
+ end
61
+ else
62
+ s.add_dependency(%q<shoulda>, [">= 0"])
63
+ s.add_dependency(%q<rr>, [">= 0"])
64
+ end
65
+ end
66
+
@@ -0,0 +1,12 @@
1
+ module HasPrice
2
+ module CoreExtensions
3
+ module Array
4
+ # In case we're not in Rails.
5
+ #
6
+ # @see http://api.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Array/ExtractOptions.html#M001202
7
+ def extract_options!
8
+ last.is_a?(::Hash) ? pop : {}
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ module HasPrice
2
+ module CoreExtensions
3
+ module String
4
+ # In case we're not in Rails.
5
+ #
6
+ # @see http://api.rubyonrails.org/classes/Inflector.html#M001631
7
+ def underscore
8
+ gsub(/::/, '/').
9
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
10
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
11
+ tr("-", "_").
12
+ downcase
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,52 @@
1
+ module HasPrice
2
+ module HasPrice
3
+
4
+ # Provides a simple DSL to defines price instance method on the receiver.
5
+ #
6
+ # @param [Hash] options the options for creating price method.
7
+ # @option options [Symbol] :attribute (:price) Name of the price method.
8
+ # @option options [Boolean] :free (false) Set `:free => true` to use null object pattern.
9
+ #
10
+ # @yield The yielded block provides method `item` for declaring price entries,
11
+ # and method `group` for declaring price groups.
12
+ #
13
+ # @example Normal usage
14
+ # class Product < ActiveRecord::Base
15
+ # has_price do
16
+ # item base_price, "base"
17
+ # item discount, "discount"
18
+ #
19
+ # group "taxes" do
20
+ # item federal_tax, "federal tax"
21
+ # item state_tax, "state tax"
22
+ # end
23
+ #
24
+ # group "shipment" do
25
+ # # Notice that delivery_method is an instance method.
26
+ # # You can call instance methods anywhere in has_price block.
27
+ # item delivery_price, delivery_method
28
+ # end
29
+ # end
30
+ # end
31
+ #
32
+ # @example Null object pattern
33
+ # class Product < ActiveRecord::Base
34
+ # # Creates method #price which returns empty Price.
35
+ # has_price :free => true
36
+ # end
37
+ #
38
+ # @see PriceBuilder#item
39
+ # @see PriceBuilder#group
40
+ #
41
+ def has_price(options = {}, &block)
42
+ attribute = options[:attribute] || :price
43
+ free = !block_given? && options[:free]
44
+
45
+ define_method attribute.to_sym do
46
+ builder = PriceBuilder.new self
47
+ builder.instance_eval &block unless free
48
+ builder.price
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,52 @@
1
+ module HasPrice
2
+ class Price < Hash
3
+ # @return [Fixnum] the recursive sum of all declared prices.
4
+ def to_i; recursive_sum end
5
+ # @return [String] the output of to_i converted to string.
6
+ def to_s; to_i.to_s end
7
+ # @return [Hash] the price as a Hash object.
8
+ def to_hash; Hash[self] end
9
+ alias total to_i
10
+
11
+ # Provides access to price items and groups using magic methods and chaining.
12
+ #
13
+ # @example
14
+ # class Product
15
+ # has_price do
16
+ # item 400, "base"
17
+ # group "tax" do
18
+ # item 100, "federal"
19
+ # item 50, "state"
20
+ # end
21
+ # end
22
+ # end
23
+ #
24
+ # product = Product.new
25
+ # product.price # => Full Price object
26
+ # product.price.base # => 400
27
+ # product.price.tax # => Price object on group tax
28
+ # product.price.tax.federal # => 100
29
+ # product.price.tax.total # => 150
30
+ #
31
+ # @return [Price, Fixnum] Price object if method matches a group, Fixnum if method matches an item.
32
+ def method_missing(meth, *args, &blk)
33
+ value = select{|k,v| k.underscore == meth.to_s}.first
34
+
35
+ if !value
36
+ super
37
+ elsif value.last.is_a?(Hash)
38
+ self.class[value.last]
39
+ else
40
+ value.last
41
+ end
42
+ end
43
+
44
+ private
45
+ def recursive_sum(target = self)
46
+ target.inject(0) do |sum, pair|
47
+ value = pair.last
48
+ sum += value.is_a?(Hash) ? recursive_sum(value) : value
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,88 @@
1
+ module HasPrice
2
+ class PriceBuilder
3
+ attr_reader :price
4
+
5
+ # Creates PriceBuilder on a target object.
6
+ #
7
+ # @param [Object] object the target object on which price is being built.
8
+ def initialize(object)
9
+ @price = Price.new
10
+ @current_nesting_level = @price
11
+ @object = object
12
+ end
13
+
14
+ # Adds price item to the current nesting level of price definition.
15
+ #
16
+ # @param [#to_hash, #to_i] price an integer representing amount for this price item.
17
+ # Alternatively, anything that responds to #to_hash can be used,
18
+ # and will be treated as a group named with item_name.
19
+ # @param [#to_s] item_name name for the provided price item or group.
20
+ #
21
+ # @see #group
22
+ def item(price, item_name)
23
+ @current_nesting_level[item_name.to_s] = price.respond_to?(:to_hash) ? price.to_hash : price.to_i
24
+ end
25
+
26
+ # Adds price group to the current nesting level of price definition.
27
+ # Groups are useful for price breakdown categorization and easy subtotal values.
28
+ #
29
+ # @example Using group subtotals
30
+ # class Product
31
+ # include HasPrice
32
+ #
33
+ # def base_price; 100 end
34
+ # def federal_tax; 15 end
35
+ # def state_tax; 10 end
36
+ #
37
+ # has_price do
38
+ # item base_price, "base"
39
+ # group "tax" do
40
+ # item federal_tax, "federal"
41
+ # item state_tax, "state"
42
+ # end
43
+ # end
44
+ # end
45
+ #
46
+ # @product = Product.new
47
+ # @product.price.total # => 125
48
+ # @product.price.tax.total # => 25
49
+ #
50
+ # @param [#to_s] group_name a name for the price group
51
+ # @yield The yielded block is executed within the group, such that all groups and items
52
+ # declared within the block appear nested under this group. This behavior is recursive.
53
+ #
54
+ # @see #item
55
+ def group(group_name, &block)
56
+ group_key = group_name.to_s
57
+
58
+ @current_nesting_level[group_key] ||= {}
59
+
60
+ if block_given?
61
+ within_group(group_key) do
62
+ instance_eval &block
63
+ end
64
+ end
65
+ end
66
+
67
+ # Delegates all missing methods to the target object.
68
+ def method_missing(meth, *args, &block)
69
+ @object.send(meth, *args, &block)
70
+ end
71
+
72
+ private
73
+ def within_group(group_name)
74
+ step_into group_name
75
+ yield
76
+ step_out
77
+ end
78
+
79
+ def step_into(group_name)
80
+ @original_nesting_level = @current_nesting_level
81
+ @current_nesting_level = @current_nesting_level[group_name]
82
+ end
83
+
84
+ def step_out
85
+ @current_nesting_level = @original_nesting_level
86
+ end
87
+ end
88
+ end
data/lib/has_price.rb ADDED
@@ -0,0 +1,14 @@
1
+ require File.dirname(__FILE__) + "/has_price/core_extensions/array.rb"
2
+ require File.dirname(__FILE__) + "/has_price/core_extensions/string.rb"
3
+
4
+ unless Array.instance_methods.include? "extract_options!"
5
+ Array.send :include, HasPrice::CoreExtensions::Array
6
+ end
7
+
8
+ unless String.instance_methods.include? "underscore"
9
+ String.send :include, HasPrice::CoreExtensions::String
10
+ end
11
+
12
+ require File.dirname(__FILE__) + "/has_price/price.rb"
13
+ require File.dirname(__FILE__) + "/has_price/price_builder.rb"
14
+ require File.dirname(__FILE__) + "/has_price/has_price.rb"
data/rails/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'has_price'
2
+
3
+ ActiveRecord::Base.extend HasPrice::HasPrice
data/test/helper.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'rr'
5
+
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ require 'has_price'
9
+
10
+ class Test::Unit::TestCase
11
+ include RR::Adapters::TestUnit
12
+ include HasPrice
13
+ end
@@ -0,0 +1,85 @@
1
+ require 'helper'
2
+
3
+ class TestHasPrice < Test::Unit::TestCase
4
+ context "A Product with HasPrice module" do
5
+ setup do
6
+ Product = Class.new
7
+ Product.extend(HasPrice)
8
+ end
9
+
10
+ context "having price defined as :free" do
11
+ setup do
12
+ Product.has_price :free => true
13
+ @product = Product.new
14
+ end
15
+
16
+ should "gain #price method" do
17
+ assert Product.instance_methods.include? "price"
18
+ end
19
+
20
+ should "return empty Price object" do
21
+ assert_equal Price.new, @product.price
22
+ end
23
+ end
24
+
25
+ context "having price defined using builder with hardcoded values" do
26
+ setup do
27
+ Product.has_price do
28
+ item 100, "base"
29
+ group "tax" do
30
+ item 20, "federal"
31
+ item 10, "state"
32
+ end
33
+ end
34
+
35
+ @product = Product.new
36
+ end
37
+
38
+ should "gain #price method" do
39
+ assert Product.instance_methods.include? "price"
40
+ end
41
+
42
+ should "return price object" do
43
+ assert_equal Price, @product.price.class
44
+ end
45
+
46
+ should "return price equivalent to corresponding hash" do
47
+ assert_equal({"base" => 100, "tax" => {"federal" => 20, "state" => 10}}, @product.price)
48
+ end
49
+ end
50
+
51
+ context "having price defined using builder with values referencing product methods" do
52
+ setup do
53
+ Product.has_price do
54
+ item base_price, "base"
55
+ group "tax" do
56
+ item federal_tax, "federal"
57
+ end
58
+ end
59
+
60
+ @product = Product.new
61
+
62
+ mock(@product).base_price { 100 }
63
+ mock(@product).federal_tax { 20 }
64
+ end
65
+
66
+ should "return Price object with values correctly set from instance methods" do
67
+ assert_equal({"base" => 100, "tax" => {"federal" => 20}}, @product.price)
68
+ end
69
+ end
70
+
71
+ context "having price defined on attribute total_price" do
72
+ setup do
73
+ Product.has_price :attribute => "total_price", :free => true
74
+ end
75
+
76
+ should "gain #total_price method" do
77
+ assert Product.instance_methods.include? "total_price"
78
+ end
79
+ end
80
+ end
81
+
82
+ def teardown
83
+ self.class.instance_eval { remove_const :Product }
84
+ end
85
+ end
@@ -0,0 +1,63 @@
1
+ require 'helper'
2
+
3
+ class TestPrice < Test::Unit::TestCase
4
+ context "Price instance" do
5
+ setup do
6
+ @price = Price[{ "base" => 100,
7
+ "tax" => 10,
8
+ "delivery" => { "shipping" => 20,
9
+ "expedite" => 5,
10
+ "discounts" => { "thanksgiving" => -20 }}}]
11
+ end
12
+
13
+ should "calculate total recursively" do
14
+ assert_equal 115, @price.total
15
+ end
16
+
17
+ should "return total on to_i" do
18
+ assert_equal 115, @price.to_i
19
+ end
20
+
21
+ should "return Hash on to_hash" do
22
+ assert_equal Hash, @price.to_hash.class
23
+ end
24
+
25
+ should "support equality with the equivalent hash" do
26
+ assert_equal @price, @price.to_hash
27
+ end
28
+
29
+ should "access hash values with missing methods" do
30
+ assert_equal 10, @price.tax
31
+ end
32
+
33
+ should "return Price object upon accessing nested group" do
34
+ assert_equal Price, @price.delivery.class
35
+ end
36
+
37
+ should "return equivalent of sub-hash as nested group" do
38
+ assert_equal({"shipping" => 20, "expedite" => 5, "discounts" => { "thanksgiving" => -20 }}, @price.delivery)
39
+ end
40
+
41
+ should "return subtotal of a nested group" do
42
+ assert_equal 5, @price.delivery.total
43
+ end
44
+
45
+ should "support deep chaining for accessing groups" do
46
+ assert_equal({"thanksgiving" => -20}, @price.delivery.discounts)
47
+ end
48
+
49
+ should "provide subtotal on deep nested group with single element" do
50
+ assert_equal -20, @price.delivery.discounts.total
51
+ end
52
+
53
+ should "access individual elements in deep nested groups" do
54
+ assert_equal -20, @price.delivery.discounts.thanksgiving
55
+ end
56
+
57
+ should "raise NoMethodError in case called non-existent item/group" do
58
+ assert_raise NoMethodError do
59
+ @price.foo
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,59 @@
1
+ require 'helper'
2
+
3
+ class TestPriceBuilder < Test::Unit::TestCase
4
+ context "PriceBuilder" do
5
+ context "instance" do
6
+ setup do
7
+ @instance = Object.new
8
+ @price_builder = PriceBuilder.new(@instance)
9
+ end
10
+
11
+ should "create an item in price hash" do
12
+ @price_builder.item 500, "base"
13
+ assert_equal({"base" => 500}, @price_builder.price)
14
+ end
15
+
16
+ should "create an item based on another price" do
17
+ tax_price = Price[{"federal" => 100, "state" => 200}]
18
+ @price_builder.item tax_price, "tax"
19
+ assert_equal({"tax" => {"federal" => 100, "state" => 200}}, @price_builder.price)
20
+ end
21
+
22
+ should "create a group in price hash" do
23
+ @price_builder.group "taxes"
24
+ assert_equal({"taxes" => {}}, @price_builder.price)
25
+ end
26
+
27
+ should "create an item in a group in a price hash" do
28
+ @price_builder.group "taxes" do
29
+ item 500, "federal tax"
30
+ end
31
+
32
+ assert_equal({"taxes" => {"federal tax" => 500}}, @price_builder.price)
33
+ end
34
+
35
+ should "support complex group structure" do
36
+ @price_builder.item 500, "base"
37
+ @price_builder.group "delivery" do
38
+ item 200, "standard shipping"
39
+
40
+ group "discounts" do
41
+ item -100, "shipping discount"
42
+ end
43
+
44
+ item 100, "expedite"
45
+ end
46
+
47
+ assert_equal({"base" => 500, "delivery" => { "standard shipping" => 200,
48
+ "expedite" => 100,
49
+ "discounts" => { "shipping discount" => -100 }
50
+ }}, @price_builder.price)
51
+ end
52
+
53
+ should "send all missing methods to the object" do
54
+ stub(@instance).base_price { 200 }
55
+ assert_equal 200, @price_builder.base_price
56
+ end
57
+ end
58
+ end
59
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: has_price
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Maxim Chernyak
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-02 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: shoulda
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rr
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description: A convenient DSL for defining complex price reader/serializer in a class and organizing a price breakdown. Price can be declared with items and groups which depend on other attributes. Price is a very simple subclass of Hash. This provides for easy serialization and flexibility in case of implementation changes. This way you can conveniently store the whole price breakdown in your serialized receipts. It also provides magic methods for convenient access, but can be fully treated as a regular Hash with some sprinkles on top.
36
+ email: max@bitsonnet.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.md
44
+ files:
45
+ - .gitignore
46
+ - LICENSE
47
+ - README.md
48
+ - Rakefile
49
+ - VERSION
50
+ - has_price.gemspec
51
+ - lib/has_price.rb
52
+ - lib/has_price/core_extensions/array.rb
53
+ - lib/has_price/core_extensions/string.rb
54
+ - lib/has_price/has_price.rb
55
+ - lib/has_price/price.rb
56
+ - lib/has_price/price_builder.rb
57
+ - rails/init.rb
58
+ - test/helper.rb
59
+ - test/test_has_price.rb
60
+ - test/test_price.rb
61
+ - test/test_price_builder.rb
62
+ has_rdoc: true
63
+ homepage: http://github.com/maxim/has_price
64
+ licenses: []
65
+
66
+ post_install_message:
67
+ rdoc_options:
68
+ - --charset=UTF-8
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "0"
82
+ version:
83
+ requirements: []
84
+
85
+ rubyforge_project:
86
+ rubygems_version: 1.3.5
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: Provides a convenient DSL for organizing a price breakdown in a class.
90
+ test_files:
91
+ - test/helper.rb
92
+ - test/test_has_price.rb
93
+ - test/test_price.rb
94
+ - test/test_price_builder.rb