date_values 0.1.3 → 0.1.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 18c625b8766c99ee4e063b2d2b756f68702d7468301cf0e14cb61c29647bc97e
4
- data.tar.gz: cc21e80e920a5590109a62894d362b541f5f7362835f7d5113980b20bc28842f
3
+ metadata.gz: 3ed7149400551e703761bd9ea324f55decb43cfe14e11d36d8e6e22f1fcb21a3
4
+ data.tar.gz: d809fce0b0215b62891a453b4e7e4278ad7a600ffea25cc045662f4a4ae1425b
5
5
  SHA512:
6
- metadata.gz: 3ea359a67138a64040d1fc62a9288c3862665f636db9e1651697ec6527390e1e53cb002f87745df2189b8eba8944f104e127b620af032be0e92d5a5d707f77a2
7
- data.tar.gz: f5a8b524c53566ee986a3eea6bf28d08e889a428d15b7ae48f04443d46108ee7b882391adc82bc597af10338ef071ec9ecb83dc6812718d67ef332c5065432eb
6
+ metadata.gz: 821ca618c27d3b17583c8420697ad11cbb3b9ee34dc44d7ea4d938ffd4500a4795b8aacd6b41df9864c982877d201c0dc616fecbca6342945c671b8b1f410679
7
+ data.tar.gz: f69de80391b67de78fafca31368c4f54683299c94740eba4047d0dff800ecb0647bcf208039473911fbf0fb60cbbb5595ef3c322d79154b72bc39e47056361a5
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.4] - 2026-03-20
4
+
5
+ - Cast returns `nil` for invalid input instead of raising, following Rails convention
6
+ - Add `date_value` validator to distinguish "invalid input" from "no input"
7
+ - `#inspect` now uses `#<ClassName value>` format following Ruby convention
8
+ - Works with Rails standard validators (`comparison`, `inclusion`, `exclusion`)
9
+
3
10
  ## [0.1.3] - 2026-03-20
4
11
 
5
12
  - Fix ActiveRecord type registration — use `ActiveSupport.on_load(:active_record)` to register types regardless of load order
data/README.md CHANGED
@@ -24,27 +24,29 @@ ym = YearMonth.new(2026, 3)
24
24
  ym.to_s # => "2026-03"
25
25
  ym.to_date # => #<Date: 2026-03-01>
26
26
 
27
- YearMonth.from(Date.today) # => YearMonth[2026-03]
28
- YearMonth.parse('2026-03') # => YearMonth[2026-03]
27
+ YearMonth.from(Date.today) # => #<DateValues::YearMonth 2026-03>
28
+ YearMonth.parse('2026-03') # => #<DateValues::YearMonth 2026-03>
29
29
 
30
- ym + 1 # => YearMonth[2026-04]
31
- ym - 1 # => YearMonth[2026-02]
30
+ ym + 1 # => #<DateValues::YearMonth 2026-04>
31
+ ym - 1 # => #<DateValues::YearMonth 2026-02>
32
32
  YearMonth.new(2026, 3) - YearMonth.new(2025, 1) # => 14
33
33
 
34
34
  # Range support
35
35
  (YearMonth.new(2026, 1)..YearMonth.new(2026, 3)).to_a
36
- # => [YearMonth[2026-01], YearMonth[2026-02], YearMonth[2026-03]]
36
+ # => [#<DateValues::YearMonth 2026-01>, #<DateValues::YearMonth 2026-02>, #<DateValues::YearMonth 2026-03>]
37
37
  ```
38
38
 
39
39
  ### MonthDay
40
40
 
41
+ String representation uses ISO 8601 `--MM-DD` format (year omitted):
42
+
41
43
  ```ruby
42
44
  md = MonthDay.new(3, 19)
43
45
  md.to_s # => "--03-19"
44
46
  md.to_date(2026) # => #<Date: 2026-03-19>
45
47
 
46
- MonthDay.from(Date.today) # => MonthDay[--03-20]
47
- MonthDay.parse('--03-19') # => MonthDay[--03-19]
48
+ MonthDay.from(Date.today) # => #<DateValues::MonthDay --03-20>
49
+ MonthDay.parse('--03-19') # => #<DateValues::MonthDay --03-19>
48
50
 
49
51
  # Range membership
50
52
  summer = MonthDay.new(6, 1)..MonthDay.new(8, 31)
@@ -59,8 +61,8 @@ tod.to_s # => "14:30"
59
61
 
60
62
  TimeOfDay.new(14, 30, 45).to_s # => "14:30:45"
61
63
 
62
- TimeOfDay.from(Time.now) # => TimeOfDay[14:30]
63
- TimeOfDay.parse('14:30') # => TimeOfDay[14:30]
64
+ TimeOfDay.from(Time.now) # => #<DateValues::TimeOfDay 14:30>
65
+ TimeOfDay.parse('14:30') # => #<DateValues::TimeOfDay 14:30>
64
66
 
65
67
  # Range membership
66
68
  business_hours = TimeOfDay.new(9, 0)..TimeOfDay.new(17, 0)
@@ -109,6 +111,37 @@ Shop.where(billing_month: YearMonth.new(2026, 3))
109
111
  # SELECT * FROM shops WHERE billing_month = '2026-03'
