Skip to content

Commit 690bb4d

Browse files
committed
Map the location of the yard documentation and source code as much as possible.
We had previously consolidated everything into the model class, but we wanted to define it in a natural way in each location. This was a hack caused by steep's inability to resolve the issue of `class_methods`, but by reconsidering our stance toward Steep, we changed it to a more natural way of writing using `class_methods`.
1 parent fc2bfd2 commit 690bb4d

File tree

7 files changed

+271
-390
lines changed

7 files changed

+271
-390
lines changed

lib/active_record_compose/attributes.rb

Lines changed: 58 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
require_relative "attributes/querying"
66

77
module ActiveRecordCompose
8-
# @private
9-
#
108
# Provides attribute-related functionality for use within ActiveRecordCompose::Model.
119
#
1210
# This module allows you to define attributes on your composed model, including support
@@ -56,47 +54,32 @@ module Attributes
5654
include ActiveModel::Attributes
5755

5856
included do
57+
# @type self: Class
58+
5959
include Querying
6060

61-
# @type self: Class
6261
class_attribute :delegated_attributes, instance_writer: false
6362
end
6463

6564
# steep:ignore:start
6665

6766
class_methods do
68-
# Defines the reader and writer for the specified attribute.
69-
#
70-
# @example
71-
# class AccountRegistration < ActiveRecordCompose::Model
72-
# def initialize(account, attributes = {})
73-
# @account = account
74-
# super(attributes)
75-
# models.push(account)
76-
# end
77-
#
78-
# attribute :original_attribute, :string, default: "qux"
79-
# delegate_attribute :name, to: :account
80-
#
81-
# private
82-
#
83-
# attr_reader :account
84-
# end
85-
#
86-
# account = Account.new
87-
# account.name = "foo"
88-
#
89-
# registration = AccountRegistration.new(account)
90-
# registration.name # => "foo" (delegated)
91-
# registration.name? # => true (delegated attribute method + `?`)
92-
#
93-
# registration.name = "bar" # => updates account.name
94-
# account.name # => "bar"
95-
# account.name? # => true
67+
# Provides a method of attribute access to the encapsulated model.
9668
#
97-
# registration.attributes
98-
# # => { "original_attribute" => "qux", "name" => "bar" }
69+
# It provides a way to access the attributes of the model it encompasses,
70+
# allowing transparent access as if it had those attributes itself.
9971
#
72+
# @param [Array<Symbol, String>] attributes
73+
# attributes A variable-length list of attribute names to delegate.
74+
# @param [Symbol, String] to
75+
# The target object to which attributes are delegated (keyword argument).
76+
# @param [Boolean] allow_nil
77+
# allow_nil Whether to allow nil values. Defaults to false.
78+
# @example Basic usage
79+
# delegate_attribute :name, :email, to: :profile
80+
# @example Allowing nil
81+
# delegate_attribute :bio, to: :profile, allow_nil: true
82+
# @see Module#delegate for similar behavior in ActiveSupport
10083
def delegate_attribute(*attributes, to:, allow_nil: false)
10184
if to.start_with?("@")
10285
raise ArgumentError, "Instance variables cannot be specified in delegate to. (#{to})"
@@ -109,47 +92,68 @@ def delegate_attribute(*attributes, to:, allow_nil: false)
10992
end
11093

11194
# Returns a array of attribute name.
112-
# Attributes declared with `delegate_attribute` are also merged.
95+
# Attributes declared with {.delegate_attribute} are also merged.
11396
#
97+
# @see #attribute_names
11498
# @return [Array<String>] array of attribute name.
11599
def attribute_names = super + delegated_attributes.to_a.map { _1.attribute_name }
116100
end
117101

118102
# steep:ignore:end
119103

120104
# Returns a array of attribute name.
121-
# Attributes declared with `delegate_attribute` are also merged.
105+
# Attributes declared with {.delegate_attribute} are also merged.
122106
#
107+
# class Foo < ActiveRecordCompose::Base
108+
# def initialize(attributes = {})
109+
# @account = Account.new
110+
# super
111+
# end
112+
#
113+
# attribute :confirmation, :boolean, default: false # plain attribute
114+
# delegate_attribute :name, to: :account # delegated attribute
115+
#
116+
# private
117+
#
118+
# attr_reader :account
119+
# end
120+
#
121+
# Foo.attribute_names # Returns the merged state of plain and delegated attributes
122+
# # => ["confirmation" ,"name"]
123+
#
124+
# foo = Foo.new
125+
# foo.attribute_names # Similar behavior for instance method version
126+
# # => ["confirmation", "name"]
127+
#
128+
# @see #attributes
123129
# @return [Array<String>] array of attribute name.
124130
def attribute_names = super + delegated_attributes.to_a.map { _1.attribute_name }
125131

126132
# Returns a hash with the attribute name as key and the attribute value as value.
127-
# Attributes declared with `delegate_attribute` are also merged.
133+
# Attributes declared with {.delegate_attribute} are also merged.
128134
#
129-
# @return [Hash] hash with the attribute name as key and the attribute value as value.
130-
# @example
131-
# class AccountRegistration < ActiveRecordCompose::Model
132-
# def initialize(account, attributes = {})
133-
# @account = account
134-
# super(attributes)
135-
# models.push(account)
136-
# end
135+
# class Foo < ActiveRecordCompose::Base
136+
# def initialize(attributes = {})
137+
# @account = Account.new
138+
# super
139+
# end
137140
#
138-
# attribute :original_attribute, :string, default: "qux"
139-
# delegate_attribute :name, to: :account
141+
# attribute :confirmation, :boolean, default: false # plain attribute
142+
# delegate_attribute :name, to: :account # delegated attribute
140143
#
141-
# private
144+
# private
142145
#
143-
# attr_reader :account
144-
# end
145-
#
146-
# account = Account.new
147-
# account.name = "foo"
146+
# attr_reader :account
147+
# end
148148
#
149-
# registration = AccountRegistration.new(account)
149+
# foo = Foo.new
150+
# foo.name = "Alice"
151+
# foo.confirmation = true
150152
#
151-
# registration.attributes # => { "original_attribute" => "qux", "name" => "bar" }
153+
# foo.attributes # Returns the merged state of plain and delegated attributes
154+
# # => { "confirmation" => true, "name" => "Alice" }
152155
#
156+
# @return [Hash<String, Object>] hash with the attribute name as key and the attribute value as value.
153157
def attributes
154158
super.merge(*delegated_attributes.to_a.map { _1.attribute_hash(self) })
155159
end

