 Let's get started. I can't see very well out there, so shout if you feel the need to ask questions or interrupt. How's the conference for everybody so far, good? All right, excellent. All right, this thing's not gonna work apparently. So, I'm Alex Boster, I work for AppFolio, which is a company in Santa Barbara. I work in their San Diego Engineering Office. We're hiring, come talk to me. So, the last, I would have said one particular project inspired this talk, but really it's also the culmination of the kind of experience you get after a few years of doing web development. So, this is a survey of surprisingly difficult things. Now, what things do I mean? I mean commonplace things that you know all about in your day-to-day life, so they're easy to model and you model them and they're great, and then it turns out it's actually a lot harder than that. So, things where the obvious implementation may very well cause problems. And in fact, these are things like time stamps, time zones, physical addresses, human names. Now, when you hear these terms, what do you think? Does this sound easy? These are solved problems, right? Easy, no problem, no complications. What am I not gonna talk about? I'm not gonna talk about cash invalidation or distributed systems or other also surprisingly difficult things, but this is about real world stuff. So, one of the things, you know, one of the reasons I wanted to give this talk was that developers fall into these traps all the time. We spent months cleaning up old buggy code and then new bugs of the same type are introduced six months later by other developers. So, maybe, you know, with a laundry list of things to be aware to watch out for, maybe you won't fall into that trap. And even very senior developers, you know, it might help to just have the occasional reminder here. If you're not a senior developer and you haven't dealt with this stuff much before, then hopefully this talk will save you some time in the future. Another good thing is that if you follow these best practices or similar things, then your app could be more inclusive. So, I know that when I start dealing with these real world things, I, you know, causes me to wanna drink. Might just drive you to drink too. So, let's start with time. So, there are a bunch of time and date classes available to you. The only one I really wanna draw attention, this is mostly for reference later, but the one I wanna draw attention to is the last line, where there's just no good cross-system standard for duration. It's different in every database. It's different in Ruby than it is in the databases. So, pay a little attention to that and check out active support duration. But what makes time actually hard to deal with is not this, it's time zones. So, again, isn't this a solved problem? You can just have a sufficiently large integer to represent seconds or fractions of a second and now you have a time value and you're good, right? No problems, let's all go home. Well, again, the problem is in time zones. So, how many time zones do you think there are? Anyone? 30 something? Say it again? 40? Well, you may be right at a certain level. Certainly, the time zone database, which I'll talk about in a minute, defines 385 time zones and then has a further 176 internal links, which are aliases, basically, to give them different names. So, something to remember, there are half hour time zones. There are quarter hour time zones. You've got daylight savings time to take into account. Daylight savings time may start, a place may start observing daylight savings time that previously didn't, like Arizona currently doesn't. A place may change their schedule, like the entire United States did 10 years ago or so and shifted when daylight savings time started. I don't know if this is true currently, but certainly in the past, we've seen examples of two hour daylight savings time changes. This was called double summertime in the UK. And a place may change time zones entirely. They may just switch. So, this time zone database I spoke of is used in many UNIX-like systems. It's used all over the place with a small band of dedicated developers. It tracks all geographic time zones since 1970. They define an area as, you know, where any two places share the same time zone at the same, share the same time at the same time. So, not before, seriously. Before 1970, they don't care as much, but they do have historical data. And it does track daylight savings time changes. So, an example of this is updated several times a year. If you think this stuff is static, no. It's updated several times a year and here's an example of some release notes. You can read through that quickly. Mongolia no longer observes daylight savings time. This region moved from one to another year round and the clocks starting at this particular time hived off a new area, which also affects part of Antarctica. If this change fixed many entries for historical time for Madrid before 1979. And it noted that Ecuador switched, actually observed daylight savings time at a particular time. So, the exact details aren't important, but just know this stuff is really complicated and they're good as somebody's keeping track of it. And that's an example of all the actual regions defined. And that's, again, just unique regions since 1970. Another little trivia, you know, how many time zones are in the United States according to this map? Just the continental United States. I count at least six. So, we use UTC as a way to standardize things. You know, when things happen at the same instant in time regardless of what you actually call that time in a particular place. Hopefully, we know that that stands for neither coordinated universal time nor Thomson and Ursell coordinate, named by diplomats no doubt. UTC is not a time zone, but every time zone has an offset from UTC. And as a rule, you should store your time values in UTC. So also, before I proceed, how many, what are the possible offsets from UTC? UTC is kind of in the middle and you can go forward from it and you can go back. How far do you, how did they go? Anyone want to take a guess? That is exactly what I thought. So, in 1995, Kiribati got tired of having their country in two different days. So, they moved the time zone of some of their outlying islands from minus 10 to plus 14. So, there are plus 14 time zones on that side. So, keep in mind, without a time zone, any time value you have without context could take place within a 26 hour range, possible half hour or quarter hour. So, how do you handle this? Well, if you don't explicitly provide a time zone, a time you provide could be interpreted using the operating systems default, using your databases default, or using your applications default time zone. That should be configured in your Rails app. Okay, now I need a slightly bigger drink. So, as we said, keep your system and database time in UTC. Rails will store its date times in UTC and time zone aware methods in Rails will use the applications default if you don't overwrite it by expressly providing one. So, for example, if you have users, be sure to store a time zone on the user's model and always use that in your views if you care at all about when things occur for said users. And just as an example here, you can see that you wanna use time.zone.now, I'll talk about this a little more in a minute, and ActiveSupport provides some really sophisticated stuff around it. So don't just use bare Ruby time, use the Rails classes for all this stuff. So these time zone aware methods, you can see we're parsing two different times in different time zones, but they're actually the same time. So it all works, that's cool. And here are some examples of methods you should use. Hours from now, days ago, those are all good. Always do time.zone.parse, don't do time.parse. If you use string parse time in the middle here, always use in time zone at the end or you will be screwed. Prefer definitely time.current to other methods for getting the current time and the UTC ISO 8601 is for if you're providing an API. Excuse me, if you're providing something to an API. These examples are all from a stolen shamelessly from a blog post. So dates are simpler, right? Dates don't have a time zone. How do you know if you should be storing something in a date or a time? Ask yourself, does it matter what time of day? This seems really basic, but people make this mistake all the time and just convert a date to a time willy-nilly. So don't do this. So what are some examples of dates? Well, birthdays, a birthday occurs on a day. We don't generally observe when the actual minute of the day a person was born. All day calendar events, maybe holidays you might think of. So, for example, I don't know. To take a Western example, Christmas is on the 25th regardless of whether or not you're in Beijing or Toronto. So as I said, don't store dates and date times. You will have problems. Be very leery of converting back and forth. You almost never wanna do that. For example, the one case I can think of offhand is if you have a calendar that you've written and you want to convert, somebody's editing an event and it goes from being, say, an all day event to a timed event, then maybe. So let's see, where are we? So, this is fine. Where did that come from? I don't know. So, you wanna use date.current because I ran these two lines in the middle of the day, seconds from each other, and that's what I got. Why did it behave that way, anyone? Yeah, basically current takes into account your time zone. The other one is basically telling me that in London it's the 24th. But I see date.today all over the place in code. So, these are really the only two date methods you should be using, and you absolutely have to avoid time.now, time.parse, time.string.parse.time without the in time zone things at the end or date.today. Something that can help you with this is, be sure to use, who uses Rubicup? Okay, how many of yous put that into your actual process formally? A few, good, cool. I gave a lightning tell a couple of years ago on this and depressingly few people did that. So, there are services like Hound, Farsi, there's probably others that I'm courtesy that I'm not thinking of or I'm not familiar with, but you can make it a blocker so that you can't merge a PR unless it passes your style check. And amongst the many benefits of that is Rubicup allows you to catch some of these errors for you. Any more comments, questions about dates and times? Let's move on to human names. So, many of you may have read, there were a couple of blog posts that were related. One was called falsehoods programmers believe about names, which I believe was actually inspired by falsehoods programs believe about time. And to take a few examples from this, we have things like, none of these statements are true. People have exactly one canonical full name. Nope, people have exactly one full name which they go by. No, people have at this point in time exactly one canonical full name. People have at this point in time one full name which they go by. No, people's names do not change. That's not true. People's names change, but only at a certain enumerated set of events. No, people's names are assigned at birth. Not true. People's names are written in ASCII, absolutely not true. I'm guessing in this room there's a bunch of people whose names are not actually written in ASCII. Although I'm guessing very few in just emojis yet, but that'll come. People's names are written in a single character set. That's not true. People's names are all mapped in Unicode code points. That's not true. Two different systems containing data about the same person will use the same name for that person. That's hopefully pretty obviously not true either. This is terrible. So now I'm feeling a bit crabby. So really the only thing you can do here with names is validate as little as possible. Just don't bother. Why are you trying? Yes, their cardholder name probably has to match when you submit a credit card, but that's their problem. If you can avoid first name, last name, consider doing so. Just use full name. No idea what's going on. And maybe use given name, family name to be a little bit less English specific. Also store things in Unicode. Remember, you can't guarantee real names are used. And don't assume just because you might have a US based and a US centric business that your users will be primarily English speaking or even have as key names. That's, you know, these things are true in the US as well as overseas. Okay, physical addresses. So how do you model physical addresses? Right? Yeah. Mm. That's good. Well, there are a lot more variations on this than you might expect, even in just the United States Postal Service. So even in the US, remember there are rural routes that look like this. There are military addresses that look like that. That doesn't quite fit the city-state paradigm. Remember, US Postal Service is Puerto Rico, which has a very different address structure. You can actually have a surprising number of lines in a valid address. I saw one example that was supposedly valid that had 12 lines long. It was an international one, but still. And basically don't do this. So until I moved recently, my address had a slash in it. These are actually pretty common in California in numbers. It doesn't validate with Southwest and it doesn't validate with quite a few legacy systems that you'll see out there. I had to have a bunch of banks and things send things to apartment one half. So you can standardize these addresses via the US Postal Service. They'll convert them for you and give you some of the right abbreviations. But remember that special characters are still allowed even after that. Note, for example, cities can have apostrophes in them. Addresses can have slashes or dashes and so forth. Some things to know about US postal codes. Don't use just zip codes. In general, try to use postal code, which is the international version of the word. And don't just make it five characters long or 10 characters long if you wanted to do plus nine because again, you're excluding the ability to store addresses from other countries. Including Canada, which is close enough you might actually wanna be able to ship to it. And also as a bit of trivia, remember that you can't even use zip codes to map to states. There is a database you can buy or possibly download for free that will attempt to give you city information but zip codes not only can cross city boundaries, they can cross state boundaries and here are the ones that currently cross state boundaries. And that's all because zip codes map to postal routes not to geography. It just so happens that most postal routes are geographically constrained. So let's say you wanna validate addresses. Again, my first instinct is to say, why are you doing this? But okay, great. The US Postal Service has a database of them. However, these are not always the same as physical addresses. There are entire towns and communities that have no physical addresses in the US Postal Service database. For example, I'm in San Diego, one of the very wealthy suburbs, not suburbs, it's actually a separate city, one of the very wealthy cities in the center of San Diego County is Rancho Santa Fe. Postal Service delivers everything to their post office and that's it because they didn't want ugly postal trucks driving around going to people's houses. Yet, UPS and FedEx will deliver to people's houses there. So if you're shipping something, maybe you should let them enter their home address even though it's not gonna validate. Oh geez, any comments, questions about fun stuff with addresses here? Anyone, Bueller? All right, money. Yay, y'all need to get paid, right? How do you model money in your Rails apps database schemas? That's a given, no, correct, not as a float. I've seen that though. Any other possibilities? That's a good one. And decimal, so I heard those two. Those are sort of the other two approaches that I'm familiar with. So you may use decimal values. That's what a migration doing that would look like. There are some issues with that. Not that it's invalid. Here we have a line of Ruby code that will render your product for your API. We'll send this out, because it's a decimal, it'll look like that when rendered. And now in JavaScript, we do this. What's wrong with that? I'm sorry, well, maybe. Anything else? Correct, ding, ding, ding, he said floating point. Current product price in JavaScript is now naively a float. That's how the JSON, so you can, sorry, after that bug came up three different times. So you can get around that. If you want to always remember to use a decimal library on the JavaScript side, there's no problem with that, but inevitably someone will forget to cast to make a new decimal object and the bug will be introduced. Also, you'll get very strange rounding when, for example, you're multiplying by, say, a tax rate or something like that that's maybe, has three significant digits, and then suddenly you'll have IEEE rounding issues in your money. Not good. So I prefer the just store sense. Use integers everywhere, you won't have rounding errors. I recommend you keep the name in sense as part of it everywhere and then only convert at the last minute for display purposes to dollars or whatever your currency is. Do be aware, some currencies have mills instead of cents. So if that's important, just remember you're multiplying by 1,000 instead. And in this case, usually it's obvious when you've forgotten to convert because it's there on the display. Your totals will be wrong, easier to test. Email addresses, those are easy, right? So this is now a valid email address. Just keep that in mind. This is now a top level domain. There are still places out there that try to validate.com.net.edu. You can validate that there's an at, there has to be an at sign in an email address and there has to be a dot somewhere in the domain name. That's it. Otherwise, you can do the whole verification email dance, right? Which everybody, people know this, but again, people still try to over validate. And it's kind of like, again, why are you doing this? I'll also just mention as a sidebar that for people who aren't aware of this most email systems, particularly Gmail, will allow you to add a plus after your username and it'll still get delivered to you, but now you can have an infinite number of email addresses without creating new users. This is great for testing or for creating a thousand free trials or whatever you need to do. So, all right, internationalization. I'm not gonna talk much about internationalization because it's a huge topic. There are literally entire conferences on it. I do have a suggestion, which is that particularly if you have a green field application, you've just done Rails Nu, start putting your hard-coded strings in your config locales just from the beginning, even if you have no particular plans to go international or support other languages. A cool thing, a cool result of doing that is that one, changing copy is easier. You don't have to search all over the place. It's all in one place. And furthermore, you can even turn over the keys to a product person or a non-dev and they can make copy changes themselves directly in the code instead of handing it off to you and then you just review their change. Also, if you add a locale to your user model and always use it, then again, you won't have to backport or fill this stuff in later and you can just start if you're in the US with USEN or whatever makes sense for you and then it's there. Payments and credit cards, another huge topic that there's a lot to talk about. Read up on PCI compliance. Again, there's an entire industry around PCI compliance. I think what most people know about it and what you need to take home is never store it, credit card information, but don't even send it from your client browser to yourself. Use a service that will let you send it directly from the client browser and then give you a web hook back. That way you're not even relying on the browser that somebody's using on an airplane with a credit. They're on a Chromebook on an airplane. Just imagine that scenario and now you're relying on them getting the token and sending it forwarding that to you. No, just use web hooks here. And be sure to consider what happens if the call to your provider, Stripe or whatever, times out. That can happen and depending on your architecture, if you don't use web hooks, you have to actually escalate timeouts to a human being to go and look and see what really happened, like did the call go through or did it fail? Recurring calendar events, that's easy, right? You just have like a day of the week and say when it recurs or something like that and you're fine, right? Now, read this RFC and consider that most recurring events have no end date. So how are you gonna model that? There's an infinite number of them. The rules can get pretty complex. This is a fairly simple one, like every month on the second to the last Thursday. And individual instances of a recurring event can be edited or moved or canceled separately from the actual recurring event. Yeah, now I need this particular Bloody Mary. That's right. Check out the garage in Seattle. I'd like to just point out that this particular one has as a garnish a second Bloody Mary for you to get through. While you're working your way down to the Bloody Mary. And in case you think onion rings, chicken wings, a submarine sandwich, a 12 inch pizza, French fries, and a lime are not enough. Here's the back view. There's a cheeseburger, an onion, a lime, a lemon, and two grilled cheese sandwiches. But you might need this if you start working on recurring events. I highly recommend it. So I talked a little fast, ran a little early. In conclusion, the main takeaway is don't overvalidate. People just have this need to check the values of these things when you can't. And I don't understand it. Even US only products will have global like problems. Be culturally aware. Remember that your experience isn't universal and probably isn't typical. And just don't make assumptions. And there are some references if you get my slides later for the blog posts I was talking about. Thank you. I have a request for that slide.