Posts Tagged ‘ruby’

datamapper debug output in Textmate RSpec Bundle

# stick this in spec_helper.rb
# this will append a <br /> to every logged message, which produces
# nicely formatted DataMapper debug outputs in Textmate's RSpec Bundle's output

module DataMapper
  class TextmateRspecLogger < Logger
    def prep_msg(message, level)
      "#{super}<br />"
    end
  end
end

# comment this for spec runs where you don't need to see logs
DataMapper::TextmateRspecLogger.new(STDOUT, :debug)

case foo.class might not work like you expect

irb(main):001:0> class Foo; end
=> nil
irb(main):002:0> class Bar; end
=> nil
irb(main):003:0> f = Foo.new
=> #
irb(main):004:0> case f.class
irb(main):005:1> when Foo then puts “f.class = Foo”
irb(main):006:1> when Bar then puts “f.class = Bar”
irb(main):007:1> else
irb(main):008:1*   puts “no match found”
irb(main):009:1> end
no match found
=> nil
irb(main):010:0> case f.class.name
irb(main):011:1> when “Foo” then puts “f.class.name = ‘Foo’”
irb(main):012:1> when “Bar” then puts “f.class.name = ‘Bar’”
irb(main):013:1> else
irb(main):014:1*   puts “no match found”
irb(main):015:1> end
f.class.name = ‘Foo’
=> nil

dm-is-cloneable

I just pushed dm-is-cloneable.

A DataMapper plugin that adds the ability to clone any model. As this alone wouldn’t really be a big deal, it is also possible to specifiy a 1:n relation with so called clone_specs. In these, the attributes_to_clone are persisted, thus leaving clients with the choice of selecting predefined sets of attributes to clone. Currently, associated objects don’t get cloned, although this might change if I need this in the future.

Client code looks something like this:


require 'rubygems'

gem 'dm-core',         '=0.9.11'
gem 'dm-validations',  '=0.9.11'
gem 'dm-is-remixable', '=0.9.11'
gem 'dm-is-cloneable', '=0.0.1'

require 'dm-core'
require 'dm-validations'
require 'dm-is-remixable'
require 'dm-is-cloneable'

DataMapper::Logger.new(STDOUT, :debug)

DataMapper.setup(:default, 'sqlite3:memory:')

class Item

  include DataMapper::Resource

  property :id,             Serial
  property :master_item_id, Integer # add this property if backlinks are desired

  property :name,           String
  property :description,    String

  is :cloneable

  # adds the following api to this class
  #
  # # attributes_to_clone can be one of
  # # <:all|String|Array[String|Symbol]|ItemCloneSpec>
  # Item#clone_resource(nr_of_clones, attributes_to_clone = :all)
  # Item#master_resource
  # Item#cloned_resources
  # Item#has_backlinks_to_master?
  # Item#has_clone_specs?

end

dm-is-remixable and overwritten property accessors

I just stumbled across some weirdness in dm-is-remixable. Seems like overwritten property writers work, whereas overwritten property readers don’t … hmm …

working with UTC and datamapper


  # stick this in the before_app_loads block in init.rb
  # if this maybe finds its way into dm-more, this wouldn't be necessary anymore

  module DataMapper
    module Types

      class UtcDateTime < DataMapper::Type

        primitive DateTime

        def self.load(v, property)
          if v.nil?
            nil
          elsif v.is_a?(DateTime)
            ::DateTime.new(v.year, v.month, v.day, v.hour, v.min, v.sec, 0)
          else
            raise ArgumentError.new("+value+ must be nil or a DateTime")
          end
        end

        def self.dump(v, property)
          if v.nil?
            nil
          elsif v.is_a?(String)
            Time.parse(v).utc.to_datetime
          elsif v.is_a?(DateTime)
            Time.parse(v.to_s).utc.to_datetime
          else
            raise ArgumentError.new("+value+ must be nil or a String or a DateTime")
          end
        end

      end # class UtcDateTime

      UTCDateTime = UtcDateTime

    end # module Types
  end # module DataMapper

  module DataMapper
    module Timestamp

      def set_timestamps
        return unless dirty? || new_record?
        TIMESTAMP_PROPERTIES.each do |name,(_type,proc)|
          if model.properties.has_property?(name)
            self.send("#{name}=", proc.call(self, model.properties[name]))
          end
        end
      end

      def utc_timestamped?
        self.class.utc_timestamped?
      end

      module ClassMethods

        def timestamps(*names)
          raise ArgumentError, 'You need to pass at least one argument' if names.empty?

          # if the last element in names is a Hash:
          # extract this hash and look for a :utc key
          opts = names.last.is_a?(Hash) ? names.pop : nil
          utc_possible = names.include?(:created_at) || names.include?(:updated_at)
          @utc = opts && opts[:utc] && utc_possible

          names.each do |name|
            case name
              when *TIMESTAMP_PROPERTIES.keys
                type = TIMESTAMP_PROPERTIES[name].first
                property name, type, :nullable => false, :auto_validation => false

                if type == DateTime && @utc # UTC makes no sense for Date
                  define_method "#{name}=", UTC::PROPERTY_WRITER.call(name, type)
                  define_method "#{name}",  UTC::PROPERTY_READER.call(name, type)
                end

              when :at
                timestamps(:created_at, :updated_at, :utc => @utc)
              when :on
                timestamps(:created_on, :updated_on) # UTC makes no sense for Date
              else
                raise InvalidTimestampName, "Invalid timestamp property name '#{name}'"
            end
          end
        end

        def utc_timestamped?
          !!@utc
        end

      end

      module UTC
        PROPERTY_WRITER = lambda { |name, type|
          typecast = "to_#{type.name.downcase}"
          lambda { |dt| attribute_set(name, Time.parse(dt.to_s).utc.send(typecast)) }
        }
        PROPERTY_READER = lambda { |name, type|
          lambda {
            return nil unless dt = attribute_get(name)
            if type == Date
              Date.new(dt.year, dt.month, dt.day, 0)
            elsif type == DateTime
              DateTime.new(dt.year, dt.month, dt.day, dt.hour, dt.min, dt.sec, 0)
            else
              nil
            end
          }
        }
      end

    end
  end

