{"id":15465740,"url":"https://github.com/hackvan/artist-song-modules","last_synced_at":"2025-02-23T22:21:44.502Z","repository":{"id":145341451,"uuid":"140617505","full_name":"hackvan/artist-song-modules","owner":"hackvan","description":null,"archived":false,"fork":false,"pushed_at":"2018-07-11T19:05:12.000Z","size":155,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-05T17:13:27.388Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hackvan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-07-11T19:03:52.000Z","updated_at":"2018-07-11T19:05:14.000Z","dependencies_parsed_at":null,"dependency_job_id":"b5f74e8d-02d9-4e99-94b5-8498899fdc82","html_url":"https://github.com/hackvan/artist-song-modules","commit_stats":{"total_commits":61,"total_committers":31,"mean_commits":1.967741935483871,"dds":0.8524590163934427,"last_synced_commit":"62eaa9e38668b7a32dfff7e8eeb3067c0a3a2f3b"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hackvan%2Fartist-song-modules","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hackvan%2Fartist-song-modules/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hackvan%2Fartist-song-modules/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hackvan%2Fartist-song-modules/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hackvan","download_url":"https://codeload.github.com/hackvan/artist-song-modules/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240386530,"owners_count":19793193,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-02T01:03:10.026Z","updated_at":"2025-02-23T22:21:44.030Z","avatar_url":"https://github.com/hackvan.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Refactoring with Modules\n\n## Objectives\n\n1. Recognize \"code smells\" that indicate the need for refactoring.\n2. Use modules to refactor away repetitious code.\n\n## Overview\n\nIn this lab, we have an `Artist` class and a `Song` class. `Artist`s have many songs and an individual instance of the `Song` class belongs to an artist. `Artist`s and `Song`s also have some familiar class methods that keep track of all of the instances of the class, count those instances and clear or reset them.\n\nOpen up the `lib` directory and spend some time reviewing the code in `artist.rb` and `song.rb`. Keep reading the code until you feel you understand what each method is doing.\n\nNotice that there are behaviors that are shared between both of these classes. For example, both classes have `.count` and `reset_all` class methods. Consequently, both classes have *the same exact code*. As programmers, you may recall, we are lazy. We don't like to repeat ourselves. We like to keep it DRY (Don't Repeat Yourself). In this lab, we'll be identifying repetition and building modules to extract it out. Then, we'll use the `extend` and `include` keywords to lend the functionality of our modules to our `Artist` and `Song` classes.\n\n## Configuring our Environment\n\nInstead of requiring individual files within one another, as you may have noticed we did in the previous two code along exercises, we created an environment file to handle those requirements for us. Open up the `config` directory and look at the `environment.rb` file. You'll see that we're already requiring the files that hold our `Artist` and `Song` class. Any additional files we make should be required by this `environment.rb` file. Our `spec_helper` file, which is required by each individual spec file, required *only this `config/environment.rb` file*, instead of each and every file from the `lib` directory. As we start to build larger and more complex programs, it begins to make sense to handle all of our requirements in one place.\n\n## Instructions\n\n### A Note on Refactoring Practices\n\nWe use TDD (test-driven development) for a reason. We write tests to define the desired behavior of our program so that we can write clean, beautiful code. Such code usually *isn't* the code you write the first time around. The code you first write is the code that makes your program *work*, the code that gets those tests passing. Then, we refactor our code to make it clean, DRY, and easy to understand. This is where our tests come in. We write thorough tests that cover all of the aspects of our code's desired behavior. We can *first* write code that passes those tests and *then* break our code, fail our tests, write better code and pass our tests again.\n\nThis is called the **red, green, refactor** pattern. First tests fail, then you write bad code to get them to pass, *then* you refactor that bad code into good code. In this lab, you'll start by running the test suite. You'll see that all of the tests pass. Then, we'll break that code in order to refactor it, write better code and get our tests passing again. Remember, don't be afraid of broken code! Broken code is the status quo in programming. Your job is often to break something to make it better. Embrace broken code.\n\n### Step 1: Class Methods\n\nFirst, run the test suite. Wow, we're passing all of our tests! Okay, now let go of those passing tests because we are about to break our code.\n\nThe first area of refactoring we'll be attacking are the class methods. Notice that both the `Song` and `Artist` class have `.count` and `reset_all` class methods. Instead of repeating the same exact code in both classes, let's extract these class methods into a module that we can *extend* into the classes.\n\nReady to break your code? Comment out the `reset_all` and `count` methods in the `Song` and `Artist` class. Run your test suite. Phew! Okay, we did it. That wasn't so bad, was it?\n\n#### The `Memorable` Module\n\nLet's define our module. Create a `concerns` folder inside `lib`. This is where we'll store our modules. It is a common practice to create a folder called `concerns` that holds modules that will be used across classes in an object oriented Ruby project.\n\nInside the `concerns` folder, create a file called `memorable.rb`. Open up that file and define a module:\n\n\n```ruby\nmodule Memorable\nend\n```\n\nInside here, define your `reset_all` and `count` methods.\n\nImportant! Remember to add `require_relative '../lib/concerns/memorable'` to your environment file before running any tests. We've already provided that line for you in fact! All you have to do is un-comment it out. :)\n\nOnce you define the two class methods mentioned above inside of the `Memorable` module, use the `extend` keyword to extend those methods, as class methods, into both the `Artist` and `Song` class. Refer to the previous code along exercise for help. Remember that the `self` keyword is omitted when defining class methods inside modules. The `extend` keyword is responsible for defining the method as a class method vs. an instance method (which would use the `include` keyword).\n\nNow you're ready to run your test suite again. Get all those tests back to passing before you move on. Once your tests are passing, make sure you delete the commented-out `reset_all` and `count` class methods from your `Song` and `Artist` class. You don't need them anymore.\n\n#### Advanced: The `find_by_name` Method\n\nBefore we build the module to house this method, let's talk a bit about it. In an upcoming unit, we'll be introducing databases. You'll learn how to connect your Ruby programs to a database and use that database to store information––even Ruby objects! Moving forward through this course, you'll be building web applications that are connected to databases that store users' information and the information pertinent to the app. Let's think about a common example:\n\nLet's say you're working on an app that serves as an online store, connecting users to everything from books to movies to shoes to stereo equipment, you name it. We'll call this app \"Nile\" (definitely not inspired by another online market-place named after a famous river). Such an application needs to store the items it has for sale as well as the information of the user who logs in to go shopping. Consequently, every time a user logs in, or searches for an item, or purchases an item, we have to *retrieve information from a database*. One of the most common ways you'll be doing that is to use methods like `find_by_name` or `find_by_email` or `find_by_product_id` or...you get the idea. We'll be learning much, much more about this later. Here, we're building a simple `find_by_name` method that introspects on a class's `.all` class method and extracts the instance of the class with a certain name.\n\nOkay, back to your regularly scheduled programming:\n\n#### The `Findable` Module\n\nIn `lib/concerns`, create a file, `findable.rb`. In this file, define a module: `Findable`. This module should define the method, `find_by_name`. This method will be used as a class method. Something like this:\n\n```ruby\nArtist.find_by_name(\"Adele\")\n#=\u003e #\u003cArtistx038230sdcmdn3872\u003e\n```\n\nExtract the code from the `find_by_name` methods that you'll see in the `Artist` and `Song` classes and place it inside the `Findable` module's `find_by_name` method.\n\nRemember that we need to keep the content of this method abstract. So, inside the `Artist` class, a `find_by_name` method might look like this:\n\n```ruby\nclass Artist\n\n  @@artists = []\n\n  attr_accessor :name\n\n  def initialize(name)\n    @name = name\n  end\n\n  def self.all\n    @@artists\n  end\n\n  def self.find_by_name(name)\n    @@artists.detect {|a| a.name == name}\n  end\nend\n```\n\nInside the `Findable.find_by_name` method, we can't use a class-specific class variable like `@@artists`, because our method would break when included in any class that *didn't* define such a variable.\n\nIs there a way to reference the collection of *all* of the instances of a class, without specifically referencing class variables that are only defined in certain classes?\n\n\n### Step 2: Instance Methods\n\nLet's go back to our `Song` and `Artist` class and take a look at another example of repetition, this time with instance methods. The `to_param` instance method is repeated in the `Song` and `Artist` class. Another great candidate for refactoring!\n\nGo ahead and comment out the `to_param` method in both the `Song` and `Artist` class. Run your test suite again and see those broken tests!\n\nOkay, now we're ready to define our module.\n\n#### The `Paramable` Module\n\nCreate a new file in your `concerns` directory called `paramable.rb`. Define your module here:\n\n```ruby\nmodule Paramable\nend\n```\n\nBuild the `to_param` method inside your module and use the `include` keyword to include the `Paramable` module in both the `Song` and `Artist` class. Once you get your tests passing again, go ahead and delete the commented-out `to_param` method from the `Song` and `Artist` classes.\n\nImportant! Remember to add `require_relative '../lib/concerns/paramable'` to your environment file before running any tests. We've already provided that line for you in fact! All you have to do is un-comment it out. :)\n\n\n#### Advanced: The `to_param` Method\n\nTo understand the concept of a parameter, let's take a look at an example URL: www.facebook.com/your-name.\n\nThe \"your-name\" part of the above URL might be referred to as a slug. Another term for this section of a URL is \"parameter\" or \"param\". One common task you'll undertake as a web developer is to take a Ruby object, such as an instance of a `User` class, and make a URL out of it. For example, let's say we have a database full of instances of a `User` class. When an individual user signs in to our app, we might want to show them their very own profile page. To do so, we would have to write a method that takes their name and turns it into a slug or parameter that could be tacked onto a URL.\n\nDon't worry too much about this use-case for now. We'll be learning much, much more about connecting our Ruby programs to the web later on. For now, just understand the general purpose of having a method like the `to_param` method.\n\n### Bonus: Refactoring the `.initialize` Method\n\n#### Recognizing Repetition\n\nLet's take a look at the `.initialize` methods of both the `Song` and `Artist` class:\n\n#### `lib/song.rb`\n\n```ruby\n def initialize\n    @@songs \u003c\u003c self\n  end\n```\n\n#### `lib/artist.rb`\n\n```ruby\ndef initialize\n    @@artists \u003c\u003c self\n    @songs = []\n  end\n```\n\nSee the repetition? Both methods push the instance on which they are being called, i.e. `self` into an array stored inside a class variable.\n\nIn `song.rb` we have:\n\n`@@songs \u003c\u003c self`\n\nIn `artist.rb` we have:\n\n`@@artists \u003c\u003c self`\n\n\nThis is pretty similar, although not exactly the same. However, it is repetitious enough to be giving off a code smell. In order to refactor it, however, we first have to get rid of any code that is specific to the class. In this case, we need to abstract away the literal reference to the `@@songs` and `@@artists` class variables.\n\n#### Abstracting Away Repetition\n\nLucky for us, we already have class methods that wrap these class variables:\n\n#### `lib/song.rb`\n\n```ruby\n def self.all\n    @@songs\n end\n```\n\n#### `lib/artist.rb`\n\n```ruby\ndef self.all\n    @@artists\nend\n```\n\nLet's begin by refactoring the content of both `.initialize` methods to use the `\u003cClassName\u003e.all` class method instead of literal class variables. How can we programmatically access the class of the instance that we are operating on inside the `.initialize` method? Take a look below:\n\n#### `lib/song.rb`\n\n```ruby\ndef initialize\n    self.class.all \u003c\u003c self\nend\n```\n\n#### `lib/artist.rb`\n\n```ruby\ndef initialize\n    self.class.all \u003c\u003c self\n    @songs = []\nend\n```\n\nRemember that `.initialize` is an instance method. So, inside `.initialize`, `self` refers to the instance of the class on which you are operating. But `.all` is a class method. We would normally call it like this:\n\n`Artist.all`\n\nor\n\n`Song.all`\n\nSo, to call the `.all` class method from *inside the `.initialize` instance method*, we can call `self.class` inside `.initialize`.\n\nTake a quick look at this reminder of how `.class` works:\n\n```ruby\nnew_song = Song.new\nnew_song.class\n =\u003e Song\n```\n\nSo, we can call `self.class.all` inside `.initialize` and it will be just as if we called `Song.all` or `Artist.all`. Only this way, our code is abstract. It doesn't explicitly reference `Song` or `Artist` class, so it is more flexible.\n\nNow we have two `.initialize` methods that contain identical lines of code. We're ready for the next refactoring step––modules.\n\n#### Extracting Repetition\n\nBefore we build a brand new module to house this code from our `.initialize` methods, let's stop and think. What is the responsibility or the behavior of the code we are trying to extract? This is code that is responsible for telling a class to keep track of its own instances. This code really goes hand in hand with the `.count` and `.reset_all` class methods that we already extracted into the `Memorable` module. It makes sense, therefore, to extract this code into that same module.\n\nBut wait (you might be thinking), isn't that module **extended** into our `Song` and `Artist` class in order to offer its methods as **class** methods? Isn't `.initialize` an instance method? How can we put class methods and instance methods in the same module? Read on to learn the answer...\n\n#### Nesting Modules\n\nWe can nest sets of modules within one another and then `include` or `extend` individual modules as needed. Let's take a look:\n\n```ruby\nmodule Memorable\n  module ClassMethods\n    def reset_all\n      self.all.clear\n    end\n\n    def count\n      self.all.count\n    end\n  end\n\n  module InstanceMethods\n    def initialize\n      # some more code coming soon!\n    end\n  end\nend\n```\n\nThen, in order to `include` or `extend` as needed, we use the `include` or `extend` keyword in the following manner:\n\nIn both the `Song` and `Artist` classes:\n\n```ruby\nextend Memorable::ClassMethods\ninclude Memorable::InstanceMethods\n```\n\nThe `Parent::Child` syntax is called **namespacing**.\n\nOkay, we're almost done. We need to fill out the content of the `.initialize` method in the module.\n\nThe `.initialize` methods in our `Song` and `Artist` classes share the following line:\n\n```\ndef initialize\n  self.class.all \u003c\u003c self\nend\n```\n\nThis is the code that will go into the new `.initialize` method of our module:\n\n```ruby\nmodule Memorable\n  module ClassMethods\n    def reset_all\n      self.all.clear\n    end\n\n    def count\n      self.all.count\n    end\n  end\n\n  module InstanceMethods\n    def initialize\n      self.class.all \u003c\u003c self\n    end\n  end\nend\n```\n\nThere's just one more step. Look back at the original `.initialize` method of the `Artist` class:\n\n```ruby\nclass Artist\n  ...\n\n  def initialize\n    self.class.all \u003c\u003c self\n    @songs = []\n  end\n```\n\nIn the `Artist` class, the initialize method is *also* responsible for setting the `@songs` instance variable equal to an empty array. We need to hang on to this behavior, even as `Artist` instances grab the *rest* of the `.initialize` from the `Memorable::InstanceMethods` module.\n\nRemember our `super` keyword from the inheritance code along exercise? The `super` keyword, placed inside a method, will tell that method to look up its behavior in the method of the same name that lives in the parent, or super, class. A method that includes the `super` keyword will execute any code placed inside the super class' method of the same name, and then execute any code inside the child class' method.\n\nWhen we `include` a module in a class, we are really telling that class to *inherit* methods from that module.\n\nSo, we can use the `super` keyword to tell our `Artist`'s `.initialize` method to use the code in the `Memorable::InstanceMethods` module's `.initialize` method *and* also to use any additional code in the `Artist`'s `.initialize` method. Take a look:\n\n```ruby\nclass Artist\n  ...\n\n  def initialize\n    super\n    @songs = []\n  end\n```\n\n## Conclusion\n\nPhew! That was some complex stuff. It's okay if you didn't understand everything covered in this lab. There were a few advanced and bonus sections that we threw in there to challenge you and make you think. Don't skip over them, even if you can't follow everything they discuss. It's important to plant the seed of some of these more complex topics––it will make them easier to understand later on when you're ready to go deeper into Ruby programming.\n\n\u003cp data-visibility='hidden'\u003eView \u003ca href='https://learn.co/lessons/artist-song-modules' title='Refactoring with Modules'\u003eRefactoring with Modules\u003c/a\u003e on Learn.co and start learning to code for free.\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhackvan%2Fartist-song-modules","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhackvan%2Fartist-song-modules","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhackvan%2Fartist-song-modules/lists"}