Hacker News new | past | comments | ask | show | jobs | submit login
Rails Is Good Enough (onurozer.me)
58 points by onurozer 6 months ago | hide | past | favorite | 49 comments



> Yes, the learning curve can be steep, and the “magic” can sometimes be frustrating.

This is possibly the absolute worst combination of factors possible. The learning curve makes it bad to ramp up with, and the magic makes it bad in the long term.

It's a framework that wants to be used for monoliths, and tooling is essential to manage the complexity of large monoliths, but you're stuck with a language that's extremely hostile to tooling, using patterns that make it even more hostile to tooling.

So, really, what you're left with is a framework that's only actually good for the early-mid stages of a project, and only if you're an existing Rails user who doesn't have to pay the ramp up price.


Ruby can parse itself, it has test coverage, vulnerability auditing, SQL auditing, static and runtime analysis and type enforcing tools…

Rails has an incredibly convenient REPL that's built into every dev error page, a one-line integration with every error, analytics, and log-tracking service under the sun, that will give you all of the insight into production runtime for basically zero effort.

You install Sidekiq or GoodJob in a couple of lines of code, and suddenly you have the entire background job system to tap into for any delayed or periodic workflows that can be offloaded, bundled with monitoring and administration UI.

All you need to do is learn how to do software design, but even there Rails starts you off with arguably the best design decisions you can make at early stage: focus on your entry points (controller actions for requests, rake tasks for CLI), and start extracting domain objects as you learn more about your domain.

Am I missing something? Where's the "hostility to tooling"?


What’s the alternative?!

You can either have a massive Python project and struggle with a lack of types and a tenseness that becomes unreadable at huge sizes;

A Laravel project that’s PHP, which despite being way, way, better than it used to be (types! traits! union type returns! faster than ruby! weak maps! [0]) still has a few footguns left;

Or you can have a JavaScript monster. The worst of all worlds. Your complexity complaints times 10. A nightmare than in 10 years will make a 2008 PHP project look well-organized. A project that has thousands of packages you’ve never read.

On the edges, you’ve got microservices (which turns into a mess); or you can go fancy with Go and Rust (have fun managing the budget). There’s also, of course, C# and Java, but come now, it’s 2024, you’re probably coupling them to a JavaScript monster anyway.

TL;DR: Every large project becomes a mess. Pick your poison. One is hardly worse than another’s. Best to have the devil you know. Even if you do things perfectly with a FAANG level budget, AWS and Azure still have outages.

[0] https://m.youtube.com/watch?v=ZRV3pBuPxEQ


My best experience with long-term maintainability has been a pretty minimal Kotlin/Undertow/Jdbi stack. Today, modern Java (>=21) would've been fine too.


The alternative is GoTH stack - Go + Templ + htmx if you want it simple. Personally I'm currently enjoying a combination of Go with connect-go on the backend for some API routes and Astro with React for all others. Keycloak for auth if you can stomach it.

I've tried many stacks in the last 6 months, including Rails. Too many outdated gems, too much magic, generators from gems doing who knows what with undo only possible because of Git.

A lot of these posts like in the OP are written by dinosaurs [1] and I mean no offence, at some point you have to give modern stack a chance, the developer UX with hot reloading that works without hacks and plethora of ready to use components in React world is just great.

1. https://peterxjang.com/blog/modern-javascript-explained-for-... this was written in 2017 but is still relevant, shows how much the ecosystem stabilised.


How do you do session management in this GoTH stack?

I am not proficient in Go, but I assume that you will go in two directions: 1. Either write the code for it yourself 2. Install a package

Here is where Rails shines: it comes with a battery included so for example, session management is as simple as doing:

  session[:user_id] = user.id
  User.find(session[:user_id])
I don't need to write my own code to handle this basic feature of a web app not do I have to decide what package to use.

Regarding the "modern" tech stack: What is so modern about Go + Templ + htmx?

Rails can work with htmx and has a wide range of templating options for HTML.

And if modern means static typing, I have bad news for you: types are old.

I don't want to say that GoTH is not a good stack. It could be a good stack, but choosing it because it is more modern than Rails is not the real reason.

> Too many outdated gems, too much magic, generators from gems doing who knows what with undo only possible because of Git

