Posts Tagged ‘datamapper’

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

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

relevant merb / datamapper ticket information

When filing a ticket for merb or datamapper, it’s a good idea to put the following information into the ticket
(Suggested by Dan Kubb - dkubb in #datamapper)

uname -a (for unixes at least)
ruby --version
mysql --version (or the equivalent for your db)
gem list '\A(?:(?:d[mo]|merb)[_-]|data_?(?:mapper|objects)|extlib)'

merb_resource_controller now supports nested (singleton) resources

merb_resource_controller is a merb plugin that provides the default CRUD actions for controllers and allows for easy customization of the generated actions. You might have already heard of or used one of its counterparts in rails world, like make_resourceful, James Golick’s resource_controller, Ian White’s resources_controller or other projects on github. Thx a lot for the inspiration guys!

As it so happens, today merb_resource_controller learned to work with arbitrarily nested (singleton) resources AND got a massive README update :-)

So if you are a  merbivore you should really check it out and give me feedback!

What’s left to say? I say let the code do the talking …

class Articles < Application
  controlling :articles
end

How to uninstall datamapper (or any rubygem)

gem list dm --no-version | xargs sudo gem uninstall -a -x
gem list data --no-version | xargs sudo gem uninstall -a -x
gem list do_ --no-version | xargs sudo gem uninstall -a -x

fun with merb -i and dm-sweatshop

# in spec/fixtures.rb
# of course these are just examples of what the content could look like
User.fix {{
  :login => /\w+/.gen,
  :password => (password = /\w{12}/.gen),
  :password_confirmation => password
}}

GeoPoint.fix {{
  :lat => BigDecimal.new((rand * 180 -  90).to_s),
  :lng => BigDecimal.new((rand * 360 - 180).to_s),
}}

Location.fix {{
  :geo_point => unique { GeoPoint.gen },
  :name => /\w+/.gen,
  :short_description => /\w+/.gen,
  :long_description => /\w+/.gen,
  :address => /\w+/.gen
}}

20.times { Location.gen }

# in config/init.rb
Merb::BootLoader.after_app_loads do

  # This will get executed after your app's classes have been loaded.

  if Merb.env == "development"
    # allows for easy playin around in merb -i
    # dm-sweatshop will provide enough data for that
    require Merb.root / "spec" / "spec_fixtures"
  end

end

support for speccing datamapper models with rspec

# Put this in spec_helper.rb
module SpecSupport

  def self.unload_class(*classes)
    classes.each do |c|
      Object.send(:remove_const, c) if Object.const_defined?(c)
    end
  end

  def self.fresh_class(name, superclass = nil, &block)
    unload_class(name)
    args = superclass ? [ superclass ] : []
    Object.const_set(name, Class.new(*args))
    Object.const_get(name).class_eval(&block)
  end

  def self.fresh_model(name)
    fresh_class(name) do
      include DataMapper::Resource
      property :id,         DataMapper::Types::Serial
      property :created_at, DateTime
      property :updated_at, DateTime
    end
  end

end

« Older Entries

Newer Entries »