The intention behind this article is to help newcomers to the Cuba library. I’ve used Cuba in production and for my side projects and came to love the expressiveness it offers, or more accurately, the way Cuba harnesses the expressiveness of Ruby.
I’ve noticed a couple (literally, 2) common pitfalls when people start using Cuba and I’m afraid that they might leave the wrong impression of what the library is all about, so I will point them out and how I think they can be solved.
The following list is by no means exhaustive and is subjective to my experience, so feel free to reach out and tell me what you think.
Deeply nested blocks.
on method evaluates different conditions to decide how to route the request, and we also have some helpers like
post, etc, so many people are inclined to write blocks like this:
Cuba.define do on get do on "users" do on ":id" do |id| on "profile" do # code code end end end end end
You can see how this becomes uncomfortable. It’s better to pass all the arguments to
on, so we can refactor the above to:
Cuba.define do on get, "users/:id/profile" do |id| # code code end end
One of Cuba’s strongest points is that you can combine and change the matchers for your routes however it feels better for the code inside. We could, for example, add more routes as follows:
Cuba.define do on get, "users/:id" do |id| user = User[id] on "profile" do # render profile page end on "settings" do # render settings page end end end
And then refactor to:
Cuba.define do on "users/:id" do |id| user = User[id] on get, "profile" do # render profile page end on get, "settings" do # render settings page end on put, "profile" do # update profile page end on put, "settings" do # update settings page end end end # or Cuba.define do on "users/:id" do |id| user = User[id] on "profile" do on get do # render profile page end on put do # update profile page end end on "settings" do on get do # render settings page end on put do # update settings page end end end end
Etc. How you nest
on blocks and how you group matchers is a matter of convenience and taste. You’ll get the hang of it after a while, just keep an eye for deeply nested chunks of code.
I believe that many people are under the impression that Cuba’s only way to access the parameters of a request is through the
param matcher, so I often see things like the following:
Cuba.define do on "api" do on get do on "home", param("a"), param("b") do |a, b| res.write "Hello World!" end end end end
Though this works, it may lead to unexpected results. If you request
GET /api/home?a=foo you will see a 404. But why?! My route is there, the verb is right, the path is ok, what the hell is going on?!
The problem is that the
param matcher requires the request to have that particular parameter. In this case,
param("b") is preventing the request above to go through, because it’s missing the
Keep in mind that each
on call is saying “match all of the conditions or keep going”.
This example is ok if you want to enforce certain mandatory parameters for a route, but if you simply want the values you can use
Cuba#req is just a
Rack::Request (see here), so you can use all the helpers already available, like
req.params. Let’s refactor:
Cuba.define do on "api" do on get, "home" do a, b = req.params["a"], req.params["b"] res.write "Hello World!" end end end
I’m not saying to avoid the
param matcher, just keep in mind that when you do use it, you’re filtering out requests without those parameters.
What you think?
Let me know if you find other painpoints when starting with this library. We can chat and extend this post with more tips for everyone taking their first steps with Cuba. You can drop me an email or find me at freenode’s #lesscode channel.