Search code examples
rubysorbet

Trying to understand this ruby syntax


I am new to ruby and lots of things are confusing me. I believe this specific one is from Sorbet which is some type checking library? not sure. This specific piece of code is right before a method declaration

sig { params(order: T::Hash[
          String, T.any(
            T.nilable(String), T::Hash[
              String, T.any(
                Integer, T.nilable(String)
              )
            ]
          )
        ]).returns(Types::Order) }

def self.build_prescription(prescription) 
# method implementation

The order object is a json object coming from REST API. Can someone explain what this nesting thing is going on.


Solution

  • This is indeed a Sorbet signature. You can read more about Sorbet in their official documentation

    The signature itself in this case is just describing the shape of the parameters and return type of the build_prescription method.

    Slightly reformatting to make it easier to annotate:

    sig { # A ruby method added by Sorbet to indicate that the signature is starting.
      params( # Start to declare the parameters that the method takes
        order: T::Hash[ # First param. In this case, it's a Hash...
          String, # ... where they key is a String ...
          T.any( # ... and the value can be either: ...
            T.nilable(String), ... # a nilable String
            T::Hash[ # or a Hash ...
              String, # ... where the key is a String...
              T.any(Integer, T.nilable(String)) # ... and the value is either an Integer or nilable string String
            ]
          )
        ])
      .returns(Types::Order) # Declaration of what the method returns
    }
    def self.build_prescription(prescription) # the regular Ruby method declaration
    

    Note, though, that this will fail a Sorbet validation, as the signature declares the parameter as order, while the method declares it as prescription. Those two names should match.

    There's a way to re-write this signature to make it a bit nicer to read/understand

    sig do
      params(
        prescription: T::Hash[
          String,
          T.nilable(
            T.any(
              String,
              T::Hash[String, T.nilable(T.any(Integer, String)]
            )
          )
      ])
      .returns(Types::Order) # Declaration of what the method returns
    }
    def self.build_prescription(prescription)
    

    Note that I'm moving the T.nilables out one level, as T.any(Integer, T.nilable(String)) means that same as T.nilable(T.any(Integer, String)) but it's more immediately obvious for a reader that the value can be nil