strict 2.0.0 → 2.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7a4d9b33782e029a5bdaf97e300a83ccb21fc76629e92d9dbbf3d12c373eaa4
4
- data.tar.gz: 715e362a7bb394c4f18d6eb46d828d5c5dbfe0090f85ed971a89369981254532
3
+ metadata.gz: f2cf2282196d21b0f929b9339bd7e93f6f97ce80796c47b29330519241d2ae11
4
+ data.tar.gz: 63ee491ee31552fa9b6449cedd24b2754f83222e362cf9cc4c0fca51cd367426
5
5
  SHA512:
6
- metadata.gz: 696e79a02adafa97c27575b5b3ac787aeb4923d07753d3149efbdf283a5357a0c74ccea46367cd6d0d8d49996e45e668418c203652738aa2a0efcc5ec24dbdf0
7
- data.tar.gz: 751a0ebdc18322f456ebc834ec7a215dc28b0de66dc010be82bcb03a5cbfdfa4c160a66110d0b60e22c056cfe34d0827b2e13533ee580c1761987c570939a4be
6
+ metadata.gz: f8cb4a343d08ef27c01ffa3d8bc02da8657cdcd98663e235e3ffe79598c0de55a9dc571b09524b6d2da31bb9cdc87e67494395eb71caff53bc90198d99d55699
7
+ data.tar.gz: e22d700e910206b31514c3661cbac6f3df04001082e4307922cd2f0203eb3e5f0b29129c1d2aab74bc03d68f561cd38a4114fd94f759a9197f0f3808b7b6d132
data/API.md CHANGED
@@ -116,10 +116,12 @@ There is no default discriminator. A union can declare zero or one `attributes`
116
116
 
117
117
  A variant name must be a lower snake-case string or symbol, and Strict generates its PascalCase nested subclass. By default, the discriminator tag is the name's corresponding symbol. The optional `tag:` can assign a different string or symbol. For example, `variant :requires_action, tag: "action-required"` generates `PaymentResult::RequiresAction < PaymentResult` with the tag `"action-required"`.
118
118
 
119
+ Each variant name also generates a class-level convenience constructor and an instance interrogation method. `PaymentResult.authorized(...)` calls `PaymentResult::Authorized.new(...)`. Every union member responds to `authorized?`, which is true for authorized members and false for other variants. These methods use the variant name even when the discriminator tag differs.
120
+
119
121
  The variant block configures the generated subclass. It can include modules, define methods, and contain zero or one `attributes` block. Strict combines the discriminator, shared union attributes, and variant attributes in that order. The discriminator is an implicit first attribute with a fixed default value:
120
122
 
121
123
  ```ruby
122
- PaymentResult::Authorized.new(
124
+ PaymentResult.authorized(
123
125
  request_id: "request_123",
124
126
  authorization_id: "auth_123",
125
127
  amount_in_cents: 1_000
@@ -127,7 +129,7 @@ PaymentResult::Authorized.new(
127
129
  # => { status: "payment.authorized", request_id: "request_123", authorization_id: "auth_123", amount_in_cents: 1_000 }
128
130
  ```
129
131
 
130
- Variants therefore use the documented `Strict::Value` behavior for initialization, validation, coercion, defaults, copying, equality, hashing, conversion, inspection, pattern matching, and declaration errors. A variant attribute cannot duplicate or share a backing instance variable with the discriminator or a shared union attribute, and generated readers cannot collide with methods defined in the variant block. The union base cannot be instantiated directly.
132
+ Variants therefore use the documented `Strict::Value` behavior for initialization, validation, coercion, defaults, copying, equality, hashing, conversion, inspection, pattern matching, and declaration errors. A variant attribute cannot duplicate or share a backing instance variable with the discriminator or a shared union attribute, and generated readers cannot collide with methods defined in the variant block. Generated convenience constructors and interrogation methods cannot collide with existing class or instance methods or with variant behavior. The union base cannot be instantiated directly.
131
133
 
132
134
  `PaymentResult === value` is true only when `value` has the exact class of a registered variant. Generated variant classes retain normal Ruby class matching, including matching instances of their subclasses. Union and variant inheritance are outside the compatibility boundary.
133
135
 
@@ -147,7 +149,7 @@ payment_result PaymentResult
147
149
  strict_payment_result PaymentResult, coerce: false
