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

About this user

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

annotate models with associations

slightly modified http://repo.pragprog.com/svn/Public/plugins/annotate_models/lib/annotate_models.rb to include short declarations for model associations.

require "config/environment"

MODEL_DIR   = File.join(RAILS_ROOT, "app/models")
FIXTURE_DIR = File.join(RAILS_ROOT, "test/fixtures")

module AnnotateModels

  PREFIX = "== Schema Information"
  
  # Simple quoting for the default column value
  def self.quote(value)
    case value
      when NilClass                 then "NULL"
      when TrueClass                then "TRUE"
      when FalseClass               then "FALSE"
      when Float, Fixnum, Bignum    then value.to_s
      # BigDecimals need to be output in a non-normalized form and quoted.
      when BigDecimal               then value.to_s('F')
      else
        value.inspect
    end
  end

  # Use the column information in an ActiveRecord class
  # to create a comment block containing a line for
  # each column. The line contains the column name,
  # the type (and length), and any optional attributes
  def self.get_schema_info(klass, header)
    info = "# == Model #{klass}\n# #{header}\n#\n"
    info << "# Table name: #{klass.table_name}\n#\n"
    
    max_size = klass.column_names.collect{|name| name.size}.max + 1
    klass.columns.each do |col|
      attrs = []
      attrs << "default(#{quote(col.default)})" if col.default
      attrs << "not null" unless col.null
      attrs << "primary key" if col.name == klass.primary_key

      col_type = col.type.to_s
      if col_type == "decimal"
        col_type << "(#{col.precision}, #{col.scale})"
      else
        col_type << "(#{col.limit})" if col.limit
      end 
      info << sprintf("#  %-#{max_size}.#{max_size}s:%-15.15s %s\n", col.name, col_type, attrs.join(", "))
    end
    assoc_list = klass.reflect_on_all_associations.sort {|x,y| d = x.class_name.to_s <=> y.class_name.to_s; c = d == 0 ? x.macro.to_s <=> y.macro.to_s : d; c == 0 ? x.name.to_s <=> y.name.to_s : c }
    unless assoc_list.empty?
      info << "#\n# == Associations\n" 
      assoc_list.each do |assoc|
        ao = assoc.options.dup
        ao.delete(:class_name)
        line = "# * " +sprintf("<tt>%-25s :%-25s (%s) %s</tt>", assoc.macro, assoc.name, assoc.class_name, ao.empty? ? '' : ao.inspect)
        info << line << "\n"
      end 
    end
    info << "#\n\n"
  end

  # Add a schema block to a file. If the file already contains
  # a schema info block (a comment starting
  # with "Schema as of ..."), remove it first.

  def self.annotate_one_file(file_name, info_block)
    if File.exist?(file_name)
      content = File.read(file_name)

      # Remove old schema info
      content.sub!(/^# #{PREFIX}.*?\n(#.*\n)*\n/, '')

      # Write it back
      File.open(file_name, "w") { |f| f.puts info_block + content }
    end
  end
  
  # Given the name of an ActiveRecord class, create a schema
  # info block (basically a comment containing information
  # on the columns and their types) and put it at the front
  # of the model and fixture source files.

  def self.annotate(klass, header)
    info = get_schema_info(klass, header)
    
    model_file_name = File.join(MODEL_DIR, klass.name.underscore + ".rb")
    annotate_one_file(model_file_name, info)

    fixture_file_name = File.join(FIXTURE_DIR, klass.table_name + ".yml")
    annotate_one_file(fixture_file_name, info)
  end

  # Return a list of the model files to annotate. If we have 
  # command line arguments, they're assumed to be either
  # the underscore or CamelCase versions of model names.
  # Otherwise we take all the model files in the 
  # app/models directory.
  def self.get_model_names
    models = ARGV.dup
    models.shift
    
    if models.empty?
      Dir.chdir(MODEL_DIR) do 
        models = Dir["**/*.rb"]
      end
    end
    models
  end

  # We're passed a name of things that might be 
  # ActiveRecord models. If we can find the class, and
  # if its a subclass of ActiveRecord::Base,
  # then pas it to the associated block

  def self.do_annotations
    header = PREFIX.dup
    version = ActiveRecord::Migrator.current_version rescue 0
    if version > 0
      header << "\n# Schema version: #{version}"
    end
    
    self.get_model_names.each do |m|
      class_name = m.sub(/\.rb$/,'').camelize
      begin
        klass = class_name.split('::').inject(Object){ |klass,part| klass.const_get(part) }
        if klass < ActiveRecord::Base && !klass.abstract_class?
          puts "Annotating #{class_name}"
          self.annotate(klass, header)
        else
          puts "Skipping #{class_name}"
        end
      rescue Exception => e
        puts "Unable to annotate #{class_name}: #{e.message}"
      end
      
    end
  end
end

Creating PayPal recurring payments profile with activemerchant



# simple extension to ActiveMerchant for basic support of recurring payments with Express Checkout API
# 
# NOTE: set pem_file when loading
module ActiveMerchant #:nodoc:
  module Billing #:nodoc:
    class PaypalExpressRecurringGateway < Gateway
      include PaypalCommonAPI

      LIVE_REDIRECT_URL = 'https://www.paypal.com/cgibin/webscr?cmd=_customer-billing-agreement&token='
      TEST_REDIRECT_URL = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_customer-billing-agreement&token='

      def redirect_url
        test? ? TEST_REDIRECT_URL : LIVE_REDIRECT_URL
      end

      def redirect_url_for(token)
        "#{redirect_url}#{token}"
      end

      def setup_agreement(description, return_url, cancel_url)
        commit 'SetCustomerBillingAgreement', build_setup_request(description, return_url, cancel_url)
      end

      def create_profile(token, description, period, cycles, amount)
        commit 'CreateRecurringPaymentsProfile', build_create_profile_request(token, description, period, cycles, amount)
      end

      def get_profile_details(profile_id)
        commit 'GetRecurringPaymentsProfileDetails', build_get_profile_details_request(profile_id)
      end

    private
      def build_setup_request(description, return_url, cancel_url)
        xml = Builder::XmlMarkup.new :indent => 2
        xml.tag! 'SetCustomerBillingAgreementReq', 'xmlns' => PAYPAL_NAMESPACE do
          xml.tag! 'SetCustomerBillingAgreementRequest', 'xmlns:n2' => EBAY_NAMESPACE do
            xml.tag! 'n2:Version', 50
            xml.tag! 'n2:SetCustomerBillingAgreementRequestDetails' do
              xml.tag! 'n2:BillingAgreementDetails' do
                xml.tag! 'n2:BillingType', 'RecurringPayments'
                xml.tag! 'n2:BillingAgreementDescription', description
              end
              xml.tag! 'n2:ReturnURL', return_url
              xml.tag! 'n2:CancelURL', cancel_url
            end
          end
        end
        xml.target!
      end

      def build_create_profile_request(token, description, period, cycles, money)
        xml = Builder::XmlMarkup.new :indent => 2
        xml.tag! 'CreateRecurringPaymentsProfileReq', 'xmlns' => PAYPAL_NAMESPACE do
          xml.tag! 'CreateRecurringPaymentsProfileRequest', 'xmlns:n2' => EBAY_NAMESPACE do
            xml.tag! 'n2:Version', 50
            xml.tag! 'n2:CreateRecurringPaymentsProfileRequestDetails' do
              xml.tag! 'Token', token
              xml.tag! 'n2:RecurringPaymentsProfileDetails' do
                xml.tag! 'n2:BillingStartDate', Time.now.utc.iso8601
              end
              xml.tag! 'n2:ScheduleDetails' do
                xml.tag! 'n2:Description', description
                xml.tag! 'n2:PaymentPeriod' do
                  xml.tag! 'n2:BillingPeriod', 'Day'
                  xml.tag! 'n2:BillingFrequency', period
                  xml.tag! 'n2:TotalBillingCycles', cycles
                  xml.tag! 'n2:Amount', amount(money), 'currencyID' => currency(money)
                end
              end
            end
          end
        end

        xml.target!
      end

      def build_get_profile_details_request(profile_id)
        xml = Builder::XmlMarkup.new :indent => 2
        xml.tag! 'GetRecurringPaymentsProfileDetailsReq', 'xmlns' => PAYPAL_NAMESPACE do
          xml.tag! 'GetRecurringPaymentsProfileDetailsRequest', 'xmlns:n2' => EBAY_NAMESPACE do
            xml.tag! 'n2:Version', 50
            xml.tag! 'ProfileID', profile_id
          end
        end

        xml.target!
      end

      def build_response(success, message, response, options = {})
        PaypalExpressResponse.new(success, message, response, options)
      end

    end
  end
end

PostgreSQL: generate DDL to alter views with dependencies

Determine dependencies on given table/view or their columns, types (base/domain/composite), functions, rules and show DROP/ALTER/CREATE series to update their definitions.

#!/usr/bin/ruby -W0
#
# This script tries to determine all dependencies on given table/view or their columns, 
# types (base/domain/composite), functions, rules.
#
# Usage:
#       ruby show_obj_deps conn_string object_id alter_stmt 
# e.g:
#       ruby show_obj_deps dbname=db1 "VIEW public.base_stats" "DROP VIEW public.base_stats; CREATE VIEW public.base_stats AS SELECT ..."
#
# Result will be series of DDL DROP statements for dependent objects, then alter_stmt, and
# then series of DDL CREATE statements for dropped objects.
#
# Format of object_id:
#   TABLE schema_name.table_name
#   VIEW schema_name.view_name
#   TABLE schema_name.table_name COLUMN column_name
#   VIEW schema_name.view_name COLUMN column_name
#   FUNCTION schema_name.func_name(type_1, type2, ...)
#   RULE rule_name ON schema_name.obj_name
#
# This script is somewhat rewritten version of http://snippets.dzone.com/posts/show/2105
#
# Developed using PostgreSQL v8.0.3, v8.1 with ruby-postgres libpq binding
# 

require 'postgres'
require 'tsort'
require 'pp'
PGconn.translate_results = true

$PG_CLASSES = {}
class PgDependencyGraph            
class DBObject
  attr_accessor :row, :o_type, :nsp
  def initialize(conn, class_id, obj_id, sub_id)
    @o_type   = $PG_CLASSES[class_id]
    @row   = conn.query(sql_for(@o_type, obj_id, sub_id)).first
    @nsp = row['nsp']
    if @o_type == 'pg_proc'
      arg_types = row.last.split(" ")
      unless arg_types.empty?
        arg_type_names = arg_types.map {|oid| "format_type(#{oid}, -1)"}.join(", ")
        row[-1] = "("+conn.query("SELECT #{arg_type_names}").first.join(", ") +")"
      else 
	     row[-1] = "()"
      end
    end
  end
  def sql_for(pg_class, obj_id, sub_id)
  base_sql = case pg_class 
    when 'pg_type' 
      "select       (case 
      	when typtype = 'b' then 'BASE '
	when typtype = 'c' then 'COMPOSITE '
	when typtype = 'd' then 'DOMAIN '
	when typtype = 'p' then 'PSEUDO '
      end) || coalesce( 
      (select (CASE WHEN relkind = 'r' THEN 'TABLE'
            WHEN relkind = 'v' THEN 'VIEW'
            WHEN relkind = 'i' THEN 'INDEX'
            WHEN relkind = 'S' THEN 'SEQUENCE'
            WHEN relkind = 's' THEN 'SPECIAL'
            WHEN relkind = 't' THEN 'TOAST'
	    WHEN relkind = 'c' THEN ' '
        END) from pg_class c where c.oid = typrelid), ' '), 
