My colleagueReid Cooper and I discovered a nice little trick of controller concerns, something we sometimes call “behaviors” in our app (typically implemented as modules). We found a trick from this link that lets us mix in behavior into both a controller and view helper, but first a brief introduction to controller concerns.
Concerns were born in Rails 4 as a nod to the limitations, vis-a-vi the domain model, of a a “strict” interpretation MVC as implemented by Rails. Around 2012 or 2013, most experience Rails developers would explain that the MVC structure created by default doesn’t necessarily dictate a strict MVC paradigm. Thanks in part to DCI architecture — which complements but does not replace MVC — a more modern understanding of larger apps includes a domain layer, i.e., where you put the business domain that is not in the traditional Rails models.
There are various options, and in a small nod to the problem the Rails core team added a blank empty folder to default Rails installs. You might notice this folder at app/controllers/concerns. What, the Rails newbie says, am I to do with a blank empty folder?
Good question. You would do well do study the excellent work of Sandi Metz and James Coplien, who cover domain abstraction (and a specific pattern the latter calls “DCI,” or domain-context interaction) in two excellent books (POODR and Lean Archtecture, respectively). The scope of these is well beyond this blog post, but since they are such my heroes I want to take an opportunity to plug these excellent books.
Reid and I wanted a behavior, a-la “concern”, that we could mix into a controller to inherit instance methods for the controller. We also wanted a view helper automagically mixed into our views for the view to access while it is rendering. To the rescue: the obscure included hook that gets called after modules are included into controllers, where you can re-access the controller itself and add both helper and actions (formerly known as filters.)
app/controllers/abc_controller.rb
include FancyConcern
def index
end
end
and here’s the magic, in app/controllers/concern/fancy_concern.rb
def self.included(base)
# http://www.railstips.org/blog/archives/2009/05/15/include-vs-extend-in-ruby/
base.helper FancyConcernViewHelper
base.before_action :set_my_instance_variable
end
def set_my_instance_variable
@my_instance_variable = “_instance variable value_”
end
end
app/views/abc/index.html
<fieldset>
<legend>An instance variable set in a before_filter</legend>
<%= @my_instance_variable %>
</fieldset>
<fieldset>
<legend>A call to the view</legend>
<%= my_view_helper_method %>
</fieldset>
Check out the full test app.
I don’t have a demo up & running, but it works (I took a screenshot below). If you want you can pull it locally and run it yourself to see.