I spent a couple of years dreaming of getting paid to work in Ruby and Rails and about 7 years doing so. There are many things I loved about Ruby, and Rails brought a ton of good ideas to web development, as this post describes (though I don't agree with all the highlights).
I've since moved to using Elixir and Phoenix, and then even more recently, done some consulting on a Rails project. So the contrasts are on my mind.
My perspective now is that the advantages Ruby/Rails have over Elixir/Phoenix (community size and number of libraries) are circumstantial, and the advantages Elixir/Phoenix have (fault tolerance, concurrency, speed, less requirement for external tools) are inherent in the VM.
Yes, speed of development and maintainability matter. And yes, you can write good or bad code in any language. But if Rails taught us anything, it's that defaults matter. And the default pattern in Rails is to use ActiveRecord and rely heavily on model callbacks, which can get confusing quickly. ActiveRecord also has no way to turn off lazy loading as far I know, and chasing down N+1 queries to improve performance is something I spent far too many hours doing.
My productivity also wasn't helped by the long test run times of Rails applications, or by wasting time trying to optimize view rendering on a heavily-used page.
All of those are either non-issues for me now or at least greatly reduced - Phoenix view rendering is crazy fast, N+1 queries aren't possible with Ecto and it doesn't have callbacks, Elixir test suites are highly concurrent and generally fast (though driving a headless browser is still rather slow).
Performance and concurrency do impact productivity. And I find that "functions in modules" is a great way to structure code and makes it easy to refactor. So does being able to run the entire test suite faster than I can get distracted.
The best ideas of Rails, in my opinion, are present in Phoenix - things like structural conventions, English-like naming, and database migrations. But many of the pain points are missing.
Phoenix and Elixir aren't the One True Way™, aren't the best for every conceivable software problem, etc. And surely they'll be superseded. But in my opinion, Rails already has been.
Hmmm, I don't want to assume too much (because of course everyone's project needs are different), but that just sounds like a terrible idea. I guess there are solutions like goldiloader (I've never tried it), but 99% of the time I'd rather not load associated records unless I use them. When I need to load/use them along with many parent records, it seems pretty obvious that I'll want to include those associations (eagerly loaded) in my AR query to avoid N+1 queries as you mentioned. Then again, maybe I've just spent too long taking those assumptions for granted where newer devs might not.
EDIT: After looking more into your claim that "N+1 queries aren't possible with Ecto", I think I have a better idea for what you might mean. Perhaps you don't want everything eager loaded, but you want an exception to be raised if you try to access an associated record that hasn't been preloaded. I suppose that's a fair point (probably good practice if having any N+1 queries will be a major problem in your project, or if subpar performance really is your biggest threat), and no, I don't know of a way to do that in AR.
> Perhaps you don't want everything eager loaded, but you want an exception to be raised if you try to access an associated record that hasn't been preloaded.
Yes, exactly.
> When I need to load/use them along with many parent records, it seems pretty obvious that I'll want to include those associations (eagerly loaded) in my AR query to avoid N+1 queries as you mentioned. Then again, maybe I've just spent too long taking those assumptions for granted where newer devs might not.
You know that and I do too, but legacy Rails apps tend to be full of N+1 queries in my experience, and it's a major cause of slowdowns.
The bullet gem has helped me solve this problem in specific applications before, yes.
My higher-level problem is that having lazy-loading on by default allows N+1 queries to creep in to a code base and you need a third-party gem to find them. I've had to spend significant time finding and fixing this after joining teams with large legacy apps.
"Disable lazy loading globally" should be an ActiveRecord setting and it should be on by default IMO; people who need lazy loading should have to turn it on per query, something like `query.allow_lazy_loading(post: :author)`. I suspect that a very small fraction of queries would use this, since many apps don't do server side rendering, many who do SSR don't use Russian doll caching, and even those who do both still execute many queries that should prefetch all the associations they use.
Yep, I used it extensively on one project, configuring it to raise exceptions if it detected an N+1 query and gradually enabling it test by test as I fixed them. It's very helpful.
The problem I had with the bullet gem however is that some times you actually want N+1 queries. Especially with Russian-Doll caching.
I want my cache key to be lean, only fetching one record without its associations, because if the data is cached, I don't need any further queries anyway. If I "fix" my N+1 queries and load all associations, I actually negate the benefits of the nested caching and incur a cost I can avoid.
> The problem I had with the bullet gem however is that some times you actually want N+1 queries. Especially with Russian-Doll caching.
I know it sounds like I keep beating the same drum, but to me this problem seems like "we have slow views, therefore we need to sprinkle conditional caching logic everywhere, therefore we need to allow lazy loading, therefore we need to watch for N+1 queries."
> By contrast, by compiling templates to functions, Phoenix automatically and universally applies this simple view caching strategy: the static parts of our template are always cached. The dynamic parts are never cached. The cache is invalidated if the template file changes. The end.
With fast views, no lazy loading is needed and no N+1 queries need to be possible. I don't think this is a problem inherent to Ruby, just to the way ActionView and ActiveRecord currently work.
If the query was expensive and could be run periodically, I'd either create a materialized view that gets refreshed as often as necessary (maybe by https://dockyard.com/blog/2017/11/29/need-an-elixir-dependen...) or use a GenServer process for caching that data (as you might do with Redis otherwise).
I don't think the view rendering itself would ever be a performance issue, but rendering a view is just a function call in Phoenix, so I could take a similar approach there - store it in a GenServer.
I love how every time there is a post about Rails, someone preaches the holy grail of Elixir/Phoenix.
I think you are missing the forest of the trees, regardless of whether or not the success of rails is circumstantial, the fact remains that Rails is in the lead position because you can build a business on top of it.
It is a solid, battle tested, mature framework that one can use and scale it (up to a certain point) and beyond that, it might get harder but scaling is never easy and I am sure you would agree that Phoenix is not a magic bullet when it comes to scaling.
Again,I don't have to recount the number of services built on rails that thousands of people depend on today: Shopify, Github, Gitlab,..
Us engineers think the only thing that matters is your rendering time.. but it's not , the main thing that execs care about is the bottom line and how fast we can push products to production.
All that being said, I agree that Phoenix is great but let's not kid ourselves, we are here to make cash first and foremost.
To any young devs out there, I strongly encourage you to not pick a framework by its hype but evaluate it based on your system needs / maturity.
> I love how every time there is a post about Rails, someone preaches the holy grail of Elixir/Phoenix.
I can understand how you might feel that way. I tried to make clear that I don't think it's the perfect answer to everything forever, but to specify ways in which I've personally seen it as an improvement. And given that the OP is about the continued merits of Rails, don't you expect some commenters to express disagreement?
> the main thing that execs care about is the bottom line and how fast we can push products to production
True. But that matters not just on day 1 but for years to come. Sandi Metz in the Ruby community teaches that point well.
I've seen Rails patterns like ActiveRecord callbacks lead to buggy, confusing code, greatly hindering progress on feature work and requiring significant debugging effort.
Certainly lots of businesses have succeeded with Rails, and I've worked for some of them. Lots of businesses have succeeded with Python, Java, .NET, PHP, and Node, too. That in itself doesn't argue for which one to pick when starting a new project.
After experiencing difficulties with my Ruby tech stack, I started looking around, and Elixir is where I landed. At this point I'm invested and biased, just as the OP is. But bouncing back and forth between the languages recently has confirmed for me that I prefer Elixir for the reasons I gave above.
> To any young devs out there, I strongly encourage you to not pick a framework by its hype but evaluate it based on your system needs / maturity.
I'd suggest young devs pick tools based at least partly on job postings, and on that measure, Rails will win by a landslide. But keep your ear to the ground. When I started as a developer, I was using PHP, but I kept hearing how Rails was better. Learning it and moving to work in it was a great career move for me.
Something will come after Rails. Something will come after Phoenix. There will always be a new something. Don't get too caught up in the hype, but don't get too attached to your current tools, either.
Hey, I am so glad to hear this. My sentiments are somewhat similar. My Elixir project didn't get traction so I am back to Rails and some other tech, but definitely Elixir is the future.
Now as far as Rails go, this can be my super power.
I see a lot of Nodejs project struggle and reinvent things that people in Rails community take for granted. Like Nuxt.js for example.
I haven't used Node, but I think it's funny how they tout asynchronous IO via callbacks. Meanwhile the BEAM has that plus asynchronous computation - you just write synchronous code and let the scheduler pause your BEAM process as needed.
If you don't have to write code to deal with the computer pausing your unix process to run a different one, and you don't have to write code to deal with the VM pausing your code to run garbage collection, why would you have to explicitly write code to deal with a pause for IO?
Callbacks suck, so write service functions for complicated CRUD. Working around ActiveRecord defaults is like the 2nd thing you learn to do at a serious Rails shop. I'm afraid you may not have been working with sophisticated Rails engineers, because we certainly modify defaults in our applications.
Elixir is hot, but it's hard to learn and will always have a barrier to entry that Rails won't. Rails and .NET will live longer than us.
> Callbacks suck, so write service functions for complicated CRUD. Working around ActiveRecord defaults is like the 2nd thing you learn to do at a serious Rails shop. I'm afraid you may not have been working with sophisticated Rails engineers, because we certainly modify defaults in our applications.
I'm not sure why you feel like you need to demean my experience to discuss this. Callbacks are extremely commonly used in Rails apps and not at all the province of Rails newbies. Here's DHH in 2018 telling you about how Basecamp uses callbacks: https://www.youtube.com/watch?v=M3JPTOTqsnE
And even if you don't like callbacks and never use them, at some point you're going to change jobs or consulting clients and land in a code base that's full of them.
> Elixir is hot, but it's hard to learn and will always have a barrier to entry that Rails won't.
Maybe so. I can't predict to what extent it will catch on, but it's paying my bills. If I get to stay in a happy little niche, that will be fine with me.
I've since moved to using Elixir and Phoenix, and then even more recently, done some consulting on a Rails project. So the contrasts are on my mind.
My perspective now is that the advantages Ruby/Rails have over Elixir/Phoenix (community size and number of libraries) are circumstantial, and the advantages Elixir/Phoenix have (fault tolerance, concurrency, speed, less requirement for external tools) are inherent in the VM.
Yes, speed of development and maintainability matter. And yes, you can write good or bad code in any language. But if Rails taught us anything, it's that defaults matter. And the default pattern in Rails is to use ActiveRecord and rely heavily on model callbacks, which can get confusing quickly. ActiveRecord also has no way to turn off lazy loading as far I know, and chasing down N+1 queries to improve performance is something I spent far too many hours doing.
My productivity also wasn't helped by the long test run times of Rails applications, or by wasting time trying to optimize view rendering on a heavily-used page.
All of those are either non-issues for me now or at least greatly reduced - Phoenix view rendering is crazy fast, N+1 queries aren't possible with Ecto and it doesn't have callbacks, Elixir test suites are highly concurrent and generally fast (though driving a headless browser is still rather slow).
Performance and concurrency do impact productivity. And I find that "functions in modules" is a great way to structure code and makes it easy to refactor. So does being able to run the entire test suite faster than I can get distracted.
The best ideas of Rails, in my opinion, are present in Phoenix - things like structural conventions, English-like naming, and database migrations. But many of the pain points are missing.
Phoenix and Elixir aren't the One True Way™, aren't the best for every conceivable software problem, etc. And surely they'll be superseded. But in my opinion, Rails already has been.