Skip to content

Instantly share code, notes, and snippets.

@jpr5
Created September 24, 2010 19:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jpr5/595941 to your computer and use it in GitHub Desktop.
Save jpr5/595941 to your computer and use it in GitHub Desktop.
DM1 example of using RefCode DM custom property (useful for avoiding outside exposure of internal ids) w/ RetrySaveMixin
require 'digest/sha1'
class RefCode < ::DataMapper::Property::String
required true
unique_index true
default proc { |_, prop| prop.class.generate(prop.length) }
# Setting :format before passing on the initialization chain will cause
# dm-validations (if present) to auto-add a format validation for us.
# For some reason setting :length is not doing the same, so our format
# validation here is technically validating both format and length.
def initialize(model, name, options = {}, type = nil)
options[:length] = options[:length]..options[:length] unless options[:length].is_a?(Range)
options[:format] = /^[0-9a-z]{#{options[:length].min},#{options[:length].max}}$/ if defined? ::DataMapper::Validations
model.send(:include, ::RetrySaveMixin)
super
end
def primitive?(value)
return value.kind_of?(::String)
end
# DM "core" validation (non dm-validations). Validates both format and
# length, but in the absence of dm-validations, the result will be #save
# failure without actually populating #errors (confusing, but better
# than saving bad data).
def valid?(value, negated = false)
super && value.match(/^[0-9a-z]{#{range.min},#{range.max}}$/)
end
def range
return @length.is_a?(Range) ? @length : @length..@length
end
protected
def self.generate(size)
return Digest::SHA1.hexdigest(rand.to_s).to_i(16).to_s(36)[0..size-1]
end
end
::DataMapper::Property::RefCode = ::RefCode
module RetrySaveMixin
MAX_SAVE_RETRIES = 5
def regenerate_defaults
self.send(:properties).select{ |p| p.default.is_a?(Proc) }.each do |prop|
attribute_set(prop.name, prop.default.call(self, prop))
end
end
def save_self(*args)
return super unless new?
retries = 0
begin
super
rescue Exception => e
raise if retries == MAX_SAVE_RETRIES
regenerate_defaults
retries += 1
retry
end
end
end
class Foo
include ::DataMapper::Resource
property :id, Serial
property :ref_code, RefCode, :length => 8
end
::DataMapper.auto_migrate!
foo = Foo.create
bar = Foo.create(:ref_code => foo.ref_code)
# Tada!
@solnic
Copy link

solnic commented Oct 2, 2010

re: valid? and absence of dm-validations...@errors object comes with dm-validations so it's hard to populate something that doesn't exist :) But you're right it's confusing that DM doesn't let you know in any way that save/update operation failed b/c of an invalid input! We're gonna tackle that problem soon...!

@jpr5
Copy link
Author

jpr5 commented Oct 2, 2010

Note that the "issue" with :length auto-validation goes away with: http://github.com/jpr5/dm-validations/commit/ea517f0bc85e04f77b61afa41c4ac6a0b0dd0e91. Pull request pending.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment