inspecting a many_to_many datamapper relationship object

The output of calling inspect on a datamapper many_to_many relationship object is very informative

migrating to datamapper 0.10.0

Some prerequisites to get ready for 0.10.0

You need to have the latest rubygems
You need to have a reasonably fresh rspec

mkdir dm-git-sources
cd dm-git-sources/

git clone git://github.com/datamapper/extlib.git
git clone git://github.com/datamapper/do.git
git clone git://github.com/datamapper/dm-core.git
git clone git://github.com/datamapper/dm-more.git
git clone git://github.com/snusnu/merb.git

cd do/
git checkout -b next --track origin/next
cd dm-core/
git checkout -b next --track origin/next
cd ../dm-more/
git checkout -b next --track origin/next

cd ../extlib/
rake install
cd ../do/data_objects/
rake install
cd ../do_sqlite3/
rake install
cd ../do_your_database/
rake install
cd ../../dm-core/
rake install
cd ../dm-more/
rake install
cd ../merb/
rake install

Migrating your app to datamapper 0.10.0 is pretty painless, you mainly need to deal with deprecation notices:

options[:class_name] => options[:model]

PropertySet#has_property? => PropertySet#named?

update_attributes => update (doesn't support *allow_attributes anymore)

new_record? => new?

:mutable => true on a has(n, :through) association is not supported/necessary anymore

:child_key => [ :key_id ] on a has(n, :through) association is now :target_key => [ :key_id ]

# database.yml
'database' key is now 'path'
'username' is now 'user'

rake task to generate CHANGELOG file (for git repositories)

generate (or update) a CHANGELOG file for git repositories (shamelessly copied from manveru/ramaze).

desc 'update changelog'
task :changelog do
  File.open('CHANGELOG', 'w+') do |changelog|
    `git log -z --abbrev-commit`.split("\0").each do |commit|
      next if commit =~ /^Merge: \d*/
      ref, author, time, _, title, _, message = commit.split("\n", 7)
      ref = ref[/commit ([0-9a-f]+)/, 1]
      author = author[/Author: (.*)/, 1].strip
      time = Time.parse(time[/Date: (.*)/, 1]).utc
      title.strip!

      changelog.puts "[#{ref} | #{time}] #{author}"
      changelog.puts '', " * #{title}"
      changelog.puts '', message.rstrip if message
      changelog.puts
    end
  end
end

strip whitespace using rake

This snippet is taken from the wonderful webrat library

desc 'Removes trailing whitespace'
task :whitespace do
  sh %{find . -name '*.rb' -exec sed -i '' 's/ *$//g' {} \\;}
end

authenticated flash upload using merb-auth and dm-paperclip

In order to get multiple file upload working, we decided to use flash. The problem with the flash upload library we used (uploadify — another alternative is swfupload), is that they both don’t send the session cookie to the server.

Since we have to authenticate our image uploads, we needed to get around this by following 2 steps:

  1. install the rack middleware from below (original code by Angel Pizarro, found via thewebfellas)
  2. send the raw cookie data as query parameter keyed by your apps session_id_key
# Merb.root/lib/rack/flash_upload.rb
module Merb
  module Rack
    class SetSessionCookieFromFlash < Merb::Rack::Middleware
      # :api: private
      def initialize(app, session_key = '_session_id')
        super(app)
        @session_key = session_key
      end
      # :api: plugin
      def call(env)
        if env["HTTP_USER_AGENT"] =~ /^(Adobe|Shockwave) Flash/
          params = Merb::Parse.query(env['QUERY_STRING'])
          if params[@session_key]
            env['HTTP_COOKIE'] = [@session_key, params[@session_key]].join('=').freeze
          end
        end
        @app.call(env)
      end
    end
  end
end

# Merb.root/config/rack.rb
require 'rack/flash_upload'
use Merb::Rack::SetSessionCookieFromFlash, Merb::Config[:session_id_key]

datamapper storage engine reflection

# Find out what storage engines are installed and are supported by datamapper
# This could be especially useful in testing scenarios, where one wants to run
# specs against all adapters that are available on the machine in use.

require "rubygems"
require "dm-core"
require "spec"

module DataMapper

  def self.available_storage_engines
    [ :sqlite3, :mysql, :postgres ].select do |name|
      storage_engine_available?(name)
    end
  end

  def self.storage_engine_available?(name)
    ::DataObjects.const_defined?(Extlib::Inflection.classify(name.to_s))
  end

end

# spec helper
# set it so that it fits your actually installed do_xxx drivers
AVAILABLE_STORAGE_ENGINES = [
  :sqlite3,
  :mysql
  # :postgres
]

describe 'DataMapper storage engine reflection: ' do

  describe 'DataMapper.storage_engine_available?(name)' do
    it 'should return true for adapters known to be installed on this machine' do
      DataMapper.storage_engine_available?(:sqlite3).should  == AVAILABLE_STORAGE_ENGINES.include?(:sqlite3)
      DataMapper.storage_engine_available?(:mysql).should    == AVAILABLE_STORAGE_ENGINES.include?(:mysql)
      DataMapper.storage_engine_available?(:postgres).should == AVAILABLE_STORAGE_ENGINES.include?(:postgres)
    end
  end

  describe 'DataMapper.available_storage_engines' do
    it 'should try all adapters shipped with dm-core and return all adapters known to be installed on this machine' do
      storage_engines = DataMapper.available_storage_engines
      storage_engines.size.should == AVAILABLE_STORAGE_ENGINES.size
      storage_engines.each do |storage_engine|
        AVAILABLE_STORAGE_ENGINES.should include(storage_engine)
      end
    end
  end

end

# mungo:Desktop snusnu$ spec -cfs storage_engine_reflection.rb
#
# DataMapper storage engine reflection:  DataMapper.storage_engine_available?(name)
# - should return true for adapters known to be installed on this machine
#
# DataMapper storage engine reflection:  DataMapper.available_storage_engines
# - should try all adapters shipped with dm-core and return all adapters known to be installed on this machine
#
# Finished in 0.002688 seconds
#
# 2 examples, 0 failures

#validatable? should work on a belongs_to association

The following ONLY APPLIES to datamapper-0.9.x, datamapper-0.10.0 doesn’t have this problem anymore

require "rubygems"
require "dm-core"
require "dm-validations"
require "spec"

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

class Person
  include DataMapper::Resource
  property :id, Serial
  has 1, :profile
  has n, :project_memberships
  has n, :projects, :through => :project_memberships
end

class Profile
  include DataMapper::Resource
  property :id, Serial
  property :person_id, Integer, :nullable => false
  belongs_to :person
end

class Project
  include DataMapper::Resource
  property :id, Serial
  has n, :tasks
end

class ProjectMembership
  include DataMapper::Resource
  property :id, Serial
  property :person_id, Integer, :nullable => false
  property :project_id, Integer, :nullable => false
  belongs_to :person
  belongs_to :project
end

class Task
  include DataMapper::Resource
  property :id, Serial
  property :project_id, Integer, :nullable => false
  belongs_to :project
end

describe "Resource#validatable?" do

  before(:all) do
    DataMapper.auto_migrate!
  end

  describe "when called directly on a Resource" do
    it "should return true if dm-validations are present" do
      Person.create.should be_validatable
      Profile.create.should be_validatable
      Project.create.should be_validatable
      ProjectMembership.create.should be_validatable
    end
  end

  describe "when called on an DataMapper::Associations::ManyToOne::Proxy" do

    it "should return true if dm-validations are present" do
      person  = Person.create
      project = Project.create
      membership = ProjectMembership.create :person => person, :project => project
      membership.person.should be_validatable
      membership.project.should be_validatable
    end

  end

  describe "when called on an DataMapper::Associations::OneToMany::Proxy" do

    describe "that points to a Resource related with has(n)" do

      it "should return true if dm-validations are present" do
        p = Project.create
        t = Task.create :project_id => p.id
        p.reload
        p.tasks.all? { |task| task.validatable? }.should be_true
      end

    end

    describe "that points to a Resource related with has(n, :through)" do

      it "should return true if dm-validations are present" do
        person  = Person.create
        project = Project.create
        membership = ProjectMembership.create :person => person, :project => project
        person.reload
        person.projects.all? { |p| p.validatable? }.should be_true
      end

    end

  end

end

# mungo:Desktop snusnu$ spec -c -f -s validatable.rb
#
# Resource#validatable? when called directly on a Resource
# - should return true if dm-validations are present
#
# Resource#validatable? when called on an DataMapper::Associations::ManyToOne::Proxy
# - should return true if dm-validations are present (FAILED - 1)
#
# Resource#validatable? when called on an DataMapper::Associations::OneToMany::Proxy that points to a Resource related with has(n)
# - should return true if dm-validations are present
#
# Resource#validatable? when called on an DataMapper::Associations::OneToMany::Proxy that points to a Resource related with has(n, :through)
# - should return true if dm-validations are present
#
# 1)
# 'Resource#validatable? when called on an DataMapper::Associations::ManyToOne::Proxy should return true if dm-validations are present' FAILED
# expected validatable? to return true, got false
# ./validatable.rb:67:
#
# Finished in 0.028411 seconds
#
# 4 examples, 1 failure

extlib/hook breaks if hooked method is redefined

require "rubygems"
require "dm-core"
require "spec"

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

class Person

  include DataMapper::Resource

  property :id, Serial

  before :save, :shower
  after  :save, :shave

  def shower
    @showered = true
  end

  def shave
    @shaved = true
  end

  def showered?
    @showered
  end

  def shaved?
    @shaved
  end

end

describe "Hooking Resource#save" do

  before(:all) do
    DataMapper.auto_migrate!
  end

  describe "when the hooked method DOES CALL super" do

    before(:each) do
      class Person
        def save(context = nil)
          super(context)
        end
      end
    end

    it_should_behave_like "hooks"

  end

  describe "when the hooked method DOES NOT CALL super" do

    before(:each) do
      class Person
        def save(context = nil)
          true
        end
      end
    end

    it_should_behave_like "hooks"

  end

  describe "hooks", :shared => true do

    it "should preserve established before and after hooks" do
      p = Person.new
      p.should_not be_showered
      p.should_not be_shaved
      p.save
      p.should be_showered
      p.should be_shaved
    end

  end

end

# mungo:Desktop snusnu$ spec -c -f -s freak.rb
#
# Hooking Resource#save when the hooked method DOES CALL super
# - should preserve established before hooks
#
# Hooking Resource#save when the hooked method DOES NOT CALL super
# - should preserve established before hooks (FAILED - 1)
#
# 1)
# 'Hooking Resource#save when the hooked method DOES NOT CALL super should preserve established before hooks' FAILED
# expected showered? to return true, got nil
# ./freak.rb:76:
#
# Finished in 0.007412 seconds
#
# 2 examples, 1 failure

datamapper and inflection of the english word address

require 'rubygems'

gem 'dm-core', '=0.9.12'
gem 'rspec'

require 'dm-core'
require "spec"

#DataMapper::Logger.new(STDOUT, :debug)
DataMapper.setup(:default, 'sqlite3:memory:')

Extlib::Inflection.rule 'ess', 'esses'
Extlib::Inflection.singular_word("address", "address")

class Person
  include DataMapper::Resource
  property :id, Serial
  has n, :addresses, :through => Resource
end

class Address
  include DataMapper::Resource
  property :id, Serial
end

describe "Inflection of 'Address'" do

  it "should work in DataMapper.auto_migrate!" do
    lambda { DataMapper.auto_migrate! }.should_not raise_error
  end

end

# mungo:Desktop snusnu$ spec -c -f -s address.rb
#
# Inflection of 'Address'
# - should work in DataMapper.auto_migrate!
#
# Finished in 0.040151 seconds
#
# 1 example, 0 failures

textmate rspec bundle (datamapper) helpers

I found that I often want to add debug outputs to the specs while working on them. Since I use textmate’s rspec bundle to run (focused) unit tests, I needed my debug messages to be html. Here’s what I came up with

http://gist.github.com/92470

« older