The use of parameter to block

Consider these two examples:

class Object
  def convert(filename)
    args ||= {}
    args[:to] = filename
    args = yield(args)
  end
end

class Hash  
  def seek(pos)
    self[:seek] = pos
    self
  end
  def duration(length)
    self[:duration] = length
    self
  end
end

convert "findinng.avi" do |args|
  args.seek '00:01:13'
  args.duration '00:10:01'
end
class Object
  def convert(filename)
    @args ||= {}
    @args[:to] = filename
    yield
  end
  
  def seek(pos)
    @args[:seek] = pos
  end
  def duration(length)
    @args[:duration] = length
  end
end

convert "findinng.avi" do
  seek '00:01:13'
  duration '00:10:01'
end

Block scope in Ruby

Watch this code run, if you still mad about ruby block.

class Test
  def redefine_a(change, &block)
    a = "value for redefine_a"
    puts "before call block: #{eval("a", block.binding)}"
    if change
      a = block.call.to_s
    else
      block.call.to_s
    end
    puts "after call block: #{eval("a", block.binding)}"
    return "returned from redefined_a: #{a}"
  end
end
 
class Invoker
  def self.invoke
    a = "original"
    puts Test.new.redefine_a(false) {a = "changed_1"}
    puts Test.new.redefine_a(true) {a = "changed_2"}
  end
end
 
Invoker.invoke

A way to learn fast: break and fix

I saw several lines of code in bcat like below:

options = {
  :Host => '127.0.0.1',
  :Port => 0,
  :format => nil,
  :title => Dir.pwd,
  :browser => (ENV['BCAT_BROWSER'].to_s.size > 0 ? ENV['BCAT_BROWSER'] : 'default'),
  :ansi => false,
  :debug => false,
  :tee => !!($0 =~ /tee$/)
}
 
(class <<self;self;end).send(:define_method, :notice) { |message|
  warn "#{File.basename($0)}: #{message}" if options[:debug] }
 
 
 
notice "loaded bcat v#{Bcat::VERSION}"
 
browser = Bcat::Browser.new(options[:browser])
notice "env BCAT_BROWSER=#{options[:browser].inspect}"
notice "env BCAT_COMMAND='#{browser.command}'"

Apparently, the author defined a method named ‘notice’in an extremely twisted way:

(class <<self;self;end).send(:define_method, :notice) { |message|
  warn "#{File.basename($0)}: #{message}" if options[:debug] }

But why? Isn’t it just to define a simple method? I really don’t like to make thing unnecessarily complicated, so I start to try to simplify this.

Act I

Let’s be straight forward:

def notice(message)
    warn "#{File.basename($0)}: #{message}" if options[:debug]
end

But I then got this error when run the code:

Undefined local variable or method ‘options’

It turns out the methods does not recognize variable outside its definition,  we need some help from ruby block.

Act II

So I change the code into this:

define_method :notice do |message|
    warn "#{File.basename($0}: #{message}" if options[:debug]
end

And, I failed again:

undefined method `define_method’ for main:Object (NoMethodError)

It seems I’ve used “use define_method” in the wrong context, it should used inside the class definition, like this:

self.class.define_method :notice do |message|
    warn "#{File.basename($0}: #{message}" if options[:debug]
end

Still fails, unfortunately:

private method `define_method’ called for Object:Class (NoMethodError)

How cant I call a private method? It almost impossible! But don’t forget we still have a super hammer in our toolbox: “send”

Act III

self.class.send(:define_method, :notice) do |message|
    warn "#{File.basename($0): #{message}}" if options[:debug]
end

Phew~, It finally works:)

Althougth the result shows that I failed in accomplishing my original goal, but the good news is by doing so I’ve made repid progress in grasping ruby.  I am not sure if this approach could work on others, but It’s worth to try:)


TCP/IP Patcher

If you are using windows XP SP3, and have trouble in accessing shared folder such as:

1. Error: no more connection can be made to this remote computer at this time

2. Cannot copy file larger than 3 MB

try this: Windows-XP-SP3-TCPIP-Patcher