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

Anecdotal random story bits from my current company on why you should stick to Rails:

- All of our "lightweight Sinatra(and similar) API" services eventually start to look more and more like Rails apps. Rails does many small developer convenience things well. Which you do not notice until you build this lightweight API yourself. E.g. console, logging, migrations, database connection pooling, rspec integration, i18n.

- No one likes to work with your arbitrary personal project structure conventions. Where's the code? 'lib'? 'core'? 'app'? 'api'? Also, no one wants to learn your lightweight "data mapper" pattern written from scratch because you thought ActiveRecord is too bloated and "does not scale". That being said, there's quite a few things you can arbitrarily pick in Rails projects as well which others will find surprising. But the spectrum of those choices is little narrower.

- Developers most of the time assume database connection pooling just magically happens. Sysadmins do have no desire to debug your apps. After couple weeks of back and forth you may realise that Rails does database connection pooling for you. And simply requiring 'activerecord' and establishing connection in your Sinatra app does not.

- One day someone doing production maintenance wanted to remind themselves rake task name. And ran 'bundle exec rake' forgetting to add '-T'. Default task was rspec. It dropped production database. That day many learned that Rails has safeguards against things like this, while none of those "lightweight arbitrary structured APIs" had any. Though the lesson was clearly not very good, since we did this again couple years later.




This.

There are so many things that are required in a modern web app that these "batteries included" frameworks provide for you that it just makes sense to use these for most standard projects.

I personally use Spring Boot as my default stack because just setting up a basic boot starter project gets me:

* Routing

* Server side templates

* i18n translation files

* database connection pooling

* database migrations

* ORM

* Logging

* Json serialization

* unit testing, integration testing, mocks

* CSRF

* Validation framework

* Dependency injection

* SMTP email sending

and much more, all compatible with each other

If I would start with a blank Sinatra/Express project I would have to piece together all of these myself from various libraries and my own code.


You could also start a NestJS project instead: https://nestjs.com/


yes, it looks like NestJS is a JS equivalent of a batteries included framework.


But those frameworks force you to do things the way they want you to. As soon as you need to deviate a little bit, and very often you'll have to, you'll have to rewrite the entire thing.


> you'll have to rewrite the entire thing.

No, you don't, because then you'll be able to lean on Rails' biggest strength, Ruby. You don't know how many times I've been able to just dig into a gem's or even Rails itself, find out what's going on, and simply write what I need leveraging all the existing code.

On the other hand, when we were replatforming our Rails project into Node for no good reason, we wound up having to rewrite a ton of stuff because, well, Ruby is a great server language, and Javascript is only at best a mediocre scripting language, and ES6 is only a marginal improvement.


I think that depends on the framework and what deviation you want.

I agree that if you use Rails and constantly override its conventions, you will have a bad day. But following conventions is usually a strength, not a weakness. Most websites are not shocking cutting-edge things that have never done before, but relatively boring simple tasks that involve grabbing data from databases and showing them to users via templates. Every decision you have to make is a cost, and if you have a small team, having basic naming convention decisions and directory location decisions already made accelerates development. There's a big advantage to following conventions when you want others to work with you, because different Rails applications look quite similar in many ways.

I have not had trouble overriding Rails when I needed to override something specific.

Btw, I like the original article. There's nothing that's good for all cases, so it's very important to understand what something is good and not good at.


Sometimes, Rails’ conventions are just wrong, though.

Databases can have various constraints (uniqueness, etc.) enforced at the DB level. ActiveRecord also allows for uniqueness checks, but these are separate mechanisms. If you want to validate for uniqueness, two processes each running the same Rails app might concurrently check for uniqueness, assume validity, and then independently insert two non-unique records. At this point, your DB will throw an error. (Edit: Rails actually wraps this exception now, but if you follow the idiomatic pattern and don’t bother catching it, it just 500’s the request.) Rails is (edit: still) not wired to treat this as a uniqueness violation at all; it just has its own uniqueness validation mechanism that doesn’t even work in the most common use case for Rails.

In a concurrent environment like this with a shared DB, the only way to avoid check-and-set conditions is to just try the insert and only complain about uniqueness violations when the insert fails the DB-level uniqueness check. (Edit: Rails provides a wrapped exception for this use case, but that’s certainly not the conventional Rails way of handling uniqueness validations.)

Conventions are fine when those conventions work and actually enforce good practices. ActiveRecord in particular falls short of this. And without ActiveRecord, the rest of Rails doesn’t do much to distinguish itself from alternatives. It’s fine, but it’s not necessarily anything special.


