301: Ants in the Cookie Store

Episode 301 · July 20th, 2021 · 42 mins 17 secs

About this Episode

What do you get when you mix a worm and a hammerhead shark? Also ants. Steph made some cool new discoveries in bug-land. She also talks about deploys versus releases and how her and her team has changed their deploy structure. Two words: feature flags.

Chris talks about cookies: cookie sessions, cookie payloads, cookie footprints, cookie storing. Mmm cookies! The convo wraps up with lamenting over truthiness in code. Truthy or falsy? What's your call?


STEPH: At the top of my notes for today, I have marauder ants and hammerhead worms. [laughs]

CHRIS: I'm sorry, what? I lost you there for...not lost you, but I stopped following. I...what? Hello and welcome to another episode of The Bike Shed, a weekly podcast from your friends at thoughtbot about developing great software. I'm Chris Toomey.

STEPH: And I'm Steph Viccari.

CHRIS: And together, we're here to share a bit of what we've learned along the way. So, Steph, how's your week going?

STEPH: Hey, Chris, it's been a good week. It's been busy, lots has been happening. I learned about a new creature that's in our backyard. They're called hammerhead worms. Have you ever heard of those?

CHRIS: I've heard of hammerhead and worms, but not together. The combination is new and novel for me.

STEPH: Cool. Cool. So take a hammerhead shark and a worm and combine the two and then you have a hammerhead worm. And it rained really heavily here recently because there's a tropical storm that's making its way up the East Coast. And when I was outside on the porch, I noticed that there were these new worms or worms that I'd never seen before on the back porch. And so I had to Google them to understand because they had the interesting hammer-shaped head. And I found out that they're called hammerhead worms. They're toxic worms that prey on earthworms. And they're basically immortal because if you cut them into multiple pieces, each section can regenerate into a fully developed organism within a few weeks, which is bananas. And a lot of people online highly recommend that you should kill them because they are a toxic predator and they prey on earthworms, which you want in your garden and in your yard. But I didn't, but I learned about them.

CHRIS: Wow. That's got some layers there, toxic, intense worms that you can cut in half. And so does their central nervous system just spread throughout their whole body? Where's their brain? How does it...I don't have any real thoughts here. That's just a bunch of stuff, and it's awesome. Thank you for sharing.

STEPH: I will warn you. I wouldn't read about hammerhead worms right before bed. Otherwise, you might have some nightmares because the way that they do prey and consume earthworms or other creatures that they prey on is the stuff of horror movies, which I find happens so much in nature, but them especially they fall into that category. So just be aware if you're reading about hammerhead worms and how they consume their food. Now I feel like everybody's going to go read. But as long as you have that warning, I feel safe sending you in that direction.

CHRIS: Yeah, first thing in the morning on a very sunny morning, that is the time to do this research.

STEPH: Exactly. He got it. I also learned about marauder ants because apparently, this is the day that I'm having. I'm learning about all these creatures. But I won't go into that one, but they're really interesting. And this one's thanks to someone on Twitter who shared, specifically @Rainmaker1973 is their Twitter handle if you want to go see what they shared about marauder ants. So I'll just leave that one for those that are curious. I won't dive into that one because I don't want to take us in the direction of that we're all about worms and ants now.

CHRIS: Not all about worms and ants but definitely some.

STEPH: But in technical news, I've got some stuff to share, but I was so excited about worms and ants that now I have to figure out which is the thing that I want to share from the week. So there's a couple of interesting things that I'd love to chat about with you, one of them, in particular, is there's been some interesting conversations going on with my client team around deploys versus releases and how we have changed our deploy structure, and then how that has impacted the rest of the team as they are communicating to customers as to what features are available. And there have been some interesting conversations around how to migrate this process forward.

So to provide a bit of context, we were previously having very strict, rigid deploys. So we would plan our deploys typically every Tuesday. It was usually once a week. And then we would make sure that everything had been through QA, things had been reviewed and tested. And then we would have one of those more like grand deploys, things are going out. And then hey, if you need to get something into the deploy, let us know; we need to talk about it. So there was just more process and structure to that. And so deploy really mapped to the idea that if we are doing a deploy, then that means all these feature bug fixes are going out, and this is now the time that we can tell customers, "Hey, this new feature is available or this bug that you reported to us has now been fixed." We have since been moving towards a more continuous deployment structure where we're not quite there where we're doing continuous deploy, but we are deploying at least once a day, so it's a lot more frequent.

And so this has changed the way that we really map the idea of the work that's being done versus the work that's actually available to customers. Because as we are merging work into the main branch, and then let's say if I'm working on a feature and then I merge that into the main branch and then push it up staging, we have an overnight QA process. So then overnight QA, if they say, "Hey, there's something that's wrong with this feature. It didn't quite meet the required specs," then they can kick that ticket back to me, but that's not true for my code. We could do a revert and take my code out at that point. But at this point, it's in main, and main may have been deployed at that point. So there have been some interesting strategies around how can we safely continue to deploy while we know we often have a 24-hour wait period for QA and to get sign-off on this work? But we want to keep moving forward and then also communicate that just because the code has been deployed doesn't necessarily mean that it's available to customers. There's a lot there. So I'm going to pause and see if you have questions.

