Help me understand this technique from an open source app?


#1

n the code at the bottom (from HTTParty), the module is included in
some other class (“O”), and among the effects is that (for example)
the method default_params becomes available in “O”. I don’t understand
the technique. Given that the module is used as a mixin, then if I
wanted to have that method, I could just define it under module
HTTParty with a normal def. Moreover, the following doesn’t work:

class Parent
include HTTParty

def my_method
default_params :x => “X”
end
end

class Child < Parent
def other
my_method
end
end

There’s some sophisticated meta programming going on. Any help
understanding it would be greatly appreciated!

Pito

=============from HTTParty==============
module HTTParty
AllowedFormats = {:xml => ‘text/xml’, :json => ‘application/json’,
:html => ‘text/html’}

def self.included(base)
base.extend ClassMethods
base.send :include, ModuleLevelInheritableAttributes
base.send(:mattr_inheritable, :default_options)
base.instance_variable_set("@default_options", {})
end

module ClassMethods
def default_options
@default_options
end

def http_proxy(addr=nil, port = nil)
default_options[:http_proxyaddr] = addr
default_options[:http_proxyport] = port
end

def base_uri(uri=nil)
return default_options[:base_uri] unless uri
default_options[:base_uri] = HTTParty.normalize_base_uri(uri)
end

def basic_auth(u, p)
default_options[:basic_auth] = {:username => u, :password => p}
end

def default_params(h={})
raise ArgumentError, ‘Default params must be a hash’ unless
h.is_a?(Hash)
default_options[:default_params] ||= {}
default_options[:default_params].merge!(h)
end

def headers(h={})
raise ArgumentError, ‘Headers must be a hash’ unless h.is_a?
(Hash)
default_options[:headers] ||= {}
default_options[:headers].merge!(h)
end

def format(f)
raise UnsupportedFormat, “Must be one of:
#{AllowedFormats.keys.join(’, ')}” unless AllowedFormats.key?(f)
default_options[:format] = f
end

def get(path, options={})
perform_request Net::HTTP::Get, path, options
end

def post(path, options={})
perform_request Net::HTTP::Post, path, options
end

def put(path, options={})
perform_request Net::HTTP::Put, path, options
end

def delete(path, options={})
perform_request Net::HTTP::Delete, path, options
end

private
def perform_request(http_method, path, options) #:nodoc:
Request.new(http_method, path,
default_options.dup.merge(options)).perform
end
end


#2

Pito S. wrote:

n the code at the bottom (from HTTParty), the module is included in
some other class (“O”), and among the effects is that (for example)
the method default_params becomes available in “O”.

Well, consider this first:

module ClassMethods
def foo
puts “hello from class”
end
end

module InstanceMethods
def bar
puts “hello from instance”
end
end

class Parent
include InstanceMethods
extend ClassMethods
end

Parent.foo
Parent.new.bar

However this pattern is very common, so it’s refactored in two ways.

  1. put the module containing class methods inside the module
    containing the instance methods

module MyTools
… instance methods go here

module ClassMethods
… class methods go here
end
end

  1. use the “included” hook so that when you include MyTools, it
    automatically does extend MyTools::ClassMethods at the same time.

The example above becomes:

module MyTools
def self.included(base)
base.extend ClassMethods
end

module ClassMethods
def foo
puts “hello from class”
end
end

def bar
puts “hello from instance”
end
end

class Parent
include MyTools
end

Parent.foo
Parent.new.bar

At least, I think that’s what you were asking about. There’s also the
ModuleLevelInheritableAttributes there, but you didn’t post all the code
for that. But notice that the include hook is calling

mattr_inheritable :default_options

in the base class, and also setting a class instance variable.

HTH,

Brian.