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 64 total  RSS 

Perform a Rails find() and iterate over the resulting records in groups

module ActiveRecord
  class Base
    # This method lets you iterate over the results of a .find, in groups.
    # (Basically an interface to LIMIT.)
    # Anything you can pass as options to .find, you can pass here. 
    # Example 1:
    #   Order.each_by(100, :conditions => { :cc_processed_at => nil }) do |order|
    #     # do stuff with order
    #   end
    # Example 2:
    #   Person.each_by(50, :order => 'name') do |person, index|
    #     # do stuff with person and index
    #   end
    # Pass :update => true in the options to print a message before each group is
    # fetched from the db.
    #
    # Author: Elliot Winkler <elliot.winkler@gmail.com>
    # Source: http://snippets.dzone.com/posts/show/5461
    def self.each_by(group_size, options={}, &blk)
      update = options.delete(:update) || false
      num_records = count(options.except(:from))
      return 0 if num_records == 0
      #raise "Number of records: #{num_records}"
      also_pass_offset = (blk.arity == 2)
      0.step(num_records, group_size) do |offset|
        find_options = { :offset => offset, :limit => group_size }.merge(options)
        if update
          if num_records == 1
            puts ">> Reading the only record."
          else
            start_offset = offset + 1
            end_offset   = offset + group_size
            end_offset   = num_records if num_records < end_offset
            puts ">> Reading records #{start_offset}-#{end_offset}."
          end
        end 
        find(:all, find_options).each do |record|
          also_pass_offset ? blk.call(record, offset) : blk.call(record)
        end
      end
      num_records
    end
  end
end

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

ActiveRecord Class Example

// Trying out the system, here is just a simple ActiveRecord class

class Task < ActiveRecord::Base 
  belongs_to :user
  belongs_to :location
  belongs_to :project
end .

Finding recent data in database

This shows how to find recent data from last hour, day, week, month, whatever

class Post < ActiveRecord::Base
  def self.find_latest(time = nil)
    r = %w( hour day week month year )
    if r.include?(time)
      self.find :all, :conditions => ['created_at > ?', 1.send(time).ago]
    else
      self.find :all
    end
  end
end

Post.find_latest('day')
Post.find_latest('week')
Post.find_latest('year')

Custom Rails Callback

// This shows you how to extend active record to allow a custom callback.

#ever needed to add your own callback into the active record callback chain?
#here's how...
module ActiveRecord
  
  #to keep things clean we'll define our own module
  module SweetCustomCallbacks
    
    #this is the ruby method that is called when you call include on a module
    #the method passed, base in our case, is the class your including yourself in
    def self.included(base)
      
      #this clas_eval method takes a block that will be added to base, which is ActiveRecord::Base
      #basically this is like writing code directly into the class ActiveRecord::Base
      base.class_eval do
        
        #alias_method_chain is very cool
        #it allows us override a method without removing the original and without the original knowing its been changed
        #here's how it works:
        # 1st argument is the name of the method you're overriding
        # 2nd argument is feature you want to add
        # so if create_or_update is called it will actually call create_or_update_with_sweet_custom_callbacks
        # the original is still avaliable at create_or_update_without_custom_callbacks
        
        alias_method_chain :create_or_update, :sweet_custom_callbacks
        #the method create_or_update is an internals method to ActiveRecord that is called whenever you call .save or .update
        
        #this is the class method that allows the cool syntax:
        # class Article < ActiveRecord::Base
        # before_before_save :add_authors_name
        #private
        #def add_authors_name
        #.....
        def self.before_before_save(*callbacks, &block)
          callbacks << block if block_given? #add the block to the array of callback functions if a block was passed
          write_inheritable_array(:before_before_save,callbacks) 
        end
      end
    end
    
    #the instance method, although not recomended is avaliable for overriding
    #also if you wanted to have a callback that always did something say add the users's id to the object 
    #you could always code that in here and remove the class above
    def before_before_save() end

    #ok this is the method that is now called instead of create_or_update
    #it calls a method callback on 
    def create_or_update_with_sweet_custom_callbacks
      return false if callback(:before_before_save) == false  #this method does all the real work and calls the callback function in callbacks.rb that gets the methods and runs them 
      create_or_update_without_sweet_custom_callbacks
    end
    
   end
 
   class Base    
     include SweetCustomCallbacks #include our module in ActiveRecord::Base
   end
