schema-model 0.11.0 → 0.12.0
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 +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +9 -3
- data/lib/schema/active_model_parsing_error.rb +15 -0
- data/lib/schema/active_model_parsing_errors.rb +31 -10
- data/lib/schema/all.rb +1 -0
- data/lib/schema/associations/has_many.rb +4 -0
- data/lib/schema/associations/has_one.rb +4 -0
- data/lib/schema/parsers/common.rb +5 -0
- data/lib/schema/parsers/decimal.rb +46 -0
- data/lib/schema/version.rb +1 -1
- data/lib/schema-model.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d02407b3970af8db7c2bde92ebe9bb6e557e9f6415b503dbee29961938d15144
|
|
4
|
+
data.tar.gz: d475d956180eabe9455e40c482f44735217ddcff04d1d121e5e86de3a4071686
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0df082a47d8e30b845f36fd72c35dd73164a365a5cf67e76f7bfcb0d97000d4ef1f9581b46377ec7129a9253c3aa39984a7e28a068a8137d0a229eda427556cd
|
|
7
|
+
data.tar.gz: fda0a963a0e4b5de8800977b85d90c981ed16885b6659d620bbef5360d0c5f72ebd8e0ed34d764f9ca9e07b915a9bb7674fba2c891cac9cefd0245fe60045a03
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.12.0](https://github.com/dougyouch/schema/compare/v0.11.0...v0.12.0) (2026-10-05)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* with Schema::All, a parsing error's type is now the code symbol (e.g. :invalid) instead of the message string; messages are unchanged.
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* decimal type, datetime alias, association was_set? and parsing error codes ([e93e683](https://github.com/dougyouch/schema/commit/e93e6836e98efcdf01da656818f70ec305f59871))
|
|
13
|
+
|
|
3
14
|
## [0.11.0](https://github.com/dougyouch/schema/compare/v0.10.0...v0.11.0) (2026-10-05)
|
|
4
15
|
|
|
5
16
|
|
data/README.md
CHANGED
|
@@ -181,7 +181,7 @@ def update
|
|
|
181
181
|
end
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
-
Associations in `set_attribute_values` are schema models; call `as_json` on them if the record expects hashes. For a single attribute, `input.name_was_set?` answers the same question.
|
|
184
|
+
Associations in `set_attribute_values` are schema models; call `as_json` on them if the record expects hashes. For a single attribute or association, `input.name_was_set?` answers the same question.
|
|
185
185
|
|
|
186
186
|
For nested input, `as_json(only_set: true)` keeps only the fields that were sent, at every level, so it can be merged into a JSON column without wiping fields the client left out:
|
|
187
187
|
|
|
@@ -201,18 +201,22 @@ record.data = record.data.deep_merge(input.as_json(only_set: true).deep_stringif
|
|
|
201
201
|
attribute :name, :string # String values
|
|
202
202
|
attribute :count, :integer # Integer values (parses "123", " 123 " and "0123" to 123)
|
|
203
203
|
attribute :price, :float # Float values (parses "9.99" to 9.99, also "1e+5")
|
|
204
|
+
attribute :total, :decimal # BigDecimal (parses "9.99" exactly); with Schema::All
|
|
204
205
|
attribute :active, :boolean # see below
|
|
205
206
|
attribute :notes, :string_or_nil # String, but nil when empty or whitespace-only
|
|
206
207
|
```
|
|
207
208
|
|
|
208
209
|
Booleans accept `1, t, true, on, y, yes` as `true` and `0, f, false, off, n, no` as `false` (any case). Any other string is an `invalid` parsing error, and numbers are `true` unless they're 0.
|
|
209
210
|
|
|
210
|
-
|
|
211
|
+
`:decimal` needs the `bigdecimal` gem on Ruby 3.4+; ActiveModel already depends on it.
|
|
212
|
+
|
|
213
|
+
For `:integer`, `:float`, `:decimal`, `:boolean`, `:date`, `:time`, `:american_date` and `:american_time`, strings are stripped first and a blank string parses to `nil` without an error, so empty CSV cells don't count as bad input.
|
|
211
214
|
|
|
212
215
|
### Date and Time Types
|
|
213
216
|
|
|
214
217
|
```ruby
|
|
215
218
|
attribute :created_at, :time # ISO 8601 date and time (Time.xmlschema)
|
|
219
|
+
attribute :updated_at, :datetime # same as :time, matching ActiveRecord's type name
|
|
216
220
|
attribute :birth_date, :date # ISO 8601 (Date.iso8601), e.g. "2024-01-31"; free text like "May 1" is invalid
|
|
217
221
|
attribute :us_date, :american_date # MM/DD/YYYY format
|
|
218
222
|
attribute :us_time, :american_time # MM/DD/YYYY HH:MM:SS format
|
|
@@ -255,7 +259,7 @@ Each read of a default returns a fresh copy (frozen values are shared), so mutat
|
|
|
255
259
|
|
|
256
260
|
### Checking If Attribute Was Set
|
|
257
261
|
|
|
258
|
-
Every attribute generates a `_was_set?` predicate method:
|
|
262
|
+
Every attribute and association generates a `_was_set?` predicate method:
|
|
259
263
|
|
|
260
264
|
```ruby
|
|
261
265
|
user = UserSchema.from_hash(name: 'John')
|
|
@@ -462,6 +466,8 @@ user.parsing_errors.full_messages # => ["Age is invalid"]
|
|
|
462
466
|
| `unknown_attribute` | is an unknown attribute | `schema.parsing_errors.unknown_attribute` |
|
|
463
467
|
| `unhandled_type` | is an unhandled type | `schema.parsing_errors.unhandled_type` |
|
|
464
468
|
|
|
469
|
+
The code is kept as each error's type, so `user.parsing_errors.details # => { age: [{ error: :invalid }] }`. Register codes for your own parsers with `Schema::ActiveModelParsingErrors.add_message(:read_only, 'is read only')`; other strings added to `parsing_errors` are kept as messages.
|
|
470
|
+
|
|
465
471
|
A plain `Schema::Model` uses `Schema::Errors`, which stores the codes themselves (`user.parsing_errors[:age] # => ["invalid"]`).
|
|
466
472
|
|
|
467
473
|
### Checking Everything at Once
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_model'
|
|
4
|
+
|
|
5
|
+
module Schema
|
|
6
|
+
# One parsing error in {ActiveModelParsingErrors}: its type is the code (e.g. :invalid) and its
|
|
7
|
+
# message is resolved when it's added. A plain ActiveModel::Error would resolve the message by
|
|
8
|
+
# reading the attribute, which unknown keys and nested markers ("items:0") don't have.
|
|
9
|
+
class ActiveModelParsingError < ActiveModel::Error
|
|
10
|
+
# @return [String]
|
|
11
|
+
def message
|
|
12
|
+
options[:message]
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
require 'active_model'
|
|
4
4
|
|
|
5
5
|
module Schema
|
|
6
|
-
# Schema::ActiveModelParsingErrors
|
|
7
|
-
#
|
|
6
|
+
# Schema::ActiveModelParsingErrors gives parsing errors readable messages, e.g. "Age is invalid"
|
|
7
|
+
# instead of "Age invalid", while keeping the code as the error's type, so
|
|
8
|
+
# `parsing_errors.details` reports `{ error: :invalid }`. Override messages with I18n keys under
|
|
9
|
+
# schema.parsing_errors, and register more codes with {.add_message}.
|
|
8
10
|
class ActiveModelParsingErrors < ActiveModel::Errors
|
|
9
|
-
# Default English message for each parsing error code.
|
|
11
|
+
# Default English message for each built-in parsing error code.
|
|
10
12
|
MESSAGES = {
|
|
11
13
|
::Schema::ParsingErrors::INVALID => 'is invalid',
|
|
12
14
|
::Schema::ParsingErrors::INCOMPATIBLE => 'is an incompatible type',
|
|
@@ -15,18 +17,37 @@ module Schema
|
|
|
15
17
|
::Schema::ParsingErrors::UNHANDLED_TYPE => 'is an unhandled type'
|
|
16
18
|
}.freeze
|
|
17
19
|
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
+
# @return [Hash{String => String}] every known code and its default message
|
|
21
|
+
def self.messages
|
|
22
|
+
@messages ||= MESSAGES.dup
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Registers a parsing error code, e.g. for a custom parser.
|
|
26
|
+
# @example
|
|
27
|
+
# Schema::ActiveModelParsingErrors.add_message(:read_only, 'is read only')
|
|
28
|
+
# model.parsing_errors.add(:id, :read_only) # => "Id is read only", details { error: :read_only }
|
|
29
|
+
# @param code [Symbol, String]
|
|
30
|
+
# @param message [String] default message; an I18n key schema.parsing_errors.<code> overrides it
|
|
31
|
+
# @return [void]
|
|
32
|
+
def self.add_message(code, message)
|
|
33
|
+
messages[code.to_s] = message
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Known codes are stored as the error's type with a readable message. Anything else is kept
|
|
37
|
+
# as a message string, since parsing error keys (e.g. "items:0") often aren't model attributes.
|
|
20
38
|
def add(attribute, type = :invalid, **)
|
|
21
|
-
|
|
39
|
+
code = type.to_s
|
|
40
|
+
return super unless self.class.messages.key?(code)
|
|
41
|
+
|
|
42
|
+
error = ActiveModelParsingError.new(@base, attribute, code.to_sym, message: message_for(code))
|
|
43
|
+
@errors.append(error)
|
|
44
|
+
error
|
|
22
45
|
end
|
|
23
46
|
|
|
24
47
|
private
|
|
25
48
|
|
|
26
|
-
def message_for(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
I18n.t("schema.parsing_errors.#{type}", default: MESSAGES[type])
|
|
49
|
+
def message_for(code)
|
|
50
|
+
I18n.t("schema.parsing_errors.#{code}", default: self.class.messages[code])
|
|
30
51
|
end
|
|
31
52
|
end
|
|
32
53
|
end
|
data/lib/schema/all.rb
CHANGED
|
@@ -15,6 +15,7 @@ module Schema
|
|
|
15
15
|
# parsers
|
|
16
16
|
base.schema_include ::Schema::Parsers::American
|
|
17
17
|
base.schema_include ::Schema::Parsers::Array
|
|
18
|
+
base.schema_include ::Schema::Parsers::Decimal
|
|
18
19
|
base.schema_include ::Schema::Parsers::Hash
|
|
19
20
|
base.schema_include ::Schema::Parsers::Json
|
|
20
21
|
|
|
@@ -26,6 +26,10 @@ module Schema
|
|
|
26
26
|
#{options[:instance_variable]}
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
def #{options[:getter]}_was_set?
|
|
30
|
+
instance_variable_defined?(:#{options[:instance_variable]})
|
|
31
|
+
end
|
|
32
|
+
|
|
29
33
|
def #{name}_schema_creator
|
|
30
34
|
@#{name}_schema_creator ||= ::Schema::Associations::SchemaCreator.new(self, #{name.inspect})
|
|
31
35
|
end
|
|
@@ -25,6 +25,10 @@ module Schema
|
|
|
25
25
|
#{options[:instance_variable]}
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def #{options[:getter]}_was_set?
|
|
29
|
+
instance_variable_defined?(:#{options[:instance_variable]})
|
|
30
|
+
end
|
|
31
|
+
|
|
28
32
|
def #{name}_schema_creator
|
|
29
33
|
@#{name}_schema_creator ||= ::Schema::Associations::SchemaCreator.new(self, #{name.inspect})
|
|
30
34
|
end
|
|
@@ -119,6 +119,11 @@ module Schema
|
|
|
119
119
|
end
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
+
# @!method parse_datetime(field_name, parsing_errors, value)
|
|
123
|
+
# Same as {#parse_time}, so `attribute :created_at, :datetime` matches ActiveRecord's type name.
|
|
124
|
+
# @return [Time, nil]
|
|
125
|
+
alias parse_datetime parse_time
|
|
126
|
+
|
|
122
127
|
# Parses ISO 8601 dates (Date.iso8601).
|
|
123
128
|
# @return [Date, nil]
|
|
124
129
|
def parse_date(field_name, parsing_errors, value)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bigdecimal'
|
|
4
|
+
|
|
5
|
+
module Schema
|
|
6
|
+
module Parsers
|
|
7
|
+
# Schema::Parsers::Decimal parses :decimal attributes into BigDecimal, for money and other
|
|
8
|
+
# exact values. Included by Schema::All. On Ruby 3.4+ bigdecimal is a bundled gem; ActiveModel
|
|
9
|
+
# already depends on it, otherwise add it to your Gemfile.
|
|
10
|
+
module Decimal
|
|
11
|
+
include StringValue
|
|
12
|
+
|
|
13
|
+
# Accepted decimal strings, e.g. "12", "-0.50", "1.5e3".
|
|
14
|
+
DECIMAL_REGEX = /\A[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[Ee][-+]?\d+)?\z/
|
|
15
|
+
|
|
16
|
+
# Parses decimals from strings and numbers; hashes and arrays are incompatible.
|
|
17
|
+
# @return [BigDecimal, nil]
|
|
18
|
+
def parse_decimal(field_name, parsing_errors, value)
|
|
19
|
+
case value
|
|
20
|
+
when BigDecimal then value
|
|
21
|
+
when Integer, Rational then BigDecimal(value, 0)
|
|
22
|
+
when Float then parse_float_as_decimal(field_name, parsing_errors, value)
|
|
23
|
+
when String
|
|
24
|
+
parse_string_value(field_name, parsing_errors, value) { |str| BigDecimal(str) if DECIMAL_REGEX.match?(str) }
|
|
25
|
+
when nil then nil
|
|
26
|
+
when ::Hash, ::Array then add_decimal_error(field_name, parsing_errors, ::Schema::ParsingErrors::INCOMPATIBLE)
|
|
27
|
+
else add_decimal_error(field_name, parsing_errors, ::Schema::ParsingErrors::UNHANDLED_TYPE)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# floats go through their shortest string form, so 1.1 becomes 1.1 and not 1.100000000000000088...
|
|
34
|
+
def parse_float_as_decimal(field_name, parsing_errors, value)
|
|
35
|
+
return add_decimal_error(field_name, parsing_errors, ::Schema::ParsingErrors::INCOMPATIBLE) unless value.finite?
|
|
36
|
+
|
|
37
|
+
BigDecimal(value.to_s)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def add_decimal_error(field_name, parsing_errors, code)
|
|
41
|
+
parsing_errors.add(field_name, code)
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/schema/version.rb
CHANGED
data/lib/schema-model.rb
CHANGED
|
@@ -26,6 +26,7 @@ module Schema
|
|
|
26
26
|
|
|
27
27
|
autoload :ActiveModelValidations, 'schema/active_model_validations'
|
|
28
28
|
autoload :All, 'schema/all'
|
|
29
|
+
autoload :ActiveModelParsingError, 'schema/active_model_parsing_error'
|
|
29
30
|
autoload :ActiveModelParsingErrors, 'schema/active_model_parsing_errors'
|
|
30
31
|
autoload :ArrayHeaders, 'schema/array_headers'
|
|
31
32
|
autoload :Arrays, 'schema/arrays'
|
|
@@ -43,6 +44,7 @@ module Schema
|
|
|
43
44
|
autoload :American, 'schema/parsers/american'
|
|
44
45
|
autoload :Array, 'schema/parsers/array'
|
|
45
46
|
autoload :Common, 'schema/parsers/common'
|
|
47
|
+
autoload :Decimal, 'schema/parsers/decimal'
|
|
46
48
|
autoload :Hash, 'schema/parsers/hash'
|
|
47
49
|
autoload :Json, 'schema/parsers/json'
|
|
48
50
|
autoload :StringValue, 'schema/parsers/string_value'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: schema-model
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Doug Youch
|
|
@@ -45,6 +45,7 @@ files:
|
|
|
45
45
|
- README.md
|
|
46
46
|
- bin/schema-json2csv
|
|
47
47
|
- lib/schema-model.rb
|
|
48
|
+
- lib/schema/active_model_parsing_error.rb
|
|
48
49
|
- lib/schema/active_model_parsing_errors.rb
|
|
49
50
|
- lib/schema/active_model_validations.rb
|
|
50
51
|
- lib/schema/all.rb
|
|
@@ -63,6 +64,7 @@ files:
|
|
|
63
64
|
- lib/schema/parsers/american.rb
|
|
64
65
|
- lib/schema/parsers/array.rb
|
|
65
66
|
- lib/schema/parsers/common.rb
|
|
67
|
+
- lib/schema/parsers/decimal.rb
|
|
66
68
|
- lib/schema/parsers/hash.rb
|
|
67
69
|
- lib/schema/parsers/json.rb
|
|
68
70
|
- lib/schema/parsers/string_value.rb
|