What gems have you tried? For building an API with Rails if that is what you can go a long way with Rails in API only mode.


Hey, thanks for the response. First off I want to say that Rails is not a bad stack if that's all you know, it definitely built tons of SaaS companies and that alone means you can go far with it, I'm not here to change your mind. It's also very fundamentally different from barebones Go approach where you do need to pick your modules, define your project structure and have a deeper understanding of request lifetime in general because you are supposed to manage it yourself, things like precise timeouts [1]. In Go, it is typical to do session management with middlewares, do JWT based auth, you can attach specific user info to request context so it is available to handlers post middleware actions. [2]. Go is very barebones with many control knobs and can throw people used to monoliths off, but it's really not that complicated and you don't need to write lots of code to handle things like sessions, post Go 1.22 the built in routing mechanism got good enough to not need any packages for that at all [3].

Regarding ORM, I've grown to dislike them eventually, even though I've used gORM which is very popular in Go land. Currently my SQL queries are handled by sqlc (example) [4] which compile the queries down to type safe Go code at build time.

Types are old of course, and I didn't mean modern = GoTH or static typing, I guess modern in my comment was for node based frontend dev with React/Astro, I won't expand on this in this comment, it's getting long as is.

Strong static typing is ridiculously good once you develop with it for a while, editor support and guarantees you get in the sense "if it compiles, it likely works" begin paying dividends soon after the project grows a bit. Not Rust level, you can write weakly typed Go if you want but that's not the default. I have type guarantees even through frontend/backend or backend-backend boundaries with gRPC/connect without writing any marshal/unmarshal code myself.

Regarding outdated gems, I can't recall now, I tried looking up my history but didn't really find anything specific. I started with barebones with devise for auth and some redcarpet markdown parsing. I had troubles setting up hot reloading, AFAIK it should work OOB with Rails 7 but didn't, then I tried hotwire-livereload [5] which pulled in Redis but also worked sort of janky. Contrast that to how great Vite based project live reloads work for modern frontend or how simple and fast Go backends live reload with Air [6].

To end, if Rails works for you and you know Ruby already then it's a great stack, I didn't like certain things and magic and view Ruby as a dead end skill career wise so in the end I ended up not using it. I use Go daily at my dayjob so it makes sense to just use what I already know.

1. https://blog.cloudflare.com/the-complete-guide-to-golang-net...

2. https://www.digitalocean.com/community/tutorials/how-to-use-...

3. https://go.dev/blog/routing-enhancements

4. https://docs.sqlc.dev/en/latest/tutorials/getting-started-po...

5. https://github.com/kirillplatonov/hotwire-livereload

6. https://github.com/air-verse/air


I’m currently building a new product with Rails in 2024.

It just feels so much more productive than anything else I’ve ever worked with.


I love Rails and Ruby and miss the ecosystem dearly, but the joy dies once you find yourself lost in layers of code without any types or IDE support. This is especially true on large teams.

So what are your options... Sorbet? Does this feel like Ruby?

    sig do
      params(x: Integer)
      .returns(String)
    end
How about RBS? Does writing the equivalent of header files for every function and class feel like Ruby?

So unfortunately I'm no longer an evangelist or practitioner.


This has been a big frustration for me as a new engineer in a Rails codebase: my ability to 'discover' how the system works is extremely limited because I can't 'see' a lot of the relationships between code. I've grown comfortable with just searching the entire codebase for a given string to find a relevant class or variable, but it's extremely failure-prone without types or import paths to follow.

I love the syntax and elegance of Ruby, but it really is maddening how difficult it is to hold all the abstractions and state in my head without the mental boundaries that good type support provides.

I often commiserate with a coworker who came from a Java background (and I from Swift/Python) about how frustrating it is when type-based issues leak to prod and cause trouble, or how it lengthens the development process insofar as determining what a given object _is_ at any point in the logical execution.

I really understand how unit testing became so popular, given Rails's historical popularity: there's simply no other way to move with confidence through the codebase without a plethora of specific tests for each individual component.


Ruby and Rails via rails console or irb is these days pretty inspectable.

Say you want to understand what does `belongs_to` do?

