lib/behaves.rb in behaves-0.2.0 vs lib/behaves.rb in behaves-0.3.0
- old
+ new
@@ -1,37 +1,87 @@
require 'behaves/version'
require 'set'
module Behaves
- def implements(*methods)
- @behaviors ||= Set.new(methods)
+ def implements(*methods, **opts)
+ @_public_behaviors ||= Set.new
+ @_private_behaviors ||= Set.new
+
+ if opts[:private] == true
+ @_private_behaviors += Set.new(methods)
+ else
+ @_public_behaviors += Set.new(methods)
+ end
end
def inject_behaviors (&block)
@inject_behaviors ||= block
end
def behaves_like(klass)
add_injected_behaviors(klass)
- at_exit { check_for_unimplemented(klass) }
+ at_exit {
+ check_for_unimplemented(klass, :public)
+ check_for_unimplemented(klass, :private)
+ }
end
private
- def check_for_unimplemented(klass)
- required = defined_behaviors(klass)
+ def check_for_unimplemented(klass, scope)
+ required = defined_behaviors(klass, scope)
- implemented = Set.new(self.instance_methods - Object.instance_methods)
+ unimplemented = required - implemented(scope)
- unimplemented = required - implemented
-
return if unimplemented.empty?
- raise NotImplementedError, "Expected `#{self}` to behave like `#{klass}`, but `#{unimplemented.to_a.join(', ')}` are not implemented."
+
+ # basic "unimplemented method" error message
+
+ err = <<~ERR
+ \n\n Expected `#{self}` to behave like `#{klass}`, but the following #{scope} methods are unimplemented:\n
+ #{unimplemented.to_a.map{|method| " * `#{method}`"}.join("\n")}\n
+ ERR
+
+
+ # add "wrong scope" message
+
+ other_scopes = (scope == :public) ? [:private] : [:public]
+ methods_in_other_scopes = Set.new( other_scopes.map{|s| implemented(s)}.map(&:to_a).flatten.compact )
+ methods_in_wrong_scope = unimplemented && methods_in_other_scopes
+
+ if !methods_in_wrong_scope.empty?
+ err += <<~ERR
+ \n The following methods appear to be defined, but in the wrong scope:\n
+ #{methods_in_wrong_scope.to_a.map{|method| " * `#{method}`"}.join("\n")}\n\n
+ ERR
+ end
+
+
+ raise NotImplementedError, err
end
- def defined_behaviors(klass)
- if behaviors = klass.instance_variable_get("@behaviors")
+ def implemented(scope)
+ lookups = {
+ public: :instance_methods,
+ private: :private_instance_methods,
+ }
+
+ method_lookup_sym = lookups.fetch(scope) do
+ raise ArgumentError.new <<~ERR
+
+ Invalid `scope`: #{scope}
+ Valid `scope`s include: #{lookups.keys.map{|sym| "`#{sym.inspect}`"}.join(', ')}
+ ERR
+ end
+
+ methods = self.send(method_lookup_sym, false)
+
+ Set.new(methods)
+ end
+
+ def defined_behaviors(klass, scope)
+ if behaviors = klass.instance_variable_get(:"@_#{scope}_behaviors")
behaviors
else
raise NotImplementedError, "Expected `#{klass}` to define behaviors, but none found."
end
end