RailsConf 2013

Maintainable Templates

Maintainable Templates

by Brendan Loudermilk

In the video "Maintainable Templates," Brendan Loudermilk discusses challenges with maintaining templates in Rails applications and shares effective patterns to create more manageable views. He emphasizes that while the Rails community often focuses on models and controllers, templates are frequently neglected, leading to frustration and reduced development velocity. The presentation outlines several common issues and solutions to improve template maintainability.

Key Points:

  • Problem of Markup Repetition: Unmaintainable templates often result from repeated markup in various views, such as sidebars and headers. To combat this, Loudermilk encourages developers to abstract these components into partials, making the codebase cleaner and reducing repetition.

  • Logic in Templates: He highlights the detriment of placing logic directly into views, as this leads to messy and hard-to-test code. Instead, Loudermilk demonstrates how to use view helpers and decorators to isolate logic from templates. By putting logic in helpers, developers can centralize frequently used code, promoting better organization.

  • Decorator Pattern: Loudermilk introduces the Decorator Pattern as an advanced solution. This pattern encapsulates presentation logic into separate classes that wrap model instances, making it easier to manage and test. He explains how to implement decorators and suggests using the Draper gem to simplify the creation of decorator classes that provide a clean interface for views.

  • Presentation Models: He discusses presentation models for UI elements that do not directly relate to a model instance. These models allow developers to encapsulate all necessary logic for rendering specific parts of a UI without cluttering views or decorators.

  • Form Builders: Loudermilk provides an example of custom form builders in Rails, demonstrating how they can significantly streamline form rendering and enhance maintainability across an application.

  • Use of I18n: He advises using internationalization (I18n) to centralize text management in applications, reducing clutter in templates and simplifying content updates.

Conclusion:

Loudermilk concludes by encouraging developers to be proactive in maintaining clean and manageable templates. He emphasizes that investing time in applying these patterns can lead to significant long-term savings in development time and improve collaboration within teams. Implementing solutions like the Decorator Pattern, presentation models, and custom form builders are vital steps in transforming unwieldy templates into maintainable components in Rails applications.

