Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I keep hearing good things about Rails. What are the downsides, other than learning a new language and framework?


I think from a business perspective, the hiring pool for Rails is small and younger engineers don’t have an interest in learning Rails (check recent university hackathons). It takes a decently long time (2-3+ months) to upskill a non-ruby engineer to be productive in Rails (although this is dampened by AI tools these days) and many senior non-ruby engineers aren’t interested in Rails jobs whereas you can get an Node or Java engineer to come to your Go shop and vice versa. Rails can also be hard to debug if you work in a multi-language shop, you can’t map your understanding of Java or Typescript over to a Rails codebase and be able to find your way around.

All that being said I still use (and like) Rails, currently comparing Phoenix/Elixir to Rails 8 in a side project. But I use typescript w/ Node and Bun in my day job.


If you "screw up and succeed" by gaining many users/customers, any Ruby or Python framework provides orders of magnitude fewer requests-per-second on the same VM or hardware than a comparable solution deployed with node.js, go, Java, C# (Including DotNet Core on Linux), or rust. And this will quickly ballon your cloud compute costs to keep up.


I believe(?) stats have shown that Java, C#, Elixir, Rust and friends are going to be quite fast, but Node.JS is going to be even slower than Ruby. At least, Next.JS (which is on top of node, I think?) will be.


Sounds improbable. Unlike Ruby, JavaScript has multiple runtime implementations with capable JIT compilers that sometimes let it even compete with Java on numeric code. Ruby is very, very far away. Please note that Elixir is also in the same single-threaded performance ballpark as Ruby and Python, of course it does not suffer from any of the single-threaded bottlenecks the other two do though.


Tl; dr: color me genuinely surprised.

---

I have now done several Google searches to - well, admittedly, to try and counter your argument; but what I've since found is:

  * Every friggin' benchmark is wildly different [0, 1]
  * Some of these test pages are obnoxious to read and filter; **BUT** Javascript regularly finds itself to be **VERY** fast [0]

On a more readable and easily-filtered version (that has very differnet answers) [1], * plain Javascript (not Next.js) has gotten *REALLY* fast, serverside * Kotlin is (confusingly?!) often slower than JS, depending on the benchmark ^-- this one doesn't make sense to me ^-- in at least one example, they're basically on par (70k rps each) * Ruby and Python are painfully slow, but everyone else sorta sits in a pack together

I will probably be able to find another benchmark that says completely different things.

Benchmarking is hard.

I'm also having trouble finding the article from HN that I was sure I saw about Next.JS's SSR rendering performance being abysmal.

[0] https://www.techempower.com/benchmarks/#section=data-r23

[1] https://web-frameworks-benchmark.netlify.app/result?asc=0&f=...


FWIW web-frameworks-benchmark is bad and has strange execution environment with results which neither correlate nor are reproducible elsewhere. TechEmpower also has gotten way worse, I stopped looking at it because its examples perform too little work and end up being highly sensitive to factors not related to the languages chosen or may be a demonstration of underlying techniques optimizing for maximum throughput, which in real world is surprisingly rare scenario (you would probably care more about overall efficiency, reasonable latency and reaching throughput target instead). TechEmpower runs on very large machines where you get into a territory that if you're operating at such scale and hardware, you're going to (have to) manually tune your application anyway.

https://benchmarksgame-team.pages.debian.net/benchmarksgame/... is the most adequate, if biased in ways you may not agree with, if you want to understand raw _language_ overhead on optimized-ish code (multiplied by the willingness of the submission authors to overthink/overengineer, you may be interested in comparing specific submissions). Which is only a half (or even one third) of the story because the other half, as you noted, is performance of frameworks/libraries. I.e. Spring is slow, ActiveJ is faster.

However, it's important to still look at the performance of most popular libraries and how well the language copes with somewhat badly written user code which absolutely will dominate the latency way more often than anyone trying to handwave away the shortcomings of interpreted languages with "but I/O bound!!!" would be willing to admit.


yes but that depends a lot on your application and how users interact with it


