views:

118

answers:

4

I have a datamapper model that has a unique index on a property called name. I want to create new records when a name doesn't already exist and silently ignore attempts to create records with duplicate names. What's the "right" way to do this in datamapper?

A: 

One solution I came up with is simply to ignore the exception:

  begin
    Committer.create!(:name => committer)
  rescue DataObjects::IntegrityError => e # ignore duplicate inserts
  end

If you have a better (more idiomatic) way of doing it, please let me know.

Carl Youngblood
+1  A: 

The best approach is to use the dm-validations gem, and ensure that your name property is specified as being unique, eg:

class Committer
  include DataMapper::Resource

  # ... other properties ...

  property :name, String, :length => 1..100, :required => true, :unique => true
end

The dm-validations gem will introspect your model and automatically setup validations for your properties. In this case it won't allow more than one Committer to have the same name.

dkubb
Thanks Dan. My main concern is not how to enforce uniqueness, but how to do an INSERT IGNORE--that is, how to create a new record but fail silently if a duplicate record is already there. I'm wondering what the best way of doing this is.
Carl Youngblood
You can't do an INSERT IGNORE in DataMapper without a plugin, but there isn't one that does this atm.
dkubb
Just to clarify, I didn't mean to specifically require the use of INSERT IGNORE. Anything that accomplished my goal was fine.
Carl Youngblood
A: 

Here is another solution I came up with:

require 'dm-core'
require 'dm-validations'
require 'dm-more'
record = Committer.find_or_create(:name => committer)

If you're using this in sinatra, requiring dm-more seems to cause other problems. My solution was to require my own file that only contained the following code:

module DataMapper
 module Model
   def first_or_create(conditions = {}, attributes = {})
     first(conditions) || create(conditions.merge(attributes))
   end

   alias find_or_create first_or_create
 end
end
Carl Youngblood
The `Model#first_or_create` method is already part of DataMapper. Also I wouldn't generally recommend using finders named `#find_*` because they might conflict with other plugins like dm-ar-finders; but more importantly the DataMapper naming convention for finders that return the first of something is to use the `#first_*` prefix.
dkubb
ok, thanks Dan.
Carl Youngblood
A: 

I think the best answer is to use first_or_create, which as Dan points out above, is already built into datamapper and therefore doesn't need to be declared.

require 'dm-core'
require 'dm-validations'

class Committer
  include DataMapper::Resource
  property :id, Serial
  property :name, String, :unique_index => true
  validates_present :name
  validates_is_unique :name
end
committer = "George"
record = Committer.first_or_create(:name => committer)
Carl Youngblood