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

Florian Aßmann

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

RSpec Association Matchers

Ok, what I essentially wanted to accomplish was something like:

describe Product, 'with Group' do

  it 'should belong to group' do
    Product.should belong_to(:product_group)
  end

end

describe ProductGroup, 'with Product' do

  it 'should have many products depending (on group)' do
    ProductGroup.should have_many(:products).depending
  end

end


Here is the code:

module AssociationMatchers

  class AssociationReflection

    def initialize(type, name)
      @messages = {
        :missing_association =>
          '%s is not associated with %s.',
        :wrong_type =>
          "%s %s %s./nExpected: %s",
        :wrong_options =>
          "Options are incorrect.\nExpected: %s Got: %s",
        :missing_column =>
          "Missing foreign key.\nExpected: %s"
      }
      @name = name
      @expected_type = type
      @expected_options = {}
    end
    
    def matches?(target)
      Class === target or
      raise ArgumentError, 'class expected'

      @target = target

      unless @assoc = target.reflect_on_association(@name)
        @failure = :missing_association
        return false
      end 

      unless @assoc.macro.eql?(@expected_type)
        @failure = :wrong_type
        return false
      end

      if @expected_options.any? { |o| @assoc.options[o.first] != o.last }
        @failure = :wrong_options
        return false
      end

      @column ||= @assoc.primary_key_name || @assoc.klass.name.foreign_key

      @failure = case @assoc.macro.to_s
      when 'belongs_to'
        if @target.column_names.include?(@column.to_s) then nil
        else
          :missing_column
        end
      when /(?:has_many|has_one)/
        if    @assoc.options[:through] then nil
        elsif @assoc.klass.column_names.include?(@column.to_s) then nil
        else
          :missing_column
        end
      end

      return @failure.nil?
    end

    def failure_message
      case @failure
      when :missing_association
        @messages[@failure] % [@target.name, @name]
      when :wrong_type
        @messages[@failure] % [
          @target.name,
          @assoc.macro,
          @name,
          @expected_type
        ]
      when :wrong_options
        @messages[@failure] % [
          @expected_options.inspect,
          @assoc.options.inspect
        ]
      when :missing_column
        @messages[@failure] % @column
      end
    end
    def negative_failure_message
    end

    ### Generic Options

    def of(class_name)
      class_name = class_name.name if Class === class_name
      @expected_options[:class_name] = class_name
      self
    end
    def for(foreign_key)
      @column = foreign_key
      self
    end
    def due_to(conditions)
      @expected_options[:conditions] = conditions
      self
    end
    def ordered_by(statement)
      @expected_options[:order] = statement
      self
    end
    def including(*models)
      @expected_options[:include] = (models.length == 1)? models.first: models
      self
    end

  end
  class BelongsToReflection < AssociationReflection

    def initialize(name)
      super :belongs_to, name
    end

    def counted(column)
      @expected_options[:counter_cache] = column
      self
    end
    def polymorphic(true_or_false = true)
      @expected_options[:polymorphic] = true_or_false
      self
    end

  end

  class HasOneReflection < AssociationReflection

    def initialize(name)
      super :has_one, name
    end

    def as(interface_name)
      @expected_options[:as] = interface_name
      self
    end
    def depending(dependency = true)
      @expected_options[:dependent] = dependency
      self
    end
    def extended_by(mod)
      @expected_options[:extend] = mod
      self
    end

  end
  class HasManyReflection < AssociationReflection

    def initialize(name)
      super :has_many, name
    end

    def as(interface_name)
      @expected_options[:as] = interface_name
      self
    end
    def depending(dependency = :destroy)
      @expected_options[:dependent] = dependency
      self
    end

  end
  class HasAndBelongsToManyReflection < AssociationReflection

    def initialize(name)
      super :has_and_belongs_to_many, name
    end

  end

  def belong_to(model)
    BelongsToReflection.new model
  end
  def have_one(model)
    HasOneReflection.new model
  end
  def have_many(models)
    HasManyReflection.new models
  end
  def have_and_belong_to_many(models)
    HasAndBelongsToManyReflection.new models
  end
  alias_method :habtm, :have_and_belong_to_many