Just open `rails console` and you can do:

  User.method(:belongs_to).source_location
And opening the file that is printed at the specified line, you can discover the following code:

        def belongs_to(name, scope = nil, **options)
          reflection = Builder::BelongsTo.build(self, name, scope, options)
          Reflection.add_reflection self, name, reflection
        end
And moreso that method has a very good comment before the declaration, documenting everything it does

You can also try `show_source` command in Rails console if you have the debug gem installed:

   show_source User.find 
And this will directly show the source code of the `find` method.

I do agree that for methods defined with meta-programming it will be harder to find the methods.

But again here rails console comes with help:

For example if you do:

   User.method(:find_by_name) 
   => 

   #<Method: User(id: integer, nickname: string, name: string, created_at: datetime, updated_at: datetime).find_by_name(_name) <edited by me>/vendor/bundle/ruby/3.2.0/gems/activerecord-7.0.8.4/lib/active_record/dynamic_matchers.rb:65>
Of course, this is mode complex meta-programming code, but you can still go to it, read it, and see what it does.

In case of relationships between entities in the code, you can try Sorbet. It is pretty good these days.


Coming from a paradigm of being able to read code and understand what it does, having to drop into console to poke at things, even just to then find the source for a given function, was extremely unintuitive to me.

At this point I always have a rails console up, just to be able to poke at objects and whatnot, but it feels... clunky. Similarly, when designing system changes I first go through with breakpoints in an existing flow to see what objects and behavior actually are when invoked, because I can't tell just from reading the code half the time--and that drives me bananas.


That seems very Ruby to me:

  sig { params(x: Integer).returns(String) } 

I can read it outloud: Signature is defined as: one parameter called x of type Integer and will return a String.

Sorbet may have some warts, but the example you show is simple and easy to understand.

The `sig` is as Ruby as you get IMO:

  class MyTest
    extend T::Sig
  end

  MyTest.method(:sig).source_location
  => ["...gem/ruby/3.3.4/gems/sorbet-runtime-0.5.11495/lib/types/sig.rb", 27]
And when opening that file we can get something like:

  def sig(arg0=nil, &blk)  
    T::Private::Methods.declare_sig(self, Kernel.caller_locations(1, 1)&.first, arg0, &blk) 
  end

I do agree about RBS being separate files that is something that can stop a coding flow.


Dynamically typed languages aren't out in the cold like they used to be with IDE support. Most of the advanced LSP work going on isn't based on explicit types signatures yet they work well. See https://railsatscale.com/2024-07-18-mastering-ruby-code-navi...


> IDE support

RubyMine is great.


Rails may indeed be good enough, but comparing a full-stack platform (Rails) to a frontend UX framework (React) is fundamentally unfair.


It’s a super naive post considering rails+react is almost a standard these days.

He’s using the built in rails front end templates which React can replace trivially in a React+Rails stack and comparing React to all of the rails stack and complaining it can’t do backend which is not where React fits in.

It’s a cringeworthy post. It should not be on the front page.


Less "unfair" IMHO, and more apples-vs-oranges.

FWIW, the javascript side of Rails is historically the least settled-upon part of the framework. I'm curious how many current Rails projects use React as an alternative over the current "convention" of Turbo. Or Hotwire. Or even UJS for legacy projects.


In our current project it's Stimulus/Turbo on the admin side (maintained by the backend devs), React on the end-user side (maintained by the front-end devs). I haven't met a single front-end dev who prefers the Rails approach. Even after trying to do some light convincing and demoing. They seem to be too committed to the React ecosystem in my experience. I can respect that, too much has been built for it already, and it's generally in high demand.

Plus, Rails story has no clear JSON boundary between back and front, which creates some discomfort (i.e. learning erb) as well. As a backend dev, I'm all-in on Rails in solo projects.


I think the post is making a poorly-worded switch to talking about fragmentation in backend node packages.


Still doesn’t make sense since React isn’t a backend node package.

You can use node to bundle react into a minified js bundle. But that’s typical for any complex js usage. My raw rails apps still have a node step to bundle a bunch of js libraries I need for the front end.

