ruby . rails . spam . irb . javascript

Tracking record modifying users with merb and datamapper

# ------------------------------------------------------------
# In a model
# ------------------------------------------------------------
# class MyModel
#   include DataMapper::Resource
#   include Trackable::Model
#
#   # uses all default properties and associations
#   track_modifications # :user_model => "User"
                        # :as_creator => :creator
                        # :as_updater => :last_modifying_user
                        # :timestamps => true

#   # uses the specified model and associations
#   track_modifications :user_model => "Account"
#                       :as_creator => :creating_account
#                       :as_updater => :updating_account
#                       :timestamps => false
# end
#
# ------------------------------------------------------------
# In a controller
# ------------------------------------------------------------
# class MyController < Application
#   include Trackable::Controller
#
#   # defaults to using :current_user
#   track_modifications
#   # specify explicitly which "current_user_getter" to use
#   track_modifications :using => :current_account
#
#   def create # or any other action
#     # instead of calling 
#     # @model.save OR
#     # @model.update_attributes(attributes)
#     # use these instance methods
#     # save_resource(@model) OR
#     # save_resource!(@model) OR 
#     # update_resource(@model, attributes) OR
#     # update_resource!(@model, attributes)
#   end
# end
# ------------------------------------------------------------

module Trackable
  
  module Model
  
    def self.included(base)
      base.extend ClassMethods
    end
  
    module ClassMethods
    
      def track_modifications(options = {})
        user_model = options[:user_model] || :user
        creator = options[:as_creator] || :creator
        modifier = options[:as_updater] || "last_modifying_#{user_model}"
        track_timestamps = options[:timestamps] || true
      
        class_eval do
        
          if track_timestamps
            include DataMapper::Timestamp
          end
        
          property :created_by, Integer, :nullable => false
          property :updated_by, Integer, :nullable => false
        
          belongs_to creator, 
            :class_name => user_model.to_s.camel_case, 
            :foreign_key => "created_by"
          
          belongs_to modifier, 
            :class_name => user_model.to_s.camel_case, 
            :foreign_key => "updated_by"
          
        end 
      end
    
    end
    
  end
  
  module Controller
  
    def self.included(base)
      base.extend ClassMethods
      base.send :include, InstanceMethods
    end
  
    module ClassMethods
  
      def track_modifications(options = {})
        user_getter = options[:using].to_sym || :current_user
        class_eval do
          alias :__current_user__ user_getter
        end
      end
  
    end

    module InstanceMethods
  
      def save_resource(resource, bang = false)
        if resource.new_record?
          if resource.respond_to? :created_by
            resource.created_by = send(__current_user__).id
          end
        else
          if resource.respond_to? :updated_by
            resource.updated_by = send(__current_user__).id
          end
        end
        bang ? resource.save! : resource.save
      end
      
      def save_resource!(resource)
        save_resource(resource, true)
      end
  
      def update_resource(resource, attributes, bang = false)
        if resource.new_record?
          if resource.respond_to? :created_by
            resource.created_by = send(__current_user__).id
          end
        else
          if resource.respond_to? :updated_by
            resource.updated_by = send(__current_user__).id
          end
        end
        if bang && resource.respond_to(:update_attributes!)
          resource.update_attributes!(attributes)
        else
          resource.update_attributes(attributes)
        end
      end
      
      def update_resource!(resource, attributes)
        update_resource(resource, attributes, true)
      end
  
    end
  end
end

Methods on a Module’s Singleton class?

irb(main):001:0> module M
irb(main):002:1>   class << self
irb(main):003:2>     def foo
irb(main):004:3>       p "Hello from M.foo"
irb(main):005:3>     end
irb(main):006:2>   end
irb(main):007:1> end
=> nil
irb(main):008:0> M.foo
"Hello from M.foo"
=> nil
irb(main):009:0>