CHRIS: Well, first, I'm just super excited to talk about this. This is something that's been very much top of mind for me, and it's a direction that I want to be going more and more, so yeah, excited that you're pushing the boundaries on this. I am intrigued. I'm guessing feature flags is the answer about how you're decoupling that and how you're making it so that you've got that separation of deployment and actual availability of the feature. So, yeah, can you talk more about that?

STEPH: Definitely. And yes, you're right. We're using feature flags, so we'll use the same scenario. I'm working on a feature, and I want to be able to release it safely, so I'm going to wrap it in a feature flag. And I'll probably wrap it, and maybe it's like a beta feature flag, something to indicate that this is a feature that's going to be available to all, but we don't actually want to turn it on until we know that it's truly ready to be turned on. So then that way, it's hidden, but then we can still merge it into the main branch. We can still have a deploy even if my code hasn't gone through QA at that point, but we know it's still safe to deploy. And then, QA can go to a staging environment; they can test it. And if they say, "No," it's fine because nothing was churned in production. But then, if it gets approved, then we can turn it on, and then we'll have a follow-up to then remove that feature flag.

CHRIS: So some follow-on questions. I'm wondering about the architecture of the application. Is this like traditional Rails app rendering HTML on the server, or do you have any more advanced client-side stuff? And then I'm also wondering what you're using for the actual feature flagging, and those will probably inform each other. But what's the story on both of those fronts?

STEPH: It's a traditional Rails application. So we're not using any other client-side application. It is Rails and rendering HTML. As for feature flags, so we're not using something traditional. And by traditional, I mean I typically have reached for Flipper in the past for managing feature flags. We're using more of a hand-rolled approach because there's a lot of context there that I don't know is necessarily helpful. But to answer your question, we essentially do have feature flags as columns in the database, and we can just check if they are enabled or disabled. And then that also allows us to easily turn it on, turn it off as well since it's just a database update.

CHRIS: Okay, that makes sense. I think the nature of being a Rails application rendering HTML on the server like what you're doing totally makes sense in that context. I think it becomes a lot harder the more complex the architecture of your application is. So if you've got microservices, then suddenly you've probably got to synchronize across some of them, and that sounds like a whole thing. Or even if you have a client-side application, then suddenly you've got to serialize the feature flag stuff across the boundary or somehow expose that, which really does push the issue of we could just render stuff on the server and send it to the client and let that be good enough, then man, is stuff simpler. But unfortunately, that's not the case in a lot of situations.

I'm expecting to be introducing feature flags on the app that I'm working on pretty soon. And again, we've got...so it's a Rails server-side thing. So there's going to be plenty of feature flag logic on that side. And then I'll need to do something to serialize it across the boundary and get it onto the client-side without ballooning every payload and adding complexity, and lookups, and whatnot. I think it's doable. Inertia, again, being the core architecture of the application, I think will make this a little bit easier, but I am interested to see what I'm able to pull off and how happy I am with where I get to.

Another question that I have for you then are you testing the various flows? So given a Boolean feature flag, you now have two different possible paths for your code to go through. And then there may be even more than Boolean, or you may have feature flags that sort of interact with each other. And how much complexity are you trying to manage and represent in the test suite?

STEPH: Yeah, good question, and we are. So we're testing both flows, especially if it's a new feature, then we are testing when the flag is enabled or disabled. One that's been tricky for me is what about a bug fix? Is that something that should be feature flagged? And I think at the surface level, if you're presuming that it needs to go through QA before this is live on production, then the answer is yes, that then you have to feature flag a bug fix, which feels weird. But then the other consideration would be, well, it is a bug fix. And could we find another way to QA this faster or some other approach so that way we don't have to wrap it in a feature flag? And I don't have a great answer for that one because I can see arguments in favor of either approach. Although wrapping everything in a feature flag does feel tedious, it's something that I'm not accustomed to doing. And it's something that then becomes a process for the team to remind each other that, hey, is this wrapped in a feature flag? Or just being mindful of that as part of our process. And it prompted me to think back on the other projects that I've worked on and how did we manage that flow? How did we go from development to staging to QA and then out to production?

And one additional consideration with this flow is that we do have an overnight QA team. So in the past, when I've worked with teams, often product managers or even other developers, we would QA each other's work. So then it was a pretty fast turnaround that then you could get something up on staging. Someone could check it out and say, "Yes" or "No." But then I'm also pretty confident most of the teams that I've worked with we have had a distinct staging branch. So we would often merge work into a staging branch, and then deploy that work, and then get it tested. And then, if it passed everything, then we would essentially cherry-pick that work and move it over into production.

And I can see there's a lot of arguments against that, but then I have also experienced that and had a really positive experience where we could test everything and not have to worry about going out to production. We didn't have to wrap everything in feature flags, and it just felt really nice to know that everything in the main or production branch, whatever you call your production branch, that everything in there was deployable versus having to go the feature flag route, or the hey, did this go through QA? I don't know. Let me check. Can I include this? Should I cherry-pick some commits into our actual deployment to avoid stuff that hasn't gone through QA? I've been through that dance before too, and that one's not great.

CHRIS: I like the way you're framing the different sort of trade-offs that we have there in velocity or deployment speed and ease of iteration versus confidence as things are going out. I have worked with a staging branch before, and I personally did not find it to be valuable. It ended up adding this indirection. Folks had to know how to use Git in a pretty deep way to be comfortable with that just as a starting point. So it already introduced this hurdle of knowledge, and then beyond that, that idea that you have commits going in in a certain order on the staging branch. But then say we verify the functionality of the third commit in that list, and we want to cherry-pick it across to the main branch. Commits don't actually...you can't just take the thing that you had there. That commit existed in the context of all the others. There are subtleties of how history exists in Git. And I would worry about those edge cases where you're taking a piece of work out of the context of the rest of the commits that were around it or before it is, more importantly…that preceded it in the history on the staging branch, and you're now bringing it across to the main branch. Have you now lost something that was meaningful?

Ideally, you would get a conflict if it was really bad, but that's more of like a syntactic diff level thing. It's not a functionality-level thing. So personally, I may be overly cautious around this, but I really like as much as possible to have the very boring linear history in Git and do everything I can such that work happens on feature branches and then gets merged in as a fast forward into the main branch or rather the main branch is fast-forward marched into my feature branch such that I'm never working with code that I haven't fully worked with in an integrated way before. But again, even that, as I'm saying that, I have this topological map of Git in my head as I'm saying all of that, and it's complicated. And having any of that complexity leak out into the way we talk about the work is something that I worry about, but maybe I'm worried about a bunch of things that don't matter. Maybe a staging branch is actually fantastic.

STEPH: I think you make a lot of good points. Those are a lot of good concerns that come up with...it comes back to the idea that we want to mimic production as much as possible, and we don't want to lose that parity. So then, by having a staging branch, then it feels that we've lost that parity. There could be stuff that's in staging that's not in production. And so staging could be a little bit of this Wild West area, and then that doesn't fully represent then what's going to production. So I certainly understand and agree with those points that you're making. And to speak specifically to the Git challenges, I agree. It does require some more Git knowledge to be able to make that work. Specifically, I think how we handled it on a previous project is where we'd actually cherry-pick our commits into staging and then deploy that. But we always had the PR issued against main. So then merging into main was often a bit easier.

But then you're right; things could get out of sync. And the PR is issued against main, so then you still could run into those oddities where then if you are cherry-picking commits in the staging, but then you have your final draft that's going into main. And then what are the differences between those, and what did you lose along the way? And as I say all of that out loud, I definitely understand the Git concerns. And I don't know; I just feel like there's not a great answer then here, which is shocking to me. I've been doing this for a while, and yet here I am feeling like there's not a great answer to this very vital part of our workflow. And I'm surprised even though that we do have a delayed QA process that this still feels like a painful thing to figure out how do we have a continuous deployment workflow even though we do have that delayed QA process?

CHRIS: I think somewhat fundamentally your comment there of "I'm surprised that we don't have a good answer to this is," I'm not surprised, I guess, is my reaction. I don't want to go to the software is bad and broken, and we don't know anything end of the spectrum. But I don't feel like we have great answers to a lot of the things about development. I feel like software is more broken than it should be. It costs more to develop. It is difficult. It's hard to create, and maintain, and build over time. And that's just, to get lofty about it, that's what the entire focus of my career is, is trying to solve that problem. But it's a big, hard problem that I do not think is solved, unlike just about any of the fronts. I know how to put stuff in a database and take it back out. And even that, I'm like, oh yeah, but what if the database gets really big? Or what if the database...everything has complexities and edge cases.

STEPH: [laughs]

CHRIS: And we've joked a handful of times about the catchphrase of The Bike Shed being it depends, and that really feels true, though. I don't know that that's unique to this industry either. I feel like everything in the world is just more complicated the more you look at it, and there aren't clear, good, obvious answers to just about anything in the world, but that's the human condition. I got weirdly philosophical on this, so we should probably round this out. [laughs]