148
150
  ```
149
151
 
150
- Declaring a discriminator more than once, declaring equivalent duplicate string or symbol tags, using an invalid variant name or tag, replacing an existing generated constant, declaring multiple union or variant attribute blocks, declaring union attributes after a variant, or redeclaring the discriminator raises `ArgumentError`. Declaration return values, generated-class reflection details, and the exact text of declaration and coercion errors are outside the compatibility boundary.
152
+ Declaring a discriminator more than once, declaring equivalent duplicate string or symbol tags, using an invalid or conflicting variant name or tag, replacing an existing generated constant, declaring multiple union or variant attribute blocks, declaring union attributes after a variant, or redeclaring the discriminator raises `ArgumentError`. Declaration return values, generated-class reflection details, and the exact text of declaration and coercion errors are outside the compatibility boundary.
151
153
 
152
154
  ### Signed methods
153
155
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [2.1.0] - 2026-08-21
4
+
5
+ ### Added
6
+
7
+ - Add class-level convenience constructors and instance interrogation methods for every `Strict::Union` variant.
8
+
3
9
  ## [2.0.0] - 2026-08-20
4
10
 
5
11
  ### Added
@@ -102,7 +108,8 @@ The equal-weight geometric mean of the eight after/before timing ratios is 0.57,
102
108
 
103
109
  - Start initial development.
104
110
 
105
- [Unreleased]: https://github.com/kylekthompson/strict/compare/v2.0.0...HEAD
111
+ [Unreleased]: https://github.com/kylekthompson/strict/compare/v2.1.0...HEAD
112
+ [2.1.0]: https://github.com/kylekthompson/strict/compare/v2.0.0...v2.1.0
106
113
  [2.0.0]: https://github.com/kylekthompson/strict/compare/v1.5.0...v2.0.0
107
114
  [1.5.0]: https://github.com/kylekthompson/strict/compare/v1.4.0...v1.5.0
108
115
  [1.4.0]: https://github.com/kylekthompson/strict/compare/v1.3.1...v1.4.0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- strict (2.0.0)
4
+ strict (2.1.0)
5
5
  zeitwerk (~> 2.6)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -103,7 +103,7 @@ class PaymentResult
103
103
  end
104
104
  end
105
105
 
106
- authorized = PaymentResult::Authorized.new(
106
+ authorized = PaymentResult.authorized(
107
107
  request_id: "request_123",
108
108
  authorization_id: "auth_123",
109
109
  amount_in_cents: 1_000
@@ -111,6 +111,12 @@ authorized = PaymentResult::Authorized.new(
111
111
  authorized.to_h
112
112
  # => { status: "payment.authorized", request_id: "request_123", authorization_id: "auth_123", amount_in_cents: 1_000 }
113
113
 
114
+ authorized.authorized?
115
+ # => true
116
+
117
+ authorized.declined?
118
+ # => false
119
+
114
120
  authorized.successful?
115
121
  # => true
116
122
 
data/lib/strict/union.rb CHANGED
@@ -43,6 +43,7 @@ module Strict
43
43
  tag_key = strict_union_tag_key(tag)
44
44
  constant_name = strict_union_validate_variant!(name, tag, tag_key)
45
45
  variant_class = strict_union_build_variant(discriminator, name, tag, definition)
46
+ strict_union_validate_interrogation_method!(name, variant_class)
46
47
  strict_union_register_variant(name, tag, tag_key, constant_name, variant_class)
47
48
  end
48
49
 
@@ -66,17 +67,50 @@ module Strict
66
67
  raise ArgumentError, "tag #{tag.inspect} already declared for #{self}" if @strict_union_variants.key?(tag_key)
67
68
  raise ArgumentError, "constant #{constant_name} is already defined for #{self}" if
68
69
  const_defined?(constant_name, false)
70
+ raise ArgumentError, "variant convenience method #{name.inspect} already exists for #{self}" if
71
+ strict_union_method_defined?(singleton_class, name)
69
72
 
70
73
  constant_name
71
74
  end
72
75
 
76
+ def strict_union_validate_interrogation_method!(name, variant_class)
77
+ method_name = strict_union_interrogation_method(name)
78
+ owners = [self, *@strict_union_variant_names.values, variant_class]
79
+ conflicting_owner = owners.find { |owner| strict_union_method_defined?(owner, method_name) }
80
+ raise ArgumentError, "interrogation method #{method_name.inspect} already exists for #{conflicting_owner}" if
81
+ conflicting_owner
82
+
83
+ strict_union_validate_existing_interrogation_methods!(variant_class)
84
+ end
85
+
86
+ def strict_union_validate_existing_interrogation_methods!(variant_class)
87
+ @strict_union_variant_names.each_key do |variant_name|
88
+ existing_method = strict_union_interrogation_method(variant_name)
89
+ next if variant_class.instance_method(existing_method).owner.equal?(self)
90
+
91
+ raise ArgumentError, "interrogation method #{existing_method.inspect} already exists for #{variant_class}"
92
+ end
93
+ end
94
+
73
95
  def strict_union_register_variant(name, tag, tag_key, constant_name, variant_class)
74
96
  const_set(constant_name, variant_class)
75
97
  @strict_union_variant_names[name] = variant_class
76
98
  @strict_union_variants[tag_key] = Variant.new(tag:, variant_class:)
99
+ define_singleton_method(name) { |**attributes| variant_class.new(**attributes) }
100
+ define_method(strict_union_interrogation_method(name)) { variant_class === self }
77
101
  variant_class
78
102
  end
79
103
 
104
+ def strict_union_interrogation_method(name)
105
+ :"#{name}?"
106
+ end
107
+
108
+ def strict_union_method_defined?(owner, name)
109
+ owner.public_method_defined?(name) ||
110
+ owner.protected_method_defined?(name) ||
111
+ owner.private_method_defined?(name)
112
+ end
113
+
80
114
  def strict_union_discriminator!
81
115
  @strict_union_discriminator ||
82
116
  raise(ArgumentError, "declare a discriminator before variants for #{self}")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Strict
4
- VERSION = "2.0.0"
4
+ VERSION = "2.1.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strict
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kyle Thompson