Skip to content

Commit b0bc461

Browse files
committed
refactor date field
1 parent b810510 commit b0bc461

File tree

7 files changed

+326
-2
lines changed

7 files changed

+326
-2
lines changed

test/dummy/app/models/fields/options/date_field.rb

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,137 @@
22

33
module Fields::Options
44
class DateField < FieldOptions
5+
attribute :start_from, :string, default: "unlimited"
6+
enum start_from: {
7+
unlimited: "unlimited",
8+
today: "today",
9+
date: "date",
10+
days_before_finish: "days_before_finish"
11+
}, _prefix: :start_from
12+
13+
attribute :start_date, :date
14+
attribute :start_from_today_days_offset, :integer, default: 0
15+
attribute :days_before_finish, :integer, default: 1
16+
17+
attribute :finish_to, :string, default: "unlimited"
18+
enum finish_to: {
19+
unlimited: "unlimited",
20+
today: "today",
21+
date: "date",
22+
days_since_start: "days_since_start"
23+
}, _prefix: :finish_to
24+
25+
attribute :finish_date, :date
26+
attribute :finish_to_today_days_offset, :integer, default: 0
27+
attribute :days_since_start, :integer, default: 1
28+
29+
validates :start_from, :finish_to,
30+
presence: true
31+
32+
validates :start_date,
33+
presence: true,
34+
if: :start_from_date?
35+
36+
validates :finish_date,
37+
presence: true,
38+
if: :finish_to_date?
39+
40+
validates :days_before_finish,
41+
numericality: {
42+
only_integer: true,
43+
greater_than: 0
44+
},
45+
allow_blank: false,
46+
if: :start_from_days_before_finish?
47+
48+
validates :days_since_start,
49+
numericality: {
50+
only_integer: true,
51+
greater_than: 0
52+
},
53+
allow_blank: false,
54+
if: :finish_to_days_since_start?
55+
56+
validates :start_from_today_days_offset, :finish_to_today_days_offset,
57+
presence: true,
58+
numericality: {
59+
only_integer: true
60+
}
61+
62+
validates :start_date,
63+
timeliness: {
64+
before: ->(r) { Time.zone.today + r.finish_to_today_days_offset.days },
65+
type: :date
66+
},
67+
allow_blank: false,
68+
if: [:start_from_date?, :finish_to_today?]
69+
70+
validates :finish_date,
71+
timeliness: {
72+
after: -> { Time.zone.today + r.start_from_today_days_offset.days },
73+
type: :date
74+
},
75+
allow_blank: false,
76+
if: [:start_from_today?, :finish_to_date?]
77+
78+
validates :finish_date,
79+
timeliness: {
80+
after: :start_date,
81+
type: :date
82+
},
83+
allow_blank: false,
84+
if: [:start_from_date?, :finish_to_date?]
85+
86+
validates :finish_to,
87+
exclusion: {in: %w[today]},
88+
if: [:start_from_today?]
89+
90+
validates :finish_to,
91+
exclusion: {in: %w[days_since_start]},
92+
if: [:start_from_days_before_finish?]
93+
94+
def interpret_to(model, field_name, accessibility, _options = {})
95+
return unless accessibility == :read_and_write
96+
97+
timeliness = {type: :date}
98+
99+
if start_from_today?
100+
start_date_days_offset = self.start_from_today_days_offset.days
101+
timeliness[:on_or_after] = -> { Time.zone.today + start_date_days_offset }
102+
elsif start_from_date?
103+
timeliness[:on_or_after] = start_date
104+
elsif start_from_days_before_finish?
105+
days_before_finish = self.days_before_finish.days
106+
if finish_to_today?
107+
finish_date_days_offset = self.finish_to_today_days_offset.days
108+
timeliness[:on_or_after] = -> {
109+
Time.zone.today + finish_date_days_offset - days_before_finish
110+
}
111+
elsif finish_to_date?
112+
timeliness[:on_or_after] = finish_date - days_before_finish
113+
end
114+
end
115+
116+
if finish_to_today?
117+
finish_date_days_offset = self.finish_to_today_date_offset.days
118+
timeliness[:on_or_before] = -> { Time.zone.today + finish_date_days_offset }
119+
elsif finish_to_date?
120+
timeliness[:on_or_before] = finish_date
121+
elsif finish_to_days_since_start?
122+
days_since_start = self.days_since_start.days
123+
if start_from_today?
124+
start_date_days_offset = self.start_from_today_days_offset.days
125+
timeliness[:on_or_before] = -> {
126+
Time.zone.today + start_date_days_offset + days_since_start
127+
}
128+
elsif start_from_date?
129+
timeliness[:on_or_before] = start_date + days_since_start
130+
end
131+
end
132+
133+
model.validates field_name,
134+
timeliness: timeliness,
135+
allow_blank: true
136+
end
5137
end
6138
end

