Skip to content

Instantly share code, notes, and snippets.

@codeodor
Created November 14, 2012 02:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save codeodor/4069922 to your computer and use it in GitHub Desktop.
Save codeodor/4069922 to your computer and use it in GitHub Desktop.
Friend functions in Ruby
# 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
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}"
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}"
@codeodor
Copy link
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment