Skip to main content

3 Open Source Vlog - Sorting Calendar Events

Episode 79 · July 18, 2015

We sort and filter calendar events for simple_calendar 2.0 in a much more friendly manner

Models Frontend


Transcripts

What's up guys? It is day 15 of the vlog which is pretty awesome, and we've made a ton of progress, so simple calendar is fully functional for pretty much everything we wanted to accomplish except for one last piece which is the events need to be filtered in a little bit better mannered than what we've done here. We've taken the events and made them this option that you can pass in, and that's great, but we're still doing a select and a sort in the view and that's kind of a little too much for the view to have to deal with, there's too much ruby logic in here, so I want to delete this code, and I want to be able to say: let's have a sorted events hash

_calendar.html.erb
<% sorted_events.each do |meeting| %>

We take the array that you gave us, and we create a hash out of it so that we can look up all those events by the day really quickly we could pull out that array, it's already sorted for us, we just grab it, and we have auto filtered everything and you're ready to go. Ideally this is what we could do, and I would love to be able to take this and then provide that to the vies so that we could do that, and it would be more performant as well, because previously we were doing a select and a sort every single day of the calendar, so this would recalculate things and then rendering the view would be a lot lot faster. That said, let's dive into our calendar and make that happen. I'm going to save this file here at the top, but I'll leave it open so you can see the sorted events day that we're trying to hit, which means that we need to make a method called sorted_events and we're going to take that and return it into sorted_events as the local variable that we're passing into the partial, so I've sorted this and we'll take this event here and I'll put it down here. This is what we're going to use to start to calculate that hash. We need this sorted hash, and basically what we're going to create here is we want it to look like a hash like something like this, where you have the date as the key and then you have the array of events and maybe you have three events in that one, maybe this other one has just one event and it's event number four or whatever, and you have like a day that's also got two on it. The hash doesn't need to have arrays for any of the days that don't have events, because who cares? They don't have events so we can just return nil back and then convert it to an array and make sure that that's cool So as long as we change this rather than doing a square bracket where it could return nil, we could change this to a fetch and use an empty array as the default, so if we didn't find any, at least the code won't crash and we don't have any if statements or anything in there, so our code is going to run correctly no matter what the case is. Now that we're headed, this is ideal for what we can do, I'm going to just brute force this, because it's fine. We're going to return this sorted hash back at the end, but we can improve the plucking out of events and sorting as we go and you guys can feel free to send me refactorings of this method, I just want to talk through the logic here because this is very ruby focused, and what we're going to take is this big array of events, we're going to go through each of those and collect the days out so that way we can get a list of keys of the hash that we need, and then we need to put that in an array, and make sure that all of them are sorted, and that's really it. It's a little bit of work but we need to go through and do that. If we go through every single event and we loop through each of those, that means we could say: For the sorted hash we could look at the event. We know the date that we're looking at, which is event.start_time.to_date, we want to make sure that we're using date objects and not times, and then this sorted date will allow us to access that key inside the hash, and if it doesn't have one yet, we could do null= empty array. That would allow us to say: If there isn't an array of events for this day yet, so it's brand new, and we've never seen it before, let's create a new array so that the next line we can do we can shovel that event into the array, and last but not least, we can go through this and we can say: Let's sort those. This would allow us to do basically those same steps that we had in our view before and if we back this all the way out you'll see that we grabbed the start_time.to_date. This was a select that we used to find the correct day, which is now happening as this selection on a hash, is the exact same as the select here but it's much more performant can look things up really quickly and then we're doing the sorting for each of those events for those days, just like we were before and we do that here at the end of the list.

calendar.rb

def sorted_events 
    events = options.fetch(:events, [])
    sorted = {}

    events.each do |event|
        date = event.start_time.to_date 
        sorted[date] ||= []
        sorted[date] << event 
        sorted[date] = sorted[date].sort_by(&:start_time)
    end

    sorted 
end

