Forum: Ruby Help me understand this technique from an open source app?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Pito S. (Guest)
on 2009-01-24 16:10
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
Brian C. (Guest)
on 2009-01-25 17:43
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

2. 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.
This topic is locked and can not be replied to.