STEPH: Well, I can circle us back because I was providing context, and I went a bit into the deep end providing all of that context. So if I circle back to what I wanted to share with you around deploys and releases, there has been that interesting conversation. Now that we have the context, there has been that interesting conversation around originally; we had this very structured deploys, a deploy map to the fact that features were going out to the world. And now we have this concept of a deploy doesn't necessarily mean that's available to customers. It doesn't mean that the code is running. It is more a deploy represents that we have placed a commit. We have placed code on the server. But that doesn't mean that it is accessible to anyone because it's probably hidden behind a feature flag.

But from the perspective of the rest of the team that then is communicating these changes out to customers, they still really need to know, okay, when is something actually available to customers? And we kept using this terminology around deploy. And so Joël Quenneville, another thoughtboter who's on this project with me, has done a lot of great, thoughtful work around how can we help them know when something is truly available versus when something is deployed? Because right now, we're using Jira for our ticket issue tracking. And there's a particular screen in Jira that was showing what's being deployed. And from that screen, you can see the status of the ticket, and you would see stuff like in code review, in QA. So, of course, those looking at the tickets are like, hold up, you're deploying something that's in QA? That sounds really dangerous and risky. Why are you doing that? And then we'd have to explain, well, we're deploying it, but it's not actually live or accessible to anybody, but we want to get close to that continuous deploy cycle.

So we have shifted to using the terminology of a release. So a deploy is more for the we're putting the code on the server and then release really represents okay, we have now released these features and these bug fixes, and they're now available all with the goal just to make sure that our teams are working well together. But it's been such an interesting conversation around how tickets move, the fact that they can progress linear and then also get moved backwards. But in continuous deployment, things don't go backwards and then making those things align. Typically, things don't go backwards. Technically, yes.

CHRIS: History is a directed acyclic graph that only points forward. The arrow of time is very clear on this matter. Yeah, that really does add one more layer of like; what does it mean to actually be out there in the world? I do wonder if giving view-only visibility to the feature flag dashboard and only when it's fully green does someone think that that's deployed? But if you're putting feature flags around everything, there's complexity. And yeah, it's just one more layer to having to manage all of this. And it sounds like you've gotten to a good place, or at least you're evolving in a way that's enjoyable. But yeah, it's complicated.

STEPH: Yeah, it definitely feels like we're moving in the right direction and that this will be a better...I want to say workflow, but it really focuses more around vocabulary and some of the changes to our processes and how we surface tickets in Jira. But it's more focused on how we talk about the changes that are getting shipped and when they're available. So, yeah, that's my story. What's new in your world?

CHRIS: Well, I very much appreciate your story. In my world, I am in the thick of the MBP initial drive to get something into production, which is one of my favorite times, especially if everyone's in agreement about what exactly do we mean by MVP? Who are the users going to be? What's it going to look like? What's the bar that we're going to maintain? What features can we drop? What can't we drop? When there's a good collaborative sort of everyone rowing in the same direction set of conversations around that, I just love the energy of that time. So I'm happily in that space hacking away on features building as much as I can as quickly as I can. But as part of that, there are a lot of just initial decisions and things that I have to wire up and stuff that I have to change or configure. Thankfully, Rails makes a lot of that not the case. I can just go with what's there and be happy about that.

But there is one thing that I did decide to change just today. But it's interesting; I don't think I've actually ever made this change before. I'm sure I've worked on an app that had this configuration, but typically, a Rails app will store the session in a cookie. So there is a signed HTTP only encrypted. I think those are all the things, but it uses a cookie to store that. And the actual data of the session lives in the payload of that cookie. And so, each time there's a request-response lifecycle, the full payload of that cookie is going up and down from the server to the client and then back and forth with all of the requests. And there's a limit; I think it's 4k is the limit on the cookie session.

But there are some limitations to cookie sessions as far as I'm coming to understand them; one is the ability to do replay attacks. So if someone gets a hold of that cookie, then unless you rotate the secret key base, which will have some pretty wide-ranging effects on your application, that cookie can be reused in the future because it basically just has like, this is the user's ID. There you go. And there's no way to revoke that other than rotating the secret key base. Additionally, there are just costs of that payload of data, especially if you're putting a non-trivial amount of stuff. Like, if you're getting close to that 4K limit, then you have 4K of overhead, both on the request and the response of your HTTP requests. So especially in apps that are somewhat chatty and making a bunch of Ajax requests or doing different things, that's some weight that you should consider.

So all of those mixed together, more so on the security side, I decided to look into it. And I have now switched from a cookie store, and I went all the way to the ActiveRecord database store. So I skipped over...there's a middle option that you can do with Memcached or Redis. We do have Redis in this particular application. We don't have Memcached yet; we probably will at some point. But you can do a memory store, so do Redis and store the session there, but I opted to go all the way to the database. And my understanding of the benefits here are we have a smaller cookie footprint, so smaller overhead on all the requests because now we're only sending the session ID. And then that references the actual payload of data that's stored in the database. We do have the ability now to invalidate sessions, so we can just truncate that table if we just want to sign all the users out and reset the world, which can be useful at times.

