Skip to main content
28 Rails Concepts:

Pair Programming on Rails Scopes

Episode 71 · July 6, 2015

James from Shakycode and I pair program on some common model scopes related to time and how we would approach them

ActiveRecord


Transcripts

James: All right, so here we are today, Chris Oliver from Gorails and James Jelinek from Shakecode, we're getting together today to talk about scopes and some custom scopes. This is going to be fun, this is going to be our first time pairing live on screen, so look for any typos and excuse us.

Chris: Yeah, it's going to be fun. Cool. Where do you want to start?

James: So basically my idea was I wanted to scope objects based off of a datetime object, so for instance, like if we create a new blog, I want to be able to scope like blog posts that were posted during certain days for instance, like on business days, I want to also scope something that's within a payroll period, and I know this is kind of odd, I mean, we're using a blog for a payroll period, so it doens't really match up. So maybe we could generate a scaffold, just a simple rails app, and you know, have some sort of object. I don't know, like clock events or something like that, we'll call it "timekeeper", 'cause that was something I was working on recently.

Chris: Cool, let's do that.

James: Cool, so I'm running the app, we're going to run bundle, and we should be good to go. And for those of you out there who are new to this, creating this will spin up sqlite and you won't need to do anything else, it will pretty much just work.

Chris: Cool, what models do we need?

James: Ummm, let's just do a single model, so what do we call that model? Let's just call it "clock event".

Chris: You want to just do like "event", generic?

James: Yeah, yeah, yeah. Event. Simple is better

Chris: Ok, what do we need? Do we need-- or do we just want to use like the created_at timestamp or do we want to add another field for that.

James: I mean, we can scope up created_at, but if we ever wat to expand this, we might want to have like a clocked in datetime field or a clocked out datetime field, something like that, I think that might be cool.

Chris: Ok, let's do that. Alright

rails g model Event clocked_in:datetime clocked_out:datetime

rake db:migrate 

James: Awesome. So let's...

Chris: I guess it would have made for sense to generate a scaffold so that we had all the views for these.

James: Acutu... yeah, I mean, we can always wipe this if you want to do that and make it easier.

Chris: Let's do a scaffold, I wonder if there's a way to...

James: Override the model?

Chris: This will work fine, actually.

James: Ok

Chris: I think it will generate the exact same files.

James: Yeah, there's a possibility it might override or have duplicate migrations, but...

Chris: Yeah, that worked perfect

rails g scaffold Event clocked_in:datetime clocked_out:datetime 

rake db:migrate 

rails s

James: And there we go! Welcome abord. Hey! we're riding on rails, we've never seen this before.

Chris: I belive the event's page should be set up, yep. Perfect.

James: Cool, so we'll need to go in our views

Chris: I'm just going to set this so that it's...

James: Oh, yeah, good idea, because that welcome page is going to get annoying, yeah. So let's go in our views and we want to be able to like add and edit stuff. So let's check our views and just make sure like everything there is in the form.

All right, so we have class, field, datetime select, ok cool. The scaffold already does it so we don't really have to do anything. So, cool. Let's add some funny clock events.

Chris: Cool, we'll just do one like for a few hours and do another one. How many do you want to make?

James: Uhh just a few.

Chris: Four or five?

James: Right.

Chris: These are going to be long ones.

James: Yeah, no, but that's ok because we can scope for that too.

Chris: Cool, Ok.

James: Alright so we have some objects now here in our app, which is awesome, but let's say in our view, we want to iterate over each one and we want to scope them, so we're going to need to write some custom scopes here, and I came up with the idea based off some stuff I was writing before, kind of scoping off of like current month, current week, business days, we're just going to be a little bit tricky but I think together we can pull this off, and then scope a payroll, week. So a payroll week would be like a 13 day run for an avarage business, so it would start on like Monday and then finish up 13 days from then on a sunday, so those are the four scopes I would like to work on, so I think together we can knock this out pretty easy.

Chris: Cool, let's write those down. So a scope for

James: Current month, and then current week, business days, and then payroll week.

Chris: Yeah, so we'll just end up, these are just the regular rails scopes, we haven't defined like... They'll require a lambda here, so we can just paste those in, they won't do anything special right now, but on to you.

James: Ok, so for current month, we're going to want to actually look at, let's say the clock in time, I already forget what that field is, so we're clocked in.

Chris: Is within the current month

James: right, so we're basically going to have to do some sql here unless you have a better idea.