This is the piece that needs to be refactored, I'll leave that up to one of you guys as a challenge, this is going through each of the events creating an array in the hash, that's fine and adds the item to the list. What's not great is when you have to resort that array every single time that you add one to a specific day, so if you have five items on a single day, what you're doing is you're sorting that list five different times which you don't need to do, you really only need to do one time at the very end of creating those lists, so ideally you would have a to-do down here that would say "move sorting by start_time to after the event loop" here is where you would want to write that code to do this sorting, you'd move that down and you would go through every key and value inside the sorted hash, and you would use that instead, not doing it here, but that's your challenge, not mine, I'm just going to get this working and hopefully one of you guys will tackle that soon for me. This actually makes some wonderful progress for us because now if we go and we update this code again, what we really need is that fetch(day, []) and this has changed our code such that we're able to go through this whole process, find out all the meetings and whatever, and we can take this and update each one of these calendars so that this code is handled inside the gem, we'll save that, we'll go to the week calendar as well, and once we've pulled that in, we should test this in our application to make sure that it's working, and let's go into localhost:3000/meetings and looks like it still is. This is really awesome, we've got everything working perfectly as before but now it's a little bit more efficient, and our code in the view actually doesn't do anything magical at all, there's no processing in the view happening, that's very good because logic in your views is always views is always bad and you should try to make sure that that code is in a logical place, and views are usually just good to display things. We've refactored that out in a pretty great way, so now our sorted_events method here will be available for every single one of those calendars that you build. The month calendar, the week calendar, anything else you want, and as you noticed here as well, this first calendar has no events passed to it, and it worked fine as well, so that means that also those events are optional because the first time we checked for events, if there isn't we'll use an empty array that means that this each block doesn't get executed at all, and we return the empty hash to the browser, and then every time you try and fetch those days, there is none so we use an empty array instead, and all of your code works functionally as before, and there's no if statements which is the key to writing good code. If there is one thing you take away from this vlog: don't use if statements unless you absolutely have to. If there's any way to pull out if statements and just structure your code like the way we just did here, that is the way to do it. So these fetches are key to pulling out the if statements, and the same thing happens to go for the hash, it helps us pull that stuff out as well. Now that that refactoring is in the bag, we should commit this and make sure that we push it up to GitHub as well because this is one of those last pieces that I wanted to clean up before we released this gem and of course we need to write a couple tests, but as you can see here, we could test this templates, they're not really that big of a deal, we may have one overall template to make sure that it renders that. One test to render the template, and then really we just need to test these methods inside of our class, so we need the initialize, I don't really know that we need to test that one that much, it's like two lines of code, if it ever got broke we would know immediately, but we should test it, and then the render's got a couple interesting things in that we need this class name so we should actually pull this one out, and we should say: Let's do a partial name method, and this can have the partial name, and that would allow us to do a test for the partial name a little bit easier and make sure that we can confirm that better. Really this render method you don't need to test it that much so long as you make sure it calls the partial with the right value and then passes in your locals, since it really only does one thing and more or less delegates to this other render method, it's pretty straightforward and these are kind of the key pieces to test, so the partial name is important because you want to make sure it renders the right template, the sorted_event is super important because you want to make sure those events are sorted and filtered on the right days. start_date is hugely important because what is a calendar without knowing which calendar version to display. date_range is hugely important because that is the key to all of this, to know how many days to display, and additional_days is not really that important because it's only used in this one calendar, but of course, this makes it so much easier because these are simple things to test individually whereas the whole simple_calendar in the past was awful, I can't believe I wrote that code, but I think we say that about all of our code from three months ago or older all the time. None of the code of the past was very good. That's a good sign because that means you're learning, so awesome. Let's do another

git status
git add .
git commit -m "Refactoring the event filtering into the calendar class"
git commit -m "Refactor the partial name into it's own method"
git push

That is it. Let's add some more tests, I would definitely love to see someone submit a pull request to fix that little performance problem, there's probably not that big of calendars that people will be rendering with that many events that that would become a performance issue, but it could happen, and we know it's kind of not a great performance thing anyways so if you feel like getting around to it, please fix it.

That's probably where I'm going to cut this off today, because it's saturday, I hope you enjoy your weekend and I will talk to you again very soon. Peace v

Transcript written by Miguel

Discussion