Customization

Summarized using AI

Curly — Rethinking the View Layer

Daniel Schierbeck • April 21, 2015 • Atlanta, GA

In the video "Curly — Rethinking the View Layer" presented by Daniel Schierbeck at RailsConf 2015, the discussion centers around the limitations of traditional ERB templating in Rails and introduces Curly, an open-source project designed to enhance the view layer of Rails applications. Curly aims to separate presentation logic from display structure, allowing for easier customization and maintenance of views.

Key Points Discussed:
- Background and Motivation:
- The speaker shares the struggles of customizing the ERP views while working on the second version of the Zendesk Help Center, which faced limitations due to its rigid structure and dependence on JavaScript for customization.
- The Challenges with ERB:
- ERB was found to be cumbersome when dealing with client customizations, leading to stagnation in updates due to unpredictability in breaking changes.
- Decision-Making Process for a New Template Language:
- Acknowledgment of prior failures when trying to allow customers to directly edit templates written in ERB. Need for a simpler, safe, and declarative approach motivated the creation of Curly.
- Key Features of Curly:
- Inspired by Mustache, but specifically designed for Rails, Curly allows HTML and templates to be separated from logic by keeping all logic in presenters, thus enhancing the maintainability and readability of code.
- Customers can customize their templates while using predefined components, which can be manipulated without breaking what's already in place.
- Benefits of Curly Over ERB and Other Templating Languages:
- Presents a declarative syntax with defined structure and logic separation, enhancing both backend and frontend development experience.
- Enables better testing practices by focusing on view logic rather than full rendering, making unit tests simpler and more effective.
- Improves caching mechanisms by allowing easy integration of cache keys in presenters, leading to better performance.
- Use Cases and Adoption:
- Curly can coexist with existing ERB and HAML templates, gradually integrating into existing Rails applications with minimal changes.
- Real-world success stories demonstrate its utility, as Curly has been in production for over two years.
- Concluding Thoughts:
- While Curly greatly enhances the view layer, there are still scenarios where standard ERB may be more appropriate for simpler templates.

Overall, Curly has been celebrated for providing a better development experience, improving the separation of concerns in Rails applications, and facilitating easier customization for users and developers alike.

Curly — Rethinking the View Layer
Daniel Schierbeck • April 21, 2015 • Atlanta, GA

by Daniel Schierbeck

While most parts of Rails have been thoroughly overhauled during the past few years, one part has stubbornly refused improvement. The view layer is still by default using ERB to transform your app’s objects into HTML. While trying to solve a seemingly unrelated problem we discovered a design that suddenly enabled us to move past the limitations of ERB while still integrating closely with Rails. We could now separate presentation logic from display structure; reason about our views and how they relate; and dramatically improve our code. We called the library Curly, and it's pretty awesome.

RailsConf 2015

