{"id":19884710,"url":"https://github.com/microverse-fullstack-program/ruby_oop_principles","last_synced_at":"2025-03-01T03:42:48.370Z","repository":{"id":168229027,"uuid":"643887023","full_name":"Microverse-Fullstack-Program/ruby_OOP_principles","owner":"Microverse-Fullstack-Program","description":"We are going to build some classes to represent animals. Initially, we are going to make some naive code and then we'll improve it by using the OOP principles.","archived":false,"fork":false,"pushed_at":"2023-05-27T20:33:13.000Z","size":367,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"dev","last_synced_at":"2025-01-11T18:41:49.602Z","etag":null,"topics":["ruby-application","ruby-gem"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Microverse-Fullstack-Program.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2023-05-22T11:12:17.000Z","updated_at":"2023-05-27T20:34:16.000Z","dependencies_parsed_at":"2023-06-08T10:02:50.589Z","dependency_job_id":null,"html_url":"https://github.com/Microverse-Fullstack-Program/ruby_OOP_principles","commit_stats":null,"previous_names":["microverse-fullstack-program/ruby_oop_principles"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Microverse-Fullstack-Program%2Fruby_OOP_principles","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Microverse-Fullstack-Program%2Fruby_OOP_principles/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Microverse-Fullstack-Program%2Fruby_OOP_principles/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Microverse-Fullstack-Program%2Fruby_OOP_principles/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Microverse-Fullstack-Program","download_url":"https://codeload.github.com/Microverse-Fullstack-Program/ruby_OOP_principles/tar.gz/refs/heads/dev","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241313188,"owners_count":19942416,"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":["ruby-application","ruby-gem"],"created_at":"2024-11-12T17:29:06.484Z","updated_at":"2025-03-01T03:42:48.341Z","avatar_url":"https://github.com/Microverse-Fullstack-Program.png","language":"Ruby","readme":"# OOP four principles by example\n\u003cimg src=\"oop_concepts.png\" alt=\"oop_concepts\"\u003e\n\n## What are we building?\n\nWe are going to build some classes to represent animals. Initially, we are going to make some naive code and then we'll improve it by using the OOP principles.\n\nWhile reading this article, create your files with classes and try some code in your IRB console. Keep the files you create saved for upcoming lessons.\n\n## Let's get building\n\nFirst, make sure you have the code from the previous article [Class vs object: examples](class_and_object_for_irb.md). You should have a file named `animal.rb` with a single class `Animal`.\n\nIn your `Animal` class add a `type` parameter and assign it to the `@type` instance variable. It should look like this:\n\n```ruby\nclass Animal\n  def initialize(type, number_of_legs, name = \"Unknown\")\n    @id = Random.rand(1..1000)\n    @name = name\n    @number_of_legs = number_of_legs\n    @type = type\n  end\nend\n```\n\n### Encapsulation\n\nIf you try the previous code in IRB you will see that you can't access or modify any of the attributes in either class. This is due to Ruby's encapsulation. Because of this encapsulation the instance variable can't be accessed outside the object, only by methods inside of it.\n\nBut then how do you interact with an object? Well, you declare public methods. By default in Ruby any method declared is public (can be called anywhere), but you can also declare them as private (can only be called inside that class).\n\nSo, we could create a public method to fetch `@name`'s value and another one to modify it. These types of methods are called **getter** (get a value) and **setter** (set a value) methods. The getter is called the same as the corresponding instance variable and the setter is called the same, but it should be followed by `=` (this is Ruby's special syntax for setters).\n\nNow that you have a better understanding, let's add some getters and setters so we can interact with the attributes. In `animal.rb` modify `Animal` by adding the following:\n\n```ruby\nclass Animal\n  ...\n\n  def id\n    @id\n  end\n\n  def type\n    @type\n  end\n\n  def number_of_legs\n    @number_of_legs\n  end\n\n  def name\n    @name\n  end\n\n  def name=(value)\n    @name = value\n  end\nend\n```\n\nGreat! now we can access `Animal`'s attributes and modify `name` if needed.\n\n\u003cdetails\u003e\n\u003csummary\u003eTry it out in your IRB console\u003c/summary\u003e\n\n```ruby\nrequire \"./animal.rb\"\n\nanimal_1 = Animal.new(\"dog\", 4, \"Rex\")\nanimal_1.id\nanimal_1.type\nanimal_1.name\nanimal_1.number_of_legs\n\nanimal_2 = Animal.new(\"cat\", 8)\nanimal_2.name\nanimal_2.name = \"Fluffy\"\nanimal_2.name\n```\n\n\u003c/details\u003e\n\n#### Useful shortcuts: attr_reader, attr_writer, and attr_accessor\n\nRuby offers you a nice way to create your getters and setters!\n\nInstead of writing two methods to get and set a name:\n\n```ruby\n  def name\n    @name\n  end\n\n  def name=(value)\n    @name = value\n  end\n```\n\nYou can declare them by using:\n\n```ruby\nattr_reader :name\nattr_writer :name \n```\n\nOr you can make it even shorter by using:\n\n```ruby\nattr_accessor :name\n```\n\nOf course if you need only a getter method you will use only `attr_reader`. Likewise, if you need only a setter method, you will use only `attr_writer`.\n\n\n### Abstraction\n\nNext, we want to see our animals speak (return a string) depending on their type. If we didn't have abstractions we would have to make a method that checks the instance's class and gives the string depending on that. Something like this:\n\n```ruby\ndef speak(animal)\n  if animal.type == \"dog\"\n    \"Woof, woof\"\n  elsif animal.type == \"spider\"\n    \"...\"\n  end\nend\n```\n\nThis implementation of `speak` depends on internal knowledge of the instance - in this case knowing that it has an attribute `type` and that it has those values. This presents the following problems:\n- Have to maintain `type`, and if it changes or is removed this method will break.\n- To add a new animal we need to also add another `elsif`.\n- This code might be repeated across different parts of our source code without us noticing.\n\nTo make this code better we can leverage abstractions and instead of having a method using internals we have a public method that uses those same internals. The main difference is that if we eventually want to change the internals of `Animal` we only need to update the `speak` method, the same to add a new animal type. So, let's update the `speak` method as follows:\n\n```ruby\nclass Animal\n  ...\n\n  def speak\n    if @type == \"dog\"\n      \"Woof, woof\"\n    elsif @type == \"spider\"\n      \"...\"\n    end\n  end\n\n  ...\nend\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eTry it out in your IRB console\u003c/summary\u003e\n\n```ruby\nrequire \"./animal.rb\"\n\nanimal_1 = Animal.new(\"dog\", 4, \"Rex\")\nanimal_2 = Animal.new(\"spider\", 8, \"Wilma\")\n\nanimal_1.speak()\nanimal_2.speak()\n```\n\n\u003c/details\u003e\n\n### Inheritance\n\nSo next we got a request for some specific functionality when the animal is of a certain type:\n- If a dog we want to be able to `bring_a_stick`.\n- If a spider we want to be able to `make_a_web`.\n\nThis would look something like this:\n\n```ruby\nclass Animal\n  ...\n\n  def bring_a_stick\n    if @type == \"dog\"\n      \"Here is your stick: ---------\"\n    end\n  end\n\n  def make_a_web\n    if @type == \"spider\"\n      \"www\"\n    end\n  end\nend\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eTry it out in your IRB console\u003c/summary\u003e\n\n```ruby\nrequire \"./animal.rb\"\n\nanimal_dog = Animal.new(\"dog\", 4, \"Rex\")\nanimal_spider = Animal.new(\"spider\", 8, \"Wilma\")\n\nanimal_dog.bring_a_stick()\nanimal_spider.bring_a_stick()\n\nanimal_dog.make_a_web()\nanimal_spider.make_a_web()\n```\n\n\u003c/details\u003e\n\nIf you test these methods out you'll notice that the method that is not for the current animal still exists, it just returns `nil`. You might think this is not that bad, but in reality it can be bad because that `nil` value can be passed through your entire codebase, end up in a place where it is not valid and causes an exception, and you might not be able to easily figure out where that value came from.\n\nSo, how can we improve this? Inheritance. We can make classes (`Dog` and `Spider`) that inherit from our `Animal` class and add the specific methods we want for them. Also, we can go a step further and set `@number_of_legs` in `initialize` and some specific attributes for each.\n\nRemove the `bring_a_stick` and `make_a_web` methods from your `Animal` class.\n\nCreate a `dog.rb` file with the class `Dog` as follows:\n\n```ruby\nrequire \"./animal.rb\"\n\nclass Dog \u003c Animal\n  def initialize(color, name = \"Unknown\")\n    super(\"dog\", 4, name)\n    @color = color\n  end\n\n  def bring_a_stick\n    \"Here is your stick: ---------\"\n  end\nend\n```\n\nCreate a `spider.rb` file with the class `Spider` as follows:\n\n```ruby\nrequire \"./animal.rb\"\n\nclass Spider \u003c Animal\n  def initialize(web_strength_level, name = \"Unknown\")\n    super(\"spider\", 8, name)\n    @web_strength_level = web_strength_level\n  end\n\n  def make_a_web\n    \"www\"\n  end\nend\n```\n\nNow if you try the `bring_a_stick` or `make_a_web` methods in the incorrect class instance you'll get an exception and they are a bit simpler each.\n\n\u003cdetails\u003e\n\u003csummary\u003eTry it out in your IRB console\u003c/summary\u003e\n\n```ruby\nrequire \"./dog.rb\"\nrequire \"./spider.rb\"\n\ndog = Dog.new(\"black\", \"Rex\")\nspider = Spider.new(85, \"Wilma\")\n\ndog.bring_a_stick()\nspider.bring_a_stick()\n\ndog.make_a_web()\nspider.make_a_web()\n```\n  \n\u003c/details\u003e\n\n### Polymorphism\n\nNow that we have specific classes for `Dog` and `Spider` how about we use that and a bit of the magic of polymorphism to refactor the code for `speak`. In this new version, we can have some generic text (`\"grrrr\"`) for animals and a specific one for specific animals.\n\nModify `speak` in `Animal` to return `\"grrrr\"`\n\n```ruby\nclass Animal\n  ...\n\n  def speak\n    \"grrrr\"\n  end\n\n  ...\nend\n```\n\nAdd a `speak` method in `Dog` that returns `\"Woof, woof\"`\n\n```ruby\nclass Dog \u003c Animal\n  ...\n\n  def speak\n    \"Woof, woof\"\n  end\nend\n```\n\nAdd a `speak` method in `Spider` that returns `\"...\"`\n\n```ruby\n\nclass Spider \u003c Animal\n  ...\n\n  def speak\n    \"...\"\n  end\nend\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eTry it out in your IRB console\u003c/summary\u003e\n\n```ruby\nrequire \"./animal.rb\"\nrequire \"./dog.rb\"\nrequire \"./spider.rb\"\n\nanimal = Animal.new(\"lion\", 4, \"Rex\")\ndog = Dog.new(\"black\", \"Rex\")\nspider = Spider.new(85, \"Wilma\")\n\nanimal.speak()\ndog.speak()\nspider.speak()\n```\n\u003c/details\u003e\n\n\n# Composition by example\n\n### What are we composing?\nWe are going to add the following two features to our objects by composing them:\n- a method to reduce the number of legs by one.\n- a method to tell if the animal likes a type of food or not.\n\n### Composable classes\n\n\u003e Method to reduce the number of legs by one.\n\nLet's create a class called `Remover` (in a file `remover.rb`) that will receive the number of legs and optionally how many to reduce them by.\n\n```ruby\nclass Remover\n  def decrease(number, step = 1)\n    number -= step\n  end\nend\n```\n\nSo, how can we use this? Well, we can use this directly in a method in our `Animal` class, but remember to add the `require` for file `remover.rb`.\n\n```ruby\nrequire \"./remover.rb\"\n\nclass Animal\n  ...\n\n  def remove_leg\n    remover = Remover.new()\n    @number_of_legs = remover.decrease(@number_of_legs)\n  end\n\n  ...\nend\n```\n\nGreat! Now you can reduce `@number_of_legs` if needed. Now on to the next feature.\n\n\u003cdetails\u003e\n\u003csummary\u003eTry it out in your IRB console\u003c/summary\u003e\n\n```ruby\nrequire \"./animal.rb\"\nrequire \"./dog.rb\"\nrequire \"./spider.rb\"\n\nanimal = Animal.new(\"lion\", 4, \"Rex\")\ndog = Dog.new(\"black\", \"Rex\")\nspider = Spider.new(85, \"Wilma\")\n\nanimal.number_of_legs\ndog.number_of_legs\nspider.number_of_legs\n\nanimal.remove_leg()\ndog.remove_leg()\nspider.remove_leg()\n\nanimal.number_of_legs\ndog.number_of_legs\nspider.number_of_legs\n```\n\n\u003c/details\u003e\n\n\u003e Method to tell if the animal likes a type of food or not\n\nFor this we are going to create some classes (`NoFood`, `DogFood`, and `SpiderFood`) that will have a list of foods and a method to tell you if one is on the list or not (abstraction!).\n\nYou can create the classes in a single file called `foods.rb`.\n\n```ruby\nclass NoFood\n  def is_liked?(food)\n   false\n  end\nend\n\nclass DogFood\n  def is_liked?(food)\n   [\"meat\", \"vegetable\", \"fruit\"].member?(food)\n  end\nend\n\nclass SpiderFood\n  def is_liked?(food)\n   [\"insect\", \"bug\"].member?(food)\n  end\nend\n```\n\nNext, we modify our classes to set `@liked_food`.\n\nModify `dog.rb` to require `foods.rb` and set `DogFood` as `@liked_food`.\n\n```ruby\n...\nrequire \"./foods.rb\"\n\nclass Dog \u003c Animal\n  def initialize(color, name = \"Unknown\")\n    super(\"dog\", 4, name)\n    @color = color\n    @liked_food = DogFood.new()\n  end\n\n  ...\nend\n```\n\nModify `spider.rb` to require `foods.rb` and set `SpiderFood` as `@liked_food`.\n\n```ruby\n...\nrequire \"./foods.rb\"\n\nclass Spider \u003c Animal\n  def initialize(web_strength_level, name = \"Unknown\")\n    super(\"spider\", 8, name)\n    @web_strength_level = web_strength_level\n    @liked_food = SpiderFood.new()\n  end\n\n  ...\nend\n```\n\nAnd finally, modify `animal.rb` to require `foods.rb` and set `NoFood` as `@liked_food`. Also, we need to add a method `likes_food?` that uses `@liked_food` and will be inherited by `Dog` and `Spider`\n\n```ruby\n...\nrequire \"./foods.rb\"\n\nclass Animal\n  def initialize(type, number_of_legs, name = \"Unknown\")\n    ...\n    @liked_food = NoFood.new()\n  end\n\n  ...\n\n  def likes_food?(food)\n    @liked_food.is_liked?(food)\n  end\nend\n```\n\nExcellent, now you can tell what food your animal likes and in the future you can make it changeable!\n\n\u003cdetails\u003e\n\u003csummary\u003eTry it out in your IRB console\u003c/summary\u003e\n\n```ruby\nrequire \"./animal.rb\"\nrequire \"./dog.rb\"\nrequire \"./spider.rb\"\n\nanimal = Animal.new(\"lion\", 4, \"Rex\")\ndog = Dog.new(\"black\", \"Rex\")\nspider = Spider.new(85, \"Wilma\")\n\nanimal.likes_food?(\"meat\")\ndog.likes_food?(\"meat\")\nspider.likes_food?(\"meat\")\n\nanimal.likes_food?(\"bug\")\ndog.likes_food?(\"bug\")\nspider.likes_food?(\"bug\")\n```\n\n\u003c/details\u003e\n\n# Association, aggregation, and composition by examples\n\n### The UML class diagram\nThe diagram below shows the classes (with their attributes and methods) and the relationships between them.\n\n![Vet UML diagram annotations](../images/uml_class_diagram_with_annotations.png)\n\n### Has many relationships\n\nTaking a look at the diagram you can see 3 *has many* relationships:\n- `Owner` has many `Animal`s\n- `Animal` has many `Visit`s\n- `Vet` has many `Visit`s\n\nBut actually, the ones involving `Visit` are part of a *many-to-many* of `Animal` and `Vet`. So for now, we will do the remaining relationship, which is a simple *has many*.\n\nWe are going to create a file `owner.rb` which defines the `Owner` class with attributes `@name` and `@animals`. That attribute `@animals` is what holds the relationship and to manage it we will also create a method `add_animals` to add animals to it. So this class ends up looking like this:\n\n```ruby\nclass Owner\n  attr_accessor :name\n  attr_reader :animals\n\n  def initialize(name)\n    @name = name\n    @animals = []\n  end\n\n  # Instead of setter for entire collection a method to add animals one by one\n  def add_animal(animal)\n    @animals.push(animal)\n  end\nend\n```\n\nNow you can have an owner and give it animals.\n\n\u003cdetails\u003e\n\u003csummary\u003eTry it out in your IRB console\u003c/summary\u003e\n\n```ruby\nrequire \"./animal.rb\"\nrequire \"./dog.rb\"\nrequire \"./spider.rb\"\nrequire \"./owner.rb\"\n\ndog = Dog.new(\"black\", \"Rax\")\nspider = Spider.new(85, \"Bob\")\nanimal = Animal.new(\"lion\", 4, \"Some name\")\n\nalex = Owner.new(\"Alex\")\nalex.animals\nalex.add_animal(dog)\nalex.animals\nalex.add_animal(spider)\nalex.animals\nalex.add_animal(animal)\nalex.animals.map {|animal| animal.name}\n\nalex.animals.count\nalex.animals.first.name\nalex.animals.first.number_of_legs\n```\n\u003c/details\u003e\n\n### Belongs to relationships\n\nIf you take a look at the diagram you will see the 3 possible *belong to* relationships, the opposite end of the *has many* ones. Again, we will only tackle the `Animal`s belongs to `Owner` relationship.\n\nTo make this relationship possible we only need to add an `attr_accessor` for `@owners` in `Animal` (`animal.rb`).\n\n```ruby\n...\n\nclass Animal\n  attr_accessor :owner\n\n  ...\nend\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eTry it out in your IRB console\u003c/summary\u003e\n\n```ruby\nrequire \"./animal.rb\"\nrequire \"./dog.rb\"\nrequire \"./spider.rb\"\nrequire \"./owner.rb\"\n\ndog = Dog.new(\"black\", \"Rax\")\nspider = Spider.new(85, \"Bob\")\nanimal = Animal.new(\"lion\", 4, \"Some name\")\n\nalex = Owner.new(\"Alex\")\nalex.animals\nalex.add_animal(dog)\nalex.animals\nalex.add_animal(spider)\nalex.animals\nalex.add_animal(animal)\n\nalex.animals.last.owner.name\n\nanimal.owner\nanimal.owner = alex\nanimal.owner\nanimal.owner.name\n\nalex.animals.last.owner.name\n```\n\u003c/details\u003e\n\n### Both ways relationships\n\nYou might have noticed that although the relationship should go both ways immediately it doesn't. To fix this you need to add the animal to the owner and then add the owner to the animal. This easily presents a problem in which a programmer can forget about this and the object results in a not-updated state.\n\nTo fix this we adapt our solution to manage both sides of the relationship in both cases. We need to modify our implementations of `add_animal` in `Owner` and create our setter for `@owner` in `Animal.\n\nIn `owner.rb`\n\n```ruby\nclass Owner\n  ...\n\n  def add_animal(animal)\n    @animals.push(animal)\n    animal.owner = self\n  end\nend\n```\n\nIn `animal.rb`\n\n```ruby\nclass Animal\n  attr_reader :owner\n\n  ...\n\n  def owner=(owner)\n    @owner = owner\n    owner.animals.push(self) unless owner.animals.include?(self)\n  end\n\n  ...\nend\n```\n\nNow any time we add an animal to an owner the owner of the animal is set as well, and vice versa.\n\n\u003cdetails\u003e\n\u003csummary\u003eTry it out in your IRB console\u003c/summary\u003e\n\n```ruby\nrequire \"./animal.rb\"\nrequire \"./dog.rb\"\nrequire \"./spider.rb\"\nrequire \"./owner.rb\"\n\ndog = Dog.new(\"black\", \"Rax\")\nspider = Spider.new(85, \"Bob\")\nanimal = Animal.new(\"lion\", 4, \"Some name\")\n\nalex = Owner.new(\"Alex\")\nalex.animals\n\ndog.owner\nalex.add_animal(dog)\ndog.owner\ndog.owner.name\nalex.animals\n\nspider.owner\nalex.add_animal(spider)\nspider.owner\nspider.owner.name\nalex.animals\n\nanimal.owner\nalex.add_animal(animal)\nanimal.owner\nanimal.owner.name\n\n\nalex.animals.count\nalex.animals.first.name\nalex.animals.first.number_of_legs\n\n\n\nsecond_animal = Animal.new(\"cat\", 4, \"Kitty\")\nsecond_animal.owner\nalex.animals.count\n\nsecond_animal.owner = alex\n\nsecond_animal.owner\nalex.animals.count\nalex.animals.last\nalex.animals.last.name\n```\n\u003c/details\u003e\n\n### Many-to-many relationship\n\nFinally, we are going to tackle the *many-to-many* relationship between `Animal` and `Vet`. This relationship is done through the class `Visit` which besides having the `@animal` and `@vet` involved it also has `@date`.\n\nFirst, let's create the `Vet` class in a file named `vet.rb`. We will initialize an empty list for the visits and a getter for it, and later you will see how we put elements in it:\n\n```ruby\nclass Vet\n  attr_reader :visits\n  attr_accessor :name, :address\n\n  def initialize(name, address)\n    @name = name\n    @address = address\n    @visits = []\n  end\nend\n```\n\nNow let's modify the `Animal` class in `animal.rb` to include `@visits` and a getter for it:\n\n```ruby\n...\n\nclass Animal\n  attr_reader :owner, :visits\n\n  def initialize(type, number_of_legs, name = \"Unknown\")\n    @id = Random.rand(1..1000)\n    @name = name\n    @number_of_legs = number_of_legs\n    @type = type\n    @liked_food = NoFood.new()\n    @visits = []\n  end\n\n  ...\nend\n```\n\nAnd last, we create the class `Visit` in a file named `visit.rb`. This class will have 3 attributes `@date`, `@animal`, and `@owner`. All of them will be given as part of the constructor and at the same time we will add the visit to `@visits` of the animal and owner:\n\n```ruby\nclass Visit\n  attr_reader :animal, :vet\n  attr_accessor :date\n\n  def initialize(date, animal, vet)\n    @date = date\n\n    @animal = animal\n    animal.visits \u003c\u003c self\n\n    @vet = vet\n    vet.visits \u003c\u003c self\n  end\nend\n```\n\nAnd that's it. We implemented the entire UML class diagram relationship.\n\n```ruby\nrequire \"./animal.rb\"\nrequire \"./dog.rb\"\nrequire \"./spider.rb\"\nrequire \"./owner.rb\"\nrequire \"./visit.rb\"\nrequire \"./vet.rb\"\n\ndog = Dog.new(\"black\", \"Rax\")\nspider = Spider.new(85, \"Bob\")\n\nvet_maria = Vet.new(\"Maria\", \"New York\")\nvet_john = Vet.new(\"John\", \"San Francisco\")\n\nvisit_1 = Visit.new(\"2017-12-22\", dog, vet_maria)\nvisit_2 = Visit.new(\"2017-12-31\", dog, vet_maria)\n\ndog.visits.count\ndog.visits.map { |visit| visit.date }\nvet_john.visits.count\nvet_maria.visits.count\nvet_maria.visits.map { |visit| visit.animal.name }\n\nvisit_3 = Visit.new(\"2017-11-11\", spider, vet_john)\nvisit_4 = Visit.new(\"2017-10-10\", spider, vet_maria)\n\nspider.visits.count\nspider.visits.map { |visit| visit.date }\nvet_john.visits.count\nvet_john.visits.map { |visit| visit.animal.name }\nvet_maria.visits.count\nvet_maria.visits.map { |visit| visit.animal.name }\n```\n\u003c/details\u003e\n\n------\n\n_If you spot any bugs or issues in this activity, you can [open an issue with your proposed change](https://github.com/microverseinc/curriculum-transversal-skills/blob/main/git-github/articles/open_issue.md)._\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmicroverse-fullstack-program%2Fruby_oop_principles","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmicroverse-fullstack-program%2Fruby_oop_principles","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmicroverse-fullstack-program%2Fruby_oop_principles/lists"}