Test-Driven Development

The Case of the Missing Method — A Ruby Mystery Story

The Case of the Missing Method — A Ruby Mystery Story

by Nadia Odunayo

In the talk "The Case of the Missing Method — A Ruby Mystery Story" presented at RubyConf 2018 by Nadia Odunayo, the speaker narrates an engaging story of Deirdre Bug, a Ruby Private Investigator tackling a case of missing class methods. The backdrop involves Mike, a junior Ruby developer, who is anxious about some discrepancies in his friend Jenny's notes, which impact their chances at a prestigious interview.

Key Points:

- Introduction to the Characters:

- Deirdre Bug is a private investigator with a focus on Ruby programming language.

- Mike, a junior developer, and Jenny, his best friend, are preparing for an interview at the Ruby Institute of Professionals.

  • Investigation Begins:

    • Mike discovers that Jenny’s notes on method lookup were flawed, leading him to seek Deirdre's help to clarify the correct information.
  • Exploring Ruby's Object Model:

    • Deirdre examines the notes, which detail Ruby objects, classes, and how method lookup operates. She finds a code snippet demonstrating a class and explores relationships between instances and classes.
  • Researching the Method Lookup:

    • Deirdre develops a hypothesis that the missing method lies within the class's ancestry tree. She creates a method to search the ancestry for the missing method, leading her down a rabbit hole of Ruby's objects.
  • Discovery of Singleton Classes:

    • During her exploration, she learns about singleton classes and how methods can reside within an object's unique singleton, which could explain the missing method.
  • Deirdre's Resolution:

    • After much investigation, Deirdre successfully locates the edible method within the Cake class’s singleton class, solving the mystery.
  • Encountering Jenny's Deception:

    • Mike confronts disappointment when he realizes Jenny intentionally misled him about the notes, revealing her as the true antagonist in the story.
  • Understanding DSLs:

    • Deirdre learns from her friend Ellen about domain-specific languages (DSLs) and how they utilize singleton classes effectively in programming.

Conclusions & Takeaways:

- Understanding singleton classes is critical for Ruby developers as they underpin method definitions and class behaviors.

- The investigation illustrates the importance of object-oriented principles in Ruby, especially how class methods are actually instance methods stored in a class's singleton class.
- The story emphasizes continuous learning for developers and highlights the community aspect of sharing knowledge in programming.