end

Rails MySQL/SQLite convenience methods

Usage:

  Person.find :all, :conditions => ["#{sql_year 'birthday'} >= ?", year]


SQLITE = true # or false

def sql_concat(*args)
  SQLITE ? args.join(' || ') : "CONCAT(#{args.join(', ')})"
end

def sql_lcase(expr)
  SQLITE ? "LOWER(#{expr})" : "LCASE(#{expr})"
end

def sql_year(expr)
  SQLITE ? "CAST(STRFTIME('%y', #{expr}) as 'INTEGER')" : "YEAR(#{expr})"
end

def sql_month(expr)
  SQLITE ? "CAST(STRFTIME('%m', #{expr}) as 'INTEGER')" : "MONTH(#{expr})"
end

def sql_day(expr)
  SQLITE ? "CAST(STRFTIME('%d', #{expr}) as 'INTEGER')" : "DAY(#{expr})"
end

def sql_now
  SQLITE ? "CURRENT_TIMESTAMP" : "NOW()"
end

Rails Array#add_condition Method

Lets you add a condition to a set of ActiveRecord conditions easily like this:

  conditions = ['active = ? and type = ?', true, 2]
  conditions.add_condition ['person_id = ?', 345]


class Array
  def add_condition(condition, conjunction='and')
    if condition.is_a? Array
      if self.empty?
        (self << condition).flatten!
      else
        self[0] += " #{conjunction} " + condition.shift
        (self << condition).flatten!
      end
    elsif condition.is_a? String
      self[0] += " #{conjunction} " + condition
    else
      raise "don't know how to handle this condition type"
    end
    self
  end
end

Marshalize (Cache) ActiveRecord Query Results

A quick way to cache results in a file and read from the file on subsequent requests instead of the database. Makes the initial query a bit slower, but later queries *much* faster.

class MyCachedModel < ActiveRecord::Base
  class << self
    alias_method :rails_original_find_by_sql, :find_by_sql
    def find_by_sql(sql)
      cache_filename = Base64.encode64(sql)
      if File.exists? cache_filename
        Marshal.load(File.open(cache_filename))
      else
        Marshal.dump(records = rails_original_find_by_sql(sql), File.open(cache_filename, 'w'))
        return records
      end
    end
  end
end

Rails validation for Phone

  validates_length_of :phone, :is => 10, :message => 'must be 10 digits, excluding special characters such as spaces and dashes. No extension or country code allowed.', :if => Proc.new{|o| !o.phone.blank?}
  validates_length_of :fax, :is => 10, :message => 'must be 10 digits, excluding special characters such as spaces and dashes. No extension or country code allowed.', :if => Proc.new{|o| !o.fax.blank?}

Crack open Firefox's new Places sqlite database with ActiveRecord

require "rubygems"
require "active_record"
require "active_support"

ActiveRecord::Base.establish_connection(
	:adapter => "sqlite3",
	:database => "places.sqlite"
)

class MozHistoryvisit < ActiveRecord::Base
	belongs_to :place, :class_name => "MozPlaces", :foreign_key => "place_id"
end

class MozPlaces < ActiveRecord::Base
end

p Time.now.yesterday
from = Time.now.yesterday.to_i * 1000000

MozHistoryvisit.find(:all, :conditions => ["visit_date > ?", from]).each do |h|
	place = h.place
	puts place.title
	puts place.url
	puts place.visit_count
	puts
end

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