Skip to content

Instantly share code, notes, and snippets.

@EdmundLeex
Last active December 16, 2017 00:20
Show Gist options
  • Save EdmundLeex/e42711cfb16d66a2f29356f3e6e246bd to your computer and use it in GitHub Desktop.
Save EdmundLeex/e42711cfb16d66a2f29356f3e6e246bd to your computer and use it in GitHub Desktop.
FigLeaf
# Tools for making inherited interfaces private to a class.
module FigLeaf
module Macros
# Given a list of classes, modules, strings, and symbols, compile
# a combined list of methods. Classes and modules will be queried
# for their instance methods; strings and symbols will be treated
# as method names.
#
# Once the list is compiled, make all of the methods private.
#
# Takes an optional options hash, which can include the following options:
#
# - :ancestors is a boolean determining whether to consider
# ancestors classes and modules.
#
# - :except is a list of classes, modules, and method names which
# will be excluded from treatment.
def hide(*stuff)
hide_methods(self, [Object], *stuff)
end
# Like #hide, only hides methods at the class/module level.
def hide_singletons(*stuff)
hide_methods(singleton_class, [Class], *stuff)
end
# The shared bits of #hide and #hide_singletons
def hide_methods(mod, except_defaults, *stuff)
options = stuff.last.is_a?(Hash) ? stuff.pop : {}
include_ancestors = options.fetch(:ancestors){false}
except = Array(options.fetch(:except){except_defaults})
protect = Array(options[:protect])
except_methods = collect_methods(true, *except)
protect_methods = collect_methods(true, *protect)
methods_to_hide = collect_methods(include_ancestors, *stuff)
(methods_to_hide - except_methods).each do |method_name|
mod.module_eval do
next unless method_defined?(method_name)
if protect_methods.include?(method_name)
protected method_name
else
private method_name
end
end
end
end
# Given a list of classes, modules, strings, and symbols, compile
# a combined list of methods. Classes and modules will be queried
# for their instance methods; strings and symbols will be treated
# as methods names. +include_ancestors+ determines whether to
# include methods defined by class/module ancestors.
def collect_methods(include_ancestors, *methods_or_modules)
methods_or_modules.inject([]) {|methods, method_or_module|
case method_or_module
when Symbol, String
methods << method_or_module.to_sym
when Module # also includes classes
methods.concat(method_or_module.instance_methods(include_ancestors))
when Array
methods.concat(method_or_module)
else
raise ArgumentError, "Bad argument: #{method_or_module.inspect}"
end
}
end
end
def self.clothe(other)
other.extend(Macros)
end
def self.included(other)
clothe(other)
other.singleton_class.extend(Macros)
end
def self.extended(object)
clothe(object.singleton_class)
end
end
class Post < ActiveRecord::Base
include FigLeaf
hide ActiveRecord::Base, ancestors: true,
except: [Object, :init_with, :new_record?,
:errors, :valid?, :save]
hide_singletons ActiveRecord::Calculations,
ActiveRecord::FinderMethods,
ActiveRecord::Relation
# ...
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment