Ruby Idioms You're Not Using Yet
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Title of Series | ||
Number of Parts | 65 | |
Author | ||
License | CC Attribution - ShareAlike 3.0 Unported: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this | |
Identifiers | 10.5446/37634 (DOI) | |
Publisher | ||
Release Date | ||
Language | ||
Producer |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
Ruby Conference 201417 / 65
2
3
4
6
9
11
14
17
18
19
20
25
27
29
30
32
34
35
39
40
46
47
50
53
55
56
58
61
63
00:00
Video gameIndependence (probability theory)Characteristic polynomialPattern languageCache (computing)Operations researchBefehlsprozessorParameter (computer programming)Default (computer science)Bloch waveHash functionModule (mathematics)Pointer (computer programming)Revision controlIntegrated development environmentPoisson-KlammerSubject indexingExterior algebraMultiplication signObject-oriented programmingHash functionArray data structureBlogTopostheorieVariable (mathematics)Loop (music)CalculationProcess (computing)Type theoryCASE <Informatik>Module (mathematics)Presentation of a groupDifferent (Kate Ryan album)Computer architectureLevel (video gaming)Template (C++)Arithmetic meanDefault (computer science)Term (mathematics)Java appletForm (programming)ExpressionSpacetimeProgramming languageRight angleBlock (periodic table)Uniform resource locatorComputer programmingConcentricSinc functionProjective planeException handlingWordSoftware testingComputer configurationSoftware design patternPattern languageInstance (computer science)BefehlsprozessorOperator (mathematics)Error messageParameter (computer programming)Goodness of fitSymbol tableVideo gameCharacteristic polynomialPoint (geometry)Real numberLie groupSlide ruleNeuroinformatikMiniDiscWorkstation <Musikinstrument>View (database)Endliche ModelltheorieMixed realityAreaGroup actionMultilaterationStandard deviationIntelProgrammschleifeComputer animationLecture/Conference
08:13
Error messageModule (mathematics)Exception handlingElement (mathematics)Web pageGroup actionTouchscreenObject-oriented programmingPasswordProblemorientierte ProgrammierspracheLoginExecution unitFormal verificationSoftware testingInstance (computer science)Social classInterior (topology)Configuration spaceQuery languageError messageWeb pageException handlingObject-oriented programmingTouchscreenPattern languageVideo gameDefault (computer science)Software testingExpected valueStandard deviationRevision controlFitness functionExecution unitFormal verificationBlock (periodic table)MereologySheaf (mathematics)CodeInstance (computer science)CASE <Informatik>System callHome pageField (computer science)Computer fileVariable (mathematics)Level (video gaming)Group actionData mining2 (number)Line (geometry)ImplementationSocial classConfiguration spaceCartesian coordinate systemType theoryLibrary (computing)Bit rateStack (abstract data type)Process (computing)Spherical capState of matterPasswordMathematicsCombinational logicGoodness of fitQuicksortProgramming languageSpacetimePhysical systemRight angleTypinferenzLoginQuery languageAbstractionExterior algebraEmailLogical constantJava appletSubsetComputer animationLecture/Conference
16:09
Query languageConfiguration spaceSocial classBefehlsprozessorParsingInstance (computer science)Multiplication signCartesian coordinate systemImplementation2 (number)Variable (mathematics)Computer fileQuicksortLibrary (computing)Different (Kate Ryan album)Lecture/Conference
17:11
Computer configurationModule (mathematics)Factory (trading post)Module (mathematics)Inclusion mapParameter (computer programming)Pattern languageData modelExecution unitConvex hullThread (computing)Computer networkError messageDigital electronicsCondition numberDrop (liquid)Exception handlingModule (mathematics)CausalityFactory (trading post)Inclusion mapPresentation of a groupPattern languageQueue (abstract data type)Computer configurationRight angleObject-oriented programmingObject-relational mappingDigital electronicsParameter (computer programming)Different (Kate Ryan album)Connected spaceDefault (computer science)Social classLibrary (computing)Endliche ModelltheorieLine (geometry)QuicksortModule (mathematics)Constructor (object-oriented programming)Vertex (graph theory)BuildingMultiplication signError messageCodeFunctional programmingException handlingCondition numberHecke operatorSystem callSource codeStatement (computer science)ImplementationThread (computing)WritingTable (information)CASE <Informatik>SoftwareServer (computing)Computer architectureEnterprise architectureCartesian coordinate systemFigurate numberReading (process)Block (periodic table)Continuum hypothesisSoftware testingSummierbarkeitService (economics)Computer animationLecture/Conference
24:18
Integrated development environmentGreen's functionFluid staticsCodeBlock (periodic table)Code refactoringException handlingPattern languageLine (geometry)Analytic continuationMereologyComplete metric spaceMultiplication signCASE <Informatik>Enumerated typeBlock (periodic table)Programming languageQuicksortCodeIntegrated development environmentException handlingHash functionContinuum hypothesisConfiguration spaceLastteilungFlip-flop (electronics)AreaObject-oriented programmingModule (mathematics)Twin primeConfidence intervalGreen's functionSynchronizationSoftware testing1 (number)DatabaseINTEGRALParameter (computer programming)Type theoryEndliche ModelltheorieCellular automatonNumeral (linguistics)NumberControl flowHypermediaFactory (trading post)Chemical equationWordRevision controlReading (process)Point (geometry)FamilyComputer programmingKey (cryptography)Computer animation
29:37
Green's functionSoftware developerFactory (trading post)Module (mathematics)Query languageWeb pageObject-oriented programmingModule (mathematics)EmailSoftware developerQuery languageWeb pageObject-oriented programmingConfiguration spaceTemplate (C++)Data miningProgram slicingEndliche ModelltheorieSlide ruleRootProcess (computing)Factory (trading post)
30:25
Slide ruleSoftwareMaizeFeedbackTwitterMultiplication signEmailPresentation of a groupFeedbackComputer animationLecture/Conference
31:05
SoftwareEvent horizonVideoconferencingArm
Transcript: English(auto-generated)
00:20
you're not using yet and there is a URL you can follow along with the slides. About me,
00:29
my name is Craig Buchek. I have been using Ruby since late 2005. It started with Rails like most of us. I've had occasional forays into other languages, which just makes
00:40
me appreciate Ruby more. I am an independent contractor, always looking for new work. I specialize in Rails rescue projects and Agile techniques and I'm also a sometimes contributor to the This Agile Life podcast. So first I want to talk about what is an idiom. So the dictionary gives us a couple
01:03
definitions and most people when they're talking about programming languages, they kind of concentrate on this first definition, something that can't be understood from the meaning of the separate words but has a separate meaning of its own. I prefer this definition. I think this is closer to what we mean when we talk about idioms in programming
01:20
languages than idiomatic Ruby, which is a form of expression characteristic to a group of people. So that's going to be my working definition, a way in which we normally express ourselves in Ruby. So here's some examples of non-idiomatic Ruby. I can tell that these were written by someone not very familiar with
01:42
Ruby. Maybe they're writing Java in Ruby. So the first one uses a for loop. Idiomatic Ruby uses a for loop so little I had to look up the documentation just to figure out how to get that syntax right. You'll notice that the indentation is not the standard two space indentation that we typically use in Ruby.
02:06
The second one is longer than it needs to be, but not just that, it takes more effort for me to understand what it means. So we've got the i equals and we've got the initializer for the dates and then we go through a while loop and we add things and then we have an explicit return at the end. So that's
02:24
pretty non-idiomatic Ruby. So the idiomatic versions of this, we use a five times or we could use an each there instead of a for loop. And let's see, I can tell that the person who wrote this probably is at least a little
02:40
familiar with Ruby. And the second one with the expiration dates is a lot clearer. And I actually ran into something like that second one in the past couple days. It happened to be JavaScript but it was very non-idiomatic. So one of the things I struggle with, what's the difference between an idiom and a pattern? And it seems like people
03:02
disagree. But a pattern is just a template for a common solution to a common problem. And this doesn't just mean design patterns and architectural level. There can be different levels of patterns. A great book is Kent Beck's Small Talk Best Practice Patterns. It talks about patterns at the method level and very small low-level patterns.
03:23
So in this presentation I'm going to treat the two terms somewhat interchangeably. So why do we use idioms? They're tools that Ruby gives us. Not necessarily on purpose, but they still are given to us by Ruby. And they're tools that Rubyists can help you
03:42
with. Often Ruby is optimized to make idiomatic versions work better than the non-idiomatic versions. So surprisingly that for loop might actually work less than the five times loop. So the first one I'm going to start out with memoization. It's pretty simple. Hopefully most of us have used this pretty often. Let's
04:03
see. If the instance variable is set to something that's not falsy, then the instance variable will be returned. So if we've already set the at yesterday, then we'll just return that. Otherwise we'll actually perform that computation, date to day minus one. And this won't work if your calculation might
04:25
return false or nil. So that's one caveat when you don't want to use that. But you use it to cache expensive operations. If you're going to take a lot of CPU time or if it's going to take some IO if you're going to have to hit the disk, then you might want to consider memoization. But don't just use
04:43
it without thinking. Sometimes you really do need to recalculate every time. An example on that previous slide, if your process might run more than a day, you're going to end up with a bug. It's going to be really hard to track down because yesterday is going to be three days ago instead of one day ago. So think before you use memoization.
05:00
So there is a gem for memoization. But for the basic case, it's not really worth bothering. There are some more difficult cases like when you do care about nil and if you have parameters. It turns out most of the times in Ruby that's not the case, though. I think it's because we extract objects better, so each object is implemented by
05:23
itself, self-contained enough that you can just memoize without parameters. The next one is pretty simple. This one comes from Optigrim from his blog and one from his Ruby Topos episodes. This works best for
05:41
hashes and arrays and hash-like objects. So in this case, we've got a hash-like object in to get environment variables. And the typical way we do this is with just the or, sort of similar to the memoization. We check to see if Ruby version environment variable is nil if we don't have it set.
06:03
And if it's not, we set it to 2.1.4. The problem comes when if Ruby version could be nil or it could be false, that's going to still return the 2.1.4, even though that may be a valid value. So the alternative is to use fetch instead of the square
06:21
brackets. If you use that without any block argument, it will raise an index error if it's not found. And it's helpful to ensure that you don't pass nil around. So if you have a nil and you keep passing around, you get a no method error at a later time and you don't know where that came from
06:42
exactly. You'd have to trace back to figure out where it got set. The second version is the one that Avdi recommends as the default. So you don't have to think about it. If you don't want to have to think about do I have to worry about nil, do I have to worry about false, prefer that version to the first. And an even better version
07:01
if you really need an explicit version specified is just raise an exception in that block. It's good for failing fast and being very explicit about how to fix the problem. Alternatively, if you're the one creating the hash, you can actually new it up with a different default. In this case, we new up the hash to return not
07:25
found as a symbol if the key doesn't exist in the hash. So that's an alternative to worrying about nil and false. Note that array doesn't have this option and env doesn't either because
07:41
we don't ever new it up ourselves. The next one I want to talk about is tag module. This one also comes from Avdi Gurim. It's from his book called Exceptional Ruby that talks a lot about exceptions. So a tag module is a mix-in module whose sole purpose is to add types to an object. Now, that sounds kind of weird
08:02
because we don't use types very much in Ruby, although if you watched the previous presentation in this room, sometimes we do. But the only place we use it is actually to test for types in an exception handler. And here's the implementation. So the idea is here that I've got my own library, and I create
08:22
my own error type in my library, and I do something and I rescue an error, but then I extend that error with my own type, and then I re-raise that same error. So what this does is allow us the caller to rescue either myLibError so he can get any errors that come from my
08:42
library, or he can also rescue on the original exception. Plus, he has all the information available from that original exception. So this next idiom I actually ran across
09:00
when I was doing some Java work, but it works great for if you're using Capybara and Cucumber. So what we do for a page object is we use an object to represent a screen in our application. And this is mainly for full stack testing, acceptance testing. And within that
09:20
object, we have methods that represent actions, and then we have different methods that represent things on the page that we might want to query. So this abstracts away those actions. You don't have to specify all the details. And that's one of the problems with Cucumber is all the steps end up being in these worlds, but there's just one big pile of methods
09:41
that you can call. This gives some object orientation to your testing. So here's an example. So we create an instance variable called homepage. We create a new login page object. That's our page object. And then we log in. We tell it to log in. And we just pass it to username and password.
10:02
In this case, I did something a little unusual. On the initialization, I actually visit the page. We may or may not do that depending on your use cases. And then we've abstracted the details into this page object. So when I call login page dot login, I don't have to worry about all those details,
10:23
like filling in the user. If the username field ended up being called email instead of user, I could fix that in that login definition instead of every place I want to use it. And one of the advice that they give on using page objects is if you perform an action,
10:43
return another page object. So in this case, when I log in, I end up at the homepage. And I create a new homepage object based on that page and return it so I can later interact with that. So that's why I assigned it to that at the very top to the homepage instance variable.
11:03
Another idiom that we use in tests, there's quite a few idioms we use in testing, especially if you're using RSpec. So one of my problems that I've always had is mocking. So mocking is where you set an expectation for what's going to happen before you actually run your code.
11:21
And I'll show you an example here shortly. But the problem I've always had with it is it doesn't fit the pattern. So there's three ways of expressing the patterns that we normally use when writing a test. The X unit standard terminology is set up, exercise, verify, tear down. The one I like to use is arrange, act, assert.
11:43
And then the more modern version is given when and then. So in the first example, down at the lower part, invitation.deliver is our act. And the expect invitation to receive deliver
12:00
is actually making an assertion. But it's making it in the wrong place. And that makes it difficult to move the act part into a common section and the setup into a common section. So RSpec3, and I believe 214 actually, gives us a different way to do it.
12:21
We can actually do expect invitation to have received. So we get to make that verification, that assertion after we've actually done our actions. So the tricky part there is that we actually have to set up our test double up at the top. But we don't really have to set up that verification until the end.
12:46
In this way, we can move that invitation deliver to a before block if we want. And the assertion only runs in one place in that case.
13:00
So the next one I'll talk about is allow class or instance. So I came across this in one case. I don't know if it's generally applicable, but I did find one use for it. And I'll show you that now. So I was actually writing a date class for the Crystal standard library. So Crystal is kind of what Matz talked about earlier today.
13:22
It's a language that already exists, and it is mostly Ruby. It is compiled, and it does type inference. So it's a new language, and I was creating the date class for it. And one of the tricky things about date classes is the US changed its calendar in 1752.
13:44
And we changed from the Julian calendar to the Gregorian calendar. So if you're on a Unix system and you type Cal 1752 space 9, it will actually print the calendar that is missing 11 days. And so if you want to get your date class right,
14:00
you have to make that work right. So in this case, I wanted my default calendar to be a combination of the Julian calendar and the Gregorian calendar that changes on a specific date. So I've got a Julian class, and I've got a Gregorian class, and then I've got a third class
14:21
that basically combines those two. So the trick was, when I'm creating new and up a date, sometimes I don't want to provide what calendar I'm using. I just wanted to take the default. Sometimes I want to say, this is the Julian calendar. Sometimes I want to say, this is the Gregorian calendar.
14:41
And sometimes I want to say, well, I'm not in the US, and in fact, those two calendars didn't change in 1752. Every country actually changed on a different date. So sometimes I need to be specific about when this calendar change happened. And so the implementation actually ended up being pretty simple.
15:02
Basically, I say, if the calendar's a class, new it up. Otherwise, assume it's already been newed up and it's an instance. So there were some alternatives I considered here. I could have made Julian and Gregorian into constants instead of classes, but then I'd have to give them singleton methods,
15:21
and that didn't seem like a good answer either. So this seemed like sort of a new idiom that I hadn't come across yet before. The next one is configuration query. And a coworker of mine came up with this. So normally when you're grabbing some configuration data,
15:44
you'll ask the configuration file what it looks like. We decided to take that up a level. Instead of asking what does it look like, we wanted to ask more of the actual question we wanted answered. And in this instance, we were doing some extra logging. And we wanted to say, hey, does this applicant get extra logging?
16:03
So we just made the API that one level higher. So we got to use a couple other idioms in our implementation of this. One is defining a class method with self there on the second line, self for applicant. And that's our main API.
16:22
And in fact, we probably would have made the rest of them private if you could make class methods private here. So we're using some memoization in that second method there. And the third method. The third method is loading a file and parsing. So that's something that's going to take some CPU time and some IO time.
16:43
And one of the nice little tricks we were able to do with memoization is we actually used unmemoization in the reload. So when we got a signal, we called this reload method, we set those memoized instance variables back to nil. And so the next time it comes through, it actually refetches the data.
17:01
So that was a nice little idiom that we came up with. So the next one is the one that sort of instigated this whole presentation. And for a long time, when I started using Rails,
17:22
we've got these different ORMs or different libraries and you've got your model class and you include the ORM and then you provide a separate line that configures that ORM. And that always bothers me, because I always thought, well, isn't table more like an option to that module being included?
17:41
Couldn't we just put it up there? And so, of course, I tried, you know, well, let's see if we can write something like that. And I tried adding table colon people to the include. But that's not a valid Ruby. Include can take more than one argument, but they all have to be modules. So for a long time, I'd looked for a solution
18:02
and didn't come up with one and had assumed that there wasn't one. And then I was reading some source code for Virtus, or actually just the documentation for Virtus, and it told me how to use Virtus to create a model. And I found what I'd been looking for for several years. And so the next thing was, well, how the heck is that working?
18:25
What is that doing? So we've got Virtus.model and then parentheses. So that's just doing a function call, right? Or a method call. And it must be returning a module because that's all that a include statement will take.
18:43
So what it's doing is it's using the factory pattern to create a module, and it configures that module dynamically per the parameters that we pass it. So I've got an example here of a simple implementation of how this can work. And, again, we're using a few simpler Ruby idioms here.
19:03
We're defining class method. We're modifying an object in line with tap there. That's pretty common in idiomatic Ruby. We're using the fetch with default value from Avdi there. And we're using an options hash, which is pretty common, to pass in arguments.
19:23
And one of the tricks here is that we're actually accessing a private method there with send. So we're taking that module that we created with module new, and we're passing that to tap and called it mod. We can't call it module because that's actually a keyword in Ruby.
19:41
And so we're sending include to that module to add the model constructor. So we're creating a module that's getting other modules included to be included into our model.
20:00
So we can do a lot of different things with this. We could actually take positional parameters. We could take a block. We could use the builder pattern. Anything that ends up creating a module can be used in that include statement. And as my friend Amos wants me to point out,
20:20
the vertice.model there doesn't have to be a lowercase. You can actually use an uppercase model there. You can have methods with uppercase names. Let's see. So this is kind of the, I'm kind of one of the first people to discover this, at least in the wild.
20:41
So I wasn't able to find a name for this pattern or idiom. So at first I call it parameterized module inclusion. But then I realized it's just a factory building module. So I decided to call it the module factory. The next pattern I'll talk about is circuit breaker.
21:02
And this came to me by way of Martin Fowler, who wrote Patterns of Enterprise Application Architecture. And he says he got it from Michael Nygaard from ReleaseIt. And this comes from Martin Fowler's Blicky. And so this is trying to stop an avalanche of inaccessible resources.
21:25
So sometimes when you've got a slow resource, a slow server, and its API isn't providing you any answers, it's timing out, it's down, whatever, sometimes that'll cause everything else to fail. The idea of the circuit breaker is, hey, let's stop the failure,
21:41
let's stop the bleeding right here at its source and respond to it in a better way. So that might be a slower unresponsive network resource. You might have filled up a queue. You might have a thread pool or a connection pool that's been exhausted. And you need to somehow deal with that in a sane way.
22:03
This is a little harder to explain in a very short amount of code. I will point out that there is a circuit breaker gem that you might want to take a look into. It's by wsargent on GitHub. And there's also an article on Martin Fowler's Blicky.
22:23
So the idea here is when we're trying to do something, before we do it, we see if the circuit's already been broken. And if it's already been broken, then we don't want to make that call. Or we might actually want to make that call maybe one times out of 10 and just see if it's up. But we don't want to make it every time, for sure.
22:41
And then if there's a problem, we rescue that error. We break the circuit any time there is that error, whether that's a timeout or some other not-there error. And we probably want to re-raise that exception. And so the other code is just basically seeing if the circuit's already been broken
23:01
and some simple code to break the circuit and a way to reset the circuit. Now, in this case, there's no automated way to reset the circuit, and that may actually be your case. You may send some logging and alerting for someone to manually reset the circuit, or you may find some way to reset that circuit.
23:22
And like I said, when the circuit is broken, you might want to try one out of 10 times. And if it comes up three times in a row, then assume it's up, and you reset that circuit. So the circuit breaker pattern has a lot of different options. What do you do when it's broken? Do you just drop the request? Do you raise an exception?
23:41
Do you use some sort of promise to return a value later? Lots of different things you can do depending on what you're trying to accomplish. Also, the conditions for deciding when to flip the breaker. Do you do it on the first error? Do you wait for five in a row? Do you wait for 50% over a given amount of time?
24:01
What conditions would automatically reset that circuit? You definitely want logging and alerting. If you're going to trip a circuit, you want to have a log of what, as much as you can, what went wrong, and you want to let someone know as soon as possible. And the next idiom I want to talk about is blue-green deployment.
24:23
This is a deployment pattern, and I'd like to see more Rubyists adopting this as a lot of us have done continuous deployment, sort of this continuum of testing, continuous integration, continuous deployment. I think the next one on that line is blue-green deployment.
24:41
And the idea here is to have two complete production environments that are equivalent except for the code running on them. One is live, and the other one is either the previous code or the next code. And when you're deploying, you deploy to the one that's not live,
25:01
and the other one just keeps running as it was, and you smoke test what you just deployed to, and then you just swap the live one with the next one. So that's usually your load balancer pointing to the green and then moving to the blue or moving from the blue to the green. This makes it very easy to switch back to the previous deployment.
25:24
We ran into a problem a couple of weeks ago where we deployed the wrong version, and it took almost eight hours to restore everything back to the way it was. Had we had had a blue-green deployment, we probably would have been back in about five seconds.
25:41
So the trickiest part about this, though, is database integrity. If you started using the new database with the new code, do you lose all that data and go back to the old database? That's the hardest part about setting all this up. Do you sync the two databases?
26:00
Do you break them? When do you break them? When do you sync them? That's the hardest part about that. So the takeaway here is we should learn the idioms because they're tools that can help us to write better code. They can help us write more readable code.
26:22
And even more importantly, it turns out that idioms are not static. We might find new ones. We might be looking for them and not find them for years, and then all of a sudden we find a new idiom. And these new idioms, sometimes they fit with our existing idioms and extend what we can do with Ruby.
26:45
So I've got some ideas for further investigation. These are basically ones that I didn't have anything important to say or I didn't have time to research and find anything interesting to say. Config blocks is one of the popular things that we use in a lot of areas in Ruby.
27:02
DSLs is something we use in Ruby probably more than any other language. I saw a mention of flip-flops the other day. I've only seen mention of that twice, but every time I see it, it blows my mind. I saw a case where sometimes a method will take a block or a value,
27:21
which is pretty handy. In fact, I think the hash fetch probably is using that idiom. Enumerable is pretty awesome. If you've got a collection, always try to implement the enumerable module. Enumerator is related to that, and I've not looked into it very much,
27:40
but it sort of gives you a promise or a continuation on enumerable return values. We can always use more object-oriented programming and object-oriented design in our Ruby. We could always use more refactoring. Keyword arguments is new to Ruby, I believe 2.0, but refined in 2.1,
28:03
and 2.2 is going to be coming out soon, so there's probably some ways we can use keyword arguments that aren't immediately obvious. Apparently, we're going to have to learn about soft typing and how to use that pretty soon. And so other languages, a lot of times, some of these things I found in other languages
28:21
that I found more applicable to Ruby than their original language. So always be on the lookout for new ideas that we can use in our favorite language. So a couple of the resources I used to find some of these, Confident Ruby by Avdi Grimm, and Exceptional Ruby by Avdi Grimm,
28:42
where I grabbed a couple of these, and anything by Avdi Grimm. In fact, anything by Kent Beck or Martin Fowler would be great to read. Martin Fowler's Smalltalk Best Practice Patterns is a great book on patterns in the small, probably more close to what most people think of as idioms.
29:03
If you think it's a little weird to recommend a Smalltalk book at a Ruby conference, Avi Bryant had a great talk at RailsConf, I think it was 2007, maybe 2006. He talked about Smalltalk and Ruby and how they're basically brothers, twins separated at birth.
29:21
So most of the things in that book are applicable. And Martin Fowler's books in Blicky, his Blicky covers pretty much all the patterns that his books cover, sometimes in more depth and sometimes in less. And here's where I got the ideas for most of these.
29:42
A memoization was suggested to me by James Edward Gray. If you have a question for a, you want to talk to one of the people that are high up, high up in the Ruby chain, send them an email. James actually answered my email really quickly and unbelievably thoroughly.
30:04
So the page object idea came from Selenium developers. It happened to be actually more for Java, but it worked really well for Ruby. The config query was Kyle Stevens and myself, a co-worker of mine,
30:21
and the module factory, like I said, came from Virtus. And if you're interested in the slideshow software, it's a slideshow called Remark, and it lets me have show notes that I can see, presenter notes that I can see that you can't, it has a timer. It's really easy to use.
30:41
Basically, you just have a template, and you throw in some markdown, and it does everything for you. And I would appreciate any feedback. My Twitter is craigbuchak, GitHub is booch, and email is boochtech.com. Thanks, I appreciate your time.