Abstract Methods
Part of the Object Oriented Programming section of Coddy's Ruby journey. Lesson 34 of 57.
A superclass can define the steps of an algorithm and leave some steps to its subclasses. This is the template method pattern: render below is fixed, while header and row differ per report:
class Report
def render(items)
[header, *items.map { |i| row(i) }, footer].join("\n")
end
def footer
"(#{self.class.name})"
end
end
class CsvReport < Report
def header
"name,price"
end
def row(item)
item.join(",")
end
end
puts CsvReport.new.render([["pen", 2], ["cup", 5]])Output:
name,price
pen,2
cup,5
(CsvReport)The superclass should still say which methods a subclass must write. The convention is an abstract method that raises NotImplementedError with a clear message:
class Report
def render(items)
[header, *items.map { |i| row(i) }].join("\n")
end
def header
raise NotImplementedError, "#{self.class} must implement header"
end
def row(_item)
raise NotImplementedError, "#{self.class} must implement row"
end
end
class HtmlReport < Report
def header
"[table]"
end
end
begin
puts HtmlReport.new.render([1])
rescue NotImplementedError => e
puts e.message
endOutput:
HtmlReport must implement rowNotImplementedError is not a StandardError, so a bare rescue does not catch it. That keeps a missing method from being swallowed by general error handling; rescue it by name when you need to:
def risky
raise NotImplementedError, "missing step"
end
begin
begin
risky
rescue => e
puts "general handler: #{e.message}"
end
rescue NotImplementedError => e
puts "caught by name: #{e.message}"
end
puts NotImplementedError.ancestors.include?(StandardError)Output:
caught by name: missing step
falseNext to abstract methods, a template can offer hook methods: steps with a default that subclasses may override. footer above is a hook; so is a yes-or-no method such as include_total?:
class Report
def render(items)
lines = [header, *items.map { |i| row(i) }]
lines << total_line(items) if include_total?
lines.join("\n")
end
def include_total? # hook: default answer
false
end
endChallenge
EasyComplete the report classes. Report#render(items) returns the header line, one row per item and the footer, joined by newlines. header and row(item) are abstract: they raise NotImplementedError with HtmlReport must implement row (the class name and the method). footer is a hook that returns end of report. CsvReport has the header name,qty and rows pen,3. TextReport has the header NAME QTY, rows with the name padded to 8 characters (pen 3), and the footer 2 items. HtmlReport only defines the header [table].
The supplied code reads the report kind from the first line and items as name qty lines, then prints render or the error message.
Your code goes in report.rb, csv_report.rb, text_report.rb and html_report.rb. main.rb holds the supplied input/output code and cannot be edited.
Try it yourself
require_relative 'report'
require_relative 'csv_report'
require_relative 'text_report'
require_relative 'html_report'
# Supplied input/output code: keep it as it is
input = []
while (line = gets)
input << line.chomp
end
kinds = { "csv" => CsvReport, "text" => TextReport, "html" => HtmlReport }
report = kinds[input[0]].new
items = input.drop(1).map(&:split)
begin
puts report.render(items)
rescue NotImplementedError => e
puts e.message
end
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Classes and Objects
Working With FilesDefining ClassesInstance VariablesMethods and selfto_s and inspectRecap - Library Card7Polymorphism and Duck Typing
PolymorphismDuck TypingAbstract Methodscase and ClassesRecap - Payment Methods2Accessors and Self
Readers and WritersCustom Settersself in MethodsIdentity and EqualityRecap - Contact List8Special Methods
Equality and hashOperator MethodsIndexing and AppendingCustom CollectionsRecap - Fractions11Project: Library Management
Books and MembersBorrowing Books3Class Methods and Variables
Class MethodsClass Instance VariablesConstantsFactory MethodsRecap - Coupon CodesPractice on your own: Online Ruby compiler