Chris: Yeah, and the reason why we have to write SQL and we can't use regular old ActiveRecord is because we're lookting at the month, but I guess also technically we're looking at the year too, because you have to specify that. So yeah, you have to make sure that you specify both the year and the month, which is important, because otherwise, if you just did the month, then you would get that same month throughout all of history, and then that wouldn't be the current month.

James: Right, and there's a couple different ways you can do this using the time class or the dateclass, I've written something like this before, but I think we're going to try to go for best practices and what makes sense.

Chris: Yeah

James: Ok, the next thing we'll need to do is we'll have to do, we're clocked in and we'll do a question mark

Chris: Like equals question mark? Or actually, I was thinking of the, like isn't there like the extract like year from clocked in or something? Have you ever done that before?

James: No, I've actually never done that, I've usually just done like the equals, question mark then passed some SQL to it.

Chris: Yeah, like one way you could do that, actually, you could skip all this, because basically if you say like Date.today.beginning_of_month..Date.today.end_of_month, this would create an array of dates over the whole entire month, and I would look to see where the clocked in was within that date, which is interesting. That's definitely one approach to it, but it like factors in the day of the month, which is kind of irrelevant. And you can kind of get rid of that, 'cause you shouldn't need it for the query, and also this is like pretty long.

James: Yeah, it is kind of long, but it actually works. I really didn't think about doing it that way,

Chris: You should be able to say Event.current_month and you'll see that this will just look for the clocked in date being between the first of July and the last of July. So that technically works, and it's actually pretty awesome for how simple it is.

James: Yeah, maybe we can go with this, I was actually going to do like old school SQL where you do... I'm flipping between your screen, I'm sorry, I already took some (inaudible). So, let's see.

Chris: Yeah, 'cause I think there's like, you know four, five different ways you can do this reasonably well, and there's a lot different approaches that you can do because they maybe even have another one in mind (inaudible)

James: Right, so what I was thinking is doing this here, where("clocked_in between ? AND ?, Time.zone.now.beginning_of month, Time.zone.now.end_of_month") And I think I'm having syntax issues.

Chris: This actually generates the exact same query as mine using times, and generally, times may be the better way to work instead of off of date. Date is like ok because it doens't really have to deal with timezones as much, but Time.zone is what you probably always want to use when you're working with timezones, and if you use this two period (..) syntax here, that will create a range, and it basically gets converted because ruby will know what the range is, that's like a built in ruby type, and this query in ActiveRecord converts to almost this exact same thing, so if we were to change this to a Time.zone.now and did the same thing here, this would generate the exact same query, so you can write your snippet of SQL in here that you're familiar with and do that, so it's actually really awesome because this is like the pure ruby version and this is a mixture of SQL, and I had another idea of doing something where it's like sort of both of these where we're in SQL you can extract the month from a date, so like this is a little different between MySQL and Postgres, I think you may have to like always make sure you keep track of that, but you can extract all of these things from the day, so you could say: EXTRACT(month from clocked_in) and then I think you can also do the year, so you could have both of these, and then you can have that equal to something, and then.

James: Ok, you know, I'll interrupt you, sorry to be rude, but just looking at this, like what I wrote. I mean what I wrote works, and what you wrote works, but I don't know, for some reason, what you wrote with the ruby range, it's more readable and it makes more sense and it just... I don't know, it just seems cleaner, and it does the exact same thing, so I think kind of like my scope should be like um... Kind of like the beginners example, kind of like how to do it quick and dirty, and I think the method that you wrote the first time is probably the refined or refactored version, so I say we go ahead and commit your version, 'cause it's cleaner, it's pure ruby, and we're using AR, so

Chris: Yeah, so this approach, you can have it basically like... You can use all of this stuff if you're actually good with SQL, you can do all of these things and make it even potentially even more efficient than this. These are actually like doing calculations to figure out what the beginning of the month is and all of that, and here you could basically do the same thing with the year, and then you would just pass in like Time.zone.now.month and Time.zone.now.year, and this would probably, depending on how SQL is built, like if these extracts are slow, which they probably are, unless you index on that, then like this wouldn't be the most ideal query, but it will probably be sort of like what we have to do for like business days for example where you like have to find out if the day is saturday or sunday. So it's like heading toward that direction.

James: Ok, so let's keep that up just so that when we move to the next scope, that's fine, so let's to current week, and current week is really, I wish we could make this more dry and use one scope but we can't, so basically we're going to take Chris's method from current month, and we're just going to do beginning of week and end of week.