Downsides:

Rails is a sharp knife. There is Rails way to do things. You may of course choose to do them differently (this is a contrast with other toolkits that fight this hard), but you are going to have to understand the system well to make that anything but a giant hassle.

With rails, the way it scales is statelessness. You have to map the front end actions to individual endpoints on the server. This works seamlessly for crud stuff (create a thing; edit a thing; delete a thing; list those things). For other use cases it works less seamlessly. NB: it works seamlessly for nested "things" too.

Complex multi-step flows are a pain point. eg you want to build data structures over time where between actions on the server (and remember, you must serialize everything you wish to save between each action), you have incomplete state. Concretely: an onboarding flow which sets up 3 different things in sequence with a decision tree is going to be somewhat unpleasant.

You must keep most state on the server and limit FE state. Hotwire works extremely well but the FE must be structured to make hotwire work well.

I've actually found it to work pretty well with individual pages build in react. My default is to build everything with hotwire and, when the FE gets too complex, to fall back to react.

Rails is nobody's idea of a fast system. You can make it perform more than well enough, but fast it is not.

Upsides, my take: it is the best tool to build websites. The whole thing is built by developers for developers. DX and niceness of tools are valued by the community. Contrast with eg the terrible standard lib that other languages (hi, js) have. Testing is by far the most pleasant I've used, with liberal support for mocking rather than having to do DI. For eg things like [logic, api calls, logic, api calls, logic, db calls] it works incredibly well. It is not the most popular toolkit and it's not react, so that can count against you in hiring.


The biggest downfall in my experience has been it can be a massive pain to find out where a method is defined in a huge codebase, especially with all the crazy ways in which one can declare methods. You can spend a non-trivial amount of time just trying to find the definition for a method.


Sorry if this sounds like a stupid question but - is there no "Go to definition" command in an IDE that can help with something like this? I mean, I understand that there is, but it doesn't work well with Ruby. Why?


Other people have mentioned "dynamic typing" as being the reason for this, but that's not actually true. The real reason is two Ruby features: `define_method` and `method_missing`.

If you have a class `Customer` with a field `roles` that is an array of strings, you can write code like this

  class Customer
    ROLES = ["superadmin", "admin", "user"]

    ROLES.each do |role|
      define_method("is_#{role}?") do
        roles.include?(role)
      end
    end
  end
In this case, I am dynamically defining 3 methods `is_superadmin?` `is_admin?` and `is_user?`. This code runs when the class is loaded by the Ruby interpreter. If you were just freshly introduced into this codebase, and you saw code using the `is_superadmin?` method, you would have no way of knowing where it's defined by simply grepping. You'd have to really dig into the code - which could be more complicated by the fact that this might not even be happening in the Customer class. It could happen in a module that the Customer class includes/extends.

The other feature is `method_missing`. Here's the same result achieved by using that instead of define_method:

  class Customer
    ROLES = ["superadmin", "admin", "user"]

    def method_missing(method_name, *args)
      if method_name.to_s =~ /^is_(\w+)\?$/ && ROLES.include?($1)
        roles.include?($1)
      else
        super
      end
    end
  end
Now what's happening is that if you try to call a method that isn't explicitly defined using `def` or the other `define_method` approach, then as a last resort before raising an error, Ruby checks "method_missing" - you can write code there to handle the situation.

These 2 features combined with modules are the reason why "Go to Definition" can be so tricky.

Personally, I avoid both define_method and method_missing in my actual code since they're almost never worth the tech debt. I have been developing in Rails happily for 15+ years and only had one or two occasions where I felt they were justified and the best approach, and that code was heavily sprinkled with comments and documentation.


To add, the above code is a pretty near approximation of the literal code inside the devise codebase, which is a very standard Ruby auth system.

See here:

https://github.com/heartcombo/devise/blob/main/lib/devise/co...

        def self.define_helpers(mapping) #:nodoc:
        mapping = mapping.name

        class_eval <<-METHODS, __FILE__, __LINE__ + 1
          def authenticate_#{mapping}!(opts = {})