We also have the ability...if there's any particular user that's like, "I left myself logged in somewhere," we can…well, I actually don't know how to do this now that I say that. I don't know how to log out a specific user because the sessions don't inherently have the user associated with them. You can have an unauthenticated session, which then transitions to be authenticated when someone signs in, and then the user ID gets installed in there. I would love to have these indexed to users such that I could invalidate and have a button on the admin dashboard that says, "Sign out all instances," and that will revoke all of the sessions or actually delete them from the database table now. I think I would have to add some extra instrumentation to do that. So anytime a user signs in via device, we annotate the session records so that it's got a user ID column and then index on that so that we can look them up efficiently. I think that's how that would work, but that's one of those things that I'm like; I think I should think very hard about this before I do it. It has security implications. It's not part of the default package. There's probably a reason for that. I'm going to do that another day.

But yeah, overall, it was a pretty easy upgrade. I think I'm happy with it. It feels like one of those things that it's not clear to me why this isn't the default sort of thing where SQLite is often the database that you use just because it's slightly easier to get up and running? But for any application that we're working on, we're like, no, no, no, we're going to go to Postgres for local development and for everything because obviously, that's what we want to do. And I'm wondering if this should be in that space, like yeah, of course, the session should go in the database. There are so many reasons that it's better that way. I'm wondering if there are some edge cases that I'm not thinking about, but overall it seems cool. Have you ever worked with an alternative to the cookie store?

STEPH: I'm thinking back to the recent projects that I've worked on. And it's been a while since I've mucked around with session work specifically. And the more recent projects that I've been on, we've used JWTs, or they're pronounced jots, I found out, which is really surprising. I don't know why, but that's a thing.

CHRIS: What?

STEPH: [laughs]

CHRIS: This doesn't feel true.

STEPH: It's JWT, but it's pronounced jot, J-O-T.

CHRIS: I think I'm just going to not do that. This is a trend I'm not going to get on board with. [chuckles]

STEPH: I don't even know if it's a trend. I'm not sure who decreed this into the world.

CHRIS: You're familiar with the great internet war around GIF versus JIF, right? I think there's room for different opinions.

STEPH: I mean, it's really not a war. There's a correct side.

CHRIS: We're on the same side, right?

STEPH: [laughs] And this is how The Bike Shed ended. No, this is perfect for The Bike Shed. What am I talking about?

CHRIS: This is perfect for The Bike Shed. I'm just going to need to hear you say the word real quick. [chuckles]

STEPH: Oh, it's GIF, absolutely,

CHRIS: Okay. All right, phew. Steph, I was worried, I was worried. Also, anyone out there that says JIF, it's fine. These things don't really matter. Although I am surprised when you have an acronym that gets turned into...I think it's an initialism, like jot versus JWT. I forget which is which. I think JWT would be the acronym. But jot, that's not even...I'm going to move on and say...[laughs] And so I think that JWTs, which is what I'm going to call them in this context, are, as far as I understand it, an orthogonal, different sort of thing. Like, you can put a JWT in the session, and the session can be stored in a cookie or in the database or wherever. You can also put JWTs...often, they are in local storage, which my understanding is that's a bad idea. That is a security vulnerability waiting to happen from cross-site scripting, I think, is the one that is coming to mind. But I think that's an independent thing where JWT is this signed assertion that you are someone. But it's coming often from an external system versus I'm using devise in this case on a Rails app and so devise is using the warden session, which is signing and encrypting and a bunch of stuff that I'm not thinking about. But it's not using JWTs at the end of the day. Jot, really, huh?

STEPH: [laughs] I like how that's the thing that stuck out to you.

CHRIS: Of course it is.

STEPH: But it's fair because it did the same to me too, so I had to share it. [laughs]

CHRIS: This is The Bike Shed, after all. [laughs]

STEPH: So, going back to your question, what you've done sounds very reasonable to me, especially because you wanted to address that possibility of a replay attack. So I like the idea. I'm also intrigued by why it's not the default. What's the reasoning there? And I'm trying to think of a reason that it wouldn't be the default. And I don't have a great answer off the top of my head. Granted, it's also been a while since I've been in this space. But yeah, everything that you've done sounds really reasonable. I like it. I also see how being able to sign out a specific user would be really neat. That seems like a really nice feature. I don't know how often that would get used, but that seems like a really nice thing to be able to do to identify a particular user if they submitted and, I don't know, if some scenario came up and someone was like, "Help, please sign me out," then to have that ability. So I'll be intrigued to hear how this advances if you still really like this approach or if you find that you need to change back to using Memcached or the cookie store.