Chris: Yep, and those are just those end_of_weeks and beginning_of_week methods, same with month, they come from ActiveSupport in rails, so they're like, I don't believe that they're in ruby itself, but they're just useful helpers for that. That makes these two really really easy.

James: Yeah, and I think you're right. I don't think it's like inherited from the distinct date or timeclass, I think those are ActiveSupport rails helpers.

Chris: Yeah, I'm almost sure they are

James: Yeah, we can probably Google it and one of us is probably going to be right. Winner winner chicken dinner

Chris: Yep, it's been around for a long time, it looks like.

James: Sweet, well, again, guys and girls out there, that's the magic of rails, you don't need to know too much ruby but it helps if you do know ruby but rails gives you a lot of magical helpers and methods that just makes your life easier.

Chris: Mmm-hmm

James: I think out of all these socpes, the hardest one for us to tackle is going to be the business days, so do you want to try to skip ahead to the payroll week and figure that out?

Chris: Sure, let's do it

James: Alright so we...

Chris: Well, so the payroll week is: You give it like a starting date, or is that going to be based off like, today?

James: It's going to... You know, that's actually a good question, technically, so like, today is... What is today> Today is wednesday, and let's just say that the beginning of my payroll period is this monday, so a payroll period lasts 13 day run, monday through the following, I guess saturday,

Chris: So, we could just do sort of like, I mean we could start this with like, let's assume that today is the beginning, and then we can move that around so that we can pull that variable out like the starting day, so that you could pass in that, so that like... Because it's not going to be every monday, it's the beginning of the payroll week, it's going to be like every other monday, so...

James: That's something that we would have to pass in via the controller or the view

Chris: Yeah.

Chris: We can actually start really simple and just like use Time.zone.now and consider today as like the beginning and then add 13 days to is, and then once we've got that working we can step backwards and like go into like changing that starting day so that it's customizable for which payroll we're looking at.

James: Ok, cool. So yeah, let' do that

Chris: So you've got your clocked_in, and really this is just going to be pretty similar to how things were before where we've got like, we can do like beginning_of_day if we want to, and these you could do, you probably also want that in there too, like this will take it to like midnight if you really are working with times, it's kind of good to keep that in mind.

James: Hey, you know what? I just had an epiphany.

Chris: What's that?

James: My syntax might be off, but let me play with this for a second,

Chris: Cool.

James: So Time.zone.now beginning of week, inherently in rails in the helper beginning_of_week it's going to start on what? A sunday or a monday?

Chris: Umm, it defaults to Monday but that's configurable, so...

James: Ok, but let's just say Monday is cool with me, and my payroll week starts on a Monday, so like we already have our Monday right here if you think about it, so we don't need to do beginning_of_day

Chris: Right, and actually I think these, like, internally do beginning_of_day because technically the beginning of the week is the first day of the week , and the very first second of that, and we can check that out here, so we could do like Time.zone.now, which is the current time, and if you do like beginning_of_day, that sets it to midnight, and then if you do beginning_of_week, it automatically does like the internal beginning_of_day for you, which is cool. So yeah, this actually does do that, you just have to make sure that in rails you've set like the beginning_of_week to monday, and that's like a really good use case for monday, beginning_of_week, even though we're from the US and like technically Sunday is like the most accepted thing, for work, it's not really..

James: Right, exactly, yeah. And that's kind of the interesting thing about business logic, you know, it doesn't always match the calendar, so I mean there's a lot of times where we have to go beyond, you know, normal rails and ruby conventions and make things work.

Chris: Yup

James: All right, cool. So payroll week, so we've got, this is our monday, and basically what we want to do is scope out 13 days from now,

Chris: So really, you could do, I'm actually going to pull this to a variable and make this like a longer block, because we can just say: start equals that, and then we can have start to...

James: I like it, it's also going to make us more easier to read,

Chris: Yeah, and here you can just do plus 13 days, and that would make an array that would... Or that would make a ruby range, that would range from the beginning_of_week to the end of the 13 days from then. Actually, you may also need to do that

James: There's also, yeah, there's also another way to do it, you could do start... Time.zone.now, end_of_week plus whatever the difference in days is, so you could do it that way too, but I like this because it's cleaner.

Chris: Yeah, you could do that where you could say like: Let's say the start equals this and then start plus 13 days or you could do, because one thing you're going to miss with plus 13 days is that it's going to be the beginning of Sunday, and you're not going to get the end of that day, so you won't have to do, end_of_day. But I like what you mentioned which is like you could do start.end_of_week + 7.days, and then you would get the same day, so you would go like the whole week, and then you would get the second week just by having 7 days.

James: Yeah, let's do that, let's do this start and yeah, exactly.

Chris: This is one of those cases where like this is complicated enough with the scope and um that you want to do something like that, and you can't use the keyword end because that's a ruby keyword, so you would write something like ending. That makes it, like, pretty clean. It's like three lines, but at the end of the day, like if you tried to do all of this in line, that would be gnarly. It would be hard to read.

James: Yeah, you would have to have dual monitors just to read it, so..

Chris: Yeah, we can already see these are long, and actually if you look at these two, because there's actually going to be like miliseconds or microseconds between each of these Time.zone.now calls, if this is run at a really specific time, like 11:59 and like at 59 seconds, if there's a possibility that this one could be the previous month and this could actually be the next month, and then you would accidentally get two months worth of stuff. So there's like a really rare case that that could happen, and it actually makes a lot of sense to have your start pulled out to something like that, and then you just have your start and end_of_month and you have your start in both cases, and that way you've saved this variable so that any case that it might be, like it's really rare for that to happen, and if you refresh the page, it would fix itself, but it would be like, there's potential for a bug there, which is really interesting that like you probably do want to pull this out and save it just to be safe.

James: Yeah, boys and girls out there, we in the dev world call this an edge case,

Chris: Yeah, exactly

James: So cool, so before we go to business days, why don't we go ahead and go to the console and test out our scopes and just see what we get.

Chris: Cool, I'm going to clear this so it's easier to read, we should get current month, we get one item for that, I believe that was right, like I don't think we had any for any other ones for July, so that's good, that is expected,

James: And then the current week, we should get one

Chris: Current week, we get that same one, and this is growing on the time as we expect it, and you'll notice here that it's the 29th through the 5th if you look at the calendar

James: it should match up.

Chris: It should be the 29th is the monday and the fifth is the sunday, so we're doing a business week like that, so that is correct.

James: Cool, and then...

Chris: Payroll week, we can do

James: Wait, hang on one second, we've got some syntax errors here, there we go. Sweet

Chris: That payroll week, and this one will query between the 29th and the 12th, so if we open up that calendar again, we can make sure that that is correct, the 29th though the 12, so it gets two full weeks, and you can also do here, it also does like 11:59:59, but also .999999 in order to include every microsecond possible for that day.

James: Likely, we have atomic time on our side

Chris: Yep, so yeah. That's good, I think all that looks correct.

James: So, alright, let's tackle the monster, because this is like, when I was thinking about doing this Gorails episode with you, like business I've been working on a ruby gem myself to define this and I kind of just got stuck with it, and it turned into a really big spaguetti mess, so I'd really like your input on this, I'm like, how you would scope business days, does it make sense to make it a scope, or does it make sense to actually build out like a complex method as a class method itself. So what would you suggest?

Chris: So, do you have, as a reminder to everybody, like the business week, or the business days are just monday through friday, so we're trying to ignore saturdays and sundays, and that's kind of the heart of why it's so complicated, because you have like these holistic ranges, like you want to include everything between these two dates in those cases, but in these cases if you want the month, so if you want all of the business days in a month, you could actually... You should be able to join these two queries ideally, where you could get clocked_in as anywhere between this, but then we ignore all of the ones between saturdays and sundays, and that's why it's so complicated, because you have to like explicitly ignore certain types, and my approach here, and I'm curious to hear what yours was, is basically to do the... So actually does business days have a range at all? Or do we want to make it sort of global, so you could chain current month with business days, so you could get only the business days for the month or the week?

James: I think it would make sense to make it global so we could chain, 'cause I mean, yeah we're gonna want to say like: You know, Event.current_month.business_days, you know that's going to be important to us,

Chris: And basically that locks you into pretty much like one solution, which is this extract that I was talking about before, I mean it really didn't make sense as much there, like it was fine that you could do that, and maybe it's slightly less ruby calculations to pull it off, but it really makes the most sense here because if you're talking about like every single event, let's pull up the events again. If you're looking at all of these dates and you want to pull out everyone that is within the business week, that's actually a hard sort of calculation to do.

