class: center # Object-Oriented Programming with Ruby ![Ruby](img/ruby-logo.png) [http://pjb3.me/bewd-ruby-oo](http://pjb3.me/bewd-ruby-oo) .footnote[ created with [remark](http://github.com/gnab/remark) ] --- # State and Behavior In Object-Oriented Programming, you compose programs by creating objects. Objects have state and classes have behavior. The state of an object is kept in its instance variables. Each object has its own set of instance variables. Changes to the instance variables in one object do not effect the instance variables of another. Each object also belongs to a class. The class is where methods are defined. Objects of the same class share the same set of methods. Objects do not have methods, classes do. When you call a method on an object, Ruby looks for the method in the object's class and calls that method. Ruby comes with many classes built-in. We've covered some of the more frequently used built-in classes in previous lessons, but in this lesson we will cover how to define your own programs. --- # Defining A Class All class names in Ruby must be a constant, so they must start with a capital letter. By convention, classes follow the **CamelCase** style instead of the **snake_case** style that is used for variables and method names or the **ALL_CAPS** style that is used for regular constants. You can use the built-in classes or create your own. Put this into a file named `my_program.rb`: ```ruby class Product end ``` An object is an instance of a class. You create an object by instantiating the class. A class is a factory for producing objects. You instantiate a class by calling the `new` method on the class: ```ruby product = Product.new puts product.inspect ``` .terminal $ ruby my_program.rb #
The weird looking numbers in the output here represents the object ID that Ruby assigns to every new object that is created while your program is running. Ruby uses this internally to keep track of the objects you have created. --- # Initializers and Instance Variables ```ruby class Product def initialize(name, price) @name = name @price = price end end product = Product.new("iPhone", 499) puts product.inspect ``` The `initialize` method is called the initializer (or constructor, in other languages). It is run when the object is created with the `new` method that you call on a class. Instance variables start with a `@` and can be accessed from within the class. .terminal $ ruby my_program.rb #
As you can see, the value of the instance variables are also included in the output when IRB inspects your object. Think of instance variables as being attached to an object instance, which is why they are called instance variables. --- # Reader methods ```ruby class Product def initialize(name, price) @name = name @price = price end def name @name end def price @price end end product = Product.new("iPhone", 499) puts product.name puts product.price ``` Reader methods, which have the same name as the instance variable, allow you to get the value of an object's instance variables from outside of the class: .terminal $ ruby my_program.rb iPhone 499 --- # Writer methods ```ruby class Product def name=(name) @name = name end end product = Product.new product.name = "iPhone" puts product.inspect ``` A writer method allows you to set the value of an object's instance variable from outside of the class. .terminal $ ruby my_program.rb #
--- # attr_accessor Ruby can generate readers and writers for you with `attr_accessor` ```ruby class Product attr_accessor :name, :price def initialize(name=nil, price=nil) @name = name @price = price end end product = Product.new product.name = "iPhone" product.price = 499 puts product.inspect puts product.name puts product.price ``` .terminal #
iPhone 499 --- # State Objects hold the state of some data and provide methods to do things with that data. Add this to the same program where the `Product` class is defined: ```ruby class Catalog def products @products ||= [] end def add(product) products << product end end ``` The `||=` operator will assign a value to the variable on the left side if it does not have one. It is the same thing as: ```ruby @products || @products = [] ``` Now in [your program][my_program], add some products to the catalog and take a look at what you have: ```ruby catalog = Catalog.new catalog.add Product.new("iPhone", 499) catalog.add Product.new("T-Shirt", 19) puts catalog.inspect ``` [my_program]: https://gist.github.com/pjb3/4e9aa40751226d482c1d --- # Inheritance You can subclass a class to override or extend behavior. When creating a class, if after the name you use the `<` operator and then the name of another class, that means the class you are creating will be a subclass of that class. In this case, `CatalogDatabase` is a subclass of `Catalog`: ```ruby class CatalogDatabase < Catalog end ``` Now change catalog to be a `CatalogDatabase` instead of a `Catalog`, so the full program looks like [this][my_program]. Notice that the method `add` that we previously defined in the super class `Catalog` is available to be called on our `catalog` instance, even though the `CatalogDatabase` class itself does not directly define an `add` method. It is inherited from the super class `Catalog`. [my_program]: https://gist.github.com/pjb3/7cd75cb1726081abecf9 --- # Overriding methods In a subclass, if you create a method with the same name as a method in the super class, that **overrides**, or replaces, the method in the super class with the one in the subclass. If you want to call the method from the super class, you use `super`. With no parentheses and no arguments, `super` calls the super class version of the method with the same arguments passed to the method in which `super` is being called from. .small[ ```ruby class CatalogDatabase < Catalog def add(product) super add_to_database(product) end def add_to_database(product) # A real program would be complicated than this puts "Adding #{product.inspect} to the database" end end ``` ] In this example, in the `add` method, the super class version of the `add` method is called first, then `add_to_database` is called. The output of [the program][my_program] will look like this, except all on one line: .small[ .terminal Adding #
to the database Adding #
to the database #
, #
]> ] [my_program]: https://gist.github.com/pjb3/d755e039c4e26ae77d1e --- # Modules Modules are used to share methods between classes. Modules are like classes in that they contain methods, but modules can't be directly instantiated. To use a module, you must **include** it into a class and then the methods defined in the module exist on the class as if they were defined in the class. ```ruby module DatabasePersistence def add(product) super add_to_database(product) end def add_to_database(product) # A real program would be complicated than this puts "Adding #{product.inspect} to the database" end end class CatalogDatabase < Catalog include DatabasePersistence end ``` [This version of the program][my_program] works the same as the previous version and produces the same output, but does so using a module. This module could be reused by being included into other classes. [my_program]: https://gist.github.com/pjb3/b312baefbf9f175e9a2d --- # Ancestors Modules work similarly to classes in that they are inserted into the class hierarchy for the class just like other classes. You can use the `ancestors` method to see the class hierarchy for a class: .terminal $ irb -r ./my_program.rb >> CatalogDatabase.ancestors => [CatalogDatabase, DatabasePersistence, Catalog, Object, Kernel, BasicObject] This should be read as `CatalogDatabase` is a subclass of `DatabasePersistence`, which is a subclass of `Catalog`, which is a subclass of `Object`, etc... Ruby does keep track of which of these classes are actually classes (_as opposed to being modules_) so when you call `superclass` you do actually get the class that the class is a subclass of. Modules in the class hierarchy are skipped over until an actual class, not a module, is reached. .terminal >> CatalogDatabase.superclass => Catalog Modules give Ruby the benefits of multiple inheritance without [the problems that come with true multiple inheritance](http://stackoverflow.com/questions/406081/why-should-i-avoid-multiple-inheritance-in-c) --- # self The current instance of a class can be referenced from within methods of a class as the special variable called `self`. Notice in the `discount` method in this code example, the call to `self.price`: ```ruby class Product DISCOUNT_RATE = 0.2 attr_accessor :name, :price def discount self.price * DISCOUNT_RATE end end ``` You don't have to explicitly use `self`. If you leave it out, Ruby will assume you want to call the method on `self`: ```ruby class Product DISCOUNT_RATE = 0.2 attr_accessor :name, :price def discount price * DISCOUNT_RATE end end ``` --- # Gotchas When you leave out `self`, there is no difference in the syntax between local variables and method calls with no arguments. This can cause problems though, like when trying to call a writer method, like this: ```ruby class Product def reduce_price(amount) price = price - amount end end ``` This doesn't do what you want because it creates a local variable `price` that is then unused. For this reason, when calling a writer, you must explicitly use `self`, like this: ```ruby class Product def reduce_price(amount) self.price = price - amount end end ``` --- # Shadowing methods with local variables Be careful of shadowing a method with a local variable. In the following code example, in the first line in the discount method, we establish a local variable named `price`. Then on the following line, the local variable is used instead of the reader method by the same name: ```ruby class Product attr_accessor :price def discount price = 99 price * DISCOUNT_RATE end end ``` You can always call the method instead of the local variable if you use `self` explicitly ```ruby class Product def discount price = 99 self.price * DISCOUNT_RATE end end ``` The common style in Ruby is to not call `self` explicitly unless you need to. --- # Flexible Initializer It would be nice to be able to create products like this: .terminal $ irb -r ./my_program >> Product.new(name: 'iPhone', price: 499) => #
In this version, the Product initializer takes a single argument, a Hash, of the attributes and their values. Using this, we could add more attributes and not have to modify all the code already calling the Product initializer. Also, if there are a lot of attributes, we can omit some, put them in any order and it is still clear what they are. So how do we do this? --- # Dynamically calling a writer A writer is just syntactic sugar for calling a method than ends in `=` .terminal >> product.name = 'iPhone' => "iPhone" >> product.name=('iPhone') => "iPhone" If it's just a method, then we can call it with `send`: .terminal >> product.send('name=', 'iPhone') => "iPhone" `send` is a method that takes the name of a method as its first argument and then calls the method passing along any arguments. If we use `send`, we can dynamically create the name of the writer: .terminal >> attr = 'name' => "name" >> product.send("#{attr}=", 'iPhone') => "iPhone" --- # Putting it together The `each` method yields each key and value to its block: ```Ruby class Product attr_accessor :name, :price def initialize(attrs={}) attrs.each do |attr, value| send("#{attr}=", value) end end end ``` Therefore we can now instantiate our Product using a Hash: .terminal $ irb -r ./my_program >> Product.new(name: 'iPhone', price: 499) => #
This style of initializer is commonly used in Rails code as you will see later. --- # Classes are objects too In Ruby, classes are objects too. They are instances of the class `Class`. Defining a class in the normal way, like this: ```ruby class Product attr_accessor :name, :price end ``` is actually just using a syntactic construct that is the same as this code: ```ruby Product = Class.new do attr_accessor :name, :price end ``` A class is just an instance of `Class` that is assigned to a constant. --- # Classes can have methods Since classes are objects, you can define methods on `Class` and then you will be able to call that method directly on a class. Class methods are useful for creating objects in a more specialized way than just instantiating it. For example, let's say we want a method where we pass it the id (a.k.a primary key) for a record in the database and we want the method to look up the record in the database that matches that id, instantiate an object and set all of the attributes on the object with the values from the columns we get from the database. We won't actually implement that method now because it's fairly complex, so we'll create a method that would do all that but for now just prints a string in place of making an actual database query: .small[ ```ruby class Class def find(id) puts "TODO: Get the #{name.downcase} record with id=#{id}" new end end class Product end Product.find(1) ``` ] The output of this program is: .small[ .terminal TODO: Get the product record with id=1 ] There are couple of interesting things happening here, let's go over each one --- # Open Classes .small[ ```ruby class Class def find(id) puts "TODO: Get the #{name.downcase} record with id=#{id}" new end end ``` ] First, `Class` is a class that is built-in to Ruby. Shouldn't we have gotten an error when we tried to define a class with the same name? No, we don't and the reason is that Ruby has a concept which is referred to as **open classes**. What this means is that when you create a class definition for a class that already exists, the code is applied to the original class as if it were in the original class definition. So this code: .small[ ```ruby class Foo def foo; "foo"; end end class Foo def bar; "bar"; end end ``` ] is equivalent to .small[ ```ruby class Foo def foo; "foo"; end def bar; "bar"; end end ``` ] .small[ _In Ruby, you can put multiple statements on one line and separate them with `;` semi-colon characters. This is generally a frowned upon style and I only did so here to fit everything on one slide._ ] --- # Predefined class methods .small[ ```ruby class Class def find(id) puts "TODO: Get the #{name.downcase} record with id=#{id}" new end end ``` ] Another interesting thing here is this `name` method. This method can be called on any class to get its name: .terminal >> String.name => "String" In this example, `name` is being called on the class without explicitly referring to `self` because as we said earlier, Ruby allows you to omit `self` if there is no conflicting local variable name. --- # Defining methods on object instances The problem with our find method is that we have defined the method for all classes. This is not what we want, we just want a method defined on our class. But how can we do that? The answer is that Ruby allows you to define a method directly on an object. You do that by putting the object instance before the method name in `def`. For example, we can create a String that has a `shout` method. .small[ ```ruby msg = "hello" def msg.shout "#{upcase}!" end puts msg.shout puts "Something Else".shout ``` ] In this example, we create the `msg` string and then we define a method on that instance with `def msg.shout`. Now when we call the `shout` method on that string, it works, but if we try to call that method on another string we get an error: .small[ .terminal HELLO! my_program.rb:9:in `
': undefined method `shout' for "Something Else":String (NoMethodError) ] --- # Singleton Class Wait a minute, on the first slide of this lesson, we said that objects have state they hold in instance variables and behavior is defined in methods in classes. Objects don't have methods, classes do. So how does this work? The answer is that each object actually has a special class called the `singleton_class`: .terminal >> "".singleton_class => #
> So what actually happens when you define a method on an object is that it defines in the method in the object's super class. When you call a method on an object, Ruby first checks the object's singleton class to see if it has the method. If it does not, then it checks each class in the object's ancestors, and uses the method from the first class it finds that has a method with that name. You can even look at the methods defined in a class: .terminal >> msg.singleton_class.instance_methods(false) => [:shout] >> "".singleton_class.instance_methods(false) => [] The argument `false` just means only methods defined directly in the class should be returned, not all methods on the class and its superclasses. --- # Class Methods So now armed with knowledge, we know that we could move our `find` method from `Class` to `Product`, like this: .small[ ```ruby def Product.find(id) puts "TODO: Get the #{name.downcase} record with id=#{id}" new end ``` ] This works, but there is a better way. In the context of a class definition, `self` refers to the class that is being defined, therefore if you prefix the method name with `self.`, you accomplish the same result: .small[ ```ruby class Product def self.find(id) puts "TODO: Get the #{name.downcase} record with id=#{id}" new end end ``` ] This is typically the way you will see class methods defined in Ruby code. --- # Review That completes our whirlwind tour of Object-Oriented Programming in Ruby. Here's what we covered: * Objects hold state in instance variables * Classes hold methods that are shared by all instance of the class * Classes are factories for making objects * How to use modules for sharing behavior / multiple inheritance * Classes methods are defined using `def self.whatever` instead of just `def whatever` for a regular instance method To learn more about this topic and help deepen your understanding of Ruby in general, I highly recommend you purchase and watch the [The Ruby Object Model and Metaprogramming](http://pragprog.com/screencasts/v-dtrubyom/the-ruby-object-model-and-metaprogramming) videos by Dave Thomas. They are not free, but are well worth it at $35 for the whole series. If you watch these videos and understand them, you will be well on your way to becoming an expert Ruby programmer.