yard-virtus 0.0.4
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 +7 -0
- data/.gitignore +22 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +75 -0
- data/Guardfile +12 -0
- data/LICENSE.txt +19 -0
- data/README.md +49 -0
- data/Rakefile +7 -0
- data/example/README.md +4 -0
- data/example/address.rb +7 -0
- data/example/city.rb +5 -0
- data/example/user.rb +9 -0
- data/lib/yard-virtus.rb +28 -0
- data/lib/yard/virtus/code_objects.rb +2 -0
- data/lib/yard/virtus/code_objects/attribute_reader.rb +26 -0
- data/lib/yard/virtus/code_objects/attribute_writer.rb +48 -0
- data/lib/yard/virtus/declarations.rb +4 -0
- data/lib/yard/virtus/declarations/options.rb +43 -0
- data/lib/yard/virtus/declarations/type.rb +65 -0
- data/lib/yard/virtus/declarations/virtus_attribute.rb +67 -0
- data/lib/yard/virtus/declarations/virtus_model.rb +33 -0
- data/lib/yard/virtus/handlers.rb +2 -0
- data/lib/yard/virtus/handlers/include_virtus_model.rb +32 -0
- data/lib/yard/virtus/handlers/virtus_attribute.rb +58 -0
- data/lib/yard/virtus/version.rb +5 -0
- data/spec/code_objects/attribute_reader_spec.rb +38 -0
- data/spec/code_objects/attribute_writer_spec.rb +75 -0
- data/spec/declarations/options_spec.rb +50 -0
- data/spec/declarations/type_spec.rb +37 -0
- data/spec/declarations/virtus_attribute_spec.rb +73 -0
- data/spec/declarations/virtus_model_spec.rb +35 -0
- data/spec/examples/include_virtus_model_001.rb.txt +15 -0
- data/spec/examples/virtus_attribute_001.rb.txt +67 -0
- data/spec/handlers/include_virtus_model_spec.rb +22 -0
- data/spec/handlers/virtus_attribute_spec.rb +71 -0
- data/spec/spec_helper.rb +65 -0
- data/spec/support/helpers/handler_helpers.rb +9 -0
- data/spec/support/helpers/parsing_helpers.rb +13 -0
- data/spec/support/matchers/attribute_matchers.rb +45 -0
- data/spec/support/matchers/method_type_matchers.rb +11 -0
- data/yard-virtus.gemspec +22 -0
- metadata +117 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module YARD
|
|
2
|
+
module Virtus
|
|
3
|
+
module Declarations
|
|
4
|
+
# VirtusModel declaration wraps AST which represents
|
|
5
|
+
# call to `attribute` method
|
|
6
|
+
class VirtusAttribute
|
|
7
|
+
attr_reader :ast
|
|
8
|
+
|
|
9
|
+
# @params [YARD::Parser::Ruby::MethodCallNode] ast
|
|
10
|
+
def initialize(ast)
|
|
11
|
+
@ast = ast
|
|
12
|
+
@options = Options.new(parameters[2])
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def readable?
|
|
16
|
+
true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def writable?
|
|
20
|
+
true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def has_private_writer?
|
|
24
|
+
options[:writer] == :private
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def attr_name
|
|
28
|
+
parameters.first.jump(:ident).first.to_sym
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def type
|
|
32
|
+
Type.new(type_param).yard_type_string
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def attribute_reader
|
|
36
|
+
CodeObjects::AttributeReader.new(attr_name, type)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def attribute_writer
|
|
40
|
+
CodeObjects::AttributeWriter.new(attr_name, type, has_private_writer?)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
protected
|
|
44
|
+
attr_reader :options
|
|
45
|
+
|
|
46
|
+
def parameters
|
|
47
|
+
ast.parameters(false)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def type_param
|
|
51
|
+
parameters[1]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def scalar_type_to_string(ref)
|
|
55
|
+
ref.path.join("::")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def collection_type_to_string(type)
|
|
59
|
+
collection_type = scalar_type_to_string(type_param[0])
|
|
60
|
+
element_type = scalar_type_to_string(type_param[1].jump(:var_ref))
|
|
61
|
+
|
|
62
|
+
"#{collection_type}<#{element_type}>"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module YARD
|
|
2
|
+
module Virtus
|
|
3
|
+
module Declarations
|
|
4
|
+
# VirtusModel declaration wraps AST which represents
|
|
5
|
+
# inclusion of Virtus and allows to extract useful data
|
|
6
|
+
# like module proxies from it.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# ast = s(:call, s(:var_ref, s(:const, "Virtus")), :".", s(:ident, "model"))
|
|
10
|
+
# d = VirtusModel.new(ast)
|
|
11
|
+
# d.module_proxies_in_ns(ns) # => P("Virtus.model")
|
|
12
|
+
#
|
|
13
|
+
class VirtusModel
|
|
14
|
+
attr_reader :ast
|
|
15
|
+
|
|
16
|
+
# @params [YARD::Parser::Ruby::MethodCallNode] ast
|
|
17
|
+
def initialize(ast)
|
|
18
|
+
@ast = ast
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @param [YARD::CodeObjects::ClassObject] namespace
|
|
22
|
+
def module_proxies_in_ns(namespace)
|
|
23
|
+
[YARD::CodeObjects::Proxy.new(namespace, mixin_name, :module)]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
protected
|
|
27
|
+
def mixin_name
|
|
28
|
+
"%s.%s" % [ast.namespace, ast.method_name].map(&:source)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module YARD
|
|
2
|
+
module Virtus
|
|
3
|
+
module Handlers
|
|
4
|
+
class IncludeVirtusModel < YARD::Handlers::Ruby::Base
|
|
5
|
+
handles method_call(:include)
|
|
6
|
+
namespace_only
|
|
7
|
+
|
|
8
|
+
def process
|
|
9
|
+
raise YARD::Handlers::HandlerAborted unless virtus_module?
|
|
10
|
+
|
|
11
|
+
declaration = Declarations::VirtusModel.new(virtus_call)
|
|
12
|
+
declaration.module_proxies_in_ns(namespace).each do |proxy|
|
|
13
|
+
namespace.mixins(scope).unshift(proxy)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
namespace[:supports_virtus_attributes] = true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
protected
|
|
20
|
+
|
|
21
|
+
def virtus_module?
|
|
22
|
+
included_module = statement.parameters.jump(:var_ref)
|
|
23
|
+
included_module and included_module.source == "Virtus"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def virtus_call
|
|
27
|
+
statement.parameters(false)[0]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module YARD
|
|
2
|
+
module Virtus
|
|
3
|
+
module Handlers
|
|
4
|
+
class VirtusAttribute < YARD::Handlers::Ruby::Base
|
|
5
|
+
handles method_call(:attribute)
|
|
6
|
+
namespace_only
|
|
7
|
+
|
|
8
|
+
def process
|
|
9
|
+
raise YARD::Handlers::HandlerAborted unless virtus_model?
|
|
10
|
+
|
|
11
|
+
declaration = Declarations::VirtusAttribute.new(statement)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
register_attribute!(declaration.attribute_reader, :read) if declaration.readable?
|
|
15
|
+
register_attribute!(declaration.attribute_writer, :write) if declaration.writable?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
protected
|
|
19
|
+
|
|
20
|
+
# @param [CodeObjects::AttributeReader, CodeObjects::AttributeWriter] mobject
|
|
21
|
+
# @param [YARD::CodeObjects::MethodObject] mobject
|
|
22
|
+
# @param [:read, :write] type
|
|
23
|
+
def register_attribute!(mobject, type)
|
|
24
|
+
yard_mobject = mobject.yard_method_object(namespace)
|
|
25
|
+
|
|
26
|
+
register_preserving_tags!(yard_mobject)
|
|
27
|
+
attributes_data_for(mobject.attr_name)[type] = yard_mobject
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def attributes_data_for(name)
|
|
31
|
+
namespace.attributes[scope][name] ||= SymbolHash[:read => nil, :write => nil]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def virtus_model?
|
|
35
|
+
namespace.inheritance_tree(true).any? { |n| n[:supports_virtus_attributes] == true }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# When you register an object it can get assigned docstring which
|
|
39
|
+
# followed statement which was handled. If such a docstring exists it
|
|
40
|
+
# can result in removal of previously extracted tags like `@return` and
|
|
41
|
+
# `@private` as well as default values. To prevent it we restore all
|
|
42
|
+
# tags which disappeared after registration.
|
|
43
|
+
#
|
|
44
|
+
# Do you love uncontrolled mutations as much as I do?
|
|
45
|
+
def register_preserving_tags!(object)
|
|
46
|
+
tags_before_registration = object.tags
|
|
47
|
+
|
|
48
|
+
register(object)
|
|
49
|
+
|
|
50
|
+
lost_tags = tags_before_registration - object.tags
|
|
51
|
+
if lost_tags.size > 0
|
|
52
|
+
object.add_tag(*lost_tags)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe YARD::Virtus::CodeObjects::AttributeReader do
|
|
4
|
+
subject { described_class.new(:title, "String") }
|
|
5
|
+
|
|
6
|
+
it "has #attr_name" do
|
|
7
|
+
expect(subject.attr_name).to eq(:title)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "has #type" do
|
|
11
|
+
expect(subject.type).to eq("String")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "has #method_name" do
|
|
15
|
+
expect(subject.method_name).to eq(:title)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe "#yard_method_object" do
|
|
19
|
+
let(:reader) { described_class.new(:title, "String") }
|
|
20
|
+
let(:namespace) { YARD::CodeObjects::ClassObject.new(nil, "TemporarySpecClass") }
|
|
21
|
+
|
|
22
|
+
subject { reader.yard_method_object(namespace) }
|
|
23
|
+
|
|
24
|
+
it { expect(subject).to be_instance_of(YARD::CodeObjects::MethodObject) }
|
|
25
|
+
|
|
26
|
+
it "is not explicit" do
|
|
27
|
+
expect(subject.is_explicit?).to be_false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "has the same name as attribute it reads" do
|
|
31
|
+
expect(subject.name).to eq(:title)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "has @return tag" do
|
|
35
|
+
expect(subject.has_tag?(:return)).to be_true
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe YARD::Virtus::CodeObjects::AttributeWriter do
|
|
4
|
+
before :each do
|
|
5
|
+
# All YARD::CodeObjects::* objects are added to
|
|
6
|
+
# registry on creation which causes conflicts in
|
|
7
|
+
# this test.
|
|
8
|
+
YARD::Registry.clear
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
subject { described_class.new(:title, "String") }
|
|
12
|
+
|
|
13
|
+
it "has #attr_name" do
|
|
14
|
+
expect(subject.attr_name).to eq(:title)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "has #type" do
|
|
18
|
+
expect(subject.type).to eq("String")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "has #method_name" do
|
|
22
|
+
expect(subject.method_name).to eq(:"title=")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe "#yard_method_object" do
|
|
26
|
+
let(:writer) { described_class.new(:title, "String") }
|
|
27
|
+
let(:namespace) { YARD::CodeObjects::ClassObject.new(nil, "TemporarySpecClass") }
|
|
28
|
+
|
|
29
|
+
subject { writer.yard_method_object(namespace) }
|
|
30
|
+
|
|
31
|
+
it { expect(subject).to be_instance_of(YARD::CodeObjects::MethodObject) }
|
|
32
|
+
|
|
33
|
+
it "is not explicit" do
|
|
34
|
+
expect(subject.is_explicit?).to be_false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "has the writer name for attribute" do
|
|
38
|
+
expect(subject.name).to eq(:"title=")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "has parameter" do
|
|
42
|
+
expect(subject.parameters).not_to be_empty
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "has type signature tag for parameter" do
|
|
46
|
+
param_tags = subject.tags(:param).select { |t| t.name == "value" }
|
|
47
|
+
|
|
48
|
+
expect(param_tags).not_to be_empty
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context "when writer visibility is not specified" do
|
|
52
|
+
let(:writer) { described_class.new(:title, "String") }
|
|
53
|
+
|
|
54
|
+
it "does not have @private tag" do
|
|
55
|
+
expect(subject.tags(:private)).to be_empty
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context "when writer is public" do
|
|
60
|
+
let(:writer) { described_class.new(:title, "String", false) }
|
|
61
|
+
|
|
62
|
+
it "does not have @private tag" do
|
|
63
|
+
expect(subject.tags(:private)).to be_empty
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
context "when writer is private" do
|
|
68
|
+
let(:writer) { described_class.new(:title, "String", true) }
|
|
69
|
+
|
|
70
|
+
it "has @private tag" do
|
|
71
|
+
expect(subject.tags(:private)).to have(1).item
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe YARD::Virtus::Declarations::Options do
|
|
4
|
+
def self.example(src, &block)
|
|
5
|
+
c = context "'#{src}'"
|
|
6
|
+
# We can not generate AST for `:a => 1` so we wrap it into
|
|
7
|
+
# method call like in `attribute :a => 1` and then extract
|
|
8
|
+
# part corresponding to options. AST for wrapped code will look
|
|
9
|
+
# like this:
|
|
10
|
+
#
|
|
11
|
+
# s(:command,
|
|
12
|
+
# s(:ident, "attribute"),
|
|
13
|
+
# s(s(s(:assoc,
|
|
14
|
+
# s(:symbol_literal, s(:symbol, s(:ident, "a"))),
|
|
15
|
+
# s(:int, "1"))), false))
|
|
16
|
+
#
|
|
17
|
+
c.let(:ast) { ruby_ast("attribute #{src}")[1] }
|
|
18
|
+
c.class_eval(&block)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
subject { described_class.new(ast) }
|
|
22
|
+
|
|
23
|
+
example "" do
|
|
24
|
+
it { expect(subject).to be_empty }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
example ":a => :b" do
|
|
28
|
+
it { expect(subject).not_to be_empty }
|
|
29
|
+
|
|
30
|
+
it "has information about key :a" do
|
|
31
|
+
expect(subject[:a]).to eq(:b)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
example "a: :b" do
|
|
36
|
+
it { expect(subject).not_to be_empty }
|
|
37
|
+
|
|
38
|
+
it "has information about key :a" do
|
|
39
|
+
expect(subject[:a]).to eq(:b)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
example ":default => lambda { 123 }" do
|
|
44
|
+
it { expect(subject).not_to be_empty }
|
|
45
|
+
|
|
46
|
+
it "has information about lambda function" do
|
|
47
|
+
expect(subject[:default]).to be_kind_of(Proc)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe YARD::Virtus::Declarations::Type do
|
|
4
|
+
def self.example(src, &block)
|
|
5
|
+
c = context "'#{src}'"
|
|
6
|
+
c.let(:ast) { ruby_ast(src) }
|
|
7
|
+
c.class_eval(&block)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe "#yard_type_string" do
|
|
11
|
+
let(:subject) { described_class.new(ast).yard_type_string }
|
|
12
|
+
|
|
13
|
+
example "String" do
|
|
14
|
+
it { expect(subject).to eq "String" }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
example "Some::Nested::String" do
|
|
18
|
+
it { expect(subject).to eq "Some::Nested::String" }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
example "Array[String]" do
|
|
22
|
+
it { expect(subject).to eq "Array<String>" }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
example "Array[Nested::String]" do
|
|
26
|
+
it { expect(subject).to eq "Array<Nested::String>" }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
example "Collection[Address]" do
|
|
30
|
+
it { expect(subject).to eq "Collection<Address>" }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
example "Hash[String => Integer]" do
|
|
34
|
+
it { expect(subject).to eq "Hash{String => Integer}" }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe YARD::Virtus::Declarations::VirtusAttribute do
|
|
4
|
+
let(:ast) { ruby_ast(declaration) }
|
|
5
|
+
let(:namespace) { YARD::CodeObjects::ClassObject.new(nil, "TemporarySpecClass") }
|
|
6
|
+
let(:subject) { described_class.new(ast) }
|
|
7
|
+
|
|
8
|
+
def self.example(src, &block)
|
|
9
|
+
c = context "'#{src}'"
|
|
10
|
+
c.let(:declaration) { src }
|
|
11
|
+
c.class_eval(&block)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
example "attribute :title, String" do
|
|
15
|
+
it { expect(subject).to be_readable }
|
|
16
|
+
it { expect(subject).to be_writable }
|
|
17
|
+
|
|
18
|
+
it { expect(subject.attr_name).to eq(:title) }
|
|
19
|
+
it { expect(subject.type).to eq("String") }
|
|
20
|
+
|
|
21
|
+
it "produces reader method object" do
|
|
22
|
+
expect(subject.attribute_reader).to be_kind_of(YARD::Virtus::CodeObjects::AttributeReader)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "produces writer method object" do
|
|
26
|
+
expect(subject.attribute_writer).to be_kind_of(YARD::Virtus::CodeObjects::AttributeWriter)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
example "attribute :title, Namespaced::String" do
|
|
31
|
+
it { expect(subject).to be_readable }
|
|
32
|
+
it { expect(subject).to be_writable }
|
|
33
|
+
|
|
34
|
+
it { expect(subject.attr_name).to eq(:title) }
|
|
35
|
+
it { expect(subject.type).to eq("Namespaced::String") }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
example "attribute :locations, Array[Address]" do
|
|
39
|
+
it { expect(subject).to be_readable }
|
|
40
|
+
it { expect(subject).to be_writable }
|
|
41
|
+
|
|
42
|
+
it { expect(subject.attr_name).to eq(:locations) }
|
|
43
|
+
it { expect(subject.type).to eq("Array<Address>") }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
example "attribute :locations, Array[Array[Point]]" do
|
|
47
|
+
it { expect(subject).to be_readable }
|
|
48
|
+
it { expect(subject).to be_writable }
|
|
49
|
+
|
|
50
|
+
it { expect(subject.attr_name).to eq(:locations) }
|
|
51
|
+
it { expect(subject.type).to eq("Array<Array<Point>>") }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
example "attribute :locations, Hash[Symbol => Address]" do
|
|
55
|
+
it { expect(subject).to be_readable }
|
|
56
|
+
it { expect(subject).to be_writable }
|
|
57
|
+
|
|
58
|
+
it { expect(subject.attr_name).to eq(:locations) }
|
|
59
|
+
it { expect(subject.type).to eq("Hash{Symbol => Address}") }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
example "attribute :unique_id, String, :writer => :private" do
|
|
63
|
+
it { expect(subject).to have_private_writer }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
example "attribute :unique_id, String, writer: :private" do
|
|
67
|
+
it { expect(subject).to have_private_writer }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
example "attribute :unique_id, String, :writer => :private, :a => :b" do
|
|
71
|
+
it { expect(subject).to have_private_writer }
|
|
72
|
+
end
|
|
73
|
+
end
|