James: Right, and we want it to try to do this as easy as possible, so we don't want to go too crazy with this, but at the same time, this is kind of a monster of a problem to tackle.

Chris: Umm, I believe the keyboard died. Oh, is it back? I can't tell. All right, batteries might be low, we'll power through this.

James: Nice

Chris: Umm, so yeah. In this case like you can't do any real logic to pull this off, like the only thing you can do is extract the day of the week, and that's why the MySQL extract from time is going to be so important, like that's really the only way to do this, and we can extract the, I wonder if we can pull out the day of the week, and that might be even harder. So it might not be like a thing we can do just in SQL. Do you know if there's a way to do that?

James: Umm, I'm honestly not sure, I'm doing a little googling while you're reasearching too, because I have another idea that's cooking but just give me a second here.

Chris: Mmm-hmm, extracting day of week as an integer. Maybe MySQL supports that, so let's try that. day of the week. Oh, here we go, I think we found something.

James: Hey, you know, I thought about something, what if, I mean this would probably get turned into like ruby soup, but what if we somehow specify, like in an array what those business were, like if we said explicitly monday through friday.

Chris: Well, the trouble is, that you don't have a range to work within. If you knew that you were doing this for the current month, you could actually make tihs range and then in ruby you could convert it to an array and then delete all of the items on Saturdays and Sundays, but because you're doing this for all of time, you're going to have to convert this with some sort of code from the database, you can't precalculate the monday through friday stuff, until it's, it has to be in the database, you can't precalculate it. You could, if you didn't do this you could make a separate scope here that was like current_month_business_days, and then you could take this and you could say like. So here's your range, and actually, you would like delete if, and you would take each day, and then you would say: Delete if the day.saturday or day.sunday, and this will go through that array and it would delete them out if it was a saturday or sunday, and then you'd have like, it's nasty. Like, it's going to be this weird, giant array, and if you were to do this inside of SQL, it's going to like, it's going to make this huge query that's like if clocked is this or it's this or it's this or it's this, and it's going to have an or clause for every single day that we pass in.

James: So basically you're telling me that the ruby gem that I'm writing, I probably really need to rethink that.

Chris: Really you can just do it with the raw sort of SQL syntax. Here is the day of week function, it's built into SQL, at least MySQL, it looks like the dow, it looks like an extract dow or something like that in Postgres, this is like a special method for it, so you can get day of week, and then you know what day of the week it is, so it's like 7 to seven is saturday in this case. This might also be different, say I believe that other page that I looked at with Postgres is zero was sunday, so it's going to be really important that if you write this, that you know that it's running on Postgres or whatever, so you'll have to have an if statement in here to handle both, but this basically just means that we can say: Where day of week for clocked in, equals whatever, and here we can just say that the days of the week that we want, so we can do 2 through 6, and that's all you have to do, so this should actually take care of it and we can test it out in the console.

Chris: So we reload and we can say Event.business_day and that crashed? Syntax error. Oh, I'm missing, let's see what am I missing? It's in in clause because we're passing in a range, so it's not... Hey, let's close the terminal and clear this out, so Event.business_days still doesn't work, I'm not super good at raw SQL syntax, but you can take the day of the week from that column and then you want to check for that value, so this would return like a 1, 2, 3, 4, 5, 6 or a 7, and you want to see if it's inside of this list. No such function this time day of week, which is very interesting, and that is because we're on sqlite.

James: Yeah, so maybe there's a method for sqlite

Chris: Yeah, so this... we're already dealing with this and sqlite is different from MySQL and yeah, they're all going to be different.

James: So, I guess, my advice, because I've only been doing this for a few years and you've mentored me throughout the years and you know, one thing I've learned from you is try to find a standard and stick to it, so guys and girls out there, if you're working in rails and ruby, try to stick to a database engine that you like, I mean you're going to hit projects that you're going to need to touch other things like NoSQL and Mongo, but, you know, try to get yourself familiar with at least one SQL engine or relational database, and get comfortable with it, so you know the constraints and all the features and methods of that database. For me, I'm a Postgres guy, MySQL.

Chris: You should use Postgres, for sure

James: Yeah, yeah. Postgres rocks, Postgres is fast, so

Chris: Alright, I'm going to say: Let's not use sqlite for this because these don't look too promising.

James: Okay

Chris: sqlite really doens't have much functionality compared to everything else, it's good for very simple things, but you're better off doing something with MySQL or Postgres, Postgres preferably, so we can open up our database yaml, and I always forget what this syntax is. We don't need test or production, and then we can do timekeeper dev, adapter will be MySQL? Or is it MySQL2? I can never remember.

