Introduction
If you like writing object-oriented code, you will probably like the view object pattern. The basic concept is that a PORO (Plain Old Ruby Object) is used to generate HTML for the view. With all the power of object-oriented programming, view objects can make your life a lot easier when it comes to handling complex logic in the view.
Example: Tabs
Consider the example of creating page tabs like below:
Home Page Tabs:

Settings Page Tabs:

Analyzing these two groups of tabs, we see commonalities and differences.
Traits common to all tabs:
- Each tab group has one active tab
- Each tab links to another page
- Each tab group has the same styling
Traits unique to each tab group:
- The number of tabs may change
- Link paths change for every tab
- The text of each tab is different
- The logic to determine active tabs changes
In order to create a new group of tabs, a developer should only have to write code about the uniquenesses of those tabs. For example, when creating the settings page tabs, we should only have to specify the links (Profile, My Account), where those links point to, and some logic to determine which link is currently active.
However, in my experience the inclination is to just replicate the totality of the tabs HTML for every implementation of tabs throughout the application, like so:
home.html.erb:
<div class="tabs home-tabs">
<%= link_to 'Home', root_path, class: "tabs--link #{'is-active' if is_active_home_tab?(:home)}" %>
<%= link_to 'Dashboard', dashboard_path, class: "tabs--link #{'is-active' if is_active_home_tab?(:dashboard)}" %>
<% if can?(:manage, @company) %>
<%= link_to 'Company Admin', company_path(@company), class: "tabs--link #{'is-active' if is_active_home_tab?(:company)}" %>
<% end %>
</div>
settings.html.erb:
<div class="tabs settings-tabs">
<%= link_to 'Profile', profile_path(current_user.profile), class: "tabs--link #{'is-active' if is_active_settings_tab?(:profile)}" %>
<%= link_to 'My Account', edit_user_registration_path(current_user), class: "tabs--link #{'is-active' if is_active_settings_tab?(:user}" %>
</div>
application_helper.rb
def is_active_home_tab?(tab)
case tab
when :home
return true if controller_name == 'home'
when :dashboard
return true if controller_name == 'dashboards'
when :company
return true if controller_name == 'companies'
end
return false
end
def is_active_settings_tab?(tab)
case tab
when :profile
return true if controller_name == 'profiles'
when :user
return true if controller_name == 'users'
end
return false
end
There are quite a few problems with this approach. First of all, the HTML structure of our tabs component is duplicated in multiple spots. For example, they must be wrapped in a div with a class tabs and each tab within the group must have the class tabs–link. The knowledge of that structure is in both home.html.erb and settings.html.erb. If we need to modify the structure of the tabs, either to change the styling or behavior, we’ll need to do it in both of these files. This is the very reason why upgrading a framework like Bootstrap is such a pain – there are dependencies to the HTML structure and CSS classes all over the place.
Secondly, the logic about which tabs a user can see based on their permissions is just written with the ERB if statement. While in this example it is not a big deal, if the logic to determine whether or not to show particular tabs was more complex, performing it in the view would get unwieldy.
Finally, the implementation of the “is-active” class is clunky at best, and isn’t really something we want implemented in the application helper since it doesn’t really pertain to every part of the site.
Implementing View Objects
Boilerplate Code
While view objects can be implemented however you choose – they are just plain old ruby classes, afterall – there is boilerplate functionality that every view object should have. Some examples include:- Route helpers- Helper methods like numbertocurrency- CanCanCan methods (if you use the gem)- The view context (for methods like controller_name)
Because of that, I’m going to start by offering some boilerplate code that you can add to your Rails project to be able to start efficiently using view objects.
Let’s start with our base ViewObject class that all view objects inherit from:
# app/view_objects/view_object.rb
class ViewObject
attr_reader :context
include Rails.application.routes.url_helpers
include ActionView::Helpers
include ActionView::Context
# comment these two lines out if not using cancan
include CanCan::ControllerAdditions
delegate :current_ability, :to => :context
def initialize(context, args = {})
@context = context
after_init(args)
end
def after_init(args = {})
# implemented by child classes
end
end
Now we need to autoload the classes in app/view_objects/
# application.rb
config.autoload_paths += Dir["#{config.root}/app/view_objects/**/"]
Note: make sure to restart your server after changing application.rb
Last but not least, let’s add this helper method to application_helper:
# application_helper.rb
def view_object(name, args = {})
if name.is_a?(Symbol)
class_name = name.to_s.titleize.split(" ").join("")
else
class_name = name.split("/").map {|n| n.titleize.sub(" ", "") }.join("::")
end
class_name.constantize.new(self, args)
end
This view_object helper method dynamically finds and instantiates the correct view object class based on the name provided. This way, we can call <%= view_object(:home_tabs, company: @company).html %> from our view to instantiate a view object class called HomeTabs and automatically pass in the view context.
Creating our first View Object
The basic idea is that we can just create a plain old ruby class that ultimately spits out some HTML. Let’s start by creating the HomeTabs view object:
app/viewobjects/hometabs.rb:
class HomeTabs < ViewObject
attr_reader :company
def html
content_tag :div, tabs.join('').html_safe, class: 'tabs'
end
private
def after_init(args = {})
@company = args[:company]
end
def tabs
[home_tab, dashboard_tab, company_tab].compact
end
def home_tab
build_tab('Home', home_path, :home)
end
def dashboard_tab
build_tab('Dashboard', dashboard_path, :dashboard)
end
def company_tab
return nil unless can?(:manage, company)
build_tab('Company Admin', company_path, :company)
end
def build_tab(text, path, tab_name)
link_to text, path, class: tab_class(tab_name)
end
def tab_class(tab)
active_class = active?(tab) ? 'is-active' : nil
['tabs--link', active_class].compact.join(' ')
end
def active?(tab)
case tab
when :home
return true if context.controller_name == 'home'
when :dashboard
return true if context.controller_name == 'dashboards'
when :company
return true if context.controller_name == 'companies'
end
return false
end
end
Now in the view (home.html.erb):
<%= view_object(:home_tabs, company: @company).html %>
While this is quite a bit of code just to implement four tabs, the real value is realized when we want to make our next group of tabs. First, let’s try to figure out which methods in the above HomeTabs class are specific to the tabs rendered on the home page versus generic to all tabs throughout the app.
Methods specific to HomeTabs view object:- hometab- dashboardtab- company_tab
Methods generic to all tabs view objects:- html- tabclass- buildtab
Interface methods (i.e. every tabs view object must implement but implementation changes):- active?- tabs
Now, let’s refactor this into two classes (1) a generic “Tabs” class, and (2) the “HomeTabs” class
app/view_objects/tabs.rb:
class Tabs < ViewObject
def html
content_tag :div, tabs.join('').html_safe, class: 'tabs'
end
private
# Interface Methods
def tabs
fail 'must be implemented by subclass'
end
def active?(tab)
fail 'must be implemented by subclass'
end
# Generic Methods
def tab_class(tab)
active_class = active?(tab) ? 'is-active' : nil
['tabs--link', active_class].compact.join(' ')
end
def build_tab(text, path, tab)
link_to text, path, class: tab_class(tab)
end
end
app/viewobjects/hometabs.rb:
class HomeTabs < Tabs
private
# Interface Methods
def tabs
[home_tab, dashboard_tab, company_tab].compact
end
def active?(tab)
case tab
when :home
return true if context.controller_name == 'home'
when :dashboard
return true if context.controller_name == 'dashboards'
when :company
return true if context.controller_name == 'companies'
end
return false
end
# Specific Methods
def home_tab
build_tab('Home', home_path, :home)
end
def dashboard_tab
build_tab('Dashboard', dashboard_path, :dashboard)
end
def company_tab
return nil unless can?(:manage, company)
build_tab('Company Admin', company_path, :company)
end
end
What I hope you’ve noticed is that the code in the HomeTabs class has been cut down to the core essence of what it means to be HomeTabs instead of any other type of tabs. That is, only code specific to HomeTabs is here – not code generic to all tabs. For example, CSS classes are not set within this HomeTabs view object, since the tabs’ CSS classes are the same for all tabs.
View Objects are Scalable
Now here’s where the beauty of View Objects really kicks in. Let’s create a new set of tabs: the settings tabs.
app/viewobjects/settingstabs.rb:
class SettingsTabs < Tabs
private
# Interface Methods
def tabs
[profile_tab, user_tab].compact
end
def active?(tab)
case tab
when :profile
return true if context.controller_name == 'profiles'
when :user
return true if context.controller_name == 'users'
end
return false
end
# Specific Methods
def profile_tab
build_tab('Profile', profile_path(context.current_user.profile), :profile)
end
def user_tab
build_tab('My Account', edit_user_registration_path(current_user), :user)
end
end
And then in the view (settings.html.erb):
<%= view_object(:settings_tabs).html %>
We need only follow the pattern already laid out in HomeTabs to implement a brand new set of fully functioning tabs. If we want to modify the way our tabs are structured, we can do so in one place: the generic “Tabs” view object. Further, should the logic become more complex surrounding what tabs to show under different circumstances, we have the power of object-oriented code to help ease the pain.