(select nspname from pg_namespace n where n.oid = typnamespace) as nsp,       typname as obj_name,
      #{sub_id} from pg_type  "
    when 'pg_proc' 
      "select (select nspname from pg_namespace n where n.oid = pronamespace) as nsp, proname as obj_name, proargtypes from pg_proc "
    when 'pg_class'
      "select 
  (CASE WHEN relkind = 'r' THEN 'TABLE'
            WHEN relkind = 'v' THEN 'VIEW'
            WHEN relkind = 'i' THEN 'INDEX'
            WHEN relkind = 'S' THEN 'SEQUENCE'
            WHEN relkind = 's' THEN 'SPECIAL'
            WHEN relkind = 't' THEN 'TOAST'
        END) , (select nspname from pg_namespace n where n.oid = relnamespace) as nsp, 
       relname as obj_name,  
      (select attname from pg_attribute where attrelid = #{obj_id} and attnum = #{sub_id}) from pg_class"
    when 'pg_rewrite'
      "select (select nspname from pg_namespace n where n.oid = (select relnamespace from pg_class c where c.oid = ev_class) ) as nsp, rulename, (select relname from pg_class c where c.oid = ev_class)  from pg_rewrite"
    else
        puts "IGNORE: #{pg_class}, #{obj_id}, #{sub_id}"
        nil
  end
  base_sql = base_sql ? base_sql << " WHERE oid = #{obj_id} ": nil
  
end
 
  def to_s
    case @o_type
    	when 'pg_proc' : "FUNCTION #{@row[0]}.#{@row[1]}#{@row[2]}"
      when 'pg_type': "TYPE: #{@row[0]} #{@row[1]}.#{@row[2]}"
      when 'pg_class' : "#{@row[0]} #{@row[1]}.#{@row[2]}" + (row[3] ? " COLUMN #{@row[3]}" : "")
      when 'pg_rewrite' :  @row[1] == '_RETURN' ? "VIEW #{@row[0]}.#{@row[2]}" : "RULE #{@row[1]} ON #{@row[0]}.#{@row[2]}"
    end 
  end
end
  attr_accessor :depend_graph
    ACCEPTED = ['pg_type', 'pg_proc', 'pg_class', 'pg_rewrite']
  def initialize(conn)
    conn.query("select distinct classid, relname from pg_class c join pg_depend d on (c.oid = d.classid)").each do |row|
      $PG_CLASSES[row['classid']] = row['relname']
    end
    conn.query("select distinct refclassid, relname from pg_class c join pg_depend d on (c.oid = d.refclassid)").each do |row|
      $PG_CLASSES[row['refclassid']] = row['relname']
    end
    dep_graphs = {}
    conn.query("SELECT * FROM pg_catalog.pg_depend   where  true or ( deptype<> 'i' and deptype <> 'p')").each do |row|
    if ACCEPTED.include?($PG_CLASSES[row['classid']]) && ACCEPTED.include?($PG_CLASSES[row['refclassid']])
      this_obj = DBObject.new(conn, row['classid'], row['objid'], row['objsubid'])
      that_obj = DBObject.new(conn, row['refclassid'], row['refobjid'], row['refobjsubid'])
      #dep_string = this_obj.nsp !~ /^pg_toast$/ ? "\"#{this_obj.to_s}\" -> \"#{that_obj.to_s}\"; \n" : nil
      dep_string = this_obj.nsp !~ /^(information_schema|pg_catalog|pg_toast)$/  ? this_obj.to_s : nil
    
      if dep_string && this_obj.to_s !~ /^INDEX /
        dep_graphs[that_obj.to_s] ||=[]
        dep_graphs[that_obj.to_s] << dep_string
        if that_obj.to_s =~ /^(VIEW|TABLE) (.+?) COLUMN /m
          ds2 = that_obj.to_s.sub(/ COLUMN.*/m, '')
          dep_graphs[ds2] ||=[]
          dep_graphs[ds2] << that_obj.to_s unless that_obj.to_s == ds2
        elsif that_obj.to_s =~ /^TYPE: COMPOSITE (TABLE|VIEW) /
          ds2 = that_obj.to_s.sub(/^TYPE: COMPOSITE (TABLE|VIEW) /, '\1 ')
          dep_graphs[ds2] ||=[]
          dep_graphs[ds2] << that_obj.to_s unless that_obj.to_s == ds2
        end
      end
    end
  end
  dep_graphs.each do |that, values|
    values.uniq!
    values.reject! {|item| item == that}
  end
  @depend_graph = dep_graphs
 end
 def list_dependencies(obj)
    dep_list = []
    if @depend_graph[obj]
      @depend_graph[obj].each do |v|
        k = list_dependencies(v)
        k.empty? ? dep_list << v : dep_list << [v, k]
      end
    end
    dep_list
 end
end

class DG
 include TSort
 def initialize(dep_graph, node_list)
   @nodes = node_list
   @dg = dep_graph
 end
 def tsort_each_node(&block)
   @nodes.each {|x| yield x}
 end
 def tsort_each_child(node, &block)
   (@dg[node]||[]).each(&block)
 end
end    

class Function
  attr_reader :typed_head
  def initialize(conn, tuple)
    @name = tuple['namespace'] + "." + tuple['function_name']
    @language = tuple['language_name']
    @src = tuple['source_code']
    @returns_set = tuple['returns_set']
    @return_type = format_type(conn, tuple['return_type'])
    @tipes = tuple['function_args'].split(" ")
    if tuple['function_arg_names'] && tuple['function_arg_names'] =~ /^\{(.*)\}$/
      @arnames = $1.split(',')
    elsif tuple['function_arg_names'].is_a? Array
      @arnames = tuple['function_arg_names']
    else
      @arnames = [""] * @tipes.length
    end
    alist = []
    atypelist = [] 
    @tipes.each_with_index do |typ,idx|
      ft = format_type(conn, typ)
      alist << (@arnames[idx] +" " + ft)
      atypelist << ft
    end
    @arglist = alist.join(" , ")
    @strict = tuple['proisstrict'] ? ' STRICT' : ''
    @secdef = tuple['prosecdef'] ? ' SECURITY DEFINER' : ''
    @volatile = case tuple['provolatile']
      when 'i' then ' IMMUTABLE'
      when 's' then ' STABLE'
      else ''
    end
    @typed_head = @name+"("+atypelist.join(", ")+")"
  end
  def signature
    "#{@name}(#{@arglist})"
  end
  def definition
    <<-EOT
CREATE OR REPLACE FUNCTION #{@name} (#{@arglist}) RETURNS #{@returns_set ?  'SETOF' : ''} #{@return_type} AS $_$#{@src}$_$ LANGUAGE '#{@language}' #{@volatile}#{@strict}#{@secdef};
EOT
  end
  def == (other)
    definition == other.definition
  end
  def format_type(conn, oid)
    t_query = <<-EOT
    SELECT pg_catalog.format_type(pg_type.oid, typtypmod) AS type_name
     FROM pg_catalog.pg_type
     JOIN pg_catalog.pg_namespace ON (pg_namespace.oid = typnamespace)
     WHERE pg_type.oid = 
    EOT
    return conn.query(t_query + oid.to_s)[0][0]
  end
  def self.find(conn, schema, name, args)
    func_query = <<-EOT
     SELECT proname AS function_name
     , nspname AS namespace
     , lanname AS language_name
     , pg_catalog.obj_description(pg_proc.oid, 'pg_proc') AS comment
     , proargtypes AS function_args
     , proargnames AS function_arg_names
     , prosrc AS source_code
     , proretset AS returns_set
     , prorettype AS return_type,
     provolatile, proisstrict, prosecdef
     FROM pg_catalog.pg_proc
     JOIN pg_catalog.pg_language ON (pg_language.oid = prolang)
     JOIN pg_catalog.pg_namespace ON (pronamespace = pg_namespace.oid)
     JOIN pg_catalog.pg_type ON (prorettype = pg_type.oid)
     WHERE pg_namespace.nspname !~ 'pg_catalog|information_schema|pg_temp_'
     AND nspname = $1
     AND proname = $2
     AND oidvectortypes(proargtypes) = $3
    EOT

    Function.new(conn, conn.query(func_query, schema, name, args).first)
  end
end

conn = PGconn.new(ARGV[0])
graph = PgDependencyGraph.new(conn)
#pp graph.list_dependencies(ARGV[1])
dep_list = []
ARGV[1].split(/\|/).each do |obj_id|
  dep_list += graph.list_dependencies(obj_id).flatten.uniq
end
dep_list.uniq!

top_sorted = DG.new(graph.depend_graph, dep_list).tsort
top_sorted.each do |line|
  case line
        when /^TYPE:/, /^VIEW (\S+) COLUMN/; 
        when /^VIEW (.+)/ then puts "DROP VIEW #$1;"
        when /^FUNCTION (.+)/ then puts "DROP FUNCTION #$1;"
        when /^RULE / then puts "DROP "+line+";"
        else 
        puts "-- SKIP #{line}"
  end
end

puts "", "--- ", "--- ALTER: ", "---"
puts ARGV[2]
puts "---", "---", ""
def view_def(conn, name)
  conn.select_value("SELECT pg_catalog.pg_get_viewdef('#{name}'::regclass, true)")
end
def rule_def(conn, tablename, rule_name)
 conn.select_value("select definition from pg_rules  where schemaname || '.' ||  tablename = $1  and rulename = $2", tablename, rule_name)
end

def func_def(conn, *args)
 Function.find(conn, *args).definition
end


top_sorted.reverse.each do |line|
  case line
        when /^TYPE:/, /^VIEW (\S+) COLUMN/ then puts "-- SKIP #{line}"
        when /^VIEW (.+)/ then puts "CREATE VIEW #$1 AS "+view_def(conn, $1)
        when /^FUNCTION (\w+)\.(\w+)\((.*)\)/ then puts func_def(conn, $1, $2, $3)
        when /^RULE (.+) ON (.+)/ then puts "CREATE "+rule_def(conn, $2, $1)
        else
          puts "-- SKIP #{line}"
  end
  puts 
end



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

vim folding for RSpec editing


" fold text between context and specify
function! ShowRSpecContext()
  let spec_idx = search('specify\s\+".\+"', 'Wbn', '^')
  let ctx_idx  = search('context\s\+".\+"', 'Wbn', '^')
  if spec_idx && ctx_idx
    exec (ctx_idx+1).','.(spec_idx-1).'fold'
  endif
endfunction

" fold text between all contexts and specify lines
function! ShowRSpecAnnotation()
 call cursor('$', 0)
 try
   foldo!
 catch
 endtry
 let cur_line = line('$')
 while cur_line > 0
   let prev_spec = search('\(context\|specify\)\s\+".\+"', 'Wb', '^')
   if ! prev_spec
     break
   endif
   exec (prev_spec).','.cur_line.'fold'
   let cur_line=prev_spec-1
 endwhile
endfunction
command! Sx :call ShowRSpecContext()
command! Sa :call ShowRSpecAnnotation()

Rails task to find code typos in rhtml templates


namespace :typos do 
  task :rhtml do
    require 'erb'
    require 'active_support'
    require 'action_view'

    module TempTemplates; end
    Dir["./app/views/**/*.rhtml"].each do |fname|
      local_source = ERB.new(IO.read(fname), nil, '-').src
      template_source = "def _tmpl\n#{local_source}\nend"
      begin
        TempTemplates.module_eval(template_source, fname, 0 )
      rescue Object => e
        t = ActionView::TemplateError.new("./app/views/", fname, {}, template_source, e)
        puts "TemplateError (#{t.message}) on line ##{t.line_number} of #{t.file_name}:\n"+t.source_extract + "\n------\n"
      end
    end
  end
end

pg_show_deps: outputs dependencies graph for tables, types, proc, views in PostgreSQL database schema.

pg_show_deps: outputs dependencies graph for tables, types, proc, views in PostgreSQL database schema.
output is produced in dot format and can be processed using graphviz (http://www.graphviz.org/) toolset
# to get a picture of dependencies.

#!/bin/env ruby
#
# pg_show_deps: outputs dependencies graph for objects (tables, types, proc, views) in PostgreSQL database schema.
# output is produced in dot format and can be processed using graphviz (http://www.graphviz.org/) toolset
# to get a picture of dependencies.
#
# Sample usage:
#    pg_show_deps 'dbname=template1 port=5432 user=looser' >template1.dot
#    ccomps -otemplate_split.dot -x template1.dot
#    dot -Tps -o template_2.ps template_split_2.dot
#
# Developed using PostgreSQL 8.1, 
# requires ruby-postgres driver (http://rubyforge.org/projects/ruby-postgres)

require 'postgres'
$PG_CLASSES = {}
            
class DBObject
  attr_accessor :row, :o_type, :nsp
  def initialize(conn, class_id, obj_id, sub_id)
    @o_type   = $PG_CLASSES[class_id]
    @row   = conn.query(sql_for(@o_type, obj_id, sub_id)).first
    @nsp = row['nsp']
    if @o_type == 'pg_proc'
      arg_types = row.last.split(" ")
      unless arg_types.empty?
        arg_type_names = arg_types.map {|oid|