James: I think it's MySQL2.

Chris: Ok, the gem is MySQL2, we get rid of sqlite, just add that at the top, and bundle, and I can't remember, probably need a user name here for the database but I don't know that I have the password, um. So let's try rake db:create and see if we can create the MySQL database.

James: Chris

Chris: It did not create it, access denied for user, yeah, you just don't have the correct options here set up, user, pass, hostname dbname db and sock will be deprecated at some point.

Chris: I don't even know if I have MySQL running on here

James: Yeah, you might not, do you have Postgres running on your machine?

Chris: Yeah, I think so, this isn't mine, it's our office's.

James: Oh, ok.

Chris: Uuuh, connection succeeded? What? There wasn't even a user name.

Chris: Ok, we have configured MySQL now for timekeeper, we ditched sqlite because it doesn't really support much of this, like date manipulation has far less features than MySQL or Postgres, so you can run into somethings like that occasionally, and now, we should be able to run the code that we just wrote to pull out the day of the week for the clocked in column, and then check to see if it's within this range of two through six, so let's see.

Chris: And it did! So that's really cool, although we don't have any results, so we should make sure that we put one in. Um, July first should be one, right?

James: Yeah, because today, today is our wednesday, so it should be in there

Chris: We had July 1rst, so that's interesting.

James: I think we're on the right track, though.

Chris: Yeah, so if we are to, I think we can just test this and like SQL pro in the terminal, so we can connect to our database with this tool and it just helps us to like write queries in here real quick and see the output. So if we set the date here to the first of July and we run that, we should get four back, and we did, but our query didn't actually give us the correct output so this first record for sure in our database for clocked_in is starting on July 1rst, that definitely should work. I'm curious because we have like, in this case we don't have these just strings, we have like a clocked in column, so maybe there's some difference there, so we're not quite sure. This doesn't look like I connected to the right database.

James: Nope, it looks like you hit MySQL's

Chris: And I didn't put any user name in for the database yaml and it connected.

James: That's pretty weird

Chris: Yeah. rake db:console that should let us jump into the... Oh, did they take that away? I think rake db:console used to like drop you into like the database.

James: Yeah, it did, it could be deprecated but that's a shame if it is.

Chris: Yeah, that was a really useful feature. They may have changed it, but...

James: Yeah, I'm honestly not sure what it's called, I usually, if I have to get in the console, I usually just hit psql or mysql and go in raw.

Chris: Yeah, I don't quite know what my step is, so this is weird.

Chris: Well, maybe we'll try postgres, I know I have an app running on postres, so let's try that. Oh databases.

James: While Chris is doing this, this is one thing I want to touch on, you know all of you guys out there that are learning full stack development, you know remember there's a lot to it besides just writing code, so I mean that the more you can get in touch with infrastructure, and when I mean infrastructure I'm talking about the server side, like NginX and Apache and Puma and Passenger and the database side and Postgres or MySQL, like the more you can actually learn about this and understand it, it's going to help you write better code, because it's going to be like a cohesive knowledge transfer between the two modalities.

Chris: Mmm-hmm, it's a really important thing to do.

James: And also when you look at the market place now, what I'm finding like, you know, in my job search is, you know, they want rails people, but they also want you to know infrastructure and DevOps, I think probably in the next five years, the term "Full Stack developer" will be synonimous with like, DevOps and Ops engineer, so you'll basically have to know both to make it.

Chris: I don't even know how postgres is set up on here. Shooot. Postgres isn't even running... Whaat?

James: Yeah, um on OS X, sometimes you gotta do a launch control

Chris: Yeah, but whoever the hell set up this machine, because we have like 15 accounts on here, and homebrew doesn't normally...

Chris: There we go, so now we've got postgres set up, we went with MySQL originally and my machine wasn't set up for it. I have postgres set up, umm, and we wrote this code actually for MySQL, and we used the day of week method that should pull out the day of week as an integer between 1 and 7, but postgres actually has a different format here, so we're going to have to look that up. So dayofweek postgres is what we're going to search for, and this should help us figure out how to extract the day of the week from there. So day of week, dow. This actually uses the regular extract syntax so that you can extract dow from some some column in the database, and it will give you a 0 through 6, so we need to adjust our code to be extract dow from clocked in, and then here we want this to be 1 through 5 because the actual range is 0 through 6 in postgres. Now if we run rails console, and do Event.business_days and cross our fingers, this should work, and it didn't complain, but we definitely didn't still get the correct output, because we should

James: Yeah, we should have hold one record

Chris: Right, but, I have. I'm going to close sql pro here, and I have an app here to connect to the database and we can go to localhost, actually I want to try this one, I'm going to go out to this other database timekeeper dev, and somewhere in here, I believe there's a way to view or like execute commands

James: Hey Chris, what app is this? Is it Postico??

Chris: Yep, the guy that made pg commander was trying to copy sql pro and did a pretty good job, and then he abandoned pg commander and rewrote it as postico. Not quite sure what his goal was with that.

Chris: Oh! This is why we have no results, because we changed databases and we have no records in the database, so we need to restart our rails server and make some records because we always get no results. That's debugging for you.

James: Yeah, we created our own bug so that. That's funny

Chris: Yeah, I think that happens pretty often, you'll be like really focused on some problem and then you'll have one of those moments where you're like: Oh, shoot, it's so obvious but I should have known.

James: Yeah, and the problem with that is at least, I don't know about you, but for me, like sometimes the time between the problem arriving and me having the lightbulb turn on can be like an hour or two hours, so it can get frustrating.

Chris: Yeah, it's definitely frustrating, and that's kind of one of those moments where it's like: Just kind of like diving at the problem from a different angle can make such a difference, like, if I didn't open this database browser and we look on the events table, I porbably would have been debugging this for the next 15 minutes, just trying to figure out what's going on. So now, we can see those, and when we run the query, it generates the correct query and the correct results, we can actually put in these dates in, in the week end, and that would be like let's do july 4 and...

James: Hey Chris, you know what I was going to... before we did that, can we go back to the console and see if we can chain it? So do like current month.business_days. Will that work?

Chris: Right, we can definitely do that, we can do Event.current_month, which should still work, and it does, business days, and that just adds the end clause in here and then tax on that. Actually, this is fantastic because here you have: This is every event in the current month, and then within those, let's pull out the ones that are only on business days. That's how you build up these queries to be really efficient and flexible like that.

James: Yeah, because if we did it my way, the query would have been SELECT FROM SELECT FROM, bam, bam, bam, and, and, and. It would have been a mess, so...

Chris: Mmm-hmm, and it's easy, to hit that to. Where you're like, working through the problem, and really you need to pull out some very generic kind of like abstract kind of thing, but it's like not easy to do all the time. I just added one in for July 4th, and sometimes it's easy to do the counts here, so you can see how many results you get. So, like before you had just two events for the business days, and so we've got, let's see. Do we have two?

Chris: So we already had one on the week end, so we have one on a business day, and we had event two on a business day, er event three on a business day. Event two was not a business day, and it didn't show up this first query, so now it didn't show up. So now we have two events on business days, and two events not on business days, which is pretty cool. So august first is on a saturday, so that's why that didn't show at first, which is actually pretty sweet.

Chris: Yeah, now you have like... and if we get rid of these old comments, because we've now used the extract as we were initally going to talk about, we actually have this wonderfully, like clean set of things, and I'm going to move start and keep these like consisntent between them. And they're a little longer, but that's not a bad thing, because you are actually doing a lot, like the query for what is in the current month is quite a big calculation, llike if you were to do that manually without these beginning of month helpers, it would be a lot of work, so this is like pretty cool that we got it down to this, obviously you can probably even shorten it, you can just compress them, because if you're like: Ok, well I know how it works, we might as well smash them into one line each.

That's ok, I don't mind having long lines of code once you've like written it out like cleanly, and then you're like: Well we probably don't have to change these again, so we should just like leave them, and it's ok to like smash them together at that point, if they're like pretty solid.

James: Right, but I think like, I mean, I don't know about you, but I like readability, I like my code to be legible, so like we're doing it this way, and it's more readable, and plus we've also dried it up a little bit, which is nice.

Chris: Mmm-hmm, and I feel like readability is the thing that like, it's hard to define, because this is good right now, if that's the only thing in our class, but if you like have a bunch of methods in here we can just paste it in here the duplicates or whatever, you're going to be like: Dang, this is a lot of stuff now, and you''re not going to want to be reading all of these, so like when it's just these four. I feel like this is wonderful to look at, but if you like double or triple the methods in here or something, then I'd start to be like: Uhh, I really don't need these to take up so much room, and that would be one of those moments where I smash them together and just keep them in this chunk for scopes. Because usually, you write the scope the first time, and assuming that there's no bugs that you run into, they're pretty straightforward like you probably never need to touch them again, and then, like I really don't need to make them like super readable at that point, but yeah. Turned out really well.

