 I appreciate you coming out to listen to me ramble about views. There is much reward in that once we are done here, we get an Aaron Patterson keynote. So there's much to look forward to. We just got to get through this. So today we're going to talk about views. And when I originally put this talk together, I really wanted to pose a question for myself and that was, do my views know too much? And so I started putting this talk together, and I thought, well, actually, I've had a change of mind. It's not really a question. I can pretty much confidently say your views know too much. So that's what we're going to look at today. Before we get through that, my name is Jason. I've been writing Ruby since 2012. This is my third RailsConf. I work for a company back home called Lens Reynolds. 90% of the time when I say Lens Reynolds, people always respond with what? We rent camera equipment nationwide. We're a Rails shop. I'm also a new dad. My wife was really excited when I told her I was going to be gone for a week and she has a three-month-old at home. So the other thing I just want to say before I get started is that it's amazing to be able to stand here and speak today because I legitimately used to be terrified of this about three years ago. I have a really crippling anxiety disorder and talking in front of people used to terrify me so much so that in college I actually took my public speaking class online. Yeah, that's a thing and it's amazing. But three years ago at RailsConf they had some technical difficulties during lightning talks and they said, hey, we need somebody to come talk about something. I was like, well, I'll talk about my anxiety. I did it for 90 seconds and it literally changed my life to where I crave coming to talk. So what does that have to do with views? Well, nothing. But I would regret not sharing that because if you want to ever get into public speaking, you should because it's amazing and there's not a better community to do it than this one. So anyway, your views know too much. Let's dive in. So know too much, what does that even mean? So as I said, I started thinking about it and really I've seen this many times because I'm the one that writes it many times. Our views just kind of become this dumping ground without realizing it. All of our sense of good code just gets thrown away when we start writing views. I just say, oh, they're just views. But maybe that's not the best thing, right? So take for example this following chunk of code. We'll be using ERB if you're a hamlet user, I'm sorry, this was easier for me today. You've probably seen code like this and if you have it, well, you could really consider yourself lucky in my opinion. But it's just so easy to get here. So let's walk through kind of what this is doing because this is what we're looking at the whole time. The first thing we're doing is we're getting all our users out of our database and then we're looping through them. Simple enough, right? Next thing we're going to do is we're going to open up a list item and maybe someone should be able to click a user's name and that takes them to a profile page. Okay? Simple enough. Requirements change as they always do and, well, if the current user sees himself on the list, they should be able to click edit. Okay? So we'll link to an edit user path. Simple enough. Well, also if the user's an admin, they should be able to delete users. Okay? So we'll check and see. Maybe we're using devise and we have a current user method. We'll check to see if they're an admin and then we'll link to it. So it's not too complex, but there's a lot there and the question I asked is well, how did we get there? Well, the complexity probably grew over time, right? We probably didn't start off with a bunch of conditionals in our views. But something kind of struck me when I was putting together this talk. You see, I love talking about clean code. My like, my favorite thing is like we'll talk design patterns, let's talk refactoring where code belongs, I will preach skinny controller, I will preach skinny model, I will tell you to use service objects and I spend a lot of time learning about this stuff because I really enjoy it. I don't know if it's something about it makes me feel smart, but I enjoy it. So there's a reason we do this though and that's because those things are good, right? Overall they make our code better and they make us better. But I noticed a trend in my own life and that is, well, once I'm done refactoring and implementing all the patterns, I just stop and I say, oh, there's the views. Okay. So when I kind of hold valuable, I just let it go. So I want to spend my time today with you talking about refocusing on views, maybe giving our views some love. Before we do that, let's talk about a list of things this talk isn't so we can go ahead and set expectations. This isn't a talk about any kind of design because I am not a designer. I can't help you make your code look better from the outside. This is a talk on another type of view you might have heard of. When it comes to front-end JavaScript frameworks, I am not the guy to talk to on that. And this is also not a definitive guide. These are just things that I've learned that helped me and I hope they might help you, but it's okay if we disagree. So with that said, we'll start digging in and we'll get to fixing our code. So the good news is people before us have already thought about this problem. So let's go through this chunk of code and let's look at it section by section and look for what we'll call code smells, but really they're not definitive code smells. They're self-proclaimed code smells, which obviously my experience is the best, right? So I'm just kidding. So let's flip our views into shape. Does anyone see a potential, what we'll call code smell here, okay? So our views shouldn't know about our database, right? Now ActiveRecord makes it really easy for us to access models anywhere, anytime. It's fantastic, but the old saying with great power comes great responsibility. Our views shouldn't know the difference between an ActiveRecord collection and just an array. It shouldn't have to care about that. In fact, think about it too, one day if you need to change the way you're getting users, is it easier to go to a controller or to go dig through a view to find it? So I would say we should give our views data to work with. They shouldn't care about implementation. And luckily, as Rails developers, we know a very easy fix for this. We can use instance variables. So we'll say that this is an admin controller, right? Maybe it has some posts that it needs to show and then all our users. So we call users.all. And then already, we've got a quick win. Our code's easier to read and you can look at it the first time and say, okay, I'm working with users. You don't have to grep the user.all.each. So with that done, let's keep chipping away. Has anybody seen anything with this one? This one's a little less harmless, right? So inherently, there's really nothing wrong with this, but I want to focus in on one thing and that's the generation and the name. Right now, this is fine. What happens when we need to use that full name somewhere else? Maybe we'll use it in a navigation when users logged in. Maybe we'll send them an email. Well, you'd be writing that over and over again. That's a little verbose. And as Rails programmers, we're taught to not repeat ourselves. Well, that's not very dry if we're having to always concatenate a first and last name. And it's highly likely that we'll use that a lot. So this next part really deals with two things. The first part is, our views shouldn't repeat themselves. Now there will be HTML elements that need to repeat themselves. But as far as computing data, well, it shouldn't have to do that, which also brings us to the next point. Our views shouldn't really have to compute data. Sure, they're responsible for showing the data, but we shouldn't have to ask them to actually do the work. So we're going to look at three ways to solve this. And this will be kind of the core of what we'll talk about today. So we'll call these the three ways to drive code. Dry number one, we could fix this at a unit level. So we're dealing with a user object here. It's just an instance of a user, and we have a first and last name on that object. So sure enough, on our user, we can just have a name method, returns a string with a first and last name. And sure enough, whenever we want to access this, we just call user.name. This works fine, and it gives us our first benefit. So we can also write tests for it. For using RSpec, we can just get a user, essentially a user, say that we want to test that the name equals the first and last name, and sure enough, this would work. Better yet, in our code, well, now we just say user.name. So a little easier to read, less brain power when you're first looking at it. So it's cleaner, in my opinion. It's reusable, and it's testable. Earlier on in my career, I would say, this is great. Let's pack it up. My work here is done. But now, remember, I love talking about code organization. I love clean code. So the more I kind of dug into this, I learned about this term, this phrase, and it's called presentation logic. Is anybody here familiar with presentation logic? Okay, cool. So essentially, we have objects that only need data to show to the user. So this kind of logic doesn't get used in the business domain at all, it's just for presenting data. Okay, so that makes sense, right? But where does that belong? Because a model's not a good fit for that, right? Okay, so let's think about a Rails way we can do that. Well, second way we can kind of draft our codes, we can use helpers. Who here is used to helper? Yes, that should be all of us, because Rails is full of helpers. Anybody here written custom helpers? Okay, so if you're unfamiliar, helpers are functions that we have access to in every view and every controller. So some of the built-in helpers are dealing with assets from the asset pipeline, making dates prettier, making forms a little easier to work with, things like that. And actually, this is a, this works. That was a complete list of all the helpers available in Rails. Yeah, okay. So it's quite a bit, and they're quite useful. Things for caching, things for dates. We have all these available, too, so these are all helpers for forms, it goes on and on. If you wanna see that list, I went through and wrote them all out for my own sanity, and they're available there. Okay, so in our case, though, we didn't see necessarily a helper that'll help us write a full name, so we'll create our own. In Rails, inside apps, helpers, we have, we've generated a user's helper file, and we just have a method called fullname, and it takes any user, and it returns that string. Okay, so the nice thing is, we can just put that into our view. Right here, you'll notice that actually now, we're using three Rails helpers. We have link2, then we have our custom helper fullname, and then userPath, which is a dynamically generated method to help us get a path. Good news is, this is still testable. We'll switch over to Minitest, because Rails makes that pretty easy, and then we'll just assert that, hey, whenever I give this method a user that has a first and last name, it should make sense. Okay, great. Once again, this works. Let's pack it up. We're done. But remember, we're not done. So, this got me thinking, do we need helpers for every bit of data that we wanna work with? You know, helpers are really useful. They provide a great value to us, but that doesn't mean that I want every helper and every request, because a lot of times, I might not be working with a user. And by default, Rails automatically includes a lot of helpers on every request. In fact, it's 178 methods alone just from those, which, if you heard DHH's keynote, this is in line with the Rails doctrine, this is fine. But for me, I don't wanna add any more to that if I don't have to. I know, this might just be my opinion. It's not wrong to use helpers, but you got me thinking, is there a better way? And this is when I found decorators. So, the best way I can explain a decorator is to open up Microsoft Paint. So, let's say that we have a model, maybe any Ruby object. We'll say it's an instance of our user, and that's this red box right here. We instantiate it, and what we wanna do is pass it to another object. And this is our decorator, this blue box. So it goes inside our decorator, and what we do is that we have our decorator as just a wrapper for our object that we're working with. So the next thing we can do from there is that any method available to our, any method available from our original object, we still have access to, but since we now have a wrapped object, we can call other methods. That's a lot of words. Don't worry, I'm gonna show you. You can actually do this a number of ways. You can write your own Ruby objects to do this, but since we're efficient developers, we'll just use a gem that already exists. The gem is called Draper. So the GitHub repo says that Draper adds an object-oriented layer of presentation logic, there's that phrase again, to your Rails application. Okay, what does this mean? What does this even look like? So this is a decorator. It's just a class that inherits in our case from Draper decorator. And you'll notice there's this method here called delegate all. So what happens is, the best way I'll explain this is, with the example of, there's a lot of words there, with the example of a zip code. So maybe we have a user object who has a zip code, and what we wanna do is, there is no presentation logic for that. We can actually rely on our object to give us that. What delegate all does is say, hey, if this method is not defined here, we're not defining zip code, let's go to the object we're wrapping to get that. The other thing we have here is, comment it out code on how to generate, or how to use helpers. Helper, we're not gonna look at that today, but helpers in this case are nice because it doesn't load the whole set of helpers in, it's just as a per-needed base. So, there's no magic here really. Decorators are pretty straightforward, easy to use. So, let's go ahead and add that name we keep talking about. Okay, so we'll remove that commented code, and we'll just say, define name, and we'll say here, object.firstName and object.lastName. Object is how we reference our object we're wrapping. Sure enough, this works, it's still testable, and it gets us back kinda where we were wanting to go. But the thing about objects, decorator objects is you have to actually instantiate them. So, there's several ways to do this. We'll look at three ways. The first way is you can actually, with a Draper gym, just call decorate on an active record instance. You can also actually call a static method called decorate on the user decorator and pass in an object. Or if you're dealing with a collection, you can say user decorator, decorate collection. So, the one we want because we're dealing with users is this decorate collection. So, we'll just add to our controller and we'll, we have user.all and we'll just say, well, we wanna decorate that. So, we'll say user decorator, decorate collection, users. And sure enough, we go back, this works. So, that's a lot. Is everyone still with me? Yes, okay, thank you. That was a lot of terms. So, now we can keep on with our refactoring. So, what we've done is we've looked at three ways. We can do unit level refactoring. We can do helper refactoring and we can do decorator refactoring. The thing about this, though, is those all deal with objects, right? We have something, we have an object we actually work with. But what about here? Well, we're saying we have a user and if the current user equals the user, then show this. Okay, well, I don't know if that really fits, right? That doesn't really fit with the object because we've got a couple of different things going on here. So, first, let's look at the smell. Your view shouldn't know about conditionals, in my opinion. This is so hard to avoid and it's so easy to do. Rails makes it so easy. But surely there's a way we can get out of it. So we're not dealing with an object, per se. We just want to generate a link. So maybe a helper is a good fit here. Okay, so one, we know that if the user were displaying, we need to know if the user who were displaying is the current user. So we can maybe make a helper method called edit link. And we can shove our condition on here. We'll say, we want an edit link for the user. We'll pass it in and we'll say if the user equals the current user, and then we'll continue on. One thing I want to bring up though is you'll notice that I'm not passing in the current user, only the user. And that's because remember, these are included into every controller. So if you're using something like devise, current user is a controller action. So it will already have access to that. Okay, so this works. Next thing we need, we know we need a list item. So this is what that looks like. There's a list item with a link to edit. So we need to shove this inside our conditional, but do we really want to write raw HTML in our Ruby that seems kind of the opposite of what we're trying to do? So we'll rely on another helper called content tag. What content tag will do is it'll take whatever HTML element you want as a symbol, in our case, list item. And then what we'll do is we'll pass it a block with what should be inside our list item. Well, that's easy enough because we know we need a link, which is also a helper. So sure enough, we can just pass our link in there, link to edit, and our method's complete. We didn't remove the logic, per se, we just subtracted it out of our view. So now all our view cares about is this. Now, this code, in my opinion, is easy to read. It's also, though, it might be hard to debug because your conditional's not abstracted, but I think this is a better win. So we can do the same thing for our admin link. We'll take this following conditional, shove it into a helper method. So we'll say, hey, if the current user's an admin, let's open up a link item, a list item, and give it a link. So now our views have cleaned up quite a bit. In fact, this is the last part of factoring, and this is what we're left with. Well, we took out the active record call. We're just looping through users. We want to link to a user's name, or user profile, but we won't show their name. And then now we can look at that unordered list and say, well, there should be an edit link here, and a delete link. This is also, these helper methods we just made are also testable as well. I just did not have a chance to cover that today. You might be asking, but what about partials? It's a great question. I would say you should use partials. You should use partials as much as you want. But I would argue that maybe we should use partials to hide complexity, such as conditional, things like that. Still use your partials, but also try and find other ways to get conditionals out of views. So just to recap, there's four things that we did today. Our view now doesn't know about how things are implemented. It doesn't have to repeat itself. It doesn't have to compute data, and it doesn't have to account for conditionals. The important thing to me about all this is that our view is no less, and now they're pretty much responsible for one thing, and that's displaying data. I ran through that, so that's all I got. But yeah, thank you for coming out, and I have plenty of time to take questions, if there are any. So the question was, do I have a preference on passing in local variables or instance variables, two partials? I don't have a particular preference. At work, we've pretty much just kind of decided that we're gonna use locals for that, and that's worked all right for us. If you're coming to a code base that you haven't seen before, I feel like it's easier to look at that rendered partial and see what's being passed in, but I don't particularly have a strict preference on it now. You could. So the question was, could you just use view models instead of decorators? You can. I use the decorator pattern, or the draper gem, mostly because, well I'm honest, mostly because it's what I've inherited, but also because I like the, sorry, I'm trying to think of how to word this out. I like being able to rely on some of the things it gives, being able to access helpers, things like that. It makes it a little bit easier for me, but when you say view model, you're talking about just making actual models that are just responsible for displaying data. Right. Something like Hanami does with view models. I don't know if you're familiar with that. Okay, yeah. Yeah, you could do that. I don't really have a preference either. Well I do have a preference, I prefer decorator, but that's because I only have it worked with view models, like just making my own objects. Right, so the question was, do I recircle with nil values in decorator objects? Yes. My entire life is dealing with nil values. Yeah, but I guess the advantage I have is, you know, the advantage I see too is you still have a place to deal with that that's not in your view. It's something you can start testing for is nil values, things like that, so. Yeah, so the question was if you are mixing manually writing LI tags in HTML or ERB verses using helpers to generate LI tags, give or run to issues, yes. I've tried to, even if there's no conditional logic, I'll still try and if it has to compute some data, I'll still try and put it in a helper, only because, so for example is, this may be a horrible example, but this is what I did recently. With bootstrap, you know, you have the carousel, and so for us, we needed to dynamically update and sometimes we'll show the buttons, like to click on some, and then we always want to show the arrows. So I kind of extracted that out into little helpers so that we can reuse it throughout the site and that if we do have to make a change, even if there's no conditionals, we don't have to make it one place. Which you could argue that some of that could have been in partials, but since I started, like some of it was in helpers, I didn't want to like split it across. So yeah, there's ways to mitigate it, but still, yeah, you're still having to repeat yourself some, so I knew someone would ask this. The difference between presenters and decorators, I don't know, I haven't worked with the presenter pattern. So far I've been limited to the things I've shown today. Yeah, unfortunately I'm not qualified to speak on the difference, no. Oh, cells, yeah, have I used the cells, Jim, sorry. Yeah, so the cells, Jim, that's part of the trailblazer project, right? I have not, I really want to. I really am intrigued by the whole trailblazer project, but I haven't taken the time commitment to learn it all yet, so no, I have not. Do I like using the content tag? Kinda. I like, so I started off as a front-end developer, which was like not my life calling. And I made my way into server side and so when I first came there, I was like, yeah, I like, I just want to use all the Ruby things. If I'm just gonna generate a slide, if I have nothing back from Ruby, like since variable is nothing, then it's not worth it to me to use, but otherwise, yeah. Well, thank you for coming out. I really appreciate it and come talk to me. I will try to not mince my words less when I talk to you in person, so thank you. Thank you. Thank you.