-
Notifications
You must be signed in to change notification settings - Fork 216
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1581 from ruby/add-guide--data-and-struct
Add a guide about using `Data` and `Struct`
- Loading branch information
Showing
2 changed files
with
57 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# Using `Data` and `Struct` | ||
|
||
`Data` and `Struct` are commonly used utilities to define simple *value* objects. The objects have attributes, and the equality between the two objects are defined by equality of the attributes. (Note that we can define additional methods and overwrite the equality definitions when we want.) | ||
|
||
```ruby | ||
# Defines `Measure` class with `#amount` and `#unit` attributes | ||
Measure = Data.define(:amount, :unit) | ||
``` | ||
|
||
Unfortunately, supporting `Data` and `Struct` in RBS is not straightforward. You have to write down the attribute definitions and initializers in RBS. | ||
|
||
```rbs | ||
class Measure | ||
attr_reader amount: Integer | ||
attr_reader unit: String | ||
def initialize: (Integer amount, String unit) -> void | ||
| (amount: Integer, unit: String) -> void | ||
end | ||
``` | ||
|
||
This is simplified definition of the `Measure` class, for the case you only use the attributes and initializers. You can add more method definitions or inherit from `Data` class to make the definition more complete. | ||
|
||
However, it's common that you don't need all of the `Data` and `Struct` methods, like `.members` and `.[]`. When you are using those utility classes just for the attributes methods, you can simply ignore other methods or skip specifying a super class. | ||
|
||
> You may want to implement a generator that understands `Data.define` and `Struct.new`. But even with the generator, you need to edit the generated RBS files so that the attribute definitions have correct types. | ||
## Type checking class definitions using `Data` and `Struct` | ||
|
||
If you use Steep, you may need additional annotation in Ruby implementation. | ||
|
||
```ruby | ||
# Type error because return type of `Data.define(...)` is not `singleton(Measure)` | ||
Measure = Data.define(:amount, :unit) | ||
``` | ||
|
||
You can please the type checker by adding a cast (`_`) or define the class inheriting from `Data.define(...)`. | ||
|
||
```ruby | ||
# Skip type checking by assigning to `_` | ||
Measure = _ = Data.define(:amount, :unit) | ||
|
||
# Super class is not type checked by Steep | ||
class Measure < Data.define(:amount, :unit) | ||
end | ||
``` | ||
|
||
@soutaro has prefered inheriting from `Data.define`, but you may find an extra annonymous class in `.ancestors` [^1]. | ||
|
||
```ruby | ||
Measure.ancestors #=> [Measure, #<Class:0xOOF>, Data, ...] | ||
``` | ||
|
||
[^1]: [Shannon Skipper](https://github.com/havenwood) told me it in Discord |