locale_dating 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +51 -39
- data/lib/locale_dating.rb +33 -26
- data/lib/locale_dating/version.rb +1 -1
- data/test/dummy/test/unit/person_test.rb +50 -0
- metadata +4 -5
- data/lib/tasks/locale_dating_tasks.rake +0 -4
data/README.md
CHANGED
|
@@ -13,6 +13,7 @@ changed the default date parsing format from mm/dd/yyyy to dd/mm/yyyy. When lett
|
|
|
13
13
|
on a form as text, Rails could no longer correctly parse a US date by default.
|
|
14
14
|
|
|
15
15
|
Common solutions seem to result in:
|
|
16
|
+
|
|
16
17
|
* explicitly converting the date to text and assigning to the form input
|
|
17
18
|
* parsing the form's param in the controller action and assigning to the model
|
|
18
19
|
|
|
@@ -22,57 +23,66 @@ While solving this problem, we get some other nice benefits. More on that after
|
|
|
22
23
|
|
|
23
24
|
Use the defined I18n locale formats from your application.
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
26
|
+
```yaml
|
|
27
|
+
# /config/locales/en.yml
|
|
28
|
+
en:
|
|
29
|
+
date:
|
|
30
|
+
formats:
|
|
31
|
+
default: '%m/%d/%Y'
|
|
32
|
+
short: '%m/%d/%Y'
|
|
33
|
+
long: ! '%Y-%m-%d'
|
|
34
|
+
ymd: ! '%Y-%m-%d'
|
|
35
|
+
datetime:
|
|
36
|
+
formats:
|
|
37
|
+
default: '%m/%d/%Y'
|
|
38
|
+
short: '%m/%d/%Y'
|
|
39
|
+
long: ! '%m/%d/%Y %I:%M%p'
|
|
40
|
+
time:
|
|
41
|
+
am: am
|
|
42
|
+
formats:
|
|
43
|
+
default: ! '%m/%d/%Y %I:%M%p'
|
|
44
|
+
short: ! '%I:%M%p'
|
|
45
|
+
long: ! '%m/%d/%Y %I:%M%p'
|
|
46
|
+
pm: pm
|
|
47
|
+
```
|
|
45
48
|
|
|
46
49
|
Specify which model attributes should be
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
```ruby
|
|
52
|
+
# /app/models/person.rb
|
|
53
|
+
class Person < ActiveRecord::Base
|
|
54
|
+
locale_date :born_on
|
|
55
|
+
locale_datetime :last_seen_at
|
|
56
|
+
locale_time :start_time
|
|
57
|
+
end
|
|
58
|
+
```
|
|
54
59
|
|
|
55
60
|
## Behavior and Benefits
|
|
56
61
|
|
|
57
62
|
LocaleDating doesn't override the model's attribute. So you can still access the native data type directly as needed.
|
|
58
63
|
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
```ruby
|
|
65
|
+
p = Person.new
|
|
66
|
+
p.born_on = Date.new(2010, 10, 4)
|
|
67
|
+
```
|
|
61
68
|
|
|
62
69
|
LocaleDating creates wrapper methods for accessing the underlying value as text through your desired locale format.
|
|
63
70
|
It uses locale's 'default' format if no format is specified.
|
|
64
71
|
|
|
65
72
|
Because the attribute isn't overridden, you can specify multiple supported formats for a single attribute.
|
|
66
73
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
```ruby
|
|
75
|
+
# /app/models/person.rb
|
|
76
|
+
class Person < ActiveRecord::Base
|
|
77
|
+
locale_datetime :last_seen_at
|
|
78
|
+
locale_datetime :last_seen_at, :format => :long
|
|
79
|
+
locale_datetime :last_seen_at, :format => :short, :ending => :shortened
|
|
80
|
+
locale_datetime :last_seen_at, :format => :special, :name => :last_seen_so_special
|
|
81
|
+
end
|
|
82
|
+
```
|
|
74
83
|
|
|
75
84
|
Generated wrapper methods:
|
|
85
|
+
|
|
76
86
|
* when using the default format on :last_seen_at, it will be 'last_seen_at_as_text'
|
|
77
87
|
* when using the format :long on :last_seen_at, it will be 'last_seen_at_as_long'
|
|
78
88
|
* when specifying the ending 'shortened', it will be 'last_seen_at_shortened'
|
|
@@ -80,10 +90,12 @@ Generated wrapper methods:
|
|
|
80
90
|
|
|
81
91
|
In a view, you specify which version you want used by referencing the name like this:
|
|
82
92
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
```erb
|
|
94
|
+
<%= form_for @person do %>
|
|
95
|
+
<%= f.label :born_on_as_text, 'Born On' %>:
|
|
96
|
+
<%= f.text_field :born_on_as_text %><br />
|
|
97
|
+
<% end %>
|
|
98
|
+
```
|
|
87
99
|
|
|
88
100
|
The input's value is the :born_on value converted to text using the locale specified format. The controller receives
|
|
89
101
|
the user's modified text and passes it to the wrapper method. The wrapper method parses the text using the format
|
data/lib/locale_dating.rb
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
module LocaleDating
|
|
2
|
+
# Exception used to prevent wrapper methods from unintentionally overwriting an existing method.
|
|
3
|
+
class MethodOverwriteError < RuntimeError; end
|
|
4
|
+
|
|
2
5
|
# Respond to being included and extend the object with class methods.
|
|
3
6
|
def self.included(base)
|
|
4
7
|
base.extend ClassMethods
|
|
@@ -33,34 +36,29 @@ module LocaleDating
|
|
|
33
36
|
#
|
|
34
37
|
def locale_date(*args)
|
|
35
38
|
options = args.extract_options!
|
|
36
|
-
|
|
39
|
+
locale_dating_naming_checks(args, options)
|
|
37
40
|
|
|
38
41
|
# Loop through all the given attributes that should be wrapped using the same settings.
|
|
39
42
|
args.each do |attrib|
|
|
40
|
-
getter_name = options
|
|
41
|
-
getter_name ||= "#{attrib}_#{options[:ending]}".to_sym
|
|
42
|
-
setter_name = "#{getter_name}=".to_sym
|
|
43
|
+
getter_name, setter_name = locale_dating_wrapper_method_names(attrib, options)
|
|
43
44
|
# Define the code to execute when the method is called
|
|
44
45
|
# Create new methods for get and set calls with blocks for implementation.
|
|
45
46
|
class_eval do
|
|
46
47
|
# == Create the GET methods
|
|
47
|
-
# EX: def
|
|
48
|
+
# EX: def birth_date_as_text()
|
|
48
49
|
define_method getter_name do
|
|
49
50
|
value = self.send(attrib)
|
|
50
51
|
I18n.l(value, :format => options[:format]) if value
|
|
51
52
|
end
|
|
52
53
|
# == Create the SET methods
|
|
53
|
-
# EX: def
|
|
54
|
+
# EX: def birth_date_as_text=()
|
|
54
55
|
define_method setter_name do |value|
|
|
55
|
-
date_value = nil
|
|
56
56
|
date_value = DateTime.strptime(value.to_s, I18n.t("date.formats.#{options[:format]}")) unless value.blank?
|
|
57
57
|
# Keep the date from the given value and preserve the original time part
|
|
58
58
|
self.send("#{attrib}=", date_value)
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
|
-
|
|
63
|
-
#include InstanceMethods
|
|
64
62
|
end
|
|
65
63
|
|
|
66
64
|
# Define how to split out a single date_time column/attribute into two attributes that can set the date and
|
|
@@ -88,26 +86,23 @@ module LocaleDating
|
|
|
88
86
|
#
|
|
89
87
|
def locale_time(*args)
|
|
90
88
|
options = args.extract_options!
|
|
91
|
-
|
|
89
|
+
locale_dating_naming_checks(args, options)
|
|
92
90
|
|
|
93
91
|
# Loop through all the given attributes that should be wrapped using the same settings.
|
|
94
92
|
args.each do |attrib|
|
|
95
|
-
getter_name = options
|
|
96
|
-
getter_name ||= "#{attrib}_#{options[:ending]}".to_sym
|
|
97
|
-
setter_name = "#{getter_name}=".to_sym
|
|
93
|
+
getter_name, setter_name = locale_dating_wrapper_method_names(attrib, options)
|
|
98
94
|
# Define the code to execute when the method is called
|
|
99
95
|
# Create new methods for get and set calls with blocks for implementation.
|
|
100
96
|
class_eval do
|
|
101
97
|
# == Create the GET methods
|
|
102
|
-
# EX: def
|
|
98
|
+
# EX: def start_time_as_text()
|
|
103
99
|
define_method getter_name do
|
|
104
100
|
value = self.send(attrib)
|
|
105
101
|
I18n.l(value.in_time_zone, :format => options[:format]) if value
|
|
106
102
|
end
|
|
107
103
|
# == Create the SET methods
|
|
108
|
-
# EX: def
|
|
104
|
+
# EX: def start_time_as_text=()
|
|
109
105
|
define_method setter_name do |value|
|
|
110
|
-
time_value = nil
|
|
111
106
|
if !value.blank?
|
|
112
107
|
time_value = DateTime.strptime(value.to_s, I18n.t("time.formats.#{options[:format]}"))
|
|
113
108
|
time_value = Time.zone.local(time_value.year, time_value.month, time_value.day,
|
|
@@ -118,8 +113,6 @@ module LocaleDating
|
|
|
118
113
|
end
|
|
119
114
|
end
|
|
120
115
|
end
|
|
121
|
-
|
|
122
|
-
#include InstanceMethods
|
|
123
116
|
end
|
|
124
117
|
|
|
125
118
|
# Define how to split out a single date_time column/attribute into two attributes that can set the date and
|
|
@@ -147,26 +140,23 @@ module LocaleDating
|
|
|
147
140
|
#
|
|
148
141
|
def locale_datetime(*args)
|
|
149
142
|
options = args.extract_options!
|
|
150
|
-
|
|
143
|
+
locale_dating_naming_checks(args, options)
|
|
151
144
|
|
|
152
145
|
# Loop through all the given attributes that should be wrapped using the same settings.
|
|
153
146
|
args.each do |attrib|
|
|
154
|
-
getter_name = options
|
|
155
|
-
getter_name ||= "#{attrib}_#{options[:ending]}".to_sym
|
|
156
|
-
setter_name = "#{getter_name}=".to_sym
|
|
147
|
+
getter_name, setter_name = locale_dating_wrapper_method_names(attrib, options)
|
|
157
148
|
# Define the code to execute when the method is called
|
|
158
149
|
# Create new methods for get and set calls with blocks for implementation.
|
|
159
150
|
class_eval do
|
|
160
151
|
# == Create the GET methods
|
|
161
|
-
# EX: def
|
|
152
|
+
# EX: def completed_at_as_text()
|
|
162
153
|
define_method getter_name do
|
|
163
154
|
value = self.send(attrib)
|
|
164
155
|
I18n.l(value.in_time_zone, :format => options[:format]) if value
|
|
165
156
|
end
|
|
166
157
|
# == Create the SET methods
|
|
167
|
-
# EX: def
|
|
158
|
+
# EX: def completed_at_as_text=()
|
|
168
159
|
define_method setter_name do |value|
|
|
169
|
-
date_value = nil
|
|
170
160
|
if !value.blank?
|
|
171
161
|
date_value = DateTime.strptime(value.to_s, I18n.t("datetime.formats.#{options[:format]}"))
|
|
172
162
|
date_value = Time.zone.local(date_value.year, date_value.month, date_value.day,
|
|
@@ -183,10 +173,27 @@ module LocaleDating
|
|
|
183
173
|
|
|
184
174
|
private
|
|
185
175
|
# Given the options for a locale_dating call, set the defaults for the naming convention to use.
|
|
186
|
-
def
|
|
176
|
+
def locale_dating_naming_checks(args, options)
|
|
187
177
|
options.reverse_merge!(:format => :default)
|
|
188
178
|
options[:ending] ||= "as_#{options[:format]}".to_sym unless options[:format] == :default
|
|
189
179
|
options[:ending] ||= :as_text
|
|
180
|
+
# error if multiple args used with :name option
|
|
181
|
+
raise MethodOverwriteError, "multiple attributes cannot be wrapped with an explicitly named method" if args.length > 1 && options.key?(:name)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Return the getter and setter wrapper method names. Result is an array of [getter_name, setter_name].
|
|
185
|
+
# An exception is raised if the class already has a method with either name.
|
|
186
|
+
def locale_dating_wrapper_method_names(attr_name, options)
|
|
187
|
+
getter_name = options[:name].try(:to_sym)
|
|
188
|
+
getter_name ||= "#{attr_name}_#{options[:ending]}".to_sym
|
|
189
|
+
setter_name = "#{getter_name}=".to_sym
|
|
190
|
+
# Detect if the names overwrite existing methods on the generated instance and prevent it. (presumed to be unintentional)
|
|
191
|
+
class_eval do
|
|
192
|
+
raise MethodOverwriteError, "locale_dating setting would overwrite method '#{getter_name}' for attribute '#{attr_name}'" if self.respond_to?(getter_name)
|
|
193
|
+
raise MethodOverwriteError, "locale_dating setting would overwrite method '#{setter_name}' for attribute '#{attr_name}'" if self.respond_to?(setter_name)
|
|
194
|
+
end
|
|
195
|
+
# Return the values
|
|
196
|
+
[getter_name, setter_name]
|
|
190
197
|
end
|
|
191
198
|
end
|
|
192
199
|
end
|
|
@@ -114,6 +114,16 @@ class PersonTest < ActiveSupport::TestCase
|
|
|
114
114
|
assert_equal nil, p.last_seen_at_as_text
|
|
115
115
|
end
|
|
116
116
|
|
|
117
|
+
test "support assigning a nil value" do
|
|
118
|
+
p = Person.new(:born_on => Date.today, :start_time => Time.zone.now, :last_seen_at => DateTime.now)
|
|
119
|
+
p.born_on_as_text = nil
|
|
120
|
+
p.start_time_as_text = nil
|
|
121
|
+
p.last_seen_at_as_text = nil
|
|
122
|
+
assert_equal nil, p.born_on
|
|
123
|
+
assert_equal nil, p.start_time
|
|
124
|
+
assert_equal nil, p.last_seen_at
|
|
125
|
+
end
|
|
126
|
+
|
|
117
127
|
#
|
|
118
128
|
# Custom Date behavior
|
|
119
129
|
#
|
|
@@ -253,4 +263,44 @@ class PersonTest < ActiveSupport::TestCase
|
|
|
253
263
|
p.start_time.to_time # underlying UTC value
|
|
254
264
|
end
|
|
255
265
|
|
|
266
|
+
#
|
|
267
|
+
# Set multiple attributes on one line
|
|
268
|
+
#
|
|
269
|
+
test "multiple attributes set at once" do
|
|
270
|
+
class MultipleDefaultAttributesSet
|
|
271
|
+
include LocaleDating
|
|
272
|
+
attr_accessor :some_date_1, :some_date_2
|
|
273
|
+
locale_date :some_date_1, :some_date_2
|
|
274
|
+
end
|
|
275
|
+
i = MultipleDefaultAttributesSet.new
|
|
276
|
+
assert i.respond_to?(:some_date_1_as_text)
|
|
277
|
+
assert i.respond_to?(:some_date_1_as_text=)
|
|
278
|
+
assert i.respond_to?(:some_date_2_as_text)
|
|
279
|
+
assert i.respond_to?(:some_date_2_as_text=)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
test "multiple attributes cannot use :name" do
|
|
283
|
+
#TODO: Validate that an exception is raised. Raise exception for overwriting an existing method.
|
|
284
|
+
assert_raise LocaleDating::MethodOverwriteError do
|
|
285
|
+
class MultipleNamedAttributesSet
|
|
286
|
+
include LocaleDating
|
|
287
|
+
attr_accessor :some_datetime_1, :some_datetime_2
|
|
288
|
+
locale_datetime :some_datetime_1, :some_datetime_2, :name => :some_explicit_function_name
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
#
|
|
294
|
+
# Prevent unintentional method overwrites
|
|
295
|
+
#
|
|
296
|
+
test "exception when overwriting existing method" do
|
|
297
|
+
assert_raise LocaleDating::MethodOverwriteError do
|
|
298
|
+
class ExistingMethodFound
|
|
299
|
+
include LocaleDating
|
|
300
|
+
attr_accessor :some_time_1
|
|
301
|
+
def name(); 'howdy' end
|
|
302
|
+
locale_time :some_time_1, :name => :name
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
end
|
|
256
306
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: locale_dating
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
4
|
+
hash: 25
|
|
5
5
|
prerelease: false
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
8
|
- 1
|
|
9
|
-
-
|
|
10
|
-
version: 0.1.
|
|
9
|
+
- 1
|
|
10
|
+
version: 0.1.1
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- Mark Ericksen
|
|
@@ -15,7 +15,7 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date: 2012-03-
|
|
18
|
+
date: 2012-03-18 00:00:00 -06:00
|
|
19
19
|
default_executable:
|
|
20
20
|
dependencies:
|
|
21
21
|
- !ruby/object:Gem::Dependency
|
|
@@ -61,7 +61,6 @@ extra_rdoc_files: []
|
|
|
61
61
|
files:
|
|
62
62
|
- lib/locale_dating.rb
|
|
63
63
|
- lib/locale_dating/version.rb
|
|
64
|
-
- lib/tasks/locale_dating_tasks.rake
|
|
65
64
|
- LICENSE
|
|
66
65
|
- Rakefile
|
|
67
66
|
- README.md
|