110
112
  ```
111
113
 
114
+ ### Validation
115
+
116
+ All classes are `Comparable` and value-equal, so standard Rails validators work as-is:
117
+
118
+ ```ruby
119
+ class Contract < ApplicationRecord
120
+ attribute :start_month, :year_month
121
+ attribute :opens_at, :time_of_day
122
+
123
+ validates :start_month, comparison: {greater_than: -> { YearMonth.from(Date.current) }}
124
+ validates :opens_at, comparison: {
125
+ greater_than_or_equal_to: TimeOfDay.new(9, 0),
126
+ less_than_or_equal_to: TimeOfDay.new(17, 0)
127
+ }
128
+ end
129
+ ```
130
+
131
+ Invalid input (e.g. `"25:00"`) is cast to `nil` rather than raising, following the same convention as Rails' built-in types. The `date_value` validator detects this and gives a meaningful error message:
132
+
133
+ ```ruby
134
+ class Shop < ApplicationRecord
135
+ attribute :opens_at, :time_of_day
136
+
137
+ validates :opens_at, presence: true, date_value: true
138
+ end
139
+
140
+ Shop.new(opens_at: '25:00').errors[:opens_at] # => ["is invalid"]
141
+ Shop.new(opens_at: '').errors[:opens_at] # => ["can't be blank"]
142
+ ```
143
+
144
+
112
145
  ### I18n / `l` Helper
113
146
 
114
147
  All classes implement `#strftime`, and the Rails integration extends `I18n.l` to support them. Define formats in your locale files:
@@ -125,6 +158,7 @@ en:
125
158
  time_of_day:
126
159
  formats:
127
160
  default: '%-I:%M %p'
161
+ long: '%-I:%M:%S %p'
128
162
  ```
129
163
 
130
164
  ```yaml
@@ -138,12 +172,14 @@ ja:
138
172
  default: '%-m月%-d日'
139
173
  time_of_day:
140
174
  formats:
141
- default: '%-H時%M分'
175
+ default: '%-H時%-M分'
176
+ long: '%-H時%-M分%-S秒'
142
177
  ```
143
178
 
144
179
  ```ruby
145
- I18n.l YearMonth.new(2026, 3), locale: :en # => "March 2026"
146
- I18n.l YearMonth.new(2026, 3), locale: :ja # => "2026年3月"
180
+ I18n.l YearMonth.new(2026, 3), locale: :en # => "March 2026"
181
+ I18n.l YearMonth.new(2026, 3), locale: :ja # => "2026年3月"
182
+ I18n.l TimeOfDay.new(14, 30), format: :long # => "2:30:00 PM"
147
183
  ```
148
184
 
149
185
  ## License
@@ -44,7 +44,7 @@ module DateValues
44
44
  end
45
45
 
46
46
  def inspect
47
- "MonthDay[#{self}]"
47
+ "#<DateValues::MonthDay #{self}>"
48
48
  end
49
49
 
50
50
  private
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_model/validator'
4
+
5
+ class DateValueValidator < ActiveModel::EachValidator
6
+ def validate_each(record, attribute, value)
7
+ raw = record.read_attribute_before_type_cast(attribute)
8
+ return if raw.blank?
9
+ return unless value.nil?
10
+
11
+ record.errors.add(attribute, :invalid, **options)
12
+ end
13
+ end
@@ -12,8 +12,9 @@ module DateValues
12
12
  when MonthDay then value
13
13
  when String then MonthDay.parse(value)
14
14
  when nil then nil
15
- else raise ArgumentError, "can't cast #{value.class} to MonthDay"
16
15
  end
16
+ rescue ArgumentError
17
+ nil
17
18
  end
18
19
 
19
20
  def serialize(value)
@@ -13,8 +13,9 @@ module DateValues
13
13
  when Time then TimeOfDay.new(value.hour, value.min, value.sec)
14
14
  when String then TimeOfDay.parse(value)
15
15
  when nil then nil
16
- else raise ArgumentError, "can't cast #{value.class} to TimeOfDay"
17
16
  end
17
+ rescue ArgumentError
18
+ nil
18
19
  end
19
20
 
20
21
  def serialize(value)
@@ -12,8 +12,9 @@ module DateValues
12
12
  when YearMonth then value
13
13
  when String then YearMonth.parse(value)
14
14
  when nil then nil
15
- else raise ArgumentError, "can't cast #{value.class} to YearMonth"
16
15
  end
16
+ rescue ArgumentError
17
+ nil
17
18
  end
18
19
 
19
20
  def serialize(value)
@@ -7,6 +7,7 @@ require_relative 'rails/year_month_type'
7
7
  require_relative 'rails/month_day_type'
8
8
  require_relative 'rails/time_of_day_type'
9
9
  require_relative 'rails/i18n_backend'
10
+ require_relative 'rails/date_value_validator'
10
11
 
11
12
  ActiveModel::Type.register(:year_month, DateValues::Rails::YearMonthType)
12
13
  ActiveModel::Type.register(:month_day, DateValues::Rails::MonthDayType)
@@ -42,7 +42,7 @@ module DateValues
42
42
  end
43
43
 
44
44
  def inspect
45
- "TimeOfDay[#{self}]"
45
+ "#<DateValues::TimeOfDay #{self}>"
46
46
  end
47
47
  end
48
48
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DateValues
4
- VERSION = '0.1.3'
4
+ VERSION = '0.1.4'
5
5
  end
@@ -62,7 +62,7 @@ module DateValues
62
62
  end
63
63
 
64
64
  def inspect
65
- "YearMonth[#{self}]"
65
+ "#<DateValues::YearMonth #{self}>"
66
66
  end
67
67
  end
68
68
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: date_values
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keita Urashima
@@ -22,6 +22,7 @@ files:
22
22
  - lib/date_values.rb
23
23
  - lib/date_values/month_day.rb
24
24
  - lib/date_values/rails.rb
25
+ - lib/date_values/rails/date_value_validator.rb
25
26
  - lib/date_values/rails/i18n_backend.rb
26
27
  - lib/date_values/rails/month_day_type.rb
27
28
  - lib/date_values/rails/time_of_day_type.rb