public
Created

Friend functions in Ruby

  • Download Gist
friend.rb
Ruby
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
# See http://www.ruby-doc.org/core-1.9.3/Kernel.html#method-i-set_trace_func for docs on this function
 
require 'ostruct'
 
module Friendly
def self.included(base)
base.extend(ClassMethods)
end
def method_missing(name, *args, &block)
self.class.disable_trace_function
friends = self.class.friends
call_history = self.class.call_history
called_by_friend = friends.include?(call_history[0].id) || friends.include?(call_history[0].class_constant)
# puts friends.inspect
# puts call_history.inspect
# puts called_by_friend.inspect
if self.private_methods.include?(name) && called_by_friend
return self.send(name, *args, &block)
else
super(name, *args, &block)
end
ensure
self.class.enable_trace_function
end
module ClassMethods
def friend(method_symbols_and_or_classes)
@call_history ||= [OpenStruct.new(id: nil)] * 1
@trace_function =
proc do |event, file, line, id, binding, class_constant|
trace_info = OpenStruct.new(
event: event,
file: file,
line: line,
id: id,
binding: binding,
class_constant: class_constant,
time: Time.now.to_f
)
@call_history = @call_history[1..-1] << trace_info if we_care_about?(event, class_constant)
end
enable_trace_function
@friends ||= []
@friends += [method_symbols_and_or_classes].flatten
end
attr_reader :friends, :call_history, :trace_function
def enable_trace_function
set_trace_func(@trace_function)
end
def disable_trace_function
set_trace_func(nil)
end
def we_care_about?(event, class_constant)
event == "call" && !(class_constant.to_s =~ /Friendly/)
end
end
end
 
 
rectangle_example.rb
Ruby
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45
require_relative 'friend'
 
class Rectangle
include Friendly
def initialize(width, height)
@width = width
@height = height
end
friend :duplicate
 
def from_square(square)
#return new(square.side, square.side)
@width = square.side
@height = square.side
return self
end
private
def width
@width
end
def height
@height
end
end
 
 
rect = Rectangle.new(1, 2)
puts "Rectangle has declared any function named 'duplicate' as a friend.\n\n"
puts "Rectangle width is private:"
val = rect.width rescue puts("\t#{$!}")
puts "Rectangle height is private:"
val = rect.height rescue puts("\t#{$!}")
 
def duplicate(rectangle)
Rectangle.new(rectangle.width, rectangle.height)
end
puts
puts "'duplicate' uses rectangles privates, but does not fail:"
new_rect = duplicate(rect)
puts "\t#{rect.inspect}"
puts "\t#{new_rect.inspect}"
square_example.rb
Ruby
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
require_relative 'friend'
 
class Rectangle
include Friendly
def initialize(width, height)
@width = width
@height = height
end
friend :duplicate
 
def self.from_square(square)
return new(square.side, square.side)
@width = square.side
@height = square.side
return self
end
private
def width
@width
end
def height
@height
end
end
 
class Square
include Friendly
friend Rectangle
def initialize(side)
@side = side
end
private
def side
@side
end
end
 
puts "Square declares the Rectangle class to be it's friend.\n\n"
square = Square.new(3)
 
puts "Square#side is private:"
puts square.side rescue puts("\t#{$!}")
puts
puts "But a Rectangle can use it:"
rect = Rectangle.from_square(square)
puts "\t#{rect.inspect}"
puts "\t#{square.inspect}"

There is a bug in this code if you run both examples together. What is it?

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.