James: Yeah, you know, I had a question, and I already know the answer to this, but I'm going to ask you and you can explain to the audience. In scopes, I mean, you can write a regular scope without a lambda, let's talk a little bit about lambdas and why they're important.

Chris: Actually, I believe in rails 4 you can't write scopes without lambdas, or something? Basically the gotcha is that if you were to run Time.zone.now outside of a lambda, it would run when rails loads, and so this start variable, we get set to the time when you restarted your rails app, and that would be permanently locked in to that month forever. And the reason why lambdas are important, or blocks in general is that this code will run only when you call current month, so the lambda is super crucial for basically that one purpose, and it was such a big gotcha that they decided to make it a requirement to write scopes so that by default you always to the right thing,

James: Yeah, I actually maintain a couple of rails 3.2 apps, and I ran into that problem where the object is actually locked at the start of the server, so I did quickly convert to lambdas to pretty much all my scopes, and now my rails 4 stuff, I think you're right, in rails 4 it was almost like a deprecated option.

Chris: Yeah, I think so

James: Lambdas rock, but a lot of people don't understand lambdas, you know what they do, but I think that's a decent explanation that makes sense to me

Chris: Yeah, it's basically just like this chunk of code that doesn't run when the rails app loads. Like when this rails app starts up, it loads up this class, and like the scope method runs, like this actually runs as soon as the app requires this class, and you don't actually see any output, nothing like really happens when that runs, but like it gives this event class the current method, or the current month method, and it gives it the business days method. These scopes define methods when the rails app run, so that you can call these things, and then this is just saved for later so that it can be executed later, and that way you don't make any mistakes with your times like that.

James: So I guess in other terms, having a lambda prevents us from setting state.

Chris: Yeah, it's just like being lazy about when the code gets run, so yeah. It saves the calculation to happen when it's needed, which is another thing that Active Record does a lot, where it's like, until you actually run the query or do something with it, it doesn't actually do something with like, it won't do all the work until you actually need it, which saves a lot of time and performance in rails

James: Awesome, well, I think we've kind of come to the end of our road for this session, so let's recap what we did, we started off with some specs for current month, current week, payroll week and business days, and we initially wrote some scopes that were not as pretty as they could be, and then with Chris's help we refactored what was in my head and got something clean and it's rocking.

Chris: Yeah, and it actually turned out super duper nice, these are something that like, these like scopes can probably be used in tons and tons of different applications, and this almost makes perfect sense to pull out into a concern, but actually even makes more sense, like you were saying with your gem to like make it global for everybody to use, these scopes are really helpful, it's like perfect to pull out into your gem, which is awesome.

James: Awesome, I think that's about it for today, my brain is a little bit tired, I dunno about yours, but

Chris: Yep, me too.

James: Yep, so cool, well it was cool doing this session with you, and like hopefully we can do another one, we'll have to have you leave a comment or like this video, so if you liked this one, let us know so we can plan to do more of these in the future.

James: Yeah, and Chris and I have been working together off and on for year, so we pair pretty well, so I have some ideas, he has some ideas, and we just want to get feedback from you, the audience as to weather or not there's value to this, so if you guys liked it, you know, put a bunch of thumbs up,

Chris: Definitely

James: I'm signing off, I'm James from shakycode.com,

Chris: Cool, and I'm Chris from Gorails, and that's it

James: All right guys, peace

Chris: Peace

Transcript written by Miguel

Discussion


Fallback

For your business days scope, could you not use Time.now.wday to keep it all ruby and DB agnostic, rather than using EXTRACT? (http://ruby-doc.org/core-2....

Fallback

The EXTRACT is required because you need something to pull out the week day inside the database so you can make the comparison. The Ruby method unfortunately can't help you here because this is running a query inside the database.


Fallback

Are you going to do more pair programming videos in the future? This and the one where you're fixing a bug in Devise are my favourites.

Fallback

Definitely planning to! I think it helps explain things a lot better with the discussion.


Fallback

This sort of tutorial is really so helpful. It would have been more nice if we could watch how to change default week days and starting week day in rails.


Login or create an account to join the conversation.