Live data from Hacker News

Rails – The Missing Parts

eng.joingrouper.com

61–70 of 173 posts

Re: Rails – The Missing Parts

#61
post #54

Earlier quoted context omitted.

I've spent the last few months re-writing a medium-sized code-base (several hundred thousand LoC) that looks like your version into code that looks like the blog version. Test suite run time has dramatically decreased, code is more loosely coupled and we see far fewer bugs. > You obviously need to choose the patterns that fit the problem you’re trying to solve – it’s rarely one-size-fits-all, and some of these princi…

Yes, rewriting a system after it's already settled and designed can indeed bring a cleaner code base about. But don't confuse that with "better architecture". Don't even get me started on the hand-wavy "loosely coupled". If this is a poor example, pick a good example. I'll be happy to code ping pong you whatever example you choose. One way to spend hundreds of thousands of LOC on an application is to stuff it with ne…

My contention is that there often are times when the complexity of the business requires a fairly extensive set of operations to occur in concert, including the creation of several models, email-sending etc.

In this case, the Right Place (tm) to put these is in POROs / Interactors that can be tested in isolation and re-used across the code-base if necessary. This code should not live in controllers and certainly not in ActiveRecord models.

It sounds like you agree with that, since you're using them in your own codebase?

In that case, is there not an advantage to standardising the API for these POROs in Rails? Simple methods like fail!, success?, failure? and message

Another way of asking the question - why do so many Rails developers fail to realise that they can use these concepts? Why are so many Rails codebases packed full of enormous ActiveRecord models that are crippled with numerous before_save callbacks?

Sure, shitty coders write shitty code. But can't you lend them a hand?

Re: Rails – The Missing Parts

#62
I agree completely that the Interactor pattern makes for cleaner Models and Controllers in a Rails codebase. I've used both the Interactor gem and PORO (in a directory outside of /app or /lib).

Having worked with the Interactor gem for a little while once you break things down into small interactors that can be used by the Organizers, I have two main complaints.

1) inputs are unclear. With calling new or some custom class method you can use descriptive variable names to say what the object expects to be present to do it's job. With the Interactor gem you end up adding in comments describing what keys need be present in the context and what keys will be added to the context so the next programmer can use the interactor you've created without having to go through and grok everything.

2) You end up having to (re)create a failure protocol to communicate with the controller and display to the user. We take the AR errors functionality for granted in our controllers/views with interactors you have to come up with a similar system.

2.5) as a result you end up writing a lot of boilerplate fail! and rollback code

2.5.2) and a non-atomic operation like notifying the payment gateway can break the whole model of rolling back and you have to end up raising so your user doesn't end up in a invalid state or get charged twice.

Re: Rails – The Missing Parts

#63

I agree completely that the Interactor pattern makes for cleaner Models and Controllers in a Rails codebase. I've used both the Interactor gem and PORO (in a directory outside of /app or /lib). Having worked with the Interactor gem for a little while once you break things down into small interactors that can be used by the Organizers, I have two main complaints. 1) inputs are unclear. With calling new or some custom…

Agreed, especially on (1). I don't love the lack of explicit inputs.

We've got a convention that all Interactors must be commented to specify what inputs they take.

Re: Rails – The Missing Parts

#64
post #54

Earlier quoted context omitted.

I've spent the last few months re-writing a medium-sized code-base (several hundred thousand LoC) that looks like your version into code that looks like the blog version. Test suite run time has dramatically decreased, code is more loosely coupled and we see far fewer bugs. > You obviously need to choose the patterns that fit the problem you’re trying to solve – it’s rarely one-size-fits-all, and some of these princi…

Yes, rewriting a system after it's already settled and designed can indeed bring a cleaner code base about. But don't confuse that with "better architecture". Don't even get me started on the hand-wavy "loosely coupled". If this is a poor example, pick a good example. I'll be happy to code ping pong you whatever example you choose. One way to spend hundreds of thousands of LOC on an application is to stuff it with ne…

> "I detest "oh that was just a poor example, but the general principle is..." kind of debates. If you can't produce a good example, you don't have a general principle." - DHH, March 3rd 2014

That's going in my quote book. Thank you DHH.

Re: Rails – The Missing Parts

#65
post #13

This is great, and does not only apply to Rails. In many other frameworks it's common to lump business logic in controllers instead of models. Either way you end up with the same thing: eventually you'll need to add a 4th (at least) type on to MVC. > Here at Grouper, we’re long-time users of Ruby on Rails – along with other New York startups like RapGenius, Etsy and Kickstarter. Etsy doesn't use Rails though, it uses…

Fixed, thanks

Re: Rails – The Missing Parts

#66
post #7

We tried using DHH's concerns in place for interactors, but ditched them for PORO/service objects because they can be tested outside Rails.

Yeah - I feel like concerns are a bit of an anti-pattern, especially as they're normally used in Rails. You've got a God class that exposes 500+ public methods, so you split it into concerns. But now you've still got a God class with 500 methods that's split across 10 files. Good luck understanding that. Concerns are useful when they're genuinely sharing functionality between classes - not just for splitting up "Big…

> Concerns are useful when they're genuinely sharing functionality between classes - not just for splitting up "Big Bags O' Methods".

Hmmm. I'd say it depends.

Of course having concerns (a fancy word for modules IMHO) shared between classes is great. But i've also found cases where separating a god class' behaviour into different concerns helped a lot. The important thing in those cases was recognizing and avoiding dependencies between different concerns, so instead of ending up with a god class split across many files of inter-dependant code (i.e. worse than before), you can end up with a "god" class split into its minimal "core" part and a couple of concerns that only depend on that core part but not between each other. Yes, you could still call that a god class, as it will have a very fat public API, but at least you can reason about what it does in terms of different concerns, so you gain something :)

Re: Rails – The Missing Parts

#67
I've spent more time thinking about clean architecture and design than most and the conclusion that I've come to is while Ruby gives you all the tools to write clean code and great architecture, it doesn't offer the best tools to do that job.

Ruby and Rails feel best as a prototyping platform for getting something out the door fast, proving a concept, and not worrying so much about correctness or maintenance.

I don't think if you are doing a lot of TDD and larger systems that piling on more Ruby and Rails is the right answer. I think once you know what you are working with, a well designed language with a compiler is a huge help and would remove a ton of useless tests and stuff that you end up writing in Ruby by hand.

This very likely leads to an API driven system with an API written in a strongly typed, compiled language like C#, Java, Scala, Haskell, or Go and writing your front end in whatever makes the most sense for your team.

At that point you get the benefits of a nice rapid development platform like Rails for your front end, and a fast, testable, API written in something else using all the clean code architecture you want.

The trick is, you do everything ugly in Rails or PHP or whatever in your initial prototype and you might not even write tests. You just ship working code until your prototype has proven a business case. Then, you move it towards something with lower TCO over time. Make the investment in your code when it makes sense to invest in it and use the best tool for the job at each step.

You probably never need to leave the standard Rails MVC stuff on most projects unless they are wildly successful and/your long term needs change. Even then, you can probably keep the rails front end stuff and just talk to an API and be very happy.

Re: Rails – The Missing Parts

#68
post #54

Earlier quoted context omitted.

I've spent the last few months re-writing a medium-sized code-base (several hundred thousand LoC) that looks like your version into code that looks like the blog version. Test suite run time has dramatically decreased, code is more loosely coupled and we see far fewer bugs. > You obviously need to choose the patterns that fit the problem you’re trying to solve – it’s rarely one-size-fits-all, and some of these princi…

Yes, rewriting a system after it's already settled and designed can indeed bring a cleaner code base about. But don't confuse that with "better architecture". Don't even get me started on the hand-wavy "loosely coupled". If this is a poor example, pick a good example. I'll be happy to code ping pong you whatever example you choose. One way to spend hundreds of thousands of LOC on an application is to stuff it with ne…

I hope I am not sticking my head into the lion's mouth here, but here (https://gist.github.com/BiggerNoise/9334673) is a link to a gist of some code in one of our apps.

Cases have Tasks and Activities. Activities are always associated with a case, and may be associated with a task.

In the controller, when assigning a case, one would type: AssignCaseCommand.new(params).execute!

The command class implements the Command pattern (the pretty well established name for interactors) and is a Composite as well.

BTW - the same command that assigns the tasks of a case is also used to merely assign a task. And, if we're just recording an activity, we would just use the create activity command.

I think it would be insanity to try and capture the rules associated with re-assigning tasks in the controller. (I am pretty sure that you would agree.)

--Minor Edit for clarity--

Re: Rails – The Missing Parts

#69
post #54

Earlier quoted context omitted.

Yes, rewriting a system after it's already settled and designed can indeed bring a cleaner code base about. But don't confuse that with "better architecture". Don't even get me started on the hand-wavy "loosely coupled". If this is a poor example, pick a good example. I'll be happy to code ping pong you whatever example you choose. One way to spend hundreds of thousands of LOC on an application is to stuff it with ne…

My contention is that there often are times when the complexity of the business requires a fairly extensive set of operations to occur in concert, including the creation of several models, email-sending etc. In this case, the Right Place (tm) to put these is in POROs / Interactors that can be tested in isolation and re-used across the code-base if necessary. This code should not live in controllers and certainly not…

I don't get why POROs would need some special API. I also don't get why you're pulling code out into things like ConfirmGrouper that still depend on the DB. Why not a true PORO and something like ConfirmGrouper.for(grouper)? Why not just write methods that return bools?

Though I don't know if I agree that the actual sending of emails belongs in a PORO either and not a controller. It seems like determining whether or not emails need to be sent is the kind of logic I would put in a PORO and do the actual email sending from a controller method as in DHH's example.

Edit: Just saw DHH's second example, that's essentially how I would write it.

Re: Rails – The Missing Parts

#70
post #54

Earlier quoted context omitted.

Yes, rewriting a system after it's already settled and designed can indeed bring a cleaner code base about. But don't confuse that with "better architecture". Don't even get me started on the hand-wavy "loosely coupled". If this is a poor example, pick a good example. I'll be happy to code ping pong you whatever example you choose. One way to spend hundreds of thousands of LOC on an application is to stuff it with ne…

My contention is that there often are times when the complexity of the business requires a fairly extensive set of operations to occur in concert, including the creation of several models, email-sending etc. In this case, the Right Place (tm) to put these is in POROs / Interactors that can be tested in isolation and re-used across the code-base if necessary. This code should not live in controllers and certainly not…

> In this case, the Right Place (tm) to put these is in POROs / Interactors that can be tested in isolation and re-used across the code-base if necessary.

Key phrase: "if necessary".

It sounds like you're advocating for building abstractions before you actually have a reason to use them. Your reason seems to be "we may need to reuse them in the future" or, "one can see where we might move in a direction where we'd want want to re-use this code", or worse yet, "we're definitely going to build some functionality after this release when we'll want to re-use this stuff."

I've been there, really, I have. Build the abstraction when you actually need it, not when you think you're going to need it, or pretty close to needing it, etc. Because 98% of the time, YAGNI.

> In that case, is there not an advantage to standardising the API for these POROs in Rails?

Not a big enough that I've come across to warrant building domain-specific abstractions and special rules that, as @dhh mentioned, will serve only to raise the barrier to entry to your codebase.

Post reply on HN