active_record_compose 1.2.0 → 1.2.1

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: 2622aa32a886a2c21fcc7836254285cb92f1ce7745674297c3e7fe46bcbe556a
4
- data.tar.gz: 747acd3e97cb78aba78b9d3f2c87ab4d9bd34b0727aaf2a1c94d9520ea9eaa08
3
+ metadata.gz: 810feb87760ff992aa21ca019081e6072ec43c99387ea0ee013f767d4d2c875c
4
+ data.tar.gz: 7077c4d5b79d12c6c5f5a0297d0d2837c3d3c79a3b8e74a13c12e05a3511c078
5
5
  SHA512:
6
- metadata.gz: e6002732e8e6fa09269ccb4a7635af4ef683d06af14e829aa062846c6dbb90c2942d5b3cc2b4d715eeef2182498e732d33b5ae004836ff703f8d8235fec12c70
7
- data.tar.gz: 18d3f9a635d3f4bf940eeb4020824fa7fc7d91cf27b0a99aa253af53e31fdca6bebd69d402537bea29749de26eba3f5a261e10a3ef2ebf079294e7fc3dbd5127
6
+ metadata.gz: 283b3beaab24fb19d30e134dbe543a25c730bf0d331bdd5114ae89747d0a247b8b572d7e6f2578c06d80f2f7cb4b8c6023f2640518c5b0dc21d335401e45ab5c
7
+ data.tar.gz: 6f55ebeb0bd4379d206f55b8dc8dc618beca9d281f0d425c0cc8d96078d60ed74fd26c67b92b2bb8c26f9cb6dd4634557ef1064990c4a7ca91bb15e99d6c381e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.2.1] - 2026-03-21
4
+
5
+ * Improved clarity of error when accessing uninitialized attributes.
6
+ (https://github.com/hamajyotan/active_record_compose/pull/71)
7
+ * doc: Minor document adjustments, etc.
8
+
3
9
  ## [1.2.0] - 2026-01-05
4
10
 
5
11
  * Avoid issuing multiple saves on the same object.
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # ActiveRecordCompose
2
2
 
3
3
  ActiveRecordCompose lets you build form objects that combine multiple ActiveRecord models into a single, unified interface.
4
- More than just a simple form object, it is designed as a **business-oriented composed model** that encapsulates complex operations-such as user registration spanning multiple tables-making them easier to write, validate, and maintain.
4
+ More than a simple form object, ActiveRecordCompose is designed as a **business-oriented composed model** that encapsulates complex operations, such as user registration spanning multiple tables, making them easier to write, validate, and maintain.
5
5
 
6
6
  [![Gem Version](https://badge.fury.io/rb/active_record_compose.svg)](https://badge.fury.io/rb/active_record_compose)
7
7
  ![CI](https://github.com/hamajyotan/active_record_compose/workflows/CI/badge.svg)
@@ -29,15 +29,14 @@ More than just a simple form object, it is designed as a **business-oriented com
29
29
 
30
30
  ## Motivation
31
31
 
32
- In Rails, `ActiveRecord::Base` is responsible for persisting data to the database.
33
- By defining validations and callbacks, you can model use cases effectively.
32
+ In Rails, `ActiveRecord::Base` is responsible for persisting data to the database and modeling application behavior through validations and callbacks.
34
33
 
35
34
  However, when a single model must serve multiple different use cases, you often end up with conditional validations (`on: :context`) or workarounds like `save(validate: false)`.
36
35
  This mixes unrelated concerns into one model, leading to unnecessary complexity.
37
36
 
38
- `ActiveModel::Model` helps here it provides the familiar API (`attribute`, `errors`, validations, callbacks) without persistence, so you can isolate logic per use case.
37
+ `ActiveModel::Model` helps address this by providing a familiar API (`attribute`, `errors`, validations, callbacks) without persistence, allowing you to isolate logic per use case.
39
38
 
40
- **ActiveRecordCompose** builds on `ActiveModel::Model` and is a powerful **business object** that acts as a first-class model within Rails.
39
+ **ActiveRecordCompose** builds on `ActiveModel::Model` and provides a powerful **business object** that acts as a first-class model within Rails.
41
40
  - Transparently accesses attributes across multiple models
42
41
  - Saves all associated models atomically in a transaction
43
42
  - Collects and exposes error information consistently
@@ -106,7 +105,7 @@ class UserRegistration < ActiveRecordCompose::Model
106
105
  end
107
106
  ```
108
107
 
109
- Usage:
108
+ Example usage:
110
109
 
111
110
  ```ruby
112
111
  # === Standalone script ===
@@ -174,7 +173,7 @@ registration.attributes
174
173
 
175
174
  ### Unified Error Handling
176
175
 
177
- Validation errors from inner models are collected into the composed model:
176
+ Validation errors from the inner models are collected into the composed object:
178
177
 
179
178
  ```ruby
180
179
  user_registration = UserRegistration.new(
@@ -274,7 +273,7 @@ model.save
274
273
 
275
274
  ### Notes on adding models dynamically
276
275
 
277
- Avoid adding `models` to the models array **after validation has already run**
276
+ Avoid adding models to the `models` array **after validation has already run**
278
277
  (for example, inside `after_validation` or `before_save` callbacks).
279
278
 
280
279
  ```ruby
@@ -323,5 +322,5 @@ The gem is available as open source under the terms of the [MIT License](https:/
323
322
 
324
323
  ## Code of Conduct
325
324
 
326
- Everyone interacting in the ActiveRecord::Compose project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/hamajyotan/active_record_compose/blob/main/CODE_OF_CONDUCT.md).
325
+ Everyone interacting in the ActiveRecordCompose project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/hamajyotan/active_record_compose/blob/main/CODE_OF_CONDUCT.md).
327
326
 
@@ -3,6 +3,7 @@
3
3
  require_relative "attributes/attribute_predicate"
4
4
  require_relative "attributes/delegation"
5
5
  require_relative "attributes/querying"
6
+ require_relative "exceptions"
6
7
 
7
8
  module ActiveRecordCompose
8
9
  # Provides attribute-related functionality for use within ActiveRecordCompose::Model.
@@ -127,7 +128,11 @@ module ActiveRecordCompose
127
128
  #
128
129
  # @see #attributes
129
130
  # @return [Array<String>] array of attribute name.
130
- def attribute_names = super + delegated_attributes.to_a.map { _1.attribute_name }
131
+ def attribute_names
132
+ _require_attributes_initialized do
133
+ super + delegated_attributes.to_a.map { _1.attribute_name }
134
+ end
135
+ end
131
136
 
132
137
  # Returns a hash with the attribute name as key and the attribute value as value.
133
138
  # Attributes declared with {.delegate_attribute} are also merged.
@@ -155,7 +160,24 @@ module ActiveRecordCompose
155
160
  #
156
161
  # @return [Hash<String, Object>] hash with the attribute name as key and the attribute value as value.
157
162
  def attributes
158
- super.merge(*delegated_attributes.to_a.map { _1.attribute_hash(self) })
163
+ _require_attributes_initialized do
164
+ super.merge(*delegated_attributes.to_a.map { _1.attribute_hash(self) })
165
+ end
166
+ end
167
+
168
+ private
169
+
170
+ def _write_attribute(...) = _require_attributes_initialized { super } # steep:ignore
171
+
172
+ def attribute(...) = _require_attributes_initialized { super }
173
+
174
+ def _require_attributes_initialized
175
+ unless @attributes
176
+ raise ActiveRecordCompose::UninitializedAttribute,
177
+ "No attributes have been set. Is proper initialization performed, such as calling `super` in `initialize`?"
178
+ end
179
+
180
+ yield
159
181
  end
160
182
  end
161
183
  end
@@ -26,4 +26,19 @@ module ActiveRecordCompose
26
26
  # outer.save #=> raises ActiveRecordCompose::CircularReferenceDetected
27
27
  #
28
28
  class CircularReferenceDetected < StandardError; end
29
+
30
+ # Occurs when accessing Attributes without initializing it.
31
+ #
32
+ # @example
33
+ # class Model < ActiveRecordCompose::Model
34
+ # def initialize
35
+ # # Intentionally not calling super...
36
+ # end
37
+ #
38
+ # attribute :foo
39
+ # end
40
+ # model = Model.new
41
+ # model.foo = 1 #=> raises ActiveRecordCompose::UninitializedAttribute
42
+ #
43
+ class UninitializedAttribute < StandardError; end
29
44
  end
@@ -113,7 +113,7 @@ module ActiveRecordCompose
113
113
  ensure_finalize = !connection.transaction_open?
114
114
 
115
115
  connection.transaction do
116
- connection.add_transaction_record(self, ensure_finalize || has_transactional_callbacks?) # steep:ignore
116
+ connection.add_transaction_record(self, ensure_finalize || has_transactional_callbacks?)
117
117
 
118
118
  yield.tap { raise ActiveRecord::Rollback unless _1 }
119
119
  end || false
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "composed_collection"
4
+ require_relative "exceptions"
4
5
 
5
6
  module ActiveRecordCompose
6
7
  using ComposedCollection::PackagePrivate
@@ -44,7 +45,7 @@ module ActiveRecordCompose
44
45
  # Returns the `ActiveModel::Errors` object that holds all information about attribute error messages.
45
46
  #
46
47
  # The `ActiveModel::Base` implementation itself,
47
- # but also aggregates error information for objects stored in {#models} when validation is performed.
48
+ # but also aggregates error information for objects stored in {ActiveRecordCompose::Model#models} when validation is performed.
48
49
  #
49
50
  # class Account < ActiveRecord::Base
50
51
  # validates :name, :email, presence: true
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordCompose
4
- VERSION = "1.2.0"
4
+ VERSION = "1.2.1"
5
5
  end
@@ -11,6 +11,9 @@ module ActiveRecordCompose
11
11
 
12
12
  @attributes: untyped
13
13
 
14
+ private
15
+ def _require_attributes_initialized: [T] () { () -> T } -> T
16
+
14
17
  class AttributePredicate
15
18
  def initialize: (untyped value) -> void
16
19
  def call: -> bool
@@ -98,4 +98,7 @@ module ActiveRecordCompose
98
98
  class Railtie < Rails::Railtie
99
99
  extend Rails::Initializable::ClassMethods
100
100
  end
101
+
102
+ class UninitializedAttribute < StandardError
103
+ end
101
104
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_compose
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - hamajyotan