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

Don't put the logic in activerecord models at all. Not even the data. Use activerecord uniquely as a querying mechanism (read or write), don't use relationships and don't put validations in there.

Create objects (aka behavioral objects, aka servoce objects) for the logic and create entities (plain ruby objects) when you need to pass around the data.

Yes, you are essentially eliminating the entirety of activerecord.

After 10 years of rails, you realize that is the only safe way to use that library.

By the way

User User::SignUp Are related. The behavior doesn't need to be in the same object, the namespace takes care of that already.



> Use activerecord uniquely as a querying mechanism (read or write), don't use relationships and don't put validations in there.

I'm sorry, but without relationships - why even use AR for querying? And how? Via connection.select/execute?


It's hard to answer, in the sense that yes, you can use ActiveRecord relationships for the purpose of _building a query_. The point is, you should use them only for that. And it comes with the downsides of distancing you from SQL, which is not negligible.

The problem is if you try to access relationships that you didn't load from the object, that shouldn't be done. It's the autoloading from the database that is the real problem.

If you have enough discipline and enough authority to ensure that never happens within the software you are working on, you can.

It's really culture problem. As soon as you pass around a `user`, someone will type `.posts` on it, or `.save`, suddenly your business logic depends on the database shape, rather than on contracts. What if your database shape is wrong? What if your database shape needs to change?

The goal of good architecture is to be resilient to change, or to even _postpone the choice_ to a moment in time where you have more data to make the right one.

The safest way is: query with activerecord, map to plain ruby objects and discard the activerecord ones immediately.

This will also help you discover the entities you didn't know about, for example, if you have a table of users identified by email, that's the "User" table in Rails. However, let's say that you take only a subset of that table (with select), such as "Full Name" and "Email": this could represent something different. A Newsletter::Subscriber for example.

And the newsletter subscriber entity can be used in the Newsletter::Weekly::Send object, as well as the Newsletter::Unsubscribe. If you realize that this should not have been in the users table in the first place (I have no idea myself), the cost of change is way lower than if you passed an activerecord object around.

I hope this answers some of your questions. There is a lot to say on the topic. I'm happy to chat more on slack or an online videoconference session


Thank you for taking the time to answer, and also to amw-zero below.

I think I understand more of the sentiment now.

> As soon as you pass around a `user`, someone will type `.posts` on it, or `.save`

I'm not much of a fan of the active record pattern, and I can see (have seen) how optimistic/naive developers can get in trouble - but I feel these are two separate problems:

> someone will type `.posts` on it

This doesn't have to be all bad, just make sure there was a join or include first - perhaps via a scope (eg: User.with_posts).

I suppose some rails apps end up doing "much more than crud", and then it can be easy to stumble. But I find that with some modicum of discipline, "fat models" can go a long way towards making "slim controllers" fall out naturally.

> someone will type.. `.save`, on it

This why I use AR objects, so I can do crud. Sure if dealing with "posts" (plural) you want to avoid: posts.map... save in favour of update_all.

And while there's save! - data integrity belongs in db constraints. But validations are a great tool for better ux and error feedback. The major problem today, is probably that you'd want to (only) run them as client side js.


I did delete this post, I'm not sure how it came back. I wasn't happy with my answer.

I would avoid any logic in the models, they already have 3 responsibilities (data gateway, data holder and validation).

For CRUD apps, Rails has competitors that can't be overlooked, there are alternatives: PostgREST, Hasura + Forst Admin provide full crud UI with borderline no code.

In all honesty, you don't have to look far. Elixir + Phoenix, which is not free of mistakes, manages to be a better Rails than Rails itself. The validation is isolated, the database access is isolated from the model, no autoloading is involved.


That is very interesting approach! I’ve never worked on a project that doesn’t use AR relationships e.g has_many... at all.

Do you have an example app or a blog post exposing this pattern? It would be very interesting to see how this actually works. Thanks you very much for bringing this up!


They aren’t saying to not use has_many. They’re saying to not use the association method that gets put on the AR object from has_many. For example, if a User has_many Posts, they’re saying to avoid calling user.posts.

That may seem weird, but the association still has value: for querying. You can still write: User.joins(:posts) for example.

The problem with the association methods is that they aren’t really methods because they always execute a query. A lot of longtime Rails developers get tired of the database being involved in every step of the way in a request, because it leads to a lack of separation of concerns.


Unfortunately I don't. I work less and less on the CRUD part of the apps, however as I recommended in another comment, Phoenix (Elixir's alternative to Rails) does that correctly: associations are used for the purpose of querying, not as methods. In combination with isolated validations and the data gateway isolated, it's easy to achieve separation of concerns and have a more maintainable code-base.


I am constantly guarding against AR callbacks as well as the insane number of queries that come from lazy loading relationships.

Might have to explore your suggestions a bit.


Good idea. The lazy loading has been particularly bad in the code-base I worked on recently, to the point that pieces of code thought to be entirely isolated from the database were actually accessing the entirety of it.

Callbacks to me are just an indicator of what is missing, an object that encapsulates the "user case". There is a need to do "other things once the data is written", but that is specific to the location where the writing is happening. Modifying a post content from an administrator's perspective could trigger an email to inform the user "your post has been moderated", but the user modifying it's own post shouldn't. Yes you can have callbacks with IFs, but the use-cases will keep piling up. Now you have a rake task that fixes some posts' content due to a bug in the whatever part of the code parsed the text. It shouldnt' trigger the email, but you do have a callback in there.


You're correct on all fronts. Domain driven design, POROs- all of it. I bet you maintain something nice!


Thanks, I really appreciate that.

I do my best, but the sad reality is that I made an insanely large amount of mistakes in my development life, I didn't reach these conclusions without doing damage myself.

I try to give back by training developers and making sure the business gain awareness so they don't have to experience the complete halt of feature development when the software becomes unsuited for changes.


Not arguing your points, just a wondering: Why stick to Rails then? Without ActiveRecord Rails is a pretty big bloat with a mediocre view layer. Routing is nice but I'm sure Sinatra could do that as well.


The majority of Ruby jobs is on Rails. All I need, personally, is a server like Puma, a router like Roda and the pg gem really. Then of course it depends on the application you need to develop.

I would probably need some library to coerce string values to types (boolean, integer) for forms, and a mapping mechanism of some form to convert a pg result in a data shape I control.


So why use AR? Sequel is a thing.


That's a breath of fresh air to read.




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

Search: