18 Sep Ruby Part 3, Continue using classes (using self, super, inheritance, encapsulation)
Let’s continue with using classes
Encapsulation
Let’s see some example
1 2 3 4 5 | send_post(“the title”, 14 ) def send_post(title, owner_id) retrieve_owner(owner_id) end |
Now we leave all the work to the class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | post = Post. new post.title = “Ruby classes” post.owner = current_user.id send_post(post.title) def send_post(title) title.owner end class Post attr_accessor :name def owner retrieve_user(owner_id) end end |
Visibility
By default in ruby the methods are public for example
1 2 3 4 5 6 7 8 9 | class Post def up_vote(friend) bump_karma friend.bump_karma end def bump_karma puts “Karma upvote for #{name}” end end |
In the before example we can see that all methods are public, but the bump_karma method it doesnt be public, then we need to change the method to private method, but if we declared the method “private” we can’t call the methods with explicit receiver and for that we gonna declare the method as “protected” to prevent to use from outside and we want use only inside, from other instance in the same class.
1 2 3 4 5 6 7 8 9 10 11 | class Post def up_vote(friend) bump_karma friend.bump_karma end protected def bump_karma puts “Karma upvote for #{name}” end end |
Inheritance
Let’s continue with the inheritance, and in the next example we can see how we have a duplicated code and later we can see when we use inheritance we can avoid duplicated code
Using duplicated code
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Image attr_accessor :title , :size , :url def to_s “ #{@title}, #{@size}” end end class Video attr_accessor :title , :size , :url def to_s “ #{@title}, #{@size}” end end |
Now using inheritance we can avoid duplicate code
1 2 3 4 5 6 | class Attachment attr_accessor :title , :size , :url def to_s “ #{@title}, #{@size}” end end |
Then at the momento to use inheritance
1 2 3 4 5 | class Video < Attachment end class Image < Attachment end |
Then we can use to_s method in both classes and if we want to add some specific attribute to specific sub-class and we will avoid the duplicate code.
SUPER
We can see in the next example, if we don’t use the @name = name the variable won’t show the name but if we use the super(name) automaticly the name will be inititialized by the super class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class User def initialize(name) @name = name end end class Follower < User def initialize(name, follower) #@name = name #super(name) @follower = follower end def relations puts "#{@name} is followed by #{@follower}" end end UserTest = Follower. new ( "Heriberto" , "Marios" ) UserTest.relations |
Some example more advanced, we can see in the next example, where the child invoke super the first search for that string in the Parent and if don’t find any method with the called name, it will continue with the search in the grandparent class.
class Grandparent
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | def my_method "String called from grandparent" end class Parent < Grandparent end class Child < Parent def my_method string = super puts "#{string} called from child method" end end Son = Child. new Son.my_method |
Overriding Methods
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <strong> </strong> class Attachment def preview "video" end end class Image < Attachment def preview "Image" end end image = Image. new puts image.preview |
This will print
1 | Image |
Hide Instances variables
We can see the duplicated code in the next example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Tweet def initialize(first, last) @first = first @last = last end def tweet_content(description) [ @first , @last ].compact.join( ", " ) + " Say \'" + description + "\'" end def Profile [ @first , @last ].compact.join( ", " ) end end tweet = Tweet. new ( "heriberto" , "perez" ) puts tweet.tweet_content "Some description" |
Now let’s go to refactor the below code hiding variables
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class Tweet def initialize(first, last) @first = first @last = last end def name [ @first , @last ].compact.join( ", " ) end def tweet_content(description) name + " Say \'" + description + "\'" end def Profile name end end tweet = Tweet. new ( "heriberto" , "perez" ) puts tweet.tweet_content "Some description" |
Exercises to practice a little
Collection Class
Managing our game library is getting a little difficult with all of these game instances floating around. Let’s create a newLibrary class which will manage a collection of Game objects. Create a Library class whose initializer stores a gamesarray. Ensure games is publicly accessible.
Final code:
1 2 3 4 5 6 7 8 | class Library attr_accessor :games def initialize(games) @games = games end end |
Encapsulation
We got a little ahead of ourselves and added a has_game? method to Library that takes in the name of a game. Then, we realized that it doesn’t compare year or system! Rather than passing in a game name to the has_game? method, pass in an instance of a game, and check for equality with the entire game object using the declared == method on Game.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | class Library attr_accessor :games def initialize(games) self .games = games end def has_game?(search_name) for game in games return true if game.name == search_name end false end end <strong>Final Code</strong> class Library attr_accessor :games def initialize(games) self .games = games end def has_game?(search_name) for game in games return true if game == search_name end false end end |
Instance Method
We can initialize our Library with an array of games, but the only way to add games from outside the class is to use thegames accessor method and alter the array. This is breaking encapsulation, so let’s create a new method in Librarycalled add_game which takes in a game and adds it to the games array.
origin code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Library attr_accessor :games def initialize(games) self .games = games end def has_game?(search_game) for game in games return true if game == search_game end false end end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class Library attr_accessor :games def initialize(games) self .games = games end def has_game?(search_game) for game in games return true if game == search_game end false end def add_game(game) games << game end end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | class Library attr_accessor :games def initialize(games) self .games = games end def has_game?(game) for game in games return true if game == search_game end false end def add_game(game) games << game log(game.name) end private def log(string) puts string end end |
For our ArcadeGame class, we’ll also want to track the weight of these giant cabinets taking up all of our available space. Luckily we thought ahead: we already take in an options parameter that we can stick weight into! Override theinitialize method for ArcadeGame to take in the same parameters as its parent class, call super, and then setweight.
1 2 3 4 5 6 7 8 9 | class ArcadeGame < Game attr_accessor :weight def initialize(name, options={}) super @weight = options[ :weight ] end end class ConsoleGame < Game end |
Whenever we output a game right now it’ll show up using the to_s method from Object, the parent object of Game. A basic to_s implementation is completed below on Game. Override this for ConsoleGame to also show the system the game is on.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Game attr_accessor :name , :year , :system attr_reader :created_at def initialize(name, options={}) self .name = name self .year = options[ :year ] self .system = options[ :system ] self .created_at = Time .now end def to_s self .name end end class ConsoleGame < Game def to_s @name + @system end end |
Refactoring
Our to_s method will come in very handy. Whenever we need to output a game, rather than calling a method on the game, we can just output the game object and Ruby will call to_s on it automatically. Refactor both classes below. Change thedescription method of Game to use the to_s method implicitly. Then remove any duplicated code in ConsoleGame. Note: you’ll need to use self inside a class to reference the entire object.
Origin code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | class Game attr_accessor :name , :year , :system attr_reader :created_at def initialize(name, options={}) self .name = name self .year = options[ :year ] self .system = options[ :system ] @created_at = Time .now end def to_s self .name end def description "#{self.name} was released in #{self.year}." end end class ConsoleGame < Game def to_s "#{self.name} - #{self.system}" end def description "#{self.name} - #{self.system} was released in #{self.year}." end end |
Final code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class Game attr_accessor :name , :year , :system attr_reader :created_at def initialize(name, options={}) self .name = name self .year = options[ :year ] self .system = options[ :system ] @created_at = Time .now end def to_s self .name end def description "#{self} was released in #{self.year}." end end class ConsoleGame < Game def to_s name = super "#{name} - #{self.system}" end end |
Angel Solorio
Posted at 02:13h, 18 JulyWhat’s the different between use @variable_name instead of self.variable_name?