That code is *literally* calling class_eval with a multi-line string parameter, where it inlines the helper name (like admin, user, whatever), to grow the class at runtime.

It hurts my soul.


It's been widely understood in the Ruby community for some time now that metaprogramming—like in the example above—should generally be limited to framework or library code, and avoided in regular application code.

Dynamically generated methods can provide amazing DX when used appropriately. A classic example from Rails is belongs_to, which dynamically defines methods based on the arguments provided:

class Post < ApplicationRecord belongs_to :user end

This generates methods like:

post.user - retrieves the associated user

post.user=(user) - sets the associated user

post.user_changed? - returns true if the user foreign key has changed.


Aren’t all these enhancement methods that are added dynamically to every ActiveRecord a major reason why regular AR calls are painfully slow and it’s better to use .pluck() instead? One builds a whole object from pieces, the other vomits put an array?


It's simply not true that "regular AR calls are painfully slow." In the context of a web request, the time spent instantiating Active Record objects is negligible. For example, on my laptop:

Customer.limit(1000).to_a

completes in about 10ms, whereas:

Customer.last(1000).pluck(:id, :name, :tenant_id, :owner_id, :created_at, :updated_at)

runs in around 7ms.

Active Record methods are defined at application boot time as part of the class, they're not rebuilt each time an instance is created. So in a typical web app, there's virtually no performance penalty for working with Active Record objects.

And when you do need raw data without object overhead, .pluck and similar methods are available. It’s just a matter of understanding your use case and choosing the right tool for the job.


Thank you both for the time you spent explaining this.


Mainly because of the dynamically typed nature of the language. Not limited to Ruby/Rails. My colleagues used RubyMine because of this. I'm using Neovim with LSP, it's ok but nowhere near Go for example.


I tried rails when it was pre version 1 and the early stages. I always felt like rails is very powerful and lots of things feel like magic until you come to a point where you want something that isnt implemented in that way.

You can prototype stuff very fast with rails and its a mighty tool in the right hands.


Post author here. I've been developing in Ruby and Rails for almost 20 years. Here are some of the downsides in my opinion.

- The Global interpreter lock (GIL) in Ruby is less performant than async thread programming in JS (and some other languages)

- Rails creates a monolith rather than a bunch of independent endpoints. If you have a large team, this can be tricky (but is great for smaller teams who want to move fast)

- How Rails integrates with JS/CSS is always changing. I recommend using Vite instead of the asset pipeline, unless you're going with the stand Rails stimulus js setup.

- Deploying Rails in a way that auto-scales the way serverless functions can is tricky. Their favored deployment is to server of set size using Kamal.


You have to be comfortable with the ORM in every layer - it lives inside your domain models, rather than in another layer shuffling DTO's to presentation/rendering. It also makes it easy to avoid separation of concerns and stuff all your logic in a controller method and call it a day.

The upsides is that by not trying to hide the database and pretend it doesn't exist you can avoid a whole class of work (and the safety abstractions provided) and be incredibly productive if the requirements align.


Downside? … One day when you’re successful and have large numbers of customers, you might need to migrate aspects of your stack to something else.


Did you miscalibrate your time machine and just make it back?


Instead of being rude and snarky you could just answer the question. Or just not reply at all.

Not everyone has looked into or tried everything.


My bad!


You have to program it using Ruby which is not a good language. It's slow. It doesn't have good static type annotations (as far as I can tell the community "gets it" even less than in Python).

Rails also uses way too much magic to dynamically construct identifiers and do control flow.

The over-use of magic and the under-use of static types makes it extraordinarily difficult to navigate Rails codebases. It's one of those things where you have to understand the entire codebase to be able to find anything. Tractable for tiny projects. For large projects it's a disaster.

Rails is a bad choice (as is Ruby).

My favourite web framework at the moment is Deno's Fresh. You get the pleasure of TSX but it's based around easy SSR rather than complex state management and hooks. Plus because it's Deno it's trivial to set up.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: