amount_field_rails3 3.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.
@@ -0,0 +1,22 @@
1
+ module AmountField #:nodoc:
2
+ module Helpers #:nodoc:
3
+ module FormTagHelper
4
+ include ActionView::Helpers::NumberHelper
5
+
6
+ def amount_field_tag(name, value = nil, options = {})
7
+ format_options = I18n.t(:'number.amount_field.format', :raise => true) rescue {}
8
+ format_options = format_options.merge(AmountField::ActiveRecord::Validations.configuration)
9
+ format_options.merge!(options.delete(:format) || {})
10
+
11
+ # if no explicit value is given, we set a formatted one. In case of an error we take the
12
+ # original value inserted by the user.
13
+ options[:value] ||= number_with_precision(value.to_s, format_options)
14
+ options[:name] = "#{AmountField::Configuration.prefix}_#{name}"
15
+ options[:class] = "#{options[:class]} #{AmountField::Configuration.css_class}"
16
+
17
+ text_field_tag(name, value, options)
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,129 @@
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,
46
+ build_error_message(configuration[:message], {
47
+ :value => original_value,
48
+ :format_example => valid_format_example(format_configuration(configuration))
49
+ })
50
+ )
51
+ end
52
+
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def define_special_setter(attr_names, configuration)
59
+ attr_names.each do |attr_name|
60
+ class_eval <<-EOV
61
+ def #{special_method_name(attr_name)}=(value)
62
+ @#{special_method_name(attr_name)} = value
63
+ self[:#{attr_name}] = value
64
+ end
65
+
66
+ def #{special_method_name(attr_name)}
67
+ @#{special_method_name(attr_name)}
68
+ end
69
+ EOV
70
+ end
71
+ end
72
+
73
+ def special_method_name(attr_name)
74
+ "#{AmountField::Configuration.prefix}_#{attr_name}"
75
+ end
76
+
77
+ def convert(original_value, configuration)
78
+ converted_value = original_value.to_s.gsub(configuration[:delimiter].to_s, '')
79
+ converted_value = converted_value.sub(configuration[:separator].to_s, '.') unless configuration[:separator].blank?
80
+ converted_value
81
+ end
82
+
83
+ # we have to read the configuration every time to get the current I18n value
84
+ def format_configuration(configuration)
85
+ format_options = I18n.t(:'number.amount_field.format', :raise => true) rescue {}
86
+ # update it with a maybe given default configuration
87
+ format_options = format_options.merge(AmountField::ActiveRecord::Validations.configuration)
88
+ # update it with a maybe given explicit configuration via the macro
89
+ format_options.update(configuration)
90
+ end
91
+
92
+ def valid_format?(value, configuration)
93
+ return false if !value.kind_of?(String) || value.blank?
94
+
95
+ # add ,00 to 1234
96
+ if !value.include?(configuration[:separator].to_s) && !configuration[:separator].blank?
97
+ value = "#{value}#{configuration[:separator]}#{'0' * configuration[:precision].to_i}"
98
+ end
99
+
100
+ cs = configuration[:separator]
101
+ cd = "\\#{configuration[:delimiter] || ' '}"
102
+ cp = configuration[:precision]
103
+
104
+ # (1234 | 123.456),00
105
+ !(value =~ /\A[-\+]{0,1}((\d*)|(\d{0,3}(#{cd}\d{3})*))#{cs}\d{0,#{cp}}\z/).nil?
106
+ end
107
+
108
+ def valid_format_example(configuration)
109
+ s = 'd'
110
+ s << "#{configuration[:delimiter]}" unless configuration[:delimiter].nil?
111
+ s << "ddd"
112
+ s << "#{configuration[:separator]}" unless configuration[:separator].nil?
113
+ s << "#{'d' * configuration[:precision].to_i}" unless configuration[:precision].nil?
114
+ s
115
+ end
116
+
117
+ def build_error_message(explicit_message, options = {})
118
+ if explicit_message.blank?
119
+ I18n.t('errors.messages.invalid_amount_format', options)
120
+ else
121
+ I18n.interpolate(explicit_message, options)
122
+ end
123
+ end
124
+
125
+ end
126
+
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,3 @@
1
+ module AmountField
2
+ VERSION = "3.0.0"
3
+ end
data/locale/de.yml ADDED
@@ -0,0 +1,10 @@
1
+ de:
2
+ errors:
3
+ messages:
4
+ invalid_amount_format: "'%{value}' ist ein ungültiges Format (%{format_example})"
5
+ number:
6
+ amount_field:
7
+ format:
8
+ separator: ","
9
+ delimiter: "."
10
+ precision: 2
data/locale/en.yml ADDED
@@ -0,0 +1,11 @@
1
+ en:
2
+ errors:
3
+ messages:
4
+ invalid_amount_format: "'%{value}' is not a valid amount format (%{format_example})"
5
+ number:
6
+ amount_field:
7
+ format:
8
+ separator: "."
9
+ delimiter: ","
10
+ precision: 2
11
+
@@ -0,0 +1,264 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+
5
+ ##
6
+ # Taken from the FormHelperTest in Rails 2.3
7
+ #
8
+ # We are testing the FormBuilder- and FormHelper-Version at once.
9
+ #
10
+ class FormHelperTest < ActionView::TestCase
11
+ tests AmountField::Helpers::FormHelper
12
+
13
+ class MyFormBuilder < ActionView::Helpers::FormBuilder
14
+ end
15
+
16
+ def setup
17
+ @controller = Class.new do
18
+ attr_reader :url_for_options
19
+ def url_for(options)
20
+ @url_for_options = options
21
+ "http://www.example.com"
22
+ end
23
+ end
24
+ @controller = @controller.new
25
+ @test_product = TestProduct.new(:price => 1234.56)
26
+
27
+ AmountField::Configuration.prefix = 'amount_field'
28
+ AmountField::Configuration.css_class = 'amount_field'
29
+ end
30
+
31
+ def test_products_path
32
+ "/products"
33
+ end
34
+
35
+ def test_product_path
36
+ "/products"
37
+ end
38
+
39
+ test "amount_field form helper with locale de" do
40
+ with_locale('de') do
41
+ output_buffer = form_for(@test_product, :builder => MyFormBuilder) do |f|
42
+ concat f.amount_field(:price)
43
+ end
44
+
45
+ expected_input =
46
+ "<input class=' amount_field' id='test_product_price'" +
47
+ " name='test_product[amount_field_price]' size='30' type='text' value='1.234,56' />"
48
+
49
+ assert_dom_equal expected_input, amount_field(:test_product, :price)
50
+ assert_dom_equal expected_form(expected_input), output_buffer
51
+ end
52
+ end
53
+
54
+ test "amount_field form helper with locale en" do
55
+ with_locale('en') do
56
+ output_buffer = form_for(@test_product, :builder => MyFormBuilder) do |f|
57
+ concat f.amount_field(:price)
58
+ end
59
+
60
+ expected_input =
61
+ "<input name='test_product[amount_field_price]' size='30' type='text'" +
62
+ " class=' amount_field' id='test_product_price' value='1,234.56' />"
63
+
64
+ assert_dom_equal expected_input, amount_field(:test_product, :price)
65
+ assert_dom_equal expected_form(expected_input), output_buffer
66
+ end
67
+ end
68
+
69
+ test "configured prefix is use in amount_field" do
70
+ with_locale('de') do
71
+ AmountField::Configuration.prefix = 'my_prefix'
72
+ output_buffer = form_for(@test_product, :builder => MyFormBuilder) do |f|
73
+ concat f.amount_field(:price)
74
+ end
75
+
76
+ expected_input =
77
+ "<input class=' amount_field' name='test_product[my_prefix_price]' size='30' type='text'" +
78
+ " id='test_product_price' value='1.234,56' />"
79
+
80
+ assert_dom_equal expected_input, amount_field(:test_product, :price)
81
+ assert_dom_equal expected_form(expected_input), output_buffer
82
+ AmountField::Configuration.prefix = 'amount_field'
83
+ end
84
+ end
85
+
86
+ test "configured css class is use in amount_field" do
87
+ with_locale('de') do
88
+ AmountField::Configuration.css_class = 'my_class'
89
+ output_buffer = form_for(@test_product, :builder => MyFormBuilder) do |f|
90
+ concat f.amount_field(:price)
91
+ end
92
+
93
+ expected_input =
94
+ "<input name='test_product[amount_field_price]' size='30' type='text'" +
95
+ " class=' my_class' id='test_product_price' value='1.234,56' />"
96
+
97
+ assert_dom_equal expected_input, amount_field(:test_product, :price)
98
+ assert_dom_equal expected_form(expected_input), output_buffer
99
+ AmountField::Configuration.css_class = 'amount_field'
100
+ end
101
+ end
102
+
103
+ test "default configuration format overwrite I18n configuration" do
104
+ with_configuration({ :delimiter => '@', :separator => '/', :precision => 2}) do
105
+ output_buffer = form_for(@test_product, :builder => MyFormBuilder) do |f|
106
+ concat f.amount_field(:price)
107
+ end
108
+
109
+ expected_input =
110
+ "<input name='test_product[amount_field_price]' size='30' type='text'" +
111
+ " class=' amount_field' id='test_product_price' value='1@234/56' />"
112
+
113
+ assert_dom_equal expected_input, amount_field(:test_product, :price)
114
+ assert_dom_equal expected_form(expected_input), output_buffer
115
+ end
116
+ end
117
+
118
+ test "explicit format overwrite default configuration" do
119
+ format = { :delimiter => '@', :separator => '/', :precision => 3 }
120
+ with_locale('de') do
121
+ output_buffer = form_for(@test_product, :builder => MyFormBuilder) do |f|
122
+ concat f.amount_field(:price, :format => format)
123
+ end
124
+
125
+ expected_input =
126
+ "<input name='test_product[amount_field_price]' size='30' type='text'" +
127
+ " class=' amount_field' id='test_product_price' value='1@234/560' />"
128
+
129
+ assert_dom_equal expected_input, amount_field(:test_product, :price, :format => format)
130
+ assert_dom_equal expected_form(expected_input), output_buffer
131
+ assert_equal({}, AmountField::ActiveRecord::Validations.configuration)
132
+ end
133
+ end
134
+
135
+ test "we show the original value for an invalid value" do
136
+ @test_product = TestProduct.new(:amount_field_price => "x")
137
+ @test_product.valid?
138
+ output_buffer = form_for(@test_product, :builder => MyFormBuilder) do |f|
139
+ concat f.amount_field(:price)
140
+ end
141
+
142
+ expected_input =
143
+ "<div class='field_with_errors'>" +
144
+ "<input name='test_product[amount_field_price]' size='30'" +
145
+ " class=' amount_field' type='text' id='test_product_price' value='x' />" +
146
+ "</div>"
147
+
148
+ assert_dom_equal expected_input, amount_field(:test_product, :price)
149
+ assert_dom_equal expected_form(expected_input), output_buffer
150
+ end
151
+
152
+ test "we show the given value instead of the invalid value" do
153
+ @test_product = TestProduct.new(:amount_field_price => "x")
154
+ @test_product.valid?
155
+ output_buffer = form_for(@test_product, :builder => MyFormBuilder) do |f|
156
+ concat f.amount_field(:price, :value => 4711)
157
+ end
158
+
159
+ expected_input =
160
+ "<div class='field_with_errors'>" +
161
+ "<input name='test_product[amount_field_price]' size='30'" +
162
+ " class=' amount_field' type='text' id='test_product_price' value='4711' />" +
163
+ "</div>"
164
+
165
+ assert_dom_equal expected_input, amount_field(:test_product, :price, :value => 4711)
166
+ assert_dom_equal expected_form(expected_input), output_buffer
167
+ end
168
+
169
+ test "we use the object from options if given" do
170
+ @test_product1 = TestProduct.new(:amount_field_price => "6543.21")
171
+ @test_product1.valid?
172
+ test_product2 = TestProduct.new(:amount_field_price => "1234.56")
173
+ test_product2.valid?
174
+ output_buffer = form_for(@test_product1, :builder => MyFormBuilder) do |f|
175
+ concat f.amount_field(:price, :object => test_product2)
176
+ end
177
+
178
+ expected_input =
179
+ "<input name='test_product[amount_field_price]' size='30'" +
180
+ " class=' amount_field' type='text' id='test_product_price' value='1,234.56' />"
181
+
182
+ assert_dom_equal expected_input, amount_field(:test_product, :price, :object => test_product2)
183
+ assert_dom_equal expected_form(expected_input), output_buffer
184
+ end
185
+
186
+ test "consider option name if given" do
187
+ @test_product = TestProduct.new(:amount_field_price => "47.11")
188
+ output_buffer = form_for(@test_product, :builder => MyFormBuilder) do |f|
189
+ concat f.amount_field(:price, :name => 'article')
190
+ end
191
+
192
+ expected_input =
193
+ "<input name='article' size='30'" +
194
+ " class=' amount_field' type='text' id='test_product_price' value='47.11' />"
195
+
196
+ assert_dom_equal expected_input, amount_field(:test_product, :price, :name => 'article', :value => 47.11)
197
+ assert_dom_equal expected_form(expected_input), output_buffer
198
+ end
199
+
200
+ test "consider option id if given" do
201
+ @test_product = TestProduct.new(:amount_field_price => "63.41")
202
+ output_buffer = form_for(@test_product, :builder => MyFormBuilder) do |f|
203
+ concat f.amount_field(:price, :name => 'article', :id => 'my_id')
204
+ end
205
+
206
+ expected_input =
207
+ "<input name='article' size='30'" +
208
+ " class=' amount_field' type='text' id='my_id' value='63.41' />"
209
+
210
+ assert_dom_equal expected_input, amount_field(:test_product, :price, :name => 'article', :id => 'my_id', :value => 63.41)
211
+ assert_dom_equal expected_form(expected_input), output_buffer
212
+ end
213
+
214
+ test "invalid negative value is displayed with current format options" do
215
+ with_locale('de') do
216
+ class MyTestProduct < ActiveRecord::Base
217
+ set_table_name 'test_products'
218
+ validates_amount_format_of :price
219
+ validates_numericality_of :price, :greater_than_or_equal_to => 0.0
220
+ end
221
+ @test_product = MyTestProduct.new(:amount_field_price => "-0,1")
222
+ @test_product.valid?
223
+
224
+ output_buffer = form_for(@test_product, :url => test_products_path, :builder => MyFormBuilder) do |f|
225
+ concat f.amount_field(:price)
226
+ end
227
+
228
+ assert_match /value="-0,1"/, output_buffer
229
+ end
230
+ end
231
+
232
+ test "invalid positive value is displayed with current format options" do
233
+ with_locale('de') do
234
+ class MyTestProduct < ActiveRecord::Base
235
+ set_table_name 'test_products'
236
+ validates_amount_format_of :price
237
+ validates_numericality_of :price, :less_than_or_equal_to => 10.0
238
+ end
239
+ @test_product = MyTestProduct.new(:amount_field_price => "12,34")
240
+ @test_product.valid?
241
+
242
+ output_buffer = form_for(@test_product, :url => test_products_path, :builder => MyFormBuilder) do |f|
243
+ concat f.amount_field(:price)
244
+ end
245
+
246
+ assert_match /value="12,34"/, output_buffer
247
+ end
248
+ end
249
+
250
+ protected
251
+
252
+ def protect_against_forgery?
253
+ false
254
+ end
255
+
256
+ def expected_form(expected_input_field)
257
+ expected_form =
258
+ "<form accept-charset='UTF-8' action='/products' class='new_test_product' id='new_test_product' method='post'>" +
259
+ "<div style='margin:0;padding:0;display:inline'>" +
260
+ "<input name='utf8' type='hidden' value='&#x2713;' /></div>" +
261
+ "#{expected_input_field}</form>"
262
+ end
263
+
264
+ end
@@ -0,0 +1,47 @@
1
+ require '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