lib/active_record_compose/callbacks.rb

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# frozen_string_literal: true
22

33
module ActiveRecordCompose
4-
# @private
5-
#
64
# Provides hooks into the life cycle of an ActiveRecordCompose model,
75
# allowing you to insert custom logic before or after changes to the object's state.
86
#
@@ -21,18 +19,48 @@ module Callbacks
2119
include ActiveModel::Validations::Callbacks
2220

2321
included do
22+
# @!method self.before_save(*args, &block)
23+
# Registers a callback to be called before a model is saved.
24+
25+
# @!method self.around_save(*args, &block)
26+
# Registers a callback to be called around the save of a model.
27+
28+
# @!method self.after_save(*args, &block)
29+
# Registers a callback to be called after a model is saved.
30+
2431
define_model_callbacks :save
32+
33+
# @!method self.before_create(*args, &block)
34+
# Registers a callback to be called before a model is created.
35+
36+
# @!method self.around_create(*args, &block)
37+
# Registers a callback to be called around the creation of a model.
38+
39+
# @!method self.after_create(*args, &block)
40+
# Registers a callback to be called after a model is created.
41+
2542
define_model_callbacks :create
43+
44+
# @!method self.before_update(*args, &block)
45+
# Registers a callback to be called before a model is updated.
46+
47+
# @!method self.around_update(*args, &block)
48+
# Registers a callback to be called around the update of a model.
49+
50+
# @!method self.after_update(*args, &block)
51+
# Registers a callback to be called after a update is updated.
2652
define_model_callbacks :update
2753
end
2854

2955
private
3056

57+
# @private
3158
# Evaluate while firing callbacks such as `before_save` `after_save`
3259
# before and after block evaluation.
3360
#
3461
def with_callbacks(&block) = run_callbacks(:save) { run_callbacks(callback_context, &block) }
3562

63+
# @private
3664
# Returns the symbol representing the callback context, which is `:create` if the record
3765
# is new, or `:update` if it has been persisted.
3866
#

lib/active_record_compose/inspectable.rb

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
require_relative "attributes"
55

66
module ActiveRecordCompose
7-
# @private
8-
#
97
# It provides #inspect behavior.
108
# It tries to replicate the inspect format provided by ActiveRecord as closely as possible.
119
#
@@ -41,6 +39,7 @@ module Inspectable
4139

4240
# steep:ignore:start
4341

42+
# @private
4443
FILTERED_MASK =
4544
Class.new(DelegateClass(::String)) do
4645
def pretty_print(pp)
@@ -58,6 +57,10 @@ def pretty_print(pp)
5857
# steep:ignore:start
5958

6059
class_methods do
60+
# Returns columns not to expose when invoking {#inspect}.
61+
#
62+
# @return [Array<Symbol>]
63+
# @see #inspect
6164
def filter_attributes
6265
if @filter_attributes.nil?
6366
superclass.filter_attributes
@@ -66,11 +69,16 @@ def filter_attributes
6669
end
6770
end
6871

72+
# Specify columns not to expose when invoking {#inspect}.
73+
#
74+
# @param [Array<Symbol>] value
75+
# @see #inspect
6976
def filter_attributes=(value)
7077
@inspection_filter = nil
7178
@filter_attributes = value
7279
end
7380

81+
# @private
7482
def inspection_filter
7583
if @filter_attributes.nil?
7684
superclass.inspection_filter
@@ -94,7 +102,36 @@ def inherited(subclass)
94102
# steep:ignore:end
95103

96104
# Returns a formatted string representation of the record's attributes.
105+
# It tries to replicate the inspect format provided by ActiveRecord as closely as possible.
106+
#
107+
# @example
108+
# class Model < ActiveRecordCompose::Model
109+
# def initialize(ar_model)
110+
# @ar_model = ar_model
111+
# super
112+
# end
113+
#
114+
# attribute :foo, :date, default: -> { Date.today }
115+
# delegate_attribute :bar, to: :ar_model
116+
#
117+
# private attr_reader :ar_model
118+
# end
119+
#
120+
# m = Model.new(ar_model)
121+
# m.inspect #=> #<Model:0x00007ff0fe75fe58 foo: "2025-11-14", bar: "bar">
122+
#
123+
# @example use {.filter_attributes}
124+
# class Model < ActiveRecordCompose::Model
125+
# self.filter_attributes += %i[foo]
126+
#
127+
# # ...
128+
# end
129+
#
130+
# m = Model.new(ar_model)
131+
# m.inspect #=> #<Model:0x00007ff0fe75fe58 foo: [FILTERED], bar: "bar">
97132
#
133+
# @return [String] formatted string representation of the record's attributes.
134+
# @see .filter_attributes
98135
def inspect
99136
inspection =
100137
if @attributes
@@ -130,6 +167,7 @@ def pretty_print(pp)
130167

131168
private
132169

170+
# @private
133171
def format_for_inspect(name, value)
134172
return value.inspect if value.nil?
135173

0 commit comments

Comments
 (0)