amount_field 1.4.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.
@@ -0,0 +1,118 @@
1
+ module AmountField #:nodoc:
2
+ module ActiveRecord #:nodoc:
3
+ module Validations
4
+
5
+ @@configuration = {}
6
+ mattr_accessor :configuration
7
+
8
+ def self.included(base) # :nodoc:
9
+ base.extend ClassMethods
10
+ end
11
+
12
+ module ClassMethods
13
+
14
+ def validates_amount_format_of(*attr_names)
15
+ # code before validates_each is called only once!
16
+ configuration = attr_names.extract_options!
17
+
18
+ define_special_setter(attr_names, configuration)
19
+
20
+ # the following code defines the callbacks methods that are called on every validation
21
+ validates_each(attr_names, configuration) do |record, attr_name, value|
22
+ # in case there is no assignment via 'amount_field_XXX=' method, we don't need to validate.
23
+ next unless record.instance_variable_defined?("@#{special_method_name(attr_name)}")
24
+
25
+ # get the original assigned value first to always run the validation for this value!
26
+ # if we us 'before_type_cast' here, we would get the converted value and not the
27
+ # original value if we call the validation twice.
28
+ original_value = record.instance_variable_get("@#{special_method_name(attr_name)}")
29
+ original_value ||= record.send("#{attr_name}_before_type_cast") || value
30
+
31
+ # in case nil or blank is allowed, we don't validate
32
+ next if configuration[:allow_nil] and original_value.nil?
33
+ next if configuration[:allow_blank] and original_value.blank?
34
+
35
+ converted_value = convert(original_value, format_configuration(configuration))
36
+
37
+ if valid_format?(original_value, format_configuration(configuration))
38
+ # assign converted value to attribute so other validations macro can work on it
39
+ # and the getter returns a value
40
+ record.send("#{attr_name}=", converted_value)
41
+ else
42
+ # assign original value as AssignedValue so multiple calls of this validation will
43
+ # consider the value still as invalid.
44
+ record.send("#{attr_name}=", original_value)
45
+ record.errors.add(attr_name, :invalid_amount_format, :value => original_value,
46
+ :default => configuration[:message],
47
+ :format_example => valid_format_example(format_configuration(configuration)))
48
+ end
49
+
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def define_special_setter(attr_names, configuration)
56
+ attr_names.each do |attr_name|
57
+ class_eval <<-EOV
58
+ def #{special_method_name(attr_name)}=(value)
59
+ @#{special_method_name(attr_name)} = value
60
+ self[:#{attr_name}] = value
61
+ end
62
+
63
+ def #{special_method_name(attr_name)}
64
+ @#{special_method_name(attr_name)}
65
+ end
66
+ EOV
67
+ end
68
+ end
69
+
70
+ def special_method_name(attr_name)
71
+ "#{AmountField::Configuration.prefix}_#{attr_name}"
72
+ end
73
+
74
+ def convert(original_value, configuration)
75
+ converted_value = original_value.to_s.gsub(configuration[:delimiter].to_s, '')
76
+ converted_value = converted_value.sub(configuration[:separator].to_s, '.') unless configuration[:separator].blank?
77
+ converted_value
78
+ end
79
+
80
+ # we have to read the configuration every time to get the current I18n value
81
+ def format_configuration(configuration)
82
+ format_options = I18n.t(:'number.amount_field.format', :raise => true) rescue {}
83
+ # update it with a maybe given default configuration
84
+ format_options = format_options.merge(AmountField::ActiveRecord::Validations.configuration)
85
+ # update it with a maybe given explicit configuration via the macro
86
+ format_options.update(configuration)
87
+ end
88
+
89
+ def valid_format?(value, configuration)
90
+ return false if !value.kind_of?(String) || value.blank?
91
+
92
+ # add ,00 to 1234
93
+ if !value.include?(configuration[:separator].to_s) && !configuration[:separator].blank?
94
+ value = "#{value}#{configuration[:separator]}#{'0' * configuration[:precision].to_i}"
95
+ end
96
+
97
+ cs = configuration[:separator]
98
+ cd = "\\#{configuration[:delimiter]}"
99
+ cp = configuration[:precision]
100
+
101
+ # (1234 | 123.456),00
102
+ !(value =~ /\A[-\+]{0,1}((\d*)|(\d{0,3}(#{cd}\d{3})*))#{cs}\d{0,#{cp}}\z/).nil?
103
+ end
104
+
105
+ def valid_format_example(configuration)
106
+ s = 'd'
107
+ s << "#{configuration[:delimiter]}" unless configuration[:delimiter].nil?
108
+ s << "ddd"
109
+ s << "#{configuration[:separator]}" unless configuration[:separator].nil?
110
+ s << "#{'d' * configuration[:precision].to_i}" unless configuration[:precision].nil?
111
+ s
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,11 @@
1
+ de:
2
+ activerecord:
3
+ errors:
4
+ messages:
5
+ invalid_amount_format: "'{{value}}' ist ein ungültiges Format ({{format_example}})"
6
+ number:
7
+ amount_field:
8
+ format:
9
+ separator: ","
10
+ delimiter: "."
11
+ precision: 2
@@ -0,0 +1,12 @@
1
+ en:
2
+ activerecord:
3
+ errors:
4
+ messages:
5
+ invalid_amount_format: "'{{value}}' is not a valid amount format ({{format_example}})"
6
+ number:
7
+ amount_field:
8
+ format:
9
+ separator: "."
10
+ delimiter: ","
11
+ precision: 2
12
+
@@ -0,0 +1,206 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ ##
4
+ # Taken from the FormHelperTest in Rails 2.3
5
+ #
6
+ # We are testing the FormBuilder- and FormHelper-Version at once.
7
+ #
8
+ class FormHelperTest < ActionView::TestCase
9
+ tests AmountField::Helpers::FormHelper
10
+
11
+ class MyFormBuilder < ActionView::Helpers::FormBuilder
12
+ end
13
+
14
+ def setup
15
+ @controller = Class.new do
16
+ attr_reader :url_for_options
17
+ def url_for(options)
18
+ @url_for_options = options
19
+ "http://www.example.com"
20
+ end
21
+ end
22
+ @controller = @controller.new
23
+ @test_product = TestProduct.new(:price => 1234.56)
24
+ end
25
+
26
+ test "amount_field form helper with locale de" do
27
+ with_locale('de') do
28
+ form_for(:test_product, @test_product, :builder => MyFormBuilder) do |f|
29
+ concat f.amount_field(:price)
30
+ end
31
+
32
+ expected_input =
33
+ "<input name='test_product[amount_field_price]' size='30' type='text'" +
34
+ " class=' amount_field' id='test_product_price' value='1.234,56' />"
35
+
36
+ expected_form =
37
+ "<form action='http://www.example.com' method='post'>#{expected_input}</form>"
38
+
39
+ assert_dom_equal expected_input, amount_field(:test_product, :price)
40
+ assert_dom_equal expected_form, output_buffer
41
+ end
42
+ end
43
+
44
+ test "amount_field form helper with locale en" do
45
+ with_locale('en') do
46
+ form_for(:test_product, @test_product, :builder => MyFormBuilder) do |f|
47
+ concat f.amount_field(:price)
48
+ end
49
+
50
+ expected_input =
51
+ "<input name='test_product[amount_field_price]' size='30' type='text'" +
52
+ " class=' amount_field' id='test_product_price' value='1,234.56' />"
53
+
54
+ expected_form =
55
+ "<form action='http://www.example.com' method='post'>#{expected_input}</form>"
56
+
57
+ assert_dom_equal expected_input, amount_field(:test_product, :price)
58
+ assert_dom_equal expected_form, output_buffer
59
+ end
60
+ end
61
+
62
+ test "configured prefix is use in amount_field" do
63
+ with_locale('de') do
64
+ AmountField::Configuration.prefix = 'my_prefix'
65
+ form_for(:test_product, @test_product, :builder => MyFormBuilder) do |f|
66
+ concat f.amount_field(:price)
67
+ end
68
+
69
+ expected_input =
70
+ "<input name='test_product[my_prefix_price]' size='30' type='text'" +
71
+ " class=' amount_field' id='test_product_price' value='1.234,56' />"
72
+
73
+ expected_form =
74
+ "<form action='http://www.example.com' method='post'>#{expected_input}</form>"
75
+
76
+ assert_dom_equal expected_input, amount_field(:test_product, :price)
77
+ assert_dom_equal expected_form, output_buffer
78
+ AmountField::Configuration.prefix = 'amount_field'
79
+ end
80
+ end
81
+
82
+ test "configured css class is use in amount_field" do
83
+ with_locale('de') do
84
+ AmountField::Configuration.css_class = 'my_class'
85
+ form_for(:test_product, @test_product, :builder => MyFormBuilder) do |f|
86
+ concat f.amount_field(:price)
87
+ end
88
+
89
+ expected_input =
90
+ "<input name='test_product[amount_field_price]' size='30' type='text'" +
91
+ " class=' my_class' id='test_product_price' value='1.234,56' />"
92
+
93
+ expected_form =
94
+ "<form action='http://www.example.com' method='post'>#{expected_input}</form>"
95
+
96
+ assert_dom_equal expected_input, amount_field(:test_product, :price)
97
+ assert_dom_equal expected_form, output_buffer
98
+ AmountField::Configuration.css_class = 'amount_field'
99
+ end
100
+ end
101
+
102
+ test "default configuration format overwrite I18n configuration" do
103
+ with_configuration({ :delimiter => '@', :separator => '/', :precision => 2}) do
104
+ form_for(:test_product, @test_product, :builder => MyFormBuilder) do |f|
105
+ concat f.amount_field(:price)
106
+ end
107
+
108
+ expected_input =
109
+ "<input name='test_product[amount_field_price]' size='30' type='text'" +
110
+ " class=' amount_field' id='test_product_price' value='1@234/56' />"
111
+
112
+ expected_form =
113
+ "<form action='http://www.example.com' method='post'>#{expected_input}</form>"
114
+
115
+ assert_dom_equal expected_input, amount_field(:test_product, :price)
116
+ assert_dom_equal expected_form, output_buffer
117
+ end
118
+ end
119
+
120
+ test "explicit format overwrite default configuration" do
121
+ format = { :delimiter => '@', :separator => '/', :precision => 3 }
122
+ with_locale('de') do
123
+ form_for(:test_product, @test_product, :builder => MyFormBuilder) do |f|
124
+ concat f.amount_field(:price, :format => format)
125
+ end
126
+
127
+ expected_input =
128
+ "<input name='test_product[amount_field_price]' size='30' type='text'" +
129
+ " class=' amount_field' id='test_product_price' value='1@234/560' />"
130
+
131
+ expected_form =
132
+ "<form action='http://www.example.com' method='post'>#{expected_input}</form>"
133
+
134
+ assert_dom_equal expected_input, amount_field(:test_product, :price, :format => format)
135
+ assert_dom_equal expected_form, output_buffer
136
+ assert_equal({}, AmountField::ActiveRecord::Validations.configuration)
137
+ end
138
+ end
139
+
140
+ test "we show the original value for an invalid value" do
141
+ @test_product = TestProduct.new(:amount_field_price => "x")
142
+ @test_product.valid?
143
+ form_for(:test_product, @test_product, :builder => MyFormBuilder) do |f|
144
+ concat f.amount_field(:price)
145
+ end
146
+
147
+ expected_input =
148
+ "<div class='fieldWithErrors'>" +
149
+ "<input name='test_product[amount_field_price]' size='30'" +
150
+ " class=' amount_field' type='text' id='test_product_price' value='x' />" +
151
+ "</div>"
152
+
153
+ expected_form =
154
+ "<form action='http://www.example.com' method='post'>#{expected_input}</form>"
155
+
156
+ assert_dom_equal expected_input, amount_field(:test_product, :price)
157
+ assert_dom_equal expected_form, output_buffer
158
+ end
159
+
160
+ test "we show the given value instead of the invalid value" do
161
+ @test_product = TestProduct.new(:amount_field_price => "x")
162
+ @test_product.valid?
163
+ form_for(:test_product, @test_product, :builder => MyFormBuilder) do |f|
164
+ concat f.amount_field(:price, :value => 4711)
165
+ end
166
+
167
+ expected_input =
168
+ "<div class='fieldWithErrors'>" +
169
+ "<input name='test_product[amount_field_price]' size='30'" +
170
+ " class=' amount_field' type='text' id='test_product_price' value='4711' />" +
171
+ "</div>"
172
+
173
+ expected_form =
174
+ "<form action='http://www.example.com' method='post'>#{expected_input}</form>"
175
+
176
+ assert_dom_equal expected_input, amount_field(:test_product, :price, :value => 4711)
177
+ assert_dom_equal expected_form, output_buffer
178
+ end
179
+
180
+ test "we use the object from options if given" do
181
+ @test_product1 = TestProduct.new(:amount_field_price => "6543.21")
182
+ @test_product1.valid?
183
+ test_product2 = TestProduct.new(:amount_field_price => "1234.56")
184
+ test_product2.valid?
185
+ form_for(:test_product, @test_product1, :builder => MyFormBuilder) do |f|
186
+ concat f.amount_field(:price, :object => test_product2)
187
+ end
188
+
189
+ expected_input =
190
+ "<input name='test_product[amount_field_price]' size='30'" +
191
+ " class=' amount_field' type='text' id='test_product_price' value='1,234.56' />"
192
+
193
+ expected_form =
194
+ "<form action='http://www.example.com' method='post'>#{expected_input}</form>"
195
+
196
+ assert_dom_equal expected_input, amount_field(:test_product, :price, :object => test_product2)
197
+ assert_dom_equal expected_form, output_buffer
198
+ end
199
+
200
+ protected
201
+
202
+ def protect_against_forgery?
203
+ false
204
+ end
205
+
206
+ end
@@ -0,0 +1,47 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class FormTagHelperTest < ActionView::TestCase
4
+ tests AmountField::Helpers::FormTagHelper
5
+
6
+ test "return an input field with the special amount field name attribute" do
7
+ assert_match /name="amount_field_price"/, amount_field_tag(:price, 1234.56)
8
+ end
9
+
10
+ test "return an value attribute with a formatted value for german locale" do
11
+ with_locale('de') do
12
+ assert_match /value="1.234,56"/, amount_field_tag(:price, 1234.56)
13
+ end
14
+ end
15
+
16
+ test "return an value attribute with a formatted value for english locale" do
17
+ with_locale('en') do
18
+ assert_match /value="1,234.56"/, amount_field_tag(:price, 1234.56)
19
+ end
20
+ end
21
+
22
+ test "default css class is used in combination with given class" do
23
+ assert_match /class="foo #{AmountField::Configuration.css_class}"/, amount_field_tag(:price, 1234.56, :class => 'foo')
24
+ end
25
+
26
+ test "configured prefix is use in amount_field" do
27
+ AmountField::Configuration.prefix = 'my_prefix'
28
+ assert_match /name="my_prefix_price"/, amount_field_tag(:price, 1234.56)
29
+ AmountField::Configuration.prefix = 'amount_field'
30
+ end
31
+
32
+ test "configured css class is use in amount_field" do
33
+ AmountField::Configuration.css_class = 'my_class'
34
+ assert_match /class=" my_class"/, amount_field_tag(:price, 1234.56)
35
+ AmountField::Configuration.css_class = 'amount_field'
36
+ end
37
+
38
+ test "explicit format overwrite default configuration" do
39
+ assert_match /value="1@234#560"/, amount_field_tag(:price, 1234.56, :format => { :delimiter => '@', :separator => '#', :precision => 3})
40
+ assert_equal({}, AmountField::ActiveRecord::Validations.configuration)
41
+ end
42
+
43
+ test "we show the given value from the options instead of the argument" do
44
+ assert_match /value="42"/, amount_field_tag(:price, 1234.56, :value => 42)
45
+ end
46
+
47
+ end
@@ -0,0 +1,7 @@
1
+ class TestProduct < ActiveRecord::Base
2
+
3
+ validates_amount_format_of :price
4
+ validates_numericality_of :price, :message => "not numericality"
5
+ validates_presence_of :price
6
+
7
+ end
@@ -0,0 +1,75 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ require File.expand_path(File.dirname(__FILE__) + "/../../../../config/environment")
3
+ require 'test_help'
4
+
5
+ #TODO/2009-07-21/tb über environment oder diese gems?
6
+ # require 'rubygems'
7
+ # require 'test/unit'
8
+ #
9
+ # require 'activesupport'
10
+ # require 'active_support'
11
+ # require 'active_support/test_case'
12
+ # require 'active_record'
13
+ # require 'actionpack'
14
+ #
15
+ # gem 'rails'
16
+
17
+ require File.join(File.dirname(__FILE__), '../init')
18
+ Dir[File.join(File.dirname(__FILE__), 'models/*.rb')].each { |f| require f }
19
+
20
+ ActiveRecord::Base.establish_connection({
21
+ :adapter => 'mysql',
22
+ :database => 'gem_amount_field_test',
23
+ :host => 'localhost',
24
+ :username => 'root',
25
+ :password => ''
26
+ })
27
+
28
+ ActiveRecord::Migration.verbose = false
29
+ ActiveRecord::Schema.define(:version => 1) do
30
+ create_table "test_products", :force => true do |t|
31
+ t.column "name", :string
32
+ t.column "price", :decimal, :precision => 12, :scale => 2
33
+ t.column "stock_price", :decimal, :precision => 12, :scale => 2
34
+ t.column "float_price", :float
35
+ end
36
+ end
37
+
38
+ class ActiveSupport::TestCase
39
+
40
+ def with_configuration(config)
41
+ begin
42
+ orig_config = AmountField::ActiveRecord::Validations.configuration
43
+ AmountField::ActiveRecord::Validations.configuration = config
44
+ yield
45
+ ensure
46
+ AmountField::ActiveRecord::Validations.configuration = orig_config
47
+ end
48
+ end
49
+
50
+ def with_locale(locale)
51
+ begin
52
+ orig_locale = I18n.locale
53
+ I18n.locale = locale
54
+ yield
55
+ ensure
56
+ I18n.locale = orig_locale
57
+ end
58
+ end
59
+
60
+ def assert_valid_formats(formats, test_clazz)
61
+ formats.each do |format, expected_value|
62
+ product = test_clazz.new(:amount_field_price => format)
63
+ assert product.valid?, "expected '#{format}' to be valid (#{product.errors.full_messages.inspect})"
64
+ assert_in_delta expected_value, product.price.to_f, 0.001
65
+ end
66
+ end
67
+
68
+ def assert_invalid_formats(formats, test_clazz)
69
+ formats.each do |format|
70
+ product = test_clazz.new(:amount_field_price => format)
71
+ assert !product.valid?, "expected '#{format}' to be invalid (#{product.errors.full_messages.inspect})"
72
+ end
73
+ end
74
+
75
+ end