Practical Crystal through API Client library building
Tags: crystal, ruby, devjournal, programming
Published: 2022-02-16 20:22:18

## Practice What You Preach
This is somewhat dated now, but when I first started at [Method](https://www.method.com/) (Skookum at the time), I attended a [virtual conference](https://crystal-lang.org/2020/11/19/organising-raw-crystal.html) for the [Crystal programming language](https://crystal-lang.org/). Afterwards, I presented to Method a quick introduction of the language and some key points from the conference. One of my points was to try Crystal out by converting a Ruby project. My reasoning was because the two languages are so similar, it would be easier to reuse what's already be done and just adopt the missing parts. This post is the result of that reasoning...

### Existing (simple) Ruby project
My goal was to convert an existing, simple Ruby project to Crystal. I didn't have to look far, once I realized Skookum already had a decent-sized Ruby Client API library for the [BambooHR](https://www.bamboohr.com/) API, called Bamboozled. After looking through the project, I was confident it would serve my purpose. And not longer afterward, [bamboozled-cr](https://github.com/mdwagner/bamboozled-cr) was born.

### The Grunt Work
Like every project you pick up, first understanding how it works is paramount. After a while of reading the [Bamboozled](https://github.com/Skookum/bamboozled) source code, I started to put together the basics for my Crystal port. Most of the code is pretty similar to how the Ruby gem works, with the exception of the initial class structure. I tried to keep the library API as close to the original, but made improvements where it made sense.

## Lessons Learned in Crystal

### Type Inference
When I first heard about [type inference in Crystal](https://crystal-lang.org/reference/1.3/syntax_and_semantics/type_inference.html), I wasn't sure how well it would work in practice. Having used Typescript extensively, I didn't think it would be as smart as they were describing it. However, almost everything in Crystal is inferred. I was only able to find a few cases where you were _required_ to provide a type, like when declaring an instance/class variable or method overloading. Otherwise, I almost never had to specify a type!

Now, just because it's not required, doesn't mean it's not useful to provide types sometimes, like for documentation or when you want to be more strict on what is allowed to be passed to your methods. After all, types should be thought of as another tool in the toolbox.

### Unit Testing
Crystal provides a [built-in test runner and a fully-featured spec library](https://crystal-lang.org/reference/1.3/guides/testing.html), inspired by [Rspec](http://rspec.info/). I tried out the built-in spec library, and it worked pretty well, but found myself reaching for more of what I was used to from the actual Rspec gem. This led me to discover the [Spectator](https://gitlab.com/arctic-fox/spectator) shard, which provided most of the Rspec helpers I was used to and made me feel much more productive. I definitely recommend it!

### Macros
If you don't already know what [Macros](https://crystal-lang.org/reference/1.3/syntax_and_semantics/macros/index.html) are, they're essentially a way to do meta-programming but at compile time. This is Crystal's equivalent to Ruby's meta-programming, and it works pretty well. I found it very easy to use and helpful for making spec helpers, but they can be used for almost anything.

### Enums
Interestingly enough, Enums are not a part of Ruby, but they can be useful for _known_ message passing. Also, instead of typing out the entire Enum, [you can write it as a symbol](https://crystal-lang.org/reference/1.3/syntax_and_semantics/enum.html#usage), making it feel more like Ruby without realizing it.

### HTTP Client
Crystal has a great built-in [HTTP Client](https://crystal-lang.org/api/1.3.1/HTTP/Client.html), which is a stark difference to Ruby's Net HTTP mess. Making wrappers around it will be common, but you shouldn't need to reach for another client library unless you want a better abstraction. For this project, the built-in one was plenty.

### JSON::Any
I found working with [JSON](https://crystal-lang.org/api/1.3.1/JSON.html) in Crystal to be good for the most part, because of it provides a safe abstraction around it. It's more like Ruby's Hash, when you don't know the underlying type(s). Crystal even makes it easy to make a class around a known JSON shape using [JSON::Serializable](https://crystal-lang.org/api/1.3.1/JSON/Serializable.html), which is highly recommended in most cases.

### Trust the Compiler
One of the biggest differences I found in writing Crystal compared to Ruby, was trusting the compiler. In Ruby, you don't have a compiler, and so any issues will be found at runtime most likely. This is not possible with Crystal, but that's a good thing! I would rather it catch a bug in development than in production any day! This led me to focus less on correctness and more on productivity, which turned out to be a less stressful experience.

## Final Note
The whole project took about a month, only being worked on in my free time. I think time spent was probably about a week. I was able to get the library to be on feature parity with the original Ruby gem. Overall, it was good experience and I learned a lot. I think my original recommendation of converting a Ruby gem to a Crystal shard was correct, it was much easier to do than starting over. Thank you for your time and go write some Crystal!