00:00:12.800 Perfect. Thank you all for coming here to hear me speak. I'm very honored that you all are here to see me. My name is Daniel Schierbeck, but I go by 'Dash' on the internet. You can find me on Twitter and GitHub. I came all the way from Copenhagen to be here. Copenhagen is not usually like this; it typically just rains, but I could use the photo of this. That would be sad. First, I have a disclaimer: there is not going to be any JavaScript here, so I'm very sorry if you thought that. We will have something that looks a bit like Mustache, so it will feel familiar if you're looking for JavaScript. How many here mainly do JavaScript front-end? Yes? Okay, not a lot. Great.
00:01:03.520 This talk is all server-side, Ruby-generated views, right now. It's a lot more like classical ERP stuff, so I'll get into that later. I just didn't want to disappoint too many people. I work at a place called Zendesk. We do customer support software. We're a hosted platform, so you'll get an email address that people can complain to. It will give you a nice UI so your employees can manage support requests and all that stuff.
00:01:37.280 The title of my talk is 'Curly: Rethinking the View Layer.' I'm going to get into what Curly is. Curly is an open-source project that helps you write great views. However, I feel like there's a nice story behind some of the design decisions—why Curly looks the way it does. I want to start out by telling you that story, and then I'll talk about Curly and how it'll make your lives a lot easier.
00:02:20.080 It all started with why we decided to build a new template language and why we did that. It's not something you typically set out to do; it's sort of a pain in the ass. Once you do, you have to write the actual templates in that language. Then, if you decide it was a stupid language, you have to rewrite all your templates. It's something you really think long and hard about before you do it, and there were lots of options already.
00:02:46.080 So, why did we do it? Well, for the last three to four years, I've been working on a product that we call 'Help Center.' It complements the main Zendesk functionality, which is ticket support. It's a hosted knowledge base and community site that integrates with Zendesk but is sort of its own product.
00:03:14.400 This was version two, basically, of an older product that we had that was also a hosted knowledge base and community site but had several shortcomings. One of the big shortcomings was that it was very difficult to customize. We allowed our customers to upload a custom logo and change some background colors, but when users entered the knowledge base, they all looked sort of alike and definitely didn’t represent the brand of our customers. Our customers were not happy with that, of course.
00:03:44.960 So, we then made the choice to allow them to inject JavaScript into their knowledge base and community site. That worked—you could do a lot of stuff with JavaScript. They added custom widgets, text, removed features they didn’t like, reordered things, and changed font colors so that it looked like their brand. It was great, and they were somewhat happy, but that implementation had some shortcomings.
00:04:36.640 Mainly, when you loaded the site, we would first render the default-looking view, and then once the DOM loaded, a bunch of JavaScript would be executed, rendering everything and changing things around. It sort of felt weird and didn't feel very professional. Another big problem was that since our customers were using JavaScript to change things, they could depend on anything. They used any last name or any sort of tag name to tie in behavior and perform selectors. This, in turn, meant that we couldn't really change anything in the HTML without breaking lots of customer sites.
00:05:29.360 We had tens of thousands of customers, so it was impossible for us to know what would break for whom when we made a change. In the end, it meant that we didn't really update that site, and we just let it stagnate because it was too painful to make changes. It looked like something from the early 2000s, which is bad when you're trying to sell to new customers.
00:06:01.680 So, for version 2, among other things, we wanted to address that problem. We wanted to have a proper fix for it. We had lots of discussions, and finally, we decided that we wanted direct support for theming and customizable templates. We wanted our customers to define how the site should actually be rendered when we render things into HTML, and it needed to happen up front—not as an afterthought once we delivered the site.
00:06:41.760 We had many discussions about how that feature should work: what kind of access should people have? How much responsibility should we give them? One extreme was to have a drag-and-drop interface, and the other extreme was that they would do everything themselves. We ended up somewhere in the middle. The idea was that we would provide a set of predefined components and default templates that they could just use, which would look nice. Then the customers could go in and change all the HTML they wanted, as long as they didn't alter the components. The components were items that they could move and remove if they wanted!
00:07:52.480 This approach minimized the API surface we had to support to just those components, making things a bit more manageable. Since then, we've learned other things, and it has gotten a bit more complex, but in the beginning, that's what we wanted to do. At that point, we had already written most of the site; it was functioning, and we had the knowledge base and community—everything was there. We sat down and talked about how we wanted to implement it: how should customers do this? We knew they wanted to use HTML, and after asking some of them, we realized there was no reason they shouldn't have access to HTML themselves; otherwise, they would just resort to JavaScript again.
00:08:55.199 We didn't want that, so we looked at what we had, which was ERB. All our views were written in ERB, and the views that we intended for the customers to customize were also written in ERB. Initially, we thought about allowing customers to just open a text field with the view in it and type ahead and save it, which would mean we would use that. We thought about that for around 30 seconds and then realized it was the stupidest idea ever, and we didn’t want to do that.
00:09:50.320 So, we decided that customers should write templates; they shouldn't be programming. They may be sort of web developers, but mostly in terms of HTML, CSS, and maybe a sprinkle of JavaScript, to use DHH's terms. So, we started looking at safe templating languages. There are a few. We were already using Liquid in other parts of our application, so we considered whether we should use it here too.
00:10:40.960 There were a few reasons why we didn't go with Liquid. While Liquid is very cool, it's also very granular; it feels almost like a programming language. It's safe to execute, but you still have to provide very granular data structures for it to be useful. We would have to define a huge API of nested objects to pass to the customers, with functions, loops, and all this complexity. It was just too powerful and too advanced for what we wanted.
00:11:18.080 We wanted something that was simple and approachable. We also looked at Mustache. If you do JavaScript programming, you probably know Mustache or Handlebars, which I believe Ember.js uses. Mustache has a very simple syntax; at least, Mustache does. Handlebars has added a lot of bells and whistles. Mustache was actually quite close to what we wanted. It's declarative, there's no logic, no for loops—it's just simple.
00:11:55.920 The main problem was that it was not well-suited for Rails. There was a Ruby implementation, but that was completely decoupled from Rails. We would have had to go all in on Mustache, and we were not prepared to do that. We wanted something we could use on just the outer layer of our views—the parts our customers should be able to customize and nothing else. There are still lots of small partials and other views that we didn’t want to convert; we just wanted to keep them as they were.
00:12:43.200 We also wanted something that was baked into Rails and could use the niceties of Rails, such as caching, which was a big deal for us. Therefore, we came up with a list of things we wanted, but we couldn't really find any language that provided everything. We wanted the templates to be safe, of course, because we were running and executing them on our servers. We also wanted it to be declarative; we wanted customers to signal their intent regarding where they wanted to place things.
00:13:24.160 They were not supposed to construct complex structures. We wanted to keep all the logic in a separate place and not expose it to our customers. They should just decide where to put things, but we wanted to keep all that advanced generation logic out of the product, so we could have a simple product and keep the complex stuff out of it.
00:14:01.399 We wanted it to be testable. This would be an API that people depended on to be stable. If we made a change and it suddenly broke something for a customer, we would receive complaints, and they would be unhappy. We wanted to have regression tests in place so we could ensure we respected our end of that bargain. We also wanted to integrate well with our existing Rails app.
00:14:44.959 So, we decided to build Curly. It's heavily inspired by Mustache, but it differs in some key ways. You keep all your HTML in templates, using the Curly bracket notation to place around components wherever you want them. All logic is kept in presenters, which are just plain Ruby classes residing in 'app/presenters.' Those presenters have full access to all the things you know from ERB, such as link helpers, path helpers, and form helpers. You can render partials and do whatever you need.
00:15:34.160 Curly can be used alongside ERB and HAML. If you have existing fragments or partials, you can just render those directly from within Curly and vice versa. It integrates completely into Rails, which was very important for us. This is what a pretty simple, but very typical, Curly template would look like: you have your components, which are just names surrounded by curly brackets, followed by your HTML. It’s very readable and simple.
00:16:27.680 All the presenters are placed in 'app/presenters,' and they are just normal Ruby classes that inherit from 'Curly::Presenter.' Did you all attend Aaron's keynote this morning? Yes? Did he sleep in? In it, he spoke about the problem of not knowing which local variables are being used by a partial template in Rails, making it difficult to know upfront what those are. He talked about having to parse them out.
00:17:43.439 Actually, in Curly, we decided early on that we wanted an explicit list of variables that a given template should accept. We wanted it to crash and complain if you didn't provide that because we had run into issues previously that stemmed from that. So, we decided we wanted to be explicit. It’s very easy in Curly to just ask a presenter what it needs, and you'll get a list. Here we say, 'Well, we expect there to be an article variable; pass me the article if I'm presenting an article.' You can have a number of public methods, and you can see that 'article' is available as an instance variable.
00:18:41.439 So, each component maps to public methods on the presenter class. This is all done automatically for you. A lot of the stuff could be done manually, but Curly removes all the boilerplate and overhead, ensuring that each public method also has access to all the helpers they need for rendering. There's a simple naming convention, where each template maps directly to a presenter.
00:19:28.480 We were happy; we used this—it was pretty generic. We could store our templates in the database. We had some on disk and, when a customer wanted to customize, we would store their templates in the database and could inject a resolution mechanism into Rails, meaning we could actually pull the templates from there while reusing the same presenter.
00:20:16.320 This made things much easier for us because we only needed to test the logic once, and it was safe and simple. Our customers were very happy, and we rolled it into our product. You have an inline editor where you can change templates directly in the product. You can preview changes, and we also allow editing of CSS and JavaScript. It was very cool; I was very proud of it when we shipped it, around two and a half years ago, and it's been running ever since.
00:21:12.120 So, we were happy and sat back, proud of ourselves. We started thinking about these templates and the presenters and considered the rest of our templates that we didn’t convert because they weren't customizable. We began to wonder if the Curly thing was actually more useful than we initially thought. Maybe it wasn't just for customization; perhaps it was something we wanted to use more broadly.
00:22:03.200 To give you an example, here is a simplified version of an actual ERB template that I extracted a few years ago from our app. I think it's pretty typical—not overly simple, but not hugely complex either. You could definitely go and make changes in ERB to simplify it, but hopefully, you’ve all seen this kind of thing. If you look at it, how much of this is actually structure? How much is related to the ordering of things or the HTML?
00:23:09.760 Not a lot; most of this is actually Ruby code, logic that determines how some things should appear. It's very difficult for a front-end developer to go in and wrap the comment form in a div and give it some class. Where's the comment form? In Curly, it's a lot easier, because all logic has been extracted and identified, which is just as important. You can go in and see what is what, where the comment form is, rearrange it, and see where all the HTML is.
00:24:02.400 This is a big deal, and our front-end developers are very happy about that because it made their lives a lot easier, especially with big HTML pages containing a lot of markup. If you remove all the logic, all the Ruby from there, you’re left with just the essence of what they need—that was really cool. On the other hand, we backend developers, because I'm not a very good front-end engineer, also benefited.
00:25:02.080 We were left with Ruby classes that we know how to deal with. We can use private methods to clean up stuff, utilize inheritance, and so on. This is separation of structure and logic, and it significantly improved the state of affairs for our entire view layer, which was pretty large. It also meant that we could move at a much faster pace.
00:25:56.000 We could address some of the complex logic that had trapped us inside ERB. With ERB, our options were to keep the logic there, extract a partial, or maybe extract a helper, but none of those options were always perfect. Now, we can just use Ruby. One of the previous issues we faced was reusing views in multiple controllers; we had to remember to include the same presenter classes in each controller because we needed them.
00:26:40.080 This was annoying, as the controller needed to know what the view required. With Curly, you don’t need that knowledge; you can just include a module. Nothing is magical about this—helpers are just methods inside a module, and you can use inheritance and other structures, making everything easier.
00:27:29.440 So, I talked about testing earlier, and testing is a significant deal for us. We use RSpec and had previously used view tests, which had been somewhat of a hassle. How many of you do view testing in your Rails apps? Just a few? I hope some of you know what I'm talking about. It can be tricky. Basically, what you do is test some specific piece of logic. You want to validate that it’s this or that, but you have to pass all the needed variables to the view, then render it. This gives back a huge blob of HTML.
00:28:21.439 But you’re only really interested in a tiny section of it, so you try to use a library for parsing that HTML and extracting the specific tag you're interested in. Then you insert just that, but we found that to be very brittle and error-prone. Sometimes, you might test that something is empty. You believe you are testing the required thing, but you might just be selecting the wrong element and have an empty test that will always pass without any relevance.
00:29:05.919 We wanted to change that. So if you look at traditional view testing, you have three parts: the test, the view, and a poor step—which you have to navigate through entirely. What we did in Curly was to cut out a part because we weren't actually interested in testing the template itself; we focused on testing the view logic.
00:30:02.320 Instead, we just passed variables to the presenter class when initializing it, and then we could call methods directly. There's nothing magical about it. You can literally just call a method and get the result back, making assertions on that specific piece. It was huge for us; we could maintain the API much more easily and do straightforward unit testing.
00:30:49.760 So, what we aimed to test was the logic, not the structure. You can still test the structure, but that can be done by simply looking at the template and seeing everything is in the right order, or just opening your browser.
00:31:33.760 Caching is another important aspect for us. We use Russian doll cell caching. How many of you use caching in general? Quite a few. Great! Basically, you have multiple caches that are nested, so using the right cache keys is essential for us. Typically, in ERB you have to specify the top-level cache and the cache key with lots of data involved.
00:32:18.760 This leads to everything getting mashed together in one file, making it really difficult to test. We wanted to enhance support for caching in Curly. We found a good way to accomplish this: if you have a cache key method in your presenter class, that view will be cached if you return a non-null value. Curly will cache the view output using that cache key.
00:32:58.560 This makes it simple and allows for much more complex logic in your cache key. You can use private methods and other structures, and it enables you to test your cache keys. You can ascertain that if you pass in two different values for a variable, the cache key will be different, providing a nice way to test that functionality.
00:33:45.520 What I'm getting at is that sometimes, it makes sense to use the best tool for the job. There is definitely value in just having a one-size-fits-all solution. This is part of the Rails mantra. However, in this case, I believe a markup language can be quite powerful. There’s a reason we don’t just have a press that renders pixels on screens in the browser; we send back HTML. HTML is nice because it's markup that shows structure, allowing easy identification of component parts.
00:34:36.160 On the other hand, HTML is not conducive to writing code. It’s actually not possible, so you use Ruby for that. It makes sense to separate the two, as they serve different purposes. It was fun to see Aaron's talk this morning because he addressed some things I’ve been frustrated about over the years, but he also touched on caching views. I'm not sure how familiar you are with the internals of Action View, and it took me a while to understand it, but I want to explain how view rendering works and where Curly fits into Action View.
00:35:24.560 Typically, you deal in Action Controller. You have a controller action that either renders a view implicitly or explicitly. You can pass the render name or let that name be the action name, such as 'edit' or 'show.' What Action Controller does is pass that template name to Action View, which looks around on the disk to find the appropriate template and load it into an object.
00:36:34.560 Then it finds the relevant template handler, usually based on the suffix, such as '.erb' or '.haml,' or in our case '.curly.' It passes the template object to that handler, which is responsible for compiling the source code into Ruby. This returns a string containing Ruby code back to Action View.
00:37:30.640 What Action View does is keep an anonymous module around with various methods corresponding to views. It takes that Ruby code, creates a new method named after the view, and evals the source code to call the method. This executes the Ruby code and returns the HTML back to Action Controller. The benefit is that the next time Action View sees the template name, it does not have to compile it again; it just calls that method on the module.
00:38:24.640 So, that’s what Aaron meant when he referred to caching views. This happens only in late execution and in each unicorn process; there are differences across processes, but it's a clever implementation nonetheless. I have been talking about Curly for a little while now. Do you think it's just awesome? You should always use Curly? Well, no, that's not solely the case.
00:39:39.600 It's not just an either-or proposition. Curly has helped us greatly, but we also still deal with some complex views. We continue to use ERB for many cases where something is simple. If it’s a two-line template, there’s no reason to use Curly. Just use ERB, especially in the initial stages of building something when everything is in flux.
00:40:29.440 Once you reach a certain threshold of complexity, Curly is a great way to start thinking about a proper API and defining the structure. I definitely encourage you to take a look at HAML. I've spoken to several HAML developers, and I see HAML as comparable to ERB, just from a different perspective. In ERB, you typically put lots of Ruby code inside your HTML, whereas in HAML, you put a lot of HTML code within your Ruby.
00:41:18.080 But it’s basically similar in terms of design: you still mix and don't separate structure from logic; you just blend them in different ways. This has been in production for over two years now, and other companies are also using it; a bunch of people in Prismic are using it! I don't know why, but it's been a huge success.
00:42:07.440 We've open-sourced it as Curly Templates. Someone already took the name Curly, which wraps around Curl. You can just use Curl, but okay. It's called Curly Templates, and you can drop it into your Rails app. It won't do anything right away; it won't break anything. But if you change one of your templates to be '.curly' instead of '.erb,' you'll use Curly. You’ll need to set up a presenter and extract code, and boom! Just that one template will use Curly, while you can continue to use ERB for the rest.
00:43:00.000 So it's easy to try out and play around with, and I think it’s quite cool. This talk may be a bit short, so I believe we have some time for questions, if you have any. Thank you again for coming. You can find this on GitHub. It’s open-source, and all contributions are welcome. I genuinely hope some people will start looking at it and maybe contributing or finding useful applications for it. So, do we have any questions?
00:44:35.360 The question was whether customers in our product know what's available in terms of these components. Yes, we have nice documentation, where they can just type 'Curly' to see a list along with descriptions. That’s convenient. Within the editor, we have a sort of ID function. The question was whether there is some HTML in the presenter in the form of a content tag. That's right. I consider myself pragmatic about that.
00:45:20.960 If you use any kind of link helper or form helper, that’s essentially the same thing. I believe you just want an API that returns a blob of HTML to you, creating HTML in the presenter accordingly. It’s logic-driven HTML. If you want a cleaner logic-only layer, create your own presenter objects or decorators outside of Curly that don’t entail HTML at all. The beauty of Curly is that it understands form helpers, link helpers, and all your paths, enabling clever view rendering.
00:46:03.600 So, that’s very useful. Yes? The question is about rendering a collection. You can do this inline in Curly, using 'render collection' from the presenter, which will use the normal mechanism of rendering a different template multiple times. You have various options for that, and you can handle it in the method itself and return HTML—though that might be messy.
00:46:53.440 The question concerns putting classes in your HTML while generating it in the presenter using something like 'link_to.' Does that cross a boundary of where front-end stuff should go? I consider presenters to encompass front-end aspects, as they are seen as view logic, not back-end logic or domain logic. If you have something that resembles domain logic, it should probably stay in the model or another appropriate place.
00:47:39.840 It’s situation-dependent. If you have front-end developers who don't mind delving into Ruby classes, I would do that. On the other hand, if there are rigid requirements, and you only want UI elements to be Curly, you can do that, too. You can have code like 'article_path something' to return the path, allowing you to use 'href=' and include the entire structure if you prefer. Both options are valid and supported.
Explore all talks recorded at RailsConf 2015
+122