[{"data":1,"prerenderedAt":7618},["ShallowReactive",2],{"blogs":3,"blog-the_spaced_odyssey":7502},[4,220,1097,4101,5435,6844],{"id":5,"title":6,"body":7,"date":207,"description":208,"extension":209,"meta":210,"navigation":211,"path":212,"seo":213,"stem":214,"tags":215,"__hash__":219},"blogs/blogs/the_spaced_odyssey.md","The Space-d Odyssey",{"type":8,"value":9,"toc":199},"minimark",[10,15,28,31,45,48,57,66,70,80,83,87,108,128,131,149,158,161,164,167,170,179,189,196],[11,12,14],"h3",{"id":13},"me-movies-series-and-foreign-languages","Me, Movies, Series, and Foreign Languages",[16,17,18,19,27],"p",{},"I used to watch a lot of English movies and series throughout my childhood, and that’s what primarily helped me learn the language (My first language is ",[20,21,26],"a",{":target":22,"href":23,"rel":24},"\\_blank","https://en.wikipedia.org/wiki/Tamil_language",[25],"nofollow","Tamil / தமிழ்",").",[16,29,30],{},"But then about the start of my college days, I started watching Japanese anime with English subtitles. Fast forward to one and a half years ago, and more than 50% of the stuff in my watch history and watchlist was anime. But I still didn't know enough Japanese to read a sentence or watch the anime without English subtitles. That's when and why I started the Japanese course on Duolingo.",[16,32,33,34,38,39,44],{},"Duolingo was a really good place to start practising a new language with its game-like theme and constant push to maintain a streak. I was making steady progress, learned hiragana and katakana quickly, practiced around 1000 words and 100 kanji. Even though the progress is slow and mostly about words (",[35,36,37],"em",{},"not enough grammar","), it was good and the learning is consistent. But then, they ",[20,40,43],{":target":22,"href":41,"rel":42},"https://www.reddit.com/r/Duolingo/comments/1s4d3qn/heads_up_round_2_increased_rollout_to_rebuilt/",[25],"updated their course",".",[16,46,47],{},"A lot of words and kanji that I didn’t know suddenly started appearing in my regular practice sessions as if I had already learned them, which made it a lot less fun. There was a lot of criticism about it on the internet but they didn't provide any way to roll back to the previous progress. That's when my daily progress went haywire.",[16,49,50,51,56],{},"Even before the course update, I was already looking for a companion app along with Duolingo to learn more kanji characters and grammar. That's how I learned about the Anki flashcards and ",[20,52,55],{":target":22,"href":53,"rel":54},"https://en.wikipedia.org/wiki/Spaced_repetition",[25],"spaced repetition",". It is an amazing concept, and I was fully intrigued by it. At the same time, I felt bad that I hadn’t discovered it sooner. Anki flashcards also fully utilize the concept but it just didn't sit right with me. Mainly because I didn't want to use a decade old User interface and was looking for more of a companion app along with my Duolingo progress instead of a do-over.",[16,58,59,60,65],{},"But their course update finally made me build one myself and I created ",[20,61,64],{":target":22,"href":62,"rel":63},"https://rebmemer.onrender.com",[25],"Rebmemer",", a mobile-centric PWA for learning new words and kanji with spaced repetition that works completely offline with the best UI I could cook up, where data can be easily imported and exported.",[11,67,69],{"id":68},"tldr","TL;DR",[16,71,72,73,76,77,44],{},"Started learning Japanese using Duolingo after watching dozens of anime, they updated their course to gaslight me with new words instead of teaching them. Learned about ",[20,74,55],{":target":22,"href":53,"rel":75},[25]," but decided hell nah for Anki UI. Created my own offline PWA for spaced repetition named ",[20,78,64],{":target":22,"href":62,"rel":79},[25],[81,82],"hr",{},[11,84,86],{"id":85},"odyssey-to-rebmemer","Odyssey to Rebmemer",[16,88,89,90,95,96,101,102,107],{},"Rebmemer, named after a spell used in the movie ",[20,91,94],{":target":22,"href":92,"rel":93},"https://www.imdb.com/title/tt11079148/",[25],"Justice League Dark: Apokolips War",", is built with Nuxt.js and ",[20,97,100],{":target":22,"href":98,"rel":99},"https://dexie.org/",[25],"Dexie"," for accessing the IndexedDB. I've built a similar offline PWA in the past, named ",[20,103,106],{":target":22,"href":104,"rel":105},"https://local-ledger.onrender.com",[25],"Local Ledger",", for expense tracking so most of the things were easy this time.",[16,109,110,111,115,116,119,120,123,124,127],{},"I wanted an app that was fully configurable and offline, with one type of input to ",[112,113,114],"strong",{},"type"," words and another type of input (",[35,117,118],{},"like canvas",") to ",[112,121,122],{},"draw"," them (",[35,125,126],{},"for kanji characters","). The drawing feature was one of the things I felt missing by default in Anki flashcards, but in their defense Anki is a general app and most of the language and flash cards might not need a drawable canvas. But I needed it badly.",[16,129,130],{},"After coming up with the list of features, I started my design in Figma. During that time, Google's Stitch AI was getting its hype and I explored it a bit, but the design it munched up was not what I had in mind. I felt more productive designing what I had in mind with Figma than prompting it to Stitch. So, I continued the design in Figma.",[16,132,133,134,139,140,145,146,27],{},"After completing the wireframe model, I had to choose the color palette. This is always the hardest part of designing for me. Usually, I choose one or a pair of similar palettes from website like ",[20,135,138],{":target":22,"href":136,"rel":137},"https://colorhunt.co",[25],"ColorHunt.co",". But this time, I took a different path and used a color palette inspired by the ",[20,141,144],{":target":22,"href":142,"rel":143},"https://brawlhalla.fandom.com/wiki/Soul_Fire",[25],"Soul Fire"," skins from the Brawlhalla game (",[35,147,148],{},"See! good things do come from playing games like a madlad",[16,150,151,152,157],{},"After that, features, designs, colors and fonts were all ready. The only thing left to do was coding. But it was the time of Use-AI-to-Code-Or-Be-Treated-Like-Caveman. I had already started using Agentic AI with ",[20,153,156],{":target":22,"href":154,"rel":155},"https://zed.dev/",[25],"Zed"," at work. So, I swallowed a hard pill and decided to code with AI.",[16,159,160],{},"But the reality was much different. At work, the AI was paid by my organization for me to use it. But I cannot legally use it for my personal projects. So I wanted an AI which is free and can communicate with my code editor. Among the big three, GPT and Claude models cannot be used without a paid API key. Google, being generous, gives Gemini's API keys for free with limitations. But the credits that come with the free plan would run out long before I could finish even the landing page.",[16,162,163],{},"Coding with AI isn’t really an option for a developer trying to build a project at no cost. So, I started building it the same way I’ve built my projects for the last five years: on my own. But that doesn't mean I didn't use AI at all.",[16,165,166],{},"A project can easily be broken down into multiple different problems or to-do lists. Some of them may be new to you. Some of them you may already know how to solve. But then there are the problems you know how to solve, yet don’t want to work on because they’re boring and you’re lazy (like a text truncation function or a function to read and parse the contents of an input file).",[16,168,169],{},"For those kinds of problems, I used to grab ready-to-paste solutions from Stack Overflow. Now I ask the free versions of the big three AIs to generate them instead, because they're probably trained on the same Stack Overflow answers anyway.",[16,171,172,173,178],{},"Apart from that, I coded everything that I could enjoy. For most of the problems that came while developing this app, I already knew the solutions from the experience of my previous app and I've easily coded them. A couple of problems that were new to me were, using the Canvas API to create a drawing board and implementing the spaced repetition algorithm. AI helped me figure out those areas as well. Finding the right formula for spaced repetition took more prompting than I could accept, but t was necessary because it forms the core functionality of the app. The formula is documented on the ",[20,174,177],{":target":22,"href":175,"rel":176},"https://github.com/ruby-ist/rebmemer",[25],"README"," page of the project.",[16,180,181,182,185,186,27],{},"Now about the functionality of the app, it is very much like Anki but with an upgraded UI. You'll first create a deck with a name, description, and image (",[35,183,184],{},"image can also be stored in indexedDB!!","). You can configure the learning settings if you want or leave it as it is. Then you can bulk import new cards or add them one by one as you learn them and start practising them. There's also an option to reverse the deck and you can practice them in reverse (",[35,187,188],{},"like finding the question from answer",[16,190,191,192,195],{},"For me, I crawled all the words I've learned from Duolingo, parsed them, and imported them to the app as cards with zero familiarity (",[35,193,194],{},"as newly learned cards","). It would take some practice to catch up on them but that is fine for me.",[16,197,198],{},"This was my journey of developing a new project in the AI era and building something to learn a new language. I feel fulfilled knowing that it’s helping me learn and practice new things every day.",{"title":200,"searchDepth":201,"depth":201,"links":202},"",2,[203,205,206],{"id":13,"depth":204,"text":14},3,{"id":68,"depth":204,"text":69},{"id":85,"depth":204,"text":86},"2026-06-08","Journey of creating spaced repetition in mobile first offline PWA","md",{},true,"/blogs/the_spaced_odyssey",{"title":6,"description":208},"blogs/the_spaced_odyssey",[216,217,218],"vue.js","nuxt.js","PWA","cHr7Vro-55h-IwFBEXlEkcQzHMHyjbmSnqjtZ81dlxk",{"id":221,"title":222,"body":223,"date":1087,"description":1088,"extension":209,"meta":1089,"navigation":211,"path":1090,"seo":1091,"stem":1092,"tags":1093,"__hash__":1096},"blogs/blogs/now_you_inspect_me.md","Now You Inspect Me",{"type":8,"value":224,"toc":1084},[225,240,253,260,360,363,372,375,378,399,405,408,425,432,441,571,574,592,598,601,604,712,732,738,741,744,772,779,781,785,791,853,856,859,938,952,962,973,979,985,988,994,1033,1047,1053,1056,1059,1065,1071,1080],[16,226,227,228,232,233,236,237,44],{},"The ",[229,230,231],"code",{},"#inspect"," method in Ruby is used to customize how an object is displayed in the console or logged. It is commonly used by debugging tools, console output, and methods like ",[229,234,235],{},"#p"," and ",[229,238,239],{},"#pp",[241,242,244],"prose-blockquote",{"type":243},"note",[16,245,246,247,249,250,44],{},"Yes, we really do have a method named ",[229,248,239],{},", short for ",[112,251,252],{},"Pretty Print",[16,254,255,256,259],{},"Consider a ",[229,257,258],{},"Note"," class:",[261,262,267],"pre",{"className":263,"code":264,"filename":265,"language":266,"meta":200,"style":200},"language-ruby shiki shiki-themes github-light github-dark","class Note\n  def initialize(content)\n    @content = content\n    @created_at = Time.now\n  end\nend\n\nNote.new(\"My First Note!\")\n","IRB console","ruby",[229,268,269,282,294,305,322,328,334,340],{"__ignoreMap":200},[270,271,274,278],"span",{"class":272,"line":273},"line",1,[270,275,277],{"class":276},"szBVR","class",[270,279,281],{"class":280},"sScJk"," Note\n",[270,283,284,287,290],{"class":272,"line":201},[270,285,286],{"class":276},"  def",[270,288,289],{"class":280}," initialize",[270,291,293],{"class":292},"sVt8B","(content)\n",[270,295,296,299,302],{"class":272,"line":204},[270,297,298],{"class":292},"    @content ",[270,300,301],{"class":276},"=",[270,303,304],{"class":292}," content\n",[270,306,308,311,313,317,319],{"class":272,"line":307},4,[270,309,310],{"class":292},"    @created_at ",[270,312,301],{"class":276},[270,314,316],{"class":315},"sj4cs"," Time",[270,318,44],{"class":292},[270,320,321],{"class":280},"now\n",[270,323,325],{"class":272,"line":324},5,[270,326,327],{"class":276},"  end\n",[270,329,331],{"class":272,"line":330},6,[270,332,333],{"class":276},"end\n",[270,335,337],{"class":272,"line":336},7,[270,338,339],{"emptyLinePlaceholder":211},"\n",[270,341,343,345,347,350,353,357],{"class":272,"line":342},8,[270,344,258],{"class":315},[270,346,44],{"class":292},[270,348,349],{"class":276},"new",[270,351,352],{"class":292},"(",[270,354,356],{"class":355},"sZZnC","\"My First Note!\"",[270,358,359],{"class":292},")\n",[16,361,362],{},"This is how it usually looks in the console:",[261,364,370],{"className":365,"code":367,"filename":368,"language":369,"meta":200},[366],"language-text","#\u003CNote:0x0000000123274b78 @content=\"My First Note!\", @created_at=2026-03-17 19:19:43.876385 +0530>\n","Output","text",[229,371,367],{"__ignoreMap":200},[16,373,374],{},"The output will have a class name, the object's encoded id and all the instance variables in it.",[16,376,377],{},"But when a class has more instance variables or its values are lengthy, the output can get messy.",[261,379,382],{"className":263,"code":380,"filename":381,"language":266,"meta":200,"style":200},"Note.new(\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\")\n","IRB Console",[229,383,384],{"__ignoreMap":200},[270,385,386,388,390,392,394,397],{"class":272,"line":273},[270,387,258],{"class":315},[270,389,44],{"class":292},[270,391,349],{"class":276},[270,393,352],{"class":292},[270,395,396],{"class":355},"\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"",[270,398,359],{"class":292},[261,400,403],{"className":401,"code":402,"filename":368,"language":369,"meta":200},[366],"#\u003CNote:0x00000001256b8980\n @content=\n  \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\",\n @created_at=2026-03-17 19:26:11.707125 +0530>\n",[229,404,402],{"__ignoreMap":200},[16,406,407],{},"In most scenarios, how an object gets displayed doesn't matter outside of the console unless you're logging it. But a proper display of values is useful during debugging.",[241,409,410],{"type":243},[16,411,412,413,418,419,421,422,424],{},"Ruby 4.0.0 introduced a new method ",[20,414,417],{"href":415,"rel":416},"https://github.com/ruby/ruby/pull/13555",[25],"#instance_variables_to_inspect"," to control which instance variables should be displayed by ",[229,420,231],{},". When you define that method, the default ",[229,423,231],{}," will automatically call it and only print those variables returned by it.",[16,426,427,428,431],{},"Let's say you can ",[35,429,430],{},"identify your note with starting content"," and you also want the created at time to be a bit more readable.",[16,433,434,435,437,438,440],{},"In that case, you can define the ",[229,436,231],{}," method in our ",[229,439,258],{}," class in the following way:",[261,442,444],{"className":263,"code":443,"filename":265,"language":266,"meta":200,"style":200},"require 'active_support/core_ext/string/filters'\n\nclass Note\n  def initialize(content)\n    @content = content\n    @created_at = Time.now\n  end\n\n  def inspect\n    \"\u003CNote \\\"#{@content.truncate(25)}\\\" @ #{@created_at.strftime('%a, %d %b %Y')}>\"\n  end\nend\n",[229,445,446,454,458,464,472,480,492,496,500,508,561,566],{"__ignoreMap":200},[270,447,448,451],{"class":272,"line":273},[270,449,450],{"class":276},"require",[270,452,453],{"class":355}," 'active_support/core_ext/string/filters'\n",[270,455,456],{"class":272,"line":201},[270,457,339],{"emptyLinePlaceholder":211},[270,459,460,462],{"class":272,"line":204},[270,461,277],{"class":276},[270,463,281],{"class":280},[270,465,466,468,470],{"class":272,"line":307},[270,467,286],{"class":276},[270,469,289],{"class":280},[270,471,293],{"class":292},[270,473,474,476,478],{"class":272,"line":324},[270,475,298],{"class":292},[270,477,301],{"class":276},[270,479,304],{"class":292},[270,481,482,484,486,488,490],{"class":272,"line":330},[270,483,310],{"class":292},[270,485,301],{"class":276},[270,487,316],{"class":315},[270,489,44],{"class":292},[270,491,321],{"class":280},[270,493,494],{"class":272,"line":336},[270,495,327],{"class":276},[270,497,498],{"class":272,"line":342},[270,499,339],{"emptyLinePlaceholder":211},[270,501,503,505],{"class":272,"line":502},9,[270,504,286],{"class":276},[270,506,507],{"class":280}," inspect\n",[270,509,511,514,517,520,523,525,528,530,533,536,538,541,543,546,548,551,553,556,558],{"class":272,"line":510},10,[270,512,513],{"class":355},"    \"\u003CNote ",[270,515,516],{"class":315},"\\\"",[270,518,519],{"class":355},"#{",[270,521,522],{"class":292},"@content",[270,524,44],{"class":355},[270,526,527],{"class":280},"truncate",[270,529,352],{"class":355},[270,531,532],{"class":315},"25",[270,534,535],{"class":355},")}",[270,537,516],{"class":315},[270,539,540],{"class":355}," @ ",[270,542,519],{"class":355},[270,544,545],{"class":292},"@created_at",[270,547,44],{"class":355},[270,549,550],{"class":280},"strftime",[270,552,352],{"class":355},[270,554,555],{"class":355},"'%a, %d %b %Y'",[270,557,535],{"class":355},[270,559,560],{"class":355},">\"\n",[270,562,564],{"class":272,"line":563},11,[270,565,327],{"class":276},[270,567,569],{"class":272,"line":568},12,[270,570,333],{"class":276},[16,572,573],{},"Now create a note with lengthy content.",[261,575,576],{"className":263,"code":380,"filename":381,"language":266,"meta":200,"style":200},[229,577,578],{"__ignoreMap":200},[270,579,580,582,584,586,588,590],{"class":272,"line":273},[270,581,258],{"class":315},[270,583,44],{"class":292},[270,585,349],{"class":276},[270,587,352],{"class":292},[270,589,396],{"class":355},[270,591,359],{"class":292},[261,593,596],{"className":594,"code":595,"filename":368,"language":369,"meta":200},[366],"\u003CNote: \"Lorem ipsum dolor sit ...\" @ Tue, 17 Mar 2026>\n",[229,597,595],{"__ignoreMap":200},[16,599,600],{},"Now this output is short, readable and has all the information you want, presented the way you want it.",[16,602,603],{},"You can go even farther with our previous example and try this:",[261,605,608],{"className":263,"code":606,"filename":607,"language":266,"meta":200,"style":200},"#...\n  def inspect\n    \u003C\u003C~STR\n    .___________________________.\n    |                           |\n    | #{@content.truncate(25).ljust(25)} |\n    |                           |\n    |        @ #{@created_at.strftime(\"%a, %d %b %Y\")} |\n    |___________________________|\n    STR\n  end\n#...\n","Code Snippet",[229,609,610,616,622,627,632,637,668,672,694,699,704,708],{"__ignoreMap":200},[270,611,612],{"class":272,"line":273},[270,613,615],{"class":614},"sJ8bj","#...\n",[270,617,618,620],{"class":272,"line":201},[270,619,286],{"class":276},[270,621,507],{"class":280},[270,623,624],{"class":272,"line":204},[270,625,626],{"class":355},"    \u003C\u003C~STR\n",[270,628,629],{"class":272,"line":307},[270,630,631],{"class":355},"    .___________________________.\n",[270,633,634],{"class":272,"line":324},[270,635,636],{"class":355},"    |                           |\n",[270,638,639,642,644,646,648,650,652,654,656,659,661,663,665],{"class":272,"line":330},[270,640,641],{"class":355},"    | ",[270,643,519],{"class":355},[270,645,522],{"class":292},[270,647,44],{"class":355},[270,649,527],{"class":280},[270,651,352],{"class":355},[270,653,532],{"class":315},[270,655,27],{"class":355},[270,657,658],{"class":280},"ljust",[270,660,352],{"class":355},[270,662,532],{"class":315},[270,664,535],{"class":355},[270,666,667],{"class":355}," |\n",[270,669,670],{"class":272,"line":336},[270,671,636],{"class":355},[270,673,674,677,679,681,683,685,687,690,692],{"class":272,"line":342},[270,675,676],{"class":355},"    |        @ ",[270,678,519],{"class":355},[270,680,545],{"class":292},[270,682,44],{"class":355},[270,684,550],{"class":280},[270,686,352],{"class":355},[270,688,689],{"class":355},"\"%a, %d %b %Y\"",[270,691,535],{"class":355},[270,693,667],{"class":355},[270,695,696],{"class":272,"line":502},[270,697,698],{"class":355},"    |___________________________|\n",[270,700,701],{"class":272,"line":510},[270,702,703],{"class":355},"    STR\n",[270,705,706],{"class":272,"line":563},[270,707,327],{"class":276},[270,709,710],{"class":272,"line":568},[270,711,615],{"class":614},[261,713,715],{"className":263,"code":714,"filename":381,"language":266,"meta":200,"style":200},"Note.new(\"Did I already wrote my second note?\")\n",[229,716,717],{"__ignoreMap":200},[270,718,719,721,723,725,727,730],{"class":272,"line":273},[270,720,258],{"class":315},[270,722,44],{"class":292},[270,724,349],{"class":276},[270,726,352],{"class":292},[270,728,729],{"class":355},"\"Did I already wrote my second note?\"",[270,731,359],{"class":292},[261,733,736],{"className":734,"code":735,"filename":368,"language":369,"meta":200},[366],".___________________________.\n|                           |\n| Did I already wrote my... |\n|                           |\n|        @ Tue, 17 Mar 2026 |\n|___________________________|\n",[229,737,735],{"__ignoreMap":200},[16,739,740],{},"Likewise, it can be customized based on your needs.",[16,742,743],{},"However, there are a few gotchas that you need to know:",[745,746,747,766],"ul",{},[748,749,750,751,754,755,758,759,762,763,765],"li",{},"If you call ",[229,752,753],{},"#puts"," or ",[229,756,757],{},"#print",", Ruby uses ",[229,760,761],{},"#to_s",", not ",[229,764,231],{},", so your custom formatting won’t be used.",[748,767,768,769,771],{},"If you're explicitly calling the ",[229,770,231],{}," method on the object, it will return it as a string (with visible opening and closing quotes, and escaped inner quotes). So in most cases, it is not meant to be called explicitly",[16,773,774,775,778],{},"The reason why I want to talk about this method or how I came to know about it, is that it plays a vital part in ",[229,776,777],{},"ActiveRecord","'s query chaining and console experience.",[81,780],{},[11,782,784],{"id":783},"activerecord-inspect-and-rails-console","ActiveRecord, Inspect and Rails Console",[16,786,787,788,790],{},"This is how you usually write a query with ",[229,789,777],{},":",[261,792,795],{"className":263,"code":793,"filename":794,"language":266,"meta":200,"style":200},"User.select(:id, :email).where(active: true).order(:created_at).limit(20)\n","users_controller.rb",[229,796,797],{"__ignoreMap":200},[270,798,799,802,804,807,809,812,815,818,820,823,825,828,831,833,836,838,841,843,846,848,851],{"class":272,"line":273},[270,800,801],{"class":315},"User",[270,803,44],{"class":292},[270,805,806],{"class":315},"select",[270,808,352],{"class":292},[270,810,811],{"class":315},":id",[270,813,814],{"class":292},", ",[270,816,817],{"class":315},":email",[270,819,27],{"class":292},[270,821,822],{"class":280},"where",[270,824,352],{"class":292},[270,826,827],{"class":315},"active:",[270,829,830],{"class":315}," true",[270,832,27],{"class":292},[270,834,835],{"class":280},"order",[270,837,352],{"class":292},[270,839,840],{"class":315},":created_at",[270,842,27],{"class":292},[270,844,845],{"class":280},"limit",[270,847,352],{"class":292},[270,849,850],{"class":315},"20",[270,852,359],{"class":292},[16,854,855],{},"Even though it involves chaining multiple methods, it is very convenient over raw SQL for most scenarios.",[16,857,858],{},"However, it’s not just convenient—it’s implemented using an interesting pattern. You can also write the above query in the following way:",[261,860,862],{"className":263,"code":861,"filename":794,"language":266,"meta":200,"style":200},"query = User.select(:id, :email)\nquery = query.where(active: true)\nquery = query.order(:created_at)\nquery.limit(20)\n",[229,863,864,890,909,925],{"__ignoreMap":200},[270,865,866,870,873,876,878,880,882,884,886,888],{"class":272,"line":273},[270,867,869],{"class":868},"s4XuR","query",[270,871,872],{"class":276}," =",[270,874,875],{"class":315}," User",[270,877,44],{"class":292},[270,879,806],{"class":315},[270,881,352],{"class":292},[270,883,811],{"class":315},[270,885,814],{"class":292},[270,887,817],{"class":315},[270,889,359],{"class":292},[270,891,892,894,896,899,901,903,905,907],{"class":272,"line":201},[270,893,869],{"class":868},[270,895,872],{"class":276},[270,897,898],{"class":292}," query.",[270,900,822],{"class":280},[270,902,352],{"class":292},[270,904,827],{"class":315},[270,906,830],{"class":315},[270,908,359],{"class":292},[270,910,911,913,915,917,919,921,923],{"class":272,"line":204},[270,912,869],{"class":868},[270,914,872],{"class":276},[270,916,898],{"class":292},[270,918,835],{"class":280},[270,920,352],{"class":292},[270,922,840],{"class":315},[270,924,359],{"class":292},[270,926,927,930,932,934,936],{"class":272,"line":307},[270,928,929],{"class":292},"query.",[270,931,845],{"class":280},[270,933,352],{"class":292},[270,935,850],{"class":315},[270,937,359],{"class":292},[16,939,940,941,943,944,947,948,951],{},"This is where ",[229,942,777],{},"'s lazy loading shines. Each time you call a query-building method, it will add the changes to the internal query and return ",[229,945,946],{},"self",", where self is an instance of ",[229,949,950],{},"ActiveRecord::Relation",". But it will not execute the built query.",[241,953,954],{"type":243},[16,955,956,957,959,960,44],{},"The returning ",[229,958,946],{}," part is the reason why you can chain methods and make long queries with the ",[229,961,777],{},[16,963,964,965,968,969,972],{},"The query is executed only when you call an ",[112,966,967],{},"enumerable method"," on the query or explicitly convert it to an array (",[229,970,971],{},"#to_a","). This is why most of the queries in a controller usually runs at rendering time. Because only during rendering, you have to iterate over the query result.",[16,974,975,976,978],{},"But if you execute the above lines of code in Rails console, you'll see that four different queries are executed. Because there is one more method that can cause the relation to execute the query, which is (as the title says) the ",[229,977,231],{}," method.",[16,980,981,982,984],{},"Why? Because of the console experience. Writing ",[229,983,777],{}," queries in Rails console feels almost like writing SQL in an SQL console.",[16,986,987],{},"In both scenarios, you are building up queries with conditions, selection, grouping, etc. And on hitting enter, you will get the query result in both.",[16,989,990,991,993],{},"Now imagine, you are getting something like this when you are writing ",[229,992,777],{}," query in Rails console.",[261,995,998],{"className":263,"code":996,"filename":997,"language":266,"meta":200,"style":200},"User.select(:id, :email).where(active: true)\n#\u003CActiveRecord::Relation ...>\n","Rails Console",[229,999,1000,1028],{"__ignoreMap":200},[270,1001,1002,1004,1006,1008,1010,1012,1014,1016,1018,1020,1022,1024,1026],{"class":272,"line":273},[270,1003,801],{"class":315},[270,1005,44],{"class":292},[270,1007,806],{"class":315},[270,1009,352],{"class":292},[270,1011,811],{"class":315},[270,1013,814],{"class":292},[270,1015,817],{"class":315},[270,1017,27],{"class":292},[270,1019,822],{"class":280},[270,1021,352],{"class":292},[270,1023,827],{"class":315},[270,1025,830],{"class":315},[270,1027,359],{"class":292},[270,1029,1030],{"class":272,"line":201},[270,1031,1032],{"class":614},"#\u003CActiveRecord::Relation ...>\n",[16,1034,1035,1036,1038,1039,1041,1042,1044,1045,44],{},"That’s not very helpful, right? If ",[229,1037,231],{}," is not firing the queries, this is what will happen. You have to call ",[229,1040,971],{}," or some enumerable method to get the final result. That is why ",[229,1043,231],{}," plays an important role in ",[229,1046,777],{},[1048,1049,1050],"blockquote",{},[16,1051,1052],{},"“Sometimes we are so used to getting the result that we forget to ask how.”",[16,1054,1055],{},"Getting the query result in console instantly is a good example for that. This won’t happen in Ruby files—it only happens in the console.",[16,1057,1058],{},"Some of you might have faced an error like this when you were working in console:",[261,1060,1063],{"className":1061,"code":1062,"filename":997,"language":369,"meta":200},[366],"Kernel Inspection failed for \u003Cobject>...\n",[229,1064,1062],{"__ignoreMap":200},[16,1066,1067,1068,1070],{},"That is because either the query is loaded but an error was raised during inspect or an error was raised even before loading the query. However, if it is the former, you may still be able to get the result by calling ",[229,1069,971],{}," on the relation.",[16,1072,1073,1074,1076,1077,1079],{},"In any case, ",[229,1075,777],{}," is a great example of why defining ",[229,1078,231],{}," for custom objects matters—it directly impacts how we debug, explore, and understand our data in real time.",[1081,1082,1083],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":200,"searchDepth":201,"depth":201,"links":1085},[1086],{"id":783,"depth":204,"text":784},"2026-03-29","Why does Rails console execute queries automatically? Learn how Ruby’s #inspect method works, how to customize it, and its role in Active Record.",{},"/blogs/now_you_inspect_me",{"title":222,"description":1088},"blogs/now_you_inspect_me",[266,1094,1095],"rails","inspect","_LCA_1v-K-MKFEJzSaSO_45ZndRG-l5APzjLjJKkyGE",{"id":1098,"title":1099,"body":1100,"date":4092,"description":4093,"extension":209,"meta":4094,"navigation":211,"path":4095,"seo":4096,"stem":4097,"tags":4098,"__hash__":4100},"blogs/blogs/the_fantastic_modules_and_how_to_use_them.md","The Fantastic Modules and How to Use Them",{"type":8,"value":1101,"toc":4085},[1102,1105,1108,1111,1137,1139,1142,1147,1168,1264,1375,1389,1395,1603,1612,1620,1623,1626,1635,1645,1655,1674,1685,1691,1850,2050,2073,2076,2081,2093,2135,2146,2175,2328,2338,2544,2547,2555,2565,2574,2581,2610,2613,3309,3320,3334,3341,3348,3491,3498,3501,3841,3847,3971,3981,3996,3999,4063,4069,4073,4076,4079,4082],[16,1103,1104],{},"Mixins are often misunderstood. Instead of serving as reusable containers for shared behavior and well-defined interfaces, they frequently end up as dumping grounds for logic tightly coupled to a single class.",[16,1106,1107],{},"Ruby has many built-in mixins that serve quite useful purposes. These modules are not only handy, but can also serve as examples of how to design a good mixin.",[16,1109,1110],{},"In this blog, I want to talk about a few such modules:",[745,1112,1113,1119,1125,1131],{},[748,1114,1115],{},[20,1116,1118],{"href":1117},"#forwardable","Forwardable",[748,1120,1121],{},[20,1122,1124],{"href":1123},"#comparable","Comparable",[748,1126,1127],{},[20,1128,1130],{"href":1129},"#enumerable","Enumerable",[748,1132,1133],{},[20,1134,1136],{"href":1135},"#observable","Observable",[81,1138],{},[11,1140,1118],{"id":1141},"forwardable",[16,1143,1144,1146],{},[229,1145,1118],{}," is useful when you want the interface on the composed object to be available on the composing object. Unlike other modules in this blog, it needs to be extended rather than included.",[16,1148,1149,1150,1153,1154,1159,1160,1163,1164,1167],{},"Consider an example of a ",[229,1151,1152],{},"Card"," representing a ",[20,1155,1158],{"href":1156,"rel":1157},"https://www.brawlhalla.com/",[25],"Brawlhalla"," character (",[35,1161,1162],{},"why? Because I play Brawlhalla like a mad lad!",") and ",[229,1165,1166],{},"Deck"," of those cards:",[261,1169,1172],{"className":263,"code":1170,"filename":1171,"language":266,"meta":200,"style":200},"class Card\n  attr_reader :name, :strength, :defense\n\n  def initialize(name:, strength:, defense:)\n    @name = name\n    @strength = strength\n    @defense = defense\n  end\nend\n","card.rb",[229,1173,1174,1181,1199,1203,1226,1236,1246,1256,1260],{"__ignoreMap":200},[270,1175,1176,1178],{"class":272,"line":273},[270,1177,277],{"class":276},[270,1179,1180],{"class":280}," Card\n",[270,1182,1183,1186,1189,1191,1194,1196],{"class":272,"line":201},[270,1184,1185],{"class":276},"  attr_reader",[270,1187,1188],{"class":315}," :name",[270,1190,814],{"class":292},[270,1192,1193],{"class":315},":strength",[270,1195,814],{"class":292},[270,1197,1198],{"class":315},":defense\n",[270,1200,1201],{"class":272,"line":204},[270,1202,339],{"emptyLinePlaceholder":211},[270,1204,1205,1207,1209,1211,1214,1216,1219,1221,1224],{"class":272,"line":307},[270,1206,286],{"class":276},[270,1208,289],{"class":280},[270,1210,352],{"class":292},[270,1212,1213],{"class":315},"name:",[270,1215,814],{"class":292},[270,1217,1218],{"class":315},"strength:",[270,1220,814],{"class":292},[270,1222,1223],{"class":315},"defense:",[270,1225,359],{"class":292},[270,1227,1228,1231,1233],{"class":272,"line":324},[270,1229,1230],{"class":292},"    @name ",[270,1232,301],{"class":276},[270,1234,1235],{"class":292}," name\n",[270,1237,1238,1241,1243],{"class":272,"line":330},[270,1239,1240],{"class":292},"    @strength ",[270,1242,301],{"class":276},[270,1244,1245],{"class":292}," strength\n",[270,1247,1248,1251,1253],{"class":272,"line":336},[270,1249,1250],{"class":292},"    @defense ",[270,1252,301],{"class":276},[270,1254,1255],{"class":292}," defense\n",[270,1257,1258],{"class":272,"line":342},[270,1259,327],{"class":276},[270,1261,1262],{"class":272,"line":502},[270,1263,333],{"class":276},[261,1265,1268],{"className":263,"code":1266,"filename":1267,"language":266,"meta":200,"style":200},"require_relative 'card'\n\nclass Deck\n  extend Forwardable\n\n  def_delegators :@cards, :each, :\u003C\u003C, :[], :[]=, :count, :sample\n\n  def initialize(*cards)\n    @cards = cards\n  end\nend\n","deck.rb",[229,1269,1270,1278,1282,1289,1297,1301,1339,1343,1357,1367,1371],{"__ignoreMap":200},[270,1271,1272,1275],{"class":272,"line":273},[270,1273,1274],{"class":276},"require_relative",[270,1276,1277],{"class":355}," 'card'\n",[270,1279,1280],{"class":272,"line":201},[270,1281,339],{"emptyLinePlaceholder":211},[270,1283,1284,1286],{"class":272,"line":204},[270,1285,277],{"class":276},[270,1287,1288],{"class":280}," Deck\n",[270,1290,1291,1294],{"class":272,"line":307},[270,1292,1293],{"class":276},"  extend",[270,1295,1296],{"class":315}," Forwardable\n",[270,1298,1299],{"class":272,"line":324},[270,1300,339],{"emptyLinePlaceholder":211},[270,1302,1303,1306,1309,1311,1314,1316,1319,1321,1324,1326,1329,1331,1334,1336],{"class":272,"line":330},[270,1304,1305],{"class":292},"  def_delegators ",[270,1307,1308],{"class":315},":@cards",[270,1310,814],{"class":292},[270,1312,1313],{"class":315},":each",[270,1315,814],{"class":292},[270,1317,1318],{"class":315},":\u003C\u003C",[270,1320,814],{"class":292},[270,1322,1323],{"class":315},":[]",[270,1325,814],{"class":292},[270,1327,1328],{"class":315},":[]=",[270,1330,814],{"class":292},[270,1332,1333],{"class":315},":count",[270,1335,814],{"class":292},[270,1337,1338],{"class":315},":sample\n",[270,1340,1341],{"class":272,"line":336},[270,1342,339],{"emptyLinePlaceholder":211},[270,1344,1345,1347,1349,1351,1354],{"class":272,"line":342},[270,1346,286],{"class":276},[270,1348,289],{"class":280},[270,1350,352],{"class":292},[270,1352,1353],{"class":276},"*",[270,1355,1356],{"class":292},"cards)\n",[270,1358,1359,1362,1364],{"class":272,"line":502},[270,1360,1361],{"class":292},"    @cards ",[270,1363,301],{"class":276},[270,1365,1366],{"class":292}," cards\n",[270,1368,1369],{"class":272,"line":510},[270,1370,327],{"class":276},[270,1372,1373],{"class":272,"line":563},[270,1374,333],{"class":276},[16,1376,1377,1378,1381,1382,1385,1386,1388],{},"Here we are ",[35,1379,1380],{},"delegating"," the methods that are expected on an array to the instance variable ",[229,1383,1384],{},"@cards",", which is an actual array, inside the ",[229,1387,1166],{}," object.",[16,1390,1391,1392,1394],{},"By doing so, we can call the delegated method directly on the ",[229,1393,1166],{}," object instead of chaining method calls.",[261,1396,1398],{"className":263,"code":1397,"filename":381,"language":266,"meta":200,"style":200},"require_relative 'deck'\n\ndeck = Deck.new\n\ndeck \u003C\u003C Card.new(name: 'Thatch', strength: 7, defense: 3)\ndeck \u003C\u003C Card.new(name: 'Scarlet', strength: 8, defense: 5)\ndeck \u003C\u003C Card.new(name: 'Diana', strength: 5, defense: 5)\n\npp deck.count                     #=> 3\npp deck[0]                        #=> \u003CCard:0x0000000125221a20 @defense=3, @name=\"Thatch\", @strength=7>\ndeck.each { print \"#{_1.name} \" } #=> Thatch Scarlet Diana\n",[229,1399,1400,1407,1411,1426,1430,1468,1503,1536,1540,1551,1565],{"__ignoreMap":200},[270,1401,1402,1404],{"class":272,"line":273},[270,1403,1274],{"class":276},[270,1405,1406],{"class":355}," 'deck'\n",[270,1408,1409],{"class":272,"line":201},[270,1410,339],{"emptyLinePlaceholder":211},[270,1412,1413,1416,1418,1421,1423],{"class":272,"line":204},[270,1414,1415],{"class":868},"deck",[270,1417,872],{"class":276},[270,1419,1420],{"class":315}," Deck",[270,1422,44],{"class":292},[270,1424,1425],{"class":276},"new\n",[270,1427,1428],{"class":272,"line":307},[270,1429,339],{"emptyLinePlaceholder":211},[270,1431,1432,1435,1438,1441,1443,1445,1447,1449,1452,1454,1456,1459,1461,1463,1466],{"class":272,"line":324},[270,1433,1434],{"class":292},"deck ",[270,1436,1437],{"class":276},"\u003C\u003C",[270,1439,1440],{"class":315}," Card",[270,1442,44],{"class":292},[270,1444,349],{"class":276},[270,1446,352],{"class":292},[270,1448,1213],{"class":315},[270,1450,1451],{"class":355}," 'Thatch'",[270,1453,814],{"class":292},[270,1455,1218],{"class":315},[270,1457,1458],{"class":315}," 7",[270,1460,814],{"class":292},[270,1462,1223],{"class":315},[270,1464,1465],{"class":315}," 3",[270,1467,359],{"class":292},[270,1469,1470,1472,1474,1476,1478,1480,1482,1484,1487,1489,1491,1494,1496,1498,1501],{"class":272,"line":330},[270,1471,1434],{"class":292},[270,1473,1437],{"class":276},[270,1475,1440],{"class":315},[270,1477,44],{"class":292},[270,1479,349],{"class":276},[270,1481,352],{"class":292},[270,1483,1213],{"class":315},[270,1485,1486],{"class":355}," 'Scarlet'",[270,1488,814],{"class":292},[270,1490,1218],{"class":315},[270,1492,1493],{"class":315}," 8",[270,1495,814],{"class":292},[270,1497,1223],{"class":315},[270,1499,1500],{"class":315}," 5",[270,1502,359],{"class":292},[270,1504,1505,1507,1509,1511,1513,1515,1517,1519,1522,1524,1526,1528,1530,1532,1534],{"class":272,"line":336},[270,1506,1434],{"class":292},[270,1508,1437],{"class":276},[270,1510,1440],{"class":315},[270,1512,44],{"class":292},[270,1514,349],{"class":276},[270,1516,352],{"class":292},[270,1518,1213],{"class":315},[270,1520,1521],{"class":355}," 'Diana'",[270,1523,814],{"class":292},[270,1525,1218],{"class":315},[270,1527,1500],{"class":315},[270,1529,814],{"class":292},[270,1531,1223],{"class":315},[270,1533,1500],{"class":315},[270,1535,359],{"class":292},[270,1537,1538],{"class":272,"line":342},[270,1539,339],{"emptyLinePlaceholder":211},[270,1541,1542,1545,1548],{"class":272,"line":502},[270,1543,1544],{"class":292},"pp deck.",[270,1546,1547],{"class":280},"count",[270,1549,1550],{"class":614},"                     #=> 3\n",[270,1552,1553,1556,1559,1562],{"class":272,"line":510},[270,1554,1555],{"class":292},"pp deck[",[270,1557,1558],{"class":315},"0",[270,1560,1561],{"class":292},"]                        ",[270,1563,1564],{"class":614},"#=> \u003CCard:0x0000000125221a20 @defense=3, @name=\"Thatch\", @strength=7>\n",[270,1566,1567,1570,1573,1576,1579,1582,1584,1587,1589,1592,1595,1597,1600],{"class":272,"line":563},[270,1568,1569],{"class":292},"deck.",[270,1571,1572],{"class":280},"each",[270,1574,1575],{"class":292}," { ",[270,1577,1578],{"class":315},"print",[270,1580,1581],{"class":355}," \"",[270,1583,519],{"class":355},[270,1585,1586],{"class":315},"_1",[270,1588,44],{"class":355},[270,1590,1591],{"class":280},"name",[270,1593,1594],{"class":355},"}",[270,1596,1581],{"class":355},[270,1598,1599],{"class":292}," } ",[270,1601,1602],{"class":614},"#=> Thatch Scarlet Diana\n",[241,1604,1605],{"type":243},[16,1606,1607,1608,1611],{},"The first argument of ",[229,1609,1610],{},"#def_delegators"," doesn't have to be an instance variable; it can also be any method name that returns an object to delegate to.",[16,1613,227,1614,1616,1617,1619],{},[229,1615,1118],{}," module contains methods (i.e. ",[229,1618,1610],{},", etc) that are meant to be invoked in the scope of a class. These kinds of extendable modules provide behaviors or class-level interfaces that help define an object's interface.",[11,1621,1124],{"id":1622},"comparable",[16,1624,1625],{},"Sometimes when you create a custom class (or custom data type in other languages), you might want to compare two objects of that class. For that, you have to write comparison logic for each operator.",[16,1627,1628,1629,1631,1632,44],{},"Ruby makes it easier with the ",[229,1630,1124],{}," module and the spaceship operator ",[229,1633,1634],{},"\u003C=>",[16,1636,1637,1638,1640,1641,1644],{},"When you include the ",[229,1639,1124],{}," module in a class and define a method for the ",[229,1642,1643],{},"spaceship"," operator, any two objects of that class can be compared with any comparison operator.",[16,1646,1647,1648,1650,1651,1654],{},"The result of the ",[229,1649,1643],{}," operator can be either one of three values: ",[229,1652,1653],{},"-1, 0, 1",". -1 means less than, 0 means equal, and 1 means greater than.",[241,1656,1657],{"type":243},[16,1658,1659,1660,1662,1663,1666,1667,1670,1671,1673],{},"When you define a ",[229,1661,1643],{}," operator for your class, it has to return an ",[229,1664,1665],{},"Integer"," value. Otherwise when you use a comparison operator on the object of that class, you will get an ",[229,1668,1669],{},"ArgumentError",". In Ruby, the ",[229,1672,1643],{}," operator has already been defined for built-in data types.",[16,1675,1676,1677,1680,1681,1684],{},"Coming back to our example, let's say you want to compare characters in the order of ",[229,1678,1679],{},"strength"," > ",[229,1682,1683],{},"defense"," (Not that it is how things work in the game, but let's compare the cards this way for the sake of an example).",[16,1686,1687,1688,1690],{},"We can modify our ",[229,1689,1152],{}," class so that it defines such comparison logic.",[261,1692,1694],{"className":263,"code":1693,"filename":1171,"language":266,"meta":200,"style":200},"class Card\n  include Comparable\n  attr_reader :name, :strength, :defense\n\n  def initialize(name:, strength:, defense:)\n    @name = name\n    @strength = strength\n    @defense = defense\n  end\n\n  def \u003C=>(other)\n    return strength \u003C=> other.strength unless (strength \u003C=> other.strength).zero?\n\n    defense \u003C=> other.defense\n  end\nend\n",[229,1695,1696,1702,1710,1724,1728,1748,1756,1764,1772,1776,1780,1790,1822,1827,1840,1845],{"__ignoreMap":200},[270,1697,1698,1700],{"class":272,"line":273},[270,1699,277],{"class":276},[270,1701,1180],{"class":280},[270,1703,1704,1707],{"class":272,"line":201},[270,1705,1706],{"class":276},"  include",[270,1708,1709],{"class":315}," Comparable\n",[270,1711,1712,1714,1716,1718,1720,1722],{"class":272,"line":204},[270,1713,1185],{"class":276},[270,1715,1188],{"class":315},[270,1717,814],{"class":292},[270,1719,1193],{"class":315},[270,1721,814],{"class":292},[270,1723,1198],{"class":315},[270,1725,1726],{"class":272,"line":307},[270,1727,339],{"emptyLinePlaceholder":211},[270,1729,1730,1732,1734,1736,1738,1740,1742,1744,1746],{"class":272,"line":324},[270,1731,286],{"class":276},[270,1733,289],{"class":280},[270,1735,352],{"class":292},[270,1737,1213],{"class":315},[270,1739,814],{"class":292},[270,1741,1218],{"class":315},[270,1743,814],{"class":292},[270,1745,1223],{"class":315},[270,1747,359],{"class":292},[270,1749,1750,1752,1754],{"class":272,"line":330},[270,1751,1230],{"class":292},[270,1753,301],{"class":276},[270,1755,1235],{"class":292},[270,1757,1758,1760,1762],{"class":272,"line":336},[270,1759,1240],{"class":292},[270,1761,301],{"class":276},[270,1763,1245],{"class":292},[270,1765,1766,1768,1770],{"class":272,"line":342},[270,1767,1250],{"class":292},[270,1769,301],{"class":276},[270,1771,1255],{"class":292},[270,1773,1774],{"class":272,"line":502},[270,1775,327],{"class":276},[270,1777,1778],{"class":272,"line":510},[270,1779,339],{"emptyLinePlaceholder":211},[270,1781,1782,1784,1787],{"class":272,"line":563},[270,1783,286],{"class":276},[270,1785,1786],{"class":280}," \u003C=>",[270,1788,1789],{"class":292},"(other)\n",[270,1791,1792,1795,1798,1800,1803,1805,1808,1811,1813,1815,1817,1819],{"class":272,"line":568},[270,1793,1794],{"class":276},"    return",[270,1796,1797],{"class":292}," strength ",[270,1799,1634],{"class":276},[270,1801,1802],{"class":292}," other.",[270,1804,1679],{"class":280},[270,1806,1807],{"class":276}," unless",[270,1809,1810],{"class":292}," (strength ",[270,1812,1634],{"class":276},[270,1814,1802],{"class":292},[270,1816,1679],{"class":280},[270,1818,27],{"class":292},[270,1820,1821],{"class":280},"zero?\n",[270,1823,1825],{"class":272,"line":1824},13,[270,1826,339],{"emptyLinePlaceholder":211},[270,1828,1830,1833,1835,1837],{"class":272,"line":1829},14,[270,1831,1832],{"class":292},"    defense ",[270,1834,1634],{"class":276},[270,1836,1802],{"class":292},[270,1838,1839],{"class":280},"defense\n",[270,1841,1843],{"class":272,"line":1842},15,[270,1844,327],{"class":276},[270,1846,1848],{"class":272,"line":1847},16,[270,1849,333],{"class":276},[261,1851,1853],{"className":263,"code":1852,"filename":381,"language":266,"meta":200,"style":200},"require_relative 'card'\n\nthatch = Card.new(name: 'Thatch', strength: 7, defense: 3)\nscarlet = Card.new(name: 'Scarlet', strength: 8, defense: 5)\ndiana = Card.new(name: 'Diana', strength: 5, defense: 5)\n\npp thatch \u003C scarlet   #=> true\npp thatch \u003C= scarlet  #=> true\npp thatch > scarlet   #=> false\npp thatch >= scarlet  #=> false\npp thatch == scarlet  #=> false\npp thatch == thatch   #=> true\npp thatch > diana     #=> true\n",[229,1854,1855,1861,1865,1898,1931,1964,1968,1982,1994,2006,2017,2028,2039],{"__ignoreMap":200},[270,1856,1857,1859],{"class":272,"line":273},[270,1858,1274],{"class":276},[270,1860,1277],{"class":355},[270,1862,1863],{"class":272,"line":201},[270,1864,339],{"emptyLinePlaceholder":211},[270,1866,1867,1870,1872,1874,1876,1878,1880,1882,1884,1886,1888,1890,1892,1894,1896],{"class":272,"line":204},[270,1868,1869],{"class":868},"thatch",[270,1871,872],{"class":276},[270,1873,1440],{"class":315},[270,1875,44],{"class":292},[270,1877,349],{"class":276},[270,1879,352],{"class":292},[270,1881,1213],{"class":315},[270,1883,1451],{"class":355},[270,1885,814],{"class":292},[270,1887,1218],{"class":315},[270,1889,1458],{"class":315},[270,1891,814],{"class":292},[270,1893,1223],{"class":315},[270,1895,1465],{"class":315},[270,1897,359],{"class":292},[270,1899,1900,1903,1905,1907,1909,1911,1913,1915,1917,1919,1921,1923,1925,1927,1929],{"class":272,"line":307},[270,1901,1902],{"class":868},"scarlet",[270,1904,872],{"class":276},[270,1906,1440],{"class":315},[270,1908,44],{"class":292},[270,1910,349],{"class":276},[270,1912,352],{"class":292},[270,1914,1213],{"class":315},[270,1916,1486],{"class":355},[270,1918,814],{"class":292},[270,1920,1218],{"class":315},[270,1922,1493],{"class":315},[270,1924,814],{"class":292},[270,1926,1223],{"class":315},[270,1928,1500],{"class":315},[270,1930,359],{"class":292},[270,1932,1933,1936,1938,1940,1942,1944,1946,1948,1950,1952,1954,1956,1958,1960,1962],{"class":272,"line":324},[270,1934,1935],{"class":868},"diana",[270,1937,872],{"class":276},[270,1939,1440],{"class":315},[270,1941,44],{"class":292},[270,1943,349],{"class":276},[270,1945,352],{"class":292},[270,1947,1213],{"class":315},[270,1949,1521],{"class":355},[270,1951,814],{"class":292},[270,1953,1218],{"class":315},[270,1955,1500],{"class":315},[270,1957,814],{"class":292},[270,1959,1223],{"class":315},[270,1961,1500],{"class":315},[270,1963,359],{"class":292},[270,1965,1966],{"class":272,"line":330},[270,1967,339],{"emptyLinePlaceholder":211},[270,1969,1970,1973,1976,1979],{"class":272,"line":336},[270,1971,1972],{"class":292},"pp thatch ",[270,1974,1975],{"class":276},"\u003C",[270,1977,1978],{"class":292}," scarlet   ",[270,1980,1981],{"class":614},"#=> true\n",[270,1983,1984,1986,1989,1992],{"class":272,"line":342},[270,1985,1972],{"class":292},[270,1987,1988],{"class":276},"\u003C=",[270,1990,1991],{"class":292}," scarlet  ",[270,1993,1981],{"class":614},[270,1995,1996,1998,2001,2003],{"class":272,"line":502},[270,1997,1972],{"class":292},[270,1999,2000],{"class":276},">",[270,2002,1978],{"class":292},[270,2004,2005],{"class":614},"#=> false\n",[270,2007,2008,2010,2013,2015],{"class":272,"line":510},[270,2009,1972],{"class":292},[270,2011,2012],{"class":276},">=",[270,2014,1991],{"class":292},[270,2016,2005],{"class":614},[270,2018,2019,2021,2024,2026],{"class":272,"line":563},[270,2020,1972],{"class":292},[270,2022,2023],{"class":276},"==",[270,2025,1991],{"class":292},[270,2027,2005],{"class":614},[270,2029,2030,2032,2034,2037],{"class":272,"line":568},[270,2031,1972],{"class":292},[270,2033,2023],{"class":276},[270,2035,2036],{"class":292}," thatch   ",[270,2038,1981],{"class":614},[270,2040,2041,2043,2045,2048],{"class":272,"line":1824},[270,2042,1972],{"class":292},[270,2044,2000],{"class":276},[270,2046,2047],{"class":292}," diana     ",[270,2049,1981],{"class":614},[16,2051,2052,2053,2055,2056,814,2058,814,2060,814,2062,814,2064,2066,2067,236,2070,44],{},"By defining a single ",[229,2054,1643],{}," operator, the module includes the definition for five different comparison operators (",[229,2057,1975],{},[229,2059,2000],{},[229,2061,2023],{},[229,2063,1988],{},[229,2065,2012],{},") and even throws in a couple of comparison helpers like ",[229,2068,2069],{},"#between?",[229,2071,2072],{},"#clamp",[11,2074,1130],{"id":2075},"enumerable",[16,2077,227,2078,2080],{},[229,2079,1130],{}," module is the most useful and my favorite one when it comes to collection classes. It contains all the helpful iterator methods that you would possibly want.",[16,2082,2083,2084,236,2087,2090,2091,44],{},"In fact, Ruby’s core collection types ",[229,2085,2086],{},"Array",[229,2088,2089],{},"Hash"," both include ",[229,2092,1130],{},[261,2094,2096],{"className":263,"code":2095,"filename":381,"language":266,"meta":200,"style":200},"Array.ancestors.include? Enumerable   #=> true\nHash.ancestors.include? Enumerable    #=> true\n",[229,2097,2098,2118],{"__ignoreMap":200},[270,2099,2100,2102,2104,2107,2109,2112,2115],{"class":272,"line":273},[270,2101,2086],{"class":315},[270,2103,44],{"class":292},[270,2105,2106],{"class":280},"ancestors",[270,2108,44],{"class":292},[270,2110,2111],{"class":280},"include?",[270,2113,2114],{"class":315}," Enumerable",[270,2116,2117],{"class":614},"   #=> true\n",[270,2119,2120,2122,2124,2126,2128,2130,2132],{"class":272,"line":201},[270,2121,2089],{"class":315},[270,2123,44],{"class":292},[270,2125,2106],{"class":280},[270,2127,44],{"class":292},[270,2129,2111],{"class":280},[270,2131,2114],{"class":315},[270,2133,2134],{"class":614},"    #=> true\n",[16,2136,2137,2138,2140,2141,44],{},"For more details on the methods provided by the ",[229,2139,1130],{}," module, take a look at the ",[20,2142,2145],{"href":2143,"rel":2144},"https://ruby-doc.org/core-3.0.1/Enumerable.html",[25],"official documentation",[16,2147,2148,2149,2151,2152,2155,2156,2159,2160,2162,2163,2165,2166,2168,2169,2171,2172,2174],{},"To use the ",[229,2150,1130],{}," module, all you have to do is define the ",[229,2153,2154],{},"#each"," method for your class and ",[229,2157,2158],{},"yield"," each element. In our ",[229,2161,1166],{}," class, we already have an ",[229,2164,2154],{}," method, which delegates to ",[229,2167,1384],{}," array. This would satisfy the requirement for the ",[229,2170,1130],{}," module, but let’s define ",[229,2173,2154],{}," ourselves anyway.",[261,2176,2178],{"className":263,"code":2177,"filename":1267,"language":266,"meta":200,"style":200},"require_relative 'card'\n\nclass Deck\n  extend Forwardable\n  include Enumerable\n\n  attr_reader :cards\n\n  def_delegators :cards, :\u003C\u003C, :[], :[]=, :count, :sample\n\n  def initialize(*cards)\n    @cards = cards\n  end\n\n  def each\n    cards.each do |item|\n      yield item\n    end\n  end\nend\n",[229,2179,2180,2186,2190,2196,2202,2209,2213,2220,2224,2251,2255,2267,2275,2279,2283,2290,2303,2312,2318,2323],{"__ignoreMap":200},[270,2181,2182,2184],{"class":272,"line":273},[270,2183,1274],{"class":276},[270,2185,1277],{"class":355},[270,2187,2188],{"class":272,"line":201},[270,2189,339],{"emptyLinePlaceholder":211},[270,2191,2192,2194],{"class":272,"line":204},[270,2193,277],{"class":276},[270,2195,1288],{"class":280},[270,2197,2198,2200],{"class":272,"line":307},[270,2199,1293],{"class":276},[270,2201,1296],{"class":315},[270,2203,2204,2206],{"class":272,"line":324},[270,2205,1706],{"class":276},[270,2207,2208],{"class":315}," Enumerable\n",[270,2210,2211],{"class":272,"line":330},[270,2212,339],{"emptyLinePlaceholder":211},[270,2214,2215,2217],{"class":272,"line":336},[270,2216,1185],{"class":276},[270,2218,2219],{"class":315}," :cards\n",[270,2221,2222],{"class":272,"line":342},[270,2223,339],{"emptyLinePlaceholder":211},[270,2225,2226,2228,2231,2233,2235,2237,2239,2241,2243,2245,2247,2249],{"class":272,"line":502},[270,2227,1305],{"class":292},[270,2229,2230],{"class":315},":cards",[270,2232,814],{"class":292},[270,2234,1318],{"class":315},[270,2236,814],{"class":292},[270,2238,1323],{"class":315},[270,2240,814],{"class":292},[270,2242,1328],{"class":315},[270,2244,814],{"class":292},[270,2246,1333],{"class":315},[270,2248,814],{"class":292},[270,2250,1338],{"class":315},[270,2252,2253],{"class":272,"line":510},[270,2254,339],{"emptyLinePlaceholder":211},[270,2256,2257,2259,2261,2263,2265],{"class":272,"line":563},[270,2258,286],{"class":276},[270,2260,289],{"class":280},[270,2262,352],{"class":292},[270,2264,1353],{"class":276},[270,2266,1356],{"class":292},[270,2268,2269,2271,2273],{"class":272,"line":568},[270,2270,1361],{"class":292},[270,2272,301],{"class":276},[270,2274,1366],{"class":292},[270,2276,2277],{"class":272,"line":1824},[270,2278,327],{"class":276},[270,2280,2281],{"class":272,"line":1829},[270,2282,339],{"emptyLinePlaceholder":211},[270,2284,2285,2287],{"class":272,"line":1842},[270,2286,286],{"class":276},[270,2288,2289],{"class":280}," each\n",[270,2291,2292,2295,2297,2300],{"class":272,"line":1847},[270,2293,2294],{"class":292},"    cards.",[270,2296,1572],{"class":280},[270,2298,2299],{"class":276}," do",[270,2301,2302],{"class":292}," |item|\n",[270,2304,2306,2309],{"class":272,"line":2305},17,[270,2307,2308],{"class":276},"      yield",[270,2310,2311],{"class":292}," item\n",[270,2313,2315],{"class":272,"line":2314},18,[270,2316,2317],{"class":276},"    end\n",[270,2319,2321],{"class":272,"line":2320},19,[270,2322,327],{"class":276},[270,2324,2326],{"class":272,"line":2325},20,[270,2327,333],{"class":276},[16,2329,2330,2331,2333,2334,754,2336,44],{},"And that's it. Now, the ",[229,2332,1166],{}," class will have all the enumerable methods that are available for ",[229,2335,2086],{},[229,2337,2089],{},[261,2339,2341],{"className":263,"code":2340,"filename":381,"language":266,"meta":200,"style":200},"require_relative 'deck'\n\ndeck = Deck.new\n\ndeck \u003C\u003C Card.new(name: 'Thatch', strength: 7, defense: 3)\ndeck \u003C\u003C Card.new(name: 'Scarlet', strength: 8, defense: 5)\ndeck \u003C\u003C Card.new(name: 'Diana', strength: 5, defense: 5)\n\npp deck.filter { _1.defense > 3  }.count  #=> 2\npp deck.map { _1.name }                   #=> [\"Thatch\", \"Scarlet\", \"Diana\"]\npp deck.find { _1.name == 'Thatch' }      #=> #\u003CCard:0x0000000124d7f918 @defense=3, @name=\"Thatch\", @strength=7>\n",[229,2342,2343,2349,2353,2365,2369,2401,2433,2465,2469,2497,2518],{"__ignoreMap":200},[270,2344,2345,2347],{"class":272,"line":273},[270,2346,1274],{"class":276},[270,2348,1406],{"class":355},[270,2350,2351],{"class":272,"line":201},[270,2352,339],{"emptyLinePlaceholder":211},[270,2354,2355,2357,2359,2361,2363],{"class":272,"line":204},[270,2356,1415],{"class":868},[270,2358,872],{"class":276},[270,2360,1420],{"class":315},[270,2362,44],{"class":292},[270,2364,1425],{"class":276},[270,2366,2367],{"class":272,"line":307},[270,2368,339],{"emptyLinePlaceholder":211},[270,2370,2371,2373,2375,2377,2379,2381,2383,2385,2387,2389,2391,2393,2395,2397,2399],{"class":272,"line":324},[270,2372,1434],{"class":292},[270,2374,1437],{"class":276},[270,2376,1440],{"class":315},[270,2378,44],{"class":292},[270,2380,349],{"class":276},[270,2382,352],{"class":292},[270,2384,1213],{"class":315},[270,2386,1451],{"class":355},[270,2388,814],{"class":292},[270,2390,1218],{"class":315},[270,2392,1458],{"class":315},[270,2394,814],{"class":292},[270,2396,1223],{"class":315},[270,2398,1465],{"class":315},[270,2400,359],{"class":292},[270,2402,2403,2405,2407,2409,2411,2413,2415,2417,2419,2421,2423,2425,2427,2429,2431],{"class":272,"line":330},[270,2404,1434],{"class":292},[270,2406,1437],{"class":276},[270,2408,1440],{"class":315},[270,2410,44],{"class":292},[270,2412,349],{"class":276},[270,2414,352],{"class":292},[270,2416,1213],{"class":315},[270,2418,1486],{"class":355},[270,2420,814],{"class":292},[270,2422,1218],{"class":315},[270,2424,1493],{"class":315},[270,2426,814],{"class":292},[270,2428,1223],{"class":315},[270,2430,1500],{"class":315},[270,2432,359],{"class":292},[270,2434,2435,2437,2439,2441,2443,2445,2447,2449,2451,2453,2455,2457,2459,2461,2463],{"class":272,"line":336},[270,2436,1434],{"class":292},[270,2438,1437],{"class":276},[270,2440,1440],{"class":315},[270,2442,44],{"class":292},[270,2444,349],{"class":276},[270,2446,352],{"class":292},[270,2448,1213],{"class":315},[270,2450,1521],{"class":355},[270,2452,814],{"class":292},[270,2454,1218],{"class":315},[270,2456,1500],{"class":315},[270,2458,814],{"class":292},[270,2460,1223],{"class":315},[270,2462,1500],{"class":315},[270,2464,359],{"class":292},[270,2466,2467],{"class":272,"line":342},[270,2468,339],{"emptyLinePlaceholder":211},[270,2470,2471,2473,2476,2478,2480,2482,2484,2487,2489,2492,2494],{"class":272,"line":502},[270,2472,1544],{"class":292},[270,2474,2475],{"class":280},"filter",[270,2477,1575],{"class":292},[270,2479,1586],{"class":315},[270,2481,44],{"class":292},[270,2483,1683],{"class":280},[270,2485,2486],{"class":276}," >",[270,2488,1465],{"class":315},[270,2490,2491],{"class":292},"  }.",[270,2493,1547],{"class":280},[270,2495,2496],{"class":614},"  #=> 2\n",[270,2498,2499,2501,2504,2506,2508,2510,2512,2515],{"class":272,"line":510},[270,2500,1544],{"class":292},[270,2502,2503],{"class":280},"map",[270,2505,1575],{"class":292},[270,2507,1586],{"class":315},[270,2509,44],{"class":292},[270,2511,1591],{"class":280},[270,2513,2514],{"class":292}," }                   ",[270,2516,2517],{"class":614},"#=> [\"Thatch\", \"Scarlet\", \"Diana\"]\n",[270,2519,2520,2522,2525,2527,2529,2531,2533,2536,2538,2541],{"class":272,"line":563},[270,2521,1544],{"class":292},[270,2523,2524],{"class":280},"find",[270,2526,1575],{"class":292},[270,2528,1586],{"class":315},[270,2530,44],{"class":292},[270,2532,1591],{"class":280},[270,2534,2535],{"class":276}," ==",[270,2537,1451],{"class":355},[270,2539,2540],{"class":292}," }      ",[270,2542,2543],{"class":614},"#=> #\u003CCard:0x0000000124d7f918 @defense=3, @name=\"Thatch\", @strength=7>\n",[11,2545,1136],{"id":2546},"observable",[16,2548,227,2549,2551,2552,44],{},[229,2550,1136],{}," module provides a convenient way to implement the ",[112,2553,2554],{},"Observer pattern",[16,2556,2557,2558,2561,2562,27],{},"It adds internal state to track whether the object has changed. Based on that state, it provides methods to register observers (",[229,2559,2560],{},"#add_observer",") and notify them (",[229,2563,2564],{},"#notify_observers",[16,2566,2148,2567,2569,2570,2573],{},[229,2568,1136],{}," module, you have to require ",[229,2571,2572],{},"observer"," in your file.",[16,2575,2576,2577,2580],{},"Now let's create a new observable class called ",[229,2578,2579],{},"Player"," with some game logic:",[745,2582,2583,2589,2596,2599,2607],{},[748,2584,2585,2586,2588],{},"Let's say a ",[229,2587,2579],{}," can be initialized with a name and deck of cards.",[748,2590,2591,2592,2595],{},"The player will have a single public interface ",[229,2593,2594],{},"brawl"," which takes another player as an argument.",[748,2597,2598],{},"Then, we choose a random card from each player's deck.",[748,2600,2601,2602,236,2604,2606],{},"The chosen card's ",[229,2603,1679],{},[229,2605,1683],{}," can be used to calculate attack power on the opponent player's card.",[748,2608,2609],{},"The attack will repeat until either player’s life becomes zero or negative.",[16,2611,2612],{},"The player who still has life will be the winner, and their victory will be announced to the observers!",[261,2614,2617],{"className":263,"code":2615,"filename":2616,"language":266,"meta":200,"style":200},"require_relative 'deck'\nrequire 'observer'\n\nclass Player\n  include Observable\n\n  attr_reader :name, :deck\n\n  def initialize(name:, deck:)\n    @name = name\n    @deck = deck\n    @life = 200\n  end\n\n  def brawl(opponent)\n    prep_fight(opponent)\n    attack(opponent) until life \u003C= 0 || opponent.life \u003C= 0\n\n    declare_result(opponent)\n  end\n\n  private\n\n  def prep_fight(opponent)\n    self.life = 200\n    opponent.life = 200\n    choose_card!\n    opponent.choose_card!\n  end\n\n  def attack(opponent)\n    attack_power = charge_attack\n    opponent_attack_power = opponent.charge_attack\n\n    if attack_power > opponent_attack_power\n      opponent.decrease_life(attack_power)\n    elsif attack_power \u003C opponent_attack_power\n      decrease_life(opponent_attack_power)\n    else\n      opponent.decrease_life(attack_power)\n      decrease_life(opponent_attack_power)\n    end\n  end\n\n  def declare_result(opponent)\n    if life.positive?\n      after_win_callback\n      \"#{name} wins\"\n    elsif opponent.life.positive?\n      opponent.after_win_callback\n      \"#{opponent.name} wins\"\n    else\n      'Match tied'\n    end\n  end\n\n  protected\n\n  attr_accessor :life\n\n  def choose_card!\n    @chosen_card = deck.sample\n  end\n\n  def charge_attack\n    10 * (Random.rand(@chosen_card.strength) + 1)\n  end\n\n  def decrease_life(attack_power)\n    self.life -= attack_power * (1 - @chosen_card.defense / 100.0)\n  end\n\n  def after_win_callback\n    return if @chosen_card.nil?\n\n    changed\n    notify_observers(self, @chosen_card)\n  end\nend\n","player.rb",[229,2618,2619,2625,2632,2636,2643,2650,2654,2665,2669,2686,2694,2704,2714,2718,2722,2732,2739,2773,2777,2784,2788,2793,2799,2804,2814,2828,2840,2846,2854,2859,2864,2874,2885,2898,2903,2917,2929,2941,2950,2956,2965,2972,2977,2982,2987,2997,3008,3014,3026,3039,3047,3061,3066,3072,3077,3082,3087,3093,3098,3107,3112,3120,3134,3139,3144,3151,3187,3192,3197,3207,3244,3249,3254,3262,3275,3280,3286,3299,3304],{"__ignoreMap":200},[270,2620,2621,2623],{"class":272,"line":273},[270,2622,1274],{"class":276},[270,2624,1406],{"class":355},[270,2626,2627,2629],{"class":272,"line":201},[270,2628,450],{"class":276},[270,2630,2631],{"class":355}," 'observer'\n",[270,2633,2634],{"class":272,"line":204},[270,2635,339],{"emptyLinePlaceholder":211},[270,2637,2638,2640],{"class":272,"line":307},[270,2639,277],{"class":276},[270,2641,2642],{"class":280}," Player\n",[270,2644,2645,2647],{"class":272,"line":324},[270,2646,1706],{"class":276},[270,2648,2649],{"class":315}," Observable\n",[270,2651,2652],{"class":272,"line":330},[270,2653,339],{"emptyLinePlaceholder":211},[270,2655,2656,2658,2660,2662],{"class":272,"line":336},[270,2657,1185],{"class":276},[270,2659,1188],{"class":315},[270,2661,814],{"class":292},[270,2663,2664],{"class":315},":deck\n",[270,2666,2667],{"class":272,"line":342},[270,2668,339],{"emptyLinePlaceholder":211},[270,2670,2671,2673,2675,2677,2679,2681,2684],{"class":272,"line":502},[270,2672,286],{"class":276},[270,2674,289],{"class":280},[270,2676,352],{"class":292},[270,2678,1213],{"class":315},[270,2680,814],{"class":292},[270,2682,2683],{"class":315},"deck:",[270,2685,359],{"class":292},[270,2687,2688,2690,2692],{"class":272,"line":510},[270,2689,1230],{"class":292},[270,2691,301],{"class":276},[270,2693,1235],{"class":292},[270,2695,2696,2699,2701],{"class":272,"line":563},[270,2697,2698],{"class":292},"    @deck ",[270,2700,301],{"class":276},[270,2702,2703],{"class":292}," deck\n",[270,2705,2706,2709,2711],{"class":272,"line":568},[270,2707,2708],{"class":292},"    @life ",[270,2710,301],{"class":276},[270,2712,2713],{"class":315}," 200\n",[270,2715,2716],{"class":272,"line":1824},[270,2717,327],{"class":276},[270,2719,2720],{"class":272,"line":1829},[270,2721,339],{"emptyLinePlaceholder":211},[270,2723,2724,2726,2729],{"class":272,"line":1842},[270,2725,286],{"class":276},[270,2727,2728],{"class":280}," brawl",[270,2730,2731],{"class":292},"(opponent)\n",[270,2733,2734,2737],{"class":272,"line":1847},[270,2735,2736],{"class":280},"    prep_fight",[270,2738,2731],{"class":292},[270,2740,2741,2744,2747,2750,2753,2755,2758,2761,2764,2767,2770],{"class":272,"line":2305},[270,2742,2743],{"class":280},"    attack",[270,2745,2746],{"class":292},"(opponent) ",[270,2748,2749],{"class":276},"until",[270,2751,2752],{"class":292}," life ",[270,2754,1988],{"class":276},[270,2756,2757],{"class":315}," 0",[270,2759,2760],{"class":276}," ||",[270,2762,2763],{"class":292}," opponent.",[270,2765,2766],{"class":280},"life",[270,2768,2769],{"class":276}," \u003C=",[270,2771,2772],{"class":315}," 0\n",[270,2774,2775],{"class":272,"line":2314},[270,2776,339],{"emptyLinePlaceholder":211},[270,2778,2779,2782],{"class":272,"line":2320},[270,2780,2781],{"class":280},"    declare_result",[270,2783,2731],{"class":292},[270,2785,2786],{"class":272,"line":2325},[270,2787,327],{"class":276},[270,2789,2791],{"class":272,"line":2790},21,[270,2792,339],{"emptyLinePlaceholder":211},[270,2794,2796],{"class":272,"line":2795},22,[270,2797,2798],{"class":276},"  private\n",[270,2800,2802],{"class":272,"line":2801},23,[270,2803,339],{"emptyLinePlaceholder":211},[270,2805,2807,2809,2812],{"class":272,"line":2806},24,[270,2808,286],{"class":276},[270,2810,2811],{"class":280}," prep_fight",[270,2813,2731],{"class":292},[270,2815,2817,2820,2822,2824,2826],{"class":272,"line":2816},25,[270,2818,2819],{"class":315},"    self",[270,2821,44],{"class":292},[270,2823,2766],{"class":280},[270,2825,872],{"class":276},[270,2827,2713],{"class":315},[270,2829,2831,2834,2836,2838],{"class":272,"line":2830},26,[270,2832,2833],{"class":292},"    opponent.",[270,2835,2766],{"class":280},[270,2837,872],{"class":276},[270,2839,2713],{"class":315},[270,2841,2843],{"class":272,"line":2842},27,[270,2844,2845],{"class":292},"    choose_card!\n",[270,2847,2849,2851],{"class":272,"line":2848},28,[270,2850,2833],{"class":292},[270,2852,2853],{"class":280},"choose_card!\n",[270,2855,2857],{"class":272,"line":2856},29,[270,2858,327],{"class":276},[270,2860,2862],{"class":272,"line":2861},30,[270,2863,339],{"emptyLinePlaceholder":211},[270,2865,2867,2869,2872],{"class":272,"line":2866},31,[270,2868,286],{"class":276},[270,2870,2871],{"class":280}," attack",[270,2873,2731],{"class":292},[270,2875,2877,2880,2882],{"class":272,"line":2876},32,[270,2878,2879],{"class":868},"    attack_power",[270,2881,872],{"class":276},[270,2883,2884],{"class":292}," charge_attack\n",[270,2886,2888,2891,2893,2895],{"class":272,"line":2887},33,[270,2889,2890],{"class":868},"    opponent_attack_power",[270,2892,872],{"class":276},[270,2894,2763],{"class":292},[270,2896,2897],{"class":280},"charge_attack\n",[270,2899,2901],{"class":272,"line":2900},34,[270,2902,339],{"emptyLinePlaceholder":211},[270,2904,2906,2909,2912,2914],{"class":272,"line":2905},35,[270,2907,2908],{"class":276},"    if",[270,2910,2911],{"class":292}," attack_power ",[270,2913,2000],{"class":276},[270,2915,2916],{"class":292}," opponent_attack_power\n",[270,2918,2920,2923,2926],{"class":272,"line":2919},36,[270,2921,2922],{"class":292},"      opponent.",[270,2924,2925],{"class":280},"decrease_life",[270,2927,2928],{"class":292},"(attack_power)\n",[270,2930,2932,2935,2937,2939],{"class":272,"line":2931},37,[270,2933,2934],{"class":276},"    elsif",[270,2936,2911],{"class":292},[270,2938,1975],{"class":276},[270,2940,2916],{"class":292},[270,2942,2944,2947],{"class":272,"line":2943},38,[270,2945,2946],{"class":280},"      decrease_life",[270,2948,2949],{"class":292},"(opponent_attack_power)\n",[270,2951,2953],{"class":272,"line":2952},39,[270,2954,2955],{"class":276},"    else\n",[270,2957,2959,2961,2963],{"class":272,"line":2958},40,[270,2960,2922],{"class":292},[270,2962,2925],{"class":280},[270,2964,2928],{"class":292},[270,2966,2968,2970],{"class":272,"line":2967},41,[270,2969,2946],{"class":280},[270,2971,2949],{"class":292},[270,2973,2975],{"class":272,"line":2974},42,[270,2976,2317],{"class":276},[270,2978,2980],{"class":272,"line":2979},43,[270,2981,327],{"class":276},[270,2983,2985],{"class":272,"line":2984},44,[270,2986,339],{"emptyLinePlaceholder":211},[270,2988,2990,2992,2995],{"class":272,"line":2989},45,[270,2991,286],{"class":276},[270,2993,2994],{"class":280}," declare_result",[270,2996,2731],{"class":292},[270,2998,3000,3002,3005],{"class":272,"line":2999},46,[270,3001,2908],{"class":276},[270,3003,3004],{"class":292}," life.",[270,3006,3007],{"class":280},"positive?\n",[270,3009,3011],{"class":272,"line":3010},47,[270,3012,3013],{"class":292},"      after_win_callback\n",[270,3015,3017,3020,3023],{"class":272,"line":3016},48,[270,3018,3019],{"class":355},"      \"",[270,3021,3022],{"class":355},"#{name}",[270,3024,3025],{"class":355}," wins\"\n",[270,3027,3029,3031,3033,3035,3037],{"class":272,"line":3028},49,[270,3030,2934],{"class":276},[270,3032,2763],{"class":292},[270,3034,2766],{"class":280},[270,3036,44],{"class":292},[270,3038,3007],{"class":280},[270,3040,3042,3044],{"class":272,"line":3041},50,[270,3043,2922],{"class":292},[270,3045,3046],{"class":280},"after_win_callback\n",[270,3048,3050,3052,3055,3057,3059],{"class":272,"line":3049},51,[270,3051,3019],{"class":355},[270,3053,3054],{"class":355},"#{opponent.",[270,3056,1591],{"class":280},[270,3058,1594],{"class":355},[270,3060,3025],{"class":355},[270,3062,3064],{"class":272,"line":3063},52,[270,3065,2955],{"class":276},[270,3067,3069],{"class":272,"line":3068},53,[270,3070,3071],{"class":355},"      'Match tied'\n",[270,3073,3075],{"class":272,"line":3074},54,[270,3076,2317],{"class":276},[270,3078,3080],{"class":272,"line":3079},55,[270,3081,327],{"class":276},[270,3083,3085],{"class":272,"line":3084},56,[270,3086,339],{"emptyLinePlaceholder":211},[270,3088,3090],{"class":272,"line":3089},57,[270,3091,3092],{"class":276},"  protected\n",[270,3094,3096],{"class":272,"line":3095},58,[270,3097,339],{"emptyLinePlaceholder":211},[270,3099,3101,3104],{"class":272,"line":3100},59,[270,3102,3103],{"class":276},"  attr_accessor",[270,3105,3106],{"class":315}," :life\n",[270,3108,3110],{"class":272,"line":3109},60,[270,3111,339],{"emptyLinePlaceholder":211},[270,3113,3115,3117],{"class":272,"line":3114},61,[270,3116,286],{"class":276},[270,3118,3119],{"class":280}," choose_card!\n",[270,3121,3123,3126,3128,3131],{"class":272,"line":3122},62,[270,3124,3125],{"class":292},"    @chosen_card ",[270,3127,301],{"class":276},[270,3129,3130],{"class":292}," deck.",[270,3132,3133],{"class":280},"sample\n",[270,3135,3137],{"class":272,"line":3136},63,[270,3138,327],{"class":276},[270,3140,3142],{"class":272,"line":3141},64,[270,3143,339],{"emptyLinePlaceholder":211},[270,3145,3147,3149],{"class":272,"line":3146},65,[270,3148,286],{"class":276},[270,3150,2884],{"class":280},[270,3152,3154,3157,3160,3163,3166,3168,3171,3174,3176,3179,3182,3185],{"class":272,"line":3153},66,[270,3155,3156],{"class":315},"    10",[270,3158,3159],{"class":276}," *",[270,3161,3162],{"class":292}," (",[270,3164,3165],{"class":315},"Random",[270,3167,44],{"class":292},[270,3169,3170],{"class":315},"rand",[270,3172,3173],{"class":292},"(@chosen_card.",[270,3175,1679],{"class":280},[270,3177,3178],{"class":292},") ",[270,3180,3181],{"class":276},"+",[270,3183,3184],{"class":315}," 1",[270,3186,359],{"class":292},[270,3188,3190],{"class":272,"line":3189},67,[270,3191,327],{"class":276},[270,3193,3195],{"class":272,"line":3194},68,[270,3196,339],{"emptyLinePlaceholder":211},[270,3198,3200,3202,3205],{"class":272,"line":3199},69,[270,3201,286],{"class":276},[270,3203,3204],{"class":280}," decrease_life",[270,3206,2928],{"class":292},[270,3208,3210,3212,3214,3216,3219,3221,3223,3225,3228,3231,3234,3236,3239,3242],{"class":272,"line":3209},70,[270,3211,2819],{"class":315},[270,3213,44],{"class":292},[270,3215,2766],{"class":280},[270,3217,3218],{"class":276}," -=",[270,3220,2911],{"class":292},[270,3222,1353],{"class":276},[270,3224,3162],{"class":292},[270,3226,3227],{"class":315},"1",[270,3229,3230],{"class":276}," -",[270,3232,3233],{"class":292}," @chosen_card.",[270,3235,1683],{"class":280},[270,3237,3238],{"class":276}," /",[270,3240,3241],{"class":315}," 100.0",[270,3243,359],{"class":292},[270,3245,3247],{"class":272,"line":3246},71,[270,3248,327],{"class":276},[270,3250,3252],{"class":272,"line":3251},72,[270,3253,339],{"emptyLinePlaceholder":211},[270,3255,3257,3259],{"class":272,"line":3256},73,[270,3258,286],{"class":276},[270,3260,3261],{"class":280}," after_win_callback\n",[270,3263,3265,3267,3270,3272],{"class":272,"line":3264},74,[270,3266,1794],{"class":276},[270,3268,3269],{"class":276}," if",[270,3271,3233],{"class":292},[270,3273,3274],{"class":280},"nil?\n",[270,3276,3278],{"class":272,"line":3277},75,[270,3279,339],{"emptyLinePlaceholder":211},[270,3281,3283],{"class":272,"line":3282},76,[270,3284,3285],{"class":292},"    changed\n",[270,3287,3289,3292,3294,3296],{"class":272,"line":3288},77,[270,3290,3291],{"class":280},"    notify_observers",[270,3293,352],{"class":292},[270,3295,946],{"class":315},[270,3297,3298],{"class":292},", @chosen_card)\n",[270,3300,3302],{"class":272,"line":3301},78,[270,3303,327],{"class":276},[270,3305,3307],{"class":272,"line":3306},79,[270,3308,333],{"class":276},[16,3310,3311,3312,3315,3316,3319],{},"Here, ",[229,3313,3314],{},"changed"," marks the object as modified, and ",[229,3317,3318],{},"notify_observers"," broadcasts that change.",[241,3321,3322],{"type":243},[16,3323,3324,3325,3327,3328,3330,3331,44],{},"Notifications are sent only if the observable's state is ",[229,3326,3314],{},". After notifying observers, the ",[229,3329,3314],{}," state automatically becomes ",[229,3332,3333],{},"false",[16,3335,3336,3337,3340],{},"In ",[229,3338,3339],{},"#after_win_callback",", the player is marked as changed, and relevant details (the player and the chosen card) are sent to all observers.",[16,3342,3343,3344,3347],{},"However, to listen to that notification, we need some more classes. Consider the class ",[229,3345,3346],{},"WinCounts"," which tracks how many times a character has won:",[261,3349,3352],{"className":263,"code":3350,"filename":3351,"language":266,"meta":200,"style":200},"class WinCounts\n  attr_reader :counts\n\n  def initialize(player: nil)\n    @player = player\n    @counts = Hash.new(0)\n  end\n\n  def update(player, card)\n    return if !@player.nil? && player != @player\n\n    @counts[card.name] += 1\n  end\nend\n","win_counts.rb",[229,3353,3354,3361,3368,3372,3388,3398,3418,3422,3426,3436,3463,3467,3483,3487],{"__ignoreMap":200},[270,3355,3356,3358],{"class":272,"line":273},[270,3357,277],{"class":276},[270,3359,3360],{"class":280}," WinCounts\n",[270,3362,3363,3365],{"class":272,"line":201},[270,3364,1185],{"class":276},[270,3366,3367],{"class":315}," :counts\n",[270,3369,3370],{"class":272,"line":204},[270,3371,339],{"emptyLinePlaceholder":211},[270,3373,3374,3376,3378,3380,3383,3386],{"class":272,"line":307},[270,3375,286],{"class":276},[270,3377,289],{"class":280},[270,3379,352],{"class":292},[270,3381,3382],{"class":315},"player:",[270,3384,3385],{"class":315}," nil",[270,3387,359],{"class":292},[270,3389,3390,3393,3395],{"class":272,"line":324},[270,3391,3392],{"class":292},"    @player ",[270,3394,301],{"class":276},[270,3396,3397],{"class":292}," player\n",[270,3399,3400,3403,3405,3408,3410,3412,3414,3416],{"class":272,"line":330},[270,3401,3402],{"class":292},"    @counts ",[270,3404,301],{"class":276},[270,3406,3407],{"class":315}," Hash",[270,3409,44],{"class":292},[270,3411,349],{"class":276},[270,3413,352],{"class":292},[270,3415,1558],{"class":315},[270,3417,359],{"class":292},[270,3419,3420],{"class":272,"line":336},[270,3421,327],{"class":276},[270,3423,3424],{"class":272,"line":342},[270,3425,339],{"emptyLinePlaceholder":211},[270,3427,3428,3430,3433],{"class":272,"line":502},[270,3429,286],{"class":276},[270,3431,3432],{"class":280}," update",[270,3434,3435],{"class":292},"(player, card)\n",[270,3437,3438,3440,3442,3445,3448,3451,3454,3457,3460],{"class":272,"line":510},[270,3439,1794],{"class":276},[270,3441,3269],{"class":276},[270,3443,3444],{"class":276}," !",[270,3446,3447],{"class":292},"@player.",[270,3449,3450],{"class":280},"nil?",[270,3452,3453],{"class":276}," &&",[270,3455,3456],{"class":292}," player ",[270,3458,3459],{"class":276},"!=",[270,3461,3462],{"class":292}," @player\n",[270,3464,3465],{"class":272,"line":563},[270,3466,339],{"emptyLinePlaceholder":211},[270,3468,3469,3472,3474,3477,3480],{"class":272,"line":568},[270,3470,3471],{"class":292},"    @counts[card.",[270,3473,1591],{"class":280},[270,3475,3476],{"class":292},"] ",[270,3478,3479],{"class":276},"+=",[270,3481,3482],{"class":315}," 1\n",[270,3484,3485],{"class":272,"line":1824},[270,3486,327],{"class":276},[270,3488,3489],{"class":272,"line":1829},[270,3490,333],{"class":276},[16,3492,3493,3494,3497],{},"Here, the player defaults to ",[229,3495,3496],{},"nil",", so that it can be initialized to track the win counts of all players throughout the game.",[16,3499,3500],{},"Now let's orchestrate them all. First, create the required objects:",[261,3502,3504],{"className":263,"code":3503,"filename":381,"language":266,"meta":200,"style":200},"require_relative 'player'\nrequire_relative 'win_counts'\n\nthatch = Card.new(name: 'Thatch', strength: 7, defense: 3)\nscarlet = Card.new(name: 'Scarlet', strength: 8, defense: 5)\ndiana = Card.new(name: 'Diana', strength: 5, defense: 5)\nlady_vera = Card.new(name: 'Lady Vera', strength: 3, defense: 8)\nransom = Card.new(name: 'Ransom', strength: 7, defense: 3)\n\ndeck_one = Deck.new(thatch, scarlet, lady_vera)\ndeck_two = Deck.new(ransom, diana, scarlet)\n\nplayer_one = Player.new(name: 'Player One', deck: deck_one)\nplayer_two = Player.new(name: 'Player Two', deck: deck_two)\n\nglobal_win_counts_tracker = WinCounts.new\nplayer_one_win_counts_tracker = WinCounts.new(player: player_one)\nplayer_two_win_counts_tracker = WinCounts.new(player: player_two)\n",[229,3505,3506,3513,3520,3524,3556,3588,3620,3654,3688,3692,3708,3724,3728,3756,3783,3787,3801,3821],{"__ignoreMap":200},[270,3507,3508,3510],{"class":272,"line":273},[270,3509,1274],{"class":276},[270,3511,3512],{"class":355}," 'player'\n",[270,3514,3515,3517],{"class":272,"line":201},[270,3516,1274],{"class":276},[270,3518,3519],{"class":355}," 'win_counts'\n",[270,3521,3522],{"class":272,"line":204},[270,3523,339],{"emptyLinePlaceholder":211},[270,3525,3526,3528,3530,3532,3534,3536,3538,3540,3542,3544,3546,3548,3550,3552,3554],{"class":272,"line":307},[270,3527,1869],{"class":868},[270,3529,872],{"class":276},[270,3531,1440],{"class":315},[270,3533,44],{"class":292},[270,3535,349],{"class":276},[270,3537,352],{"class":292},[270,3539,1213],{"class":315},[270,3541,1451],{"class":355},[270,3543,814],{"class":292},[270,3545,1218],{"class":315},[270,3547,1458],{"class":315},[270,3549,814],{"class":292},[270,3551,1223],{"class":315},[270,3553,1465],{"class":315},[270,3555,359],{"class":292},[270,3557,3558,3560,3562,3564,3566,3568,3570,3572,3574,3576,3578,3580,3582,3584,3586],{"class":272,"line":324},[270,3559,1902],{"class":868},[270,3561,872],{"class":276},[270,3563,1440],{"class":315},[270,3565,44],{"class":292},[270,3567,349],{"class":276},[270,3569,352],{"class":292},[270,3571,1213],{"class":315},[270,3573,1486],{"class":355},[270,3575,814],{"class":292},[270,3577,1218],{"class":315},[270,3579,1493],{"class":315},[270,3581,814],{"class":292},[270,3583,1223],{"class":315},[270,3585,1500],{"class":315},[270,3587,359],{"class":292},[270,3589,3590,3592,3594,3596,3598,3600,3602,3604,3606,3608,3610,3612,3614,3616,3618],{"class":272,"line":330},[270,3591,1935],{"class":868},[270,3593,872],{"class":276},[270,3595,1440],{"class":315},[270,3597,44],{"class":292},[270,3599,349],{"class":276},[270,3601,352],{"class":292},[270,3603,1213],{"class":315},[270,3605,1521],{"class":355},[270,3607,814],{"class":292},[270,3609,1218],{"class":315},[270,3611,1500],{"class":315},[270,3613,814],{"class":292},[270,3615,1223],{"class":315},[270,3617,1500],{"class":315},[270,3619,359],{"class":292},[270,3621,3622,3625,3627,3629,3631,3633,3635,3637,3640,3642,3644,3646,3648,3650,3652],{"class":272,"line":336},[270,3623,3624],{"class":868},"lady_vera",[270,3626,872],{"class":276},[270,3628,1440],{"class":315},[270,3630,44],{"class":292},[270,3632,349],{"class":276},[270,3634,352],{"class":292},[270,3636,1213],{"class":315},[270,3638,3639],{"class":355}," 'Lady Vera'",[270,3641,814],{"class":292},[270,3643,1218],{"class":315},[270,3645,1465],{"class":315},[270,3647,814],{"class":292},[270,3649,1223],{"class":315},[270,3651,1493],{"class":315},[270,3653,359],{"class":292},[270,3655,3656,3659,3661,3663,3665,3667,3669,3671,3674,3676,3678,3680,3682,3684,3686],{"class":272,"line":342},[270,3657,3658],{"class":868},"ransom",[270,3660,872],{"class":276},[270,3662,1440],{"class":315},[270,3664,44],{"class":292},[270,3666,349],{"class":276},[270,3668,352],{"class":292},[270,3670,1213],{"class":315},[270,3672,3673],{"class":355}," 'Ransom'",[270,3675,814],{"class":292},[270,3677,1218],{"class":315},[270,3679,1458],{"class":315},[270,3681,814],{"class":292},[270,3683,1223],{"class":315},[270,3685,1465],{"class":315},[270,3687,359],{"class":292},[270,3689,3690],{"class":272,"line":502},[270,3691,339],{"emptyLinePlaceholder":211},[270,3693,3694,3697,3699,3701,3703,3705],{"class":272,"line":510},[270,3695,3696],{"class":868},"deck_one",[270,3698,872],{"class":276},[270,3700,1420],{"class":315},[270,3702,44],{"class":292},[270,3704,349],{"class":276},[270,3706,3707],{"class":292},"(thatch, scarlet, lady_vera)\n",[270,3709,3710,3713,3715,3717,3719,3721],{"class":272,"line":563},[270,3711,3712],{"class":868},"deck_two",[270,3714,872],{"class":276},[270,3716,1420],{"class":315},[270,3718,44],{"class":292},[270,3720,349],{"class":276},[270,3722,3723],{"class":292},"(ransom, diana, scarlet)\n",[270,3725,3726],{"class":272,"line":568},[270,3727,339],{"emptyLinePlaceholder":211},[270,3729,3730,3733,3735,3738,3740,3742,3744,3746,3749,3751,3753],{"class":272,"line":1824},[270,3731,3732],{"class":868},"player_one",[270,3734,872],{"class":276},[270,3736,3737],{"class":315}," Player",[270,3739,44],{"class":292},[270,3741,349],{"class":276},[270,3743,352],{"class":292},[270,3745,1213],{"class":315},[270,3747,3748],{"class":355}," 'Player One'",[270,3750,814],{"class":292},[270,3752,2683],{"class":315},[270,3754,3755],{"class":292}," deck_one)\n",[270,3757,3758,3761,3763,3765,3767,3769,3771,3773,3776,3778,3780],{"class":272,"line":1829},[270,3759,3760],{"class":868},"player_two",[270,3762,872],{"class":276},[270,3764,3737],{"class":315},[270,3766,44],{"class":292},[270,3768,349],{"class":276},[270,3770,352],{"class":292},[270,3772,1213],{"class":315},[270,3774,3775],{"class":355}," 'Player Two'",[270,3777,814],{"class":292},[270,3779,2683],{"class":315},[270,3781,3782],{"class":292}," deck_two)\n",[270,3784,3785],{"class":272,"line":1842},[270,3786,339],{"emptyLinePlaceholder":211},[270,3788,3789,3792,3794,3797,3799],{"class":272,"line":1847},[270,3790,3791],{"class":868},"global_win_counts_tracker",[270,3793,872],{"class":276},[270,3795,3796],{"class":315}," WinCounts",[270,3798,44],{"class":292},[270,3800,1425],{"class":276},[270,3802,3803,3806,3808,3810,3812,3814,3816,3818],{"class":272,"line":2305},[270,3804,3805],{"class":868},"player_one_win_counts_tracker",[270,3807,872],{"class":276},[270,3809,3796],{"class":315},[270,3811,44],{"class":292},[270,3813,349],{"class":276},[270,3815,352],{"class":292},[270,3817,3382],{"class":315},[270,3819,3820],{"class":292}," player_one)\n",[270,3822,3823,3826,3828,3830,3832,3834,3836,3838],{"class":272,"line":2314},[270,3824,3825],{"class":868},"player_two_win_counts_tracker",[270,3827,872],{"class":276},[270,3829,3796],{"class":315},[270,3831,44],{"class":292},[270,3833,349],{"class":276},[270,3835,352],{"class":292},[270,3837,3382],{"class":315},[270,3839,3840],{"class":292}," player_two)\n",[16,3842,3843,3844,3846],{},"Now add the ",[229,3845,3346],{}," tracker as observers to the players and let the brawl begin:",[261,3848,3850],{"className":263,"code":3849,"filename":381,"language":266,"meta":200,"style":200},"player_one.add_observer(global_win_counts_tracker)\nplayer_two.add_observer(global_win_counts_tracker)\nplayer_one.add_observer(player_one_win_counts_tracker)\nplayer_two.add_observer(player_two_win_counts_tracker)\n\n10.times { puts player_one.brawl(player_two) }\n\nputs \"Overall Wins: #{global_win_counts_tracker.counts}\"\nputs \"Player One Wins: #{player_one_win_counts_tracker.counts}\"\nputs \"Player Two Wins: #{player_two_win_counts_tracker.counts}\"\n",[229,3851,3852,3863,3872,3881,3890,3894,3917,3921,3939,3955],{"__ignoreMap":200},[270,3853,3854,3857,3860],{"class":272,"line":273},[270,3855,3856],{"class":292},"player_one.",[270,3858,3859],{"class":280},"add_observer",[270,3861,3862],{"class":292},"(global_win_counts_tracker)\n",[270,3864,3865,3868,3870],{"class":272,"line":201},[270,3866,3867],{"class":292},"player_two.",[270,3869,3859],{"class":280},[270,3871,3862],{"class":292},[270,3873,3874,3876,3878],{"class":272,"line":204},[270,3875,3856],{"class":292},[270,3877,3859],{"class":280},[270,3879,3880],{"class":292},"(player_one_win_counts_tracker)\n",[270,3882,3883,3885,3887],{"class":272,"line":307},[270,3884,3867],{"class":292},[270,3886,3859],{"class":280},[270,3888,3889],{"class":292},"(player_two_win_counts_tracker)\n",[270,3891,3892],{"class":272,"line":324},[270,3893,339],{"emptyLinePlaceholder":211},[270,3895,3896,3899,3901,3904,3906,3909,3912,3914],{"class":272,"line":330},[270,3897,3898],{"class":315},"10",[270,3900,44],{"class":292},[270,3902,3903],{"class":280},"times",[270,3905,1575],{"class":292},[270,3907,3908],{"class":315},"puts",[270,3910,3911],{"class":292}," player_one.",[270,3913,2594],{"class":280},[270,3915,3916],{"class":292},"(player_two) }\n",[270,3918,3919],{"class":272,"line":336},[270,3920,339],{"emptyLinePlaceholder":211},[270,3922,3923,3925,3928,3931,3934,3936],{"class":272,"line":342},[270,3924,3908],{"class":315},[270,3926,3927],{"class":355}," \"Overall Wins: ",[270,3929,3930],{"class":355},"#{global_win_counts_tracker.",[270,3932,3933],{"class":280},"counts",[270,3935,1594],{"class":355},[270,3937,3938],{"class":355},"\"\n",[270,3940,3941,3943,3946,3949,3951,3953],{"class":272,"line":502},[270,3942,3908],{"class":315},[270,3944,3945],{"class":355}," \"Player One Wins: ",[270,3947,3948],{"class":355},"#{player_one_win_counts_tracker.",[270,3950,3933],{"class":280},[270,3952,1594],{"class":355},[270,3954,3938],{"class":355},[270,3956,3957,3959,3962,3965,3967,3969],{"class":272,"line":510},[270,3958,3908],{"class":315},[270,3960,3961],{"class":355}," \"Player Two Wins: ",[270,3963,3964],{"class":355},"#{player_two_win_counts_tracker.",[270,3966,3933],{"class":280},[270,3968,1594],{"class":355},[270,3970,3938],{"class":355},[16,3972,3973,3974,3976,3977,3980],{},"Once observers are added to the players, every win triggers ",[229,3975,2564],{},", which calls ",[229,3978,3979],{},"#update"," on each tracker.",[241,3982,3983],{"type":243},[16,3984,3985,3986,3988,3989,3991,3992,3995],{},"The notification receiver method does not need to be named ",[229,3987,3979],{},". If you want it to be a different method, you have to specify its name to ",[229,3990,2560],{}," method call via ",[229,3993,3994],{},"func:"," keyword.",[16,3997,3998],{},"Based on how the game went, the output would be something like this:",[261,4000,4004],{"className":4001,"code":4002,"filename":368,"language":4003,"meta":200,"style":200},"language-csv shiki shiki-themes github-light github-dark","Player Two wins\nPlayer One wins\nPlayer Two wins\nPlayer One wins\nPlayer One wins\nPlayer Two wins\nPlayer Two wins\nPlayer One wins\nPlayer Two wins\nPlayer One wins\nOverall Wins: {\"Scarlet\" => 7, \"Ransom\" => 1, \"Thatch\" => 1, \"Diana\" => 1}\nPlayer One Wins: {\"Scarlet\" => 4, \"Thatch\" => 1}\nPlayer Two Wins: {\"Scarlet\" => 3, \"Ransom\" => 1, \"Diana\" => 1}\n","csv",[229,4005,4006,4011,4016,4020,4024,4028,4032,4036,4040,4044,4048,4053,4058],{"__ignoreMap":200},[270,4007,4008],{"class":272,"line":273},[270,4009,4010],{},"Player Two wins\n",[270,4012,4013],{"class":272,"line":201},[270,4014,4015],{},"Player One wins\n",[270,4017,4018],{"class":272,"line":204},[270,4019,4010],{},[270,4021,4022],{"class":272,"line":307},[270,4023,4015],{},[270,4025,4026],{"class":272,"line":324},[270,4027,4015],{},[270,4029,4030],{"class":272,"line":330},[270,4031,4010],{},[270,4033,4034],{"class":272,"line":336},[270,4035,4010],{},[270,4037,4038],{"class":272,"line":342},[270,4039,4015],{},[270,4041,4042],{"class":272,"line":502},[270,4043,4010],{},[270,4045,4046],{"class":272,"line":510},[270,4047,4015],{},[270,4049,4050],{"class":272,"line":563},[270,4051,4052],{},"Overall Wins: {\"Scarlet\" => 7, \"Ransom\" => 1, \"Thatch\" => 1, \"Diana\" => 1}\n",[270,4054,4055],{"class":272,"line":568},[270,4056,4057],{},"Player One Wins: {\"Scarlet\" => 4, \"Thatch\" => 1}\n",[270,4059,4060],{"class":272,"line":1824},[270,4061,4062],{},"Player Two Wins: {\"Scarlet\" => 3, \"Ransom\" => 1, \"Diana\" => 1}\n",[16,4064,352,4065,4068],{},[35,4066,4067],{},"Yes, my girl Scarlet is OP",")",[11,4070,4072],{"id":4071},"conclusion","Conclusion",[16,4074,4075],{},"All of these modules introduce behavior that isn’t tied to any specific class. Even more interestingly, they don’t implement everything themselves. Instead, they rely on the including class to define the part of the behavior they depend on. This contract-based design is what makes mixins truly powerful.",[16,4077,4078],{},"The best mixins define a minimal contract and remain agnostic about their host. The moment a module assumes knowledge of a specific class, it stops being a mixin and starts being misplaced logic.",[16,4080,4081],{},"Ruby’s standard library shows us the difference. The real question is whether we follow that example.",[1081,4083,4084],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":200,"searchDepth":201,"depth":201,"links":4086},[4087,4088,4089,4090,4091],{"id":1141,"depth":204,"text":1118},{"id":1622,"depth":204,"text":1124},{"id":2075,"depth":204,"text":1130},{"id":2546,"depth":204,"text":1136},{"id":4071,"depth":204,"text":4072},"2026-03-10","A practical guide to Ruby’s Forwardable, Comparable, Enumerable, and Observable modules — and what they teach us about designing clean, contract-driven mixins.",{},"/blogs/the_fantastic_modules_and_how_to_use_them",{"title":1099,"description":4093},"blogs/the_fantastic_modules_and_how_to_use_them",[266,4099],"mixins","XuABjzzuCsyCkvG9vVF4tb2Q4fuWFJsV-qtrOxLwRVU",{"id":4102,"title":4103,"body":4104,"date":5427,"description":5428,"extension":209,"meta":5429,"navigation":211,"path":5430,"seo":5431,"stem":5432,"tags":5433,"__hash__":5434},"blogs/blogs/refinements_and_the_chamber_of_edge_cases.md","Refinements and the Chamber of Edge Cases",{"type":8,"value":4105,"toc":5425},[4106,4122,4138,4187,4193,4200,4203,4209,4258,4269,4276,4403,4420,4436,4445,4645,4653,4660,4667,4828,4837,4916,4919,4922,4928,5114,5120,5210,5217,5220,5223,5226,5295,5419,5422],[16,4107,4108,4109,4114,4115,236,4118,4121],{},"One of the most featured alternatives to monkey patching in Ruby is ",[20,4110,4113],{"href":4111,"rel":4112,":target":22},"https://docs.ruby-lang.org/en/3.4/syntax/refinements_rdoc.html",[25],"Refinements",", with the help of the ",[229,4116,4117],{},"refine",[229,4119,4120],{},"using"," methods.",[16,4123,4124,4125,4130,4131,4134,4135,44],{},"Let's say we need to translate words in ",[20,4126,4129],{"href":4127,"rel":4128,":target":22},"https://en.wikipedia.org/wiki/Groot",[25],"Groot","'s language and you want the translation to happen via a method that is available on all strings. To achieve this, you can open the ",[229,4132,4133],{},"String"," class in Ruby and add a new method ",[229,4136,4137],{},"#in_groot",[261,4139,4141],{"className":263,"code":4140,"filename":381,"language":266,"meta":200,"style":200},"class String\n  def in_groot\n    \"I am Groot\"\n  end\nend\n\n\"Hello World\".in_groot  # => I am Groot\n",[229,4142,4143,4150,4157,4162,4166,4170,4174],{"__ignoreMap":200},[270,4144,4145,4147],{"class":272,"line":273},[270,4146,277],{"class":276},[270,4148,4149],{"class":280}," String\n",[270,4151,4152,4154],{"class":272,"line":201},[270,4153,286],{"class":276},[270,4155,4156],{"class":280}," in_groot\n",[270,4158,4159],{"class":272,"line":204},[270,4160,4161],{"class":355},"    \"I am Groot\"\n",[270,4163,4164],{"class":272,"line":307},[270,4165,327],{"class":276},[270,4167,4168],{"class":272,"line":324},[270,4169,333],{"class":276},[270,4171,4172],{"class":272,"line":330},[270,4173,339],{"emptyLinePlaceholder":211},[270,4175,4176,4179,4181,4184],{"class":272,"line":336},[270,4177,4178],{"class":355},"\"Hello World\"",[270,4180,44],{"class":292},[270,4182,4183],{"class":280},"in_groot",[270,4185,4186],{"class":614},"  # => I am Groot\n",[16,4188,4189,4190,44],{},"This is what we call ",[112,4191,4192],{},"monkey patching",[16,4194,4195,4196,4199],{},"However, this is generally considered a bad practice as it violates the ",[112,4197,4198],{},"Open/Closed Principle"," of the SOLID principles. (i.e., it can introduce a different or conflicting interface for a class that is already defined, used and in communication with other parts of the system)",[16,4201,4202],{},"In most of the languages, this wouldn’t even be possible but good old Ruby allows it like a super power, and to make sure it is used with great responsibility, refinements comes into play.",[16,4204,4205,4206,4208],{},"To define refinements, you first have to ",[229,4207,4117],{}," one or more classes under a module:",[261,4210,4213],{"className":263,"code":4211,"filename":4212,"language":266,"meta":200,"style":200},"module GrootLanguageSupport\n  refine String do\n    def in_groot\n      \"I am Groot\"\n    end\n  end\nend\n","refinements.rb",[229,4214,4215,4223,4234,4241,4246,4250,4254],{"__ignoreMap":200},[270,4216,4217,4220],{"class":272,"line":273},[270,4218,4219],{"class":276},"module",[270,4221,4222],{"class":280}," GrootLanguageSupport\n",[270,4224,4225,4228,4231],{"class":272,"line":201},[270,4226,4227],{"class":276},"  refine",[270,4229,4230],{"class":315}," String",[270,4232,4233],{"class":276}," do\n",[270,4235,4236,4239],{"class":272,"line":204},[270,4237,4238],{"class":276},"    def",[270,4240,4156],{"class":280},[270,4242,4243],{"class":272,"line":307},[270,4244,4245],{"class":355},"      \"I am Groot\"\n",[270,4247,4248],{"class":272,"line":324},[270,4249,2317],{"class":276},[270,4251,4252],{"class":272,"line":330},[270,4253,327],{"class":276},[270,4255,4256],{"class":272,"line":336},[270,4257,333],{"class":276},[241,4259,4260],{"type":243},[16,4261,4262,4263,4268],{},"You can not only define methods inside a refinement, but also import methods from a module using the ",[20,4264,4267],{"href":4265,"rel":4266},"https://docs.ruby-lang.org/en/master/Refinement.html#method-i-import_methods",[25],"Refinement#import_methods"," method. However, this behaves more like copying than inheriting, so any change made to the original methods after import will not be reflected.",[16,4270,4271,4272,4275],{},"And then you can use the module to ",[35,4273,4274],{},"activate"," the refinements:",[261,4277,4280],{"className":263,"code":4278,"filename":4279,"language":266,"meta":200,"style":200},"require_relative 'refinements'\n\nclass Message\n  using GrootLanguageSupport\n  attr_reader :content\n\n  def initialize(content)\n    @content = content\n  end\n\n  def translate_to_groot\n    content.in_groot\n  end\nend\n\nm = Message.new(\"Hello\")\nputs m.translate_to_groot # => I am Groot\n","message.rb",[229,4281,4282,4289,4293,4300,4307,4314,4318,4326,4334,4338,4342,4349,4357,4361,4365,4369,4390],{"__ignoreMap":200},[270,4283,4284,4286],{"class":272,"line":273},[270,4285,1274],{"class":276},[270,4287,4288],{"class":355}," 'refinements'\n",[270,4290,4291],{"class":272,"line":201},[270,4292,339],{"emptyLinePlaceholder":211},[270,4294,4295,4297],{"class":272,"line":204},[270,4296,277],{"class":276},[270,4298,4299],{"class":280}," Message\n",[270,4301,4302,4305],{"class":272,"line":307},[270,4303,4304],{"class":276},"  using",[270,4306,4222],{"class":315},[270,4308,4309,4311],{"class":272,"line":324},[270,4310,1185],{"class":276},[270,4312,4313],{"class":315}," :content\n",[270,4315,4316],{"class":272,"line":330},[270,4317,339],{"emptyLinePlaceholder":211},[270,4319,4320,4322,4324],{"class":272,"line":336},[270,4321,286],{"class":276},[270,4323,289],{"class":280},[270,4325,293],{"class":292},[270,4327,4328,4330,4332],{"class":272,"line":342},[270,4329,298],{"class":292},[270,4331,301],{"class":276},[270,4333,304],{"class":292},[270,4335,4336],{"class":272,"line":502},[270,4337,327],{"class":276},[270,4339,4340],{"class":272,"line":510},[270,4341,339],{"emptyLinePlaceholder":211},[270,4343,4344,4346],{"class":272,"line":563},[270,4345,286],{"class":276},[270,4347,4348],{"class":280}," translate_to_groot\n",[270,4350,4351,4354],{"class":272,"line":568},[270,4352,4353],{"class":292},"    content.",[270,4355,4356],{"class":280},"in_groot\n",[270,4358,4359],{"class":272,"line":1824},[270,4360,327],{"class":276},[270,4362,4363],{"class":272,"line":1829},[270,4364,333],{"class":276},[270,4366,4367],{"class":272,"line":1842},[270,4368,339],{"emptyLinePlaceholder":211},[270,4370,4371,4374,4376,4379,4381,4383,4385,4388],{"class":272,"line":1847},[270,4372,4373],{"class":868},"m",[270,4375,872],{"class":276},[270,4377,4378],{"class":315}," Message",[270,4380,44],{"class":292},[270,4382,349],{"class":276},[270,4384,352],{"class":292},[270,4386,4387],{"class":355},"\"Hello\"",[270,4389,359],{"class":292},[270,4391,4392,4394,4397,4400],{"class":272,"line":2305},[270,4393,3908],{"class":315},[270,4395,4396],{"class":292}," m.",[270,4398,4399],{"class":280},"translate_to_groot",[270,4401,4402],{"class":614}," # => I am Groot\n",[16,4404,4405,4406,4409,4410,4412,4413,4415,4416,4419],{},"The advantage of the refinements over monkey patching is that whatever modifications you've made will only be activated in the ",[112,4407,4408],{},"lexical scope"," where you are ",[229,4411,4120],{}," the refinements. In our case, refinements to ",[229,4414,4133],{}," will only be active within the lexical scope of the ",[229,4417,4418],{},"Message"," class's scope and will not be active anywhere else.",[241,4421,4422],{"type":243},[16,4423,4424,4427,4428,4431,4432,4435],{},[35,4425,4426],{},"Lexical scope"," (also known as ",[35,4429,4430],{},"static scope",") determines visibility based on where code is defined in the source, not where it is executed. In contrast, ",[35,4433,4434],{},"scope"," in general often refers to runtime visibility, which can vary depending on the call site or receiver.",[16,4437,4438,4439,4441,4442,790],{},"But one man's advantage is another man's limitation. Due to its restrictive nature, refinements behave in a way that feels a bit strange in some scenarios. For example, let's say you are inheriting from the ",[229,4440,4418],{}," class, which has refinements, to create a new class ",[229,4443,4444],{},"AngryMessage",[261,4446,4448],{"className":263,"code":4447,"filename":4279,"language":266,"meta":200,"style":200},"require_relative 'refinements'\n\nclass Message\n  using GrootLanguageSupport\n  attr_reader :content\n\n  def initialize(content)\n    @content = content\n  end\n\n  def translate_to_groot\n    content.in_groot\n  end\nend\n\nclass AngryMessage \u003C Message\n  def angry_content\n    content.upcase\n  end\n\n  def angry_translation_to_groot\n    angry_content.in_groot\n  end\nend\n\nm = AngryMessage.new(\"You need to leave!!\")\nputs m.angry_content                          # => YOU NEED TO LEAVE!!\nputs m.translate_to_groot                     # => I am Groot\nputs m.angry_translation_to_groot             # => undefined method 'in_groot' for an instance of String (NoMethodError)\n",[229,4449,4450,4456,4460,4466,4472,4478,4482,4490,4498,4502,4506,4512,4518,4522,4526,4530,4543,4550,4557,4561,4565,4572,4579,4583,4587,4591,4610,4622,4633],{"__ignoreMap":200},[270,4451,4452,4454],{"class":272,"line":273},[270,4453,1274],{"class":276},[270,4455,4288],{"class":355},[270,4457,4458],{"class":272,"line":201},[270,4459,339],{"emptyLinePlaceholder":211},[270,4461,4462,4464],{"class":272,"line":204},[270,4463,277],{"class":276},[270,4465,4299],{"class":280},[270,4467,4468,4470],{"class":272,"line":307},[270,4469,4304],{"class":276},[270,4471,4222],{"class":315},[270,4473,4474,4476],{"class":272,"line":324},[270,4475,1185],{"class":276},[270,4477,4313],{"class":315},[270,4479,4480],{"class":272,"line":330},[270,4481,339],{"emptyLinePlaceholder":211},[270,4483,4484,4486,4488],{"class":272,"line":336},[270,4485,286],{"class":276},[270,4487,289],{"class":280},[270,4489,293],{"class":292},[270,4491,4492,4494,4496],{"class":272,"line":342},[270,4493,298],{"class":292},[270,4495,301],{"class":276},[270,4497,304],{"class":292},[270,4499,4500],{"class":272,"line":502},[270,4501,327],{"class":276},[270,4503,4504],{"class":272,"line":510},[270,4505,339],{"emptyLinePlaceholder":211},[270,4507,4508,4510],{"class":272,"line":563},[270,4509,286],{"class":276},[270,4511,4348],{"class":280},[270,4513,4514,4516],{"class":272,"line":568},[270,4515,4353],{"class":292},[270,4517,4356],{"class":280},[270,4519,4520],{"class":272,"line":1824},[270,4521,327],{"class":276},[270,4523,4524],{"class":272,"line":1829},[270,4525,333],{"class":276},[270,4527,4528],{"class":272,"line":1842},[270,4529,339],{"emptyLinePlaceholder":211},[270,4531,4532,4534,4537,4540],{"class":272,"line":1847},[270,4533,277],{"class":276},[270,4535,4536],{"class":280}," AngryMessage",[270,4538,4539],{"class":292}," \u003C ",[270,4541,4542],{"class":280},"Message\n",[270,4544,4545,4547],{"class":272,"line":2305},[270,4546,286],{"class":276},[270,4548,4549],{"class":280}," angry_content\n",[270,4551,4552,4554],{"class":272,"line":2314},[270,4553,4353],{"class":292},[270,4555,4556],{"class":280},"upcase\n",[270,4558,4559],{"class":272,"line":2320},[270,4560,327],{"class":276},[270,4562,4563],{"class":272,"line":2325},[270,4564,339],{"emptyLinePlaceholder":211},[270,4566,4567,4569],{"class":272,"line":2790},[270,4568,286],{"class":276},[270,4570,4571],{"class":280}," angry_translation_to_groot\n",[270,4573,4574,4577],{"class":272,"line":2795},[270,4575,4576],{"class":292},"    angry_content.",[270,4578,4356],{"class":280},[270,4580,4581],{"class":272,"line":2801},[270,4582,327],{"class":276},[270,4584,4585],{"class":272,"line":2806},[270,4586,333],{"class":276},[270,4588,4589],{"class":272,"line":2816},[270,4590,339],{"emptyLinePlaceholder":211},[270,4592,4593,4595,4597,4599,4601,4603,4605,4608],{"class":272,"line":2830},[270,4594,4373],{"class":868},[270,4596,872],{"class":276},[270,4598,4536],{"class":315},[270,4600,44],{"class":292},[270,4602,349],{"class":276},[270,4604,352],{"class":292},[270,4606,4607],{"class":355},"\"You need to leave!!\"",[270,4609,359],{"class":292},[270,4611,4612,4614,4616,4619],{"class":272,"line":2842},[270,4613,3908],{"class":315},[270,4615,4396],{"class":292},[270,4617,4618],{"class":280},"angry_content",[270,4620,4621],{"class":614},"                          # => YOU NEED TO LEAVE!!\n",[270,4623,4624,4626,4628,4630],{"class":272,"line":2848},[270,4625,3908],{"class":315},[270,4627,4396],{"class":292},[270,4629,4399],{"class":280},[270,4631,4632],{"class":614},"                     # => I am Groot\n",[270,4634,4635,4637,4639,4642],{"class":272,"line":2856},[270,4636,3908],{"class":315},[270,4638,4396],{"class":292},[270,4640,4641],{"class":280},"angry_translation_to_groot",[270,4643,4644],{"class":614},"             # => undefined method 'in_groot' for an instance of String (NoMethodError)\n",[16,4646,4647,4648,4650,4651,44],{},"Refinements are active inside the methods that are defined in the parent class ",[229,4649,4418],{}," but they are not active for the methods that are defined in the child class ",[229,4652,4444],{},[16,4654,4655,4656,4659],{},"This is because refinements are resolved at ",[35,4657,4658],{},"method definition time, not at call time",", which is why methods defined in subclasses don’t see refinements from parent classes. (i.e., any class inheriting from another class with refinements, will not inherit those refinements)",[16,4661,4662,4663,4666],{},"Refinements are also not active inside any ",[229,4664,4665],{},"blocks"," given to a method which was defined when refinements were active:",[261,4668,4670],{"className":263,"code":4669,"filename":4279,"language":266,"meta":200,"style":200},"require_relative 'refinements'\n\nclass Message\n  using GrootLanguageSupport\n  attr_reader :content\n\n  def initialize(content)\n    @content = content\n  end\n\n  # ...\n\n  def yield_content\n    puts content.in_groot    # => I am Groot\n    yield content\n  end\nend\n\nm = Message.new(\"hello\")\n\nm.yield_content do |content|\n  puts content.in_groot      # => undefined method 'in_groot' for an instance of String (NoMethodError)\nend\n",[229,4671,4672,4678,4682,4688,4694,4700,4704,4712,4720,4724,4728,4733,4737,4744,4757,4764,4768,4772,4776,4795,4799,4812,4824],{"__ignoreMap":200},[270,4673,4674,4676],{"class":272,"line":273},[270,4675,1274],{"class":276},[270,4677,4288],{"class":355},[270,4679,4680],{"class":272,"line":201},[270,4681,339],{"emptyLinePlaceholder":211},[270,4683,4684,4686],{"class":272,"line":204},[270,4685,277],{"class":276},[270,4687,4299],{"class":280},[270,4689,4690,4692],{"class":272,"line":307},[270,4691,4304],{"class":276},[270,4693,4222],{"class":315},[270,4695,4696,4698],{"class":272,"line":324},[270,4697,1185],{"class":276},[270,4699,4313],{"class":315},[270,4701,4702],{"class":272,"line":330},[270,4703,339],{"emptyLinePlaceholder":211},[270,4705,4706,4708,4710],{"class":272,"line":336},[270,4707,286],{"class":276},[270,4709,289],{"class":280},[270,4711,293],{"class":292},[270,4713,4714,4716,4718],{"class":272,"line":342},[270,4715,298],{"class":292},[270,4717,301],{"class":276},[270,4719,304],{"class":292},[270,4721,4722],{"class":272,"line":502},[270,4723,327],{"class":276},[270,4725,4726],{"class":272,"line":510},[270,4727,339],{"emptyLinePlaceholder":211},[270,4729,4730],{"class":272,"line":563},[270,4731,4732],{"class":614},"  # ...\n",[270,4734,4735],{"class":272,"line":568},[270,4736,339],{"emptyLinePlaceholder":211},[270,4738,4739,4741],{"class":272,"line":1824},[270,4740,286],{"class":276},[270,4742,4743],{"class":280}," yield_content\n",[270,4745,4746,4749,4752,4754],{"class":272,"line":1829},[270,4747,4748],{"class":315},"    puts",[270,4750,4751],{"class":292}," content.",[270,4753,4183],{"class":280},[270,4755,4756],{"class":614},"    # => I am Groot\n",[270,4758,4759,4762],{"class":272,"line":1842},[270,4760,4761],{"class":276},"    yield",[270,4763,304],{"class":292},[270,4765,4766],{"class":272,"line":1847},[270,4767,327],{"class":276},[270,4769,4770],{"class":272,"line":2305},[270,4771,333],{"class":276},[270,4773,4774],{"class":272,"line":2314},[270,4775,339],{"emptyLinePlaceholder":211},[270,4777,4778,4780,4782,4784,4786,4788,4790,4793],{"class":272,"line":2320},[270,4779,4373],{"class":868},[270,4781,872],{"class":276},[270,4783,4378],{"class":315},[270,4785,44],{"class":292},[270,4787,349],{"class":276},[270,4789,352],{"class":292},[270,4791,4792],{"class":355},"\"hello\"",[270,4794,359],{"class":292},[270,4796,4797],{"class":272,"line":2325},[270,4798,339],{"emptyLinePlaceholder":211},[270,4800,4801,4804,4807,4809],{"class":272,"line":2790},[270,4802,4803],{"class":292},"m.",[270,4805,4806],{"class":280},"yield_content",[270,4808,2299],{"class":276},[270,4810,4811],{"class":292}," |content|\n",[270,4813,4814,4817,4819,4821],{"class":272,"line":2795},[270,4815,4816],{"class":315},"  puts",[270,4818,4751],{"class":292},[270,4820,4183],{"class":280},[270,4822,4823],{"class":614},"      # => undefined method 'in_groot' for an instance of String (NoMethodError)\n",[270,4825,4826],{"class":272,"line":2801},[270,4827,333],{"class":276},[16,4829,4830,4831,236,4834,790],{},"You probably wouldn’t use this in day-to-day life, but the same applies to ",[229,4832,4833],{},"instance_eval",[229,4835,4836],{},"class_eval",[261,4838,4840],{"className":263,"code":4839,"filename":4279,"language":266,"meta":200,"style":200},"#...\nm = Message.new(\"hello\")\nm.instance_eval do\n  content.in_groot         # => undefined method 'in_groot' for an instance of String (NoMethodError)\nend\n\nMessage.class_eval do\n  \"Hello world\".in_groot   # => undefined method 'in_groot' for an instance of String (NoMethodError)\nend\n",[229,4841,4842,4846,4864,4872,4882,4886,4890,4900,4912],{"__ignoreMap":200},[270,4843,4844],{"class":272,"line":273},[270,4845,615],{"class":614},[270,4847,4848,4850,4852,4854,4856,4858,4860,4862],{"class":272,"line":201},[270,4849,4373],{"class":868},[270,4851,872],{"class":276},[270,4853,4378],{"class":315},[270,4855,44],{"class":292},[270,4857,349],{"class":276},[270,4859,352],{"class":292},[270,4861,4792],{"class":355},[270,4863,359],{"class":292},[270,4865,4866,4868,4870],{"class":272,"line":204},[270,4867,4803],{"class":292},[270,4869,4833],{"class":280},[270,4871,4233],{"class":276},[270,4873,4874,4877,4879],{"class":272,"line":307},[270,4875,4876],{"class":292},"  content.",[270,4878,4183],{"class":280},[270,4880,4881],{"class":614},"         # => undefined method 'in_groot' for an instance of String (NoMethodError)\n",[270,4883,4884],{"class":272,"line":324},[270,4885,333],{"class":276},[270,4887,4888],{"class":272,"line":330},[270,4889,339],{"emptyLinePlaceholder":211},[270,4891,4892,4894,4896,4898],{"class":272,"line":336},[270,4893,4418],{"class":315},[270,4895,44],{"class":292},[270,4897,4836],{"class":280},[270,4899,4233],{"class":276},[270,4901,4902,4905,4907,4909],{"class":272,"line":342},[270,4903,4904],{"class":355},"  \"Hello world\"",[270,4906,44],{"class":292},[270,4908,4183],{"class":280},[270,4910,4911],{"class":614},"   # => undefined method 'in_groot' for an instance of String (NoMethodError)\n",[270,4913,4914],{"class":272,"line":502},[270,4915,333],{"class":276},[16,4917,4918],{},"You're probably now wondering what if I used the refinements at the top level.",[16,4920,4921],{},"In that case, the refinements will be active everywhere under the file where it is used. If you load that file in some other file, it will not be active there.",[16,4923,4924,4925,4927],{},"Let's change our ",[229,4926,4279],{}," file so that it uses refinements on top level:",[261,4929,4931],{"className":263,"code":4930,"filename":4279,"language":266,"meta":200,"style":200},"require_relative 'refinements'\n\nusing GrootLanguageSupport\n\nclass Message\n  attr_reader :content\n\n  def initialize(content)\n    @content = content\n  end\n\n  def translate_to_groot\n    content.in_groot\n  end\n\n  def yield_content\n    yield content\n  end\nend\n\nputs \"World\".in_groot          # => I am Groot\n\nm = Message.new(\"Hello\")\nputs m.translate_to_groot      # => I am Groot\n\nm.yield_content do |content|\n puts content.in_groot         # => I am Groot\nend\n",[229,4932,4933,4939,4943,4949,4953,4959,4965,4969,4977,4985,4989,4993,4999,5005,5009,5013,5019,5025,5029,5033,5037,5051,5055,5073,5084,5088,5098,5110],{"__ignoreMap":200},[270,4934,4935,4937],{"class":272,"line":273},[270,4936,1274],{"class":276},[270,4938,4288],{"class":355},[270,4940,4941],{"class":272,"line":201},[270,4942,339],{"emptyLinePlaceholder":211},[270,4944,4945,4947],{"class":272,"line":204},[270,4946,4120],{"class":276},[270,4948,4222],{"class":315},[270,4950,4951],{"class":272,"line":307},[270,4952,339],{"emptyLinePlaceholder":211},[270,4954,4955,4957],{"class":272,"line":324},[270,4956,277],{"class":276},[270,4958,4299],{"class":280},[270,4960,4961,4963],{"class":272,"line":330},[270,4962,1185],{"class":276},[270,4964,4313],{"class":315},[270,4966,4967],{"class":272,"line":336},[270,4968,339],{"emptyLinePlaceholder":211},[270,4970,4971,4973,4975],{"class":272,"line":342},[270,4972,286],{"class":276},[270,4974,289],{"class":280},[270,4976,293],{"class":292},[270,4978,4979,4981,4983],{"class":272,"line":502},[270,4980,298],{"class":292},[270,4982,301],{"class":276},[270,4984,304],{"class":292},[270,4986,4987],{"class":272,"line":510},[270,4988,327],{"class":276},[270,4990,4991],{"class":272,"line":563},[270,4992,339],{"emptyLinePlaceholder":211},[270,4994,4995,4997],{"class":272,"line":568},[270,4996,286],{"class":276},[270,4998,4348],{"class":280},[270,5000,5001,5003],{"class":272,"line":1824},[270,5002,4353],{"class":292},[270,5004,4356],{"class":280},[270,5006,5007],{"class":272,"line":1829},[270,5008,327],{"class":276},[270,5010,5011],{"class":272,"line":1842},[270,5012,339],{"emptyLinePlaceholder":211},[270,5014,5015,5017],{"class":272,"line":1847},[270,5016,286],{"class":276},[270,5018,4743],{"class":280},[270,5020,5021,5023],{"class":272,"line":2305},[270,5022,4761],{"class":276},[270,5024,304],{"class":292},[270,5026,5027],{"class":272,"line":2314},[270,5028,327],{"class":276},[270,5030,5031],{"class":272,"line":2320},[270,5032,333],{"class":276},[270,5034,5035],{"class":272,"line":2325},[270,5036,339],{"emptyLinePlaceholder":211},[270,5038,5039,5041,5044,5046,5048],{"class":272,"line":2790},[270,5040,3908],{"class":315},[270,5042,5043],{"class":355}," \"World\"",[270,5045,44],{"class":292},[270,5047,4183],{"class":280},[270,5049,5050],{"class":614},"          # => I am Groot\n",[270,5052,5053],{"class":272,"line":2795},[270,5054,339],{"emptyLinePlaceholder":211},[270,5056,5057,5059,5061,5063,5065,5067,5069,5071],{"class":272,"line":2801},[270,5058,4373],{"class":868},[270,5060,872],{"class":276},[270,5062,4378],{"class":315},[270,5064,44],{"class":292},[270,5066,349],{"class":276},[270,5068,352],{"class":292},[270,5070,4387],{"class":355},[270,5072,359],{"class":292},[270,5074,5075,5077,5079,5081],{"class":272,"line":2806},[270,5076,3908],{"class":315},[270,5078,4396],{"class":292},[270,5080,4399],{"class":280},[270,5082,5083],{"class":614},"      # => I am Groot\n",[270,5085,5086],{"class":272,"line":2816},[270,5087,339],{"emptyLinePlaceholder":211},[270,5089,5090,5092,5094,5096],{"class":272,"line":2830},[270,5091,4803],{"class":292},[270,5093,4806],{"class":280},[270,5095,2299],{"class":276},[270,5097,4811],{"class":292},[270,5099,5100,5103,5105,5107],{"class":272,"line":2842},[270,5101,5102],{"class":315}," puts",[270,5104,4751],{"class":292},[270,5106,4183],{"class":280},[270,5108,5109],{"class":614},"         # => I am Groot\n",[270,5111,5112],{"class":272,"line":2848},[270,5113,333],{"class":276},[16,5115,5116,5117,790],{},"Now let's require this file in some other file, let's say ",[229,5118,5119],{},"message_processor.rb",[261,5121,5123],{"className":263,"code":5122,"filename":5119,"language":266,"meta":200,"style":200},"require_relative 'message'\n\nm = Message.new(\"Hello\")\nputs m.translate_to_groot     # => I am Groot\n\nm.yield_content do |content|\n  puts content.in_groot       # => undefined method 'in_groot' for an instance of String (NoMethodError)\nend\n\nputs \"World\".in_groot         # => undefined method 'in_groot' for an instance of String (NoMethodError)\n",[229,5124,5125,5132,5136,5154,5165,5169,5179,5190,5194,5198],{"__ignoreMap":200},[270,5126,5127,5129],{"class":272,"line":273},[270,5128,1274],{"class":276},[270,5130,5131],{"class":355}," 'message'\n",[270,5133,5134],{"class":272,"line":201},[270,5135,339],{"emptyLinePlaceholder":211},[270,5137,5138,5140,5142,5144,5146,5148,5150,5152],{"class":272,"line":204},[270,5139,4373],{"class":868},[270,5141,872],{"class":276},[270,5143,4378],{"class":315},[270,5145,44],{"class":292},[270,5147,349],{"class":276},[270,5149,352],{"class":292},[270,5151,4387],{"class":355},[270,5153,359],{"class":292},[270,5155,5156,5158,5160,5162],{"class":272,"line":307},[270,5157,3908],{"class":315},[270,5159,4396],{"class":292},[270,5161,4399],{"class":280},[270,5163,5164],{"class":614},"     # => I am Groot\n",[270,5166,5167],{"class":272,"line":324},[270,5168,339],{"emptyLinePlaceholder":211},[270,5170,5171,5173,5175,5177],{"class":272,"line":330},[270,5172,4803],{"class":292},[270,5174,4806],{"class":280},[270,5176,2299],{"class":276},[270,5178,4811],{"class":292},[270,5180,5181,5183,5185,5187],{"class":272,"line":336},[270,5182,4816],{"class":315},[270,5184,4751],{"class":292},[270,5186,4183],{"class":280},[270,5188,5189],{"class":614},"       # => undefined method 'in_groot' for an instance of String (NoMethodError)\n",[270,5191,5192],{"class":272,"line":342},[270,5193,333],{"class":276},[270,5195,5196],{"class":272,"line":502},[270,5197,339],{"emptyLinePlaceholder":211},[270,5199,5200,5202,5204,5206,5208],{"class":272,"line":510},[270,5201,3908],{"class":315},[270,5203,5043],{"class":355},[270,5205,44],{"class":292},[270,5207,4183],{"class":280},[270,5209,4881],{"class":614},[16,5211,5212,5213,5216],{},"So, while using our refinements, you have to activate it explicitly with ",[229,5214,5215],{},"using GrootLanguageSupport"," in wherever the places you need it.",[16,5218,5219],{},"This one line rule may be annoying to keep track of while writing code, but it improves the readability of the code by stating what are all the classes are using the refinements and in which module they were refined.",[16,5221,5222],{},"This is why refinements are favored over monkey patching. You have to keep in mind that they still violate the Open/Closed Principle, but in a more careful, declarative way.",[16,5224,5225],{},"If you are still not satisfied with refinements and need a \"true\" alternative to monkey patching, you can always create a subclass.",[261,5227,5230],{"className":263,"code":5228,"filename":5229,"language":266,"meta":200,"style":200},"class MultilingualString \u003C String\n  def in_groot\n    \"I am Groot\"\n  end\nend\n\nstr = MultilingualString.new('hello')\nstr.in_groot    # => I am Groot\n","multilingual_string.rb",[229,5231,5232,5244,5250,5254,5258,5262,5266,5286],{"__ignoreMap":200},[270,5233,5234,5236,5239,5241],{"class":272,"line":273},[270,5235,277],{"class":276},[270,5237,5238],{"class":280}," MultilingualString",[270,5240,4539],{"class":292},[270,5242,5243],{"class":280},"String\n",[270,5245,5246,5248],{"class":272,"line":201},[270,5247,286],{"class":276},[270,5249,4156],{"class":280},[270,5251,5252],{"class":272,"line":204},[270,5253,4161],{"class":355},[270,5255,5256],{"class":272,"line":307},[270,5257,327],{"class":276},[270,5259,5260],{"class":272,"line":324},[270,5261,333],{"class":276},[270,5263,5264],{"class":272,"line":330},[270,5265,339],{"emptyLinePlaceholder":211},[270,5267,5268,5271,5273,5275,5277,5279,5281,5284],{"class":272,"line":336},[270,5269,5270],{"class":868},"str",[270,5272,872],{"class":276},[270,5274,5238],{"class":315},[270,5276,44],{"class":292},[270,5278,349],{"class":276},[270,5280,352],{"class":292},[270,5282,5283],{"class":355},"'hello'",[270,5285,359],{"class":292},[270,5287,5288,5291,5293],{"class":272,"line":342},[270,5289,5290],{"class":292},"str.",[270,5292,4183],{"class":280},[270,5294,4756],{"class":614},[261,5296,5298],{"className":263,"code":5297,"filename":4279,"language":266,"meta":200,"style":200},"require_relative 'multilingual_string'\n\nclass Message\n  attr_reader :content\n\n  def initialize(content)\n    @content = content\n  end\n\n  def translate_to_groot\n    content.in_groot\n  end\nend\n\nmc = MultilingualString.new(\"hello\")\nm = Message.new(mc)\nputs m.translate_to_groot # => I am Groot\n",[229,5299,5300,5307,5311,5317,5323,5327,5335,5343,5347,5351,5357,5363,5367,5371,5375,5394,5409],{"__ignoreMap":200},[270,5301,5302,5304],{"class":272,"line":273},[270,5303,1274],{"class":276},[270,5305,5306],{"class":355}," 'multilingual_string'\n",[270,5308,5309],{"class":272,"line":201},[270,5310,339],{"emptyLinePlaceholder":211},[270,5312,5313,5315],{"class":272,"line":204},[270,5314,277],{"class":276},[270,5316,4299],{"class":280},[270,5318,5319,5321],{"class":272,"line":307},[270,5320,1185],{"class":276},[270,5322,4313],{"class":315},[270,5324,5325],{"class":272,"line":324},[270,5326,339],{"emptyLinePlaceholder":211},[270,5328,5329,5331,5333],{"class":272,"line":330},[270,5330,286],{"class":276},[270,5332,289],{"class":280},[270,5334,293],{"class":292},[270,5336,5337,5339,5341],{"class":272,"line":336},[270,5338,298],{"class":292},[270,5340,301],{"class":276},[270,5342,304],{"class":292},[270,5344,5345],{"class":272,"line":342},[270,5346,327],{"class":276},[270,5348,5349],{"class":272,"line":502},[270,5350,339],{"emptyLinePlaceholder":211},[270,5352,5353,5355],{"class":272,"line":510},[270,5354,286],{"class":276},[270,5356,4348],{"class":280},[270,5358,5359,5361],{"class":272,"line":563},[270,5360,4353],{"class":292},[270,5362,4356],{"class":280},[270,5364,5365],{"class":272,"line":568},[270,5366,327],{"class":276},[270,5368,5369],{"class":272,"line":1824},[270,5370,333],{"class":276},[270,5372,5373],{"class":272,"line":1829},[270,5374,339],{"emptyLinePlaceholder":211},[270,5376,5377,5380,5382,5384,5386,5388,5390,5392],{"class":272,"line":1842},[270,5378,5379],{"class":868},"mc",[270,5381,872],{"class":276},[270,5383,5238],{"class":315},[270,5385,44],{"class":292},[270,5387,349],{"class":276},[270,5389,352],{"class":292},[270,5391,4792],{"class":355},[270,5393,359],{"class":292},[270,5395,5396,5398,5400,5402,5404,5406],{"class":272,"line":1847},[270,5397,4373],{"class":868},[270,5399,872],{"class":276},[270,5401,4378],{"class":315},[270,5403,44],{"class":292},[270,5405,349],{"class":276},[270,5407,5408],{"class":292},"(mc)\n",[270,5410,5411,5413,5415,5417],{"class":272,"line":2305},[270,5412,3908],{"class":315},[270,5414,4396],{"class":292},[270,5416,4399],{"class":280},[270,5418,4402],{"class":614},[16,5420,5421],{},"Of course, this won't offer you the syntactic advantages of creating a string with double quotes. But it is clean and does not violate any design principle. So in the end, it's all about the trade-offs you are willing to take.",[1081,5423,5424],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":200,"searchDepth":201,"depth":201,"links":5426},[],"2026-02-22","Learn how Ruby refinements work, including lexical scope, inheritance and block edge cases, top-level usage, and when to prefer them over monkey patching.",{},"/blogs/refinements_and_the_chamber_of_edge_cases",{"title":4103,"description":5428},"blogs/refinements_and_the_chamber_of_edge_cases",[266],"2UDC6exPLF_HsGJYH7yX3VQ8b7958s8NGgh1M9tusNA",{"id":5436,"title":5437,"body":5438,"date":6835,"description":6836,"extension":209,"meta":6837,"navigation":211,"path":6838,"seo":6839,"stem":6840,"tags":6841,"__hash__":6843},"blogs/blogs/the_return_of_the_proc.md","The Return of the Proc",{"type":8,"value":5439,"toc":6829},[5440,5450,5453,5465,5475,5479,5487,5564,5567,5584,5635,5649,5723,5736,5779,5782,5786,5794,5803,5878,5948,5957,5968,5976,5980,5986,5998,6056,6066,6071,6126,6135,6143,6201,6210,6213,6215,6218,6221,6312,6332,6339,6345,6348,6367,6380,6401,6407,6424,6471,6488,6503,6520,6565,6568,6583,6596,6738,6791,6803,6818,6826],[16,5441,5442,5443,236,5446,5449],{},"Have you ever been asked what the differences between ",[229,5444,5445],{},"proc",[229,5447,5448],{},"lambda"," are? I’m sure the developers intrigued by Ruby closures have been asked this—and are probably capable of answering it. Let’s talk about those differences and take a closer look at one in particular.",[16,5451,5452],{},"There are three main differences (or two, if you don’t think the first one counts):",[5454,5455,5456,5459,5462],"ol",{},[748,5457,5458],{},"Instantiation",[748,5460,5461],{},"Arity check",[748,5463,5464],{},"Return behavior",[241,5466,5467],{"type":243},[16,5468,5469,5470,5474],{},"If you already know the basics of these differences, skip ahead to the ",[20,5471,5473],{"href":5472},"/blogs/the_return_of_the_proc#the-return-of-the-proc","main section"," of this blog.",[11,5476,5478],{"id":5477},"_1-instantiation","1. Instantiation",[16,5480,5481,5482,5484,5485,790],{},"These are the common ways to instantiate a ",[229,5483,5445],{}," and a ",[229,5486,5448],{},[261,5488,5490],{"className":263,"code":5489,"filename":265,"language":266,"meta":200,"style":200},"# proc\nProc.new { |x| x * 2 }\nproc { |x| x * 2 }\n\n# lambda\nlambda { |x| x * 2 }\n->(x) { x * 2 }\n",[229,5491,5492,5497,5517,5529,5533,5538,5550],{"__ignoreMap":200},[270,5493,5494],{"class":272,"line":273},[270,5495,5496],{"class":614},"# proc\n",[270,5498,5499,5502,5504,5506,5509,5511,5514],{"class":272,"line":201},[270,5500,5501],{"class":315},"Proc",[270,5503,44],{"class":292},[270,5505,349],{"class":276},[270,5507,5508],{"class":292}," { |x| x ",[270,5510,1353],{"class":276},[270,5512,5513],{"class":315}," 2",[270,5515,5516],{"class":292}," }\n",[270,5518,5519,5521,5523,5525,5527],{"class":272,"line":204},[270,5520,5445],{"class":315},[270,5522,5508],{"class":292},[270,5524,1353],{"class":276},[270,5526,5513],{"class":315},[270,5528,5516],{"class":292},[270,5530,5531],{"class":272,"line":307},[270,5532,339],{"emptyLinePlaceholder":211},[270,5534,5535],{"class":272,"line":324},[270,5536,5537],{"class":614},"# lambda\n",[270,5539,5540,5542,5544,5546,5548],{"class":272,"line":330},[270,5541,5448],{"class":315},[270,5543,5508],{"class":292},[270,5545,1353],{"class":276},[270,5547,5513],{"class":315},[270,5549,5516],{"class":292},[270,5551,5552,5555,5558,5560,5562],{"class":272,"line":336},[270,5553,5554],{"class":315},"->",[270,5556,5557],{"class":292},"(x) { x ",[270,5559,1353],{"class":276},[270,5561,5513],{"class":315},[270,5563,5516],{"class":292},[16,5565,5566],{},"Although the syntax differs, that’s not the only distinction in instantiation. These are explicit instantiations. There are two implicit instantiation scenarios.",[16,5568,5569,5570,5573,5574,754,5576,5578,5579,5581,5582,44],{},"The first occurs when you pass a ",[229,5571,5572],{},"block"," (not a ",[229,5575,5445],{},[229,5577,5448],{},", but a ",[229,5580,5572],{},") to a method, it gets captured as a ",[229,5583,5445],{},[261,5585,5587],{"className":263,"code":5586,"filename":265,"language":266,"meta":200,"style":200},"def what_am_i_giving_you(&block)\n  p block\nend\n\nwhat_am_i_giving_you { |x| x * 2 } # => #\u003CProc:0x000000010cb7df88>\n",[229,5588,5589,5605,5613,5617,5621],{"__ignoreMap":200},[270,5590,5591,5594,5597,5599,5602],{"class":272,"line":273},[270,5592,5593],{"class":276},"def",[270,5595,5596],{"class":280}," what_am_i_giving_you",[270,5598,352],{"class":292},[270,5600,5601],{"class":276},"&",[270,5603,5604],{"class":292},"block)\n",[270,5606,5607,5610],{"class":272,"line":201},[270,5608,5609],{"class":315},"  p",[270,5611,5612],{"class":292}," block\n",[270,5614,5615],{"class":272,"line":204},[270,5616,333],{"class":276},[270,5618,5619],{"class":272,"line":307},[270,5620,339],{"emptyLinePlaceholder":211},[270,5622,5623,5626,5628,5630,5632],{"class":272,"line":324},[270,5624,5625],{"class":292},"what_am_i_giving_you { |x| x ",[270,5627,1353],{"class":276},[270,5629,5513],{"class":315},[270,5631,1599],{"class":292},[270,5633,5634],{"class":614},"# => #\u003CProc:0x000000010cb7df88>\n",[16,5636,5637,5638,754,5640,5642,5643,5645,5646,5648],{},"However, if you explicitly convert a ",[229,5639,5445],{},[229,5641,5448],{}," to a ",[229,5644,5572],{}," using the ",[229,5647,5601],{}," indicator in a method call, the original behavior is preserved.",[261,5650,5652],{"className":263,"code":5651,"filename":265,"language":266,"meta":200,"style":200},"pr = proc { |x| x * 2 }\nla = lambda { |x| x * 2 }\n\nwhat_am_i_giving_you(&pr) # => #\u003CProc:0x000000010cf51330>\nwhat_am_i_giving_you(&la) # => #\u003CProc:0x000000010cf51268 (lambda)>\n",[229,5653,5654,5672,5690,5694,5709],{"__ignoreMap":200},[270,5655,5656,5659,5661,5664,5666,5668,5670],{"class":272,"line":273},[270,5657,5658],{"class":868},"pr",[270,5660,872],{"class":276},[270,5662,5663],{"class":315}," proc",[270,5665,5508],{"class":292},[270,5667,1353],{"class":276},[270,5669,5513],{"class":315},[270,5671,5516],{"class":292},[270,5673,5674,5677,5679,5682,5684,5686,5688],{"class":272,"line":201},[270,5675,5676],{"class":868},"la",[270,5678,872],{"class":276},[270,5680,5681],{"class":315}," lambda",[270,5683,5508],{"class":292},[270,5685,1353],{"class":276},[270,5687,5513],{"class":315},[270,5689,5516],{"class":292},[270,5691,5692],{"class":272,"line":204},[270,5693,339],{"emptyLinePlaceholder":211},[270,5695,5696,5699,5701,5703,5706],{"class":272,"line":307},[270,5697,5698],{"class":280},"what_am_i_giving_you",[270,5700,352],{"class":292},[270,5702,5601],{"class":276},[270,5704,5705],{"class":292},"pr) ",[270,5707,5708],{"class":614},"# => #\u003CProc:0x000000010cf51330>\n",[270,5710,5711,5713,5715,5717,5720],{"class":272,"line":324},[270,5712,5698],{"class":280},[270,5714,352],{"class":292},[270,5716,5601],{"class":276},[270,5718,5719],{"class":292},"la) ",[270,5721,5722],{"class":614},"# => #\u003CProc:0x000000010cf51268 (lambda)>\n",[16,5724,5725,5726,5729,5730,5733,5734,44],{},"The second one occurs when you call ",[229,5727,5728],{},"#to_proc"," on a ",[229,5731,5732],{},"Method",". It will always return a ",[229,5735,5448],{},[261,5737,5739],{"className":263,"code":5738,"filename":265,"language":266,"meta":200,"style":200},"def double_it(x) = x * 2\nmethod(:double_it).to_proc # => #\u003CProc:0x0000000109bfc390 (lambda)>\n",[229,5740,5741,5761],{"__ignoreMap":200},[270,5742,5743,5745,5748,5751,5753,5756,5758],{"class":272,"line":273},[270,5744,5593],{"class":276},[270,5746,5747],{"class":280}," double_it",[270,5749,5750],{"class":292},"(x) ",[270,5752,301],{"class":276},[270,5754,5755],{"class":292}," x ",[270,5757,1353],{"class":276},[270,5759,5760],{"class":315}," 2\n",[270,5762,5763,5766,5768,5771,5773,5776],{"class":272,"line":201},[270,5764,5765],{"class":280},"method",[270,5767,352],{"class":292},[270,5769,5770],{"class":315},":double_it",[270,5772,27],{"class":292},[270,5774,5775],{"class":280},"to_proc",[270,5777,5778],{"class":614}," # => #\u003CProc:0x0000000109bfc390 (lambda)>\n",[16,5780,5781],{},"The reason for these different implicit conversions brings us to our second difference…",[11,5783,5785],{"id":5784},"_2-arity-check","2. Arity Check",[241,5787,5788],{"type":243},[16,5789,227,5790,5793],{},[229,5791,5792],{},"arity"," of a method indicates the number of arguments it accepts.",[16,5795,5796,5797,5799,5800,5802],{},"A ",[229,5798,5448],{}," enforces arity strictly, while a ",[229,5801,5445],{},"… doesn’t care.",[261,5804,5806],{"className":263,"code":5805,"filename":265,"language":266,"meta":200,"style":200},"la = lambda { |x| x * 2 }\nla.call(2) # => 4\nla.call() # => wrong number of arguments (given 0, expected 1) (ArgumentError)\nla.call(1, 2, 3) # => wrong number of arguments (given 3, expected 1) (ArgumentError)\n",[229,5807,5808,5824,5842,5854],{"__ignoreMap":200},[270,5809,5810,5812,5814,5816,5818,5820,5822],{"class":272,"line":273},[270,5811,5676],{"class":868},[270,5813,872],{"class":276},[270,5815,5681],{"class":315},[270,5817,5508],{"class":292},[270,5819,1353],{"class":276},[270,5821,5513],{"class":315},[270,5823,5516],{"class":292},[270,5825,5826,5829,5832,5834,5837,5839],{"class":272,"line":201},[270,5827,5828],{"class":292},"la.",[270,5830,5831],{"class":280},"call",[270,5833,352],{"class":292},[270,5835,5836],{"class":315},"2",[270,5838,3178],{"class":292},[270,5840,5841],{"class":614},"# => 4\n",[270,5843,5844,5846,5848,5851],{"class":272,"line":204},[270,5845,5828],{"class":292},[270,5847,5831],{"class":280},[270,5849,5850],{"class":292},"() ",[270,5852,5853],{"class":614},"# => wrong number of arguments (given 0, expected 1) (ArgumentError)\n",[270,5855,5856,5858,5860,5862,5864,5866,5868,5870,5873,5875],{"class":272,"line":307},[270,5857,5828],{"class":292},[270,5859,5831],{"class":280},[270,5861,352],{"class":292},[270,5863,3227],{"class":315},[270,5865,814],{"class":292},[270,5867,5836],{"class":315},[270,5869,814],{"class":292},[270,5871,5872],{"class":315},"3",[270,5874,3178],{"class":292},[270,5876,5877],{"class":614},"# => wrong number of arguments (given 3, expected 1) (ArgumentError)\n",[261,5879,5881],{"className":263,"code":5880,"filename":265,"language":266,"meta":200,"style":200},"pr = proc { |x| x * 2 }\npr.call(2) # => 4\npr.call() # => undefined method '*' for nil (NoMethodError)\npr.call(1, 2, 3) # => 2\n",[229,5882,5883,5899,5914,5925],{"__ignoreMap":200},[270,5884,5885,5887,5889,5891,5893,5895,5897],{"class":272,"line":273},[270,5886,5658],{"class":868},[270,5888,872],{"class":276},[270,5890,5663],{"class":315},[270,5892,5508],{"class":292},[270,5894,1353],{"class":276},[270,5896,5513],{"class":315},[270,5898,5516],{"class":292},[270,5900,5901,5904,5906,5908,5910,5912],{"class":272,"line":201},[270,5902,5903],{"class":292},"pr.",[270,5905,5831],{"class":280},[270,5907,352],{"class":292},[270,5909,5836],{"class":315},[270,5911,3178],{"class":292},[270,5913,5841],{"class":614},[270,5915,5916,5918,5920,5922],{"class":272,"line":204},[270,5917,5903],{"class":292},[270,5919,5831],{"class":280},[270,5921,5850],{"class":292},[270,5923,5924],{"class":614},"# => undefined method '*' for nil (NoMethodError)\n",[270,5926,5927,5929,5931,5933,5935,5937,5939,5941,5943,5945],{"class":272,"line":307},[270,5928,5903],{"class":292},[270,5930,5831],{"class":280},[270,5932,352],{"class":292},[270,5934,3227],{"class":315},[270,5936,814],{"class":292},[270,5938,5836],{"class":315},[270,5940,814],{"class":292},[270,5942,5872],{"class":315},[270,5944,3178],{"class":292},[270,5946,5947],{"class":614},"# => 2\n",[16,5949,5950,5951,5953,5954,5956],{},"No matter how many (or how few) arguments you pass to a ",[229,5952,5445],{},", it takes only what it needs. If it doesn’t have enough, it assigns ",[229,5955,3496],{}," to the missing arguments.",[16,5958,5959,5960,5729,5962,5964,5965,5967],{},"This is the reasons why calling ",[229,5961,5728],{},[229,5963,5732],{}," returns a ",[229,5966,5448],{}," because it must preserve method semantics such as strict arity and return behavior",[16,5969,5970,5971,5973,5974,44],{},"The lack of strict arity checking also makes ",[229,5972,5445],{}," suitable for implicit block-to-proc conversion, since there are use cases where we don’t need all the arguments yielded to a ",[229,5975,5572],{},[11,5977,5979],{"id":5978},"_3-return-behavior","3. Return Behavior",[16,5981,5982,5983,5985],{},"Let’s start with ",[229,5984,5448],{},", because it’s easier to explain.",[16,5987,5796,5988,5991,5992,5994,5995,5997],{},[229,5989,5990],{},"return"," statement inside a ",[229,5993,5448],{}," returns control to the line where the ",[229,5996,5448],{}," was called.",[261,5999,6001],{"className":263,"code":6000,"filename":265,"language":266,"meta":200,"style":200},"def some_method\n  la = lambda { return }\n  la.call\n  p \"prints this!\"\nend\n\nsome_method # => \"prints this!\"\n",[229,6002,6003,6010,6025,6033,6040,6044,6048],{"__ignoreMap":200},[270,6004,6005,6007],{"class":272,"line":273},[270,6006,5593],{"class":276},[270,6008,6009],{"class":280}," some_method\n",[270,6011,6012,6015,6017,6019,6021,6023],{"class":272,"line":201},[270,6013,6014],{"class":868},"  la",[270,6016,872],{"class":276},[270,6018,5681],{"class":315},[270,6020,1575],{"class":292},[270,6022,5990],{"class":276},[270,6024,5516],{"class":292},[270,6026,6027,6030],{"class":272,"line":204},[270,6028,6029],{"class":292},"  la.",[270,6031,6032],{"class":280},"call\n",[270,6034,6035,6037],{"class":272,"line":307},[270,6036,5609],{"class":315},[270,6038,6039],{"class":355}," \"prints this!\"\n",[270,6041,6042],{"class":272,"line":324},[270,6043,333],{"class":276},[270,6045,6046],{"class":272,"line":330},[270,6047,339],{"emptyLinePlaceholder":211},[270,6049,6050,6053],{"class":272,"line":336},[270,6051,6052],{"class":292},"some_method ",[270,6054,6055],{"class":614},"# => \"prints this!\"\n",[16,6057,5796,6058,5991,6060,6062,6063,6065],{},[229,6059,5990],{},[229,6061,5445],{},", on the other hand, returns from the context where the ",[229,6064,5445],{}," was defined. In other words, a non-local return.",[241,6067,6068],{"type":243},[16,6069,6070],{},"A return is non-local if it exits a scope other than the one in which it is written.",[261,6072,6074],{"className":263,"code":6073,"filename":265,"language":266,"meta":200,"style":200},"def some_method\n  pr = proc { return }\n  pr.call\n  p \"does not print this!\"\nend\n\nsome_method # => nil\n",[229,6075,6076,6082,6097,6104,6111,6115,6119],{"__ignoreMap":200},[270,6077,6078,6080],{"class":272,"line":273},[270,6079,5593],{"class":276},[270,6081,6009],{"class":280},[270,6083,6084,6087,6089,6091,6093,6095],{"class":272,"line":201},[270,6085,6086],{"class":868},"  pr",[270,6088,872],{"class":276},[270,6090,5663],{"class":315},[270,6092,1575],{"class":292},[270,6094,5990],{"class":276},[270,6096,5516],{"class":292},[270,6098,6099,6102],{"class":272,"line":204},[270,6100,6101],{"class":292},"  pr.",[270,6103,6032],{"class":280},[270,6105,6106,6108],{"class":272,"line":307},[270,6107,5609],{"class":315},[270,6109,6110],{"class":355}," \"does not print this!\"\n",[270,6112,6113],{"class":272,"line":324},[270,6114,333],{"class":276},[270,6116,6117],{"class":272,"line":330},[270,6118,339],{"emptyLinePlaceholder":211},[270,6120,6121,6123],{"class":272,"line":336},[270,6122,6052],{"class":292},[270,6124,6125],{"class":614},"# => nil\n",[16,6127,6128,6129,6131,6132,44],{},"Here, the return inside the ",[229,6130,5445],{}," performs a non-local return of the enclosing ",[229,6133,6134],{},"#some_method",[16,6136,6137,6138,6140,6141,790],{},"When I said context earlier, I didn’t mean only a method’s context, but any returnable context. For example, consider a ",[229,6139,5445],{}," defined inside the context of a ",[229,6142,5448],{},[261,6144,6146],{"className":263,"code":6145,"filename":265,"language":266,"meta":200,"style":200},"la = lambda do\n  pr = proc { return }\n  pr.call\n  p \"does not print this!\"\nend\n\nla.call # => nil\n",[229,6147,6148,6158,6172,6178,6184,6188,6192],{"__ignoreMap":200},[270,6149,6150,6152,6154,6156],{"class":272,"line":273},[270,6151,5676],{"class":868},[270,6153,872],{"class":276},[270,6155,5681],{"class":315},[270,6157,4233],{"class":276},[270,6159,6160,6162,6164,6166,6168,6170],{"class":272,"line":201},[270,6161,6086],{"class":868},[270,6163,872],{"class":276},[270,6165,5663],{"class":315},[270,6167,1575],{"class":292},[270,6169,5990],{"class":276},[270,6171,5516],{"class":292},[270,6173,6174,6176],{"class":272,"line":204},[270,6175,6101],{"class":292},[270,6177,6032],{"class":280},[270,6179,6180,6182],{"class":272,"line":307},[270,6181,5609],{"class":315},[270,6183,6110],{"class":355},[270,6185,6186],{"class":272,"line":324},[270,6187,333],{"class":276},[270,6189,6190],{"class":272,"line":330},[270,6191,339],{"emptyLinePlaceholder":211},[270,6193,6194,6196,6198],{"class":272,"line":336},[270,6195,5828],{"class":292},[270,6197,5831],{"class":280},[270,6199,6200],{"class":614}," # => nil\n",[16,6202,6203,6204,6206,6207,6209],{},"This also means that you can't call a ",[229,6205,5445],{}," with ",[229,6208,5990],{}," statement from top level execution or from console. Because there is no return target for those contexts.",[16,6211,6212],{},"When I first read about this, I shrugged it off, thinking it wasn’t something big to watch out for. Later, when I tried to take advantage of it's non-local return behaviour, it threw a brick in my face—and thus, we arrive at the title of this blog.",[81,6214],{},[11,6216,5437],{"id":6217},"the-return-of-the-proc",[16,6219,6220],{},"Try to guess the output of this code:",[261,6222,6224],{"className":263,"code":6223,"filename":265,"language":266,"meta":200,"style":200},"def create_a_doubler_proc\n  proc { |x| return x * 2 }\nend\n\ndef double_it(y)\n  func = create_a_doubler_proc\n  func.call(y)\n  p \"does not print this!\"\nend\n\ndouble_it(4)\n",[229,6225,6226,6233,6251,6255,6259,6268,6277,6286,6292,6296,6300],{"__ignoreMap":200},[270,6227,6228,6230],{"class":272,"line":273},[270,6229,5593],{"class":276},[270,6231,6232],{"class":280}," create_a_doubler_proc\n",[270,6234,6235,6238,6241,6243,6245,6247,6249],{"class":272,"line":201},[270,6236,6237],{"class":315},"  proc",[270,6239,6240],{"class":292}," { |x| ",[270,6242,5990],{"class":276},[270,6244,5755],{"class":292},[270,6246,1353],{"class":276},[270,6248,5513],{"class":315},[270,6250,5516],{"class":292},[270,6252,6253],{"class":272,"line":204},[270,6254,333],{"class":276},[270,6256,6257],{"class":272,"line":307},[270,6258,339],{"emptyLinePlaceholder":211},[270,6260,6261,6263,6265],{"class":272,"line":324},[270,6262,5593],{"class":276},[270,6264,5747],{"class":280},[270,6266,6267],{"class":292},"(y)\n",[270,6269,6270,6273,6275],{"class":272,"line":330},[270,6271,6272],{"class":868},"  func",[270,6274,872],{"class":276},[270,6276,6232],{"class":292},[270,6278,6279,6282,6284],{"class":272,"line":336},[270,6280,6281],{"class":292},"  func.",[270,6283,5831],{"class":280},[270,6285,6267],{"class":292},[270,6287,6288,6290],{"class":272,"line":342},[270,6289,5609],{"class":315},[270,6291,6110],{"class":355},[270,6293,6294],{"class":272,"line":502},[270,6295,333],{"class":276},[270,6297,6298],{"class":272,"line":510},[270,6299,339],{"emptyLinePlaceholder":211},[270,6301,6302,6305,6307,6310],{"class":272,"line":563},[270,6303,6304],{"class":280},"double_it",[270,6306,352],{"class":292},[270,6308,6309],{"class":315},"4",[270,6311,359],{"class":292},[16,6313,6314,6315,6317,6318,6320,6321,6323,6324,6327,6328,6331],{},"Instead of copying and executing it, let’s reason it out first. You’re calling a method that calls another method to get a ",[229,6316,5445],{},", then calling that returned ",[229,6319,5445],{}," with an argument. So the ",[229,6322,5990],{}," should be triggered inside ",[229,6325,6326],{},"#double_it",", return ",[229,6329,6330],{},"8",", and print nothing… right?",[16,6333,6334,6335,6338],{},"Now run it (",[35,6336,6337],{},"and brace yourself for the brick","):",[261,6340,6343],{"className":6341,"code":6342,"filename":265,"language":369,"meta":200},[366],"'block in Object#create_a_doubler_proc': unexpected return (LocalJumpError)\n",[229,6344,6342],{"__ignoreMap":200},[16,6346,6347],{},"Why?",[16,6349,6350,6351,6353,6354,6357,6358,6360,6361,6363,6364,6366],{},"Because the ",[229,6352,5990],{}," is triggered for the method ",[229,6355,6356],{},"#create_a_doubler_proc","—where the ",[229,6359,5445],{}," was defined—not for ",[229,6362,6326],{},", where the ",[229,6365,5445],{}," was used.",[16,6368,6369,6370,6373,6374,6376,6377,6379],{},"By the time the ",[229,6371,6372],{},"proc#call"," is called, ",[229,6375,6356],{}," has already finished executing and returned. there is no ",[229,6378,6356],{}," in the current stack, which is why the error was raised.",[16,6381,6382,6383,6385,6386,6388,6389,6391,6392,6394,6395,6397,6398,6400],{},"If you remove the ",[229,6384,5990],{}," statement, the ",[229,6387,5445],{}," won’t try to force a return. Instead, the value of the last expression inside the ",[229,6390,5445],{}," will be returned to where ",[229,6393,6372],{}," was invoked—similar to a ",[229,6396,5448],{},"’s ",[229,6399,5990],{}," behavior.",[16,6402,6403,6404,6406],{},"This behavior is why ",[229,6405,5445],{}," needs to be handled with extra care.",[16,6408,6409,6410,6412,6413,6415,6416,6418,6419,6421,6422,44],{},"In fact, you can never use a ",[229,6411,5445],{}," with an explicit ",[229,6414,5990],{}," if that ",[229,6417,5445],{}," is returned by another method. It's also why you cannot use ",[229,6420,5990],{}," inside the body of a method’s ",[229,6423,5572],{},[261,6425,6427],{"className":263,"code":6426,"filename":265,"language":266,"meta":200,"style":200},"def yielder\n  yield\nend\n\nyielder do\n  return true # => unexpected return (LocalJumpError)\nend\n",[229,6428,6429,6436,6441,6445,6449,6457,6467],{"__ignoreMap":200},[270,6430,6431,6433],{"class":272,"line":273},[270,6432,5593],{"class":276},[270,6434,6435],{"class":280}," yielder\n",[270,6437,6438],{"class":272,"line":201},[270,6439,6440],{"class":276},"  yield\n",[270,6442,6443],{"class":272,"line":204},[270,6444,333],{"class":276},[270,6446,6447],{"class":272,"line":307},[270,6448,339],{"emptyLinePlaceholder":211},[270,6450,6451,6454],{"class":272,"line":324},[270,6452,6453],{"class":292},"yielder ",[270,6455,6456],{"class":276},"do\n",[270,6458,6459,6462,6464],{"class":272,"line":330},[270,6460,6461],{"class":276},"  return",[270,6463,830],{"class":315},[270,6465,6466],{"class":614}," # => unexpected return (LocalJumpError)\n",[270,6468,6469],{"class":272,"line":336},[270,6470,333],{"class":276},[16,6472,6473,6474,6477,6478,6480,6481,6483,6484,6487],{},"If you ",[112,6475,6476],{},"have to"," use a ",[229,6479,5445],{}," and also need an explicit ",[229,6482,5990],{},"-like control flow, you can use the ",[229,6485,6486],{},"next"," statement instead.",[241,6489,6490],{"type":243},[16,6491,227,6492,6494,6495,754,6497,6499,6500,6502],{},[229,6493,6486],{}," statement not only moves to the next iteration, but also returns a value from the current iteration (i.e, exits the current ",[229,6496,5572],{},[229,6498,5445],{}," invocation with a value.). Like ",[229,6501,5990],{},", it can also be called with arguments.",[16,6504,6505,6506,814,6508,6510,6511,814,6513,6515,6516,6397,6518,6400],{},"Inside a ",[229,6507,5445],{},[229,6509,5448],{},", or ",[229,6512,5572],{},[229,6514,6486],{}," mirrors a ",[229,6517,5448],{},[229,6519,5990],{},[261,6521,6523],{"className":263,"code":6522,"filename":265,"language":266,"meta":200,"style":200},"def yielder\n  yield\nend\n\nyielder do\n  next true\nend # => true\n",[229,6524,6525,6531,6535,6539,6543,6549,6557],{"__ignoreMap":200},[270,6526,6527,6529],{"class":272,"line":273},[270,6528,5593],{"class":276},[270,6530,6435],{"class":280},[270,6532,6533],{"class":272,"line":201},[270,6534,6440],{"class":276},[270,6536,6537],{"class":272,"line":204},[270,6538,333],{"class":276},[270,6540,6541],{"class":272,"line":307},[270,6542,339],{"emptyLinePlaceholder":211},[270,6544,6545,6547],{"class":272,"line":324},[270,6546,6453],{"class":292},[270,6548,6456],{"class":276},[270,6550,6551,6554],{"class":272,"line":330},[270,6552,6553],{"class":276},"  next",[270,6555,6556],{"class":315}," true\n",[270,6558,6559,6562],{"class":272,"line":336},[270,6560,6561],{"class":276},"end",[270,6563,6564],{"class":614}," # => true\n",[16,6566,6567],{},"…",[16,6569,6570,6573,6574,6582],{},[112,6571,6572],{},"However,"," if you decide to embrace your dark side and write code that reads like an unreadable spell, you ",[35,6575,6576,6577,6397,6579,6581],{},"can exploit a ",[229,6578,5445],{},[229,6580,5990],{}," behavior"," in certain cases.",[16,6584,6585,6586,754,6589,6592,6593,27],{},"For example, when you want something like ",[229,6587,6588],{},"throw",[229,6590,6591],{},"raise",", but not quite—or don’t actually want to use either (",[35,6594,6595],{},"like a menace to readability!",[261,6597,6599],{"className":263,"code":6598,"filename":265,"language":266,"meta":200,"style":200},"def call_a_proc(func, arg)\n  result = func.call(arg)\n  p \"prints it only for odd numbers\"\n  result\nend\n\ndef double_only_if_odd(y)\n  pr = proc do |x|\n    return if x.even?\n    x * 2\n  end\n\n  result = call_a_proc(pr, y)\n\n  p \"prints it only for odd numbers\"\n  result\nensure\n  p \"prints it for all numbers\"\nend\n",[229,6600,6601,6611,6626,6633,6638,6642,6646,6655,6668,6680,6689,6693,6697,6708,6712,6718,6722,6727,6734],{"__ignoreMap":200},[270,6602,6603,6605,6608],{"class":272,"line":273},[270,6604,5593],{"class":276},[270,6606,6607],{"class":280}," call_a_proc",[270,6609,6610],{"class":292},"(func, arg)\n",[270,6612,6613,6616,6618,6621,6623],{"class":272,"line":201},[270,6614,6615],{"class":868},"  result",[270,6617,872],{"class":276},[270,6619,6620],{"class":292}," func.",[270,6622,5831],{"class":280},[270,6624,6625],{"class":292},"(arg)\n",[270,6627,6628,6630],{"class":272,"line":204},[270,6629,5609],{"class":315},[270,6631,6632],{"class":355}," \"prints it only for odd numbers\"\n",[270,6634,6635],{"class":272,"line":307},[270,6636,6637],{"class":292},"  result\n",[270,6639,6640],{"class":272,"line":324},[270,6641,333],{"class":276},[270,6643,6644],{"class":272,"line":330},[270,6645,339],{"emptyLinePlaceholder":211},[270,6647,6648,6650,6653],{"class":272,"line":336},[270,6649,5593],{"class":276},[270,6651,6652],{"class":280}," double_only_if_odd",[270,6654,6267],{"class":292},[270,6656,6657,6659,6661,6663,6665],{"class":272,"line":342},[270,6658,6086],{"class":868},[270,6660,872],{"class":276},[270,6662,5663],{"class":315},[270,6664,2299],{"class":276},[270,6666,6667],{"class":292}," |x|\n",[270,6669,6670,6672,6674,6677],{"class":272,"line":502},[270,6671,1794],{"class":276},[270,6673,3269],{"class":276},[270,6675,6676],{"class":292}," x.",[270,6678,6679],{"class":280},"even?\n",[270,6681,6682,6685,6687],{"class":272,"line":510},[270,6683,6684],{"class":292},"    x ",[270,6686,1353],{"class":276},[270,6688,5760],{"class":315},[270,6690,6691],{"class":272,"line":563},[270,6692,327],{"class":276},[270,6694,6695],{"class":272,"line":568},[270,6696,339],{"emptyLinePlaceholder":211},[270,6698,6699,6701,6703,6705],{"class":272,"line":1824},[270,6700,6615],{"class":868},[270,6702,872],{"class":276},[270,6704,6607],{"class":280},[270,6706,6707],{"class":292},"(pr, y)\n",[270,6709,6710],{"class":272,"line":1829},[270,6711,339],{"emptyLinePlaceholder":211},[270,6713,6714,6716],{"class":272,"line":1842},[270,6715,5609],{"class":315},[270,6717,6632],{"class":355},[270,6719,6720],{"class":272,"line":1847},[270,6721,6637],{"class":292},[270,6723,6724],{"class":272,"line":2305},[270,6725,6726],{"class":276},"ensure\n",[270,6728,6729,6731],{"class":272,"line":2314},[270,6730,5609],{"class":315},[270,6732,6733],{"class":355}," \"prints it for all numbers\"\n",[270,6735,6736],{"class":272,"line":2320},[270,6737,333],{"class":276},[261,6739,6741],{"className":263,"code":6740,"filename":265,"language":266,"meta":200,"style":200},"double_only_if_odd(2)\n# \"prints it for all numbers\"\n# => nil\ndouble_only_if_odd(3)\n# \"prints it only for odd numbers\"\n# \"prints it only for odd numbers\"\n# \"prints it for all numbers\"\n# => 6\n",[229,6742,6743,6754,6759,6763,6773,6778,6782,6786],{"__ignoreMap":200},[270,6744,6745,6748,6750,6752],{"class":272,"line":273},[270,6746,6747],{"class":280},"double_only_if_odd",[270,6749,352],{"class":292},[270,6751,5836],{"class":315},[270,6753,359],{"class":292},[270,6755,6756],{"class":272,"line":201},[270,6757,6758],{"class":614},"# \"prints it for all numbers\"\n",[270,6760,6761],{"class":272,"line":204},[270,6762,6125],{"class":614},[270,6764,6765,6767,6769,6771],{"class":272,"line":307},[270,6766,6747],{"class":280},[270,6768,352],{"class":292},[270,6770,5872],{"class":315},[270,6772,359],{"class":292},[270,6774,6775],{"class":272,"line":324},[270,6776,6777],{"class":614},"# \"prints it only for odd numbers\"\n",[270,6779,6780],{"class":272,"line":330},[270,6781,6777],{"class":614},[270,6783,6784],{"class":272,"line":336},[270,6785,6758],{"class":614},[270,6787,6788],{"class":272,"line":342},[270,6789,6790],{"class":614},"# => 6\n",[16,6792,6793,6794,814,6796,6798,6799,6802],{},"This flow can be implemented far more cleanly using ",[229,6795,6591],{},[229,6797,6588],{},", or simple ",[229,6800,6801],{},"if"," conditions.",[16,6804,6805,6806,6808,6809,6811,6812,6814,6815,6817],{},"The weird ",[229,6807,5990],{}," behavior of ",[229,6810,5445],{}," is often mentioned, but rarely explained in depth. But that doesn’t make ",[229,6813,5445],{}," unusable—it just means it has sharp edges. A ",[229,6816,5445],{}," has its own purposes and valid use cases.",[16,6819,6820,6821,3162,6823,27],{},"Like I said, you just have to be extra careful when returning from a ",[229,6822,5445],{},[35,6824,6825],{},"and lookout for flying bricks >_\u003C",[1081,6827,6828],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":200,"searchDepth":201,"depth":201,"links":6830},[6831,6832,6833,6834],{"id":5477,"depth":204,"text":5478},{"id":5784,"depth":204,"text":5785},{"id":5978,"depth":204,"text":5979},{"id":6217,"depth":204,"text":5437},"2026-02-08","Understand the real differences between Ruby proc and lambda, including arity, instantiation, and the infamous non-local return, plus its workarounds.",{},"/blogs/the_return_of_the_proc",{"title":5437,"description":6836},"blogs/the_return_of_the_proc",[266,6842],"closures","pYuYjXjuczbmJUXDl85_vQZZJJFDJYjrSEQrGOS3Ebs",{"id":6845,"title":6846,"body":6847,"date":7494,"description":7495,"extension":209,"meta":7496,"navigation":211,"path":7497,"seo":7498,"stem":7499,"tags":7500,"__hash__":7501},"blogs/blogs/mixing_into_every_object_in_ruby.md","Mixing into every Object in Ruby (Without Realizing It!)",{"type":8,"value":6848,"toc":7492},[6849,6852,6858,6911,6914,6920,6927,6959,6966,7021,7103,7106,7111,7114,7117,7154,7157,7170,7173,7180,7191,7203,7210,7213,7262,7265,7311,7318,7321,7373,7376,7408,7416,7437,7457,7474,7483,7489],[16,6850,6851],{},"In Ruby, mixins provide a convenient way to extract shared logic into modules for reuse. At some point, most codebases try to make that shared logic widely accessible. That decision, if carried out without knowing the consequences, can haunt your application like a glitch in the fabric of your codebase",[16,6853,6854,6855,790],{},"Consider a module named ",[229,6856,6857],{},"CommonHelper",[261,6859,6862],{"className":263,"code":6860,"filename":6861,"language":266,"meta":200,"style":200},"module CommonHelper\n  def current_time_in_chicago\n    TZInfo::Timezone.get('America/Chicago').now\n  end\nend\n","app/helpers/common_helper.rb",[229,6863,6864,6871,6878,6903,6907],{"__ignoreMap":200},[270,6865,6866,6868],{"class":272,"line":273},[270,6867,4219],{"class":276},[270,6869,6870],{"class":280}," CommonHelper\n",[270,6872,6873,6875],{"class":272,"line":201},[270,6874,286],{"class":276},[270,6876,6877],{"class":280}," current_time_in_chicago\n",[270,6879,6880,6883,6886,6889,6891,6894,6896,6899,6901],{"class":272,"line":204},[270,6881,6882],{"class":315},"    TZInfo",[270,6884,6885],{"class":292},"::",[270,6887,6888],{"class":315},"Timezone",[270,6890,44],{"class":292},[270,6892,6893],{"class":280},"get",[270,6895,352],{"class":292},[270,6897,6898],{"class":355},"'America/Chicago'",[270,6900,27],{"class":292},[270,6902,321],{"class":280},[270,6904,6905],{"class":272,"line":307},[270,6906,327],{"class":276},[270,6908,6909],{"class":272,"line":324},[270,6910,333],{"class":276},[16,6912,6913],{},"A module is usually mixed into a class or in some weird use case to an object. The purpose of a mixin is mainly to share behaviours and compose interfaces. In larger applications (like in Rails), it is also for code organization.",[16,6915,6916,6917,6919],{},"Most beginners will forget the former purpose and solely create modules for the latter. In our case, we have a module called ",[229,6918,6857],{}," created, as the name says, to be a common helper.",[16,6921,6922,6923,6926],{},"After creating this module, considering its name and purpose, you might want it to be available everywhere and try to include it at the top level in some files. Let's say in the ",[229,6924,6925],{},"application.rb"," file of a Rails application.",[261,6928,6931],{"className":263,"code":6929,"filename":6930,"language":266,"meta":200,"style":200},"require_relative '../app/helpers/common_helper'\n\ninclude CommonHelper\n\n#...\n","config/application.rb",[229,6932,6933,6940,6944,6951,6955],{"__ignoreMap":200},[270,6934,6935,6937],{"class":272,"line":273},[270,6936,1274],{"class":276},[270,6938,6939],{"class":355}," '../app/helpers/common_helper'\n",[270,6941,6942],{"class":272,"line":201},[270,6943,339],{"emptyLinePlaceholder":211},[270,6945,6946,6949],{"class":272,"line":204},[270,6947,6948],{"class":276},"include",[270,6950,6870],{"class":315},[270,6952,6953],{"class":272,"line":307},[270,6954,339],{"emptyLinePlaceholder":211},[270,6956,6957],{"class":272,"line":324},[270,6958,615],{"class":614},[16,6960,6961,6962,6965],{},"Now, the method ",[229,6963,6964],{},"current_time_in_chicago"," will be available throughout the application.",[261,6967,6970],{"className":263,"code":6968,"filename":6969,"language":266,"meta":200,"style":200},"class Resource \u003C ApplicationRecord\n  #...\n  def update_processed_at\n    update(processed_at: current_time_in_chicago)\n  end\n  #...\nend\n","app/models/resource.rb",[229,6971,6972,6984,6989,6996,7009,7013,7017],{"__ignoreMap":200},[270,6973,6974,6976,6979,6981],{"class":272,"line":273},[270,6975,277],{"class":276},[270,6977,6978],{"class":280}," Resource",[270,6980,4539],{"class":292},[270,6982,6983],{"class":280},"ApplicationRecord\n",[270,6985,6986],{"class":272,"line":201},[270,6987,6988],{"class":614},"  #...\n",[270,6990,6991,6993],{"class":272,"line":204},[270,6992,286],{"class":276},[270,6994,6995],{"class":280}," update_processed_at\n",[270,6997,6998,7001,7003,7006],{"class":272,"line":307},[270,6999,7000],{"class":280},"    update",[270,7002,352],{"class":292},[270,7004,7005],{"class":315},"processed_at:",[270,7007,7008],{"class":292}," current_time_in_chicago)\n",[270,7010,7011],{"class":272,"line":324},[270,7012,327],{"class":276},[270,7014,7015],{"class":272,"line":330},[270,7016,6988],{"class":614},[270,7018,7019],{"class":272,"line":336},[270,7020,333],{"class":276},[261,7022,7025],{"className":263,"code":7023,"filename":7024,"language":266,"meta":200,"style":200},"class ResourcesController \u003C ApplicationController\n  #...\n  def index\n    @resources =\n      Resource\n        .where(updated_at: (current_time_in_chicago - 1.day)..)\n  end\n  #...\nend\n","app/controllers/resources_controller.rb",[229,7026,7027,7039,7043,7050,7058,7063,7091,7095,7099],{"__ignoreMap":200},[270,7028,7029,7031,7034,7036],{"class":272,"line":273},[270,7030,277],{"class":276},[270,7032,7033],{"class":280}," ResourcesController",[270,7035,4539],{"class":292},[270,7037,7038],{"class":280},"ApplicationController\n",[270,7040,7041],{"class":272,"line":201},[270,7042,6988],{"class":614},[270,7044,7045,7047],{"class":272,"line":204},[270,7046,286],{"class":276},[270,7048,7049],{"class":280}," index\n",[270,7051,7052,7055],{"class":272,"line":307},[270,7053,7054],{"class":292},"    @resources ",[270,7056,7057],{"class":276},"=\n",[270,7059,7060],{"class":272,"line":324},[270,7061,7062],{"class":315},"      Resource\n",[270,7064,7065,7068,7070,7072,7075,7078,7081,7083,7085,7088],{"class":272,"line":330},[270,7066,7067],{"class":292},"        .",[270,7069,822],{"class":280},[270,7071,352],{"class":292},[270,7073,7074],{"class":315},"updated_at:",[270,7076,7077],{"class":292}," (current_time_in_chicago ",[270,7079,7080],{"class":276},"-",[270,7082,3184],{"class":315},[270,7084,44],{"class":292},[270,7086,7087],{"class":280},"day",[270,7089,7090],{"class":292},")..)\n",[270,7092,7093],{"class":272,"line":336},[270,7094,327],{"class":276},[270,7096,7097],{"class":272,"line":342},[270,7098,6988],{"class":614},[270,7100,7101],{"class":272,"line":502},[270,7102,333],{"class":276},[16,7104,7105],{},"Quite useful, right?",[16,7107,7108],{},[112,7109,7110],{},"No, it is not",[16,7112,7113],{},"Because you just shot yourself in the foot with a machine gun—and the most horrifying part is that it doesn’t hurt you.",[16,7115,7116],{},"Try the following in the Rails console:",[261,7118,7121],{"className":263,"code":7119,"filename":7120,"language":266,"meta":200,"style":200},"current_time_in_chicago\nCommonHelper.current_time_in_chicago\nTime.current_time_in_chicago\nDate.current_time_in_chicago\n",">_ Rails Console",[229,7122,7123,7128,7136,7145],{"__ignoreMap":200},[270,7124,7125],{"class":272,"line":273},[270,7126,7127],{"class":292},"current_time_in_chicago\n",[270,7129,7130,7132,7134],{"class":272,"line":201},[270,7131,6857],{"class":315},[270,7133,44],{"class":292},[270,7135,7127],{"class":280},[270,7137,7138,7141,7143],{"class":272,"line":204},[270,7139,7140],{"class":315},"Time",[270,7142,44],{"class":292},[270,7144,7127],{"class":280},[270,7146,7147,7150,7152],{"class":272,"line":307},[270,7148,7149],{"class":315},"Date",[270,7151,44],{"class":292},[270,7153,7127],{"class":280},[16,7155,7156],{},"All of them will return the current time in Chicago. If that hasn't weirded you out yet, try the following:",[261,7158,7160],{"className":263,"code":7159,"filename":7120,"language":266,"meta":200,"style":200},"nil.current_time_in_chicago\n",[229,7161,7162],{"__ignoreMap":200},[270,7163,7164,7166,7168],{"class":272,"line":273},[270,7165,3496],{"class":315},[270,7167,44],{"class":292},[270,7169,7127],{"class":280},[16,7171,7172],{},"Now you will realize something is fishy here. I'll explain why but let's refresh how mixins work first.",[1048,7174,7175],{},[16,7176,7177,7179],{},[229,7178,6948],{}," is used to mix in a module interface to any instances of a class. It can only be called from the scope of a class.",[1048,7181,7182],{},[16,7183,7184,7187,7188,7190],{},[229,7185,7186],{},"prepend"," works in the same way as ",[229,7189,6948],{}," but the difference is that if you prepend a module, methods defined in that module will appear first in the method lookup path of instances, instead of methods defined in the class. (it'll override the methods defined in the class)",[1048,7192,7193],{},[16,7194,7195,7198,7199,7202],{},[229,7196,7197],{},"extend"," is used to mix in a module to a single object. It can either be class or object (",[35,7200,7201],{},"inner me is screaming both are same!","). After extending, methods defined in the extended module will appear first in the method lookup path of the object.",[16,7204,7205,7206,7209],{},"All of them are basically methods provided by the ",[229,7207,7208],{},"Kernel"," module. Every time you use them, you are basically calling them in some scope. All of them will return the scope owner in which it's included, extended or prepended.",[16,7211,7212],{},"Consider a module and a class,",[261,7214,7217],{"className":263,"code":7215,"filename":7216,"language":266,"meta":200,"style":200},"module Foo\n  def ping\n    \"pong\"\n  end\nend\n\nclass Bar; end\n",">_ IRB Console",[229,7218,7219,7226,7233,7238,7242,7246,7250],{"__ignoreMap":200},[270,7220,7221,7223],{"class":272,"line":273},[270,7222,4219],{"class":276},[270,7224,7225],{"class":280}," Foo\n",[270,7227,7228,7230],{"class":272,"line":201},[270,7229,286],{"class":276},[270,7231,7232],{"class":280}," ping\n",[270,7234,7235],{"class":272,"line":204},[270,7236,7237],{"class":355},"    \"pong\"\n",[270,7239,7240],{"class":272,"line":307},[270,7241,327],{"class":276},[270,7243,7244],{"class":272,"line":324},[270,7245,333],{"class":276},[270,7247,7248],{"class":272,"line":330},[270,7249,339],{"emptyLinePlaceholder":211},[270,7251,7252,7254,7257,7260],{"class":272,"line":336},[270,7253,277],{"class":276},[270,7255,7256],{"class":280}," Bar",[270,7258,7259],{"class":292},"; ",[270,7261,333],{"class":276},[16,7263,7264],{},"On the class level,",[261,7266,7268],{"className":263,"code":7267,"filename":7216,"language":266,"meta":200,"style":200},"Bar.include Foo #=>  Bar  (instances of Bar will inherit `ping` method)\nBar.extend Foo #=> Bar  (Bar will inherit `ping` method)\nBar.prepend Foo #=> Bar  (instances of Bar will inherit `ping` method, Foo will be added first in the method lookup path of Bar instances)\n",[229,7269,7270,7285,7298],{"__ignoreMap":200},[270,7271,7272,7275,7277,7279,7282],{"class":272,"line":273},[270,7273,7274],{"class":315},"Bar",[270,7276,44],{"class":292},[270,7278,6948],{"class":276},[270,7280,7281],{"class":315}," Foo",[270,7283,7284],{"class":614}," #=>  Bar  (instances of Bar will inherit `ping` method)\n",[270,7286,7287,7289,7291,7293,7295],{"class":272,"line":201},[270,7288,7274],{"class":315},[270,7290,44],{"class":292},[270,7292,7197],{"class":276},[270,7294,7281],{"class":315},[270,7296,7297],{"class":614}," #=> Bar  (Bar will inherit `ping` method)\n",[270,7299,7300,7302,7304,7306,7308],{"class":272,"line":204},[270,7301,7274],{"class":315},[270,7303,44],{"class":292},[270,7305,7186],{"class":276},[270,7307,7281],{"class":315},[270,7309,7310],{"class":614}," #=> Bar  (instances of Bar will inherit `ping` method, Foo will be added first in the method lookup path of Bar instances)\n",[16,7312,7313,7314,7317],{},"Same logic applies for ",[229,7315,7316],{},"singleton"," classes as well.",[16,7319,7320],{},"On the object level,",[261,7322,7324],{"className":263,"code":7323,"filename":7216,"language":266,"meta":200,"style":200},"b = Bar.new\nb.include Foo #=>  Error: undefined method `include`\nb.prepend Foo #=>  Error: undefined method `prepend`\nb.extend Foo  #=> #\u003CBar:0x0000000128f21e70>  (Only the object b will inherit `ping` method)\n",[229,7325,7326,7339,7351,7362],{"__ignoreMap":200},[270,7327,7328,7331,7333,7335,7337],{"class":272,"line":273},[270,7329,7330],{"class":868},"b",[270,7332,872],{"class":276},[270,7334,7256],{"class":315},[270,7336,44],{"class":292},[270,7338,1425],{"class":276},[270,7340,7341,7344,7346,7348],{"class":272,"line":201},[270,7342,7343],{"class":292},"b.",[270,7345,6948],{"class":276},[270,7347,7281],{"class":315},[270,7349,7350],{"class":614}," #=>  Error: undefined method `include`\n",[270,7352,7353,7355,7357,7359],{"class":272,"line":204},[270,7354,7343],{"class":292},[270,7356,7186],{"class":276},[270,7358,7281],{"class":315},[270,7360,7361],{"class":614}," #=>  Error: undefined method `prepend`\n",[270,7363,7364,7366,7368,7370],{"class":272,"line":307},[270,7365,7343],{"class":292},[270,7367,7197],{"class":276},[270,7369,7281],{"class":315},[270,7371,7372],{"class":614},"  #=> #\u003CBar:0x0000000128f21e70>  (Only the object b will inherit `ping` method)\n",[16,7374,7375],{},"Let's see what it returns on the top level",[261,7377,7379],{"className":263,"code":7378,"filename":7216,"language":266,"meta":200,"style":200},"include Foo #=> Object\nextend Foo #=> main\nprepend Foo #=> Error: undefined method `prepend`\n",[229,7380,7381,7390,7399],{"__ignoreMap":200},[270,7382,7383,7385,7387],{"class":272,"line":273},[270,7384,6948],{"class":276},[270,7386,7281],{"class":315},[270,7388,7389],{"class":614}," #=> Object\n",[270,7391,7392,7394,7396],{"class":272,"line":201},[270,7393,7197],{"class":276},[270,7395,7281],{"class":315},[270,7397,7398],{"class":614}," #=> main\n",[270,7400,7401,7403,7405],{"class":272,"line":204},[270,7402,7186],{"class":276},[270,7404,7281],{"class":315},[270,7406,7407],{"class":614}," #=> Error: undefined method `prepend`\n",[16,7409,7410,7412,7413,7415],{},[229,7411,7186],{}," strictly works on classes and modules. Since the top level ",[229,7414,946],{}," is an object, it will not work at the top level.",[16,7417,7418,7419,7421,7422,7425,7426,7428,7429,7431,7432,7434,7435,44],{},"Calling ",[229,7420,7197],{}," with any module in the top level will return ",[229,7423,7424],{},"main"," as a return value. ",[229,7427,7424],{}," is a special object in Ruby. Within its scope only, all of the top level code is executed. So extending ",[229,7430,7424],{}," with a module will make that module's methods available in ",[229,7433,7424],{}," (top level execution). This is the expected behavior of ",[229,7436,7197],{},[16,7438,7418,7439,7421,7441,7444,7445,7447,7448,7450,7451,7453,7454,7456],{},[229,7440,6948],{},[229,7442,7443],{},"Object"," as output. If you read the definition again, you'll notice the abnormality here. ",[229,7446,7424],{}," is an object. You cannot call ",[229,7449,6948],{}," method in any other objects in Ruby. But ",[229,7452,7424],{}," allows it and the module is included in scope of an ",[229,7455,7443],{},", which is everywhere.",[16,7458,7459,7460,7462,7463,7465,7466,7468,7469,7471,7472,44],{},"In Ruby, everything is an object. By that rule, everything is inherited from ",[229,7461,7443],{},". This is the reason why our method ",[229,7464,6964],{}," was available for all objects, even ",[229,7467,3496],{},". Because even ",[229,7470,3496],{}," is an instance of ",[229,7473,7443],{},[16,7475,7476,7477,236,7480,4121],{},"A single one-line mistake can bloat all objects in Ruby, leading to unwanted methods available in all objects. It'll also mess up the method lookup path for all objects and lead to unexpected behaviors like a method that is supposed to call a super method, calling itself (endless recursion). Other nightmares include overriding ",[229,7478,7479],{},"to_param",[229,7481,7482],{},"method_missing",[16,7484,7485,7486,7488],{},"You might ask \"Using ",[229,7487,7197],{}," at the top level is safe, right?\". The answer is yes and no. Just because you can, you shouldn't. We are writing an Object-Oriented Program here. So it never makes sense to extend an interface outside of all the objects, not even for code organization or reusability.",[1081,7490,7491],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":200,"searchDepth":201,"depth":201,"links":7493},[],"2026-01-20","Exploring how a one-line mistake can pollute all objects and cause unusual behaviors across a Ruby application.",{},"/blogs/mixing_into_every_object_in_ruby",{"title":6846,"description":7495},"blogs/mixing_into_every_object_in_ruby",[266,1094],"EkEoOjAEhMxobOKykV43l39MPzlPj6KSLEucXuzpYdU",{"id":5,"title":6,"body":7503,"date":207,"description":208,"extension":209,"meta":7615,"navigation":211,"path":212,"seo":7616,"stem":214,"tags":7617,"__hash__":219},{"type":8,"value":7504,"toc":7610},[7505,7507,7512,7514,7521,7523,7528,7533,7535,7543,7545,7547,7558,7568,7570,7580,7585,7587,7589,7591,7593,7598,7604,7608],[11,7506,14],{"id":13},[16,7508,18,7509,27],{},[20,7510,26],{":target":22,"href":23,"rel":7511},[25],[16,7513,30],{},[16,7515,33,7516,38,7518,44],{},[35,7517,37],{},[20,7519,43],{":target":22,"href":41,"rel":7520},[25],[16,7522,47],{},[16,7524,50,7525,56],{},[20,7526,55],{":target":22,"href":53,"rel":7527},[25],[16,7529,59,7530,65],{},[20,7531,64],{":target":22,"href":62,"rel":7532},[25],[11,7534,69],{"id":68},[16,7536,72,7537,76,7540,44],{},[20,7538,55],{":target":22,"href":53,"rel":7539},[25],[20,7541,64],{":target":22,"href":62,"rel":7542},[25],[81,7544],{},[11,7546,86],{"id":85},[16,7548,89,7549,95,7552,101,7555,107],{},[20,7550,94],{":target":22,"href":92,"rel":7551},[25],[20,7553,100],{":target":22,"href":98,"rel":7554},[25],[20,7556,106],{":target":22,"href":104,"rel":7557},[25],[16,7559,110,7560,115,7562,119,7564,123,7566,127],{},[112,7561,114],{},[35,7563,118],{},[112,7565,122],{},[35,7567,126],{},[16,7569,130],{},[16,7571,133,7572,139,7575,145,7578,27],{},[20,7573,138],{":target":22,"href":136,"rel":7574},[25],[20,7576,144],{":target":22,"href":142,"rel":7577},[25],[35,7579,148],{},[16,7581,151,7582,157],{},[20,7583,156],{":target":22,"href":154,"rel":7584},[25],[16,7586,160],{},[16,7588,163],{},[16,7590,166],{},[16,7592,169],{},[16,7594,172,7595,178],{},[20,7596,177],{":target":22,"href":175,"rel":7597},[25],[16,7599,181,7600,185,7602,27],{},[35,7601,184],{},[35,7603,188],{},[16,7605,191,7606,195],{},[35,7607,194],{},[16,7609,198],{},{"title":200,"searchDepth":201,"depth":201,"links":7611},[7612,7613,7614],{"id":13,"depth":204,"text":14},{"id":68,"depth":204,"text":69},{"id":85,"depth":204,"text":86},{},{"title":6,"description":208},[216,217,218],1781356021472]