🗞 Ruby's Data class, Abstract Vision, Borscht
Edition #3 – Experimenting with the new Ruby core Data class, Sharing some inspiration from an Engineering Event, Cooking borscht
Welcome! Here’s an important poll to begin with:
The Data Class 🗃
Recently there’s been some discussion about introducing some new core functionality into the Ruby language as a basis for Value Objects. Then the new Data class has been merged into Ruby and is now expected be included into the next releases. We’re expecting to get it with the Ruby 3.2 release this Christmas 🎄
What is it
The new class delivers a Struct-like functionality for defining classes which you can then use for immutable Value Objects. It is lightweight, easy to use, and gives you some immutability out of the box. It’s something that a lot of projects have built in some form or another, though as a boilerplate. Here you have it right out of the box with the language!
How to get it
You can just install ruby version 3.2.0-dev from rbenv or rvm, or just build ruby from its GitHub repo, and it should work! Or just wait for the Ruby 3.2 release this Christmas 🎄
Using it
It’s easy to define such Data classes. The most common use case, as with Struct, would be to define it inside of another class (like a Service Class), and then play around with the tiny Data class inside, for the ease of processing.
In order to be more concise here, I will only define the tiny Coffee class here, not wrapped into anything, but we can imagine a much more complex class covering (wrapping) the Coffee class (like a CoffeePlace or Barista that brews multiple instances of Coffee for the period of its own existence).
Coffee = Data.define(:country, :roast, :made_with, :recipe_url) do
def announce
puts "Your coffee is ready! It's made of #{country} beans, #{roast} roast, equipment is #{made_with}, recipe: #{recipe_url}"
end
end
So what exactly is this? We’ve just defined a new class called Coffee which has some features out of the box. I will list those features after we have a look at them. For now, you can see that we’ve given the class some attributes – country, roast, made_with and recipe_url. These will be necessary to provide when creating an instance of Coffee.
I’ve also defined the custom #announce method which will be available for every instance of Coffee.
Let’s finally create some objects of that new Coffee class.
coffee = Coffee.new(
country: 'Colombia',
roast: :light,
made_with: :aeropress,
recipe_url: 'https://aeroprecipe.com/recipes/james-hoffmann-aeropress-recipe'
)
=> #<data Coffee country="Colombia", roast=:light, made_with=:aeropress, recipe_url="https://aeroprecipe.com/recipes/james-hoffmann-aeropress-rec...
Let’s take a look at the object now.
coffee.roast
=> :light
coffee.announce
Your coffee is ready! It's made of Colombia beans, light roast, equipment is aeropress, recipe: https://aeroprecipe.com/recipes/james-hoffmann-aeropress-recipe
Let’s try to change one of its attributes:
coffee.roast = :dark
(irb):26:in `<main>': undefined method `roast=' for #<data Coffee country="Colombia", roast=:light, made_with=:aeropress, recipe_url="https://aeroprecipe.com/recipes/james-hoffmann-aeropress-recipe"> (NoMethodError)
Makes sense! You can’t change a roast of a coffee sample, especially when it’s already brewed. We’re getting immutability out of the box, so you can make sure the objects stay the same way as created initially. However, there’s a caveat described in this great article by Swaathi Kakarla:
If some of the data members are of a mutable class, Data does no additional immutability enforcement.
This means there’s still some room for mutability, like changing values inside of a Hash key, so it makes sense to be careful with such attributes.
Summarizing the Features
Easy class definition. You don’t have to set attr_reader and #initialize method. All you do is you list the attributes for the objects of the class. Very handy for defining small Data “structures” inside of other classes, no boilerplate.
Immutability out of the box. Which means you are ensured that nothing gets accidentally changed, especially if there’s a long chain of an object being used in multiple places, or within some multi-thread logic.
You can still optionally define methods inside of this class, just as with regular class definition. I’ve defined the #announce method which announces that the coffee has been brewed.
You have some flexibility of argument passing when creating new instances. Namely you can provide the arguments with their keywords for some extra flexibility (a), or you can directly list the argument values as in mathematical functions or more traditional programming style (b).
Coffee.new(country: ‘Colombia’, roast: :light, made_with: :v60, recipe_url: ‘recipes.com/1’)
Coffee.new(‘Colombia’, :light, :v60, ‘recipes.com/1’)
Potential usages
I might actually see this feature being used not only inside of some service classes that process something and need some structural immutable objects instead of dealing with rather primitive types like Hash, but also as simple models that represent something that’s not persisted to the database.
In both cases they would all be rather simple classes with minimal amount of methods, and not directly related to a table from a database. Think Value Objects.
Ideas from my side
Being exposed to Ruby on Rails and its philosophy on a daily basis, I would also expect the use case where we could simply inherit from a class somewhat close to this new Data class, and then just build our own “models” or Value Objects in a traditional way (not through the newly introduced #define method).
So instead of the code examples above, I would also like to have something like this:
class User < ValueObject # or inherit from Data
# or include ValueObject instead of inheritance
attr_init :name, :age
end
class Ticket < ValueObject
attr_init :id, :user_id, :event_id, :start_at, :valid_until
end
However, this idea does bring some complications, like having to know a special keyword for defining the attributes.
I've posted my question here, and got some reasonable answers from matz and Victor Shepelev aka zverok (creator of this feature).
So let’s see how it goes as people start incorporating this new feature into their projects, and what needs and ideas might pop up in the Ruby and Ruby on Rails communities to maybe further extend this. So far I’m glad that such easy to use features are being provided out of the box, and I also enjoy the fact that they eliminate a lot of boilerplate.
Abstract View 🦅
When I attended an Engineering Event at Getsafe this fall, we as engineers were asked to practice thinking about our company, its vision, goals, things where we could improve etc. And these exercises were separated in the way that the first ones were very abstract, like a high-level vision (actually something that’s easy to forget about when you’re an engineer dealing mostly with code. Luckily at Getsafe I didn’t feel like I was “deep into low-level code and nothing else”. There were various customer-facing, partner-facing, business-facing, data-driven tasks which diversified the ways I see and perceive the company. Still, it was important to abstract even more).
Then, after visualizing everything from the high-level, “vision and goals” perspective, we were asked to think about the ways we could achieve those goals and apply that vision. This could include new tools or technologies to be integrated, changes in behavior/relationship policies, modifying or replacing approaches with other ones if we felt like it etc. So that’s already the “middle” layer – actively discussing and getting on the same page in terms of how we do it.
As we came to the same page on those decisions, the next, lower-level step would be investigating (spiking) and then implementing them. This wasn’t fully the part of the Engineering Event, but rather most of it would come up later, during our plannings for example.
A related situation
As I was exploring the jobs market in Software Engineering, I wanted to get a high-level picture of what’s going on but I didn’t really know how to. I knew there were people proficient in this – recruiters, CEOs, maybe CFOs, but how do I get it all clarified? In the first year of Covid, and now after Russia’s invasion of Ukraine (which lead to corresponding policies, sanctions) it all started affecting economies and markets around the world, and I definitely realized, and heard sometimes, that things started getting worse on different levels: from stocks and investors to jobs and pays. There was a lot of uncertainty and the world didn’t feel as “ideal” and prosperous (at least for a while) as it felt before, that’s for sure.
And so I’ve heard a lot from my environment and circles that things started getting worse in the IT domain as well (obviously) – many companies trying to maximize efficiency, minimize costs, executing layoffs, collecting as much runway as possible in order to survive the hard times on the market. Yet I still struggled to understand how it all was inter-related. I believe I was missing the “vision” of the markets and companies inside them, as well as the “medium” layer which might include ways, approaches and strategies to be applied by companies on the market. It was still an important part to understand in order to adjust my expectations and negotiations accordingly.
Suddenly I found out that Morning Brew, a newsletter that I’m subscribed to (and am not sponsored by it if you’re wondering) actually has a few different editions based on its audience. So there is a separate “CFO Brew”, which, after an instant glance, had some topics that interested me, e.g. this one “Finance chiefs see parallels between the dot-com bust and the current techlash“, which represents thoughts of some CFOs about how they perceive the current situation on the market, what they expect it to be like in the near future, as well as what they suggest doing.
It instantly gave me a better picture of what’s going on – and this time not just the global events like Covid or Russia’s war and Ukraine’s Resistance (these factors being so huge that they affect basically everything around us), but also the layer behind it – namely, how markets and companies are operating under the circumstances, and, more importantly – how executives are thinking and reasoning about their strategies and decisions.
Key takeaways
If you have a “lower-level” job and responsibilities like Software Engineering and it’s not really touching economies or markets or deals on your daily job, it might still be beneficial to start getting a sense of the surrounding world in a more abstract scale.
Newsletters like Morning Brew, CFO Brew are nice because they represent thinking and reasoning behind decisions frequently made by businesses.
Engineering Events are great, as well as the practice of learning to abstract from your position and think about the company and its state on the market, then sync your ideas with others and understand the direction better.
Something to eat (borscht) 🥘
We need the borscht emoji
Revisiting the fact that Ukrainian Borscht and its cooking culture has been recognized by Unesco as “Intangible Cultural Heritage in Need of Urgent Safeguarding”, and just reminding how good it is, my wife and I really wanted to have it last week, so we bought all the ingredients that were needed and made it (my wife mostly made it as she’s much more of a Pro when it comes to borscht than I am).
The approximate recipe is here, although it’s usually beneficial to check out a few recipes in advance, and then take the best of all worlds.
Some twists
Just like the recipe mentioned above, we’ve prepared the borscht with a twist: we also added some slices of smoked apple, smoked pear and smoked plum (the smoked fruits season has just started recently in some local grocery stores). I’ve tried a borsch(t) with smoked pear for the first time in a restaurant in Kyiv last year and it was amazing, and even though it was a vegan option, it was still thick and soothing enough to enjoy it to the fullest. Some smokiness is a great thing for such kind of dish.
In Moldova where I’m from (but also in Romania) it’s popular to add some sourness to soups by adding some fermented ingredient called Borș Acru (which is another amazing sour-herbal twist to soups, including borscht). You could probably replace it with some lemon juice but it’s still very different. I was happy enough to realize that a similar thing called żur or żurek is available at grocery stores in Poland, so I’m going to give it a try.
Takeaways
When done properly, borscht is a great must-have on the menu
Smokiness is a great component for this kind of dish. You can achieve it by adding some smoked fruits
It’s better to use beef or pork rib meat (not chicken or turkey), though it requires slower cooking for a longer period of time. Vegan options are possible too, of course, and the one I’ve tried with smoked pears was vegan
Thanks for scrolling through (hopefully you’ve considered something as useful), and have a great week ahead!