> Rails does not, or did not when I encountered this issue, recognize the error as anything other than “whoa dude, MySQL/Postgres/whatever is complaining, check this out”

ActiveRecord has thrown ActiveRecord::RecordNotUnique for this and done the right thing for over 11 years (the exception definition got moved to a different file at that point so idk exactly how old it is.)

> ActiveRecord in particular falls short of this

> without ActiveRecord, the rest of Rails doesn’t do much to distinguish itself from alternatives.

Callbacks are pretty bad but it sounds like Rails went through several near-complete rewrites worth of changes since the last time you used it. There's a lot more in Rails 6 compared to Rails 2.


> ActiveRecord has thrown ActiveRecord::RecordNotUnique for this and done the right thing for over 11 years

I had these issues in Rails 3 when that was the newest version. I found the git blame you’re referring to here and it may have been written 11 years ago. Odd. It looks like maybe Rails 4 finally fixed this?

More to the point, the broken uniqueness validation is still there, as is the idiomatic “validate then write to the DB” race condition that leads to this issue in the first place. There’s zero correlation between stating in your ActiveRecord model that you would like uniqueness (or any other constraint) and actually enforcing that constraint in the database, where it actually works. Having an actual wrapped exception to catch is an improvement but it doesn’t fix the problem.


I think this is pretty clearly documented: https://guides.rubyonrails.org/active_record_validations.htm...

"This [uniqueness] helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint in the database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, you must create a unique index on that column in your database."

It's pretty clear from that text that if you want a unique index in a database, it has to be a uniqueness constraint in database itself. But that's true for any such system, that's not specific to Rails.

In most applications there are simple isolated validations you can verify without consulting the database, and other validations that can only be validated by consulting the database (such as uniqueness). Rails can do that, as can many other frameworks. I don't think that's broken, that's how things are.


Rails not only has to consult the database to validate uniqueness, but it still doesn’t know for sure even then whether the row will remain valid. So why not just insert the row and see if you get away with it?

Simply removing the uniqueness validation and relying exclusively on database indices would be strictly better. ActiveRecord dynamically infers from the DB schema which columns your tables have; it could also infer foreign key relationships and uniqueness guarantees and use them to generate pre-insertion “best guesses” about validity if the user wants to run those without necessarily inserting. But Rails is supposed to be an opinionated framework, and “just try to insert and we’ll let you know how that worked out for you, since we can’t make any guarantees otherwise” is a perfectly valid opinion.


This is not my experience. When you have to deviate, which has been quite rare for me, it’s all just ruby at the end of the day so you can do whatever the heck you want.


Every time I’ve seen an engineering organization run with rewriting the whole thing, they/we figured out 6 months later a more effective data model. That’s usually the root problem.

Rails can do many things but no framework can design a product’s data model, and when you have an excellent one Rails will sing.

Also sometimes you need some separate services for GPU/security/reasons. That’s fine.


That's why they are called frameworks, they solve standard problems in a specific way and the good frameworks provide extension points so you can customize when you need to deviate a little bit.

Can you give a specific example of when this poses a problem?


> It dropped production database. That day many learned that Rails has safeguards against things like this, while none of those "lightweight arbitrary structured APIs" had any.

While having a depth of defense is a good idea (and so "+1 to rspec for its default rake command not performing destructive operations" is ok), I'd suggest the bigger takeaway should've been about permissions surrounding the production database. -- You can't accidentally do something you don't have permission to do, or can't do easily.


This is a great point, so how do you handle this?

Rails doesn't have any separate facility for migration account vs production account, by default. Which because our deployment paradigm also doesn't have any such separation, resulted in an awkward conversation with our DBAs where we explained that our production login requires the DDL permissions they thought should be reserved for an admin user.

I think they're right, but there's only one place for setting the value of DATABASE_USER in each environment. So do you handle migrations by hand, out of band with a different user than production uses? (How should a regular Heroku user handle this issue, I guess is where I'm looking for a straightforward answer to this common issue that I think you're absolutely right about...)


The answer is proper 12 factor separation of concerns. The last Rails project I had a role in, I complained mightily about the lack of proper config separation and not keeping your effing config in the codebase. They fell on deaf ears, devs would edit the .env file, it would invariably make it to the repo, and database commands would invariably get run on the wrong database, including prod in some instances.

Worse, the CI/CD pipeline would break when I tried pulling the .env file out of the codebase. It took me a while to figure out why and finally keep all the config in the environment and not the codebase, I breathed a great sigh of relief when I was finally able to add .env to the gitignore.

