Ruby Part 3, Continue using classes (using self, super, inheritance, encapsulation)

Ruby Part 3, Continue using classes (using self, super, inheritance, encapsulation)

Let’s continue with using classes
Encapsulation

Let’s see some example

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

 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


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.


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


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


class Attachment
 attr_accessor :title, :size, :url
 def to_s
  “#{@title}, #{@size}”
 end
 end

Then at the momento to use inheritance

 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.


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


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

 

<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

 Image

Hide Instances variables
We can see the duplicated code in the next example


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

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:

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.

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:


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


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


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.


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.


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:

 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:

 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

1 Comment
  • Angel Solorio
    Posted at 02:13h, 18 July

    What’s the different between use @variable_name instead of self.variable_name?

Post A Comment