CHRIS: Yeah, I'm in that space where as I'm looking at it, I'm like, I only see upside here. I guess there's a tiny bit of extra complexity. You have to watch that database table and set up a regular recurring job to sort of sweep old sessions that haven't been touched in a while because this is sort of like an append-only store. Every time someone signs in anew, they're getting a new session. So over time, this database table is just going to grow and grow and grow. But it's very easy to stay on top of that if you just set up a recurring job that's cleaning them. It's part of the ActiveRecord session store is the name of the gem. It's under the Rails namespace or the Rails GitHub organization. So that seems manageable. Maybe that's the one complexity is it has this sort of runaway trait to it that you have to stay on top of, whereas the cookie-based sessions don't. But yeah, I'm seeing a lot of upside for us, so I'm going to try it. I think it's going to be good.

I'm also unfortunately in that space where I think I see all the moving parts as to how I could implement the sign out a user in all of their sessions. But I'm worried that I'm tricking myself there. It's one of those things it's like this feels like it would be built in if it was that straightforward, or it could easily have subtle...it's like, don't invent your own crypto. Like, I think I know how crypto algorithms work. I can just write one real quick. No, don't do that, definitely don't do that. And this one, it seems clear enough, but it's still in the space of crypto security, et cetera, that I just don't want to mess with without really thoroughly convincing myself that I know what I'm talking about. So maybe six months from now, I will have talked myself into it. Or if anyone out there is listening and knows of a good founded, well-thought-out version of yeah, this is totally a thing that we do; here’s what it looks like; I would love to hear that. But otherwise, I'll probably just be happy with the ability to wipe everyone's session as necessary. If any one user leaves themselves logged in at a library and needs me to log them out, I'll just log out every user. That's fine. That's a good enough solution.

STEPH: Yeah. All of that makes sense. And also, the part that you highlighted around that there is that additional work of where then you have to make sure that you have a rake task that's running to then sign people out since there's that additional lift that you mentioned. But I'm excited to hear what folks have to say if they're using this approach and what they think about it. It is super interesting.

CHRIS: Well, yeah, I am very excited about this new development and the management of sessions. And I will let you know if I make any headway on the signing out a user sort of thing. But I think that covers that topic. As an aside, I just wanted to take a quick moment to ask folks out there; we are getting to the bottom of our listener question queue, and we absolutely love getting listener questions. They really help us find novel things to talk about that whenever we start talking about them, it turns out that we have a lot to say. So please do send in any questions that you have. You can send them to hosts@bikeshed.fm. That's an email option. You can tweet at us; we're @bikeshed, or either of us individually. I'm @christoomey.

STEPH: And I'm @SViccari.

CHRIS: And we also have a Google Form, which we will link in the show notes of this episode. So any of those versions send us questions. It can be about more tech stuff, more process stuff, more team-building, really anything across the spectrum. But we really do love getting the questions in, and definitely helps provide a little bit more structure to the show. So, with that aside, Steph, what else is going on in your world?

STEPH: Yeah, I love when we call from our listener questions, for the reason that you highlighted because it often exposes me to different ways of thinking in topics that I hadn't considered before. And you're right; we’re often very opinionated souls. [laughs] And along that note, so I have a question for you. The context is another developer, and I ran into a bug. And when we initially looked at the bug, it was one of those there's no way. There's no way the code is in this state. That does not make sense. And then, of course, it's one of those well, the computer says otherwise, so clearly we're wrong. We just can't see how the code is getting to this place. And what was happening is we were setting a value. We were parsing some JSON. We're looking for a value in that JSON, and we're using dig specifically in Ruby. So if it's the JSON or if it's a hash, and then we're doing dig, and then we're going two layers deep. So let's say we're going foo and then bar, and then dig; if it doesn't find those values, instead of erroring, it's just going to return nil. And then we have an or, and then we have a hard-coded string.

So it's like, hey, we want to set this attribute to this value. If it's the hash, then give us back that value; if not, it's going to be nil, and then give us this hard-coded string. What we were seeing in the actual data is that we were getting an empty string. And initially, it was one of those; how are we possibly getting an empty string when we gave you a hard-coded string to give us instead? And it's because empty strings are truthy. When we were performing the dig, it was finding both of those values, but that value was set to an empty string. And because that evaluates to truthy, we weren't getting the hard-coded string, and then we were setting it to an empty string, and then that caused some problems. So then my question to you is should we have truthiness in our code?

CHRIS: Oh wow. That's a big question. It's also each language I might have a slightly different version of my answer. Yeah, I'm going to have to go sort of across languages to answer. I think in Ruby, I have generally been happy with Ruby's somewhat conservative implementation of truthiness. Yeah, anything that isn't nil false...is that it? Are those the only falsy values? There's maybe one more, but zero is not a falsy value. Empty string is not a falsy value. They're truthy, to name it in the affirmative. And I like that Ruby has a more conservative view of what things are. And so it can have this other surprising edge. I will say that I do reach for present? in Rails, so present? Present with a question mark at the end, that method in Rails, which I pronounce as present, huh?