irb(main):001:0> m = Module.new
=> #<Module:0x19bcc>
irb(main):002:0> m.foo
NoMethodError: undefined method `foo' for #<Module:0x19bcc>
        from (irb):2
irb(main):003:0> class << m
irb(main):004:1>   def foo
irb(main):005:2>     "Hello from m.foo"
irb(main):006:2>   end
irb(main):007:1> end
=> nil
irb(main):008:0> m.foo
=> "Hello from m.foo"

restful_search plugin for rails

module RestfulSearch
  
  def self.included(base)
    base.extend ClassMethods
    base.send :include, InstanceMethods
  end
  
  class MissingSpecException < Exception; end
  class InvalidSpecException < Exception; end
  
  
  class RestfulSearchSpec
    
    attr_accessor :model
    
    attr_reader :columns
    attr_reader :joins
    
    attr_writer :search_param_name
    
    def columns=(args)
      @columns = args.is_a?(Array) ? args.map(&:to_sym) : [ args.to_sym ]
    end
    
    alias :column :columns
    
    def joins=(args)
      @joins = args.is_a?(Array) ? args : [ args ]
    end
    
    def model_class
      model.to_s.singularize.camelize.constantize
    end
    
    def search_param_name
      @search_param_name ? @search_param_name.to_sym : :match
    end
    
    def conditions(search_param_value)
      condition_string = columns.map { |c| "#{c} LIKE ?" }.join(' OR ')
      if search_param_value && !search_param_value.blank?
        [ condition_string, *bind_variables(search_param_value) ]
      else
        nil
      end
    end
    
    def valid?
      true
    end
    
    def bind_variables(search_param_value)
      returning [] do |v|
        columns.size.times { v << search_param_value }
      end
    end
    
  end
 
  
  module ClassMethods
    
    # USAGE EXAMPLE
    # -----------------------------------------------------------
    # class PostsController < ApplicationController
    #   restful_search do |s|
    #     s.model = :foo
    #     s.search_param_name = "search" # defaults to 'match'
    #     s.columns = [ 'foo.name', 'bar.state', 'baz.comment' ], 
    #     s.joins = [ :foo => { :bar => :baz } ], 
    #   end
    #   
    #   def index
    #     @posts = restful_search_results
    #   end
    # end
    # -----------------------------------------------------------
    
    def restful_search
      
      if block_given?
        spec = RestfulSearchSpec.new
        
        yield spec
        
        unless spec.valid?
          msg = "restful_search must be called with a block"
          raise InvalidSpecException, msg
        end
      else
        msg = "restful_search must be called with a block"
        raise MissingSpecException, msg
      end
      
      # make spec available
      define_method :restful_search_spec do
        spec
      end

      include InstanceMethods
        
    rescue Exception => e
      logger.error "You have an error in your restful_search configuration"
      raise e
    end
    
  end
  
  module InstanceMethods
    
    protected
    
    def restful_search_results
      restful_search_spec.model_class.paginate(:all,
        :joins => restful_search_spec.joins,
        :conditions => restful_search_spec.conditions(search_param_value),
        :page => params[:page]
      )
    end
    
    def search_param_value
      params[restful_search_spec.search_param_name]
    end
    
  end
  
end

integer range collection_select fields in rails

<%= f.collection_select :priority, (1..5), :to_i, :to_i %>

ruby statement modifier local variables

irb(main):001:0> puts foo if foo = "foo" 
(irb):1: warning: found = in conditional, should be ==
NameError: undefined local variable or method `foo' for main:Object
        from (irb):1

irb(main):002:0> puts foo if(foo = "foo")
(irb):2: warning: found = in conditional, should be ==
foo
=> nil
irb(main):003:0> 

helper to print out down migrations for my loaded fixtures (at least)

def generate_down_migration(fixture_array)
  puts "  def self.down"
  fixture_array.reverse.map do |t|
    puts "    drop_table :#{t.to_s}"
  end
  puts "  end"
end

sex spam

Need a mighty wang to bring out your greatest bang?
-- sorrapac@margulius.com

creating a tar.gz archive

tar -pczf name_of_your_archive.tar.gz /path/to/directory

truncating files to zero length in the bash shell

> some_file.ext

calling named_scope in a loop does not work with rails 2.1.0

#  # Raises NoMethodError: undefined method `processing'
#  %(pending processing complete error).each do |s|
#    named_scope s.to_sym, 
#      :conditions => ["state = ?", s],
#      :order => "priority, created_at"
#  end

named_scope :pending, 
  :conditions => "state = 'pending'",
  :order => "priority, created_at"

named_scope :processing, 
  :conditions => "state = 'processing'",
  :order => "priority, created_at"

named_scope :complete, 
  :conditions => "state = 'complete'",
  :order => "priority, created_at"

named_scope :errorneous, 
  :conditions => "state = 'error'",
  :order => "priority, created_at"

generating print optimized pdf documents from html and css using apache fop and css2xslfo

# Uses css2xslfo and apache fop to generate a print optimized pdf document
# from an html document and a css stylesheet. This works by converting the
# styled html to an xslfo document, which then gets translated to the final
# pdf document. For further details on apache fop and how wo write appropriate
# css stylesheets for css2xslfo, see http://xmlgraphics.apache.org/fop/ and
# http://www.re.be/css2xslfo/

module Css2fopRenderer
  
  class PdfGenerationException < Exception; end
  class HtmlGenerationException < Exception; end
  
  # overwrite for customization
  def after_html_generation; end
  
  def css2fop(template_file, html_file, pdf_file, base_url, erb_binding = nil)
    render_html(template_file, html_file, erb_binding)
    after_html_generation
    render_pdf(html_file, pdf_file, base_url)
  end
  
  def render_pdf(html_file, pdf_file, base_url)
    success = execute_css2fop(html_file, pdf_file, base_url)
    unless success && $?.exitstatus == 0 && (f = File.stat(pdf_file)) && f.size > 0
      raise PdfGenerationException
    end
  end

  def render_html(template_file, html_file, erb_binding = nil)
    template = ERB.new(File.read(template_file))
    File.open(html_file, "w") { |f| f.puts template.result(erb_binding) }
    unless (f = File.stat(html_file)) && f.size > 0
      raise HtmlGenerationException
    end
  end
  
  # a shell script is used so that sysadmins can customize the fop installation
  def execute_css2fop(html_file, pdf_file, base_url)
    system "#{ENV['css2fop-pdf']} #{html_file} #{pdf_file} #{base_url}"
  end
  
end

calling rake tasks with parameters from other rake tasks

namespace :backup do
  
  task :dump_dynamic_tables => :environment do

    ENV[TABLES] = 'users'
    Rake::Task['db:fixtures:export_for_tables'].invoke

  end

end

sex spam

Don’t let your giant sleep
-- tergange1974@mariopuzo.com

ruby heredocs as parameters

irb(main):001:0> class_name = "Foo"
=> "Foo"
irb(main):002:0> eval(<<EOS, binding, __FILE__, __LINE__)
irb(main):003:1"   class #{class_name}
irb(main):004:1"     def foo
irb(main):005:1"       :foo
irb(main):006:1"     end
irb(main):007:1"   end
irb(main):008:1" EOS
=> nil
irb(main):009:0> f = Foo.new
=> #<Foo:0x9114>
irb(main):010:0> f.foo
=> :foo

capistrano, thin, remote_cache, safe credentials and symlinked vendor/rails

set :application, "coolapp"
set :repository,  "http://snusnu.info/svn/#{coolapp}/trunk/coolapp"

set :deploy_via, :remote_cache
set :deploy_to, "/u/apps/coolapp"

role :app,  "snusnu.info", :primary => true

# rely on ssh-agent or the likes to serve our private key on authentication
ssh_options[:port] = 22
ssh_options[:username] = 'coolio'
ssh_options[:host_key] = "ssh-rsa"
ssh_options[:auth_methods] = %w(publickey)

# workaround for capistrano-2.3 bug
ssh_options[:keys] = %w(~/.ssh/id_dsa.snusnu.info ~/.ssh/id_rsa.snusnu.info)

# taken from Advanced Rails Recipes
desc "Watch multiple log files at the same time"
task :tail_log, :roles => :app do
  stream "tail -f #{shared_path}/log/production.log"
end

