Resources

Transcripts

What's up guys, this episode we're talking about the draper gem, how you can use it to add decorators to your rails app. Now in the last episode I explained what decorators are, how you use them and how you build them from scratch and why you would want to use them. So we're going to talk about implementing them with Draper in this episode. So the reason why you might want to use Draper over your own implementation is that it's actually integrated super deeply into rails, but that could also potentially be a problem, becuase if you want to upgrade your rails app, then you are probably waiting along for draper to get upgraded to be compatible with your rails version. If we scroll down here to the installation instructions, we just need to add Draper to our Gemfile. Now if you're using rails 5, you want to grab the prerelease version of draper, becuase the ActiveModel serializers XML dependency is not included in rails 5, so if you were to install the latest stable version, that won't work with rails 5 yet. So this now depends on active_model_serializers and everything should be good if you use that. So go down here, we'll paste that in, and we want to make sure we get that pre-release, so let's grab that one and paste that in. Then we can go into our terminal and run bundle install.

As you can see right thee, Draper is designed to be heavily integrated to rails, so you will get things like that where if rails changes, then your draper gem is going to have to change, or you're going to have to use a version from GitHub, and it's not going to be near as stable as building your own decorators where they're going to have a less deep integration with rails and you have more control over that way. But there are some great benefits of using this gem, such as whenever you generate a resource, it will automatically create a model and a decorator for you, but you can also generate a decorator with the command line which is great.

rails generate decorator

And we'll try and recreate what we wrote in the last episode. So here we get a test for the user decorator, and we also get the decorator in the exact same location as we saw before, so let's take a look at that and see what's different.

This time, we have Draper::Decorator that we inherit from, and that's going to provide us all of the methods specific to Draper, such as the initialize so that we can take the object like the user automatically, and it creates the initialize method, and stuff like that, so we don't have to do any of that. It will also take care of the helpers for us, so we get that view contex that we had to pass in ourselves. We get all of that access to that via the helpers method. So helpers, or the alias for it, called H, can allow us to create those content tags, or link_to or anything we want inside of the decorator here, and we already have that all set up for us, which is nice.

Let's talk about how we instantiate the decorator, and then we'll talk about how we can use it, and how it's different from our own version. So if we go into our controller, let's imagine that we were logged in and we had a current_user object. This could be decorated by just saying current_user.decorate and this will work for any sort of ActiveRecord model. You might say user.first.decorate and that would give you a user decorator back, so it just looks at the class name of this object, whatever you give it and call decorate on, look at that class, it will say: Oh, this is a user, therefore we need a user decorator, and we initialize a new one of those objects and we give that back to you and there you go, you have that decorated object.

You can see this in the console if we run User.first.decorate, this should give us a user decorator object back, and we can see that the variable inside of our decorator is pointing to that user that we pulled out. That means that this is pretty much doing exactly the same thing as if you were to say: User.decorator.new(User.first), that's just a helper method that they add to ActiveRecord::Base so that you can go in and call the decorate method on any of your individual objects. So that works well, and you can also use this option if you would like to specifically pass in that class. So this works just as well. Now for collections, it's a little bit different, but it also goes to ActiveRecord::Base, so if you have ActiveRecord relation, you can say the same thing. So where as before, we can actually say:

defindex@users=Users.all@users.decorateend

And as long as @users or whatever you call decorate on is an ActiveRecord relation, this will work just fine. So if we go into the console, and we say User.all.decorate, we're going to get a Draper collection decorator back. So this has wrapped all of that. Now if you were to do an array, that's not going to work, so if you were to grab all the users and convert it to an array, this is an array object, it is not an ActiveRecord relation so the Draper helper method is not available here, but what you can say:

UserDecorator.decorate_collection(User.all.to_a)

and do the same thing if you have an array rather than an ActiveRecord relation, so that will work as well in case you were doing something like that where you need to go and filter and sort your results using pure ruby and you end up with an array, you can absolutely use this for those as well. What this means is that for our show views and our index views, we can just assign the decorators to the instance variable as a replacement for our normal version of this, and that can be more fluid that way. And this can be done the same way if you do decorators from scratch, you just have to remember that you're operating with decorators. Now be careful with this, because when you're doing an update, you don't want to update @user and do that on a decorator, you want to make sure you do that on a user model, and then at the very last moment, before you go to render a view, you want to then make the @user model or a user instance variable, a decorator and not a model. So what you would do here is something like this

That way, you know that the only time you assign instance variables, you can make sure that they are decorators, so you know that you're separately working with the database when you were calling update and decorators when you're in your views, because you don't want to call update on a decorator because that's only designed to work with views and display that information in an interactible way, so you could pass through a decorator to do that, but that is not a good idea and that totally defeats the purpose of using decorators, so don't do that.

One of the ways that draper is designed to help combat that is to introduce a decorates_assigned method that you can put in your controller, and you could say here: user. And what would happen is you would operate on your user model just like normal, you would set your instance variables, and then in the views, if you ever wanted to call @user, you would not do that, you would actually use user. And of course in the views, there would be erb tags around that, and what that means is that this, when it gets accessed in the views, we wouldn't call this instance variable directly in the typical fashion of saying @user, we would call user which would be a helper method provided by the controller that this defines. So if we wanted to test this out, we can try to say users here, and our index action which we have defined with the decorators, we can actually skip the decorators, save this, and if we go and look at our users index, we want to change the instance variable to call that helper method that gets defined there, and if this works, then we will see in our browser the exact same output, but all of these will have been decorators that were automatically decorated by Draper, so that's really nifty to have that built in. that way you don't have to worry about doing the decoration yourself, and if you do your own decorators from scratch, you can actually build that little helper method as well, because in your controller there, this is effectively just defining a user's method here, and saying

defusers@users.decorateend

and that's about it. So this behind the scenes is doing a pretty simple thing, but it's already done for you, and then ends up cleaning up your controllers so that there's no real knowledge of decorator happening, it's kind of automatically taken care of for you by accessing the right methods and never using instance variables anymore, so this is pretty good. I like that, I like that a lot, so that gives us that ability, and if we wanted to, we can go into the view and we can say index.html, and let's do a console here, and this should give us that console at the bottom, and we can access the user's function, and if we go up to the top you will see that we get a draper collection decorator which hast the object which has the ActiveRecord relation, and at the very end, we can see that there is a whole big list of decorators that we get. So we get user decorator and the object for each one of those, and it's basically doing that map that we did whenever you call that on an association. So the decorator stuff works really nicely and isn't obtrusive in any way which is what I like a lot, so we can learn a lot about if you wanted to build your own decorator stuff in your own rails app, you could go and replicate all of this stuff, you just have to define those helper methods to be compatible. So all of that handles our controller's concerns about the decorators, which is a very important thing, we have to make sure that we assign the decorators the right time, and we interact with them at the right places, and this is a really good job of that, I'm really impressed with this solution because it makes it very seamless and doesn't end up changing our code hardly at all. In the views, it does a litle bit, but that's it, so we don't use instance variables anymore but that is it. Now when you want to implement one of your decorators, for example we want to implement that full_name method that we had before, and we have access to that object or alternatively you can call it model, but it is actually named object internally, so you can use that, and you'll see that if you inspect the user decorator, and so here you can call it

andupdateourviewstoprintoutthosenames.Andgotoourviewsandtestthisout,andweseethatwegetthefullnamesandthatiscombinedforuswonderfully.Now,oneofthebenefitsthatyougetfromtheinheritancefromDraperdecoratoristhatifyouwantedto,youcanusethemethod*missing*thatitimplementstodelegateallthosecallstoyourobject.Sowhenyoucall*first_name*or*last_name*,thatisbasicallygoingtothrowanerrorexceptthaterrorgetscaughtbythemethod*missing*functionthatdraperdecoratorimplements,andthenit's going to attempt to call tha on your user in this decorator. So it will call whatever object that you passed in, and then try to call it on there, and that way, you can end up writing this code similarly as if it was inside of the model, and you don'thavetowriteanyofyourspecificdelegatemethodslikewedidbefore.Soyoucangetridofthis`delegate_all`,andyouwouldlosethatfunctionality,andyoucadothe`delegate :first_name, :last_name, to: :object`soyoucoulddoanexplicitversionofthis,oryoucanalsousethe*delegate_all*whichwillusethemethod*missing*implementation.Soherewerefreshagainandwearenowusingthat*methodmissing*implementationthatwillautomaticallypassthoseovertotheobject.NowI'm going to paste in the two methods that we had before for the staff badge and the moderator badge, and you'llrememberthatwepassedinthe*view_context*variablehere,andthatgivesusaccesstoalloftheviews,helpersandeverythinglikethat,andwecandotheexactsamethingbysaying`helpers.whatever`andthat's going to give us the exact same thing, so it'sprettymuchtheexactsameimplementationthere,soyoucansay`h.content_tag`soyouhaveaniceshortmethodthere,andherewecansay`user.mod_badge`and`user.staff_badge`,sowiththatimplemented,wecanrefreshandwewillgetthosebadgesbackjustlikewedidbefore,andourimplementationisalmosexactlythesame.OneinterestingthingIwanttopointouthere,Iwouldn't really recommend this, but it is kind of nifty that you can say `include Draper::LazyHelpers` and this is actually going to do a similar delegate thing in a sense where all of these methods for your helpers will get mixed into this decorator and you can say *content_tag* directly without specifying that helpers or h. ahead of that, and so you can kind of write these as if you'rebothatthesametimeinsideofyourmodelandinsidethehelpers,whichisinteresting,butIreallywouldn't recommend that approach because it'simportantforyouinsideofthedecoratortoknowthedifferencesbetweenwhichmethodsarehelpermethodsandwhichonesareusermethods,orwhatevermodelyouweredecoratingthosemethods,soit's important to separate those, and you'realsonotincludingthatagiantamountofhelpersdirectlyinsideofyourdecorator.Sodrapergivesusalotofnicitieshere,butwhataboutthecaseofpagination?Thisisoneofthosethingswherepaginationisactuallykindofaviewthing,butit's also just as equally database thing because you have to worry about your limits and your offsets, so if you said you want 10 results per page, then you need to limit to 10, but you may be on page 5 and you need to skip the first 40 or 50 records and then take the next 10, and so your views need to know a lot about the database on how to calculate those, and if you were using a gem like *will_paginate* or *kaminari* you need to know how to integrate that with your decorators, so do you pass this through your decorators or do you not? and there'sacoupleoptionsthatyoucandowiththis.Solet's install *will_paginate* here, so I'veaddedthattomyGemfile,andI'vealreadyrunbundlertoinstallit,soweneedtogointoourcontrollerfirst**app/controllers/users_controller.rb**```ruby class UsersController < ApplicationController decorates_assigned :user, :users def index @users = Users.all.paginate(page: params[:page]) end end

Just so that it knows what the default page is, and then we can go into our views and add the will_paginate call, and we have to pass in the users into this, which one do we pass in? Do we do @users or do we use the users decorators? Well really, as long as you're decorating at the very last moment, then you can use your @users assigned variable and know that you're operating with your database models, and so this is never interacting with Draper, and that's probably the way that you want to do this. So you would skip draper entirely for your pagination, and you're going to be able to refresh, and you will have your pages and everything will work just like normal because you aren't actually even using decorators here whatsoever. Now you could pass in decorators, in case there was some reason you needed to, but if you refresh this page, you're going to see that you're going to get a methods missing, such as total_pages, because your Draper's collection decorator is the object that you have there, it's a collection decorator, and it needs to implement some methods in order to be compatible with will_paginate.

Draper's docs have some information about this, and you actually have to go and build another decorator, called PaginatingDecorator and then later on you can say: This will be the collection decorator class. So let's go build this out and show you how that works. Inside our decorators folder, we want to create a new one,

And you'll notice that we have to do this decorator because there's two things that we've decorated. Number one, is that we can decorate a relation, so a set of records, we can have that we decorate, and then we also have an individual record. So a set is actually a bunch of individual records that are decorated but the collection is actually what will paginate and kaminari you're going to care about, not the individual ones, which means that we can't implement these methods inside of the individual decorator, we have to put it in the collection decorator, and so we have to define this class to specifically delegate these methods, and there is no delegate_all that we want to do here for the collection, because it's a collection, it isn't an individual record. But once that is done, and you've saved it, you can grab the self.collection_decorator_class snippet here, and we can paste that inside of our user decorator at the bottom and keep that all nice and hidden away, and that should do the trick for us, we can get rid of this example, and if we go back to our browser, it all works again, and we are now using pagination through our decorators. Now of course, there's almost no reason that you would need to do that, but for some reason, if you didn't have the access to the models directly, with the instance variables, then you can use this as a solution for paginating your decorated collection. Now we've already gone on very long, so I'm going to cut this off here, but there's a lot of other cools stuff that Draper can do out of the box, such as deecorating associations. You might have a blog post, and you might have an association for comments; well, when you access the decorated blog post, and you call comments on it, it's actually going to call the association normally but you can also use decorates_association inside of your decorator so that when you call comments, it will actually call Draper around those comments, and then comments will be decorated as well so you can have that automatically connected and set up so that you can issue those associations in your views just like we normally would, but they would automatically be decorated, and so you can trust that you're always working with decorators in your views, and that's pretty nifty, I like that. So there's some other stuff you can do, there's finders there's contact stuff, and I would encourage you to check that out in the README if you want to learn more about it, but I think that Draper does a pretty good job of implementing this, but as you might have noticed, we got a very very long ways into building something just like Draper and we hardly wrote any special code to pull that off, so you can definitely build your decorators without a gem and do a really really easy job of implementing that. There are a lot of nicities that Draper gives you, once you get into more advanced decorator stuff that you might want to use the gem for fo you have that already ready to go when you get around to that. I hope you enjoyed this episode, let me know in the comments below, like this video if you enjoyed it and want to see more design pattern stuff, and I will talk to you in the next one. Peace