For a long time I fell in the trap of not fully distinguishing Ruby from Rails, so
I had a lot of code that relied on
ActiveRecord objects. This led to a lot of
small classes in the root models folder that were easily visible to each and every
other class in the project. This ended up letting classes leak all over the place,
until most classes use other classes and simple updates were never simple anymore.
Recently, I've begun trying out a pattern that's new to me, but that I've seen in quite a few big Ruby projects. Defining classes right with the definition of another class. In the same file. This is usually limited to smaller "helper" classes, but can end up with nested classes up to 20 lines long.
Let's go over where I started from, then move to where I got to and what it's given me.
# app/models/post.rb class Post < ActiveRecord::Base def excerpt Excerpt.new(excerpt_content) end end # app/models/excerpt.rb class Excerpt attr_reader :content def initialize(content) @content = content end def formatted content.upcase end end
That's a pretty simple start. We should pretend that the
Excerpt is a little more
advanced in this example and wouldn't actually be a good candidate for the
As the project went forward, I would end up depending on and expending
in ways that just made the code harder to maintain and understand. More classes
in the project would use it and add methods to it in their own way.
# app/models/page.rb class Page < ActiveRecord::Base def summary Excerpt.new(excerpt_content).first(100) end end # app/models/excerpt.rb class Excerpt # ... def first(length = 50) content[0, length] end end
I've ended up using the
Excerpt class for a side effect of something it provided
rather than its actual purpose. It doesn't summarize things, but that's how I want
to use it because it's there. My solution has been to make these kinds of "helper"
classes less available to the rest of the application and keep behavior that really
is actually specific to
Post, entirely within
# app/models/post.rb class Post < ActiveRecord::Base def excerpt Excerpt.new(excerpt_content) end class Excerpt attr_reader :content def initialize(content) @content = content end def formatted content.upcase end end end
This has generally pushed me to consider when I should and shouldn't be reusing
code in a more fruitful way by providing a little bit of auto loading hiding from
Rails and a more clear purpose for the class when it's first implemented. From here
I can then consider if the class is worth pulling out into a concern when I implement
Page#summary or if a separate, more specialized object would be more