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,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe Cattri::DeferredAttributes do
|
6
|
+
def define_attribute(name, value, scope: :instance)
|
7
|
+
Cattri::Attribute.new(name, default: value, scope: scope, defined_in: klass)
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:klass) do
|
11
|
+
Class.new do
|
12
|
+
include Cattri
|
13
|
+
extend Cattri::DeferredAttributes
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:class_attribute) { define_attribute(:class_attr, "class", scope: :class) }
|
18
|
+
let(:instance_attribute) { define_attribute(:instance_attr, "instance") }
|
19
|
+
let(:context) { Cattri::Context.new(klass) }
|
20
|
+
|
21
|
+
describe "core methods" do
|
22
|
+
subject { klass }
|
23
|
+
|
24
|
+
before do
|
25
|
+
subject.defer_attribute(class_attribute)
|
26
|
+
subject.defer_attribute(instance_attribute)
|
27
|
+
end
|
28
|
+
|
29
|
+
describe ".extended" do
|
30
|
+
let(:base_module) { Module.new }
|
31
|
+
|
32
|
+
it "prepends Hook if not already present" do
|
33
|
+
expect(base_module.singleton_class.ancestors).not_to include(Cattri::DeferredAttributes::Hook)
|
34
|
+
|
35
|
+
described_class.extended(base_module)
|
36
|
+
|
37
|
+
expect(base_module.singleton_class.ancestors).to include(Cattri::DeferredAttributes::Hook)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "does not prepend Hook twice" do
|
41
|
+
described_class.extended(base_module)
|
42
|
+
original_ancestors = base_module.singleton_class.ancestors.dup
|
43
|
+
|
44
|
+
described_class.extended(base_module)
|
45
|
+
|
46
|
+
expect(base_module.singleton_class.ancestors).to eq(original_ancestors)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "defer_attribute" do
|
51
|
+
it "stores deferred attributes" do
|
52
|
+
deferred_attributes = subject.instance_variable_get(:@deferred_attributes)
|
53
|
+
|
54
|
+
expect(deferred_attributes).to include(
|
55
|
+
class_attribute.name => class_attribute,
|
56
|
+
instance_attribute.name => instance_attribute
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "apply_deferred_attributes" do
|
62
|
+
before do
|
63
|
+
allow(Cattri::Context).to receive(:new).and_return(context)
|
64
|
+
allow(Cattri::AttributeCompiler).to receive(:define_accessor)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "applies deferred attributes to the given target" do
|
68
|
+
subject.apply_deferred_attributes(klass)
|
69
|
+
|
70
|
+
expect(Cattri::AttributeCompiler).to have_received(:define_accessor).with(class_attribute, context)
|
71
|
+
expect(Cattri::AttributeCompiler).to have_received(:define_accessor).with(instance_attribute, context)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe Cattri::DeferredAttributes::Hook do
|
77
|
+
let(:klass) { Class.new }
|
78
|
+
|
79
|
+
let(:plain_module) do
|
80
|
+
Module.new.tap do |mod|
|
81
|
+
mod.singleton_class.prepend(Cattri::DeferredAttributes::Hook)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
let(:hook_module) do
|
86
|
+
Module.new do
|
87
|
+
extend Cattri::DeferredAttributes
|
88
|
+
|
89
|
+
def self.apply_deferred_attributes(_target)
|
90
|
+
@called = true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "with hooked module" do
|
96
|
+
it "triggers apply_deferred_attributes (include)" do
|
97
|
+
klass.include(hook_module)
|
98
|
+
expect(hook_module.instance_variable_get(:@called)).to eq(true)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "triggers apply_deferred_attributes (extend)" do
|
102
|
+
klass.extend(hook_module)
|
103
|
+
expect(hook_module.instance_variable_get(:@called)).to eq(true)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "without hooked module" do
|
108
|
+
it "does not call apply_deferred_attributes if not defined (include)" do
|
109
|
+
expect { klass.include(plain_module) }.not_to raise_error
|
110
|
+
end
|
111
|
+
|
112
|
+
it "does not call apply_deferred_attributes if not defined (extend)" do
|
113
|
+
expect { klass.extend(plain_module) }.not_to raise_error
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Cattri::Dsl do
|
4
|
+
let(:registry) { instance_double(Cattri::AttributeRegistry) }
|
5
|
+
|
6
|
+
let(:klass) do
|
7
|
+
Class.new do
|
8
|
+
extend Cattri::Dsl
|
9
|
+
|
10
|
+
def self.__cattri_visibility
|
11
|
+
:protected
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
before do
|
17
|
+
allow(klass).to receive(:attribute_registry).and_return(registry)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe ".cattri" do
|
21
|
+
it "delegates to attribute_registry.define_attribute with merged visibility" do
|
22
|
+
expect(registry).to receive(:define_attribute)
|
23
|
+
.with(:foo, 123, visibility: :protected)
|
24
|
+
|
25
|
+
klass.cattri(:foo, 123)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "merges user-provided options with current visibility" do
|
29
|
+
expect(registry).to receive(:define_attribute)
|
30
|
+
.with(:bar, nil, visibility: :protected, final: true)
|
31
|
+
|
32
|
+
klass.cattri(:bar, nil, final: true)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "passes through block if provided" do
|
36
|
+
expect(registry).to receive(:define_attribute) do |name, value, **opts, &blk|
|
37
|
+
expect(name).to eq(:lazy)
|
38
|
+
expect(value).to be_nil
|
39
|
+
expect(opts[:visibility]).to eq(:protected)
|
40
|
+
expect(blk.call).to eq(:computed)
|
41
|
+
end
|
42
|
+
|
43
|
+
klass.cattri(:lazy) { :computed }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe ".final_cattri" do
|
48
|
+
it "calls cattri with final: true merged into options" do
|
49
|
+
expect(klass).to receive(:cattri)
|
50
|
+
.with(:token, "xyz", hash_including(final: true))
|
51
|
+
|
52
|
+
klass.final_cattri(:token, "xyz")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "respects additional options" do
|
56
|
+
expect(klass).to receive(:cattri)
|
57
|
+
.with(:token, "xyz", hash_including(scope: :class, final: true))
|
58
|
+
|
59
|
+
klass.final_cattri(:token, "xyz", scope: :class)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "passes block through to cattri" do
|
63
|
+
block = proc { "lazy!" }
|
64
|
+
expect(klass).to receive(:cattri).with(:val, nil, hash_including(final: true), &block)
|
65
|
+
|
66
|
+
klass.final_cattri(:val, nil, &block)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe Cattri::Error do
|
6
|
+
describe ".new" do
|
7
|
+
it "inherits from StandardError" do
|
8
|
+
expect(described_class).to be < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
it "sets the message and custom backtrace" do
|
12
|
+
backtrace = ["custom/location.rb:42"]
|
13
|
+
error = described_class.new("oops", backtrace)
|
14
|
+
|
15
|
+
expect(error.message).to eq("oops")
|
16
|
+
expect(error.backtrace).to eq(backtrace)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "defaults to caller backtrace if none given" do
|
20
|
+
error = described_class.new("default trace")
|
21
|
+
expect(error.backtrace).to be_an(Array)
|
22
|
+
expect(error.backtrace.first).to include(__FILE__)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
RSpec.describe Cattri::AttributeError do
|
28
|
+
it "inherits from Cattri::Error" do
|
29
|
+
expect(described_class).to be < Cattri::Error
|
30
|
+
end
|
31
|
+
|
32
|
+
it "retains message and backtrace" do
|
33
|
+
error = described_class.new("bad attr", ["attr.rb:1"])
|
34
|
+
expect(error.message).to eq("bad attr")
|
35
|
+
expect(error.backtrace).to eq(["attr.rb:1"])
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe Cattri::Inheritance do
|
6
|
+
let(:base_class) do
|
7
|
+
Class.new.tap do |klass|
|
8
|
+
klass.include(Cattri::ContextRegistry)
|
9
|
+
klass.singleton_class.include(Cattri::ContextRegistry)
|
10
|
+
|
11
|
+
klass.attr_reader :inherited_called_with
|
12
|
+
|
13
|
+
def klass.inherited(subclass)
|
14
|
+
@inherited_called_with = subclass
|
15
|
+
end
|
16
|
+
|
17
|
+
def klass.inherited_called_with # rubocop:disable Style/TrivialAccessors
|
18
|
+
@inherited_called_with
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
before do
|
24
|
+
allow(Cattri::Context).to receive(:new).and_call_original
|
25
|
+
allow_any_instance_of(Cattri::AttributeRegistry).to receive(:copy_attributes_to)
|
26
|
+
end
|
27
|
+
|
28
|
+
describe ".install" do
|
29
|
+
it "preserves and calls existing inherited method" do
|
30
|
+
Cattri::Inheritance.install(base_class)
|
31
|
+
|
32
|
+
subclass = Class.new(base_class) # triggers inherited hook
|
33
|
+
|
34
|
+
expect(base_class.inherited_called_with).to eq(subclass)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "initializes a Cattri::Context for the subclass" do
|
38
|
+
Cattri::Inheritance.install(base_class)
|
39
|
+
|
40
|
+
expect(Cattri::Context).to receive(:new) do |actual_subclass|
|
41
|
+
expect(actual_subclass.superclass).to eq(base_class)
|
42
|
+
end
|
43
|
+
|
44
|
+
Class.new(base_class)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "invokes copy_attributes_to with the subclass context" do
|
48
|
+
context = instance_double(Cattri::Context)
|
49
|
+
allow(Cattri::Context).to receive(:new).and_return(context)
|
50
|
+
|
51
|
+
registry = instance_double(Cattri::AttributeRegistry)
|
52
|
+
allow(base_class).to receive(:attribute_registry).and_return(registry)
|
53
|
+
|
54
|
+
expect(registry).to receive(:copy_attributes_to).with(context)
|
55
|
+
|
56
|
+
Cattri::Inheritance.install(base_class)
|
57
|
+
Class.new(base_class)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe Cattri::InitializerPatch do
|
6
|
+
let(:klass) do
|
7
|
+
Class.new do
|
8
|
+
include Cattri
|
9
|
+
|
10
|
+
cattri :final_attr, -> { "default-final" }, final: true
|
11
|
+
cattri :regular_attr, -> { "default-regular" }
|
12
|
+
|
13
|
+
def initialize(preset: nil)
|
14
|
+
self.final_attr = preset if preset
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
subject(:instance) { klass.new }
|
20
|
+
|
21
|
+
describe "#initialize (via InitializerPatch)" do
|
22
|
+
it "sets the default value for final attribute if unset" do
|
23
|
+
expect(instance.final_attr).to eq("default-final")
|
24
|
+
end
|
25
|
+
|
26
|
+
it "does not override a final value if already set in initialize" do
|
27
|
+
custom = klass.new(preset: "explicit")
|
28
|
+
expect(custom.final_attr).to eq("explicit")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "does not set default for non-final attributes" do
|
32
|
+
expect(instance.cattri_variable_defined?(:regular_attr)).to be false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe Cattri::InternalStore do
|
6
|
+
let(:klass) do
|
7
|
+
Class.new do
|
8
|
+
include Cattri::InternalStore
|
9
|
+
|
10
|
+
public :normalize_ivar, :__cattri_store, :__cattri_set_variables
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
subject(:instance) { klass.new }
|
15
|
+
|
16
|
+
describe "#cattri_variable_defined?" do
|
17
|
+
it "returns false when variable is not set" do
|
18
|
+
expect(instance.cattri_variable_defined?(:foo)).to be false
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns true when variable is set" do
|
22
|
+
instance.cattri_variable_set(:foo, 123)
|
23
|
+
expect(instance.cattri_variable_defined?(:foo)).to be true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#cattri_variable_get" do
|
28
|
+
it "returns nil when variable is not set" do
|
29
|
+
expect(instance.cattri_variable_get(:bar)).to be_nil
|
30
|
+
end
|
31
|
+
|
32
|
+
it "returns the value when variable is set" do
|
33
|
+
instance.cattri_variable_set(:bar, "hello")
|
34
|
+
expect(instance.cattri_variable_get(:bar)).to eq("hello")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#cattri_variable_set" do
|
39
|
+
it "sets the value for a variable" do
|
40
|
+
instance.cattri_variable_set(:baz, 42)
|
41
|
+
expect(instance.cattri_variable_get(:baz)).to eq(42)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "tracks the variable as explicitly set" do
|
45
|
+
instance.cattri_variable_set(:baz, 42)
|
46
|
+
expect(instance.__cattri_set_variables).to include(:baz)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "normalizes the key (removes @ and converts to symbol)" do
|
50
|
+
instance.cattri_variable_set("@qux", 99)
|
51
|
+
expect(instance.__cattri_store).to have_key(:qux)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "raises if modifying a final value" do
|
55
|
+
instance.cattri_variable_set(:locked, "a", final: true)
|
56
|
+
|
57
|
+
expect do
|
58
|
+
instance.cattri_variable_set(:locked, "b", final: true)
|
59
|
+
end.to raise_error(Cattri::AttributeError, /Cannot modify final attribute :locked/)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#cattri_variable_memoize" do
|
64
|
+
it "memoizes value if not already set" do
|
65
|
+
result = instance.cattri_variable_memoize(:lazy) { "computed" }
|
66
|
+
|
67
|
+
expect(result).to eq("computed")
|
68
|
+
expect(instance.cattri_variable_get(:lazy)).to eq("computed")
|
69
|
+
end
|
70
|
+
|
71
|
+
it "returns existing value if already set" do
|
72
|
+
instance.cattri_variable_set(:lazy, "preset")
|
73
|
+
result = instance.cattri_variable_memoize(:lazy) { "should not run" }
|
74
|
+
|
75
|
+
expect(result).to eq("preset")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "#__cattri_store" do
|
80
|
+
it "returns a Hash" do
|
81
|
+
expect(instance.__cattri_store).to be_a(Hash)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "memoizes the store" do
|
85
|
+
expect(instance.__cattri_store).to equal(instance.__cattri_store)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "#__cattri_set_variables" do
|
90
|
+
it "returns a Set" do
|
91
|
+
expect(instance.__cattri_set_variables).to be_a(Set)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "memoizes the set" do
|
95
|
+
expect(instance.__cattri_set_variables).to equal(instance.__cattri_set_variables)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "#normalize_ivar" do
|
100
|
+
it "converts string with @ to symbol without @" do
|
101
|
+
expect(instance.normalize_ivar("@name")).to eq(:name)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "converts plain string to symbol" do
|
105
|
+
expect(instance.normalize_ivar("email")).to eq(:email)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "converts symbol to symbol" do
|
109
|
+
expect(instance.normalize_ivar(:age)).to eq(:age)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "freezes the returned symbol" do
|
113
|
+
sym = instance.normalize_ivar("cool")
|
114
|
+
expect(sym).to be_frozen
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "#guard_final!" do
|
119
|
+
it "raises if key is defined and final" do
|
120
|
+
instance.__cattri_store[:foo] = Cattri::AttributeValue.new("sealed", true)
|
121
|
+
expect do
|
122
|
+
instance.send(:guard_final!, :foo)
|
123
|
+
end.to raise_error(Cattri::AttributeError, /Cannot modify final attribute :foo/)
|
124
|
+
end
|
125
|
+
|
126
|
+
it "does nothing if key is not defined" do
|
127
|
+
expect do
|
128
|
+
instance.send(:guard_final!, :bar)
|
129
|
+
end.not_to raise_error
|
130
|
+
end
|
131
|
+
|
132
|
+
it "does nothing if key is defined but not final" do
|
133
|
+
instance.__cattri_store[:baz] = Cattri::AttributeValue.new("open", false)
|
134
|
+
expect do
|
135
|
+
instance.send(:guard_final!, :baz)
|
136
|
+
end.not_to raise_error
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe Cattri::Introspection do
|
6
|
+
let(:klass) do
|
7
|
+
Class.new do
|
8
|
+
include Cattri
|
9
|
+
cattri :foo, 123
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:subclass) do
|
14
|
+
Class.new(klass) do
|
15
|
+
include Cattri::Introspection
|
16
|
+
cattri :bar, "bar"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
before do
|
21
|
+
klass.include(Cattri::Introspection)
|
22
|
+
end
|
23
|
+
|
24
|
+
describe ".attribute_defined?" do
|
25
|
+
it "returns true for defined attributes" do
|
26
|
+
expect(klass.attribute_defined?(:foo)).to be true
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns false for unknown attributes" do
|
30
|
+
expect(klass.attribute_defined?(:missing)).to be false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe ".attribute" do
|
35
|
+
it "returns the attribute definition" do
|
36
|
+
attr = klass.attribute(:foo)
|
37
|
+
expect(attr).to be_a(Cattri::Attribute)
|
38
|
+
expect(attr.name).to eq(:foo)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "returns nil for unknown attributes" do
|
42
|
+
expect(klass.attribute(:missing)).to be_nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe ".attributes" do
|
47
|
+
it "lists local attributes by default" do
|
48
|
+
expect(subclass.attributes).to eq([:bar])
|
49
|
+
end
|
50
|
+
|
51
|
+
it "includes inherited attributes when requested" do
|
52
|
+
expect(subclass.attributes(with_ancestors: true)).to match_array(%i[foo bar])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe ".attribute_definitions" do
|
57
|
+
it "returns a hash of defined attributes" do
|
58
|
+
defs = subclass.attribute_definitions
|
59
|
+
expect(defs).to be_a(Hash)
|
60
|
+
expect(defs).to have_key(:bar)
|
61
|
+
expect(defs[:bar]).to be_a(Cattri::Attribute)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "includes ancestors if requested" do
|
65
|
+
defs = subclass.attribute_definitions(with_ancestors: true)
|
66
|
+
expect(defs).to have_key(:foo)
|
67
|
+
expect(defs[:foo]).to be_a(Cattri::Attribute)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe ".attribute_methods" do
|
72
|
+
it "returns a hash of methods per attribute" do
|
73
|
+
methods = subclass.attribute_methods
|
74
|
+
expect(methods).to be_a(Hash)
|
75
|
+
expect(methods.keys).to include(:bar)
|
76
|
+
expect(methods[:bar]).to include(:bar)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe ".attribute_source" do
|
81
|
+
it "returns the class where the attribute was originally defined" do
|
82
|
+
expect(subclass.attribute_source(:foo)).to eq(klass)
|
83
|
+
expect(subclass.attribute_source(:bar)).to eq(subclass)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "returns nil for unknown attributes" do
|
87
|
+
expect(subclass.attribute_source(:missing)).to be_nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
require "cattri/visibility"
|
5
|
+
|
6
|
+
RSpec.describe Cattri::Visibility do
|
7
|
+
let(:klass) do
|
8
|
+
Class.new do
|
9
|
+
extend Cattri::Visibility
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#__cattri_visibility" do
|
14
|
+
it "defaults to public" do
|
15
|
+
expect(klass.send(:__cattri_visibility)).to eq(:public)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#public" do
|
20
|
+
it "sets visibility to public when no args are passed" do
|
21
|
+
klass.protected
|
22
|
+
klass.public
|
23
|
+
|
24
|
+
expect(klass.send(:__cattri_visibility)).to eq(:public)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "delegates to Module#public when args are passed" do
|
28
|
+
klass.class_eval do
|
29
|
+
def sample_method; end
|
30
|
+
end
|
31
|
+
|
32
|
+
expect { klass.public :sample_method }.not_to raise_error
|
33
|
+
expect(klass.public_instance_methods).to include(:sample_method)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#protected" do
|
38
|
+
it "sets visibility to protected when no args are passed" do
|
39
|
+
klass.protected
|
40
|
+
expect(klass.send(:__cattri_visibility)).to eq(:protected)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "delegates to Module#protected when args are passed" do
|
44
|
+
klass.class_eval do
|
45
|
+
def protected_method; end
|
46
|
+
end
|
47
|
+
|
48
|
+
expect { klass.protected :protected_method }.not_to raise_error
|
49
|
+
expect(klass.protected_instance_methods).to include(:protected_method)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#private" do
|
54
|
+
it "sets visibility to private when no args are passed" do
|
55
|
+
klass.private
|
56
|
+
expect(klass.send(:__cattri_visibility)).to eq(:private)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "delegates to Module#private when args are passed" do
|
60
|
+
klass.class_eval do
|
61
|
+
def private_method; end
|
62
|
+
end
|
63
|
+
|
64
|
+
expect { klass.private :private_method }.not_to raise_error
|
65
|
+
expect(klass.private_instance_methods).to include(:private_method)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/spec/cattri_spec.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe Cattri do
|
6
|
+
let(:klass) do
|
7
|
+
Class.new do
|
8
|
+
include Cattri
|
9
|
+
|
10
|
+
cattri :token, -> { "abc123" }
|
11
|
+
cattri :final_token, -> { "321cba" }, final: true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
subject(:instance) { klass.new }
|
16
|
+
|
17
|
+
describe "including Cattri" do
|
18
|
+
it "defines a working accessor" do
|
19
|
+
expect(instance.token).to eq("abc123")
|
20
|
+
instance.token = "xyz"
|
21
|
+
expect(instance.token).to eq("xyz")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "respects default visibility (public)" do
|
25
|
+
expect(klass.public_instance_methods).to include(:token, :token=)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "installs initializer patch for default assignment" do
|
29
|
+
expect(instance.cattri_variable_defined?(:final_token)).to be true
|
30
|
+
expect(instance.cattri_variable_get(:final_token)).to eq("321cba")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe ".with_cattri_introspection" do
|
35
|
+
let(:introspective_class) do
|
36
|
+
Class.new do
|
37
|
+
include Cattri
|
38
|
+
cattri :id, "foo"
|
39
|
+
|
40
|
+
with_cattri_introspection
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it "adds .attribute_defined?" do
|
45
|
+
expect(introspective_class.attribute_defined?(:id)).to be true
|
46
|
+
expect(introspective_class.attribute_defined?(:missing)).to be false
|
47
|
+
end
|
48
|
+
|
49
|
+
it "returns attribute source and methods" do
|
50
|
+
expect(introspective_class.attribute_source(:id)).to eq(introspective_class)
|
51
|
+
expect(introspective_class.attribute_methods).to include(:id)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "simplecov"
|
4
|
+
require "simplecov-cobertura"
|
5
|
+
require "simplecov-html"
|
6
|
+
|
7
|
+
SimpleCov.formatters = [
|
8
|
+
SimpleCov::Formatter::CoberturaFormatter,
|
9
|
+
SimpleCov::Formatter::HTMLFormatter
|
10
|
+
]
|
11
|
+
|
12
|
+
SimpleCov.start do
|
13
|
+
enable_coverage :branch
|
14
|
+
|
15
|
+
track_files "lib/cattri/**/*.rb"
|
16
|
+
|
17
|
+
add_filter "lib/cattri/version.rb"
|
18
|
+
add_filter "/spec/"
|
19
|
+
end
|
20
|
+
|
21
|
+
SimpleCov.minimum_coverage 100
|