I honestly think the post is just incredibly naive and they haven’t ever needed more than basic rails templated html functionality in their frontend. They can’t see the value in a larger frontend framework because they don’t need it. They tried replacing the entire rails stack with a front end and didn’t understand why anyone would use that and found it painful to do the backend stuff.

Here’s the path for the og poster to grow. Add more and more complexity to the frontend. At some point you’ll want to use the node system to manage the front end js libraries you are using. At some point you’ll want something more consistent to manage the front end across the site. You’ll look to Vue or Angular or React. You add this to your rails app and now you have a very common rails+react stack.

It can’t see a justification for this post apart from naivety.


I agree that this article is just a naive conversation trigger, and that complex front-ends will lead you down this path.

However, there's another path, which is where you keep things on the backend. Unless you're talking about very complex interactive experiences, the amount of front-end code can go from >100% of the backend code, to <5-10%. Backend always implements almost everything anyway, and the question is how much should front-end reimplement on their side too.

Every time I push for smaller front-end, I'm faced with how the team might get bored or not have enough to show on the resume.


There's no mention of React in the post...


The page was edited. It initially state how react was terrible at backend functionality and had a full paragraph on why rails was better in this aspect which was super naive.


One of Rails most under appreciated features is testing. Basically everything you can do in Rails has a way to write an automated test for it. Having good tests is a way to keep momentum as the project grows in complexity.

I love Rails. Every investment I've made in learning it has yielded returns for years. Everytime I learn a new JS framework, its obsolete two years later.


That sounds really interesting. I'm working on a similar autotesting module for my Django project. Would you mind describing essentially how it works and it's capabilities? There are a lot of great ideas in Rails, but I've never had an excuse to learn it.


https://guides.rubyonrails.org/testing.html

Model tests, controller tests, integration tests, system tests. Its got it all. Just run 'rails test'


Ruby/Rails with static type checking is a mediocre experience. I would say it's good enough when starting out, even great. But once you have many teams working on it, you wish for better language strictness, to do safe and widespread refactoring for instance.