00:00:16.480 Hello, everyone. My name is Brendan Loudermilk. I'm a software engineer and developer at Philosophie, a consultancy based in Venice. I've been programming for about ten years, and I've been fortunate enough to work with Ruby for the last four. Along the way, I've learned a few tricks about views that I'd like to share with you today.
00:00:36.480 We're here today because I think templates are commonly neglected in Rails applications. The community tends to focus a lot on models and controllers. People are often discussing service objects and similar topics. However, every time I open up app views, I cringe a little and slump down into my seat, ready to start a new adventure. Today, I'm hoping to help you and your teams improve your views.
00:01:15.200 I assume that you already know how to write clean markup in whatever language you prefer, whether that's HTML, HAML, Slim, or Liquid. That's not what we're going to focus on today. I believe that unmaintainable templates often boil down to two main issues: markup repetition and logic in templates. Markup repetition is a simple problem, yet it's very common in many applications.
00:01:40.640 The second problem, which many of us likely already acknowledge, is that including logic in templates is bad practice. However, it still occurs frequently, and I'm going to provide you with some tools to help prevent this from happening, thus improving your work and your team's efficiency.
00:02:08.399 Let's discuss markup repetition first. Good designers typically use repetition effectively in their designs to create a consistent user experience. However, good developers often avoid repetition. Yet, when we code designs and write HTML, I notice sidebar elements repeated five times throughout an app, page headers duplicated ten times, and other instances of needless repetition alike. This is an easy problem to fix.
00:02:40.560 To address markup repetition, you need to abstract shared interface components. Sit down with your designer and review what’s in front of you. Identify reusable elements across different pages, like sidebars, page headers, breadcrumbs, and footers. You might even look to Bootstrap as a reference for a list of these components that will likely be reused in your application.
00:03:07.599 I encourage you to use partials in your application to generate the end markup for these frequently repeated elements. That covers markup repetition effectively.
00:03:28.800 The bigger offender is logic in templates, and this can lead to significant repercussions as your application grows. Logic present in templates is often highly repetitive. Common examples include formatting a name in a particular way or manipulating data specifically for presentation purposes, which frequently leads to those pieces of logic being duplicated across multiple pages in the application. Logic in templates complicates testing as well, which is already tough enough; why make it harder?
00:04:14.960 An example of logic in templates that isn't excessively complicated involves formatting a credit card number. The developer wrote a simple method to mask the credit card number with three sets of X’s followed by the last four digits. This solution passes all tests and seems to work well. However, when the developer moves on to code another page, they replicate that logic, leading to the same repetitive process taking place again.
00:04:38.880 This example illustrates how behavior can become duplicated in applications if you place your logic directly into views, and it doesn’t just stop there. It impacts other pages or emails where credit card numbers are processed.
00:05:03.680 You might have another developer who knows about using helpers, which is a fantastic alternative. View helpers, as defined by Rails guides, live in app/helpers and provide small snippets of reusable code for your views. The key focus here is small snippets, which are perfect for straightforward functions like masking a credit card number. Helpers are easy to use and accessible for anyone familiar with Rails since they can search for your helper methods and see how they’re implemented.
00:05:32.320 In this specific example, we took that same masking logic for a credit card and defined it in a helper instead of implementing it directly in our views. This clearly adds a win, as it allows us to clean up the logic processing out of views and place it in a reusable helper method.
00:05:58.960 However, helpers should not be viewed as the ultimate solution for logic in your views. While they can solve a variety of problems, relying solely on them can create an overwhelming number of helper methods. This abundance can complicate organization, especially as the context of a view helper is tied to the view itself. There’s no natural way to categorize helper methods, leading to confusion over their purpose.
00:06:20.800 Complex logic is often not suitable within helpers. They excel for simple string manipulation or very straightforward logic, but anything beyond that should probably be handled elsewhere. Our goal should be embracing an object-oriented approach, making full use of classes and unit tests, rather than relying on a tangled web of methods spread across multiple files.
00:06:44.160 I crave a way to enhance my domain objects, or models, and introduce behaviors specific to the views they relate to. This is where the decorator pattern comes into play. According to the 'Gang of Four,' decorators dynamically attach additional responsibilities to an object, providing a flexible alternative to subclassing when extending functionality.
00:07:08.880 A decorator wraps a single object—an instance of your model—and maintains a transparent interface. This means that it looks and behaves the same way as your model instance does. It achieves this by overriding or defining new methods while passing any other method calls through to the original object. In our case, we will use decorators to hold all the presentation logic required for our views.
00:07:30.760 Implementing a decorator in Ruby is straightforward, particularly when you reference the code samples from the 'Gang of Four' book. If you plan to use multiple decorators in your application, creating a decorator-based class or an abstract class is a good approach. The initialize method should take the object you want to decorate. I refer to this as a 'component' in my implementation.
00:08:12.720 Next, you set up method forwarding. By defining 'method_missing,' you enable your decorator to call methods that aren’t explicitly defined within it on the underlying component. Additionally, implementing the 'respond_to_missing?' method as a follow-up can help maintain compatibility in your code.
00:08:37.920 In summary, defining a decorator class is a straightforward task, and for each object you want to decorate, you can simply subclass the original decorator class. For example, using the credit card example, I defined a credit card decorator class that also inherits from the existing decorator class, allowing for specific methods like 'masked_number' to be implemented where logic resides, similar to what was done in the helper.
00:09:04.880 The boundary between helpers and decorators is not always clear, and it is entirely up to you how you want to separate functionality between the two. My belief is that if a piece of logic pertains specifically to one model, it should be encapsulated within a decorator, allowing for organized and cohesive presentation logic.
00:09:24.640 Using decorators is also simple. In your controller, just instantiate the decorator before rendering the view by passing along the specific instance of the original record to the initializer. When stated in the view, it appears as though the model has a method called 'masked_number,' enhancing the user experience without introducing additional complexity.
00:09:58.080 Things get even more convenient when you introduce a new team member to decorators. The concept is very simple to explain, and it helps clean up code organization immensely. They are also straightforward to test, and the advantages of utilizing decorators far exceed any costs associated with adding them to your project.
00:10:39.400 We implement decorators on almost all of our projects. However, the discussion doesn't stop there. Presentation logic tied to a single instance of your model is an ideal application of decorators; however, I don’t always implement my own decorators. There’s an excellent gem called Draper, which I rely on for consistent decorator implementation across projects.
00:11:03.920 Draper provides access to view context, allowing you to call Rails helpers when necessary and even rendering partials as needed. In addition, Draper makes it straightforward to decorate collections, even those containing multiple model types into an array.
00:11:39.440 Draper works seamlessly with Rails forms because as you're using form_for or link_to, it pretends to be the actual model rather than identifying as a decorator. This ability can be quite convenient because it makes your code cleaner and behavior more robust.
00:12:09.920 When it comes to decorating associations, if you're passing a record from the controller to a view while traversing the association tree, Draper can automatically decorate those associations, streamlining that process. While it's not entirely automatic, it's relatively easy to set up, significantly benefiting the workflow.
00:12:31.760 I encourage you to check out Draper at github.com/drapergem/draper if you're interested in further exploring the decorator concept. Although decorators are very useful for adding behaviors and presentation logic to model instances, the conversation doesn't end there.
00:12:57.280 There arise challenges when handling complexities related to user interface elements that do not directly associate with model records. For instance, in a ticket tracking application, when displaying which user a story is assigned to, if that user is the current user, we do not need to name them. Instead, we simply state, 'It belongs to you.'
00:13:20.640 The participants list in that story might include those who have interacted with it, but it shouldn't display the current user in this list. While the logic for rendering the assigned user and participants can be implemented in the view, it leads to clutter and repeats logic across the code base. So rather than placing this logic in a decorator, I found a new approach with the presentation model.
00:13:45.920 The definition of a presentation model is that it is a fully self-contained class representing all the data and behavior necessary for a UI component but without the controls to display it on the screen. In essence, a view object simply projects the state of a presentation model onto the user interface, leading to clearer and more maintainable code.
00:14:21.280 Much of my experience with presentation models comes from working with Backbone.js where we emphasize the importance of making templates 'dumb' while enhancing the maintainability of complex UIs. By abstracting specific behaviors and leveraging presentation models, we allow our front-end developers to manage DOM changes without worrying about interfering with the underlying logic.
00:14:51.360 In practical terms, a view object in Ruby holds a reference to the template's context, the relevant record, and any additional parameters that may be useful, such as the current user. Methods within the view object handle specific behaviors, allowing logic to be neatly organized and easily managed.
00:15:14.560 By using private methods, we can compartmentalize complex processes within these view objects. For instance, in our example, the participant names method effectively maps participant names to a comma-separated list while ensuring the current user is not included. Thanks to Ruby's object-oriented capabilities, instances of our view objects can also utilize the `to_s` method to dictate how they should be rendered within templates.
00:15:48.880 When we pass our view object to a template, it is self-sufficient, rendering the necessary information smoothly, leading to cleaner and more maintainable codebases. As a best practice, I prefer to utilize helpers to initialize these view objects to declutter my views even further, allowing my code to showcase clear behaviors.
00:16:09.440 The objective is to create a universal helper for your application that can easily render the story summary anywhere, streamlining your workflow. Additionally, custom form builders within Rails allow you to pre-define methods to encapsulate complex forms elegantly, making the system more efficient.
00:16:45.680 Using a form builder helps maintain consistent interfaces across your forms, which is especially valuable in administrative or similar applications. This simplicity allows designers to make adjustments, such as altering the HTML structure from a div to a list, without requiring significant changes to the underlying logic.
00:17:37.680 I want to highlight two additional takeaways before concluding. First, consider using internationalization (i18n) whenever possible. Though it may require some effort to set up initially, centralizing your copy in language files improves project maintainability and keeps your commit history clean.
00:18:09.040 Second, I encourage you to continue refining your views as you progress. There are many gems available, like Simple Form, that can simplify building forms while ensuring consistent designs across your app. Similarly, Tablecloth provides an excellent way to create uniformly styled tables without the pain of managing them manually.
00:18:34.640 Thank you for your time today, I hope these tips help you keep your Rails apps manageable and efficient. We're out of time now, but I'm happy to take questions if you have any.