I HATE WORKING WITH DATE AND DATETIME AND TIME

seriously, i do ….

ruby method calls with heredoc parameter and block

This is taken from here and serves mainly as a reminder for myself.

  dbh.execute(<<-STMT) { |sth|
      select distinct customer, business_unit_id, business_unit_key_name
       from problem_ticket_lz
       order by customer
    STMT
    sth.fetch { |row|
      print "#{row[1]}\t#{row[0]}\t#{row[2]}\n"
    }
  }

sharing common properties between datamapper models

require "rubygems"
require "dm-core"

module Locatable

  # using a lambda instead of self.included(base) class_eval hack
  # to add properties and/or associations that are shared by many models
  #
  # use this module in two steps.
  #
  # 1) eval the lambda with property definitions in class scope
  #   class_eval &Locatable::PROPERTIES
  #
  # 2) include the common instance methods if any
  #   include Locatable
  #
  # this also makes it possible, that this module gets included at a different
  # time (place in code) than the property definitions, which might come in
  # handy in situations where you deal with overriding methods with/from
  # this module (say you want to establish aliases for methods that only get
  # added by some calls to belongs_to or has or the likes)

  PROPERTIES = lambda do
    property :location_id, Integer, :nullable => false
  end

  ASSOCIATIONS = lambda do
    belongs_to :location
  end

  # common instance methods

  # need to support different protocol
  # for whatever reason ...
  alias :information :description

  def latitude
    location.latitude
  end

  def longitude
    location.longitude
  end

  # ...

end

class Item

  include DataMapper::Resource

  # properties

  property :id, Serial
  property :owner_id, Integer, :nullable => false

  # using the lambda here makes it very explicit what's going on.
  # it also allows to preserve the property order in the underlying
  # table definition in a natural and consistent way.
  class_eval &Locatable::PROPERTIES

  property :name,         String
  property :description, Text

  # associations

  # again, using the lambda here makes it very explicit what's going on.
  class_eval &Locatable::ASSOCIATIONS

  belongs_to :owner
  has n, :item_categories
  has n, :categories, :through => :item_categories

  # include Locatable methods after #description exists
  # we want to support the alias

  include Locatable

end

default ordering with datamapper

Thanks to Adam French for this tip on the datamapper mailinglist.

You’d essentially be update’ing the default query object that all
calls to all() and first() start off with before applying the user’s
parameters

class Post
 include DataMapper::Resource
 #... other properties here
 default_scope(:default).update(:order => [:created_at.desc])
end

datamapper stand alone script (for bugreports)

#!/usr/bin/env ruby

require 'rubygems'

gem 'dm-core', '~>0.9.8'
require 'dm-core'

DataMapper::Logger.new(STDOUT, :debug)
# DataObjects::Sqlite3.logger = DataObjects::Logger.new(STDOUT, :debug)

DataMapper.setup(:default, 'sqlite3::memory:')

# here come the demo class(es)

DataMapper.auto_migrate!

# here comes initial creation of records if necessary

puts '-' * 80

# here comes the demo code demonstrating the error or whatever

« Older Entries

Newer Entries »