Class: Class

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

Constant Summary collapse

USE_VERSION =

The version of the ‘use’ library

'1.4.0'.freeze

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 Alpha
  def bar
    puts 'hello'
  end

  def baz
    puts 'world'
  end
end

# Defines a 'bar', 'blah', and 'zap' methods
module Beta
  def bar
    puts 'goodbye'
  end

  def blah
    puts 'new york'
  end

  def zap
    puts 'zap'
  end
end

# From the Alpha module, only mixin the 'bar' method. From the Beta
# module exclude the 'bar' and 'zap' methods.
class Zap
  use Alpha, :bar
  use Beta, :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 Alpha, :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.


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
177
178
# File 'lib/use.rb', line 76

def use(*args)
  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?
    self_instance_methods = self.instance_methods.map{ |e| e.to_s }
    (local_instance_methods - included_methods).each{ |meth|
      if self_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