December 31st, 2015
(written by lawrence krubner, however indented passages are often quotes). You can contact lawrence at: firstname.lastname@example.org
A lot of things in Rails are are anti-patterns in large code bases, but pragmatic in small ones. ActiveRecord itself is a prime example: when you start an app, putting your business logic directly in ActiveRecord objects works pretty well in most cases, but later on as the models proliferate and grow, you realize that some of them contain business logic which is far too complex to warrant being conflated with persistence concerns. The result is difficulty grokking the higher level business logic that crosses database table boundaries, and potentially slow tests because you can’t reliably test complex logic without hitting the database. Of course it’s easy enough to add a service layer in a Rails app, Ruby is very flexible, but there’s no convention for it so there is a high barrier for making this decision since you lose the benefits of shared patterns.
Some might argue this is bad and Rails got it wrong not to anticipate this problem, but I think it’s a good decision to avoid incidental complexity for the 95% of Rails apps that will never grow beyond a certain point. In fact this is a significant reason Rails was able to get traction: in 2004 Java had solid, proven solutions to all possible concerns, but the combinatorial complexity of configuring and making them all play together made simple apps take 10x as long to get working. It’s smarter to ship fast and then refactor based on real-world feedback than attempt to implement the perfect architecture before you even know if you’re designing the right solution.