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:

   1  
   2  describe Product, 'with Group' do
   3  
   4    it 'should belong to group' do
   5      Product.should belong_to(:product_group)
   6    end
   7  
   8  end
   9  
  10  describe ProductGroup, 'with Product' do
  11  
  12    it 'should have many products depending (on group)' do
  13      ProductGroup.should have_many(:products).depending
  14    end
  15  
  16  end


Here is the code:

   1  
   2  module AssociationMatchers
   3  
   4    class AssociationReflection
   5  
   6      def initialize(type, name)
   7        @messages = {
   8          :missing_association =>
   9            '%s is not associated with %s.',
  10          :wrong_type =>
  11            "%s %s %s./nExpected: %s",
  12          :wrong_options =>
  13            "Options are incorrect.\nExpected: %s Got: %s",
  14          :missing_column =>
  15            "Missing foreign key.\nExpected: %s"
  16        }
  17        @name = name
  18        @expected_type = type
  19        @expected_options = {}
  20      end
  21      
  22      def matches?(target)
  23        Class === target or
  24        raise ArgumentError, 'class expected'
  25  
  26        @target = target
  27  
  28        unless @assoc = target.reflect_on_association(@name)
  29          @failure = :missing_association
  30          return false
  31        end 
  32  
  33        unless @assoc.macro.eql?(@expected_type)
  34          @failure = :wrong_type
  35          return false
  36        end
  37  
  38        if @expected_options.any? { |o| @assoc.options[o.first] != o.last }
  39          @failure = :wrong_options
  40          return false
  41        end
  42  
  43        @column ||= @assoc.primary_key_name || @assoc.klass.name.foreign_key
  44  
  45        @failure = case @assoc.macro.to_s
  46        when 'belongs_to'
  47          if @target.column_names.include?(@column.to_s) then nil
  48          else
  49            :missing_column
  50          end
  51        when /(?:has_many|has_one)/
  52          if    @assoc.options[:through] then nil
  53          elsif @assoc.klass.column_names.include?(@column.to_s) then nil
  54          else
  55            :missing_column
  56          end
  57        end
  58  
  59        return @failure.nil?
  60      end
  61  
  62      def failure_message
  63        case @failure
  64        when :missing_association
  65          @messages[@failure] % [@target.name, @name]
  66        when :wrong_type
  67          @messages[@failure] % [
  68            @target.name,
  69            @assoc.macro,
  70            @name,
  71            @expected_type
  72          ]
  73        when :wrong_options
  74          @messages[@failure] % [
  75            @expected_options.inspect,
  76            @assoc.options.inspect
  77          ]
  78        when :missing_column
  79          @messages[@failure] % @column
  80        end
  81      end
  82      def negative_failure_message
  83      end
  84  
  85      ### Generic Options
  86  
  87      def of(class_name)
  88        class_name = class_name.name if Class === class_name
  89        @expected_options[:class_name] = class_name
  90        self
  91      end
  92      def for(foreign_key)
  93        @column = foreign_key
  94        self
  95      end
  96      def due_to(conditions)
  97        @expected_options[:conditions] = conditions
  98        self
  99      end
 100      def ordered_by(statement)
 101        @expected_options[:order] = statement
 102        self
 103      end
 104      def including(*models)
 105        @expected_options[:include] = (models.length == 1)? models.first: models
 106        self
 107      end
 108  
 109    end
 110    class BelongsToReflection < AssociationReflection
 111  
 112      def initialize(name)
 113        super :belongs_to, name
 114      end
 115  
 116      def counted(column)
 117        @expected_options[:counter_cache] = column
 118        self
 119      end
 120      def polymorphic(true_or_false = true)
 121        @expected_options[:polymorphic] = true_or_false
 122        self
 123      end
 124  
 125    end
 126  
 127    class HasOneReflection < AssociationReflection
 128  
 129      def initialize(name)
 130        super :has_one, name
 131      end
 132  
 133      def as(interface_name)
 134        @expected_options[:as] = interface_name
 135        self
 136      end
 137      def depending(dependency = true)
 138        @expected_options[:dependent] = dependency
 139        self
 140      end
 141      def extended_by(mod)
 142        @expected_options[:extend] = mod
 143        self
 144      end
 145  
 146    end
 147    class HasManyReflection < AssociationReflection
 148  
 149      def initialize(name)
 150        super :has_many, name
 151      end
 152  
 153      def as(interface_name)
 154        @expected_options[:as] = interface_name
 155        self
 156      end
 157      def depending(dependency = :destroy)
 158        @expected_options[:dependent] = dependency
 159        self
 160      end
 161  
 162    end
 163    class HasAndBelongsToManyReflection < AssociationReflection
 164  
 165      def initialize(name)
 166        super :has_and_belongs_to_many, name
 167      end
 168  
 169    end
 170  
 171    def belong_to(model)
 172      BelongsToReflection.new model
 173    end
 174    def have_one(model)
 175      HasOneReflection.new model
 176    end
 177    def have_many(models)
 178      HasManyReflection.new models
 179    end
 180    def have_and_belong_to_many(models)
 181      HasAndBelongsToManyReflection.new models
 182    end
 183    alias_method :habtm, :have_and_belong_to_many
 184  
 185  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.

   1  
   2  require 'tempfile'
   3  
   4  class File
   5    def self.prepend(path, string)
   6      Tempfile.open File.basename(path) do |tempfile|
   7        # prepend data to tempfile
   8        tempfile << string
   9  
  10        File.open(path, 'r+') do |file|
  11          # append original data to tempfile
  12          tempfile << file.read
  13          # reset file positions
  14          file.pos = tempfile.pos = 0
  15          # copy all data back to original file
  16          file << tempfile.read
  17        end
  18      end
  19    end
  20  end