# call this if 'deploy_via :remote_cache' is set
# and you want to deploy a different tag or branch
# inspired from Jonathan Weiss' explanation of the issue
# http://blog.innerewut.de/2008/3/12/remote-cache-pitfalls
task :delete_remote_cache, :roles => :app do
  run "rm -rf #{shared_path}/cached-copy"
end


namespace :symlink do
  
  task :vendor_rails, :roles => :app do
    run "ln -s #{shared_path}/rails #{release_path}/vendor/rails"
  end
  
end

after "deploy:update_code", "symlink:vendor_rails"


namespace :thin do
  
  %w(start stop restart).each do |action|
  desc "#{action.to_s.capitalize} the application's thin server(s)"  
    task action.to_sym, :roles => :app do  
      run "thin #{action} -C #{deploy_to}/current/config/thin.yml"
    end
  end
  
end

namespace :deploy do
  
  # we don't need sudo for cleanup
  before("deploy:cleanup") do
    set :use_sudo, false
  end
  
  # taken from Advanced Rails Recipes
  desc "Runs after every successful deployment" 
  task :after_default do
    cleanup
  end
  
  # taken from Advanced Rails Recipes
  task :copy_database_configuration do
    production_db_config = "#{deploy_to}/shared/production.database.yml"
    run "cp #{production_db_config} #{release_path}/config/database.yml"
  end
  
  after "deploy:update_code", "deploy:copy_database_configuration"

  %w(start stop restart).each do |action| 
     desc "#{action.to_s.capitalize} the thin server(s)"  
     task action.to_sym do
       find_and_execute_task("thin:#{action}")
    end
  end

end

!nil is true

irb(main):001:0> nil
=> nil
irb(main):002:0> !nil
=> true
irb(main):003:0> !!nil
=> false
irb(main):004:0> nil && true
=> nil
irb(main):005:0> true && nil
=> nil
irb(main):006:0> !!nil && true
=> false
irb(main):007:0> true && !!nil
=> false

Hashes have unique keys :-)

# just in case this isn't more than obvious
irb(main):001:0> { :a => :foo, :a => :bar }
=> {:a=>:bar}

sex spam

Get your lassie immediately turned on by the only glance at your mighty weapon!
-- Lisa Sosa

WTFs/minute

ZeroDivisionError, Infinity and NaN in ruby

irb(main):005:0> 1/0
ZeroDivisionError: divided by 0
irb(main):027:0> 1.0 / 0
=> Infinity

irb(main):024:0> Infinity = 1.0 / 0
=> Infinity
irb(main):025:0> Infinity 
=> Infinity
irb(main):026:0> Infinity == 1.0 / 0
=> true

irb(main):006:0> 0/0
ZeroDivisionError: divided by 0
irb(main):001:0> 0.0 / 0
=> NaN
irb(main):028:0> 1.0 % 0
=> NaN

irb(main):020:0> NaN = 0.0 / 0
=> NaN
irb(main):021:0> NaN
=> NaN
irb(main):022:0> NaN == 0.0 / 0
=> false

irb(main):003:0> result = 0.0 / 0
=> NaN
irb(main):004:0> result.nan? # only available on Float
=> true

checking the return code of ruby system calls

irb(main):001:0> success = system 'pwd'
/Users/snusnu
=> true
irb(main):002:0> if success && $?.exitstatus == 0
irb(main):003:1>   :success
irb(main):004:1> else
irb(main):005:1*   :failure
irb(main):006:1> end
=> :success

ruby default parameter values may well be calls to instance methods

irb(main):038:0> class Foo
irb(main):039:1>   attr_accessor :bar
irb(main):040:1>   def foo(b = bar)
irb(main):041:2>     b
irb(main):042:2>   end
irb(main):043:1> end
=> nil
irb(main):044:0> f = Foo.new
=> #<Foo:0x57f30>
irb(main):019:0> f.bar = :baz 
=> :baz
irb(main):020:0> f.foo
=> :baz
irb(main):021:0> f.foo :bam
=> :bam

commas in ruby hash literals

# I didn't realize this was possible
# and I still don't know if I like it.
# However, there are reasons for this,
# like easy addition or moving of literal 
# entries without thinking about commas

