delocalize 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +24 -30
- data/lib/delocalize/i18n_ext.rb +11 -5
- data/lib/delocalize/rails_ext/action_view.rb +4 -53
- data/lib/delocalize/rails_ext/action_view_rails3.rb +53 -0
- data/lib/delocalize/rails_ext/action_view_rails4.rb +38 -0
- data/lib/delocalize/rails_ext/active_record.rb +4 -79
- data/lib/delocalize/rails_ext/active_record_rails3.rb +80 -0
- data/lib/delocalize/rails_ext/active_record_rails4.rb +72 -0
- data/test/delocalize_test.rb +34 -35
- data/test/test_helper.rb +3 -7
- metadata +8 -9
data/README.markdown
CHANGED
@@ -1,16 +1,20 @@
|
|
1
|
-
delocalize
|
2
|
-
==========
|
1
|
+
# delocalize
|
3
2
|
|
4
3
|
[![Build Status](https://secure.travis-ci.org/clemens/delocalize.png)](http://travis-ci.org/clemens/delocalize)
|
5
4
|
|
6
5
|
delocalize provides localized date/time and number parsing functionality for Rails.
|
7
6
|
|
8
|
-
|
9
|
-
|
7
|
+
## 1.0 Beta
|
8
|
+
|
9
|
+
Check out the [1-0-beta branch](https://github.com/clemens/delocalize/tree/1-0-beta) for an unfinished/unreleased improved version of delocalize. The goal is to get rid of all the nasty hacks and have a library that doesn't break with every new release of Rails.
|
10
|
+
|
11
|
+
Also check out the [ongoing discussion the wiki](https://github.com/clemens/delocalize/wiki/delocalize-1.0) and feel free to put in your opinion there.
|
12
|
+
|
13
|
+
## Installation
|
10
14
|
|
11
15
|
You can use delocalize as a gem (preferred). Using delocalize as a Rails plugin has been discontinued and is no supported. If you want/need to use delocalize as a gem (I really don't see a reason why you'd want to), consider using the `0-2-stable` branch.
|
12
16
|
|
13
|
-
### Rails 3
|
17
|
+
### Rails 3 and Rails 4
|
14
18
|
|
15
19
|
To use delocalize, put the following gem requirement in your `Gemfile`:
|
16
20
|
|
@@ -20,7 +24,7 @@ gem "delocalize"
|
|
20
24
|
|
21
25
|
### Rails 2
|
22
26
|
|
23
|
-
Note: Support for Rails 2 has been discontinued. This version is only considered stable for Rails 3. If you need Rails 2 support, please use the `0.2.x` versions or the `0-2-stable` branch respectively.
|
27
|
+
Note: Support for Rails 2 has been discontinued. This version is only considered stable for Rails >= 3. If you need Rails 2 support, please use the `0.2.x` versions or the `0-2-stable` branch respectively.
|
24
28
|
|
25
29
|
To use delocalize, put the following gem requirement in your `environment.rb`:
|
26
30
|
|
@@ -30,8 +34,7 @@ config.gem "delocalize", :source => 'http://gemcutter.org'
|
|
30
34
|
|
31
35
|
In Rails 2.3, alternatively, you can use it with Bundler. See http://gembundler.com/rails23.html for instructions.
|
32
36
|
|
33
|
-
What does it do? And how do I use it?
|
34
|
-
--------------------------------------
|
37
|
+
## What does it do? And how do I use it?
|
35
38
|
|
36
39
|
Delocalize, just as the name suggest, does pretty much the opposite of localize.
|
37
40
|
|
@@ -96,8 +99,8 @@ delocalize then overrides `to_input_field_tag` in ActionView's `InstanceTag` so
|
|
96
99
|
|
97
100
|
In this example, a user can enter the release date and the price just like he's used to in his language, for example:
|
98
101
|
|
99
|
-
> Name: "Couch"
|
100
|
-
> Released on: "12. Oktober 2009"
|
102
|
+
> Name: "Couch"
|
103
|
+
> Released on: "12. Oktober 2009"
|
101
104
|
> Price: "2.999,90"
|
102
105
|
|
103
106
|
When saved, ActiveRecord automatically converts these to a regular Ruby date and number.
|
@@ -107,11 +110,11 @@ Edit forms then also show localized dates/numbers. By default, dates and times a
|
|
107
110
|
You can also customize the output using some options:
|
108
111
|
|
109
112
|
The price should always show two decimal digits and we don't need the delimiter:
|
110
|
-
|
113
|
+
|
111
114
|
```erb
|
112
115
|
<%= f.text_field :price, :precision => 2, :delimiter => '' %>
|
113
116
|
```
|
114
|
-
|
117
|
+
|
115
118
|
The `released_on` date should be shown in the `:full` format:
|
116
119
|
|
117
120
|
```erb
|
@@ -124,7 +127,7 @@ You can also customize the output using some options:
|
|
124
127
|
<%= f.text_field :released_on, :format => "%B %Y" %>
|
125
128
|
```
|
126
129
|
|
127
|
-
|
130
|
+
## Ruby 1.9 + Psych YAML Parser
|
128
131
|
|
129
132
|
You will need to adjust the localization formatting when using the new YAML parser Psych. Below is an example error message you may receive in your logs as well as an example of acceptable formatting and helpful links for reference:
|
130
133
|
|
@@ -208,7 +211,7 @@ en:
|
|
208
211
|
- :year
|
209
212
|
time:
|
210
213
|
input:
|
211
|
-
formats:
|
214
|
+
formats:
|
212
215
|
- :default
|
213
216
|
- :long
|
214
217
|
- :short
|
@@ -222,26 +225,17 @@ en:
|
|
222
225
|
pm: "pm"
|
223
226
|
```
|
224
227
|
|
225
|
-
|
228
|
+
## Compatibility
|
226
229
|
|
227
230
|
* Tested with Rails 2.3.5 in Ruby 1.8.7, Ruby 1.9.1 and Ruby 1.9.2 (head)
|
228
|
-
* Tested with Rails 3
|
229
|
-
|
230
|
-
### Contributors
|
231
|
+
* Tested with Rails 3 in Ruby 1.9.3, Ruby 2.0 and Ruby 2.1 (head)
|
232
|
+
* Tested with Rails 4 in Ruby 1.9.3, Ruby 2.0 and Ruby 2.1 (head)
|
231
233
|
|
232
|
-
|
234
|
+
## Contributors
|
233
235
|
|
234
|
-
|
235
|
-
* [Stephan Zalewski](http://github.com/stepahn)
|
236
|
-
* [Lailson Bandeira](http://github.com/lailsonbm)
|
237
|
-
* [Carlos Antonio da Silva](http://github.com/carlosantoniodasilva)
|
238
|
-
* [Michele Franzin](http://github.com/michelefranzin)
|
239
|
-
* [Raphaela Wrede](https://github.com/rwrede)
|
240
|
-
* [Jan De Poorter](https://github.com/DefV)
|
241
|
-
* [Blake Lucchesi](https://github.com/BlakeLucchesi)
|
242
|
-
* [Ralph von der Heyden](https://github.com/ralph)
|
236
|
+
Thanks to [all the people who contributed](https://github.com/clemens/delocalize/graphs/contributors) and submitted issues.
|
243
237
|
|
244
|
-
|
238
|
+
## TODO
|
245
239
|
|
246
240
|
* Improve test coverage
|
247
241
|
* Separate Ruby/Rails stuff to make it usable outside Rails
|
@@ -249,6 +243,6 @@ People who have contributed to delocalize (in no particular order):
|
|
249
243
|
* Implement AM/PM support
|
250
244
|
* Cleanup, cleanup, cleanup ...
|
251
245
|
|
252
|
-
Copyright (c) 2009-
|
246
|
+
Copyright (c) 2009-2014 Clemens Kofler <clemens@railway.at>
|
253
247
|
<http://www.railway.at/>
|
254
248
|
Released under the MIT license
|
data/lib/delocalize/i18n_ext.rb
CHANGED
@@ -16,15 +16,21 @@ module I18n
|
|
16
16
|
def with_delocalization_disabled(&block)
|
17
17
|
old_value = I18n.enable_delocalization
|
18
18
|
I18n.enable_delocalization = false
|
19
|
-
|
20
|
-
|
19
|
+
begin
|
20
|
+
yield
|
21
|
+
ensure
|
22
|
+
I18n.enable_delocalization = old_value
|
23
|
+
end
|
21
24
|
end
|
22
25
|
|
23
26
|
def with_delocalization_enabled(&block)
|
24
27
|
old_value = I18n.enable_delocalization
|
25
28
|
I18n.enable_delocalization = true
|
26
|
-
|
27
|
-
|
29
|
+
begin
|
30
|
+
yield
|
31
|
+
ensure
|
32
|
+
I18n.enable_delocalization = old_value
|
33
|
+
end
|
28
34
|
end
|
29
35
|
end
|
30
|
-
end
|
36
|
+
end
|
@@ -1,54 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
ActionView::Helpers::InstanceTag.class_eval do
|
7
|
-
include ActionView::Helpers::NumberHelper
|
8
|
-
|
9
|
-
alias original_to_input_field_tag to_input_field_tag
|
10
|
-
def to_input_field_tag(field_type, options = {})
|
11
|
-
options.symbolize_keys!
|
12
|
-
# numbers and dates/times should be localized unless value is already defined
|
13
|
-
if object && (options[:value].blank? || !options[:value].is_a?(String)) && object.respond_to?(:column_for_attribute) && column = object.column_for_attribute(method_name)
|
14
|
-
value = options[:value] || object.send(method_name)
|
15
|
-
|
16
|
-
if column.number?
|
17
|
-
number_options = I18n.t(:'number.format')
|
18
|
-
separator = options.delete(:separator) || number_options[:separator]
|
19
|
-
delimiter = options.delete(:delimiter) || number_options[:delimiter]
|
20
|
-
precision = options.delete(:precision) || number_options[:precision]
|
21
|
-
opts = { :separator => separator, :delimiter => delimiter, :precision => precision }
|
22
|
-
# integers don't need a precision
|
23
|
-
opts.merge!(:precision => 0) if column.type == :integer
|
24
|
-
|
25
|
-
hidden_for_integer = field_type == 'hidden' && column.type == :integer
|
26
|
-
|
27
|
-
# the number will be formatted only if it has no numericality errors
|
28
|
-
if object.respond_to?(:errors) && !Array(object.errors[method_name]).try(:include?, 'is not a number')
|
29
|
-
# we don't format integer hidden fields because this breaks nested_attributes
|
30
|
-
options[:value] = number_with_precision(value, opts) unless hidden_for_integer
|
31
|
-
end
|
32
|
-
elsif column.date? || column.time?
|
33
|
-
options[:value] = value ? I18n.l(value, :format => options.delete(:format)) : nil
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
original_to_input_field_tag(field_type, options)
|
38
|
-
end
|
1
|
+
if Gem::Version.new(ActionPack::VERSION::STRING) >= Gem::Version.new('4.0.0.beta')
|
2
|
+
require 'delocalize/rails_ext/action_view_rails4'
|
3
|
+
else
|
4
|
+
require 'delocalize/rails_ext/action_view_rails3'
|
39
5
|
end
|
40
|
-
|
41
|
-
# TODO: does it make sense to also override FormTagHelper methods?
|
42
|
-
# ActionView::Helpers::FormTagHelper.class_eval do
|
43
|
-
# include ActionView::Helpers::NumberHelper
|
44
|
-
#
|
45
|
-
# alias original_text_field_tag text_field_tag
|
46
|
-
# def text_field_tag(name, value = nil, options = {})
|
47
|
-
# value = options.delete(:value) if options.key?(:value)
|
48
|
-
# if value.is_a?(Numeric)
|
49
|
-
# value = number_with_delimiter(value)
|
50
|
-
# end
|
51
|
-
# original_text_field_tag(name, value, options)
|
52
|
-
# end
|
53
|
-
# end
|
54
|
-
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'action_view'
|
2
|
+
|
3
|
+
# TODO: also override other methods like to_check_box_tag since they might contain numeric values?
|
4
|
+
# ActionView needs some patching too
|
5
|
+
|
6
|
+
ActionView::Helpers::InstanceTag.class_eval do
|
7
|
+
include ActionView::Helpers::NumberHelper
|
8
|
+
|
9
|
+
alias original_to_input_field_tag to_input_field_tag
|
10
|
+
def to_input_field_tag(field_type, options = {})
|
11
|
+
options.symbolize_keys!
|
12
|
+
# numbers and dates/times should be localized unless value is already defined
|
13
|
+
if object && (options[:value].blank? || !options[:value].is_a?(String)) && object.respond_to?(:column_for_attribute) && column = object.column_for_attribute(method_name)
|
14
|
+
value = options[:value] || object.send(method_name)
|
15
|
+
|
16
|
+
if column.number?
|
17
|
+
number_options = I18n.t(:'number.format')
|
18
|
+
separator = options.delete(:separator) || number_options[:separator]
|
19
|
+
delimiter = options.delete(:delimiter) || number_options[:delimiter]
|
20
|
+
precision = options.delete(:precision) || number_options[:precision]
|
21
|
+
opts = { :separator => separator, :delimiter => delimiter, :precision => precision }
|
22
|
+
# integers don't need a precision
|
23
|
+
opts.merge!(:precision => 0) if column.type == :integer
|
24
|
+
|
25
|
+
hidden_for_integer = field_type == 'hidden' && column.type == :integer
|
26
|
+
|
27
|
+
# the number will be formatted only if it has no numericality errors
|
28
|
+
if object.respond_to?(:errors) && !Array(object.errors[method_name]).try(:include?, 'is not a number')
|
29
|
+
# we don't format integer hidden fields because this breaks nested_attributes
|
30
|
+
options[:value] = number_with_precision(value, opts) unless hidden_for_integer
|
31
|
+
end
|
32
|
+
elsif column.date? || column.time?
|
33
|
+
options[:value] = value ? I18n.l(value, :format => options.delete(:format)) : nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
original_to_input_field_tag(field_type, options)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# TODO: does it make sense to also override FormTagHelper methods?
|
42
|
+
# ActionView::Helpers::FormTagHelper.class_eval do
|
43
|
+
# include ActionView::Helpers::NumberHelper
|
44
|
+
#
|
45
|
+
# alias original_text_field_tag text_field_tag
|
46
|
+
# def text_field_tag(name, value = nil, options = {})
|
47
|
+
# value = options.delete(:value) if options.key?(:value)
|
48
|
+
# if value.is_a?(Numeric)
|
49
|
+
# value = number_with_delimiter(value)
|
50
|
+
# end
|
51
|
+
# original_text_field_tag(name, value, options)
|
52
|
+
# end
|
53
|
+
# end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'action_view'
|
2
|
+
|
3
|
+
# TODO: also override other methods like to_check_box_tag since they might contain numeric values?
|
4
|
+
# ActionView needs some patching too
|
5
|
+
|
6
|
+
ActionView::Helpers::Tags::TextField.class_eval do
|
7
|
+
include ActionView::Helpers::NumberHelper
|
8
|
+
|
9
|
+
def render_with_localization
|
10
|
+
if object && (@options[:value].blank? || !@options[:value].is_a?(String)) && object.respond_to?(:column_for_attribute) && column = object.column_for_attribute(@method_name)
|
11
|
+
value = @options[:value] || object.send(@method_name)
|
12
|
+
|
13
|
+
if column.number?
|
14
|
+
number_options = I18n.t(:'number.format')
|
15
|
+
separator = @options.delete(:separator) || number_options[:separator]
|
16
|
+
delimiter = @options.delete(:delimiter) || number_options[:delimiter]
|
17
|
+
precision = @options.delete(:precision) || number_options[:precision]
|
18
|
+
opts = { :separator => separator, :delimiter => delimiter, :precision => precision }
|
19
|
+
# integers don't need a precision
|
20
|
+
opts.merge!(:precision => 0) if column.type == :integer
|
21
|
+
|
22
|
+
hidden_for_integer = field_type == 'hidden' && column.type == :integer
|
23
|
+
|
24
|
+
# the number will be formatted only if it has no numericality errors
|
25
|
+
if object.respond_to?(:errors) && !Array(object.errors[@method_name]).try(:include?, 'is not a number')
|
26
|
+
# we don't format integer hidden fields because this breaks nested_attributes
|
27
|
+
@options[:value] = number_with_precision(value, opts) unless hidden_for_integer
|
28
|
+
end
|
29
|
+
elsif column.date? || column.time?
|
30
|
+
@options[:value] = value ? I18n.l(value, :format => @options.delete(:format)) : nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
render_without_localization
|
35
|
+
end
|
36
|
+
|
37
|
+
alias_method_chain :render, :localization
|
38
|
+
end
|
@@ -1,80 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
require 'active_record/connection_adapters/column'
|
6
|
-
rescue LoadError
|
7
|
-
# Not Rails 3.1, it seems
|
8
|
-
end
|
9
|
-
|
10
|
-
# let's hack into ActiveRecord a bit - everything at the lowest possible level, of course, so we minimalize side effects
|
11
|
-
ActiveRecord::ConnectionAdapters::Column.class_eval do
|
12
|
-
def date?
|
13
|
-
klass == Date
|
14
|
-
end
|
15
|
-
|
16
|
-
def time?
|
17
|
-
klass == Time
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
ActiveRecord::Base.class_eval do
|
22
|
-
def write_attribute_with_localization(attr_name, original_value)
|
23
|
-
new_value = original_value
|
24
|
-
if column = column_for_attribute(attr_name.to_s)
|
25
|
-
if column.date?
|
26
|
-
new_value = Date.parse_localized(original_value) rescue original_value
|
27
|
-
elsif column.time?
|
28
|
-
new_value = Time.parse_localized(original_value) rescue original_value
|
29
|
-
end
|
30
|
-
end
|
31
|
-
write_attribute_without_localization(attr_name, new_value)
|
32
|
-
end
|
33
|
-
alias_method_chain :write_attribute, :localization
|
34
|
-
|
35
|
-
def convert_number_column_value_with_localization(value)
|
36
|
-
value = convert_number_column_value_without_localization(value)
|
37
|
-
value = Numeric.parse_localized(value) if I18n.delocalization_enabled?
|
38
|
-
value
|
39
|
-
end
|
40
|
-
alias_method_chain :convert_number_column_value, :localization
|
41
|
-
|
42
|
-
|
43
|
-
define_method( (Gem::Version.new(ActiveRecord::VERSION::STRING) < Gem::Version.new('3.2.9')) ? :field_changed? : :_field_changed? ) do |attr, old, value|
|
44
|
-
if column = column_for_attribute(attr)
|
45
|
-
if column.number? && column.null && (old.nil? || old == 0) && value.blank?
|
46
|
-
# For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
|
47
|
-
# Hence we don't record it as a change if the value changes from nil to ''.
|
48
|
-
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
|
49
|
-
# be typecast back to 0 (''.to_i => 0)
|
50
|
-
value = nil
|
51
|
-
elsif column.number?
|
52
|
-
value = column.type_cast(convert_number_column_value_with_localization(value))
|
53
|
-
else
|
54
|
-
value = column.type_cast(value)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
old != value
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
ActiveRecord::Base.instance_eval do
|
62
|
-
def define_method_attribute=(attr_name)
|
63
|
-
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
|
64
|
-
method_body, line = <<-EOV, __LINE__ + 1
|
65
|
-
def #{attr_name}=(original_time)
|
66
|
-
time = original_time
|
67
|
-
unless time.acts_like?(:time)
|
68
|
-
time = time.is_a?(String) ? (I18n.delocalization_enabled? ? Time.zone.parse_localized(time) : Time.zone.parse(time)) : time.to_time rescue time
|
69
|
-
end
|
70
|
-
time = time.in_time_zone rescue nil if time
|
71
|
-
write_attribute(:#{attr_name}, original_time)
|
72
|
-
@attributes_cache["#{attr_name}"] = time
|
73
|
-
end
|
74
|
-
EOV
|
75
|
-
generated_attribute_methods.module_eval(method_body, __FILE__, line)
|
76
|
-
else
|
77
|
-
super
|
78
|
-
end
|
79
|
-
end
|
1
|
+
if Gem::Version.new(ActionPack::VERSION::STRING) >= Gem::Version.new('4.0.0.beta')
|
2
|
+
require 'delocalize/rails_ext/active_record_rails4'
|
3
|
+
else
|
4
|
+
require 'delocalize/rails_ext/active_record_rails3'
|
80
5
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
require 'active_record/connection_adapters/abstract/schema_definitions'
|
4
|
+
begin
|
5
|
+
require 'active_record/connection_adapters/column'
|
6
|
+
rescue LoadError
|
7
|
+
# Not Rails 3.1, it seems
|
8
|
+
end
|
9
|
+
|
10
|
+
# let's hack into ActiveRecord a bit - everything at the lowest possible level, of course, so we minimalize side effects
|
11
|
+
ActiveRecord::ConnectionAdapters::Column.class_eval do
|
12
|
+
def date?
|
13
|
+
klass == Date
|
14
|
+
end
|
15
|
+
|
16
|
+
def time?
|
17
|
+
klass == Time
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
ActiveRecord::Base.class_eval do
|
22
|
+
def write_attribute_with_localization(attr_name, original_value)
|
23
|
+
new_value = original_value
|
24
|
+
if column = column_for_attribute(attr_name.to_s)
|
25
|
+
if column.date?
|
26
|
+
new_value = Date.parse_localized(original_value) rescue original_value
|
27
|
+
elsif column.time?
|
28
|
+
new_value = Time.parse_localized(original_value) rescue original_value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
write_attribute_without_localization(attr_name, new_value)
|
32
|
+
end
|
33
|
+
alias_method_chain :write_attribute, :localization
|
34
|
+
|
35
|
+
def convert_number_column_value_with_localization(value)
|
36
|
+
value = convert_number_column_value_without_localization(value)
|
37
|
+
value = Numeric.parse_localized(value) if I18n.delocalization_enabled?
|
38
|
+
value
|
39
|
+
end
|
40
|
+
alias_method_chain :convert_number_column_value, :localization
|
41
|
+
|
42
|
+
|
43
|
+
define_method( (Gem::Version.new(ActiveRecord::VERSION::STRING) < Gem::Version.new('3.2.9')) ? :field_changed? : :_field_changed? ) do |attr, old, value|
|
44
|
+
if column = column_for_attribute(attr)
|
45
|
+
if column.number? && column.null && (old.nil? || old == 0) && value.blank?
|
46
|
+
# For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
|
47
|
+
# Hence we don't record it as a change if the value changes from nil to ''.
|
48
|
+
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
|
49
|
+
# be typecast back to 0 (''.to_i => 0)
|
50
|
+
value = nil
|
51
|
+
elsif column.number?
|
52
|
+
value = column.type_cast(convert_number_column_value_with_localization(value))
|
53
|
+
else
|
54
|
+
value = column.type_cast(value)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
old != value
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
ActiveRecord::Base.instance_eval do
|
62
|
+
def define_method_attribute=(attr_name)
|
63
|
+
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
|
64
|
+
method_body, line = <<-EOV, __LINE__ + 1
|
65
|
+
def #{attr_name}=(original_time)
|
66
|
+
time = original_time
|
67
|
+
unless time.acts_like?(:time)
|
68
|
+
time = time.is_a?(String) ? (I18n.delocalization_enabled? ? Time.zone.parse_localized(time) : Time.zone.parse(time)) : time.to_time rescue time
|
69
|
+
end
|
70
|
+
time = time.in_time_zone rescue nil if time
|
71
|
+
write_attribute(:#{attr_name}, original_time)
|
72
|
+
@attributes_cache["#{attr_name}"] = time
|
73
|
+
end
|
74
|
+
EOV
|
75
|
+
generated_attribute_methods.module_eval(method_body, __FILE__, line)
|
76
|
+
else
|
77
|
+
super
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
# let's hack into ActiveRecord a bit - everything at the lowest possible level, of course, so we minimalize side effects
|
4
|
+
ActiveRecord::ConnectionAdapters::Column.class_eval do
|
5
|
+
def date?
|
6
|
+
klass == Date
|
7
|
+
end
|
8
|
+
|
9
|
+
def time?
|
10
|
+
klass == Time
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ActiveRecord::AttributeMethods::Write
|
15
|
+
def type_cast_attribute_for_write(column, value)
|
16
|
+
return value unless column
|
17
|
+
|
18
|
+
value = Numeric.parse_localized(value) if column.number? && I18n.delocalization_enabled?
|
19
|
+
column.type_cast_for_write value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
ActiveRecord::Base.class_eval do
|
24
|
+
def write_attribute_with_localization(attr_name, original_value)
|
25
|
+
new_value = original_value
|
26
|
+
if column = column_for_attribute(attr_name.to_s)
|
27
|
+
if column.date?
|
28
|
+
new_value = Date.parse_localized(original_value) rescue original_value
|
29
|
+
elsif column.time?
|
30
|
+
new_value = Time.parse_localized(original_value) rescue original_value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
write_attribute_without_localization(attr_name, new_value)
|
34
|
+
end
|
35
|
+
alias_method_chain :write_attribute, :localization
|
36
|
+
|
37
|
+
define_method :_field_changed? do |attr, old, value|
|
38
|
+
if column = column_for_attribute(attr)
|
39
|
+
if column.number? && column.null && (old.nil? || old == 0) && value.blank?
|
40
|
+
# For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
|
41
|
+
# Hence we don't record it as a change if the value changes from nil to ''.
|
42
|
+
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
|
43
|
+
# be typecast back to 0 (''.to_i => 0)
|
44
|
+
value = nil
|
45
|
+
elsif column.number?
|
46
|
+
value = column.type_cast(Numeric.parse_localized(value))
|
47
|
+
else
|
48
|
+
value = column.type_cast(value)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
old != value
|
52
|
+
end
|
53
|
+
|
54
|
+
def define_method_attribute=(attr_name)
|
55
|
+
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
|
56
|
+
method_body, line = <<-EOV, __LINE__ + 1
|
57
|
+
def #{attr_name}=(original_time)
|
58
|
+
time = original_time
|
59
|
+
unless time.acts_like?(:time)
|
60
|
+
time = time.is_a?(String) ? (I18n.delocalization_enabled? ? Time.zone.parse_localized(time) : Time.zone.parse(time)) : time.to_time rescue time
|
61
|
+
end
|
62
|
+
time = time.in_time_zone rescue nil if time
|
63
|
+
write_attribute(:#{attr_name}, original_time)
|
64
|
+
@attributes_cache["#{attr_name}"] = time
|
65
|
+
end
|
66
|
+
EOV
|
67
|
+
generated_attribute_methods.module_eval(method_body, __FILE__, line)
|
68
|
+
else
|
69
|
+
super
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/test/delocalize_test.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
require 'test_helper'
|
4
|
-
require 'active_record/test_case'
|
5
|
-
require 'action_view/test_case'
|
6
4
|
|
7
|
-
class DelocalizeActiveRecordTest <
|
5
|
+
class DelocalizeActiveRecordTest < ActiveSupport::TestCase
|
8
6
|
def setup
|
9
7
|
Time.zone = 'Berlin' # make sure everything works as expected with TimeWithZone
|
10
8
|
Timecop.freeze(Time.zone.local(2009, 3, 1, 12, 0))
|
@@ -65,7 +63,7 @@ class DelocalizeActiveRecordTest < ActiveRecord::TestCase
|
|
65
63
|
end
|
66
64
|
|
67
65
|
test "delocalizes with fallback locale" do
|
68
|
-
I18n::Backend::Simple.include
|
66
|
+
I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
|
69
67
|
I18n.fallbacks[:xx] = [:xx, :tt]
|
70
68
|
|
71
69
|
I18n.with_locale :xx do
|
@@ -147,6 +145,14 @@ class DelocalizeActiveRecordTest < ActiveRecord::TestCase
|
|
147
145
|
end
|
148
146
|
end
|
149
147
|
|
148
|
+
test "properly resets when an error is raised in a with_delocalization_disabled block" do
|
149
|
+
I18n.enable_delocalization = true
|
150
|
+
I18n.with_delocalization_disabled do
|
151
|
+
raise 'error'
|
152
|
+
end rescue nil
|
153
|
+
assert_equal true, I18n.enable_delocalization
|
154
|
+
end
|
155
|
+
|
150
156
|
test "uses localized parsing if called with with_delocalization_enabled" do
|
151
157
|
I18n.with_delocalization_enabled do
|
152
158
|
@product.price = '1.299,99'
|
@@ -157,6 +163,14 @@ class DelocalizeActiveRecordTest < ActiveRecord::TestCase
|
|
157
163
|
end
|
158
164
|
end
|
159
165
|
|
166
|
+
test "properly resets when an error is raised in a with_delocalization_enabled block" do
|
167
|
+
I18n.enable_delocalization = false
|
168
|
+
I18n.with_delocalization_enabled do
|
169
|
+
raise 'error'
|
170
|
+
end rescue nil
|
171
|
+
assert_equal false, I18n.enable_delocalization
|
172
|
+
end
|
173
|
+
|
160
174
|
test "dirty attributes must detect changes in decimal columns" do
|
161
175
|
@product.price = 10
|
162
176
|
@product.save
|
@@ -202,7 +216,7 @@ class DelocalizeActiveRecordTest < ActiveRecord::TestCase
|
|
202
216
|
end
|
203
217
|
end
|
204
218
|
|
205
|
-
class DelocalizeActionViewTest <
|
219
|
+
class DelocalizeActionViewTest < ActiveSupport::TestCase
|
206
220
|
include ActionView::Helpers::FormHelper
|
207
221
|
|
208
222
|
def setup
|
@@ -212,62 +226,52 @@ class DelocalizeActionViewTest < ActionView::TestCase
|
|
212
226
|
|
213
227
|
test "shows text field using formatted number" do
|
214
228
|
@product.price = 1299.9
|
215
|
-
|
216
|
-
text_field(:product, :price)
|
229
|
+
assert_match /value="1\.299,90"/, text_field(:product, :price)
|
217
230
|
end
|
218
231
|
|
219
232
|
test "shows text field using formatted number with options" do
|
220
233
|
@product.price = 1299.995
|
221
|
-
|
222
|
-
text_field(:product, :price, :precision => 3, :delimiter => ',', :separator => '.')
|
234
|
+
assert_match /value="1,299\.995"/, text_field(:product, :price, :precision => 3, :delimiter => ',', :separator => '.')
|
223
235
|
end
|
224
236
|
|
225
237
|
test "shows text field using formatted number without precision if column is an integer" do
|
226
238
|
@product.times_sold = 20
|
227
|
-
|
228
|
-
text_field(:product, :times_sold)
|
239
|
+
assert_match /value="20"/, text_field(:product, :times_sold)
|
229
240
|
|
230
241
|
@product.times_sold = 2000
|
231
|
-
|
232
|
-
text_field(:product, :times_sold)
|
242
|
+
assert_match /value="2\.000"/, text_field(:product, :times_sold)
|
233
243
|
end
|
234
244
|
|
235
245
|
test "shows text field using formatted date" do
|
236
246
|
@product.released_on = Date.civil(2009, 10, 19)
|
237
|
-
|
238
|
-
text_field(:product, :released_on)
|
247
|
+
assert_match /value="19\.10\.2009"/, text_field(:product, :released_on)
|
239
248
|
end
|
240
249
|
|
241
250
|
test "shows text field using formatted date and time" do
|
242
251
|
@product.published_at = Time.zone.local(2009, 3, 1, 12, 0, 0)
|
243
252
|
# careful - leading whitespace with %e
|
244
|
-
|
245
|
-
text_field(:product, :published_at)
|
253
|
+
assert_match /value="Sonntag, 1\. März 2009, 12:00 Uhr"/, text_field(:product, :published_at)
|
246
254
|
end
|
247
255
|
|
248
256
|
test "shows text field using formatted date with format" do
|
249
257
|
@product.released_on = Date.civil(2009, 10, 19)
|
250
|
-
|
251
|
-
text_field(:product, :released_on, :format => :long)
|
258
|
+
assert_match /value="19\. Oktober 2009"/, text_field(:product, :released_on, :format => :long)
|
252
259
|
end
|
253
260
|
|
254
261
|
test "shows text field using formatted date and time with format" do
|
255
262
|
@product.published_at = Time.zone.local(2009, 3, 1, 12, 0, 0)
|
256
263
|
# careful - leading whitespace with %e
|
257
|
-
|
258
|
-
text_field(:product, :published_at, :format => :short)
|
264
|
+
assert_match /value=" 1\. März, 12:00 Uhr"/, text_field(:product, :published_at, :format => :short)
|
259
265
|
end
|
260
266
|
|
261
267
|
test "shows text field using formatted time with format" do
|
262
268
|
@product.cant_think_of_a_sensible_time_field = Time.zone.local(2009, 3, 1, 9, 0, 0)
|
263
|
-
|
264
|
-
text_field(:product, :cant_think_of_a_sensible_time_field, :format => :time)
|
269
|
+
assert_match /value="09:00 Uhr"/, text_field(:product, :cant_think_of_a_sensible_time_field, :format => :time)
|
265
270
|
end
|
266
271
|
|
267
272
|
test "integer hidden fields shouldn't be formatted" do
|
268
273
|
@product.times_sold = 1000
|
269
|
-
|
270
|
-
hidden_field(:product, :times_sold)
|
274
|
+
assert_match /value="1000"/, hidden_field(:product, :times_sold)
|
271
275
|
end
|
272
276
|
|
273
277
|
test "doesn't raise an exception when object is nil" do
|
@@ -287,28 +291,24 @@ class DelocalizeActionViewTest < ActionView::TestCase
|
|
287
291
|
|
288
292
|
test "delocalizes a given non-string :value" do
|
289
293
|
@product.price = 1299.9
|
290
|
-
|
291
|
-
text_field(:product, :price, :value => 1499.90)
|
294
|
+
assert_match /value="1\.499,90"/, text_field(:product, :price, :value => 1499.90)
|
292
295
|
end
|
293
296
|
|
294
297
|
test "doesn't override given string :value" do
|
295
298
|
@product.price = 1299.9
|
296
|
-
|
297
|
-
text_field(:product, :price, :value => "1.499,90")
|
299
|
+
assert_match /value="1\.499,90"/, text_field(:product, :price, :value => "1.499,90")
|
298
300
|
end
|
299
301
|
|
300
302
|
test "doesn't convert the value if field has numericality errors" do
|
301
303
|
@product = ProductWithValidation.new(:price => 'this is not a number')
|
302
304
|
@product.valid?
|
303
|
-
|
304
|
-
text_field(:product, :price)
|
305
|
+
assert_match /value="this is not a number"/, text_field(:product, :price)
|
305
306
|
end
|
306
307
|
|
307
308
|
test "should convert the value if field have non-numericality errors, but have other errors, e.g. business rules" do
|
308
309
|
@product = ProductWithBusinessValidation.new(:price => '1.337,66')
|
309
310
|
@product.valid?
|
310
|
-
|
311
|
-
text_field(:product, :price)
|
311
|
+
assert_match /value="1\.337,66"/, text_field(:product, :price)
|
312
312
|
end
|
313
313
|
|
314
314
|
test "doesn't raise an exception when object isn't an ActiveReccord" do
|
@@ -324,8 +324,7 @@ class DelocalizeActionViewTest < ActionView::TestCase
|
|
324
324
|
end
|
325
325
|
|
326
326
|
test "formats field with default value correctly" do
|
327
|
-
|
328
|
-
text_field(:product, :some_value_with_default)
|
327
|
+
assert_match /value="0,00"/, text_field(:product, :some_value_with_default)
|
329
328
|
end
|
330
329
|
end
|
331
330
|
|
data/test/test_helper.rb
CHANGED
@@ -6,8 +6,7 @@ require 'bundler'
|
|
6
6
|
Bundler.require(:default, :development)
|
7
7
|
|
8
8
|
require 'rails/all'
|
9
|
-
|
10
|
-
require 'test/unit'
|
9
|
+
require 'rails/test_help'
|
11
10
|
|
12
11
|
require 'delocalize/rails_ext/action_view'
|
13
12
|
require 'delocalize/rails_ext/active_record'
|
@@ -66,6 +65,7 @@ tt[:date][:formats][:default] = '%d|%m|%Y'
|
|
66
65
|
I18n.backend.store_translations :de, de
|
67
66
|
I18n.backend.store_translations :tt, tt
|
68
67
|
|
68
|
+
I18n.enforce_available_locales = false
|
69
69
|
I18n.locale = :de
|
70
70
|
|
71
71
|
class NonArProduct
|
@@ -83,11 +83,7 @@ class ProductWithValidation < Product
|
|
83
83
|
end
|
84
84
|
|
85
85
|
class ProductWithBusinessValidation < Product
|
86
|
-
|
87
|
-
if record.price > 10
|
88
|
-
record.errors.add(:price, :invalid)
|
89
|
-
end
|
90
|
-
end
|
86
|
+
validates_numericality_of :price, :less_than => 10
|
91
87
|
end
|
92
88
|
|
93
89
|
config = YAML.load_file(File.dirname(__FILE__) + '/database.yml')
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: delocalize
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2014-08-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -19,9 +19,6 @@ dependencies:
|
|
19
19
|
- - ! '>='
|
20
20
|
- !ruby/object:Gem::Version
|
21
21
|
version: '3.0'
|
22
|
-
- - <
|
23
|
-
- !ruby/object:Gem::Version
|
24
|
-
version: '4'
|
25
22
|
type: :runtime
|
26
23
|
prerelease: false
|
27
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -30,9 +27,6 @@ dependencies:
|
|
30
27
|
- - ! '>='
|
31
28
|
- !ruby/object:Gem::Version
|
32
29
|
version: '3.0'
|
33
|
-
- - <
|
34
|
-
- !ruby/object:Gem::Version
|
35
|
-
version: '4'
|
36
30
|
- !ruby/object:Gem::Dependency
|
37
31
|
name: timecop
|
38
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -60,7 +54,11 @@ files:
|
|
60
54
|
- lib/delocalize/localized_date_time_parser.rb
|
61
55
|
- lib/delocalize/localized_numeric_parser.rb
|
62
56
|
- lib/delocalize/rails_ext/action_view.rb
|
57
|
+
- lib/delocalize/rails_ext/action_view_rails3.rb
|
58
|
+
- lib/delocalize/rails_ext/action_view_rails4.rb
|
63
59
|
- lib/delocalize/rails_ext/active_record.rb
|
60
|
+
- lib/delocalize/rails_ext/active_record_rails3.rb
|
61
|
+
- lib/delocalize/rails_ext/active_record_rails4.rb
|
64
62
|
- lib/delocalize/rails_ext/time_zone.rb
|
65
63
|
- lib/delocalize/railtie.rb
|
66
64
|
- lib/delocalize/ruby_ext/date.rb
|
@@ -75,7 +73,8 @@ files:
|
|
75
73
|
- test/delocalize_test.rb
|
76
74
|
- test/test_helper.rb
|
77
75
|
homepage: http://github.com/clemens/delocalize
|
78
|
-
licenses:
|
76
|
+
licenses:
|
77
|
+
- MIT
|
79
78
|
post_install_message:
|
80
79
|
rdoc_options:
|
81
80
|
- --charset=UTF-8
|