errgonomic 0.8.2 → 0.8.3

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: 4e2e41e94d099dc04ef7a03ce86060c8d991fb8df5ac0f0faf864e25e49082dd
4
- data.tar.gz: 0de70e5eebd4319f18579a8bfb4c64597af07f8963d64cdb9ba357c5aec751e8
3
+ metadata.gz: 6cfa38b0fc666fade921ea48c1301cc51643175a59e82b4f2c50ac3a471e6696
4
+ data.tar.gz: 8294913794e216f90443e3396753a537d1fbbede4bf6e5eab459bdea51691e6e
5
5
  SHA512:
6
- metadata.gz: 2a46187771e5ec92b4229dd4f89de9467358e73b2302400cf54093d12e3db5b303333aea1f3d1a21ddc88e4bfbcc5d205ca5913f84799d16ed706222376f1c9c
7
- data.tar.gz: 0d72126a463307e8d40c1d9f4a905fbc4b0f52bb3a791fca7a523c8c124a974f935e3ab0f296ffa59722c869b85f1335bfea81b127ae34131af5a84389bdad2c
6
+ metadata.gz: 395971235a600af1c734db4dddb9aa8363e63d76ce65175ccb40c358e67aeb48e16379b0682177c42ec4f5d90a50fa14a55f7e7b994b0cd8ffe88dda7f89e688
7
+ data.tar.gz: d02c232ea2f79a5a3a04ce3913c1e9c07909655da06b11a5654699be7e371f5a5f8345b5efb9a3be8b40e5de667280b59c49dd2ba9541555faef7ccce8b1ca19
data/README.md CHANGED
@@ -201,7 +201,7 @@ end
201
201
 
202
202
  When `Rails::Railtie` is defined, Errgonomic installs a Railtie with two opt-in integrations for ActiveRecord:
203
203
 
204
- - `include Errgonomic::Rails::ActiveRecordOptional` in a model makes its nullable attributes and `optional: true` associations return `Some(value)` or `None()` instead of a value-or-nil. Every nullable column and optional association is wrapped, with no per-attribute opt-in. Two kinds of attribute stay unwrapped: those declared with `encrypts`, whose surrounding machinery reads the raw value, and those named by `errgonomic_optional_except`.
204
+ - `include Errgonomic::Rails::ActiveRecordOptional` in a model makes its nullable attributes and `optional: true` associations return `Some(value)` or `None()` instead of a value-or-nil. Every nullable column and optional association is wrapped, with no per-attribute opt-in. Three kinds of reader stay unwrapped: attributes declared with `encrypts` and singular associations with `accepts_nested_attributes_for`, both of which ActiveRecord's own machinery reads raw, and anything named by `errgonomic_optional_except`.
205
205
 
206
206
  ```ruby
207
207
  class Credential < ApplicationRecord
@@ -209,6 +209,8 @@ class Credential < ApplicationRecord
209
209
  include Errgonomic::Rails::ActiveRecordOptional
210
210
 
211
211
  encrypts :access_secret # also left unwrapped, declared either side of the include
212
+ has_one :rotation_schedule # wrapped: Some(schedule) or None()
213
+ has_one :owner, required: true # left unwrapped: absence is a validation failure
212
214
  end
213
215
  ```
214
216
 
@@ -19,9 +19,11 @@ module Errgonomic
19
19
  # boundary, so an Option can be passed to where/quote.
20
20
  # 4. SomeValidator provides a presence-style validation for Option
21
21
  # attributes.
22
- # 5. Attributes declared with encrypts are never wrapped: ActiveRecord
23
- # Encryption registers a length validator outside Model.validators
24
- # that reads the raw value and cannot survive an Option.
22
+ # 5. Readers that ActiveRecord's own machinery reads raw are never
23
+ # wrapped: an attribute declared with encrypts, whose length validator
24
+ # sits outside Model.validators and calls to_s on the value, and a
25
+ # singular association with nested attributes, which are assigned
26
+ # through the reader and ask the value whether it is a new record.
25
27
  #
26
28
  # errgonomic_optional_except is not on the list: it is configuration, an
27
29
  # escape hatch for whatever conflict shows up next, not a semantic
@@ -33,6 +35,9 @@ module Errgonomic
33
35
  reflect_on_all_associations(:belongs_to)
34
36
  .select { |r| r.options[:optional] }
35
37
  .each { |r| errgonomic_wrap_optional(r.name) }
38
+ reflect_on_all_associations(:has_one)
39
+ .reject { |r| r.options[:required] }
40
+ .each { |r| errgonomic_wrap_optional(r.name) }
36
41
  end
37
42
 
38
43
  class_methods do
@@ -61,7 +66,10 @@ module Errgonomic
61
66
  []
62
67
  end
63
68
 
64
- inherited | Array(encrypted_attributes).map(&:to_s) | Array(try(:errgonomic_optional_exceptions)).map(&:to_s)
69
+ inherited |
70
+ Array(encrypted_attributes).map(&:to_s) |
71
+ Array(try(:errgonomic_optional_exceptions)).map(&:to_s) |
72
+ errgonomic_nested_attribute_associations
65
73
  end
66
74
 
67
75
  # A model that keeps value-or-nil throughout, for whatever the
@@ -116,6 +124,32 @@ module Errgonomic
116
124
  super.tap { errgonomic_wrap_optional(name) if options[:optional] }
117
125
  end
118
126
 
127
+ # A has_one is absent whenever no row points back at the record, so
128
+ # its reader carries the same absence a nullable column does.
129
+ # required: true is the exception: it asserts the record is there, and
130
+ # absence is a validation failure rather than a value to handle.
131
+ def has_one(name, scope = nil, **options)
132
+ super.tap { errgonomic_wrap_optional(name) unless options[:required] }
133
+ end
134
+
135
+ # Nested attributes are assigned through the public reader, and
136
+ # ActiveRecord asks whatever it finds there whether it is a new
137
+ # record. An absent association has to arrive as nil for that, so a
138
+ # singular association with nested attributes keeps its plain reader.
139
+ def accepts_nested_attributes_for(*names, **options)
140
+ super.tap { errgonomic_unwrap_optionals(*names) }
141
+ end
142
+
143
+ # ActiveRecord keeps its own register of these, so the exclusion can be
144
+ # read from there rather than recorded as it goes past.
145
+ def errgonomic_nested_attribute_associations
146
+ return [] unless respond_to?(:nested_attributes_options)
147
+
148
+ nested_attributes_options.keys.map(&:to_s).select do |name|
149
+ %i[has_one belongs_to].include?(reflect_on_association(name)&.macro)
150
+ end
151
+ end
152
+
119
153
  # Encryption surrounds an attribute with machinery that reads the raw
120
154
  # value, including a length validator that calls to_s on it, so a
121
155
  # wrapped encrypted attribute cannot be saved. Declaring encrypts
@@ -223,6 +257,18 @@ class Object
223
257
  end
224
258
  end
225
259
 
260
+ module Errgonomic
261
+ module Option
262
+ # An Option is already lifted. Lifting it again would nest it, and the
263
+ # nesting is invisible until something reaches for the inner value.
264
+ class Any
265
+ def to_option
266
+ self
267
+ end
268
+ end
269
+ end
270
+ end
271
+
226
272
  module Errgonomic
227
273
  module Rails
228
274
  # Teach ActiveRecord SQL quoting to unwrap Options, quoting a None as
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Errgonomic
4
- VERSION = '0.8.2'
4
+ VERSION = '0.8.3'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: errgonomic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 0.8.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Zadrozny