STEPH: Which is delightful, by the way.

CHRIS: Well, thank you. That method I reach for often or presence would be the variant in this case where you can presence or and then chain on the thing that you want, and that gets the value. It will basically do the thing that you want here. And so, I do find myself reaching for that, which does imply that maybe Ruby's default truthiness is not quite what I want. And I want a little more permissive truthiness, a little more like, no, empty strings are not truthy. Empty string is an empty value, so it is empty. But yeah, I think I can always convince myself of the other argument when I'm angrily fighting against a bug that I ran into, and I'm surprised by. Like, I've experienced this from both sides many times in my life. I will say in JavaScript, I am constantly surprised by the very, very permissive type coercion that happens where you compare a string and a number, and suddenly they're both strings, and they get smashed together. It's like, wait, how is that ever the thing that I would want? And so JavaScript's version feels like it is definitively foundationally wrong.

Ruby's feels like it's maybe a tiny bit conservative, but I like that as a default and then Rails building on top of that. I think I lean towards that most of the time. I will say at the other end of the spectrum, I've worked with Haskell, and Haskell has I want to say it's like a list of chr, like C-H-R list of characters as the canonical way to do strings. I may be mixing this up. It may be actually the string type, but then there's also a text type, and they're slightly different. Maybe it's UTF. I forget what the distinction was, but they both exist, and they are both often found in libraries and in code. And you end up having to constantly convert back and forth. And there are no subtle equivalents between them or any type coercion between them because it's Haskell, and there isn't really any of that. And this was early on.

I never got particularly far in Haskell, but I found that so painful and frustrating. It was just like, come on; they’re like strings. Please just do the thing. You know what I mean. And Haskell was like, "I do not. And I require you to be ridiculously specific about it." So that was sort of the high end for me of like nope, definitely not that JavaScript of like anything's anything and it's fine. That feels bad. So somewhere in the middle, Ruby feels like it's a happy in the middle. Maybe Rails is actually where I want to land, but I don't know that there is a good answer to this. I don't know that there's a language that's like, we got it. It's this very specific set of things. It's truthy, and these are falsy, and it's perfect every time. Like, I don't think that can happen.

STEPH: As an aside, I like how your Haskell voice had the slight air of pretension that really resonated with me. [laughs]

CHRIS: I don't know what you're talking about. That doesn't sound familiar to me at all. [laughs]

STEPH: I agree. I don't know that anyone has gotten this perfect. But then again, I also haven't tried all the languages that are out there, so I don't feel like that's really a fair statement for me to make either. Specific to the Ruby world, I do think Boolean coercions are a bit nice because then they do make certain checks easier. So if you are working with an if statement, you can say, "If this, and then do that, else, do this." And that feels like a pretty nice common idiomatic flow that we use in Ruby but then still feels like one of those areas that can really bite you.

So while having this conversation with some other thoughtboters, Mike Burns provided a succinct approach to this that I think I really like where he said that he likes the use of truthy and falsy for if statements, Booleans for the and statement, and only truthy falsy for Booleans, so no nulls. So Boolean should not have three states is what that last part is highlighting. It should be just true or false. And then if we're working with the double ampersand and in Ruby, that then if you have that type of conditional that you are conveying, then to use a strict Boolean, be more strict and use the methods that you were referring to earlier, like empty and explicitly checking is this an actual...like turn it into a Boolean instead of relying on that that truthy falsy of is it present? Is it an empty string? Does that count? But then, for the if statements, those can be a little more loose.

And actually, now that I'm saying it, that first part, I get it. It's convenient, but I still feel like bugs lie down that path. And so, I think I'm still in favor of being more explicit. If I really care if something is true or false, I want to call out explicitly. I expect this to be true or false versus relying on the fact that I know it will evaluate, although I'm sure I do it all the time, just because that's how you often write idiomatic Ruby. So I'm interested in watching my own behavior now to see how often I'm relying on that truthy, falsy behavior, and then see the areas that I can mitigate that just because yeah, that bug is fresh in my mind, and I'd like to prevent those bugs going forward.

CHRIS: I really liked that phrase of that bug is fresh. So that bug is going to own a little bit more mindshare than that old bug that's a bit stale in the back of my brain. I will say as you were talking about idiomatic Ruby, I think you're right that the sort of core or idiomatic way to do it would be if the user or whatever to see is the user here, or are they nil? Did we find one, or did we not? That sort of thing is commonly the way it would be done. I almost always write those as if users are not present? I will convert it into that because A, I'm writing Ruby, and I write Ruby because I want it to sound like the human words that I would say. And so I wouldn't say like, "If user," I would say, "If the user is present, then do the thing." And so I write the code to do that, but I also get the different semantics that present? Brings or blank? Is the counterpart, the other side of it. That seems to be the way that I write my code. That's idiomatic me, Ruby, and I don't know how strongly I hold that belief. But that is definitely how I write those, which I find interesting in contrast to what you were saying.

