- The document describes the author's experience working on a Ruby project for the first time over the course of a month at his job. He was tasked with building a REST wrapper API to make developers' lives easier by handling annoying request signing logic.
- To complete the project, the author learned Ruby by reading books and documentation, setting up the project with Bundler and Rake, writing tests with RSpec and Guard, and considering options for request handling, response objects, and project structure before ultimately releasing the API on GitHub. The author found Ruby enjoyable to learn but encountered some challenges around mutability and "magic" that caused bugs.
6. What was I supposed to build?
- Thin Rest Wrapper
- But why?
- Make developers life easier.
- Annoying request signing logic
- Document and provide examples for the APIs
26. Too much magic
- It was all great until this:
/hashie/dash.rb:41:in `block in property': wrong number of arguments (1 for 0)
(ArgumentError)
from /hashie-3.2.0/lib/hashie/hash.rb:48:in `flexibly_convert_to_hash'
from /hashie-3.2.0/lib/hashie/hash.rb:34:in `block in to_hash'
from /hashie-3.2.0/lib/hashie/hash.rb:20:in `each'
from /hashie-3.2.0/lib/hashie/hash.rb:20:in `to_hash'
from /hashie-3.2.0/lib/hashie/hash.rb:42:in `to_json'
Thin Rest Wrapper around a collection of API’s that Wix exposes to third party application developers.
How do you choose tools when you have no experience and no one to ask? You use google :) and find ruby toolbox. This really helped me and almost all (I’ll talk about exceptions in a minute) of the tools that used are based on the stats from this place.
IRB - Although scala has the repl and I always found it useful, what I loved about the IRB and found more convenient is the fact that you can simply require gems and play around with them.
Coming from the world of Scala/Java where you need to choose between maven gradle or sbt for your project using bundler and rake was like a dream come true. And what I truly loved about them is the simplicity and the fact that the dependency management is separated from the tasks part! No more xmls or spaghetti code for dependencies, build ,package and test tasks.
Guard running the tests. Amazing when coming from Scala!
No compiler no types I need to test everything! During my first week this was the attitude that I had and I wrote a whole bunch of unit tests that had nothing to do with behaviour! But I quickly realised that I’m trying swim against the tide here. I heard/read various opinions on how you should write more unit tests when you are coding in a dynamic language but what I actually found the most useful are integration tests!
But say you had a test that tested the system/component from the outside? Would that help? IMHO it might and it might help you catch bugs faster and thats why we need integration and e2e tests….
As we spoke just a minute more often than not we need to refactor some bits of the system or in this case library. This will involve changing sometimes multiple components and although unit tests are great in this case they were not enough for me to feel comfortable making those changes. Also early in that one month I found myself creating bugs :) and these tests made sure to keep me in line.
OK I realised I needed integration and end 2 end tests but I now had to add them to the project.
What I wanted was: 1. Fast integration tests to run with guard. 2. E2E tests to exercise the library against a test app.
After playing around a bit I realised that the tests in both cases are the same the only thing that was diferent is that I wanted the integration tests to run faster :) And as I already learned by now: There must be a GEM for it :) and there was VCR.
Its an awesome tool basically it records the HTTP interactions and enables you to re-execute them in a fast manner locally while you are developing. Exactly what I needed.
Another nice thing about VCR was that the interactions were saved as yaml files and were very readable so I found myself looking through them to resolve some issues both on the server and in the library iteslf.
OK now that I had the tests set up it was time to actually write the gem…
By this point I’ve already had a few weeks of coding in Ruby… The first impression that I had about the language was how easy it was to pick it up and how naturaly it was to write code in Ruby. The code that I would write was almost like english making it the most redable language I’ve had the pleasure to work with.
Not everything is pink in Ruby land though :) Mutability was something that I had to get used to coming from Scala.
Here is the outline of how I wanted the gem to look like:
One entry point…. meaning I the client would have to know only about a single class and easily pull it in.
Minimal configuration… again I wanted developers to be able to require the gem and be able to start using it in a matter of min without having to go through a ton of documentation.
Optional extensive configuration… for more advanced clients I wanted them to be able to configure anything from logging to http timeouts to headers.
With that in mind and having looked at a couple of examples :) I set out to write the gem.
First thing that I loved about Ruby was the modules! Lets look at the example of the structure that I was able to give to my code. So lets start with the client it pulls in the Rest API. The rest api defines the different groups of apis that it has. And finally lets look at the first one. One thing that was hard to get used to is that you can call methods from the parent object but that again comes down to me not being used to the dynamic nature of the language :)
Now that I had this walking skeleton in place it was time to actually make some http calls and make the first e2e test pass.
??similar to Scala traits they are similar in the sense that modules are mixins and mixins are traits that can have state.
Although the title of the slide looks like there was some battle :) between the two. To be honest there was not after maybe a day playing around with the two these were the takeaways:
REST Client is for sure easier to use… It has a simple interface BUT but you pay for that simplicity by the lack of extensibility.
This was the thing that made up my mind about Faraday! My code was encapsulated in small “racks” that I pluged in and they did one very specific job! For example the error handling [show slide].
And also it fit nicely with my initial idea to expose a fair amount of configuration to the advanced end user.
Once I had the the whole thing set up and got my first response back from the gem I was not very satisfied with the response… :) I got a hash back… this was not the usage I had in mind. How I actually wanted this gem to work is that the user would receive an object back that he can access. For example: [show example]
So I started creating this kind of objects by hand and added some parsing logic to populate them it looked great until moment I actually wanted to send a request to create a contact for example… and that looked like this [show example] and this was also not what I wanted… and if that was not enough I realised that I also need some validation on the objects.
And again before long I applied the most valuable lesson in Ruby that I’ve learned so far and that is: If you are doing something complicated stop there is a gem for it :D
And there was Introducing hashie the best of two worlds [show example] with hashie I was able to give the flexibility for developers to choose their own style in which they read or create objects.
I was making rapid progress with Hashie. Things were so easy :) I even took it a step further and generated some of the classes from a predefined json schema. Until I got this error. First things first this didnt tell me anything to be honest so I started debugging and after maybe a day or so I figured it out :) one of the responses that I was getting contained a field named method. And guess what hashie did when it found a field like that? Well it declared a method called method :) and kaboom you loose one of the base object methods. And its just a ticking bomb until this pops up somewhere on runtime.
Lets start off with the fact that I had to solve my problem. It was too late to go back and do a reafctor on the library because of one “small” issue. So I resorted to I suppose everyone in this room has done once or twice in their Ruby career and that is Monkey Patching. I suppose you have read heard argued on this subject so I wont take too much of your time and I will break my view on it down to 3 simple points.
Obviously the good part is that the language enabled me to go around an issue or to add functionality to a class where needed. This fits nicely with the OOP open closed principle.
Well the bad thing here is that I can override or practically erase a behaviour of any class. Thus violating the “closed for modification” part of the open closed principle.
Once you monkey patch for the first time it works like heroin :) you just want to do it more and every problem looks like a perfect fit for monkey patching. And if you are not carful you can endup with an ugly unmanageable code base.
And although I lack the experience to speak about this in the Ruby world we have something very similar in Scala called implicits which is basically allows you to extend a class with new behaviour. But what happens usually when you learn implicits for the first time is that you use them everywhere you go crazy with them. And then good luck reading the code or debugging through it.