cattri 0.1.3 → 0.2.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 +4 -4
- data/.github/workflows/main.yml +34 -0
- data/.gitignore +72 -0
- data/.rubocop.yml +6 -3
- data/CHANGELOG.md +41 -0
- data/Gemfile +12 -0
- data/README.md +163 -151
- data/Steepfile +6 -0
- data/bin/console +33 -0
- data/bin/setup +8 -0
- data/cattri.gemspec +5 -5
- data/lib/cattri/attribute.rb +119 -155
- data/lib/cattri/attribute_compiler.rb +104 -0
- data/lib/cattri/attribute_options.rb +183 -0
- data/lib/cattri/attribute_registry.rb +155 -0
- data/lib/cattri/context.rb +124 -106
- data/lib/cattri/context_registry.rb +36 -0
- data/lib/cattri/deferred_attributes.rb +73 -0
- data/lib/cattri/dsl.rb +54 -0
- data/lib/cattri/error.rb +17 -90
- data/lib/cattri/inheritance.rb +35 -0
- data/lib/cattri/initializer_patch.rb +37 -0
- data/lib/cattri/internal_store.rb +104 -0
- data/lib/cattri/introspection.rb +56 -49
- data/lib/cattri/version.rb +3 -1
- data/lib/cattri.rb +38 -99
- data/sig/lib/cattri/attribute.rbs +105 -0
- data/sig/lib/cattri/attribute_compiler.rbs +61 -0
- data/sig/lib/cattri/attribute_options.rbs +150 -0
- data/sig/lib/cattri/attribute_registry.rbs +95 -0
- data/sig/lib/cattri/context.rbs +130 -0
- data/sig/lib/cattri/context_registry.rbs +31 -0
- data/sig/lib/cattri/deferred_attributes.rbs +53 -0
- data/sig/lib/cattri/dsl.rbs +55 -0
- data/sig/lib/cattri/error.rbs +28 -0
- data/sig/lib/cattri/inheritance.rbs +21 -0
- data/sig/lib/cattri/initializer_patch.rbs +26 -0
- data/sig/lib/cattri/internal_store.rbs +75 -0
- data/sig/lib/cattri/introspection.rbs +61 -0
- data/sig/lib/cattri/types.rbs +19 -0
- data/sig/lib/cattri/visibility.rbs +55 -0
- data/sig/lib/cattri.rbs +37 -0
- data/spec/cattri/attribute_compiler_spec.rb +179 -0
- data/spec/cattri/attribute_options_spec.rb +267 -0
- data/spec/cattri/attribute_registry_spec.rb +257 -0
- data/spec/cattri/attribute_spec.rb +297 -0
- data/spec/cattri/context_registry_spec.rb +45 -0
- data/spec/cattri/context_spec.rb +346 -0
- data/spec/cattri/deferred_attrributes_spec.rb +117 -0
- data/spec/cattri/dsl_spec.rb +69 -0
- data/spec/cattri/error_spec.rb +37 -0
- data/spec/cattri/inheritance_spec.rb +60 -0
- data/spec/cattri/initializer_patch_spec.rb +35 -0
- data/spec/cattri/internal_store_spec.rb +139 -0
- data/spec/cattri/introspection_spec.rb +90 -0
- data/spec/cattri/visibility_spec.rb +68 -0
- data/spec/cattri_spec.rb +54 -0
- data/spec/simplecov_helper.rb +21 -0
- data/spec/spec_helper.rb +16 -0
- metadata +79 -6
- data/lib/cattri/attribute_definer.rb +0 -143
- data/lib/cattri/class_attributes.rb +0 -277
- data/lib/cattri/instance_attributes.rb +0 -276
- data/sig/cattri.rbs +0 -4
@@ -0,0 +1,267 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe Cattri::AttributeOptions do
|
6
|
+
describe ".validate_expose!" do
|
7
|
+
subject(:instance) { described_class.new(:attr) }
|
8
|
+
|
9
|
+
it "returns the valid option provided" do
|
10
|
+
expect(described_class.validate_expose!(:read)).to eq(:read)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "returns the valid option provided from a string" do
|
14
|
+
expect(described_class.validate_expose!("read")).to eq(:read)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "raises on an invalid option" do
|
18
|
+
expect do
|
19
|
+
described_class.validate_expose!(:invalid)
|
20
|
+
end.to raise_error(Cattri::AttributeError, /Invalid expose option `:invalid`/)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe ".validate_visibility!" do
|
25
|
+
subject(:instance) { described_class.new(:attr) }
|
26
|
+
|
27
|
+
it "returns the valid option provided" do
|
28
|
+
expect(described_class.validate_visibility!(:public)).to eq(:public)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "returns the valid option provided from a string" do
|
32
|
+
expect(described_class.validate_visibility!("public")).to eq(:public)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "raises on an invalid option" do
|
36
|
+
expect do
|
37
|
+
described_class.validate_visibility!(:invalid)
|
38
|
+
end.to raise_error(Cattri::AttributeError, /Invalid visibility `:invalid`/)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#initialize" do
|
43
|
+
let(:name) { :my_attribute }
|
44
|
+
|
45
|
+
it "sets @name to symbolized name" do
|
46
|
+
instance = described_class.new(name)
|
47
|
+
expect(instance.instance_variable_get(:@name)).to eq(:my_attribute)
|
48
|
+
end
|
49
|
+
|
50
|
+
context "with default options" do
|
51
|
+
subject(:instance) { described_class.new(name) }
|
52
|
+
|
53
|
+
it "sets @ivar to normalized default" do
|
54
|
+
expect(instance.instance_variable_get(:@ivar)).to eq(:@my_attribute)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "sets @final to false" do
|
58
|
+
expect(instance.instance_variable_get(:@final)).to be(false)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "sets @scope to :instance" do
|
62
|
+
expect(instance.instance_variable_get(:@scope)).to be(:instance)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "sets @predicate to false" do
|
66
|
+
expect(instance.instance_variable_get(:@predicate)).to be(false)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "sets @default using normalize_default" do
|
70
|
+
expect(instance.instance_variable_get(:@default).call).to eq(nil)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "sets @transformer using normalize_transformer" do
|
74
|
+
transformer = instance.instance_variable_get(:@transformer)
|
75
|
+
expect(transformer).to respond_to(:call)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "sets @expose using validate_expose!" do
|
79
|
+
expect(instance.instance_variable_get(:@expose)).to eq(:read_write)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "sets @visibility using validate_visibility!" do
|
83
|
+
expect(instance.instance_variable_get(:@visibility)).to eq(:public)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "freezes the instance" do
|
87
|
+
expect(instance).to be_frozen
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "with provided options" do
|
92
|
+
let(:ivar) { :custom_ivar }
|
93
|
+
let(:final) { true }
|
94
|
+
let(:scope) { :class }
|
95
|
+
let(:predicate) { true }
|
96
|
+
let(:default) { :custom_default }
|
97
|
+
let(:transformer) { ->(val) { val } }
|
98
|
+
let(:expose) { :read }
|
99
|
+
let(:visibility) { :private }
|
100
|
+
|
101
|
+
subject(:instance) do
|
102
|
+
described_class.new(
|
103
|
+
name,
|
104
|
+
ivar: ivar,
|
105
|
+
final: final,
|
106
|
+
scope: scope,
|
107
|
+
predicate: predicate,
|
108
|
+
default: default,
|
109
|
+
transformer: transformer,
|
110
|
+
expose: expose,
|
111
|
+
visibility: visibility
|
112
|
+
)
|
113
|
+
end
|
114
|
+
|
115
|
+
it "assigns the custom ivar" do
|
116
|
+
expect(instance.instance_variable_get(:@ivar)).to eq(:@custom_ivar)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "assigns the custom final value" do
|
120
|
+
expect(instance.instance_variable_get(:@final)).to eq(true)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "assigns the custom scope value" do
|
124
|
+
expect(instance.instance_variable_get(:@scope)).to eq(:class)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "assigns the custom predicate value" do
|
128
|
+
expect(instance.instance_variable_get(:@predicate)).to eq(true)
|
129
|
+
end
|
130
|
+
|
131
|
+
it "assigns the custom default value" do
|
132
|
+
expect(instance.instance_variable_get(:@default).call).to eq(:custom_default)
|
133
|
+
end
|
134
|
+
|
135
|
+
it "assigns the custom transformer" do
|
136
|
+
expect(instance.instance_variable_get(:@transformer)).to eq(transformer)
|
137
|
+
end
|
138
|
+
|
139
|
+
it "assigns the custom expose value" do
|
140
|
+
expect(instance.instance_variable_get(:@expose)).to eq(:read)
|
141
|
+
end
|
142
|
+
|
143
|
+
it "assigns the custom visibility value" do
|
144
|
+
expect(instance.instance_variable_get(:@visibility)).to eq(:private)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe "#[]" do
|
150
|
+
subject(:instance) { described_class.new(:attr) }
|
151
|
+
|
152
|
+
it "returns for known options" do
|
153
|
+
expect(instance[:name]).to eq(:attr)
|
154
|
+
end
|
155
|
+
|
156
|
+
it "returns nil for unknown options" do
|
157
|
+
expect(instance[:unknown]).to be_nil
|
158
|
+
end
|
159
|
+
|
160
|
+
it "converts the key provided to a symbol" do
|
161
|
+
expect(instance["ivar"]).to eq(:@attr)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe "#to_h" do
|
166
|
+
it "returns a hash representation of the options" do
|
167
|
+
instance = described_class.new(:attr, ivar: :custom_ivar)
|
168
|
+
hash = instance.to_h
|
169
|
+
|
170
|
+
expect(hash).to be_frozen
|
171
|
+
expect(hash).to include(
|
172
|
+
name: :attr,
|
173
|
+
ivar: :@custom_ivar,
|
174
|
+
final: false,
|
175
|
+
scope: :instance,
|
176
|
+
predicate: false,
|
177
|
+
expose: :read_write,
|
178
|
+
visibility: :public
|
179
|
+
)
|
180
|
+
|
181
|
+
expect(hash[:default].call).to be_nil
|
182
|
+
expect(hash[:transformer].call(123)).to be(123)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe "#normalize_ivar" do
|
187
|
+
subject(:instance) { described_class.new(:attr) }
|
188
|
+
|
189
|
+
[
|
190
|
+
[nil, :@attr],
|
191
|
+
%i[@custom @custom],
|
192
|
+
["@_custom", :@_custom],
|
193
|
+
%i[@__custom @__custom]
|
194
|
+
].each do |(ivar, expected)|
|
195
|
+
it "returns #{expected.inspect} for '#{ivar}'" do
|
196
|
+
expect(instance.send(:normalize_ivar, ivar)).to eq(expected)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
describe "#normalize_default" do
|
202
|
+
subject(:instance) { described_class.new(:attr) }
|
203
|
+
|
204
|
+
it "returns existing callable unchanged" do
|
205
|
+
fn = -> { :ok }
|
206
|
+
default = instance.send(:normalize_default, fn)
|
207
|
+
|
208
|
+
expect(default).to be_a(Proc)
|
209
|
+
expect(default.call).to eq(fn.call)
|
210
|
+
end
|
211
|
+
|
212
|
+
it "wraps immutable value in lambda" do
|
213
|
+
default = instance.send(:normalize_default, :sym)
|
214
|
+
|
215
|
+
expect(default).to be_a(Proc)
|
216
|
+
expect(default.call).to eq(:sym)
|
217
|
+
end
|
218
|
+
|
219
|
+
it "wraps mutable values and duplicates them" do
|
220
|
+
default = instance.send(:normalize_default, [1, 2])
|
221
|
+
v1 = default.call
|
222
|
+
v2 = default.call
|
223
|
+
|
224
|
+
expect(v1).to eq([1, 2])
|
225
|
+
expect(v1).not_to be(v2)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
describe "#normalize_transformer" do
|
230
|
+
subject(:instance) { described_class.new(:attr) }
|
231
|
+
|
232
|
+
it "returns kwargs if no positional args provided" do
|
233
|
+
expect(instance.transformer.call(a: 2)).to eq({ a: 2 })
|
234
|
+
end
|
235
|
+
|
236
|
+
it "returns single value if one positional arg provided" do
|
237
|
+
expect(instance.transformer.call("only")).to eq("only")
|
238
|
+
end
|
239
|
+
|
240
|
+
it "returns all positional args if multiple provided without kwargs" do
|
241
|
+
expect(instance.transformer.call(1, 2, 3)).to eq([1, 2, 3])
|
242
|
+
end
|
243
|
+
|
244
|
+
it "returns positional args and kwargs" do
|
245
|
+
expect(instance.transformer.call(1, a: 2)).to eq([1, { a: 2 }])
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
describe "#validate_scope!" do
|
250
|
+
subject(:instance) { described_class.new(:attr) }
|
251
|
+
|
252
|
+
[
|
253
|
+
[nil, :instance],
|
254
|
+
%i[instance instance],
|
255
|
+
%i[class class]
|
256
|
+
].each do |(scope, expected)|
|
257
|
+
it "returns :#{expected} when provided :#{scope}" do
|
258
|
+
expect(instance.send(:validate_scope!, scope)).to eq(expected)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
it "raises an error when provided an unknown scope" do
|
263
|
+
expect { instance.send(:validate_scope!, :invalid) }
|
264
|
+
.to raise_error(Cattri::AttributeError, /Invalid scope `:invalid`/)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
@@ -0,0 +1,257 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe Cattri::AttributeRegistry do
|
6
|
+
let(:parent_klass) do
|
7
|
+
Class.new do
|
8
|
+
include Cattri
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:klass) do
|
13
|
+
Class.new(parent_klass) do
|
14
|
+
include Cattri
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:context) { Cattri::Context.new(klass) }
|
19
|
+
let(:defer) { false }
|
20
|
+
|
21
|
+
subject(:registry) { Cattri::AttributeRegistry.new(context) }
|
22
|
+
|
23
|
+
before do
|
24
|
+
allow(context).to receive(:defer_definitions?).and_return(defer)
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#initialize" do
|
28
|
+
it "instantiates an AttributeRegistry instance with context" do
|
29
|
+
expect(registry).to be_a(Cattri::AttributeRegistry)
|
30
|
+
expect(registry.context).to eq(context)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#registered_attributes" do
|
35
|
+
it "returns a Hash" do
|
36
|
+
expect(context.send(:__cattri_defined_methods)).to be_a(Hash)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "returns the defined attributes" do
|
40
|
+
registry.define_attribute(:enabled, true)
|
41
|
+
|
42
|
+
expect(registry.registered_attributes).to be_a(Hash)
|
43
|
+
expect(registry.registered_attributes[:enabled]).to be_a(Cattri::Attribute)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#defined_attributes" do
|
48
|
+
let(:parent_attribute) { Cattri::Attribute.new(:parent, defined_in: context.target) }
|
49
|
+
let(:attribute) { Cattri::Attribute.new(:enabled, defined_in: context.target) }
|
50
|
+
|
51
|
+
before do
|
52
|
+
parent_klass.send(:attribute_registry)
|
53
|
+
.instance_variable_set(
|
54
|
+
:@__cattri_registered_attributes,
|
55
|
+
{ parent_attribute.name => parent_attribute }
|
56
|
+
)
|
57
|
+
|
58
|
+
registry.instance_variable_set(:@__cattri_registered_attributes, { attribute.name => attribute })
|
59
|
+
end
|
60
|
+
|
61
|
+
it "returns only instance-level attributes by default" do
|
62
|
+
expect(registry.defined_attributes.values).to eq([attribute])
|
63
|
+
end
|
64
|
+
|
65
|
+
it "returns all attributes, instance- and ancestor-level, when setting with_ancestors: true" do
|
66
|
+
expect(registry.defined_attributes(with_ancestors: true).values).to eq([parent_attribute, attribute])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#fetch_attribute" do
|
71
|
+
it "fetches a defined attribute" do
|
72
|
+
registry.define_attribute(:enabled, true)
|
73
|
+
attribute = registry.fetch_attribute(:enabled)
|
74
|
+
|
75
|
+
expect(attribute).to be_a(Cattri::Attribute)
|
76
|
+
expect(attribute.name).to eq(:enabled)
|
77
|
+
expect(attribute.defined_in).to eq(context.target)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "returns nil on undefined attributes" do
|
81
|
+
attribute = registry.fetch_attribute(:undefined)
|
82
|
+
expect(attribute).to be_nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "#fetch_attribute!" do
|
87
|
+
it "fetches a defined attribute" do
|
88
|
+
registry.define_attribute(:enabled, true)
|
89
|
+
attribute = registry.fetch_attribute!(:enabled)
|
90
|
+
|
91
|
+
expect(attribute).to be_a(Cattri::Attribute)
|
92
|
+
expect(attribute.name).to eq(:enabled)
|
93
|
+
expect(attribute.defined_in).to eq(context.target)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "raises an error on undefined attributes" do
|
97
|
+
expect { registry.fetch_attribute!(:undefined) }
|
98
|
+
.to raise_error(Cattri::AttributeError, "Attribute :undefined has not been defined")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "#define_attribute" do
|
103
|
+
it "defines an attribute" do
|
104
|
+
registry.define_attribute(:enabled, true)
|
105
|
+
attribute = registry.fetch_attribute(:enabled)
|
106
|
+
|
107
|
+
expect(attribute).to be_a(Cattri::Attribute)
|
108
|
+
expect(attribute.name).to eq(:enabled)
|
109
|
+
expect(attribute.defined_in).to eq(context.target)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "raises an error on duplicate attribute definitions" do
|
113
|
+
registry.define_attribute(:enabled, true)
|
114
|
+
|
115
|
+
expect { registry.define_attribute(:enabled, true) }
|
116
|
+
.to raise_error(Cattri::AttributeError, "Attribute :enabled has already been defined")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "#copy_attributes_to" do
|
121
|
+
let(:target_klass) do
|
122
|
+
Class.new do
|
123
|
+
include Cattri
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
let!(:target_registry) { target_klass.send(:attribute_registry) }
|
128
|
+
let(:target_context) { Cattri::Context.new(target_klass) }
|
129
|
+
|
130
|
+
it "copies final class attributes and sets their values" do
|
131
|
+
registry.define_attribute(:enabled, "yes", scope: :class, final: true)
|
132
|
+
registry.context.target.cattri_variable_set(:@enabled, "yes")
|
133
|
+
|
134
|
+
registry.copy_attributes_to(target_context)
|
135
|
+
|
136
|
+
expect(target_klass.cattri_variable_get(:@enabled)).to eq("yes")
|
137
|
+
expect(target_registry.fetch_attribute(:enabled)).to be_a(Cattri::Attribute)
|
138
|
+
end
|
139
|
+
|
140
|
+
it "skips non-final or instance-level attributes" do
|
141
|
+
registry.define_attribute(:skipped1, "val", scope: :instance, final: true)
|
142
|
+
registry.define_attribute(:skipped2, "val", scope: :class, final: false)
|
143
|
+
|
144
|
+
expect do
|
145
|
+
registry.copy_attributes_to(target_context)
|
146
|
+
end.not_to(change { target_klass.send(:attribute_registry).defined_attributes })
|
147
|
+
end
|
148
|
+
|
149
|
+
it "restores original context after copying" do
|
150
|
+
original_context = registry.context
|
151
|
+
registry.copy_attributes_to(target_context)
|
152
|
+
|
153
|
+
expect(registry.context).to equal(original_context)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe "#validate_unique!" do
|
158
|
+
context "when no attributes are registered" do
|
159
|
+
it "returns without raising" do
|
160
|
+
expect { registry.send(:validate_unique!, :enabled) }.not_to raise_error
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context "when attribute exists in @__cattri_registered_attributes" do
|
165
|
+
before do
|
166
|
+
registry.define_attribute(:enabled, true)
|
167
|
+
end
|
168
|
+
|
169
|
+
it "raises an error on duplicate attribute definitions" do
|
170
|
+
expect { registry.send(:validate_unique!, :enabled) }
|
171
|
+
.to raise_error(Cattri::AttributeError, "Attribute :enabled has already been defined")
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
context "when @__cattri_registered_attributes is defined but attribute does not exist" do
|
176
|
+
before do
|
177
|
+
registry.define_attribute(:existing, true)
|
178
|
+
end
|
179
|
+
|
180
|
+
it "does not raise error for a different attribute" do
|
181
|
+
expect { registry.send(:validate_unique!, :something_else) }.not_to raise_error
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe "#register_attribute" do
|
187
|
+
let(:attribute) { Cattri::Attribute.new(:enabled, defined_in: context.target) }
|
188
|
+
|
189
|
+
before do
|
190
|
+
allow(registry).to receive(:defer_definition).and_return(nil)
|
191
|
+
allow(registry).to receive(:apply_definition!).and_return(nil)
|
192
|
+
end
|
193
|
+
|
194
|
+
it "registers the attribute in __defined_attributes" do
|
195
|
+
expect(registry.defined_attributes).to be_empty
|
196
|
+
|
197
|
+
registry.send(:register_attribute, attribute)
|
198
|
+
|
199
|
+
expect(registry.fetch_attribute(attribute.name)).to eq(attribute)
|
200
|
+
end
|
201
|
+
|
202
|
+
context "when attributes are not deferred" do
|
203
|
+
it "applies the attribute definition" do
|
204
|
+
registry.send(:register_attribute, attribute)
|
205
|
+
|
206
|
+
expect(registry).not_to have_received(:defer_definition)
|
207
|
+
expect(registry).to have_received(:apply_definition!).with(attribute)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
context "when attributes are deferred" do
|
212
|
+
let(:defer) { true }
|
213
|
+
|
214
|
+
it "defers the attribute definition" do
|
215
|
+
registry.send(:register_attribute, attribute)
|
216
|
+
|
217
|
+
expect(registry).to have_received(:defer_definition).with(attribute)
|
218
|
+
expect(registry).not_to have_received(:apply_definition!)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
describe "#defer_definition" do
|
224
|
+
let(:attribute) { Cattri::Attribute.new(:enabled, defined_in: context.target) }
|
225
|
+
|
226
|
+
before do
|
227
|
+
allow(context).to receive(:ensure_deferred_support!)
|
228
|
+
allow(context.target).to receive(:defer_attribute)
|
229
|
+
end
|
230
|
+
|
231
|
+
it "defers the attribute definition to context" do
|
232
|
+
registry.send(:defer_definition, attribute)
|
233
|
+
|
234
|
+
expect(context).to have_received(:ensure_deferred_support!)
|
235
|
+
expect(context.target).to have_received(:defer_attribute).with(attribute)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
describe "#apply_definition!" do
|
240
|
+
let(:attribute) { Cattri::Attribute.new(:enabled, defined_in: context.target) }
|
241
|
+
|
242
|
+
it "calls Cattri::AttributeCompiler.define_accessor" do
|
243
|
+
allow(Cattri::AttributeCompiler).to receive(:define_accessor)
|
244
|
+
|
245
|
+
registry.send(:apply_definition!, attribute)
|
246
|
+
|
247
|
+
expect(Cattri::AttributeCompiler).to have_received(:define_accessor).with(attribute, context)
|
248
|
+
end
|
249
|
+
|
250
|
+
it "raises a Cattri::AttributeError when definition fails" do
|
251
|
+
allow(Cattri::AttributeCompiler).to receive(:define_accessor).and_raise(TypeError, "boom")
|
252
|
+
|
253
|
+
expect { registry.send(:apply_definition!, attribute) }
|
254
|
+
.to raise_error(Cattri::AttributeError, "Attribute #{attribute.name} could not be defined. Error: boom")
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|