locale_dating 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
- # /config/locales/en.yml
26
- en:
27
- date:
28
- formats:
29
- default: '%m/%d/%Y'
30
- short: '%m/%d/%Y'
31
- long: ! '%Y-%m-%d'
32
- ymd: ! '%Y-%m-%d'
33
- datetime:
34
- formats:
35
- default: '%m/%d/%Y'
36
- short: '%m/%d/%Y'
37
- long: ! '%m/%d/%Y %I:%M%p'
38
- time:
39
- am: am
40
- formats:
41
- default: ! '%m/%d/%Y %I:%M%p'
42
- short: ! '%I:%M%p'
43
- long: ! '%m/%d/%Y %I:%M%p'
44
- pm: pm
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
- # /app/models/person.rb
49
- class Person < ActiveRecord::Base
50
- locale_date :born_on
51
- locale_datetime :last_seen_at
52
- locale_time :start_time
53
- end
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
- p = Person.new
60
- p.born_on = Date.new(2010, 10, 4)
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
- # /app/models/person.rb
68
- class Person < ActiveRecord::Base
69
- locale_datetime :last_seen_at
70
- locale_datetime :last_seen_at, :format => :long
71
- locale_datetime :last_seen_at, :format => :short, :ending => :shortened
72
- locale_datetime :last_seen_at, :format => :special, :name => :last_seen_so_special
73
- end
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
- <%= form_for @person do %>
84
- <%= f.label :born_on_as_text, 'Born On' %>:
85
- <%= f.text_field :born_on_as_text %><br />
86
- <% end %>
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
@@ -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
- locale_dating_naming_option_defaults(options)
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[:name].try(:to_sym)
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 date_of_birth_text()
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 date_of_birth_text=()
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
- locale_dating_naming_option_defaults(options)
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[:name].try(:to_sym)
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 date_of_birth_text()
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 date_of_birth_text=()
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
- locale_dating_naming_option_defaults(options)
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[:name].try(:to_sym)
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 date_of_birth_text()
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 date_of_birth_text=()
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 locale_dating_naming_option_defaults(options)
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
@@ -1,3 +1,3 @@
1
1
  module LocaleDating
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  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: 27
4
+ hash: 25
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 0
10
- version: 0.1.0
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-16 00:00:00 -06:00
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
@@ -1,4 +0,0 @@
1
- # desc "Explaining what the task does"
2
- # task :locale_dating do
3
- # # Task goes here
4
- # end