nulogy-delocalize 0.3.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.markdown +233 -0
- data/Rakefile +14 -0
- data/VERSION +1 -0
- data/lib/delocalize.rb +9 -0
- data/lib/delocalize/i18n_ext.rb +30 -0
- data/lib/delocalize/localized_date_time_parser.rb +75 -0
- data/lib/delocalize/localized_numeric_parser.rb +18 -0
- data/lib/delocalize/rails_ext/action_view.rb +57 -0
- data/lib/delocalize/rails_ext/active_record.rb +80 -0
- data/lib/delocalize/rails_ext/time_zone.rb +7 -0
- data/lib/delocalize/railtie.rb +15 -0
- data/lib/delocalize/ruby_ext.rb +4 -0
- data/lib/delocalize/ruby_ext/date.rb +9 -0
- data/lib/delocalize/ruby_ext/datetime.rb +9 -0
- data/lib/delocalize/ruby_ext/numeric.rb +9 -0
- data/lib/delocalize/ruby_ext/time.rb +9 -0
- data/test/database.yml +3 -0
- data/test/delocalize_test.rb +315 -0
- data/test/test_helper.rb +104 -0
- metadata +118 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009-2011 Clemens Kofler and contributors
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
delocalize
|
2
|
+
==========
|
3
|
+
|
4
|
+
[![Build Status](https://secure.travis-ci.org/clemens/delocalize.png)](http://travis-ci.org/clemens/delocalize)
|
5
|
+
|
6
|
+
delocalize provides localized date/time and number parsing functionality for Rails.
|
7
|
+
|
8
|
+
Installation
|
9
|
+
------------
|
10
|
+
|
11
|
+
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
|
+
|
13
|
+
### Rails 3
|
14
|
+
|
15
|
+
To use delocalize, put the following gem requirement in your `Gemfile`:
|
16
|
+
|
17
|
+
gem "delocalize"
|
18
|
+
|
19
|
+
### Rails 2
|
20
|
+
|
21
|
+
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.
|
22
|
+
|
23
|
+
To use delocalize, put the following gem requirement in your `environment.rb`:
|
24
|
+
|
25
|
+
config.gem "delocalize", :source => 'http://gemcutter.org'
|
26
|
+
|
27
|
+
In Rails 2.3, alternatively, you can use it with Bundler. See http://gembundler.com/rails23.html for instructions.
|
28
|
+
|
29
|
+
What does it do? And how do I use it?
|
30
|
+
--------------------------------------
|
31
|
+
|
32
|
+
Delocalize, just as the name suggest, does pretty much the opposite of localize.
|
33
|
+
|
34
|
+
In the grey past, if you want your users to be able to input localized data, such as dates and numbers, you had to manually override attribute accessors:
|
35
|
+
|
36
|
+
def price=(price)
|
37
|
+
write_attribute(:price, price.gsub(',', '.'))
|
38
|
+
end
|
39
|
+
|
40
|
+
delocalize does this under the covers -- all you need is your regular translation data (as YAML or Ruby file) where you need Rails' standard translations:
|
41
|
+
|
42
|
+
de:
|
43
|
+
number:
|
44
|
+
format:
|
45
|
+
separator: ','
|
46
|
+
delimiter: '.'
|
47
|
+
date:
|
48
|
+
input:
|
49
|
+
formats: [:default, :long, :short] # <- this and ...
|
50
|
+
|
51
|
+
formats:
|
52
|
+
default: "%d.%m.%Y"
|
53
|
+
short: "%e. %b"
|
54
|
+
long: "%e. %B %Y"
|
55
|
+
only_day: "%e"
|
56
|
+
|
57
|
+
day_names: [Sonntag, Montag, Dienstag, Mittwoch, Donnerstag, Freitag, Samstag]
|
58
|
+
abbr_day_names: [So, Mo, Di, Mi, Do, Fr, Sa]
|
59
|
+
month_names: [~, Januar, Februar, März, April, Mai, Juni, Juli, August, September, Oktober, November, Dezember]
|
60
|
+
abbr_month_names: [~, Jan, Feb, Mär, Apr, Mai, Jun, Jul, Aug, Sep, Okt, Nov, Dez]
|
61
|
+
order: [ :day, :month, :year ]
|
62
|
+
|
63
|
+
time:
|
64
|
+
input:
|
65
|
+
formats: [:long, :medium, :short, :default, :time] # <- ... this are the only non-standard keys
|
66
|
+
formats:
|
67
|
+
default: "%A, %e. %B %Y, %H:%M Uhr"
|
68
|
+
short: "%e. %B, %H:%M Uhr"
|
69
|
+
long: "%A, %e. %B %Y, %H:%M Uhr"
|
70
|
+
time: "%H:%M"
|
71
|
+
|
72
|
+
am: "vormittags"
|
73
|
+
pm: "nachmittags"
|
74
|
+
|
75
|
+
For dates and times, you have to define input formats which are taken from the actual formats. The important thing here is to define input formats sorted by descending complexity; in other words: the format which contains the most (preferably non-numeric) information should be first in the list because it can produce the most reliable match. Exception: If you think there most complex format is not the one that most users will input, you can put the most-used in front so you save unnecessary iterations.
|
76
|
+
|
77
|
+
Careful with formats containing only numbers: It's very hard to produce reliable matches if you provide multiple strictly numeric formats!
|
78
|
+
|
79
|
+
delocalize then overrides `to_input_field_tag` in ActionView's `InstanceTag` so you can use localized text fields:
|
80
|
+
|
81
|
+
<% form_for @product do |f| %>
|
82
|
+
<%= f.text_field :name %>
|
83
|
+
<%= f.text_field :released_on %>
|
84
|
+
<%= f.text_field :price %>
|
85
|
+
<% end %>
|
86
|
+
|
87
|
+
In this example, a user can enter the release date and the price just like he's used to in his language, for example:
|
88
|
+
|
89
|
+
> Name: "Couch"
|
90
|
+
> Released on: "12. Oktober 2009"
|
91
|
+
> Price: "2.999,90"
|
92
|
+
|
93
|
+
When saved, ActiveRecord automatically converts these to a regular Ruby date and number.
|
94
|
+
|
95
|
+
Edit forms then also show localized dates/numbers. By default, dates and times are localized using the format named :default in your locale file. So with the above locale file, dates would use `%d.%m.%Y` and times would use `%A, %e. %B %Y, %H:%M Uhr`. Numbers are also formatted using your locale's thousands delimiter and decimal separator.
|
96
|
+
|
97
|
+
You can also customize the output using some options:
|
98
|
+
|
99
|
+
The price should always show two decimal digits and we don't need the delimiter:
|
100
|
+
<%= f.text_field :price, :precision => 2, :delimiter => '' %>
|
101
|
+
|
102
|
+
The `released_on` date should be shown in the `:full` format:
|
103
|
+
<%= f.text_field :released_on, :format => :full %>
|
104
|
+
|
105
|
+
Since `I18n.localize` supports localizing `strftime` strings, we can also do this:
|
106
|
+
<%= f.text_field :released_on, :format => "%B %Y" %>
|
107
|
+
|
108
|
+
### Ruby 1.9 + Psych YAML Parser
|
109
|
+
|
110
|
+
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:
|
111
|
+
|
112
|
+
__Error message from logs:__
|
113
|
+
|
114
|
+
Psych::SyntaxError (couldn't parse YAML at line x column y):
|
115
|
+
|
116
|
+
__References:__
|
117
|
+
|
118
|
+
The solution can be found here: http://stackoverflow.com/questions/4980877/rails-error-couldnt-parse-yaml#answer-5323060
|
119
|
+
|
120
|
+
http://pivotallabs.com/users/mkocher/blog/articles/1692-yaml-psych-and-ruby-1-9-2-p180-here-there-be-dragons
|
121
|
+
|
122
|
+
__Psych Preferred Formatting:__
|
123
|
+
|
124
|
+
en:
|
125
|
+
number:
|
126
|
+
format:
|
127
|
+
separator: '.'
|
128
|
+
delimiter: ','
|
129
|
+
precision: 2
|
130
|
+
date:
|
131
|
+
input:
|
132
|
+
formats:
|
133
|
+
- :default
|
134
|
+
- :long
|
135
|
+
- :short
|
136
|
+
formats:
|
137
|
+
default: "%m/%d/%Y"
|
138
|
+
short: "%b %e"
|
139
|
+
long: "%B %e, %Y"
|
140
|
+
only_day: "%e"
|
141
|
+
day_names:
|
142
|
+
- Sunday
|
143
|
+
- Monday
|
144
|
+
- Tuesday
|
145
|
+
- Wednesday
|
146
|
+
- Thursday
|
147
|
+
- Friday
|
148
|
+
- Saturday
|
149
|
+
abbr_day_names:
|
150
|
+
- Sun
|
151
|
+
- Mon
|
152
|
+
- Tue
|
153
|
+
- Wed
|
154
|
+
- Thur
|
155
|
+
- Fri
|
156
|
+
- Sat
|
157
|
+
month_names:
|
158
|
+
- ~
|
159
|
+
- January
|
160
|
+
- February
|
161
|
+
- March
|
162
|
+
- April
|
163
|
+
- May
|
164
|
+
- June
|
165
|
+
- July
|
166
|
+
- August
|
167
|
+
- September
|
168
|
+
- October
|
169
|
+
- November
|
170
|
+
- December
|
171
|
+
abbr_month_names:
|
172
|
+
- ~
|
173
|
+
- Jan
|
174
|
+
- Feb
|
175
|
+
- Mar
|
176
|
+
- Apr
|
177
|
+
- May
|
178
|
+
- Jun
|
179
|
+
- Jul
|
180
|
+
- Aug
|
181
|
+
- Sep
|
182
|
+
- Oct
|
183
|
+
- Nov
|
184
|
+
- Dec
|
185
|
+
order:
|
186
|
+
- :month
|
187
|
+
- :day
|
188
|
+
- :year
|
189
|
+
time:
|
190
|
+
input:
|
191
|
+
formats:
|
192
|
+
- :default
|
193
|
+
- :long
|
194
|
+
- :short
|
195
|
+
- :time
|
196
|
+
formats:
|
197
|
+
default: "%m/%d/%Y %I:%M%p"
|
198
|
+
short: "%B %e %I:%M %p"
|
199
|
+
long: "%A, %B %e, %Y %I:%M%p"
|
200
|
+
time: "%l:%M%p"
|
201
|
+
am: "am"
|
202
|
+
pm: "pm"
|
203
|
+
|
204
|
+
### Compatibility
|
205
|
+
|
206
|
+
* Tested with Rails 2.3.5 in Ruby 1.8.7, Ruby 1.9.1 and Ruby 1.9.2 (head)
|
207
|
+
* Tested with Rails 3 Beta 3 in Ruby 1.9.2 (head)
|
208
|
+
|
209
|
+
### Contributors
|
210
|
+
|
211
|
+
People who have contributed to delocalize (in no particular order):
|
212
|
+
|
213
|
+
* [Fernando Luizao](http://github.com/fernandoluizao)
|
214
|
+
* [Stephan Zalewski](http://github.com/stepahn)
|
215
|
+
* [Lailson Bandeira](http://github.com/lailsonbm)
|
216
|
+
* [Carlos Antonio da Silva](http://github.com/carlosantoniodasilva)
|
217
|
+
* [Michele Franzin](http://github.com/michelefranzin)
|
218
|
+
* [Raphaela Wrede](https://github.com/rwrede)
|
219
|
+
* [Jan De Poorter](https://github.com/DefV)
|
220
|
+
* [Blake Lucchesi](https://github.com/BlakeLucchesi)
|
221
|
+
* [Ralph von der Heyden](https://github.com/ralph)
|
222
|
+
|
223
|
+
### TODO
|
224
|
+
|
225
|
+
* Improve test coverage
|
226
|
+
* Separate Ruby/Rails stuff to make it usable outside Rails
|
227
|
+
* Decide on other ActionView hacks (e.g. `text_field_tag`)
|
228
|
+
* Implement AM/PM support
|
229
|
+
* Cleanup, cleanup, cleanup ...
|
230
|
+
|
231
|
+
Copyright (c) 2009-2011 Clemens Kofler <clemens@railway.at>
|
232
|
+
<http://www.railway.at/>
|
233
|
+
Released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rdoc/task'
|
4
|
+
|
5
|
+
desc 'Test the delocalize gem.'
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
7
|
+
t.libs << 'lib'
|
8
|
+
t.libs << 'test'
|
9
|
+
t.pattern = 'test/delocalize_test.rb'
|
10
|
+
t.verbose = true
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Default: run unit tests.'
|
14
|
+
task :default => :test
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.3.1
|
data/lib/delocalize.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'delocalize/ruby_ext'
|
2
|
+
require 'delocalize/i18n_ext'
|
3
|
+
require 'delocalize/localized_date_time_parser'
|
4
|
+
|
5
|
+
if defined?(Rails::Railtie)
|
6
|
+
require 'delocalize/railtie'
|
7
|
+
elsif defined?(Rails::Initializer)
|
8
|
+
raise "This version of delocalize is only compatible with Rails 3.0 or newer"
|
9
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
2
|
+
|
3
|
+
module I18n
|
4
|
+
mattr_accessor :enable_delocalization
|
5
|
+
I18n.enable_delocalization = true
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def delocalization_enabled?
|
9
|
+
!!I18n.enable_delocalization
|
10
|
+
end
|
11
|
+
|
12
|
+
def delocalization_disabled?
|
13
|
+
!delocalization_enabled?
|
14
|
+
end
|
15
|
+
|
16
|
+
def with_delocalization_disabled(&block)
|
17
|
+
old_value = I18n.enable_delocalization
|
18
|
+
I18n.enable_delocalization = false
|
19
|
+
yield
|
20
|
+
I18n.enable_delocalization = old_value
|
21
|
+
end
|
22
|
+
|
23
|
+
def with_delocalization_enabled(&block)
|
24
|
+
old_value = I18n.enable_delocalization
|
25
|
+
I18n.enable_delocalization = true
|
26
|
+
yield
|
27
|
+
I18n.enable_delocalization = old_value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# TODO:
|
2
|
+
# * AM/PM calculation
|
3
|
+
# * proper documentation (comments)
|
4
|
+
module Delocalize
|
5
|
+
class LocalizedDateTimeParser
|
6
|
+
# extend/change this according to your needs by merging your custom regexps
|
7
|
+
REGEXPS = {
|
8
|
+
'%B' => "(#{Date::MONTHNAMES.compact.join('|')})", # long month name
|
9
|
+
'%b' => "(#{Date::ABBR_MONTHNAMES.compact.join('|')})", # short month name
|
10
|
+
'%m' => "(\\d{2})", # numeric month
|
11
|
+
'%A' => "(#{Date::DAYNAMES.join('|')})", # full day name
|
12
|
+
'%a' => "(#{Date::ABBR_DAYNAMES.join('|')})", # short day name
|
13
|
+
'%Y' => "(\\d{4})", # long year
|
14
|
+
'%y' => "(\\d{2})", # short year
|
15
|
+
'%e' => "(\\s\\d|\\d{2})", # short day
|
16
|
+
'%d' => "(\\d{2})", # full day
|
17
|
+
'%H' => "(\\d{2})", # hour (24)
|
18
|
+
'%M' => "(\\d{2})", # minute
|
19
|
+
'%S' => "(\\d{2})" # second
|
20
|
+
}
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def parse(datetime, type)
|
24
|
+
return unless datetime
|
25
|
+
return datetime if datetime.respond_to?(:strftime) # already a Date/Time object -> no need to parse it
|
26
|
+
|
27
|
+
translate_month_and_day_names(datetime)
|
28
|
+
input_formats(type).each do |original_format|
|
29
|
+
next unless datetime =~ /^#{apply_regex(original_format)}$/
|
30
|
+
|
31
|
+
datetime = DateTime.strptime(datetime, original_format)
|
32
|
+
return Date == type ?
|
33
|
+
datetime.to_date :
|
34
|
+
Time.zone.local(datetime.year, datetime.mon, datetime.mday, datetime.hour, datetime.min, datetime.sec)
|
35
|
+
end
|
36
|
+
default_parse(datetime, type)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def default_parse(datetime, type)
|
41
|
+
return if datetime.blank?
|
42
|
+
begin
|
43
|
+
today = Date.current
|
44
|
+
parsed = Date._parse(datetime)
|
45
|
+
return if parsed.empty? # the datetime value is invalid
|
46
|
+
# set default year, month and day if not found
|
47
|
+
parsed.reverse_merge!(:year => today.year, :mon => today.mon, :mday => today.mday)
|
48
|
+
datetime = Time.zone.local(*parsed.values_at(:year, :mon, :mday, :hour, :min, :sec))
|
49
|
+
Date == type ? datetime.to_date : datetime
|
50
|
+
rescue
|
51
|
+
datetime
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def translate_month_and_day_names(datetime)
|
56
|
+
translated = [:month_names, :abbr_month_names, :day_names, :abbr_day_names].map do |key|
|
57
|
+
I18n.t(key, :scope => :date)
|
58
|
+
end.flatten.compact
|
59
|
+
|
60
|
+
original = (Date::MONTHNAMES + Date::ABBR_MONTHNAMES + Date::DAYNAMES + Date::ABBR_DAYNAMES).compact
|
61
|
+
translated.each_with_index { |name, i| datetime.gsub!(name, original[i]) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def input_formats(type)
|
65
|
+
# Date uses date formats, all others use time formats
|
66
|
+
type = type == Date ? :date : :time
|
67
|
+
I18n.t(:"#{type}.formats").slice(*I18n.t(:"#{type}.input.formats")).values
|
68
|
+
end
|
69
|
+
|
70
|
+
def apply_regex(format)
|
71
|
+
format.gsub(/(#{REGEXPS.keys.join('|')})/) { |s| REGEXPS[$1] }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# TODO:
|
2
|
+
# * proper documentation (comments)
|
3
|
+
module Delocalize
|
4
|
+
class LocalizedNumericParser
|
5
|
+
class << self
|
6
|
+
# Parse numbers removing unneeded characters and replacing decimal separator
|
7
|
+
# through I18n. This will return a valid Ruby Numeric value (as String).
|
8
|
+
def parse(value)
|
9
|
+
if value.is_a?(String)
|
10
|
+
separator = I18n.t(:'number.format.separator')
|
11
|
+
delimiter = I18n.t(:'number.format.delimiter')
|
12
|
+
value = value.gsub(delimiter, '').gsub(separator, '.')
|
13
|
+
end
|
14
|
+
value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,57 @@
|
|
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? && object.respond_to?(:column_for_attribute) && column = object.column_for_attribute(method_name)
|
14
|
+
# a little verbose
|
15
|
+
if column.number? || column.date? || column.time?
|
16
|
+
value = object.send(method_name)
|
17
|
+
|
18
|
+
if column.number?
|
19
|
+
number_options = I18n.t(:'number.format')
|
20
|
+
separator = options.delete(:separator) || number_options[:separator]
|
21
|
+
delimiter = options.delete(:delimiter) || number_options[:delimiter]
|
22
|
+
precision = options.delete(:precision) || number_options[:precision]
|
23
|
+
opts = { :separator => separator, :delimiter => delimiter, :precision => precision }
|
24
|
+
# integers don't need a precision
|
25
|
+
opts.merge!(:precision => 0) if column.type == :integer
|
26
|
+
|
27
|
+
hidden_for_integer = field_type == 'hidden' && column.type == :integer
|
28
|
+
|
29
|
+
# the number will be formatted only if it has no numericality errors
|
30
|
+
if object.respond_to?(:errors) && !Array(object.errors[method_name]).try(:include?, 'is not a number')
|
31
|
+
# we don't format integer hidden fields because this breaks nested_attributes
|
32
|
+
options[:value] = number_with_precision(value, opts) unless hidden_for_integer
|
33
|
+
end
|
34
|
+
elsif column.date? || column.time?
|
35
|
+
options[:value] = value ? I18n.l(value, :format => options.delete(:format)) : nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
original_to_input_field_tag(field_type, options)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# TODO: does it make sense to also override FormTagHelper methods?
|
45
|
+
# ActionView::Helpers::FormTagHelper.class_eval do
|
46
|
+
# include ActionView::Helpers::NumberHelper
|
47
|
+
#
|
48
|
+
# alias original_text_field_tag text_field_tag
|
49
|
+
# def text_field_tag(name, value = nil, options = {})
|
50
|
+
# value = options.delete(:value) if options.key?(:value)
|
51
|
+
# if value.is_a?(Numeric)
|
52
|
+
# value = number_with_delimiter(value)
|
53
|
+
# end
|
54
|
+
# original_text_field_tag(name, value, options)
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
|
@@ -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
|
+
def field_changed?(attr, old, value)
|
43
|
+
if column = column_for_attribute(attr)
|
44
|
+
if column.number? && column.null && (old.nil? || old == 0) && value.blank?
|
45
|
+
# For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
|
46
|
+
# Hence we don't record it as a change if the value changes from nil to ''.
|
47
|
+
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
|
48
|
+
# be typecast back to 0 (''.to_i => 0)
|
49
|
+
value = nil
|
50
|
+
elsif column.number?
|
51
|
+
value = column.type_cast(convert_number_column_value_with_localization(value))
|
52
|
+
else
|
53
|
+
value = column.type_cast(value)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
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,15 @@
|
|
1
|
+
module Delocalize
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
initializer "delocalize" do |app|
|
4
|
+
ActiveSupport.on_load :active_record do
|
5
|
+
require 'delocalize/rails_ext/active_record'
|
6
|
+
end
|
7
|
+
|
8
|
+
ActiveSupport.on_load :action_view do
|
9
|
+
require 'delocalize/rails_ext/action_view'
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'delocalize/rails_ext/time_zone'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/test/database.yml
ADDED
@@ -0,0 +1,315 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'active_record/test_case'
|
5
|
+
require 'action_view/test_case'
|
6
|
+
|
7
|
+
class DelocalizeActiveRecordTest < ActiveRecord::TestCase
|
8
|
+
def setup
|
9
|
+
Time.zone = 'Berlin' # make sure everything works as expected with TimeWithZone
|
10
|
+
Timecop.freeze(Time.zone.local(2009, 3, 1, 12, 0))
|
11
|
+
@product = Product.new
|
12
|
+
end
|
13
|
+
|
14
|
+
test "delocalizes localized number" do
|
15
|
+
@product.price = '1.299,99'
|
16
|
+
assert_equal 1299.99, @product.price
|
17
|
+
|
18
|
+
@product.price = '-1.299,99'
|
19
|
+
assert_equal -1299.99, @product.price
|
20
|
+
end
|
21
|
+
|
22
|
+
test "delocalizes localized date with year" do
|
23
|
+
date = Date.civil(2009, 10, 19)
|
24
|
+
|
25
|
+
@product.released_on = '19. Oktober 2009'
|
26
|
+
assert_equal date, @product.released_on
|
27
|
+
|
28
|
+
@product.released_on = '19.10.2009'
|
29
|
+
assert_equal date, @product.released_on
|
30
|
+
end
|
31
|
+
|
32
|
+
test "delocalizes localized date with year even if locale changes" do
|
33
|
+
date = Date.civil(2009, 10, 19)
|
34
|
+
|
35
|
+
@product.released_on = '19. Oktober 2009'
|
36
|
+
assert_equal date, @product.released_on
|
37
|
+
|
38
|
+
I18n.with_locale :tt do
|
39
|
+
@product.released_on = '10|11|2009'
|
40
|
+
date = Date.civil(2009, 11, 10)
|
41
|
+
assert_equal date, @product.released_on
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
test "delocalizes localized date without year" do
|
46
|
+
date = Date.civil(Date.today.year, 10, 19)
|
47
|
+
|
48
|
+
@product.released_on = '19. Okt'
|
49
|
+
assert_equal date, @product.released_on
|
50
|
+
end
|
51
|
+
|
52
|
+
test "delocalizes localized datetime with year" do
|
53
|
+
time = Time.zone.local(2009, 3, 1, 12, 0, 0)
|
54
|
+
|
55
|
+
@product.published_at = 'Sonntag, 1. März 2009, 12:00 Uhr'
|
56
|
+
assert_equal time, @product.published_at
|
57
|
+
|
58
|
+
@product.published_at = '1. März 2009, 12:00 Uhr'
|
59
|
+
assert_equal time, @product.published_at
|
60
|
+
end
|
61
|
+
|
62
|
+
test "delocalizes with fallback locale" do
|
63
|
+
I18n::Backend::Simple.include(I18n::Backend::Fallbacks)
|
64
|
+
I18n.fallbacks[:xx] = [:xx, :tt]
|
65
|
+
|
66
|
+
I18n.with_locale :xx do
|
67
|
+
@product.released_on = '10|11|2009'
|
68
|
+
date = Date.civil(2009, 11, 10)
|
69
|
+
assert_equal date, @product.released_on
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
test "delocalizes localized datetime without year" do
|
74
|
+
time = Time.zone.local(Date.today.year, 3, 1, 12, 0, 0)
|
75
|
+
|
76
|
+
@product.published_at = '1. März, 12:00 Uhr'
|
77
|
+
assert_equal time, @product.published_at
|
78
|
+
end
|
79
|
+
|
80
|
+
# TODO can I somehow do this smarter? or should I use another zone w/o DST?
|
81
|
+
if Time.current.dst?
|
82
|
+
test "delocalizes localized time (DST)" do
|
83
|
+
now = Date.today
|
84
|
+
time = Time.zone.local(now.year, now.month, now.day, 9, 0, 0)
|
85
|
+
@product.cant_think_of_a_sensible_time_field = '09:00 Uhr'
|
86
|
+
assert_equal time, @product.cant_think_of_a_sensible_time_field
|
87
|
+
end
|
88
|
+
else
|
89
|
+
test "delocalizes localized time (non-DST)" do
|
90
|
+
now = Date.today
|
91
|
+
time = Time.zone.local(now.year, now.month, now.day, 8, 0, 0)
|
92
|
+
@product.cant_think_of_a_sensible_time_field = '08:00 Uhr'
|
93
|
+
assert_equal time, @product.cant_think_of_a_sensible_time_field
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
test "invalid dates should be delocalized to nil" do
|
98
|
+
date = '32. Oktober 2009'
|
99
|
+
@product.released_on = date
|
100
|
+
assert_equal nil, @product.released_on
|
101
|
+
assert_equal date, @product.released_on_before_type_cast
|
102
|
+
end
|
103
|
+
|
104
|
+
test "uses default parse if format isn't found (non-DST)" do
|
105
|
+
date = Date.civil(2009, 10, 19)
|
106
|
+
|
107
|
+
@product.released_on = '2009/10/19'
|
108
|
+
assert_equal date, @product.released_on
|
109
|
+
|
110
|
+
time = Time.zone.local(2009, 3, 1, 12, 0, 0)
|
111
|
+
@product.published_at = '2009/03/01 12:00'
|
112
|
+
assert_equal time, @product.published_at
|
113
|
+
|
114
|
+
now = Time.current
|
115
|
+
time = Time.zone.local(now.year, now.month, now.day, 8, 0, 0)
|
116
|
+
@product.cant_think_of_a_sensible_time_field = '08:00'
|
117
|
+
assert_equal time, @product.cant_think_of_a_sensible_time_field
|
118
|
+
end
|
119
|
+
|
120
|
+
test "should return nil if the input is empty or invalid" do
|
121
|
+
@product.released_on = ""
|
122
|
+
assert_nil @product.released_on
|
123
|
+
|
124
|
+
@product.released_on = "aa"
|
125
|
+
assert_nil @product.released_on
|
126
|
+
end
|
127
|
+
|
128
|
+
test "doesn't raise when attribute is nil" do
|
129
|
+
assert_nothing_raised {
|
130
|
+
@product.price = nil
|
131
|
+
@product.released_on = nil
|
132
|
+
@product.published_at = nil
|
133
|
+
@product.cant_think_of_a_sensible_time_field = nil
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
test "uses default formats if enable_delocalization is false" do
|
138
|
+
I18n.enable_delocalization = false
|
139
|
+
|
140
|
+
@product.price = '1299.99'
|
141
|
+
assert_equal 1299.99, @product.price
|
142
|
+
|
143
|
+
@product.price = '-1299.99'
|
144
|
+
assert_equal -1299.99, @product.price
|
145
|
+
end
|
146
|
+
|
147
|
+
test "uses default formats if called with with_delocalization_disabled" do
|
148
|
+
I18n.with_delocalization_disabled do
|
149
|
+
@product.price = '1299.99'
|
150
|
+
assert_equal 1299.99, @product.price
|
151
|
+
|
152
|
+
@product.price = '-1299.99'
|
153
|
+
assert_equal -1299.99, @product.price
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
test "uses localized parsing if called with with_delocalization_enabled" do
|
158
|
+
I18n.with_delocalization_enabled do
|
159
|
+
@product.price = '1.299,99'
|
160
|
+
assert_equal 1299.99, @product.price
|
161
|
+
|
162
|
+
@product.price = '-1.299,99'
|
163
|
+
assert_equal -1299.99, @product.price
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
test "dirty attributes must detect changes in decimal columns" do
|
168
|
+
@product.price = 10
|
169
|
+
@product.save
|
170
|
+
@product.price = "10,34"
|
171
|
+
assert @product.price_changed?
|
172
|
+
end
|
173
|
+
|
174
|
+
test "dirty attributes must detect changes in float columns" do
|
175
|
+
@product.weight = 10
|
176
|
+
@product.save
|
177
|
+
@product.weight = "10,34"
|
178
|
+
assert @product.weight_changed?
|
179
|
+
end
|
180
|
+
|
181
|
+
test "attributes that didn't change shouldn't be marked dirty" do
|
182
|
+
@product.name = "Good cookies, Really good"
|
183
|
+
@product.save
|
184
|
+
@product.name = "Good cookies, Really good"
|
185
|
+
assert !@product.name_changed?
|
186
|
+
end
|
187
|
+
|
188
|
+
test "should remember the value before type cast" do
|
189
|
+
@product.price = "asd"
|
190
|
+
assert_equal @product.price, 0
|
191
|
+
assert_equal @product.price_before_type_cast, "asd"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
class DelocalizeActionViewTest < ActionView::TestCase
|
196
|
+
include ActionView::Helpers::FormHelper
|
197
|
+
|
198
|
+
def setup
|
199
|
+
Time.zone = 'Berlin' # make sure everything works as expected with TimeWithZone
|
200
|
+
@product = Product.new
|
201
|
+
end
|
202
|
+
|
203
|
+
test "shows text field using formatted number" do
|
204
|
+
@product.price = 1299.9
|
205
|
+
assert_dom_equal '<input id="product_price" name="product[price]" size="30" type="text" value="1.299,90" />',
|
206
|
+
text_field(:product, :price)
|
207
|
+
end
|
208
|
+
|
209
|
+
test "shows text field using formatted number with options" do
|
210
|
+
@product.price = 1299.995
|
211
|
+
assert_dom_equal '<input id="product_price" name="product[price]" size="30" type="text" value="1,299.995" />',
|
212
|
+
text_field(:product, :price, :precision => 3, :delimiter => ',', :separator => '.')
|
213
|
+
end
|
214
|
+
|
215
|
+
test "shows text field using formatted number without precision if column is an integer" do
|
216
|
+
@product.times_sold = 20
|
217
|
+
assert_dom_equal '<input id="product_times_sold" name="product[times_sold]" size="30" type="text" value="20" />',
|
218
|
+
text_field(:product, :times_sold)
|
219
|
+
|
220
|
+
@product.times_sold = 2000
|
221
|
+
assert_dom_equal '<input id="product_times_sold" name="product[times_sold]" size="30" type="text" value="2.000" />',
|
222
|
+
text_field(:product, :times_sold)
|
223
|
+
end
|
224
|
+
|
225
|
+
test "shows text field using formatted date" do
|
226
|
+
@product.released_on = Date.civil(2009, 10, 19)
|
227
|
+
assert_dom_equal '<input id="product_released_on" name="product[released_on]" size="30" type="text" value="19.10.2009" />',
|
228
|
+
text_field(:product, :released_on)
|
229
|
+
end
|
230
|
+
|
231
|
+
test "shows text field using formatted date and time" do
|
232
|
+
@product.published_at = Time.zone.local(2009, 3, 1, 12, 0, 0)
|
233
|
+
# careful - leading whitespace with %e
|
234
|
+
assert_dom_equal '<input id="product_published_at" name="product[published_at]" size="30" type="text" value="Sonntag, 1. März 2009, 12:00 Uhr" />',
|
235
|
+
text_field(:product, :published_at)
|
236
|
+
end
|
237
|
+
|
238
|
+
test "shows text field using formatted date with format" do
|
239
|
+
@product.released_on = Date.civil(2009, 10, 19)
|
240
|
+
assert_dom_equal '<input id="product_released_on" name="product[released_on]" size="30" type="text" value="19. Oktober 2009" />',
|
241
|
+
text_field(:product, :released_on, :format => :long)
|
242
|
+
end
|
243
|
+
|
244
|
+
test "shows text field using formatted date and time with format" do
|
245
|
+
@product.published_at = Time.zone.local(2009, 3, 1, 12, 0, 0)
|
246
|
+
# careful - leading whitespace with %e
|
247
|
+
assert_dom_equal '<input id="product_published_at" name="product[published_at]" size="30" type="text" value=" 1. März, 12:00 Uhr" />',
|
248
|
+
text_field(:product, :published_at, :format => :short)
|
249
|
+
end
|
250
|
+
|
251
|
+
test "shows text field using formatted time with format" do
|
252
|
+
@product.cant_think_of_a_sensible_time_field = Time.zone.local(2009, 3, 1, 9, 0, 0)
|
253
|
+
assert_dom_equal '<input id="product_cant_think_of_a_sensible_time_field" name="product[cant_think_of_a_sensible_time_field]" size="30" type="text" value="09:00 Uhr" />',
|
254
|
+
text_field(:product, :cant_think_of_a_sensible_time_field, :format => :time)
|
255
|
+
end
|
256
|
+
|
257
|
+
test "integer hidden fields shouldn't be formatted" do
|
258
|
+
@product.times_sold = 1000
|
259
|
+
assert_dom_equal '<input id="product_times_sold" name="product[times_sold]" type="hidden" value="1000" />',
|
260
|
+
hidden_field(:product, :times_sold)
|
261
|
+
end
|
262
|
+
|
263
|
+
test "doesn't raise an exception when object is nil" do
|
264
|
+
assert_nothing_raised {
|
265
|
+
text_field(:not_here, :a_text_field)
|
266
|
+
}
|
267
|
+
end
|
268
|
+
|
269
|
+
test "doesn't raise for nil Date/Time" do
|
270
|
+
@product.published_at, @product.released_on, @product.cant_think_of_a_sensible_time_field = nil
|
271
|
+
assert_nothing_raised {
|
272
|
+
text_field(:product, :published_at)
|
273
|
+
text_field(:product, :released_on)
|
274
|
+
text_field(:product, :cant_think_of_a_sensible_time_field)
|
275
|
+
}
|
276
|
+
end
|
277
|
+
|
278
|
+
test "doesn't override given :value" do
|
279
|
+
@product.price = 1299.9
|
280
|
+
assert_dom_equal '<input id="product_price" name="product[price]" size="30" type="text" value="1.499,90" />',
|
281
|
+
text_field(:product, :price, :value => "1.499,90")
|
282
|
+
end
|
283
|
+
|
284
|
+
test "doesn't convert the value if field has numericality errors" do
|
285
|
+
@product = ProductWithValidation.new(:price => 'this is not a number')
|
286
|
+
@product.valid?
|
287
|
+
assert_dom_equal %(<div class="field_with_errors"><input id="product_price" name="product[price]" size="30" type="text" value="this is not a number" /></div>),
|
288
|
+
text_field(:product, :price)
|
289
|
+
end
|
290
|
+
|
291
|
+
test "should convert the value if field have non-numericality errors, but have other errors, e.g. business rules" do
|
292
|
+
@product = ProductWithBusinessValidation.new(:price => '1.337,66')
|
293
|
+
@product.valid?
|
294
|
+
assert_dom_equal %(<div class="field_with_errors"><input id="product_price" name="product[price]" size="30" type="text" value="1.337,66" /></div>),
|
295
|
+
text_field(:product, :price)
|
296
|
+
end
|
297
|
+
|
298
|
+
test "doesn't raise an exception when object isn't an ActiveReccord" do
|
299
|
+
@product = NonArProduct.new
|
300
|
+
assert_nothing_raised {
|
301
|
+
text_field(:product, :name)
|
302
|
+
text_field(:product, :times_sold)
|
303
|
+
text_field(:product, :published_at)
|
304
|
+
text_field(:product, :released_on)
|
305
|
+
text_field(:product, :cant_think_of_a_sensible_time_field)
|
306
|
+
text_field(:product, :price, :value => "1.499,90")
|
307
|
+
}
|
308
|
+
end
|
309
|
+
|
310
|
+
test "formats field with default value correctly" do
|
311
|
+
assert_dom_equal '<input id="product_some_value_with_default" name="product[some_value_with_default]" size="30" type="text" value="0,00" />',
|
312
|
+
text_field(:product, :some_value_with_default)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
|
6
|
+
Bundler.require(:default, :development)
|
7
|
+
|
8
|
+
require 'test/unit'
|
9
|
+
|
10
|
+
require 'delocalize/rails_ext/action_view'
|
11
|
+
require 'delocalize/rails_ext/active_record'
|
12
|
+
require 'delocalize/rails_ext/time_zone'
|
13
|
+
|
14
|
+
de = {
|
15
|
+
:date => {
|
16
|
+
:input => {
|
17
|
+
:formats => [:long, :short, :default]
|
18
|
+
},
|
19
|
+
:formats => {
|
20
|
+
:default => "%d.%m.%Y",
|
21
|
+
:short => "%e. %b",
|
22
|
+
:long => "%e. %B %Y",
|
23
|
+
:only_day => "%e"
|
24
|
+
},
|
25
|
+
:day_names => %w(Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag),
|
26
|
+
:abbr_day_names => %w(So Mo Di Mi Do Fr Sa),
|
27
|
+
:month_names => [nil] + %w(Januar Februar März April Mai Juni Juli August September Oktober November Dezember),
|
28
|
+
:abbr_month_names => [nil] + %w(Jan Feb Mär Apr Mai Jun Jul Aug Sep Okt Nov Dez)
|
29
|
+
},
|
30
|
+
:time => {
|
31
|
+
:input => {
|
32
|
+
:formats => [:long, :medium, :short, :default, :time]
|
33
|
+
},
|
34
|
+
:formats => {
|
35
|
+
:default => "%A, %e. %B %Y, %H:%M Uhr",
|
36
|
+
:short => "%e. %B, %H:%M Uhr",
|
37
|
+
:medium => "%e. %B %Y, %H:%M Uhr",
|
38
|
+
:long => "%A, %e. %B %Y, %H:%M Uhr",
|
39
|
+
:time => "%H:%M Uhr"
|
40
|
+
},
|
41
|
+
:am => 'vormittags',
|
42
|
+
:pm => 'nachmittags'
|
43
|
+
},
|
44
|
+
:number => {
|
45
|
+
:format => {
|
46
|
+
:precision => 2,
|
47
|
+
:separator => ',',
|
48
|
+
:delimiter => '.'
|
49
|
+
}
|
50
|
+
},
|
51
|
+
:activerecord => {
|
52
|
+
:errors => {
|
53
|
+
:messages => {
|
54
|
+
:not_a_number => 'is not a number'
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
# deeply clone the hash for a fantasy language called tt
|
61
|
+
tt = Marshal.load(Marshal.dump(de))
|
62
|
+
tt[:date][:formats][:default] = '%d|%m|%Y'
|
63
|
+
|
64
|
+
I18n.backend.store_translations :de, de
|
65
|
+
I18n.backend.store_translations :tt, tt
|
66
|
+
|
67
|
+
I18n.locale = :de
|
68
|
+
|
69
|
+
class NonArProduct
|
70
|
+
attr_accessor :name, :price, :times_sold,
|
71
|
+
:cant_think_of_a_sensible_time_field,
|
72
|
+
:released_on, :published_at
|
73
|
+
end
|
74
|
+
|
75
|
+
class Product < ActiveRecord::Base
|
76
|
+
end
|
77
|
+
|
78
|
+
class ProductWithValidation < Product
|
79
|
+
validates_numericality_of :price
|
80
|
+
validates_presence_of :price
|
81
|
+
end
|
82
|
+
|
83
|
+
class ProductWithBusinessValidation < Product
|
84
|
+
validate do |record|
|
85
|
+
if record.price > 10
|
86
|
+
record.errors.add(:price, :invalid)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
config = YAML.load_file(File.dirname(__FILE__) + '/database.yml')
|
92
|
+
ActiveRecord::Base.establish_connection(config['test'])
|
93
|
+
|
94
|
+
ActiveRecord::Base.connection.create_table :products do |t|
|
95
|
+
t.string :name
|
96
|
+
t.date :released_on
|
97
|
+
t.datetime :published_at
|
98
|
+
t.time :cant_think_of_a_sensible_time_field
|
99
|
+
t.decimal :price
|
100
|
+
t.float :weight
|
101
|
+
t.integer :times_sold
|
102
|
+
t.decimal :some_value_with_default, :default => 0, :precision => 20, :scale => 2
|
103
|
+
end
|
104
|
+
|
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nulogy-delocalize
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Clemens Kofler
|
9
|
+
- Nulogy
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-05-24 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rails
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '3.0'
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: sqlite3
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ~>
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: 1.3.4
|
39
|
+
type: :development
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.3.4
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: timecop
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.3.5
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 0.3.5
|
63
|
+
description: Delocalize is a tool for parsing localized dates/times and numbers.
|
64
|
+
email: clemens@railway.at
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files:
|
68
|
+
- README.markdown
|
69
|
+
files:
|
70
|
+
- MIT-LICENSE
|
71
|
+
- Rakefile
|
72
|
+
- VERSION
|
73
|
+
- lib/delocalize.rb
|
74
|
+
- lib/delocalize/i18n_ext.rb
|
75
|
+
- lib/delocalize/localized_date_time_parser.rb
|
76
|
+
- lib/delocalize/localized_numeric_parser.rb
|
77
|
+
- lib/delocalize/rails_ext/action_view.rb
|
78
|
+
- lib/delocalize/rails_ext/active_record.rb
|
79
|
+
- lib/delocalize/rails_ext/time_zone.rb
|
80
|
+
- lib/delocalize/railtie.rb
|
81
|
+
- lib/delocalize/ruby_ext.rb
|
82
|
+
- lib/delocalize/ruby_ext/date.rb
|
83
|
+
- lib/delocalize/ruby_ext/datetime.rb
|
84
|
+
- lib/delocalize/ruby_ext/numeric.rb
|
85
|
+
- lib/delocalize/ruby_ext/time.rb
|
86
|
+
- README.markdown
|
87
|
+
- test/database.yml
|
88
|
+
- test/delocalize_test.rb
|
89
|
+
- test/test_helper.rb
|
90
|
+
homepage: http://github.com/clemens/delocalize
|
91
|
+
licenses: []
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options:
|
94
|
+
- --charset=UTF-8
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ! '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 1.8.24
|
112
|
+
signing_key:
|
113
|
+
specification_version: 3
|
114
|
+
summary: Localized date/time and number parsing
|
115
|
+
test_files:
|
116
|
+
- test/database.yml
|
117
|
+
- test/delocalize_test.rb
|
118
|
+
- test/test_helper.rb
|