dry-view 0.3.0: another step towards solving the view layer mess

We’re excited to announce the release of dry-view 0.3.0, which introduces two concepts for better organising your views: view parts and decorators.

How do these work? Every dry-view controller now comes configured with a decorator (you can use our default or provide your own), whose job is to wrap up your values in view parts before passing them to the template. View parts give you a place to cleanly combine view-specific behaviour with your application’s values.

By default, every view part is a plain Dry::View::Part instance. This gives you access to a #render method for rendering a partial with the view part included in the partial’s scope:

class AccountView < Dry::View::Controller
  configure do |config|
    config.template = "account"
  end

  expose :account do
    # fetch the account value
  end
end
<h1>Your account</h1>

<%# Renders "account/_info_box.html.erb" with `account` in scope %>
<%= account.render :info_box %>

This is just the beginning. Things get interesting when you specify your own view part classes:

expose :account, as: AccountPart

Since view parts are designed as wrappers, you have access to every method on the value:

class AccountPart < Dry::View::Part
  def display_name
    "#{full_name} <#{email}>"
  end
end

This is nice, but so far it’s just typical delegator-style behaviour. We can do more: these things are called view parts for a reason! Every view part is initialised with the view’s current context object and renderer. This means the view part can now encapsulate much of the logic you’d otherwise have to scatter around your templates.

Let’s say our context object has #attachment_url and #asset_url methods, for generating URLs for user-uploaded files and application assets respectively. If you wanted to start showing a profile image for your “account” value, you could now do this:

class AccountPart < Dry::View::Part
  def profile_image_url
    # profile_image_path is an attribute on the wrapped value
    if profile_image_path
      context.attachment_url(profile_image_path, "80x80")
    else
      context.asset_url("images/default_user_profile.jpg")
    end
  end
end

Then all you have left to do is add this one-liner to your template:

<img src=<%= account.profile_image_url %>>

The result? A cleaner template, and your view logic properly named and encapsulated in its own class, which you can also test independently.

In this way, view parts provide a critical new layer for placing the majority of your complex view logic. They make your templates easier to understand and easier to work with, and they help ensure that your view layer isn’t where good code structure has to stop.

Want to learn more? Check out view parts in the dry-view documentation and give dry-view 0.3.0 a try!