An Overview of Ruby on Rails 7.1 Features. Part III
web.archive.org
web.archive.org
But you wouldn't want to use it for a fully interactive desktop style application with lots of interacting components.
Sadly, everyone things they have such a big budget and they're building the next Figma. Even if you're building a CRUD app. (I'm talking to you previous team of mine).
Complexity is tricky. Are three simple things more complex than one thing thats harder to understand?
I've been doing Rails dev for 7 years now but I just don't see it winning over TypeScript/nodejs frameworks. The only advantage Rails still holds is the out of the box batteries included package. But once a TS framework gets this, Rails will be in decline.
Sorbet is great on vanilla Ruby code that doesn't try to use too much arcane metaprogramming. It's "okay" on vanilla Ruby code that uses some arcane metaprogramming that humans can reliably reflect in RBS files. In a Rails codebase, which is (IMO) a huge ball of relatively hard to statically analyze magic and quite heavily uses all sorts of tricks for either API ergonomics/skimmability or for performance, it was a mess, and frankly, I had a pretty nightmarish time selling almost any of the feature set beyond (1) ADTs/Enums (2) the subset of Interfaces as exposed by `T::Struct`.
Sorbet is a herculean effort by some extremely talented devs. I don't at all want to discredit that. Tapioca is likewise an impressive DSL-reflection machine on top of RBS files, particularly post-0.8 release. But type safety in a Rails app is, at least in my experience, extremely high cost both upfront and maintenance.
That said, type safety via Sorbet/Tapioca is ultimately an afterthought in Ruby and the DX suffers from it compared to languages where type safety is baked in. In my limited experience, MyPy in Python was even worse though.
As for RBS, I have literally never seen it used in the wild and I don't know why the Ruby core team bothers with it rather than go all-in on Sorbet.
Anyway, I did have my strong suspicions that Sorbet would pay off more in a codebase that used it from Day 1. Glad to hear at least one report that indeed, that was the case.
Certainly, if you let gems start to get out of date the dependency chains to get them updated can become a task but that's fairly true of any language. It's one of the selling points for moving parts of an app to isolated services too.
Everything is a tradeoff though. I don't know that you can get the Aspect Oriented Programming gains that you get with Ruby and Rails with a more strictly typed language.
There is an entire market on Google of companies expert in upgrading Rails, that to me is a bad signal.
(notice, I have almost 15 years of experience with rails)
No,there are still many undocumented breaking changes. I recently upgraded an old app from Rail 4 to 7 that used minimal non-rails stuff and was shocked by the corner cases.
Manu changes to activerecord api go completely undocumented, but they break the code nonetheless
I actually found a bug that broke my app at version 6 that was resolved in 6.1,so technically my tests were not passing in version 6 and I did an "unsafe upgrade" to version 6.1
I think you're comparing a language/runtime with a framework here. I also prefer Node and JavaScript over Ruby but I get what you say, and I agree.
I think Adonisjs ticks all the boxes to be that framework in the future. It just doesn't have the community it needs to be 100% successful yet... with more adoption it will get all the missing features, documentation and little annoyances it might have today polished.
But something I'm worried about is if that the fragmentation is just too big at this point. I don't see people agreeing on a single "full stack" framework as it happened in other environments.
I think this happens because most other "big frameworks" were born when there were not that much competition and not that many people on their ecosystems, so there were a lot fewer options and people concentrated around those frameworks and made them grow to what they are today (Laravel, Rails, Django, Spring...).
The "backend JavaScript" ecosystem is a freaking mess. So many competing options, all of them terribly incomplete, all of them claiming to be "full stack" just because they can execute JavaScript on the server... yes, maybe "full stack" but without the battery... it's a lamborghini that you have to put together yourself before being able to use it.
Edit: i really like seeing "overview" posts like these to get up to speed on new features because many devs seem to rarely work on "up-to-date" versions
It's not really the "normal" way of using Rails but seems to function quite well on a "large scale". I agree it's probably not just the language change itself that's the reason but also due to the agility/advantages of Node compared to a Rails "monolith" - and I'm already starting to speak outside my familiarity of the architectural goals of everything
Generally I'm interested in stories of these big do-overs because it hasn't been the smoothest of sailing for us.
I’ve been on teams in the past that had to deal with rails apps (legacy code usually) and I found ruby to be really difficult to understand. There are so many ways to do one thing, and with the rails addition there are now so many conventions to follow. Then when those conventions don’t work anymore, and the code base goes off the rails (no pun intended) and the out of the box conventions no longer make sense.
Granted most of my experience is writing Go, followed by dart, swift, and typescript. I have a hard time navigating codebase of a dynamic language and it’s a pita to have to rely on tests (not everyone writes good tests).
I use Go for all my backend tasks and it is not only fast performance, but really easy to be productive in because there is usually one way to do things and the std lib is amazing.
Where does rails fit in? Like I can build an entire production ready backend service that can scale in like 2 days with Go that is easy to deploy and maintain, and not need to bring in any big Go framework or libs besides like uuid, managed sdk, or db driver.
Why would someone choose rails over Go or even node JS with express and a few middlewares? (In 2023)
Go is procedural.
They're different.
I'm asking why someone would choose rails to build software (a company) than something like Go which is IMO easier to learn and way easier to actually manage.
For example, you use a library which gives you 99% of functionalities you want. Now there's a 1% use case you want different behaviour and you can't directly modify library code. There's an escape hatch here, it's Ruby. It's like "fork" on steroid.
Why Rails ? Because it's the "only" web framework in Ruby. No second choice for large scale application.
from a cost perspective, again I'm not super experienced in rails but enough to debug production issues and ensure stuff works -
I recall that for this one rails application, there were like 30 instances of it running on some beefy configurations and it was getting hammered quite a bit. We saw it was only able to manage like 300 req/second. A lot of time was spent rendering the template, like a few ms.
I ported that thing to Go and it takes microseconds to render a template. A single Go application running on cheap, shared configuration can handle like 10x the traffic.
Less code >> more performant in general use case.
TL;DR is: Vendor lock in vs performance lock in. It's hard to not have both at once.
I still am not convinced to use rails over Go for my needs, but I am glad you helped me get some more insight.
A second issue is that if you come from a background where everything is either explicit or obvious, you are in for a world of hurt. I've seen senior developers try to figure out where a particular method is defined by setting a breakpoint, connecting a REPL, then invoking some special methods to get Ruby to tell them the file and line for the source definition.
It is just a different way of developing, and (personally) I think the reputation is a bit cult-ish.
If you wanted to write a small backend in Ruby, you would typically use Sinatra.
So you’re comparing apples to orchards. :)
You can use Go for making websites (its a general programming language) in fact we use it for exactly that. You can render templates easily.
This is my one pain point with JS actually. I've tried getting comfortable for literally years. Sometimes when i look at how stuff is done in JS, it's so incredibly alien to me. I just can't wrap my head around what is happening.
I was hoping it would go away with time. Unfortunately it doesn't. Maybe it's the "flexibility" of the language or something.
Whenever I have to use typescript, I use es6 classes with static methods on them. Helps me keep no internal state and everything is organized.
1. REPL. Huge. You can be insanely productive with it. If you're integrating with APIs and therefore dealing with complex nested data structs, you can explore them live. You can explore what's possible with any lib, try things before you write any code. You can also debug errors on the fly as soon as they occurred, right in the browser.
2. Your app is organized around entry points by default. Nobody knows the proper architecture up front for a brand new application. Rails gives you rake tasks for CLI, controller actions for web requests, ActiveJob as a convention for background jobs. You can grow your app's inner architecture over time, but you got all the entry points laid out up front, and they rarely ever change.
3. A lot of built-in security risk mitigation. Cross-site request forgery, encryption for cookies and sessions, convenient encrypted configs and secrets, log filtering functionality, escaping everything by default, and many more. Everything is already handled out of the box.
4. Veteran open source packages (gems) with large companies sponsoring maintenance. Decades of dev and polish on most needed gems, still going.
5. Analytics and monitoring out of the box. Completely wired for tracking performance of everything out of the box. No need to manually integrate performance trackers, just one-line install and you have full profiling capabilities of every part of the app, with many popular dashboard services.
6. Error monitoring out of the box. Many ways to notify exceptions via chats, email, and services, with typically zero integration work.
7. Completely thought-out testing environment out of the box, for both integration and isolated testing. Clean slate on every test without manual setup.
8. Lots of Ruby and Rails tools/functions (e.g. Enumerable) for transforming data via functional pipelines (select, reject, tally, map, take_while, etc) out of the box.
9. Ability to write incredibly reusable code to solve a problem for all data types, not just one particular type (this has gotten better in Go with generics).
A lot of those things make sense to me and I can see why people would prefer to use a framework like that.
I guess for me, I already know what a web service needs like adding CORS, setting up csrf protection, cookies, logging, etc. so its trivial to add that to a project whether its Go or Node or whatever. Conceptually I know what is required. As for project structure clean architecture has helped me basically not have that be an issue.
I guess its a matter of trading high performance and robust scaling for developer convenience. Like I said to another comment, I probably won't ever use rails but I understand now why people use it. It is probably good for teams that have "fresh" employees like folks out of college or something to quickly be up to speed without having to hand hold too much.
Thank you for taking the time to explain your points.
If you setup your Go app structure like a Rails app structure (basically what clean architecture is), then you have it covered. However, I believe you do have to set it up by hand each time in Go, or write your own generators.
> It is probably good for teams that have "fresh" employees like folks out of college or something to quickly be up to speed without having to hand hold too much.
Architecturally speaking, yes and no. Rails does a lot of non-obvious (to new devs) things for very good reasons, that these devs would have to spend quite a bit of time to understand. Try to write Rails without knowing all the wheres and whys, and you will quickly become frustrated. In Go, you can kinda guide new devs along as you build out most things from scratch. Both of these situations require guidance from experts.
Syntactically speaking, the opposite is true. Ruby lets you write incredibly expressive code by giving you nearly limitless flexibility. In newbie hands this could become a disaster.
It's a common misconception that Rails is beginner-friendly. Its authors have been trying to correct the record on that, by using sharp knife analogies. You have to be very careful with Ruby and Rails, since there aren't nearly the same guardrails as you get with Go.
The thing I think you're missing, is that it's trivial TO YOU to do this, and of course you will understand it end to end and be supe productive.
What happens when more people join you to work on that project? What happens when you leave and the next developer needs to understand all of this? What if he doesn't agree in the way you did things? Did you write thorough and detailed documentation for all the decisions you've taken? Can you guarantee it has no security flaws? What if the translations library you've picked has gone unmaintained because the developer that was building it as his side project got something more interesting to do on their weekends?
That's the problem. Ending with a custom snafu that once made sense to somebody. Of course this can be made, and of course the next developer can pick it up, refactor, improve, etc.
But from a business point of view it just makes literally zero sense. Think about all the time (and thus money) spent integrating things or writing your own infrastructure code and documenting it (because you're documenting it right?) and testing it (because you're testing it right) that could have been spent on actual business code.
Compare that with the situation where you've just picked Rails/Laravel/Django/etc. The next developer knows what to expect. Knows how the most important parts work, has tons of documentation and material for learning, and when applied to the position they already knew more or less what to expect.
The benefits of a "batteries included" framework is not the framework per se, is the community around it.
Of course you can use dry-schema for that purpose, but i do think the framework should enforce that for the whole ecosystem to follow.
Schema layer purpose is to enforce invariant at the boundary of HTTP.
class ApplicationForm
extend Portrayal
include ActiveModel::Model
class << self
def from_params(params)
new(**filter_params(params))
end
def filter_params(params)
params
.require(model_name.param_key)
.permit(*portrayal.keywords)
.to_hash
.transform_keys(&:to_sym)
end
end
end
class MyForm < ApplicationForm
keyword :first_name
keyword :last_name
validates :first_name, :last_name, presence: true
end
Can use it in a controller action like form = MyForm.from_params(params)
if form.valid?…
# the usual
One of the good things about this approach is that you can also use this object in form_for/form_with in your views.It's a bit dated now, but, I did a short presentation for my local ruby meetup and the slides are here [1]
1. https://slides.com/patrickdavey/rails-5-2-attributes-api#/25
attribute :my_time_at, :datetime, default: -> { Time.now }
attribute :persisted, :boolean, default: false
What I like about the types is that it will automatically coerce the messy user data automatically (this is what AR is doing under the hood anyway right?), you can just be more intentional about it.Anyway, glad you're happy with portrayal :) I definitely agree with you on the "freeze, read-onlyness" etc. being good things to have!
It wasn't lack of defaults, it was handling of defaults. Portrayal does a couple of smart things like evaluating defaults in a specific order in the correct context (while still only evaluating once), such that this becomes possible:
keyword :name
keyword :greeting, default: proc { "Hello, #{name}" }
I like being able to do this. (This also works gracefully with subclassing.)The coercion argument is good, but I'm not a fan of doing it implicitly. (This was the real counter-argument I should've made). I prefer to have a single place where input is entirely processed, such as a `from_params(params)`-style constructor. In that constructor you could either coerce, or do anything else with the input prior to passing it through to `.new`.
We're missing the 'form' part. Validations should be done against forms, not models. Default forms can easily be derived from models, which is nice for simple apps, but this is really something that's lacking in rails.
Permitted params exists because there is no form (dry-schema for example). There's ActiveModel, but it's not the same, and it's not ad-hoc / bound to the view/controller layer of things.
The enforcement of allowed params (and type casting from strings) definitely seemed like it was in the right place per-action in the control layer though, so we kept that from the forms even though we implemented it in the controllers.
I wonder if we just didn't understand the idea though? Or maybe if the original implementers didn't implement it right?
typed_params {
param :data, type: :hash do
param :first_name, type: :string, optional: true
param :last_name, type: :string, optional: true
param :email, type: :string, format: { with: /@/ }
param :password, type: :string, length: { minimum: 8 }
with if: -> { current_bearer&.admin? } do
param :metadata, type: :hash, allow_blank: true, optional: true
param :role, type: :string, inclusion: { in: %w[admin user] }, optional: true,
transform: -> (_, role) { [:role_attributes, { role: }] }
param :permissions, type: :array, optional: true, if: -> { current_account.ent? } do
items type: :string
end
end
end
}
def create
# ... controller code here
end
Each param schema has to be defined by source, e.g. request body vs query string. And params aren't intermixed (including routing params).Even with the evolution of Ruby on Rails and adding more rich feature sets, I wouldn’t recommend it as an out of the box Webapp BECAUSE upgrading to newer versions of RoR sucks.
The more time I invest in trying to like RoR the more I end up having to fork gems to patch them because upgrades cannot be made smoothly.
It so easily integrates ActiveRecord, and heavily encourages it, that when you want to pull out of using it it’s a huge pain.
Test suite combines unit and integration testing.
It’s been a net loss imho to learn and work with it over the years.
I’ve been waiting to be persuaded otherwise.
Edit: apparently people disagree with me but haven’t countered the points, is HN just full of Ruby fans?
You can also fork/vendor the gem and fix it, vs rewriting the whole thing from scratch. If a gem is abandoned you don't need to nuke it - you just take ownership of the code. Essentially if you use a gem you are saying "this code is as good as our code" so just maintain it just like it is your code.
And you can use https://railsdiff.org/ to check what you're missing in upgrade apps. Anything other thing that breaks would most like come from another gem, which doesn't make it Rails' fault or Ruby itself, in the case of Ruby, you can track everything to the source to fix it. Ruby keeps a change log diligently. Everything is trackable, at least in my experience.
Even with very high test coverage on a large app it is a huge and high risk effort to upgrade - there are so many deprecations and unnecessary behavior changes. The `belongs_to` now validates presence was especially egregious and arbitrary. Of course you can turn it off with a variable - but now we have a rails app that is a mix if defaults from 4 different versions of rails.
Every single Rails update is extremely painful and issues will slip through every unit test and browser automation you can come up with. Stuff that would be trivially catchable with type checking.
But upgrading pure API Rails projects is much less painful
> upgrading to newer versions of RoR sucks
This hasn't been my experience at all. Rails comes with a built-in tool that interactively helps you upgrade your app `rails app:update`. Even on a 300k+ line Rails app I work on, our last major Rails upgrade took a single engineer about a week to do. It could be better, but it's one of the easier frameworks to upgrade, in my experience.
> The more time I invest in trying to like RoR the more I end up having to fork gems to patch them because upgrades cannot be made smoothly
Isn't this the case generally? Dependencies are liabilities. Not sure why this is a problem with Rails?!
> It so easily integrates ActiveRecord, and heavily encourages it, that when you want to pull out of using it it’s a huge pain.
ActiveRecord is a central component of the Rails framework. Why would they encourage you to use an alternative?
> Test suite combines unit and integration testing.
I think it's first important to state upfront: there is no single canonical definition of "unit" or "integration" tests. I assume your point is that the standard unit test examples in Rails hit the database? That being bad is, like, your opinion, man. And you totally can decide to not design things that way.