Ideas (unverified):
   1  
   2  class FileString < String
   3    extend Forwardable
   4  
   5    def initialize(file)
   6      @file = file.reopen(file.path, 'r+')
   7      at_exit {@file.close}
   8    end
   9  
  10    def_delegators :@file, :<<, :pos, :pos=
  11  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:
   1  
   2  paths = []
   3  complex_hash = Hash[
   4    :a => { :aa => '1', :ab => '2' },
   5    :b => { :ba => '3', :bb => '4' }
   6  ]
   7  complex_hash.each_path { |path, value| paths << [ path, value ] }
   8  paths.inspect
   9  # => "[[\"b/ba/\", \"3\"], [\"b/bb/\", \"4\"], [\"a/aa/\", \"1\"], [\"a/ab/\", \"2\"]]"


   1  
   2  class Hash
   3    def each_path
   4      raise ArgumentError unless block_given?
   5      self.class.each_path( self ) { |path, object| yield path, object }
   6    end
   7  
   8    protected
   9    def self.each_path( object, path = '', &block )
  10      if object.is_a?( Hash ) then object.each do |key, value|
  11          self.each_path value, "#{ path }#{ key }/", &block
  12        end
  13      else yield path, object
  14      end
  15    end
  16  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'

   1  
   2  module ActiveRecord
   3    class Base
   4      class << self
   5  
   6        def parse_order( order )
   7          order = order.to_s.gsub /([ \+\-][a-z_]+)/ do |match|
   8            next unless self.column_names.include?( match[1..-1] )
   9  
  10            case match[0, 1]
  11            when '-' then "#{ match[1..-1] } DESC, "
  12            else "#{ match[1..-1] }, "
  13            end
  14          end and order[0..-3]
  15        end
  16      
  17      end
  18    end
  19  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.

   1  
   2  require 'date'
   3  
   4  class Date
   5    def self.last_of_month( arg = Time.now )
   6      year = ( arg.is_a? Fixnum ) ? Time.now.year : arg.year
   7      mon  = ( arg.is_a? Fixnum ) ? arg : ( arg.mon rescue Time.now.mon )
   8      
   9      raise ArgumentError unless mon.between?( 1, 12 )
  10  
  11      begin; Date.new year, mon, mday ||= 31
  12      rescue ArgumentError; mday -= 1; retry
  13      end
  14    end
  15  end
« Newer Snippets
Older Snippets »
Showing 1-5 of 5 total  RSS