Class: Class

Inherits:
Object
  • Object
show all
Defined in:
lib/use.rb

Constant Summary collapse

USE_VERSION =

The version of the ‘use’ library

'1.3.0'

Instance Method Summary collapse

Instance Method Details

#use(*args) ⇒ Object

Allows you to include mixins in a fine grained manner. Instead of including all methods from a given module, you can can instead mixin only those methods you want through a combination of the :include, :exclude, and :alias options.

Examples:

# Defines a 'bar' and 'baz' method
module Foo
   def bar
      "hello"
   end
   def baz
     "world"
   end
end

# Defines a 'bar', 'blah', and 'zap' methods
module Test
   def bar
      "goodbye"
   end
   def blah
      "new york"
   end
   def zap
      "zap"
   end
end

# From the Foo module, only mixin the 'bar' method. From the Test
# module exclude the 'bar' and 'zap' methods.
class Zap
   use Foo, :bar
   use Test, :exclude => [:bar, :zap]
end

z = Zap.new

z.bar  # => "hello"
z.baz  # => NoMethodError - wasn't mixed in
z.zap  # => NoMethodError - wasn't mixed in
z.blah # =>"new york"

# Alias a method on the fly
class MyKlass
   use Foo :alias => {:bar, :test}
end

m = MyKlass.new
m.test # => "hello"
m.bar  # => NoMethodError - was aliased to 'test'

If no options follow the module name this method is identical
to a standard include.

Designer's note: Most of the explicit .to_s calls on method lists are
here to deal with the fact that Ruby 1.8 returns strings for method
lists while Ruby 1.9 returns symbols. To ensure compatibility, and
preserve my sanity, all method names are converted to strings.


74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/use.rb', line 74

def use(*args)
   valid_keys = %w/include exclude alias/
   excluded_methods = []
   included_methods = []
   aliased_methods  = []

   mod = args.shift.clone

   # If no arguments follow the module name, treat it as a standard include
   if args.empty?
      included_methods.concat(mod.instance_methods)
   end

   m = Module.new

   args.each{ |arg|
      if arg.kind_of?(Hash)
         arg.each{ |key, val|
            case key.to_s
               when 'include'
                  if val.respond_to?(:each)
                     val.each{ |element| included_methods << element.to_s }
                  else
                     included_methods << val.to_s
                  end
               when 'exclude'
                  if val.respond_to?(:each)
                     val.each{ |element| excluded_methods << element.to_s }
                  else
                     excluded_methods << val.to_s
                  end
               when 'alias'
                  aliased_methods.push(val)
               else
                  raise "invalid key '#{key}'"
            end
         }
      else
         included_methods.push(arg.to_s)
      end
   }

   unless included_methods.empty? || excluded_methods.empty?
      err = 'you cannot include and exclude in the same statement'
      raise ArgumentError, err
   end

   local_instance_methods = mod.instance_methods.map{ |e| e.to_s }

   excluded_methods.map!{ |e| e.to_s }

   # Remove excluded_methods methods
   unless excluded_methods.empty?
      (local_instance_methods & excluded_methods).each{ |meth|
         mod.module_eval{ remove_method(meth) }
      }
   end

   # Alias methods. All aliased methods are automatically included.
   aliased_methods.each{ |pair|
      pair.each{ |old_method, new_method|
         included_methods << new_method
         mod.module_eval{
            alias_method(new_method, old_method)
            remove_method(old_method) rescue nil
         }
      }
   }

   included_methods.map!{ |e| e.to_s }

   # Remove all methods not specifically included. The rescue was needed
   # for those cases where a module included another module. Also, don't
   # remove methods from classes that already exist unless specifically
   # included.
   unless included_methods.empty?
      (local_instance_methods - included_methods).each{ |meth|
         if superclass.instance_methods.include?(meth)
            if included_methods.include?(meth)
               mod.module_eval{ undef_method(meth) rescue nil }
            else
               mod.module_eval{ remove_method(meth) rescue nil }
            end
         else
            mod.module_eval{ undef_method(meth) rescue nil }
         end
      }
   end

   m.module_eval{ include mod }

   # Raise a warning if methods are shadowed (in $VERBOSE mode)
   if $VERBOSE
      local_instance_methods = instance_methods(true)
      m.instance_methods.each{ |meth|
         next unless local_instance_methods.include?(meth)
         msg = "method '#{meth}' aliased, shadows old '#{meth}'"
         warn MethodRedefinedWarning, msg
      }
   end

   include m
end