Skip to main content
Building A Calendar Gem:

Open Source Vlog - Finding a balance between Ruby and ERB

Episode 72 · July 7, 2015

Taking the ERB code we wrote for simple_calendar and finding the right balance of Ruby and ERB for our gem

Open Source Views


Earn a free month

What's up guys? I'm back, I'm back. I did eight days in a row, took a few days break with no heads up and I apologize for that, it was the fourth of July, I was getting super tired at the end of the week there, so I took a few days off. Vlog is back on now, plan to continue doing this as much as I can, but I definitely gonna try to manage my burnout, because that was bad, but I'll let you know.

Today we're taking a look at pretty much where we left off, we wrote all of this awesome code, and we now have to figure out how we balance erb and we balance ruby, and that's a really fine line to straddle here because in the past, I've written in all ruby what I've shown you in the last few vlog episodes, was all erb, and that's been great too, but I think there's a really fine balance that we can take, so let's try to hit that today. I was thinking deeply about this, and before I started recording, I was diving into this content_for method, we might have used in your layouts before, content_for is really cool, and actually I was looking at this idea of like to use a partial and yield to a block if you're inside of a partial, and then, how would we go about taking these titles and making these interchangeable and configurable and stuff like that, and I was not really sure of what I was going to do exactly, so it's kind of like doing a little bit of research, and I should have recorded it, I kind of didn't because I was honestly not sure where I was going, so I did a little research, and I found this Stack Overflow question, and the guy comes up with like: Here, I'd ideally like this code where it renders a partial, name, and I give it some variables, and then it yields to a block, and then I could print out stuff, that is definitely possible, you can definitely do that, you just have to make sure you render a layout. Layouts are special, they're different than partials, in that they allow you to yield to a block. This is useful information and could potentially allow us to skip much of the ruby stuff maybe, I'm not quite sure, but you basically write a yield in your layout, and then your render layout allows you to do that, so it kind of works like a layout and a partial combined, which is cool, but then it's like: That's going to require lot's of locals, and then if these layouts are always in the gem, they're not very flexible, and it's going to be really hard to overwrite the headers and the titles and like that and I was not exactly sure if I want to go in this direction so I was basically reading like what can content_for do, because I know you can yield with a specific name, so you could say: yield title, and then your views could do like content_for title, and then it would just put that into the yield section, and that works for layouts and stuff, but if you put two calendars on the same page, good luck, it's not going to work because you would have to generate dynamic names for each calendar and it didn't seem write, it seems overly complex and we'd be going down this rabbit hole that wouldn't be a lot of fun. Without further ado, I'm going to transfer this to my previous self, because I'm recording the intro after I recorded most of the vlog, so I'll jump back and we'll just start diving into the code and playing with it without diving into content_for because this doesn't seem like the direction that will get us the approach that we want, and I'm pretty happy with what I came up with today, so I hope you enjoyed the vlog, and let's do this.

Last that we checked, we had this giant list of methods that we used to have in our simple calendar gem, we deleted all the code in them, we kept the method names around, and this was kind of important because I needed to see all the things we needed before so we could clean it up, but now really I think we can delete most of these. I think these are the two core methods we're going to need, and we can start playing with these inside our rails app. I'm going to leave these calendar side by side here, and I'm going to put a horizontal line between them to separate them, but you'll be able to see that, and we're going to experiment what we want our implementation to work like. In the past I've done these things like month calendar, and then you pass in your options like maybe you have events and there's meetings, and then you do

<%= month_calendar events: @meetings do |day, meetings| %>

You can print out the day, and so on. This is pretty good. I'm going to leave out meetings because that's a later feature that we're going to add. This is like what we had in the past, and I really like this because it's easy to just wrap into your application, but this month_calendar thing is not really necessary, and I don't really mind just saying: Let's access the simple calendar calendar, we have to create a new one. self is actually this view that we're inside. self in this case, because we're in erb there's something special here, and it's like a view context, and it's what gives us access to all the render methods and things like that, link_to, you name it.


<%= do |day| %> 
    <%= day %> 
<% end %> 

That's important, and we're going to need to use that inside of our gem, and we're going to accept that as the view context. That's really all we need there. Hopefully in this case we're not going to need that many options, and then we'll just be able to call render and pass in a block. We're already making some progress with cleaning this up, I think this is going to be a lot better, and if we save this, we'll see what happens, it definitely should break, and there's my test room earlier with the old calendar, but we got a horizontal line, so one of the weird things and maybe you if you know the answer to this, let me know in the comments, but when you change, I've got gem 'simple_calendar' here loaded from a local path on this computer, every time I make a change to the gem, I have to restart the rails server so I can reload those things, I've looked at autoload in the past and it's never really worked for me, and I've read tons and tons of things on it, I had tried all these stuff, and it's never really worked, so maybe if you know how to get these gems to reload on every request, let me know, but I've never ever been able to get it to work, so that's one of those caveats that I'm dealing with, that I apologize because it's going to be really slow. We have to restart the rails server, refresh the page, this is the error we were expecting and it's basically saying that we pass in a block, and we weren't really expecting a block, so we need to change it to & so that ruby knows that this block that we passed in is a block that we should accept. If we jump back to our simple calendar code, save it, restart our rails server, jump back to chrome, refresh our page, this is now functional. it doesn't print anything else out, but that's totally fine, because we didn't tell it to do that, now one of these pieces here that I find really interesting is that this table generation is much nicer to deal with when it's in erb. I was doing all these content tags and concats and all this nested stuff that was really nasty to do in ruby and I'd like to be able to do that in the gem so just provide a partial in there that we could do. First off, in order to do that, we need to write an accessor for view contacts so that we can set that.


module SimpleCalendar 
    class Calendar 
        attr_accessor :view_context 

        def initialize(view_context, opts={})
            @view_context = view_context

        def render(&block)
            view_context.render partial: "calendar", locals: {}


Let's restart our rails server again, we're not going to get an error because I've actually created an app/views/meetings/calendar that's empty, so no error, it's printing out this calendar, it's printing our this calendar erb file, it's relative to wherever you're at, so because we call this in meetings/index, this calls render inside the gem, which happens here, which calls calendar, there's this thing called the view paths, which is a list of locations in your rails app to look for views like partials and things like that. We will actually need eventually to put our gem folder into that list, but right now we can just create the calendar file inside the meetings folder, and do that here.

If we paste in the code that we had before, this is going to be really cool, because we'll be able to use this table, and we'll be able to edit it, and allow users to add this into their own app, and then override these things, and that'll be nice for people to be able to tweak that. Ideally what we can get rid of, is all this class options everywhere, because they were horrible to deal with, and if we let you just customize the partial and put your own classes in or anything else you want, it will save everyone so much trouble, it's really painful to deal with that in ruby, but it's perfect to deal with erb, so we're finding that balance, and it feels right, so if you want to change these table classes, it's going to be way easier that it ever was before, so we're making good progress. I'm going to save this and let's see what happens. If we restart the rails server, refresh our page, we should get an error, and the reason why we're going to get this error is it's going to say there's an undefined local variable or method called date_range, and that's absolutely true because we have date_range here but our calendar doesn't have any idea of what the date_range is, but we need to actually go and take the code from the date_range, and then move it into the gem, so I'm just going to take these two lines here and put it in the gem, because these are really the only ones we want to expose publicly. We can take these variables and make them methods instead, and then make some progress with this.


def date_range 
    (start_date..(start_date + 3.days)).to_a

we now have a lot of the table stuff already sorted out, so we can give the partial these variables, so you can say date_range is going to be the value of this date_range method, and then the same with the start date, actually let's check to see if we need start_date, does our calendar need that? We have the date range, the date range, we have the start time, but that's for the meetings. One thing we're missing here is we've forgot to grab the links and the title at the top of the calendar, so we should grab these and move them in, and that is going to reference the date range as well as the start date, so we will need to pass in the start date as one of the options into our partial.


def render 
    view_context.render partial: "calendar",
        locals: { date_range: date_range, start_date: start_date}

That's starting to look pretty good. we'll wrap this in parenthesis so it formats nicely. Now we're starting to make some progress, we've got the render, we know exactly which variables are crucial to rendering this out, and then our start date and our date range are the only two that are like requirements so far, so let's restart our server and see if we can make some progress on this. Cool, now we have undefined method params, and that of course would be because we're accessing params inside the start date method, now something I'm not completely sure is if our view context can access brands, so maybe we can try that here, and of course it pulls it out, so we could just do


def start_date 

To access the regular params hash that we're used to, but because we're passing that in as a variable, we're not in the same context anymore, which is maybe a little confusing, we just basically don't have the access to all those variables that are normally there by default in your views, so because we're doing this and we abstracted it out, we have limited access to things which is totally fine, and the view context allows us to do pretty much everything we need. This is cool, and now we can restart one more time, and refresh the page, and in theory if this all goes well, we should now have a calendar being rendered through the gem and a partial, that is super duper cool. We've basically reduced the lines of code for this gigantically. There's some flexibility we need to add in with options, we need to be able to override the number of days that you want to display, but that's going to be super easy, so if we have these options here, let's make options a variable @options = opts, here we can have this where it's basically


def date_range 
    (start_date..(start_date + options.fetch(:number_of_days, 3).days)).to_a

Then, let's restart the server and make sure that still works, just to be safe, and then here, if we jump back to our view, index.html.erb, we have this, the simple calendar render, here you can just pass in number_of_days as 3, and that's the same one, but now if you do number_of_days: 1, you now get a two day calendar, number_of_days: 0 is a single day calendar. It's a little weird of course, because it's the number of extra days, so we probably want to change that name so it's a little bit more clear, so we can probably change this number to subtract one from the value you pass in, so if it's like number_of_days we want four, and then we subtract one so we actually have three, so we get three additional days because we're always going to have the start date so we can always assume that that's going to be the thing and I think if you're thinking about building a calendar you know the total number of days that you want, like 4, 14 or whatever, so I think it makes more sense for us to say: This is a two day calendar, or actually let's do one as the example, and then here we'll do a number_of_days or 4-1, so we can pull this out into a method called:


def additional_days
    options.fetch(:number_of_days, 4) -1 

That might make more sense for clarity when you're editing this gem or working on it that the additional days has the subtract one for an important reason, whereas the length of days doesn't kind of make sense at all to have this negative one, because it's a tiny little piece there, so I think I'm going to name it additional_days so we can call out that little weird use case there or a very tricky thing to name, but I think if we do it this way, it will be much clearer. We have this now, and we should be able to restart, and everything should still work. We have one day calendar, we can jump back to our code and make it a six day calendar, we have this wonderful calendar already functional here, and we can do like a fourteen day calendar, and this should automatically split, and it does. Something that's interesting here is that Tuesday as the beginning, I believe that was playing with the code, so don't worry about that, I think if we go into application.rb. Let's check our code to see if we have any references to Tuesday, because it's definitely weird as the start of the week. Nothing that I can find, so maybe we'll just ignore that for now, and worst case I deleted that start of the week is Sunday.

The reason why this is Tuesday is because today is Tuesday, and the regular calendar always starts on the current day, it doesn't have the calculation to go back and forth. That's a very very important piece, so what we're looking at here is the date_range method has different calculation than any of the other ones, so this being more the default calendar is more an agenda calendar used for like four days long or maybe one day long, but it always starts at the current day, it never goes back to the beginning of the week. That's an important piece to note for this type of calendar, and the start date especially. What we've got here is this wonderfully simple calendar now, and we've pulled out almost all of the rendering into an html template, which is so cool, and that means that now we can go back into this tomorrow and pull out all of the month calendar stuff and build that and build the week calendar, and they'll really just be inheriting from this, and overriding the date range, and I think that's really it. This additional_days method is actually totally specific to the agenda calendar, and maybe the agenda calendar becomes it's own class too, but what we've accomplished here today is we pulled out all of the rendering and put it in a view that saves us so much work. Tomorrow we'll take the month calendar, and the week calendar with this new layout, and then we'll go from there

Transcript written by Miguel


Subscribe to the newsletter

Join 31,353+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.