ActiveRecord has many limitations when trying to get efficiency from infrastructure (either because you've grown/scaled, or to minimize costs while growing). The main problem is with model instances having actions attached to them, you have to make and jump through hoops to batch the individual callbacks.

Working on a performance improvement project, we discovered that doing ModelClass.new (not doing any I/O and even if that class is empty with no callbacks) is about 400x slower than doing MyClass.new for a plain Ruby class that holds a table's attributes. Processing a thousand of these things in a request with a handful of associated models for each can eat up 100s of ms before doing anything useful.

And even dropping down to using ModelClass.insert_all(array_of_hashes) which bypasses ActiveRecord model instantiation is still slow in the way it generates the SQL before executing it.

You can scale throughput by having many webapp servers (at the cost/constraint of many database connections) but that doesn't help latency per request.

Working at a Rails and MySQL shop is about as close to guaranteed employment as I know as there's always tough problems to solve as a company continues to expand in scope and scale, and always new devs doing their best but still introducing patterns that don't age well.


Rails is Boring Technology these days. https://boringtechnology.club/

That's absolutely meant as a compliment!


Rails is more than good enough - it is a framework fine tuned for productivity and quick iterations.

However, convention over configuration means quicker delivery for small(er) teams, but more discussions once more opinionated developers join.


Hard disagree. "More discussions once more opinionated developers join" is universally true regardless of which frameworks/philosophies you start with. The point of convention being more important than configuration is that it reduces those conversations, because you can rely on the conventions as a default.

If an opinionated developer thinks a different configuration is better, then that helps form the conversation immediately. Everyone is already familiar with the convention, so the discussion only needs to inform everyone about the pros/cons of the alternative.


Point taken. I was actually referring to concepts that do not come out of thr box with Rails.

For example, it promotes use of concerns, which can easily go out of control. Then other people come with singletons, interactors or service objects.

Or, like Minitest, which is fine to begin with, but the sooner you switch to RSpec, you will be better off in the long run.

In my experience, these kind of tooling options tend to fire up almost religious discussions.


Tangentially, YC has a Ruby meetup in San Francisco on Aug 13th: https://news.ycombinator.com/item?id=41059148


I honestly hate rails.

1. Code is loaded automatically and globally. I’ve had weird bugs caused by someone creating a function with the same name in a completely different file.

2. The module system is strange. Why do my file names and class names need to match? Why can’t i just import the files i want like other languages?

3. Includes (aka mixins) are a bad idea. See below.

4. Many of the language features are designed to hide how code works. Quite often i wonder to myself “where does this variable/function come from?” #1 is also a good example of this.

5. Coming from a typed language, the lack of types makes me feel naked and more prone to screw something up.

6. The strange and elitist opinions of ruby fanboys. Sorry but code terseness is not a panacea and “unless” is terrible. It seems like code readability and understandability are less important to this crowd.


> 1. Code is loaded automatically and globally. I’ve had weird bugs caused by someone creating a function with the same name in a completely different file.

Ruby doesn't have functions. It only has Objects, and methods on those Objects. Generally you won't run into method nuking because generally there's no need to define the same class in different files.

Yes, you can overwrite method definitions on objects, you can even do that dynamically at runtime. Yes, it's a shotgun. No, it's not wrong, it's just another tool in your toolbelt.

> 2. The module system is strange. Why do my file names and class names need to match? Why can’t i just import the files i want like other languages?

You can require whatever you want, named whatever you want. Rails only looks like magic in this regard because it's coded around conventions so that it can abstract away all that boilerplate. Somewhere before you try instantiate MyController, it was require'd into the project for you utilizing those basic language features. `config/application.rb` is a good reading point to start exploring how this works.

A tremendious amount of logic in Rails is derived from how you name things. Don't fight this system, learn it. Fighting it will only be pain and agony, and I promise you there is a reason for why it works the way it does.

There is absolutly nothing stopping you from calling `require 'my_module'` to bring "BobsGreatestModuleEver" into context. You can missmatch all you want. I have no idea _why_ you would want to do that, seems like a bad precident, but go wild. Ruby works this way like any other language.

> 3. Includes (aka mixins) are a bad idea. See below. > 4. Many of the language features are designed to hide how code works. Quite often i wonder to myself “where does this variable/function come from?” #1 is also a good example of this.

Includes/Mixins are designed to encapsulate shared functionality. Like any effective shotgun if you shoot your feet off you'll be hurting, so don't shoot your feet.

Use your console! If you've got a class with a method and you have no f'n idea where it's defined, just ask it to tell you.

        irb(main):002:0> MyWeirdClass.new.method(:search).source_location
        => ["/app/helpers/weird_stuff/search_methods.rb", 4]


> 5. Coming from a typed language, the lack of types makes me feel naked and more prone to screw something up.

Eh, I think this is one of those tech religious war topics. I've wrangled a lot of typescript and I cannot say that having types have made me feel protected in any way. If anything, TS in particular, it's felt like just _more code_, and I'm a fan of not having any more code that can be helped.

That aside, the Ruby way is Duck typing. If your code is in danger of receiving arbitrary properties, it's very straightforward to protect that code by just inspecting the properties. If it walks like a duck and quacks like a duck, it's probably a duck.

        def dangerous_method(arbitrary_thing)
          return unless arbitrary_thing.is_a? KnownThing
          raise UnsupportedPropertyError unless arbitrary_thing.respond_to? :each
          
          arbitrary_thing.map { |d| ... }
        end

> 6. The strange and elitist opinions of ruby fanboys. Sorry but code terseness is not a panacea and “unless” is terrible. It seems like code readability and understandability are less important to this crowd.

Ruby is _all about_ code readability and developer experience. `return unless some_condition?` is gods greatest gift to programming and I will die on that hill.

It can go to an extreme. Like every language you will meet your code golfers who want to take things way, way too far, but that's not unique to Ruby. That's just a people thing. During PR review it's not abnormal for me to ask for terse code to be expanded into a longer form simply for readability.


To each their own, but frankly your assertions sound like nonsense to me.

> Ruby doesn't have functions. It only has Objects, and methods on those Objects. Generally you won't run into method nuking because generally there's no need to define the same class in different files.

I was writing a rake task and all I did was write a function above the rake task definition. That function was loaded globally and conflicted with another function. If it’s in a rake file then what object is it a part of? This goes back to my main issue about how you don’t know where shit comes from in ruby and there’s too much implicit magic.

> Rails only looks like magic in this regard because it's coded around conventions so that it can abstract away all that boilerplate

This is what I’m talking about. What you call “abstract away all the boilerplate” I call magical bullshit that makes code confusing.

> Includes/Mixins are designed to encapsulate shared functionality. Like any effective shotgun if you shoot your feet off you'll be hurting, so don't shoot your feet.

Classes already do that. I’d much rather create an instance of a helper object than magically inject methods into my class. It makes the code more explicit and discoverable too.

> I've wrangled a lot of typescript and I cannot say that having types have made me feel protected in any way.

That’s strange because types tell you what a thing is and helps you find where it comes from. It offers protection if you use the type system but to each their own.

> `return unless some_condition?` is gods greatest gift to programming and I will die on that hill.

No, just no. `return if !some_condition?` is far more readable, especially if you have other lines that use `if`. Consistency makes code more understandable. And using `if` everywhere makes it so I don’t have to invert things in my head.

> Like every language you will meet your code golfers

I’ve worked with a lot of programming languages and ruby is particularly bad. Lots of people jerking themselves off about how short their functions are and you don’t see that in other communities as much.


> return if !some_condition?` is far more readable

Maybe it is easier to understand the condition or the boolean logic. I could agree with that.

But readable? No. It is very easy to miss the `!` when reading multiple lines of code. While missing `unless` it way hardwer.

Compare:

   return if organisation_exists?
   return if team_exists?
   return if !user_exists?
   return if project_exists?
with:

   return if organisation_exists?
   return if team_exists?
   return unless user_exists?
   return if project_exists?
Where can you see quicker that it will exit when the user does not exists? For me I need to read twice the lines that only have `if` and I can pick at the first read the `unless`.

Not saying you should use `unless` but it is a keyword, like any other keyword that you learn when you learn a new language. We should not confuse lack of familiarity with readability.


The purpose of reading is to understand. And I don't know what to tell you other than when I see "unless" I have to pause for a moment to flip it in my brain. This doesn't happen with "!".


I dont disagree with the idea that the purpose of reading is to understand.

My assertion is that lack of familiarity (or better said lack of seeing unless used often) is the cause of the hard to understand issue and not that unless is a word hard to understand on its own.

Here is a question to reflect:

How come ! is easy to understand while actually "not" is better (short enough to not consume reading speed while being explicit)? But a lot of languages are using ! so it seems to be good enough for reading.

My answer to this is: because it is used so much => familiarity.


> I was writing a rake task and all I did was write a function above the rake task definition. That function was loaded globally and conflicted with another function. If it’s in a rake file then what object is it a part of? This goes back to my main issue about how you don’t know where shit comes from in ruby and there’s too much implicit magic.

You added a method to the base `Object` object. Everything is an Object in Ruby. Unlike Javascript, blocks in Ruby (of which Rake tasks are comprised entirely of) do not create a private context in which to hide functions. If you redefined that same function name later in the task, it would clobber your original implementation.

> Classes already do that. I’d much rather create an instance of a helper object than magically inject methods into my class. It makes the code more explicit and discoverable too.

You might even want to create a helper class, and include several modules by which to build out the functionality of that helper class. That class might even inherit from a baser class, providing even more functionality. All options are available to you in Ruby.

> To each their own, but frankly your assertions sound like nonsense to me.

It sounds like you didn't bother to learn much about the language, or framework, at all. To each their own indeed.


One of the biggest issues we have with Rails these days is finding Rails devs (at least where I work). Our next project's backend may very well have to be Node or Python specifically for this reason.


Can anyone recommend a guide for full stack devs that have mastered other stacks (e.g. django) and want to learn how to use rails correctly?


The official rails guides https://guides.rubyonrails.org/ are really well done. I recommend starting there.


I've always been curious about what Rails or Laravel would be like as a django dev. They both seem super fully featured and productive, but I guess I've never had a reason to explore either. Would love to know if anyone has experience or opinions on the strengths and weaknesses of each framework (and their respective languages).


I can say the same things about django.


I find that most developers' "good enough" is very poor


Dotnet MVC is better.


Why?


it just works




Join us for AI Startup School this June 16-17 in San Francisco!

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

Search: