declare_schema 4.0.0 → 4.0.1.colin.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: 929f250672103aa42777cb918340e5281d100fdaf19cedb5f7aaa96504e66a8c
4
- data.tar.gz: 377a3f2aaae4fa4b6294a4c4a9edd672a8626a505be6a07e06b953a28cb78ffc
3
+ metadata.gz: '09079a0e965a85eba22eade7bc713fcacf35d4fd6f874e9d8b41eb36f8e6901e'
4
+ data.tar.gz: b7f0fa06ec76c298d2b9248ef10b6a1010d36bf2edb3c3a667f57790d98bb6b5
5
5
  SHA512:
6
- metadata.gz: 8d05a979184d7e93041a462b1269b7d71a6a675f37b8914e27728db0f2d28878d0436cc10eba4bd107f1cb57355c1321147659842367e9231ebb32eec5d14b79
7
- data.tar.gz: 3c361cb45d57de4c4aac717155b354f34d0c654b4881c29742a9f611cf7ca8efd0c9297e70433dd7b2df4df70751bf3c1d0463824ccc8eb77f5f9a1d836e711d
6
+ metadata.gz: 5cb66b34c4d70e0673106ee94fec485e7e5eec25e34b75a64a94e0d3fea9f6f822157dadc8b662ba57af9878c3686807be1ab97b01ce89b8de3b3354c2dfd3a9
7
+ data.tar.gz: 6359500ff616315a48dd3921a9fac1b9d51e3a24c1dfb1b761160b048da1ef8222d036f60b6e5d2d5e5b74de5f12e44c2db70717a985257be420d78fcbe7f64d
data/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@ Inspired by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
4
4
 
5
5
  Note: this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [4.0.1] - 2026-05-06
8
+ ### Fixed
9
+ - `DeferredFieldSpec` (the lazy stand-in introduced in 4.0.0 for `belongs_to`
10
+ foreign-key field specs) now transparently delegates the `FieldSpec` read
11
+ API (`.options`, `.type`, `.limit`, `.null`, etc.) to its memoized resolved
12
+ spec. Application code that reads `Model.field_specs[name].options` at
13
+ runtime would raise `NoMethodError: undefined method 'options' for an
14
+ instance of DeclareSchema::Model::DeferredFieldSpec`.
15
+
7
16
  ## [4.0.0] - 2026-05-05
8
17
  ### Added
9
18
  - Generalized `belongs_to` foreign keys to always match the primary key they point at, including
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- declare_schema (4.0.0)
4
+ declare_schema (4.0.1.colin.1)
5
5
  rails (>= 7.0)
6
6
 
7
7
  GEM
@@ -12,7 +12,7 @@ module DeclareSchema
12
12
  # The migrator calls {#resolve} on every value in `field_specs` at the start
13
13
  # of migration generation (see `Migrator#generate`); for plain {FieldSpec}s
14
14
  # `#resolve` is a no-op returning self, while for instances of this class it
15
- # invokes the block (memoized) with the default spec and returns the produced
15
+ # invokes the resolver block with the default spec and returns the produced
16
16
  # {FieldSpec}.
17
17
  class DeferredFieldSpec
18
18
  # @param default_spec [FieldSpec] the eager placeholder spec
@@ -24,14 +24,26 @@ module DeclareSchema
24
24
  @resolver = resolver
25
25
  end
26
26
 
27
- # Invoke the resolver block with the default spec and return its result.
28
- # The migrator's `transform_values!(&:resolve)` swaps this DeferredFieldSpec
29
- # out of `field_specs` for the produced FieldSpec, so resolve is called
30
- # exactly once per instance in production -- no memoization needed.
27
+ # Resolve and memoize the produced FieldSpec. Memoization matters because
28
+ # application code can hit several FieldSpec accessors per request (e.g.
29
+ # ModelReport reads `.options`, ApplicationModel reads `.limit`); without
30
+ # it each one would re-run the resolver and re-touch `reflection.klass`.
31
31
  #
32
32
  # @return [FieldSpec]
33
33
  def resolve
34
- @resolver.call(@default_spec)
34
+ @resolved ||= @resolver.call(@default_spec)
35
+ end
36
+
37
+ def respond_to_missing?(name, include_private = false)
38
+ resolve.respond_to?(name, include_private) || super
39
+ end
40
+
41
+ def method_missing(name, *args, **kwargs, &)
42
+ if resolve.respond_to?(name)
43
+ resolve.public_send(name, *args, **kwargs, &)
44
+ else
45
+ super
46
+ end
35
47
  end
36
48
  end
37
49
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "4.0.0"
4
+ VERSION = "4.0.1.colin.1"
5
5
  end
@@ -24,5 +24,61 @@ RSpec.describe DeclareSchema::Model::DeferredFieldSpec do
24
24
  expect(deferred.resolve).to equal(mirrored_spec)
25
25
  expect(captured).to equal(default_spec)
26
26
  end
27
+
28
+ it 'memoizes the resolver result so repeated calls invoke it only once' do
29
+ call_count = 0
30
+ deferred = described_class.new(default_spec) do |_|
31
+ call_count += 1
32
+ mirrored_spec
33
+ end
34
+
35
+ 3.times { deferred.resolve }
36
+
37
+ expect(call_count).to eq(1)
38
+ end
39
+ end
40
+
41
+ # Application code (e.g. ModelReport reading `field_specs[name].options[:rr_report_options]`)
42
+ # reads from `field_specs` without first calling `.resolve`. The deferred spec must
43
+ # therefore quack like the resolved FieldSpec; otherwise we get NoMethodError at runtime
44
+ # in apps that read FieldSpec attributes, AND we'd return a wrong default-typed answer
45
+ # (e.g. :bigint when the parent's PK is actually :integer or :binary).
46
+ describe 'FieldSpec API delegation' do
47
+ let(:deferred) { described_class.new(default_spec) { mirrored_spec } }
48
+
49
+ it 'delegates #options to the resolved spec (parent-mirrored, not default)' do
50
+ expect(deferred.options).to eq(mirrored_spec.options)
51
+ end
52
+
53
+ it 'delegates #type to the resolved spec' do
54
+ expect(deferred.type).to eq(:string)
55
+ end
56
+
57
+ it 'delegates #limit to the resolved spec' do
58
+ expect(deferred.limit).to eq(36)
59
+ end
60
+
61
+ it 'delegates #null to the resolved spec' do
62
+ expect(deferred.null).to eq(false)
63
+ end
64
+
65
+ it 'reports respond_to? for delegated methods' do
66
+ expect(deferred).to respond_to(:options, :type, :limit, :null, :name, :position)
67
+ end
68
+
69
+ it 'invokes the resolver only once across many delegated reads' do
70
+ call_count = 0
71
+ deferred = described_class.new(default_spec) do |_|
72
+ call_count += 1
73
+ mirrored_spec
74
+ end
75
+
76
+ deferred.options
77
+ deferred.type
78
+ deferred.limit
79
+ deferred.null
80
+
81
+ expect(call_count).to eq(1)
82
+ end
27
83
  end
28
84
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: declare_schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.0.1.colin.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Invoca Development adapted from hobo_fields by Tom Locke