@home   @rss   @archive   @codeforpeople.com     @radio[:m3u|:pls|:ruby]   @family  

Rails: ActiveRecord default values

ActiveRecord is an amazing peice of software but one thing it lacks is an easy mechanism for setting column defaults outside of the database and virtually no mechanism for setting defaults via database functions. Basically there is no easy way to do this

  class Model < ActiveRecord::Base
    defaults time => now()
  end

The first thing we need is a generic mechanism for setting defaults. This does nicely:

  class ActiveRecord::Base
    alias_method __initialize__, initialize

    def initialize options = nil, &block
      returning( __initialize__(options, &block) ) do
        options ||= {}
        options.to_options!
        defaults = self.class.defaults || self.defaults || Hash.new
        (defaults.keys - options.keys).each do |key|
          value = defaults[key]
          case value
            when Proc
              value = instance_eval &value
            when Symbol
              value = send value
          end
          send #{ key }=, value
        end
      end
    end

    def self.defaults *argv
      @defaults = argv.shift.to_hash if argv.first
      return @defaults if defined? @defaults
    end

    def defaults *argv
      @defaults = argv.shift.to_hash if argv.first
      return @defaults if defined? @defaults
    end
  end

Now we can do this

  class Model < ActiveRecord::Base
    defaults foo => 42
  end

and this

  class Model < ActiveRecord::Base
    defaults foo => lambda{ 42 } 
  end

and this

  class Model < ActiveRecord::Base
    defaults foo => :bar 
    def bar() 42 end
  end

and this

  class Model < ActiveRecord::Base
    defaults foo => lambda{ bar }
    def bar() 42 end
  end

now, sprinkling in a little abuse of the rails’ source we do

  class ActiveRecord::Base
    module UnQuoted
      def quoted_id() self end ### hackity hack, don’t talk back
    end

    DEFAULT = DEFAULT
    DEFAULT.extend UnQuoted
    DEFAULT.freeze

    def pgsql(*argv, &block)
      string = argv.join( )
      string +=  #{ block.call } if block
      string.extend UnQuoted
      string
    end

    def self.nextval seq
      connection = ActiveRecord::Base.connection
      lambda{
        Integer(connection.execute(select nextval(‘#{ seq }’))[0][0])
      }
    end

    def nextval *a, &b
      self.class.nextval *a, &b
    end
  end

and we’re all good to do things like

  class Model < ActiveRecord::Base
  ### use the db default, even function
    defaults time => DEFAULT
  end
and
  class Model < ActiveRecord::Base
  ### use arbitrary sql for default value
    defaults time => pgsql(now())
  end


and

  class Model < ActiveRecord::Base
  ### yank a value out of a sequence as the default
    defaults voter_id => nextval(voter_id_seq)
  end

All in all a lot of bang for not much code. Enjoy.


Comments (View)



blog comments powered by Disqus