Common pitfalls in Cuba
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.
Cuba’s on method evaluates different conditions to decide how to route the request, and we also have some helpers like get, 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.
The param matcher.
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 b param.
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 req.params. 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.