test/dummy/app/presenters/fields/date_field_presenter.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,44 @@ def value_for_preview
66
value = super
77
I18n.l(value) if value
88
end
9+
10+
def field_options
11+
return {} if access_readonly?
12+
13+
options = {}
14+
current_date = Time.zone.today
15+
16+
if @model.options.start_from_today?
17+
start_date_days_offset = @model.options.start_from_today_days_offset.days
18+
options[:min] = current_date + start_date_days_offset
19+
elsif @model.options.start_from_date?
20+
options[:min] = @model.options.start_date
21+
elsif @model.options.start_from_days_before_finish?
22+
days_before_finish = @model.options.days_before_finish.days
23+
if @model.options.finish_to_today?
24+
finish_date_days_offset = @model.options.finish_to_today_days_offset.days
25+
options[:min] = current_date + finish_date_days_offset - days_before_finish
26+
elsif @model.options.finish_to_date?
27+
options[:min] = @model.options.finish_date - days_before_finish
28+
end
29+
end
30+
31+
if @model.options.finish_to_today?
32+
finish_date_days_offset = @model.options.finish_to_today_days_offset.days
33+
options[:max] = current_date + finish_date_days_offset
34+
elsif @model.options.finish_to_date?
35+
options[:max] = @model.options.finish_date
36+
elsif @model.options.finish_to_days_since_start?
37+
days_since_start = @model.options.days_since_start.days
38+
if @model.options.start_from_today?
39+
start_date_days_offset = @model.options.start_from_today_days_offset.days
40+
options[:max] = current_date + start_date_days_offset + days_since_start
41+
elsif @model.options.start_from_date?
42+
options[:max] = @model.options.start_date + days_since_start
43+
end
44+
end
45+
46+
options
47+
end
948
end
1049
end
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<h3 class="title">Date field</h3>
2+
3+
<div class="field">
4+
<%= f.label :start_from, class: "label" %>
5+
<div class="control">
6+
<div class="field has-addons">
7+
<p class="control">
8+
<label class="radio button">
9+
<%= f.radio_button :start_from, "unlimited" %>
10+
Unlimited
11+
</label>
12+
</p>
13+
</div>
14+
<div class="field has-addons">
15+
<p class="control">
16+
<label class="radio button">
17+
<%= f.radio_button :start_from, "today" %>
18+
Today with offset
19+
</label>
20+
</p>
21+
<p class="control">
22+
<%= f.number_field :start_from_today_days_offset, class: "input", step: 1 %>
23+
</p>
24+
</div>
25+
<div class="field has-addons">
26+
<p class="control">
27+
<label class="radio button">
28+
<%= f.radio_button :start_from, "date" %>
29+
Date
30+
</label>
31+
</p>
32+
<p class="control">
33+
<%= f.date_field :start_date, class: "input" %>
34+
</p>
35+
</div>
36+
<div class="field has-addons">
37+
<p class="control">
38+
<label class="radio button">
39+
<%= f.radio_button :start_from, "days_before_finish" %>
40+
Days before finish
41+
</label>
42+
</p>
43+
<p class="control">
44+
<%= f.number_field :days_before_finish, class: "input", min: 1, step: 1 %>
45+
</p>
46+
</div>
47+
</div>
48+
</div>
49+
50+
<div class="field">
51+
<%= f.label :finish_to, class: "label" %>
52+
<div class="control">
53+
<div class="field has-addons">
54+
<p class="control">
55+
<label class="radio button">
56+
<%= f.radio_button :finish_to, "unlimited" %>
57+
Unlimited
58+
</label>
59+
</p>
60+
</div>
61+
<div class="field has-addons">
62+
<p class="control">
63+
<label class="radio button">
64+
<%= f.radio_button :finish_to, "today" %>
65+
Today with offset
66+
</label>
67+
</p>
68+
<p class="control">
69+
<%= f.number_field :finish_to_today_days_offset, class: "input", step: 1 %>
70+
</p>
71+
</div>
72+
<div class="field has-addons">
73+
<p class="control">
74+
<label class="radio button">
75+
<%= f.radio_button :finish_to, "date" %>
76+
Date
77+
</label>
78+
</p>
79+
<p class="control">
80+
<%= f.date_field :finish_date, class: "input" %>
81+
</p>
82+
</div>
83+
<div class="field has-addons">
84+
<p class="control">
85+
<label class="radio button">
86+
<%= f.radio_button :finish_to, "days_since_start" %>
87+
Days since start
88+
</label>
89+
</p>
90+
<p class="control">
91+
<%= f.number_field :days_since_start, class: "input", min: 1, step: 1 %>
92+
</p>
93+
</div>
94+
</div>
95+
</div>
96+
97+
<hr>

