Never been to DZone Snippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

« Newer Snippets
Older Snippets »
Showing 1-10 of 10 total  RSS 

using object(s) do ...

A little meta-hack that allows one to temporarily assign one or more objects into the scope of a block. My thanks to vinterbleg for optimizing it.

def using (*args)
  yield *args
end


For (a rather half-assed) example:

>> using [10,20,30], 40 do |x,y|
>>   x << y if y.is_a? Fixnum
>>   puts x.to_s
>> end
10203040
=> nil

With object do...

This code enters the scope of an object, so you can temporarily avoid having to reference it by name.

Effectively changes the value of 'self' in the block scope. This also enables you to run private methods on the object without using __send__.

def with(object, &block)
    object.instance_eval(&block)
end


Example of usage:
numbers = [1, 2, 3]

with numbers do
	map! { |n| n + 100 }
	reject! { |n| n % 2 == 0}
end

p numbers # => [101, 103]

n = 15
n = with n do
    self + 13
end
p n # => 28


This gets much more interesting with complex objects that have lots of attributes and you want strict control over how they're set.

Acts as Java Class Variable

// Ruby's Class Variable is soooooooooooooooo confusing
// http://www.oreillynet.com/ruby/blog/2007/01/nubygems_dont_use_class_variab_1.html
// However, if we use

module JCV
  def self.included(the_class)
    class << the_class
      def acts_like_java_class_variable( *arg)

        the_class = self
        singleton_class = class << self; self; end
        arg.each do |var|
          singleton_class.send :define_method, "#{var}",
                               & lambda{ the_class.send "__real_#{var}"}
          singleton_class.send :define_method, "#{var}=",
                               & lambda{|c| the_class.send "__real_#{var}=",c}
          singleton_class.send :define_method, "__real_#{var}",
                               & lambda{ instance_variable_get "@#{var}"}
          singleton_class.send :define_method, "__real_#{var}=",
                               & lambda{|c| instance_variable_set "@#{var}", c}
        end
      end
    end
  end
end

class A
  include JCV
  acts_like_java_class_variable :count

  @count = 0
  def initialize
    A.count +=1
  end
end

Wrap all methods of an object

Given an existing object "obj", wraps all of its methods. In this case, the wrapper prints a log of the method invocation, and the call stack of the call, but you can modify it to do anything...

virtual_class = class <<obj; self; end

virtual_class.class_eval {
  obj.methods.each { |method|
    the_alias = method.gsub(/([^?]+)(\??)/, "\\1_orig\\2").to_sym
    alias_method the_alias, method

    define_method(method) { |*args|
        args_str = args.map { |a| a.inspect }.join(", ")
        unless method == "to_s"
          puts "#{self.inspect_orig}.#{method}(#{args_str}) called from #{caller.inspect}"
        end
        self.send_orig(the_alias, *args)
    }
  }
}

Attribute Cache

Sometimes, you don't want to calculate the value of an attribute every time--like the sum of an array. However, you don't want it cached on the outside of the object since that makes for messy code. But you seem to cache things over and over again inside objects. Here's a module AttributeCache that does a little meta programming to make attribute caching a little bit easier. more details at http://webjazz.blogspot.com/2007/05/ruby-snippet-caching-object-attributes.html

module AttributeCache

  def metaclass; class << self; self; end; end
  
  def cache(attr_name, options)
    instance_variable_set "@#{attr_name}", options[:initial]
    instance_variable_set "@#{:sum}_outdated", true

    metaclass.instance_eval do 
      define_method("outdate_#{attr_name}") do
        puts "in outdated"
        instance_variable_set "@#{attr_name}_outdated", true
      end
    end
    
    metaclass.class_eval %Q{
      def cached_#{attr_name}(&block)
        if @#{attr_name}_outdated
          puts "in cached to update!"
          @#{attr_name}_outdated = false
          @#{attr_name} = block.call
        else
          puts "in cached to give cache!"
        end
        return @#{attr_name}
      end
    }

  end
  
end

class Collection
  include AttributeCache

  def initialize
    @array = []
    cache :sum, :initial => 0
  end

  def add(x)
    outdate_sum
    @array << x
  end

  def sum
    cached_sum { @array.inject {|t, e| t += e} }
  end
  
end

c = Collection.new
puts c.public_methods(false).inspect
c.add(2)
c.add(3)
puts c.sum
puts c.sum
c.add(4)
puts c.sum
puts c.sum

method triggers: instead, before, after

Redefine method calls, in manner similar to SQL triggers and PostgreSQL rules - i.e. execute code block instead, before or after call to original method. rules can be appended and removed.

class Object

  def self.__rules__
  # container for defined rules, each item is:
  #   [class, event_name, method_name, alias_for_original_method, caller, comment]
    @@rules ||= []
  end

  def self.__create_rule_instead( method, comment = '', &block) # creates and returns new rule
    b_id = "%04x" % block.object_id
    old_method_name = :"__previous_#{method}_#{b_id}"
    alias_method old_method_name, method
    define_method method, &block
    __rules__ << rule = [self, "INSTEAD", method, old_method_name,caller[0], comment ]
    rule
  end

  def self.__create_rule_before( method, comment = '', &block)
    args = instance_method(method).arity == 0 ? '' : '(*args)'
    b_id = "%04x" % block.object_id
    old_method_name = :"__previous_#{method}_#{b_id}"
    alias_method old_method_name, method
    define_method :"__before_#{method}_#{b_id}", &block
    class_eval <<-EOT
      def #{method}#{args}
        __before_#{method}_#{b_id}#{args}
        __previous_#{method}_#{b_id}#{args}
      end
    EOT
    __rules__ << rule = [self, "BEFORE", method, old_method_name, caller[0], comment]
    rule
  end

  def self.__create_rule_after( method, comment = '', &block)
    args = instance_method(method).arity == 0 ? '' : '(*args)'
    b_id = "%04x" % block.object_id
    old_method_name = :"__previous_#{method}_#{b_id}"
    alias_method old_method_name, method
    define_method :"__after_#{method}_#{b_id}", &block
    class_eval <<-EOT
      def #{method}#{args}
        res = __previous_#{method}_#{b_id}#{args}
        __after_#{method}_#{b_id}#{args}
        res
      end
    EOT
    __rules__ << rule = [self, "AFTER", method, old_method_name,caller[0], comment ]
    rule
  end

  def self.__remove_rule( rule ) # has some bugs when rules on subclasses are defined :(
    idx = __rules__.index(rule)
    if idx
      # look for next rule for the same method
      idx += 1
      while idx < __rules__.size
        break if  __rules__[idx][2] == rule[2] && __rules__[idx][0] == rule[0]
        idx+=1
      end
      if idx < __rules__.size
        next_rule = __rules__[idx]
        next_rule[0].send :remove_method, next_rule[3]
        next_rule[0].send :alias_method, next_rule[3], rule[3]
      else
        # that was last
        rule[0].send :remove_method, rule[2]
        rule[0].send :alias_method, rule[2], rule[3]
      end
      __rules__.delete(rule)
    end
  end

end


Example:
class Model
  def save
    puts "save"
  end
  def reload(flag)
    "reloaded"
  end
end

r1 = Model.__create_rule_instead(:reload) {|flag| flag ? "FRESH" : "STALE" }

obj = Model.new

puts "RELOAD:"+obj.reload(true)
puts "RELOAD:"+obj.reload(false)

Object.__remove_rule(r1)
puts "RELOAD:"+obj.reload(false)

Model.__create_rule_before(:save) { puts "BEFORE SAVE" }
r2 = Model.__create_rule_before(:save) { puts "YET BEFORE SAVE" }
Model.__create_rule_after(:save)  { puts "AFTER SAVE" }
r3 = Model.__create_rule_after(:save)  { puts "YET AFTER SAVE" }
obj.save
Object.__remove_rule(r2)
puts "----------"
obj.save
Object.__remove_rule(r3)
puts "----------"
obj.save

produces:
RELOAD:FRESH
RELOAD:STALE
RELOAD:reloaded
YET BEFORE SAVE
BEFORE SAVE
save
AFTER SAVE
YET AFTER SAVE
----------
BEFORE SAVE
save
AFTER SAVE
YET AFTER SAVE
----------
BEFORE SAVE
save
AFTER SAVE

PrettyConditions

// PrettyConditions allows you to easily print a report based on a condition for an ActiveRecord subclass

class ActiveRecord::Base
  def self.pretty_conditions(hash)
    meta_def hash[:name] do
      array = []
      puts "#{hash[:message]}:"
      puts "------------------"
      self.find(:all, :conditions => hash[:conditions]).each do |record|
        module_eval %[puts record.#{hash[:column_name]}]
        array << record
      end
      puts "------------------"
      puts "Total: #{array.size}"
      puts "------------------"
    end
  end
end


class Youth < ActiveRecord::Base
  pretty_conditions :name => 'unsponsored', :column_name => 'name', :message => "Unsponsored Youth", :conditions => "sponsor LIKE '%XXX%'"
  pretty_conditions :name => 'touchgroup_leaders', :column_name => 'name', :message => "Touchgroup Leaders", :conditions => ["touchgroup =?",'t']
  pretty_conditions :name => 'vegitarians', :column_name => 'name', :message => "Vegitarians", :conditions => ["vegitarian =?",'t'] 
end

Youth.unsponsored
Youth.touchgroup_leaders
Youth.vegitarians


Output:
saurasaurusrex:~/software cdcarter$ ruby actions.rb
Unsponsored Youth:
------------------
Hannah Gilbert
Jesse Lavercombe
Zach Adams
Danika Zabertini
------------------
Total: 4
------------------

Touchgroup Leaders:
------------------
Leland McKeeman
Marlee Leebrick-Stryker
Meggie Huges-Morrison
------------------
Total: 3
------------------

Vegitarians:
------------------
Marlee Leebrick-Stryker
Meggie Huges-Morrison
Amelia Nybakke
Ethan Edl
Maggie Heath
------------------
Total: 5
------------------

Overload for Ruby

An module let you overload functions

# Example:
#
# class Test1
#   include Overload
# 
#   def foo
#     puts "foo # Original one"
#   end
# 
#   overload :foo, :foo_str, String do |str, x|
#     puts "foo_str(String str, x)"
#   end
# 
#   overload :foo, Integer do |i|
#     puts "foo(Integer i) # no specific name"
#   end
# end
module Overload

  def self.included(klass)
    class << klass
      include Overload::Feature
    end
  end

  def self.spec_to_cond(spec)
    code = "args.length == #{spec[0]}"
    spec[1..-1].each_with_index { |s, i|
      code << " && #{s.inspect} === args[#{i}]"
    }
    code
  end

  module Feature

    private

    def overload(method_id, *spec, &block)
      if spec[0].instance_of? Symbol
        specific_method_id = spec.shift
        define_method(specific_method_id, block) if block
        block = instance_method(specific_method_id)
      elsif block
        define_method(method_id, block)
        block = instance_method(method_id)
        remove_method(method_id)
      else
        raise "You must give a block or a name of existing method!"
      end

      spec.unshift(block.arity)
      add_method_to_dispatch_table(method_id, spec, block)

      update_dispatcher(method_id)
    end

    def add_method_to_dispatch_table(method_id, key, block)
      if !dispatch_table[method_id]
        dispatch_table[method_id] = Hash.new
      end
      dispatch_table[method_id][key] = block
    end

    def update_dispatcher(method_id)
      remove_method(method_id) if method_defined?(method_id)
      class_eval dispatcher_code(method_id), "#{method_id}_dispatcher", 0
    end

    def dispatcher_code(method_id)
      method_body = <<-EOS
        def #{method_id.to_s}(*args, &blk)
          dt = DispatchTable[#{method_id.inspect}]
      EOS

      sorted_dispatch_table_of(method_id).each_with_index do |(spec, block), i|
        method_body << <<-EOS
          #{(i==0) ? "if" : "elsif"} #{Overload::spec_to_cond(spec)}
            dt[#{spec.inspect}].bind(self).call(*args, &blk)
        EOS
      end

      method_body << <<-EOS
          else
            raise "Couldn't find method for #{method_id}(\#{args.inspect[1..-2]})"
          end
        end
      EOS
    end

    def sorted_dispatch_table_of(method_id)
      dispatch_table[method_id].sort do |a,b|
        a[0].length <=> b[0].length
      end
    end

    def dispatch_table
      const_set("DispatchTable", Hash.new) if !const_defined?("DispatchTable")
      const_get("DispatchTable")
    end

  end

end

Get the name of the current method in Ruby

Found at http://nubyonrails.com/articles/2006/08/04/seattle-rbbq

def method_name
  if  /`(.*)'/.match(caller.first)
    return $1
  end
  nil
end

def blah
  puts method_name
end

blah  # => 'blah'

metaprogramming new classes in ruby

Take a class name in a string ('Test' or 'My::Module::Test') and create a new class for it.

old method. didn't like evaling the string though...
agent_name = "#{model_name.to_s.camelize}Agent"
agent_class = instance_eval %{Object::#{agent_name} = Class.new(Administration::Agent)}


new method
agent_name_pieces = agent_name.split('::')
agent_class_name = agent_name_pieces.pop
agent_module = agent_name_pieces.inject(Object) { |obj, name| obj.const_defined?(name) ? obj.const_get(name) : obj.const_set(name, Module.new)  }
agent_class = agent_module.const_set(agent_class_name, Class.new(Administration::Agent))


For acedemic purposes mainly, I don't know if the new method is any faster. I just wanted a version with as little string evaluation as possible. See my blog entry on this very topic.
« Newer Snippets
Older Snippets »
Showing 1-10 of 10 total  RSS