It may not be the fastest programming language, nor is it the most popular language when it comes to the numbers game, but how can you not fall in love with:
10.times { print "Hello!" }
(instead of something like for(var i=0; i<10; i++){ console.log("Hello!"); }
)
Do things with sets like:
["a", "b", "c"] & ["b", "c", "d"] # gives you ["b", "c"]
[1,2,3] + [4,5] # gives you [1,2,3,4,5]
["a", "b", "c"] - ["b", "c", "d"] # gives you ["a"]
Or (with a little (opinions differ on this one ;)) help of Rails):
10.days.ago
That offers you exactly what you would think it would return: the date of 10 days ago.
And no ;
’s, only brackets when absolutely needed, everything is an object…
Yes, it’s actually a language a human might understand, and still: it is pretty powerful, powering some of the most popular sites on the web, like AirBnB, Shopify, Basecamp (they’re the creators of Ruby on Rails), Github, Kickstarter, Twitch, Strava and many more (most of the full stack projects I did & do are using ruby).
Enjoyed this? Follow me on Mastodon or add the RSS, euh ATOM feed to your feed reader.
Today's review had a final note: I'm nitpicking a bit on code naming. The approaches you take are good, but naming is hard:
> There are only two hard things in Computer Science: cache invalidation and naming things.
(see here a list of variations on these two hard problem 'jokes' in Computer Science)
I enjoy the ruby programming language because you can get a long way just assuming things. They might say, never assume things, because a lot of things are chaos, but I prefer to work from assuming things, and if it doesn't match to my assumption choose:
… and/or maybe, I am just wrong and I should level up my understanding of things. We all have our internal models of how things work, some…
It is good practice to leave your database in a consistent state. There are different ways to do this. Foreign key constraints, indexes, typing of columns, are all strategies to keep your database in a consistent state. Transactions are another way providing you a tool to keep the database consistent: if one of the inserts or updates fail, your database will rollback to the state before the first in the series of inserts and updates within that transaction.
Some languages make it really simple to create a transaction. In Ruby on Rails it is simply opening a block:
User.transaction do
## ... all db operations are now in a transaction
end
But be cautious; transactions don't come for free: they lock the table or row, which is bad for performance. It can, by design, stop other processes from updating the same rows. And all this gets worse when transactions take longer, when for example they contain request to remote resources.
Hence, my approach to transact…
I'm fond of data-URI's (MDN Link). 12 years ago I reappropriated a tool that stored a webpage with its related resources in a Microsoft specific format and rewrote it into something that would store it in normal HTML where the related resources were encoded in data URI's. Recently the topic came up again at a project I was working in, where microservices are still a thing. And while discussing it with colleagues it seemed as if knowledge about this quite useful URI-scheme wasn't on top of everyone else's mind. Instead, the original idea was, we could upload the resource to S3, pass the link, download the resource from S3 at the receiving end, and then have some policy that takes care of deleting it… nah…
This is the most simple data-URI:
data:,Hello%2C%20World%21
You [can open it in your browser](dat…
Are you sure?
Decorators decorate your class with new set of functionality.
What are your decorators doing? Adding a few rendering specific methods to a class to help with rendering? Perhaps you should consider Presenters. But better: how will it scale, can it be grouped, will it really add the simplification. Be wary of too quick branching off functionality to decorators. Most cases I've seen them were overly architectured, and they didn't bring much value.
One might consider using Concerns or mixins as an alternative. The disadvantage here is that your main object gets more public methods, but I consider it as a feature after having experienced too much potentially reusable functionality grouped arbitrarily away in other presenter / decorator classes.
I'm quite familiar with writing ruby code. I've even expressed my love for the language long time ago. But some constructs still bug me. One of them is unless…
I've seen variants of this code:
temperature = 36.7
unless temperature > 37.2 || temperature 35.5 && temperature < 37.2
puts "Healthy temperature. Temperature is within the normal range."
end
or perhaps even nicer:
if (35.5...37.2).include?
puts "Healthy temperature. Temperature is within the normal range."
end
There are a few hard problems in computing. Correctly handling time, naming, preventing off by one errors… sorting text may not be one of them but recently we ran into a discussion where I couldn't make up my mind anymore. Hence, this post's topic: sorting text.
How do you sort the following words:
If you'd ask ruby I'd get:
%w[cheese Ape Drums dent Beer].sort
Results in:
Which in my useless and ramshackle programmer's brain translates to, well why not, it is sorted right?
But then we moved the data into a database which was correctly set up with a proper locale for 'collation', a term that I've seen but never meant anything to me until this problem. Collation is:
> the assembly of written information into a standard order.
(thanks Wikipedia - Collation)
Databas…
So I wrote a few short articles on when to use FormObjects and Jobs and ServiceObjects. The question is of course "it depends", but the leading principle I have is keep it simple. That being said, for inspiration, some suggestion for different layers to manage the application complexity from Vladimir Dementyev's talk on Railsconf:
Whenever your model gets too heavy?
The easiest way to clean up your classes might be to create smaller, more concise methods. The next easiest way of tiding up your models is moving stuff to modules (whether they are 'Concerns' or not). Modules can then be included in the final classes. It will lead to a crowded list of methods exposed on these classes, for which alternative solutions exist (Presenters, Decorators), but if you shield off private methods nicely and have a consistent way of naming things, I wouldn't be too concerned about that. Note that having many modules used in only a single class might be a code smell: perhaps you're trying to do too much with that single class.
When you're using Rails, you can make use of Concerns. They offer a few advantages over traditional modules, so use it whenever you're bothering recreating the same behaviour using plain old ruby Modules. I prefer consistency, so if you've adopted Concerns, use con…
When necessary.
It depends. By default I would advise against them; not creating Form objects to receive and validate data that could be validated by the Model directly. Even when you have a few nested attributes that belong to the main model modified, I would advise against Form objects. Keep It Simple.
But… sometimes you have more complex forms that don't fit the database-mirroring ActiveRecord model as nicely.
Always.
When you are able to do stuff async (not blocking the web-request), make it async. It will also reduce the need for a category of Service-objects. Worker or Job objects can often be called inline if desired.
Sidenote: I personally prefer the "Job" object name, a Job that needs to be performed. Worker is a name that was popularised by Sidekiq, but Sidekiq moved to Jobs as well.
I've got some opinions about certain ways of setting up more advanced (mostly Rails) applications. These might be short 'posts' which I might return to later. Let's see how it goes :)
Never.
There is of course never an absolute answer to stuff but if you are running it in a background job anyway have you considered directly writing it in a Worker or Job-object? Note that you can always run jobs async when needed.
My main objection against service objects is that all too often they are ill defined as a category. So while having fat controllers or fat models may be a bad thing, just creating a bunch of somewhat arbitrary 'Services' is not making the code more manageable.
When considering adding a 'services' directory to your app, try to think of what class of problems you want to tackle. And when in doubt, just keep messing around with the somewhat fatter models & controllers.
A simple OAuth provider. See below for more information, or check out the source of CentralLogin on GitLab. To integrate it with your ruby-apps, use the omniauth-central_login
gem.
This app builds on the foundations of the Doorkeeper, Doorkeeper::OpenidConnect and Devise to provide a central login system.
While Doorkeeper supports other OAuth flows, CentralLogin focusses on OpenID Connect as it is a more complete, and hence useful standard, for most use cases where you want to support authentication & authorization.
This project builds on years of juggling with different authentication providers and implementations. It may cut corners to be a pragmatic and less flexible solution which you can host on your own. You don't have to tie your users to a closed authentication system such as Auth0, Azure Directory, Cognito (the horror, really, stay away from it) or something else. In the past I've been a happy user of Keycloak, which is definitely way more advanced than this project, but it in the end it is a Java application and hence harder for me to maintain and not focussed on what I think are the core requirements :)
So, are you in the market for:
Dit artikel van murblog van Maarten Brouwers (murb) is in licentie gegeven volgens een Creative Commons Naamsvermelding 3.0 Nederland licentie .