end


It checks:
* association exists
* association macro
* foreign key exists (except for habtm)
* options match (only a subset is supported)

Setup:
* put the code in RAILS_ROOT + "/lib/association_matchers.rb"
* put "config.include AssociationMatchers # lib/association_matchers.rb" in your spec_helper.rb configure block
* refactor your model specs...

Prepend a String to a file

I happen to read a question on the ruby ML that inspired me to monkey patch (not really since this method does not exist...) the file class. I added a method called prepend to the File class. You need to require 'tempfile' or this patch will raise a NameError, imho.

require 'tempfile'

class File
  def self.prepend(path, string)
    Tempfile.open File.basename(path) do |tempfile|
      # prepend data to tempfile
      tempfile << string

      File.open(path, 'r+') do |file|
        # append original data to tempfile
        tempfile << file.read
        # reset file positions
        file.pos = tempfile.pos = 0
        # copy all data back to original file
        file << tempfile.read
      end
    end
  end
end


Ideas (unverified):
class FileString < String
  extend Forwardable

  def initialize(file)
    @file = file.reopen(file.path, 'r+')
    at_exit {@file.close}
  end

  def_delegators :@file, :<<, :pos, :pos=
end


Find every path and it's value in a Hash

Extends Hash class with each_path method.

This method takes a block as argument which is called each time a the recursivly searched Hash returns a key that does not point to another Hash.

Example:
paths = []
complex_hash = Hash[
  :a => { :aa => '1', :ab => '2' },
  :b => { :ba => '3', :bb => '4' }
]
complex_hash.each_path { |path, value| paths << [ path, value ] }
paths.inspect
# => "[[\"b/ba/\", \"3\"], [\"b/bb/\", \"4\"], [\"a/aa/\", \"1\"], [\"a/ab/\", \"2\"]]"


class Hash
  def each_path
    raise ArgumentError unless block_given?
    self.class.each_path( self ) { |path, object| yield path, object }
  end

  protected
  def self.each_path( object, path = '', &block )
    if object.is_a?( Hash ) then object.each do |key, value|
        self.each_path value, "#{ path }#{ key }/", &block
      end
    else yield path, object
    end
  end
end

SQL-Injection save parser generates ORDER BY statement

Parses a string and generates an SQL order statement.

Because it's SQL-Injection save you can put it in your link_to method as :order => '+name' and then call #parse_order( params[:order] ).

Examples:
'+name' => 'name'
'+lastname+firstname' => 'lastname, firstname'
'+lastname-gender' => 'lastname, gender DESC'

module ActiveRecord
  class Base
    class << self

      def parse_order( order )
        order = order.to_s.gsub /([ \+\-][a-z_]+)/ do |match|
          next unless self.column_names.include?( match[1..-1] )

          case match[0, 1]
          when '-' then "#{ match[1..-1] } DESC, "
          else "#{ match[1..-1] }, "
          end
        end and order[0..-3]
      end
    
    end
  end
end

Try'n'Go: The Last Date

Extends Date class with a method that return the last Date of a month.
Date#last_of_month takes either a Fixnum or a Time as argument.

require 'date'

class Date
  def self.last_of_month( arg = Time.now )
    year = ( arg.is_a? Fixnum ) ? Time.now.year : arg.year
    mon  = ( arg.is_a? Fixnum ) ? arg : ( arg.mon rescue Time.now.mon )
    
    raise ArgumentError unless mon.between?( 1, 12 )

    begin; Date.new year, mon, mday ||= 31
    rescue ArgumentError; mday -= 1; retry
    end
  end
end
« Newer Snippets
Older Snippets »
Showing 1-5 of 5 total  RSS