Tabular layouts can be difficult to work with, especially when some display elements are conditional. Outside of email templates, tables are largely eschewed in favor of logical divisions or more semantic HTML, but what if the data you have for display is actually tabular data? I want to show you a technique I wrote for dealing with conditional columns in HTML tables.
I’ve used the idea of a feature flag before to toggle the availability of new development in an application. For these conditionals, I’ve copied much of that same design down to a smaller scale, including the name “Feature.” A feature may be initialized during a request and queried when rendering a template.
widget.html.erb:
<table>
<thead>
<th>Foo</th>
<% if feature_enabled?(:premium) %>
<th>Premium</th>
<% end %>
</thead>
<tbody>
<tr>
<td><%= @widget.foo %></td>
<% if feature_enabled?(:premium) %>
<td><%= @widget.premium %></td>
<% end %>
</tr>
</tbody>
</table>
widget_controller.rb:
class WidgetController
def show
add_feature(:premium)
@widget = Widget.find(widget_params)
end
end
With these template features though, there’s an extra layer of indirection. Rather than a simple on/off switch, I need to inspect the properties of another model before the feature may be displayed. While that part is delegated to another class, the important piece here is how we interact with those classes during the request. Two new controller methods, add_feature and feature_enabled?, let us initialize features and see if they’re available to the request.
premium_feature.rb:
class PremiumFeature
attr_accessor :controller
def initialize(controller)
@controller = controller
end
def enabled?
controller.current_user.premium?
end
end
widget_controller.rb
class WidgetController
# Further down the controller class
def add_feature(name)
feature = "#{name}_feature".camelize.constantize.new(self)
if feature.respond_to?(:enabled?)
features[name] = feature
end
rescue
fail 'Feature does not exist'
end
def feature_enabled?(name)
features.fetch(name, NeverEnabledFeature.new).enabled?
end
helper_method :feature_enabled?
end
When I called add_feature(:premium), a new instance of PremiumFeature was initialized and added to the features hash by name. When I called feature_enabled?(:premium), it retrieved that instance and called its enabled? method. As an added bonus, if we query for a feature that doesn’t exist, Ruby’s Hash#fetch method lets us define a default return value, which in this case is a class whose enabled? method always returns false. Note that the features hash itself isn’t shown in the examples. For this application, I used Rails’ HashWithIndifferentAccess so that features could be queried by symbol or string. You could use a simple hash instance if you wished to be more strict.
An idea to take this utility further might be to allow unimplemented features by name. Extend the idea of the default NeverEnabledFeature and add an AlwaysEnabledFeature when adding a feature by name that does not exist. You could also move feature initialization into your app’s configuration if usage were more widespread.
There really are few options when conditionally rendering columns in an HTML table, and thankfully this should be a rare case. Without any way to disable entire columns, an easy Boolean helps to keep templates readable and thus maintainable. There’s little doubt that a future programmer, or your future self, will appreciate you having taken the time to be expressive in such a corner case as this.