Set DATABASE_USER and everything else to ENV['DATABASE_USER']. Better yet use connection strings instead, they're easier to manage. Devs need to hand-manage their own environment, I prefer direnv, but you can use dotenv if you have to, just dictate that .env has to be in the gitignore. Every single CI/CD platform and PaaS lets you set environment variables. So no .env files get pulled down on git pull, messing everyone's day up.


Yup, with .envs that can be overrided from the command line, the migration command gets a more privileged user set in its environment for just that command, everything else falls back to the file configured production user. Then you can't even fat finger a migration command because you have to set up the privileged environment first.


This intuitively is exactly what I thought was (should be) the answer, after spending some thought on it.

But is it documented anywhere? One of the Rails 6 bounties is for multiple database configurations to be made clear, IOW in configurations involving a partitioned database cluster, or a cluster with separate main and secondary streaming replicas for read and write locking separately; those configurations are all being made clearer in the latest Rails guides, as I understand it. There were always people that knew the right way to do it, but there was no one blessed right way to do it.

This seems like it belongs in the rails guides as a regular part of the instruction manual. Then again .env support itself is still separately provided by add-on gems, so maybe not. (Thanks for sharing!)


The problem came from fat-fingering a command for DB maintenance while running as an admin. This then ran scripts intended for a development environment. (Development environments don't need to be careful about destroying stuff).

I suggest ideally all the maintenance tasks can be done automatically, without needing the command to be run manually. Such scripts can then run in staging before in prod; or if the scripts necessarily only apply to prod, they should be reviewed carefully.

If prod admin must be done manually, then make you could make it harder to run the development scripts with such admin privileges.


But there is a release phase in Heroku, and reasonably that's the only time you should expect to need DDL permissions...

The principle of least required privilege seems to suggest that the production web server shouldn't have permission to perform DDL when it doesn't need it (so that fat-finger would have likely never happened, or never caused a problem, so long as it was only running in the context of the prod web service.)

It's just a little bit surprising that there don't seem to be any concrete solutions for this, I find it hard to believe I'm the first to whine about this issue related to commonly understood DBA best-practices. I don't know how you solve it, (maybe add a production_migrations environment?)


> Rails doesn't have any separate facility for migration account vs production account.

Writing your migrations in anything but SQL is one problem. (OK, perhaps pl/pgsql too.) Running it with a system user and database role that has access to the web app code is another.

But the major one is that web apps should use very limited database roles that only have execute permission for specific stored procedures. In any environment.

Anything else will lead to security exposures, as we continually see.

Yes, it takes longer and requires you to become proficient in SQL. So what? You might learn to make better use of your RDBMS. If you're working with data, you need to know SQL. And you especially need to know the access control features of your RDBMS.

From the perspective of data security, the answer to "when should you not use Rails?" is always.


What about removing authorization to drop a database?

Creating and destroying databases happens so sparingly, it can be done by some "root" user that is allowed to create the other users.

Granted that ot won't fix the problem if the next operation is to recreate all tables.

I'd argue the main question is: how is possible the test suite runs on the same db as the application? Even for local development it's a nightmare.


more of a Python person here, but in the python world its really similar. People feel like their service is too small / not a good fit for django and build Flask services. I like Flask, its really nice. But no 2 Flask services do look remotely similar, after 4-5 weeks the services have pulled in so many plugions that they might have just used Django or similar larger frameworks.

Sometimes people don't like to use Django because they write fairly stateless services not involving a database, its understandable but that aspect "doesn't eat much hay" tbh.

As a middle ground I often recommend https://trypyramid.com/ which has more structure, a better plugin-integration infrastructure than flask but isn't so much bound to a SQL ORM (while you still can use sqlalchemy just fine).


If you don't need a database at all I kinda understand not wanting to "sandblast a soupcracker" as the saying goes.. but I've definitely seen a very similar trend with Flask apps!

To be frank django has been my go-to for almost every app I've built or prototyped which required a db, sometimes if exposing a REST API is more of the main goal I'll use DRF and then build a minimal frontend SPA using React (served by a separate app within the project). Not sure why it's not taught/used more tbh.


> Where's the code

Honestly for anything of real-world size, that question is not well answered by rails either. Developers tend to stuff everything into either models or controllers until they learn about "service classes" and then they put all logic into service classes, with the same issues.


This is a very good comment. Every complex Rails app I've worked on, we end up having the same debates:

"Our models are too fat, let's refactor."

"Where should the code go?"

"Let's put them in a utility class."

"No, a utility class is a code smell from the point of view of true Object Oriented theory."

"Then let's put them in a service class."

"No, a service class is just a utility class with a different name."

"Then where should the code go?"

A long debate ensues, and then we end up with some crazy solution that includes everything that jeff_vader just suggested as a problematic:

" Where's the code? 'lib'? 'core'? 'app'? 'api'?"


This isn't unique to Rails apps. You'd have code organization tensions in any complex app you write.

Fortunately, for whatever you decide, you can create as many folders as you want in `app/` and Rails will detect & autoload them for you.


I think one of the things I have started to realize is how rarely people in Rails-land tend to reach for more established patterns that are generally thought to be important to writing good object oriented code.

There seems to be a real adversity among many to go ahead and create new classes as needed especially when that new class isn't obviously a model, view or a controller.

I just went over a short YouTube series DHH (creator of Rails) did where he walked through the production code behind Basecamp and I was struck by how different it looked to almost any other Rails app I recalled looking at previously.

But it seemed like there was not a whole lot that they were doing that would seem in any way out of place from a SOLID OO perspective. It's a very interesting set of videos to take a look at what a modern Rails app with a decent amount of complexity looks like as intended by the team who wrote the framework. You can find it here if you're interested https://www.youtube.com/playlist?list=PL3m89j0mV0pdNAg6x9oq6...


We use the interactor gem with great success for this.


I ran into a great "where's the code" on a "lightweight API" ruby project once. Opened up the codebase to fix an issue, and there were no .rb files at all. After opening every file in the repo, it turned out the developer of the code base had the entire service in the Rakefile.


>All of our "lightweight Sinatra(and similar) API" services eventually start to look more and more like Rails apps

Oh man, I've been working as a freelance/agency with a a team of 3 experts in nodejs for 5 years now - we've built API & products for many many clients

Our nodejs/express "lightweight" framework is looking exactly like a rails app now (migrations, controllers and models generator, some scaffolding generators)


to me, this sounds more like "Ruby tools don't have sane defaults unless you use Rails"

Some examples from crufty Java world. You magically get connection pooling with any JPA provider. If you import a logging library, it magically works. Flyway and Liquibase db migration tools have built-in failsafes, and "just work" with defaults.

I think Ruby suffers immeasurably from most things being built for Rails. In Java and Python, there's so many options that everything is forced to be modular when running standalone and play nice with various frameworks.

Ruby is loved for Rails, but Rails is slowly killing the langauge by making it hard to do anything without it. For most purposes, Ruby is Rails. To me this is a good reason to use a different language where I have flexible options


If you import log4j it automatically does exactly nothing and to a total beginner it's absolutely not understandable why a logging framework wouldn't log.

Unless they changed something in recent years, but to me it's been the worst logging framework I've ever seen.


now that everything is using SLF4J all you do is import it and put a config file in one of various supported places in project


But most Ruby devs don't want flexible, they want Rails. And this article makes a good case for why that's a good thing. It's also happening in php world where it seems 90% of projects now go with Laravel.


IMO its happening to languages that are no longer popular enough to maintain multiple frameworks. The difference with Ruby is that it was always mostly rails. I'm worried Ruby will descend into a Rails death spiral. Depending so heavily on one framework stops innovation


I think you’re right to worry, I wouldn’t be able to think of any reason to use Ruby instead of something else if Rails is off the table (in terms of ecosystem, not talking about personal preference here). Which is a shame because I like it the most of all the scripting languages, but Rails just sucks all the air out of Ruby.


Unless you're into data science, what can u do in Python that you wouldn't be able to do in Ruby? Most "famous" gems are still well maintained afaik. The Ruby eco system is still nothing to sneeze at.


I recently used Ruby, Roda, and soap4r for a JSON-to-SOAP gateway project. We didn’t need all of the ceremony of Rails (no database), and needed more velocity than Java could give us, and none of the Elixir/Erlang SOAP libraries actually worked with the WSDLs (plural) we had.

There’s a _lot_ of reasons to use Ruby if Rails is off the table.


Ruby became niche long ago, just have to accept it. It doesn't mean you won't find work in Ruby if that's what you want, plenty of companies depend on it. Rewrites in different stacks are quite impossible sometimes.


Regarding dropping the prod db. I remember when Github dropped their production database because they had prod in their test env setting.

https://github.blog/2010-11-15-today-s-outage/


yeah the claim that Rails is somehow "too much" for an API-only application smuggles in a lot of assumptions about what you might be using instead (and how good your team is at designing their own patchwork of libraries and how desirable that is).


Spot on. Very well said.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

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

Search: