amount_field_rails3 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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