The other thing that came to mind as you were saying this is that particular one of an empty string. I kind of want to force empty strings to not be okay, particularly at the database level. So I'll often have null false on a string column, but then I'll find empty strings in there. And I'm like, well, that's not what I meant. I wanted stuff in there. Database, I want you to stop it if I was just putting in an empty string because you're supposed to be the gatekeeper that keeps me honest. And so I do wonder if there is a Postgres extension that we could have similar to the citexts, citext, which is case-insensitive text. So you can say, "Yeah, store this as it is, but whenever you compare it, compare case-insensitively," because an email is an email. Even if I capitalize the third letter, it doesn't make it a different email. I want a non-empty text as a column type that is both null false but also has a check constraint for an empty string and prevents that.

And then similarly, the three-state Boolean thing that you're talking about, I will always do null false on a Boolean column because it's a lie if I ever tell myself. I'm like, yeah, but this Boolean could be null, then you've got something else. Then you've got an ADT, which I also can't represent in my database, and that makes me sad. I guess I can enum those, but it's not quite the same because I can't have additional data attached. That's a separate feeling that I have about databases. I'm going down a rabbit hole here. I wish the database would prevent me from putting in empty strings into null, false string columns. I understand that I'm going to have to do some work on my side to make that happen, but that's the world I want to live in.

STEPH: I'm trying to think of a name for when you have a Boolean that's also a potential null value. What do you have? You have nullean at that point?

CHRIS: Quantum Boolean.

STEPH: Quantum Boolean. [laughs]

CHRIS: Spooky Boolean.

STEPH: The maybe Boolean?

CHRIS: Yeah.

STEPH: No, that's worse. [laughs] Yeah, I'm with you. And I like the idiomatic Ruby. I think that is something that I would like to do more of where I'm explicitly checking if user instead of just checking for that presence and allowing that to flow through doing the present check and verifying that yes, we do have a user versus allowing that nil to then evaluate to falsy. That's the type of code that I think I'd like to be more strict about writing. But then it's also interesting as I'm formulating these ideas. Is it one of those if I'm reviewing a PR and I see that someone else didn't do it, am I going to advise like, hey, let's actually check or turn this into a true Boolean versus just relying on the truthy and falsy behavior? And probably not. I don't think I'm there yet. And I think this is more in the space that I'm interested in pursuing and seeing how it benefits the code that I'm writing. But I don't think I'm at the state where then I would advocate, at least not loudly, on other PRs that we do it. If it is, it'd be like a small suggestion, but it wouldn't be something that I would necessarily expect someone else to do.

CHRIS: Yeah, definitely the same for me on that, although it's a multi-step plan here, a multi-year plan. First, we say it on a podcast, then we say it again on a podcast, then we change all the hearts and minds, then everyone writes the style, then we're all in agreement that this is the thing that we should do. And then it's reasonable to bring up in a pull request, or even then, I still wouldn't want it. Then it's like standard rb or somebody else's job. That's the level of pull request comment that I'm like, really? Come on. Come on.

STEPH: This is a grassroots movement for eradicating truthiness and falsyness. I think we're going to need a lot of help to get this going. [laughs]

CHRIS: Thankfully, there are the millions of listeners to this show that will carry this torch forward, I assume.

STEPH: Millions. Absolutely.

CHRIS: I'm rounding roughly a little.

STEPH: There are a couple, yeah. [laughs] I'd be far more nervous if I knew we had millions of people listening.

CHRIS: I kind of know that people listen. But at the same time, most of the time, I just entirely forget about that, and I feel like we're just having a conversation, which I think is good. But yeah, the idea that actual humans will listen to this in the future is a weird one that just doesn't do good things in my head. So I just let that go. And you and I are just having a chat, and it's great.

STEPH: Yeah. I'm with you. And just to reiterate what you were saying earlier, we love getting listener questions. So if there's anything that you'd like to send our way and have us to chat about or something you'd like to share with us, then please do so. On that note, shall we wrap up?

CHRIS: Let's wrap up. The show notes for this episode can be found at bikeshed.fm.

STEPH: This show is produced and edited by Mandy Moore.

CHRIS: If you enjoyed listening, one really easy way to support the show is to leave us a quick rating or even a review in iTunes, as it really helps other folks find the show.

STEPH: If you have any feedback for this or any of our other episodes, you can reach us at @bikeshed or reach me on Twitter @SViccari.

CHRIS: And I'm @christoomey.

STEPH: Or you can reach us at hosts@bikeshed.fm via email.

CHRIS: Thanks so much for listening to The Bike Shed, and we'll see you next week.

All: Byeeeeeeeeee.

Announcer: This podcast was brought to you by thoughtbot. thoughtbot is your expert design and development partner. Let's make your product and team a success._

Support The Bike Shed