test/dummy/app/views/_form_core/field_options/_datetime_field.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
<p class="control">
7474
<label class="radio button">
7575
<%= f.radio_button :finish_to, "time" %>
76-
Fixed time
76+
Time
7777
</label>
7878
</p>
7979
<p class="control">

test/dummy/app/views/_form_core/fields/_date_field.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<div class="field">
22
<%= f.label field.name, field.label, class: 'label' %>
33
<div class="control">
4-
<%= f.date_field field.name, class: 'input', id: field.id, required: field.required, disabled: field.access_readonly? %>
4+
<%= f.date_field field.name, field.field_options.merge(class: 'input', id: field.id, required: field.required, disabled: field.access_readonly?) %>
55
</div>
66
<% if field.hint.present? %>
77
<p class="help"><%= field.hint %></p>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
ValidatesTimeliness.setup do |config|
2+
# Extend ORM/ODMs for full support (:active_record included).
3+
config.extend_orms = [:active_record]
4+
#
5+
# Default timezone
6+
# config.default_timezone = :utc
7+
#
8+
# Set the dummy date part for a time type values.
9+
# config.dummy_date_for_time_type = [ 2000, 1, 1 ]
10+
#
11+
# Ignore errors when restriction options are evaluated
12+
# config.ignore_restriction_errors = false
13+
#
14+
# Re-display invalid values in date/time selects
15+
# config.enable_date_time_select_extension!
16+
#
17+
# Handle multiparameter date/time values strictly
18+
# config.enable_multiparameter_extension!
19+
#
20+
# Shorthand date and time symbols for restrictions
21+
# config.restriction_shorthand_symbols.update(
22+
# :now => lambda { Time.current },
23+
# :today => lambda { Date.current }
24+
# )
25+
#
26+
# Use the plugin date/time parser which is stricter and extendable
27+
config.use_plugin_parser = true
28+
#
29+
# Add one or more formats making them valid. e.g. add_formats(:date, 'd(st|rd|th) of mmm, yyyy')
30+
# config.parser.add_formats()
31+
#
32+
# Remove one or more formats making them invalid. e.g. remove_formats(:date, 'dd/mm/yyy')
33+
# config.parser.remove_formats()
34+
#
35+
# Change the ambiguous year threshold when parsing a 2 digit year
36+
# config.parser.ambiguous_year_threshold = 30
37+
#
38+
# Treat ambiguous dates, such as 01/02/1950, as a Non-US date.
39+
# config.parser.remove_us_formats
40+
end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
en:
2+
errors:
3+
messages:
4+
invalid_date: "is not a valid date"
5+
invalid_time: "is not a valid time"
6+
invalid_datetime: "is not a valid datetime"
7+
is_at: "must be at %{restriction}"
8+
before: "must be before %{restriction}"
9+
on_or_before: "must be on or before %{restriction}"
10+
after: "must be after %{restriction}"
11+
on_or_after: "must be on or after %{restriction}"
12+
validates_timeliness:
13+
error_value_formats:
14+
date: '%Y-%m-%d'
15+
time: '%H:%M:%S'
16+
datetime: '%Y-%m-%d %H:%M:%S'

0 commit comments

Comments
 (0)