00:00:15.350 Hi everyone, thank you so much for coming to my talk. I'm going to tell you a story today. You've come to hear about the case of the missing method, but before I get started, I've got a question for you.
00:00:28.260 Do you have a side gig? You know, something that brings in a little extra each month? I do! By day, I'm a product manager for a software team in a bank, but by night I'm a private investigator.
00:00:41.730 And what is it I investigate? Ruby crimes! Not jewel theft—the programming language. And why did I decide to go into private investigating? Well, there have been rumors of a shady mastermind causing havoc and confusion among Ruby developers worldwide with his constant meddling in the source code. I wanted to help put a stop to it.
00:01:02.879 But I couldn't do this job under my normal name, right? I had to protect my identity, so I chose the name Deirdre Bug! You know, D for sure! And why did I choose this name? Well, primarily to make this joke.
00:01:16.860 Today, I'm going to tell you about one of my more memorable cases: the case of the missing method. Chapter one. I was sitting alone, bored in my office—well, I like to call it my office, but it's really just my flat—when the doorbell rang. It was Mike, and he didn't look too happy.
00:01:51.439 Let me tell you about Mike. He was an acquaintance of mine. We had some mutual friends and saw each other now and then. He was a 24-year-old junior Ruby developer who was excited about test-driven development.
00:02:07.280 He had been applying for an apprenticeship at the prestigious Ruby Institute of Professionals. He managed to get through all the stages, beating hundreds of applicants to the final stage. That final interview was happening in two days, and there were two people left fighting for this spot: himself and a woman named Jenny.
00:02:31.390 Jenny, age 27, was also a junior Ruby developer who loved all things Rails. She was Mike's best friend, and together, they had quit their consulting jobs to enter the world of tech. Not long after that, they decided to live together.
00:02:46.010 For the interviews, they agreed to work together to prepare, hoping for the best shot for both. 'May the best person win,' they said. For this final stage, they were asked to research a series of topics they were going to be grilled on, one of which was method lookup.
00:03:10.790 It was in Jenny's notes that Mike found something that didn't add up. 'She’s been so stressed and panicked, and uncharacteristically, she won't hear me out,' he said. 'She’s convinced that we don't have time for my doubts, but you can help me find the answer to this mystery.'
00:03:25.489 He said that if I could help him, he’d have what he needed to confidently correct Jenny’s notes and save them both from interview failure. He reached into his satchel and drew out some sheets of paper: Jenny's supposedly flawed notes. I asked him to walk me through them.
00:03:47.660 Jenny had created boxes representing the concept of a Ruby object, and all of these labeled boxes had a 'class.' This acted as a reference to the parent class of the object in question. Some of these objects were instance objects, and their class labels referred to another Ruby object of type Class.
00:04:00.500 Objects also had their class label, and all objects of type Class had a methods label, which pointed to a table of all the instance methods that instances of that class could call. Jenny had written, 'Any class you define is an instance of a class object called Class.'
00:04:12.140 So if we were to write class Cake in our code, we are creating a class object named Cake, which is an instance of another class object called Class. Thus, all classes are of type Class with a capital C.
00:04:31.620 Jenny's notes had code that read: class Cake, with an instance method called tasty that returned true if the flavor of the cake was carrot. I immediately recognized that Jenny was smart! She also included a class method called edible that always returned true.
00:04:57.350 Then, she wrote, 'Imagine we had a cake instance called carrot.' So, she created carrot = Cake.new. This is what the method lookup chain would look like: she had a box labeled carrot, pointing to another Ruby object labeled Cake, which also had its own class label.
00:05:14.580 It had a methods label pointing to a table with a single entry: the tasty method because all instances of Cake could call tasty. The class label for the Cake object pointed to another object labeled Class with a capital C, containing its own methods label.
00:05:30.460 That label pointed to a table with a single entry called edible. Jenny had written, 'A method definition always comes from an object's class.' At this point, Mike shook his head in frustration.
00:05:49.570 'It can't be that the edible method lives on the parent class for all classes,' he said. 'Can I show you something? Can I jump on your computer?' I said okay. He went over to my desk, opened the terminal, and his Pry console.
00:06:09.750 He loaded the Cake class from Jenny's notes and showed that the class of Cake is indeed Class. Then he searched all of the instance methods for edible in class, but found nothing. What a mystery! I was stumped at this point, unsure how to proceed further.
00:06:36.859 But if anyone could tackle a challenge like this, it's me! Besides, this meant a lot to Mike. He was prepared to pay me handsomely, so I agreed to take on the case. Chapter two.
00:06:50.819 Ever heard of Google? Or if you care about your privacy, perhaps you use DuckDuckGo? Well, when I'm Deirdre, I don't believe in these tools. I don't trust them. It’s no coincidence that I've become the best Ruby private investigator the industry has to offer.
00:07:07.100 What did I have? Books—lots of books! I spent a whole day quickly skimming through many of them but couldn't find any useful information. So I thought, why not form a hypothesis and go from there?
00:07:21.060 I hypothesized that the edible method, while not on the Cake object, must be somewhere in the ancestry tree. So what's the ancestry tree? Well, it shows all the classes and modules that our class inherits from; all the possible places that a method could come from when you call it on an object.
00:07:37.650 I thought about how to search this ancestry tree, and made a method called where that took two parameters: an object and a method. This method would help me find the method in relation to the object.
00:07:53.370 I searched through the ancestors of the object's class to find the class where the instance methods of that class included the method I was looking for. I thought, 'Let me check that this method is working.' So I created an instance of Cake and said, 'Let me find the tasty method on this instance, carrot.' If it's working, it should be on Cake.
00:08:09.150 Great! Now it was time to find the edible method. It must be somewhere in the ancestry tree. What? No! I was still at war with this mystery. At this point, I was confused and thought, 'Okay, it's time for some fresh air. It's time for a change of scenery.'
00:08:24.780 So I decided to go to my favorite co-working space. Here, I felt at home surrounded by people hacking away. I quickly settled down at one desk. Given my naturally inquisitive nature, my eyes couldn’t help but stray to the screen of the guy next to me.
00:08:40.110 He was playing around with this thing called ObjectSpace in an IRB session. I asked him, 'What's that? That looks interesting.' He said, 'It's a way to interact with all of the live objects within a Ruby session. For example, if you type in ObjectSpace.count_objects and pass in the symbol T_CLASS, you can count all the live class objects you have in that Ruby session.'
00:09:01.800 I thought, okay, it’s probably time for a break. Let me have a play by myself in IRB. I went into IRB and did what he showed me—I counted all the class objects. I had 936. So I thought, 'Okay, let me create a class and see if that number goes up.'
00:09:20.210 I created a new class. '937! 938! What? Okay, let me try again... 940!' What a mystery! I didn’t have much time to think about this because just then, my phone rang. It was my friend wondering where I was!
00:09:36.640 You see, I had completely forgotten that I agreed to go to this tech lecture with her. I was anxious because I didn’t have much time to solve Mike's case. I had been cancelling on her lately, putting my investigative duties first, but I thought that maybe this time I should make a little time for her.
00:10:01.350 So I rushed out the door. Chapter three. I arrived just in time for the beginning of the lecture, which was about a language called Smalltalk. I wasn't interested at all—I only have space in my heart for Ruby! However, I was there for my friend.
00:10:18.320 The lecture discussed how Smalltalk was born in the 1970s and led to the creation of object-oriented programming. However, I couldn't concentrate, as I kept thinking about my play with ObjectSpace. Each time I created one class, it seemed like two objects were being created.
00:10:36.000 My friend interrupted my thoughts. 'Pay attention!' she said. I looked up to see the lecturer asking the room, 'What is the class of a class in Smalltalk?' She had some code on the screen. I hadn't been following, but I could see that she printed out a polygon object, then asked the system what the class of the polygon object was.
00:10:55.480 It had returned PolygonClass! Then she typed in PolygonClass.class, and it printed out MetaClass. She said, 'The class of a class in Smalltalk is called a MetaClass.' Then she continued, mentioning that all languages inspired by Smalltalk have their concept of MetaClasses, including Objective C, Java, Python, and Ruby.
00:11:07.600 Suddenly, something clicked! One class too many! I made my apologies to my soon-to-be ex-friend and rushed out the door, back home. 'Okay, let me try my luck,' I thought. Of course, that would be too easy. I said to myself, 'Let me look at all the methods that exist on Cake.'
00:11:26.320 I found a very overwhelming list, so I thought, 'How could I filter it down?' Let me look at all the methods that include the word class. This list was way more manageable, and two methods stood out: superclass and singleton class.
00:11:38.450 I reminded myself of the ancestry tree for Cake. I told myself that the edible method already didn’t live on any of these classes or modules. I thought, 'Let's see what superclass gives me.' Object! I immediately knew superclass was not what I was looking for.
00:11:48.880 Object was in Cake's class ancestors, so it was time to try singleton class. This was new! I hadn't seen this before. It looked rather strange, but when I looked at the ancestry tree, I found three new classes that I hadn't seen before. This looked intriguing.
00:12:08.160 So, I thought, 'Why don’t I go back to my where method? Instead of searching the ancestors of the object's class, why don’t I search the ancestors of an object's singleton class?' Now it was time to give it a go.
00:12:30.550 I was excited, but in my enthusiasm, I forgot how to type. I eventually confirmed that the edible method lived on Cake's singleton class. Case closed.
00:12:46.370 Chapter four. At this point, I was delighted! I was so happy and couldn’t wait to share the news with Mike. I also took a moment to consider whether I should retire because this would prove to be one of the biggest successes of my career. They always say you should quit while you’re ahead.
00:13:03.470 But I pushed those thoughts aside, picked up the phone, and called Mike. The phone rang forever, and I was about to lose hope when he finally answered. I said, 'Hey, can I come round? I’ve solved the case!'
00:13:20.240 He was excited. 'Of course! Come round! I'll have the notes ready and waiting for you.' But although the case was solved for Mike, I wasn't satisfied because I had just discovered a whole new concept in the language that I hadn't heard of before: singleton classes.
00:13:36.910 So instead of going directly to Mike's, I took a detour to a friend of mine named Ellen. She was 43 years old, a freelance developer who regularly contributed to the Ruby codebase. I proceeded to tell her all about the case.
00:13:55.320 When I was done, I asked her, 'What is this singleton class all about?' She said it was a hidden class created internally behind the scenes in Ruby, used to hold methods defined only for a particular object. For example, if you took your instance of Cake, its singleton class would hold methods specific to Cake only and not any other instance.
00:14:13.640 'Since they work behind the scenes, when does knowing about them become useful?' I asked. Ellen thought for a while and then told me about one of her recent clients, Budgeting Inc.
00:14:32.230 They were a clever artificial intelligence machine learning personal finance tool for small business owners who were expanding globally. They needed to roll out slightly unique versions of their software for each new city they entered. Ellen explained that when she first looked at the codebase, she was horrified.
00:14:48.480 Different developers had been responsible for each new city, and it looked like they had been experimenting with different approaches. There was a lot of duplication in the codebase, some of which was obvious, and other aspects were hidden beneath the surface. This led to wasted time on development.
00:15:08.480 The developers were unhappy due to tiresome repetitive work, and the cognitive load was really high. The product owner was also disappointed because delivery was either very slow or things were spun up quickly and filled with bugs.
00:15:29.250 Ellen wasn’t sure what to do, and then she said, 'I decided to create a DSL—a domain-specific language.' She asked me if I knew what she was talking about. 'It's a mystery,' I replied, 'and you're going to have to explain!'
00:15:49.120 Ellen invited me over to her computer, where she opened up Pry. She defined a basic class for me called City. It had a class method called construct that took a block, setting up a variable called city which would match up with the initialized variable via the new keyword.
00:16:08.720 We created an instance of Valence City by passing in the block we passed to the construct method, returning the city object. There was also an attribute reader called taxes, with an initializes method that set a taxes instance variable as an empty array.
00:16:27.160 We had a method called tax that took an argument called name, which pushed that tax into the taxes collection. Ellen created a city instance called New York, added a couple of taxes, and printed them out.
00:16:44.620 There we had it—a very simple DSL! She emphasized that this way, we could quickly spin up these lightweight city objects. Imagine if we had other properties, like a list of banks in a city or finance schemes.
00:17:05.180 Imagine if we had more information on these properties. Instead of just a tax name, for example, there could be information about thresholds or different rates. Using this simple framework as a starting point, it wouldn’t be hard to extend the city instance class to create more complex city objects.
00:17:22.870 Now, imagine taking this to the next level. We could interact with the city instance class in the command line but instead of just creating variables in our Pry session, we’d be spinning up new subclasses of City and other related models for each tax scheme.
00:17:45.780 Ellen explained that this was the sort of thing she had created for Budgeting Inc—a DSL that allowed quick, easy scaffolding for each new city subclass and any other related classes. I asked her, 'Okay, I’ve seen how to spin up identical city instances. But what if we had one city with a quirk?'
00:18:02.260 She asked me to think of a place in the UK, and I suggested Bath—after all, that was the scene of my first-ever case! She said, 'Okay, let’s take Bath and add a tax. Can you think of something that makes people in the UK really unhappy?'
00:18:22.780 When I suggested something controversial, she sighed. Instead, she said we could focus on how it rains all the time in the UK. Let's say the local government feels sorry for the people and clears all the taxes when it rains, calling it a 'rainy day amnesty.'
00:18:45.030 Now, we looked at the Bath taxes, and when it rained, the government set up this amnesty. What would we look at next? The taxes again. Suddenly, we had an afternoon tea tax appearing!
00:19:01.300 Ellen's friends in New York wanted some of this rainy day amnesty too! The government tried to emulate it there, but faced an error: 'undefined method rainy_day for City instance.' There were these interesting characters behind it.
00:19:19.320 Ellen then said, 'What are these interesting characters?' Let me show you something! I noticed that when she called the singleton class of New York, the exact same class object was returned.
00:19:37.370 Ellen explained that when we enter the realm of DSLs, we're calling methods like instance_eval, and by doing this, we're leveraging the existence of singleton classes. What instance_eval does is it stores any method declarations that we pass into the class via the block argument or to an object's singleton class.
00:19:56.840 Taking a step back to the high level, Ellen said, 'By creating a DSL for Budgeting Inc, I enabled developers to spin up each new city instance effortlessly.' She added how she abstracted the key similarities between any city, providing developers with a frictionless way of getting the foundation they needed.
00:20:15.780 The code was much better maintained because all scaffolding had been tested once—and tested by Ellen. Developers no longer had to touch the code unless changes were needed. They could focus on interesting parts, the required customization for each new city.
00:20:34.380 The scope was now refined, leaving the developers happy as interacting with the system became a joy. They had a high-level overview by just looking at the DSL documentation. Meanwhile, the product owner was satisfied because there were far fewer bugs, delivery was faster, and she could communicate in the same terms as the developers.
00:20:55.480 Returning to singleton classes, I asked Ellen, 'Why is knowing about them useful?' She explained, 'One reason I could complete this project to a high standard was because I understood where I was defining methods at any given time.'
00:21:11.370 Once you enter the realm of dynamically creating classes, methods, and singletons, the class hierarchy and method lookup become much more interesting. You might encounter error messages, and identifying where singleton methods and classes are involved can save you a lot of headaches.
00:21:29.980 'But beware,' Ellen warned. 'I've been going on about DSLs, but they’re not the solution to everything. If you have repeated business rules that need specific customizations, consider DSLs—but approach with caution.'
00:21:48.920 She added that you don’t even have to be creating DSLs for it to be beneficial to understand how they work. 'You use Rails, right? You use it every day. Well then, you're using DSLs every day!'
00:22:07.889 Ellen told me that when you run migrations in Rails, like create_table, you specify what each column is—this is a DSL. The same goes for how your Rails app handles HTTP requests—this is also done via DSL.
00:22:25.470 Ellen explained that when people talk about Rails magic, it’s not magic at all but a collection of well-written DSLs! She looked at me and said, 'I hope you're eating all of that up.' I replied, 'Of course, Ellen! What do you take me for?'
00:22:42.080 She replied, 'Good because callbacks, context, blocks, those are all DSLs as well.' Knowing about singleton classes can help as you might find yourself in a tricky situation that you can't comprehend, especially if you inherit a codebase.
00:23:00.090 If you encounter a strange bug related to methods, you never know—singleton classes might be the answer. Having this understanding as part of your debugging tools can be incredibly useful.
00:23:16.180 At this point, I was feeling quite leveled-up by the end of this conversation. Armed with this newfound knowledge, I headed over to Mike's. But when I arrived, I found Mike with tears in his eyes. He had obviously been crying.
00:23:34.160 He raised his arms towards me, offering me crumpled papers that looked exactly like Jenny's method lookup notes from the day before. I took the notes and looked through them, finding nothing wrong—they looked exactly the same.
00:23:50.720 There was the carrot object with its class label pointing to the cake object, which also had its class label and methods label, including tasty and edible. But wait! This time, it didn't say 'Class.' This time it said 'Cake's Singleton'.
00:24:05.680 As Mike noticed this difference, he fell to his knees and broke down in tears. Jenny had known about singleton classes all along! He had gone into her room to find the notes before my arrival. This was the copy of the notes he found.
00:24:16.390 Jenny was so desperate to secure the job for herself that she set out to intentionally mislead Mike, hoping he would fail a whole section of the interview and look unprepared.
00:24:30.680 I, for one, was disappointed in myself. I had been so focused on the main villain, the shady mastermind, that I failed to spot the villain right under my nose—my best friend was trying to sabotage me!
00:24:47.350 Mike cried, starting to wail again. 'I'm not going to go to the Ruby interview tomorrow!' I said, 'Nonsense! You cannot give up now!' I stepped closer to him, gave him a comforting pat on the shoulder, and said, 'You can do this! I have just the thing to help set you apart from Jenny.'
00:25:00.350 He looked up, hopeful. 'Have you heard of DSLs?' I asked him and proceeded to share everything that Ellen had just told me. Although Mike still looked devastated as I left him, I had confidence that I inspired him with the power of singleton classes.
00:25:14.820 I believed he would pull himself together and secure the Ruby internship for himself.
00:25:27.710 Two months later, I managed to drag myself out of the house to attend a hack night. I was standing around, enjoying the free food and drink when I overheard some people whispering in the corner.
00:25:40.550 'Who’s Ruby famous?' one of them asked. I looked across the room and who did I see? Ellen!
00:25:56.750 I walked over, and as we caught up, I told her that I had been reflecting on the case of the missing method. I shared my takeaways. She nodded and said, 'You know singleton classes hold methods defined for one particular object, and understanding them opens up a whole world of Ruby.'
00:26:15.800 When you're dynamically creating classes and methods on the fly in more complex applications, understanding this is really useful. But I told her, 'I still feel as if day-to-day, I can get by ignoring singleton classes.'
00:26:33.030 Ellen replied, 'Well, like you say, if you’re writing DSLs, then definitely yes—know what you’re doing! They underpin popular frameworks like Rails.' But she also added, 'Day-to-day, you can get by ignoring them.
00:26:49.410 Understanding why singleton classes are there is super interesting and empowering. Take the Ruby core team: they wanted to keep things straightforward and simple as possible. By 'simple' I mean they sought to minimize special cases.'
00:27:03.040 They aimed to have one way, one pattern for explaining how things work in the language. They asked themselves, 'How consistent can we get things?'
00:27:21.180 In the realm of Ruby methods, if you think about it, all methods are defined in one of two places—a normal class object or a singleton class. Every method in Ruby is an instance method; method lookup always starts with a singleton class.
00:27:38.650 Class methods don't have fundamentally different behavior from other methods. What we call class methods are really instance methods, and the method is stored on the class's singleton class.
00:27:55.540 So yeah, singleton classes may be invisible, but they’re everywhere. They're a fundamental part of how Ruby and its method lookup work.
00:28:10.640 I left the hack night deep in thought. Ellen had inspired me to explore more about Ruby behind the scenes because there was so much I didn’t know. I'd only scratched the surface, and although I'd always been a general Ruby PI and found success, I considered finding a niche to reach the next level.