Replies: 1 comment
-
I'm going to come back to this with a decision on how data objects should be handled — everything else is straightforward enough! |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Data objects
Data accessors
attr_reader
with instance variables (or data store)The simplest (but most verbose) way to implement these getters is using instance variables and
attr_reader
.attr_reader
is syntactic sugar that simply acts as a getter for an instance variable — it's shorthand for this:This is explicit, but verbose. This is very easily typed with Sorbet or RBS, and easy to generate. Instance variables can very easily replaced with a
Hash
for the actual data — for example:Metaprogramming with
#method_missing
The
#method_missing
method in Ruby is heavily used for metaprogramming — when defined in a class, any method call to an object of that class that doesn't exist would then be forwarded to this method. For example:Therefore, with an internal structure of a
Hash
, you could do something like this:The downside here is that there is no autocomplete — since the methods are never defined (just used at runtime), inspecting the class with pry reveals only the
#method_missing
method.You can define methods that don't exist in the actual code within RBI and RBS files, but the
#method_missing
method itself cannot be typed.Metaprogramming with
define_method
The other method of metaprogramming in Ruby is with
.define_method
, which, uh, defines a method on the class.This has the important benefit of being discoverable... kind of. These methods are defined on the class only after they're defined in the initializer.
Again, you can define methods that don't exist in the actual code within RBI and RBS files, but there is no
#method_missing
to type here. This is the approach that Stripe uses in the stripe-ruby gem.Attribute properties
The simplest approach here is to have two different data structures:
The first can be stored within the class, using some sort of DSL. For example:
And the data about the fields would be stored like so:
This would also be fairly easy to extend into other classes as well with a DSL:
Validating input and serialization
Using the aforementioned attribute store, you can then use this to validate input and serialize/deserialize data.
Enums
Enums are not really common in Ruby — Sorbet has them (fairly useless without the runtime, I think), but the closest that RBS can get is a union of possible values. With OpenAPI's definition of enums, we can add on to the attributes just a regular array of possible values (or a
Set
, if you're worried about uniqueness). Serialization doesn't seem to be a huge problem here (since it's a set of possible values).In terms of forward compatibility is more complex, I would defer to how Fern does that in other languages.
Literals
Literals (such as
"admin"
) are generally defined using symbols (i.e.,:admin
).Undiscriminated unions
Undiscriminated unions are easy to type in both Sorbet (
T.any(String, Boolean)
) and RBS (String | Boolean
).Discriminated unions
With RBS, this can be typed with unions:
With Sorbet, you'd need to use a mixture of shapes, with a
T::Enum
as the discriminator:Beta Was this translation helpful? Give feedback.
All reactions