SorbetTyped::Props
An extension of sorbets native props syntax, to make it usable and fully typed in any class. Mainly provides a tapioca
dsl compiler to generate the initializer signature when using props outside of T::Struct.
You can use the T::Struct props and const syntax to define attributes on any
class.
This should make it easier to create classes with a set of attributes they should be initialized with. Inspiration was the integration of literal properties into phlex, which doesn't really work with sorbet, if you want to have everything fully typed (and not use two runtime typesystems in parallel).
Installation
Install the gem and add to the application's Gemfile by executing:
bundle add sorbet_typed-props
If bundler is not being used to manage dependencies, install the gem by executing:
gem install sorbet_typed-props
Usage
Include the SorbetTyped::Props module in any class you want to have T::Struct-like attributes:
class MyClass
include SorbetTyped::Props
prop :my_prop, String
end
my_object = MyClass.new(my_prop: 'foo')
my_object.my_prop # => "foo"
my_object.my_prop = 'bar'
Method Visibility
If you want attributes in your initializer but not be part of your public class interface, you can use ruby's visibility modifiers. Unfortunately I found no better way and did not want to modify sorbet's prop syntax.
class MyClass
extend T::Sig
include SorbetTyped::Props
prop :my_prop, Integer # reader and writer are public
const :my_const, String # reader ist public, has no writer
prop :prop_with_private_writer, String # reader is public, writer should be private
private :prop_with_private_writer= # makes the writer private
const :my_private_prop, String # reader and writer are private
private :my_private_prop, :my_private_prop=
sig { void }
def foo
self.prop_with_private_writer = 'foo'
end
sig { returns(String) }
def
self.my_private_prop
end
sig { void }
def baz
self.my_private_prop = 'baz'
end
end
my_object = MyClass.new(my_prop: 1, my_const: 'my_const', prop_with_private_writer: 'abc', my_private_prop: 'xyz')
my_object.my_prop # => 1
my_object.my_prop = 2
my_object.my_const # => "my_const"
my_object.my_const = 'abc' # => Setter method `my_const=` does not exist on `MyClass`
my_object.prop_with_private_writer # => "abc"
my_object.prop_with_private_writer = 'foo' # => Non-private call to private method `prop_with_private_writer=` on `MyClass`
my_object.foo
my_object.prop_with_private_writer # => "foo"
my_object.my_private_prop # => Non-private call to private method `my_private_prop` on `MyClass`
my_object. # => "xyz"
my_object.my_private_prop = 'baz' # => Non-private call to private method `my_private_prop=` on `MyClass`
my_object.baz
my_object. # => "baz"
Putting prop-definition after an access modifier without arguments does not work:
class MyClass
include SorbetTyped::Props
private
prop :my_prop, String # <= still public
end
Development
The project uses mise-en-place as development tool.
After checking out the repo, run mise run setup to install dependencies. Then, run mise test to run the tests. You
can also run mise task ls for a list of available tasks.
RSpec is used as test suite. Spec files can and should be placed right beside their associated class files.
Contributing
Bug reports and pull requests are welcome on GitLab at gitlab.com/sorbet_typed/props.