amount_field 1.4.0

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