irb(main):001:0> {
irb(main):002:1*   :foo => :bar,
irb(main):003:1*   :baz => :bam,
irb(main):004:1* }
=> {:foo=>:bar, :baz=>:bam}

ruby’s last character in a String

# returns the character code
# I don't know how to translate this back to a String
irb(main):002:0> "bar"[-1]
=> 114

# This works but reads a little awkward
irb(main):001:0> "bar"[-1..-1]
=> "r"

ruby conditional syntax

irb(main):001:0> if 1 == 1
irb(main):002:1>   :foo
irb(main):003:1> elsif 2 == 2
irb(main):004:1>   :bar
irb(main):005:1> elsif 3 == 3
irb(main):006:1>   :baz
irb(main):007:1> else
irb(main):008:1*   :bam
irb(main):009:1> end
=> :foo

irb(main):010:0> if    1 == 1 : :foo
irb(main):011:1> elsif 2 == 2 : :bar
irb(main):012:1> elsif 3 == 3 : :baz
irb(main):013:1> else
irb(main):014:1*   :bam
irb(main):015:1> end
=> :foo

irb(main):028:0> if    1 == 1; :foo
irb(main):029:1> elsif 2 == 2; :bar
irb(main):030:1> elsif 3 == 3; :baz
irb(main):031:1> else
irb(main):032:1*   :bam
irb(main):033:1> end
=> :foo

irb(main):022:0> if    1 == 1 then :foo
irb(main):023:1> elsif 2 == 2 then :bar
irb(main):024:1> elsif 3 == 3 then :baz
irb(main):025:1> else
irb(main):026:1*   :bam
irb(main):027:1> end
=> :foo

irb(main):016:0> if    1 == 1 :foo
irb(main):017:1> elsif 2 == 2 :bar
irb(main):018:1> elsif 3 == 3 :baz
irb(main):019:1> else  
irb(main):020:1*   :bam
irb(main):021:1> end
NameError: undefined local variable or method `foo' for main:Object
        from (irb):16
        from :0

common treetop pattern for parsing lists of things

rule expression_list
  expression tail:(SPACE expression)* {
    def eval(env = {})
      expressions.inject([]) do |exprs, expr|
        exprs << expr.eval(env)
      end
    end

    def expressions
      [ expression ] + tail.elements.map { |e| e.expression }
    end
  }
end

comment and keyword handling in treetop

# whitespace
rule white 
  [ \r\t\n]+
end

# mandatory space
rule SPACE
  (white / comment)+ 
end

# optional space
rule space
  SPACE?
end

# anything but not white
rule non_white_char
  !white .
end

# typical keyword rule
rule var_keyword
  'var' !non_white_char
end


rule comment
  comment_to_eol / multiline_comment
end

rule multiline_comment
  '/*' (!'*/' . )* '*/'
end

rule comment_to_eol
  # TODO find out why this doesn't work in specs
  #'#' (!"\n" .)+ "\n"

  '#' (!"\n" .)*
end

(ab)using ruby within treetop(?)

rule case_expression

  case_keyword SPACE case_exp:expression SPACE
    when_expression_list SPACE
    else_keyword SPACE else_exp:statement_list SPACE
  end_keyword 

  {
    def eval(env = {})
      case_val = case_exp.eval(env)
      else_val = else_exp.eval(env)
      Kernel.eval <<-CASE_STMT
        lambda do
          case #{case_val.is_a?(String) ? "'#{case_val}'" : case_val}
            #{ruby_when_expressions(env)}
            else #{else_val.is_a?(String) ? "'#{else_val}'" : else_val}
          end
        end [] # call this lambda immediately   
      CASE_STMT
    end

    def ruby_when_expressions(env = {})
      when_expression_list.eval(env).inject('') do |ruby, e|
        # possible string values have been wrapped in '' already
        ruby << "when #{e[:condition]} then #{e[:expression]} "
      end
    end
  }

end

sex spam

Tent your trousers with your new rod
-- olossien@BIGIRONTECH.COM

aligned numbers

# zerofill(7,3) => "007"
def zerofill(value, digits = 9)
  "%0#{digits}d" % value.to_i
end
« Back in time