module DataMapper::Shim::ClassMethods

Constants

EmailAddress

RFC2822 (No attribution reference available)

Public Class Methods

where(opts, *rest) click to toggle source

Substitute enum values in where clauses Note, we don’t define this on ActiveRecord::Relation

Calls superclass method
# File lib/data_mapper/shim.rb, line 218
def self.where(opts, *rest)
  @__datamapper_shim_enums.each do |name, enum|
    if opts.is_a?(Hash) && opts.has_key?(name) && !opts[name].is_a?(Integer)
      opts[name] = enum[opts[name]]
    end
  end

  super
end
with_deleted() { || ... } click to toggle source
# File lib/data_mapper/shim.rb, line 240
def self.with_deleted
  self.unscoped.where("`#{self.table_name}`.deleted_at IS NOT NULL").scoping do
    yield if block_given?
  end
end

Public Instance Methods

after(event, callback = nil, &block) click to toggle source

Callbacks

# File lib/data_mapper/shim.rb, line 516
def after(event, callback = nil, &block)
  case event
  when :create
    callback ? after_create(callback, &block) : after_create(&block)
  when :save
    callback ? after_save(callback, &block) : after_save(&block)
  when :destroy
    callback ? after_destroy(callback, &block) : after_destroy(&block)
  else
    raise "Implement `after #{event.inspect}` in DataMapper::Shim"
  end
end
all(*args) click to toggle source

Finders

# File lib/data_mapper/shim.rb, line 159
def all(*args)
  if args.empty?
    scoped
  else
    where(*args)
  end
end
before(event, callback = nil, &block) click to toggle source
# File lib/data_mapper/shim.rb, line 529
def before(event, callback = nil, &block)
  case event
  when :create
    callback ? before_create(callback, &block) : before_create(&block)
  when :save
    callback ? before_save(callback, &block) : before_save(&block)
  when :destroy
    callback ? before_destroy(callback, &block) : before_destroy(&block)
  when :valid?
    # Per http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html,
    # before_validation callbacks in AR cause a #save call to fail if they
    # return false. Since this wasn't the case in DM, we need to wrap
    # before_validation callbacks in a proc that always returns true.
    if callback
      raise "Need to handle before_validation with callback AND block" unless block.nil?
      callback_with_true_return = proc do |*args|
        self.__send__ callback
        true
      end
      before_validation &callback_with_true_return
    else
      block_with_true_return = block && proc { |*args| self.instance_eval █ true }
      before_validation &block_with_true_return
    end
  when :update
    callback ? before_update(callback, &block) : before_update(&block)
  else
    raise "Add before case statement for #{event} event"
  end
end
belongs_to(name, options = {}) click to toggle source
Calls superclass method
# File lib/data_mapper/shim.rb, line 478
def belongs_to(name, options = {})
  relationship_options = {}

  if options.has_key?(:nullable) && options.delete(:nullable) == false
    validates_presence_of name
  end

  if foreign_key = options.delete(:child_key)
    if foreign_key.is_a?(Enumerable) && foreign_key.length == 1
      foreign_key = foreign_key.first
    end

    relationship_options.merge! :foreign_key => foreign_key
  end

  if class_name = options.delete(:class_name)
    relationship_options.merge! :class_name => class_name
  end

  if foreign_key = options.delete(:parent_key)
    warn "Not handling parent_key options in belongs_to"
  end

  if inverse_of = options.delete(:inverse_of)
    relationship_options.merge! :inverse_of => inverse_of
  end

  if autosave = options.delete(:autosave)
    relationship_options.merge! :autosave => autosave
  end

  handle_leftover_options(options)
  attr_accessible name
  super name, relationship_options
end
first(*args) click to toggle source
# File lib/data_mapper/shim.rb, line 167
def first(*args)
  if args.empty?
    scoped.first
  else
    where(*args).first
  end
end
get(*args) click to toggle source
# File lib/data_mapper/shim.rb, line 175
def get(*args)
  find_by_id(*args)
end
has(cardinality, name, options = {}) click to toggle source

Relationships

Calls superclass method
# File lib/data_mapper/shim.rb, line 385
def has(cardinality, name, options = {})
  relationship_options = {}

  if class_name = options.delete(:class_name)
    relationship_options.merge! :class_name => class_name
  end

  if foreign_key = options.delete(:child_key)
    relationship_options.merge! :foreign_key => foreign_key
  end

  if key = options.delete(:association_foreign_key)
    relationship_options.merge! :association_foreign_key => key
  end

  if order = options.delete(:order)
    relationship_options.merge! :order => order
  end

  if inverse_of = options.delete(:inverse_of)
    relationship_options.merge! :inverse_of => inverse_of
  end

  if mutable = options.delete(:mutable)
    # no-op, ActiveRecord relationships are mutable by default
  end

 if constraint = options.delete(:constraint)
    # https://github.com/datamapper/dm-constraints for more information on
    # all possible values for :constraint
    case constraint
    when :protect
      relationship_options[:dependent] = :restrict

      # DataMapper returns false if a model can't be destroyed.
      # Here, we capture the exception AR raises if a model
      # can't be destroyed due to an association.
      self.instance_eval do
        define_method(:destroy) do |*args|
          begin
            super(*args)
          rescue ActiveRecord::DeleteRestrictionError => e
            false
          end
        end
      end
    when :destroy
      relationship_options[:dependent] = :destroy
    when :destroy!
      relationship_options[:dependent] = :delete_all
    when :set_nil
      relationship_options[:dependent] = :nullify
    when :skip
      # no-op
    end
  end

  if through = options.delete(:through)
    if through == Resource
      cardinality = -1
    else
      relationship_options.merge! :through => through
    end
  end

  case cardinality
  when -1
    if relationship_options.key?(:dependent)
      case dependent = relationship_options.delete(:dependent)
      when :destroy
        before_destroy do
          self.__send__(name).each do |record|
            record.destroy
          end
        end
      when :restrict
        warn "ActiveRecord's HABTM doesn't support :dependent => :restrict; implement relationship as a has_many :through instead"
      else
        raise "Missing implementation for :#{dependent} constraint on #{name} for #{self}"
      end
    end

    has_and_belongs_to_many name, relationship_options
  when 1
    has_one name, relationship_options
  when n
    has_many name, relationship_options
  end

  attr_accessible name
  handle_leftover_options(options)
end
n() click to toggle source

As in ‘has n, :messages’

# File lib/data_mapper/shim.rb, line 125
def n
  1/0.0
end
properties() click to toggle source
# File lib/data_mapper/shim.rb, line 138
def properties
  @__datamapper_shim_properties ||= Set.new
end
property(name, type, options = {}) click to toggle source

Model definition

# File lib/data_mapper/shim.rb, line 180
def property(name, type, options = {})
  properties << Property.new(name, type)

  case
  when type == Boolean
    # property :trial, Boolean
    #
    # If the :trial column in the DB is created with TINYINT(4),
    # Rails doesn't map it to a boolean type,
    # so we need to coerce it here if necessary.
    define_method name do
      value = read_attribute(name)
      if value.is_a? Integer
        value.zero? ? false : true
      else
        value
      end
    end
  when type.is_a?(Enum)
    warn "Rails 4 handles enums out of the box."

    # Attribute accessors for the property
    self.instance_eval do
      define_method(name) do
        type[read_attribute(name)]
      end

      define_method("#{name}=") do |value|
        value = value.is_a?(Integer) ? value : type[value]
        write_attribute(name, value)
      end
    end

    @__datamapper_shim_enums ||= {}
    @__datamapper_shim_enums[name] = type

    # Substitute enum values in where clauses
    # Note, we don't define this on ActiveRecord::Relation
    def self.where(opts, *rest)
      @__datamapper_shim_enums.each do |name, enum|
        if opts.is_a?(Hash) && opts.has_key?(name) && !opts[name].is_a?(Integer)
          opts[name] = enum[opts[name]]
        end
      end

      super
    end
  when type == Json
    serialize name, JSON
  when type == List
    serialize name, List.new
  when type == ParanoidDateTime
    self.instance_eval do
      define_method :destroy do
        update_attribute(name, Time.zone.try(:now) || Time.now)
      end
    end

    default_scope where(:deleted_at => nil)

    def self.with_deleted
      self.unscoped.where("`#{self.table_name}`.deleted_at IS NOT NULL").scoping do
        yield if block_given?
      end
    end
  when type == Yaml
    serialize name
  end

  if type == DateTime || type == ParanoidDateTime
    # ActiveRecord maps datetime columns to TimeWithZone,
    # which can have some slight inconsistencies with DateTime
    self.instance_eval do
      define_method "#{name}" do
        read_attribute(name).try(:to_datetime)
      end
    end
  end

  if options.has_key?(:default)
    default = options.delete(:default)
    # :if => :class guarantees that the default value is always returned
    attr_default(name, :if => :class, :persisted => false) do |record|
      if record.new_record? && !record.__send__(:"#{name}_changed?")
        default.respond_to?(:call) ? default.call : default
      else
        record.instance_eval do
          read_attribute(name)
        end
      end
    end
  end

  if reader = options.delete(:reader)
    self.instance_eval do
      define_method name do
        read_attribute(name)
      end
    end

    case reader
    when :private
      private name
    when :protected
      protected name
    else
      raise "option :#{reader} for :reader not handled in shim"
    end
  end

  if writer = options.delete(:writer)
    self.instance_eval do
      define_method(:"#{name}=") do |value|
        write_attribute(name, value)
      end
    end

    case writer
    when :protected
      protected :"#{name}="
      attr_protected name
    when :private
      private :"#{name}="
      attr_protected name
    else
      raise "option :#{writer} for :writer not handled in shim"
    end
  else
    attr_accessible name
  end

  # http://rubydoc.info/github/datamapper/dm-core/master/DataMapper/Property
  # No validation necessary here, as these just create a database index.
  options.delete(:index)
  options.delete(:unique_index)

  # DataMapper does some default validation on certain datatypes, e.g.
  # Strings can't have a length longer than 50 chars. ActiveRecord doesn't
  # do any such validations, so we can ignore this option.
  options.delete(:auto_validation)

  # http://stackoverflow.com/questions/95061/stop-activerecord-from-loading-blob-column
  # No obvious way to lazily load an attribute
  options.delete(:lazy)

  # Primary keys

  if options.delete(:key)
    @__datamapper_shim_primary_keys ||= []
    @__datamapper_shim_primary_keys << name

    if @__datamapper_shim_primary_keys.length == 1
      self.primary_key = @__datamapper_shim_primary_keys.first
    else
      self.primary_keys = *@__datamapper_shim_primary_keys
    end

    validates_presence_of name
  end

  # Validations

  validation_options = {}

  if length = options.delete(:length) || options.delete(:size)
    validation_options.merge! :length => { :maximum => length }
  end

  if unique = options.delete(:unique)
    validation_options.merge! :uniqueness => true
  end

  if format = options.delete(:format)
    if format == :email_address
      validation_options.merge! :format => { :with => EmailAddress }
    else
      validation_options.merge! :format => { :with => format }
    end
  end

  # This needs to be checked last due to how
  # :allow_nil works.
  if options.has_key?(:nullable)
    if options.delete(:nullable) == false
      if type == Boolean
        # http://stackoverflow.com/questions/7781174/rspec-validation-failed-attribute-cant-be-blank-but-it-isnt-blank
        validation_options.merge! :inclusion => { :in => [true, false, 0, 1] }
      else
        validation_options.merge! :presence => true
      end
    else
      # Can't add an :allow_nil option unless there's an actual
      # validation happening.
      unless validation_options.empty?
        validation_options.merge! :allow_nil => true
      end
    end
  end

  validates(name, validation_options) unless validation_options.empty?
  handle_leftover_options(options)
end
storage_names() click to toggle source
# File lib/data_mapper/shim.rb, line 142
def storage_names
  storage = Object.new
  storage.instance_variable_set :@model, self

  def storage.[]=(repo, name)
    @model.table_name = name
  end
  storage
end
timestamps(suffix) click to toggle source
# File lib/data_mapper/shim.rb, line 152
def timestamps(suffix)
  # ActiveRecord handles timestamps (created_at, updated_at)
  # automatically if the columns exist.
end
validates_format(name, options = {}) click to toggle source
# File lib/data_mapper/shim.rb, line 657
def validates_format(name, options = {})
  validation_options = {}

  if with = options.delete(:with)
    validation_options.merge! :format => { :with => with }
  end

  validates name, validation_options
  handle_leftover_options(options)
end
validates_is_confirmed(name, options = {}) click to toggle source
# File lib/data_mapper/shim.rb, line 631
def validates_is_confirmed(name, options = {})
  confirmation_options = { }

  if message = options.delete(:message)
    confirmation_options.merge! :message => message
  end

  if condition = options.delete(:if)
    confirmation_options.merge! :if => condition
  end

  validates name, :confirmation => confirmation_options
  validates :"#{name}_confirmation", :presence => true, :if => confirmation_options[:if]

  handle_leftover_options(options)
end
validates_is_unique(name, options = {}) click to toggle source
# File lib/data_mapper/shim.rb, line 585
def validates_is_unique(name, options = {})
  validation_options = { :uniqueness => true }

  if options.delete(:allow_nil)
    validation_options.merge! :allow_nil => true
  end

  if scope = options.delete(:scope)
    validation_options.merge! :uniqueness => { :scope => scope }
  end

  if condition = options.delete(:if)
    validation_options.merge! :if => condition
  end

  validates name, validation_options
  handle_leftover_options(options)
end
validates_length(name, options) click to toggle source
# File lib/data_mapper/shim.rb, line 604
def validates_length(name, options)
  validation_options = {}

  if max = options.delete(:max)
    validation_options.merge! :maximum => max
  end

  if max = options.delete(:maximum)
    validation_options.merge! :maximum => max
  end

  if min = options.delete(:min)
    validation_options.merge! :minimum => min
  end

  if condition = options.delete(:if)
    validation_options.merge! :if => condition
  end

  if message = options.delete(:message)
    validation_options.merge! :message => message
  end

  validates name, :length => validation_options
  handle_leftover_options(options)
end
validates_present(name, options = {}) click to toggle source

Validations

# File lib/data_mapper/shim.rb, line 562
def validates_present(name, options = {})
  validation_options = {}

  if message = options.delete(:message)
    validation_options.merge! :message => message
  end

  if condition = options.delete(:if)
    validation_options.merge! :if => condition
  end

  if on = options.delete(:when)
    if on.length > 1
      raise "Can't handle :when option with more than one value (had #{on})"
    else
      validation_options.merge! :on => on.first
    end
  end

  validates name, :presence => validation_options
  handle_leftover_options(options)
end
validates_with_block(name, &block) click to toggle source
# File lib/data_mapper/shim.rb, line 648
def validates_with_block(name, &block)
  validates_each name do |record, property_name|
    valid, error_message = record.instance_exec(record.__send__(property_name), &block)
    unless valid
      record.errors.add(name, error_message)
    end
  end
end
validates_with_method(name, options) click to toggle source
# File lib/data_mapper/shim.rb, line 679
def validates_with_method(name, options)
  validation_options = {}

  if on = options.delete(:when)
    case on
    when Array
      if on.length > 1
        raise "Can't handle :when option with more than one value (had #{on})"
      else
        validation_options.merge! :on => on.first
      end
    else
      validation_options.merge! :on => on
    end
  end

  method = options.delete(:method)

  # Validation methods made for DM return either true if the model validation passed,
  # or [false, "error message"] if validation failed. Here we add the error message to
  # the model's ActiveModel::Errors object.
  block = proc do
    valid, error_message = self.__send__ method
    unless valid
      self.errors.add(name, error_message)
    end
  end

  validate validation_options, &block
  handle_leftover_options(options)
end
validates_within(name, options = {}) click to toggle source
# File lib/data_mapper/shim.rb, line 668
def validates_within(name, options = {})
  validation_options = {}

  if set = options.delete(:set)
    validation_options.merge! :inclusion => { :in => set }
  end

  validates name, validation_options
  handle_leftover_options(options)
end

Protected Instance Methods

attr_default(sym, *args, &block) click to toggle source

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

# File lib/data_mapper/shim.rb, line 747
      def attr_default(sym, *args, &block)
        options = args.extract_options!
        default = options.delete(:default) || args.shift
        raise 'Default value or block required' unless !default.nil? || block

        evaluator = "__eval_attr_default_for_#{sym}".to_sym
        setter    = "__set_attr_default_for_#{sym}".to_sym
        block   ||= default.is_a?(Proc) ? default : proc { default }

        after_initialize setter.to_sym
        define_method(evaluator, &block)

        module_eval(<<-EVAL, __FILE__, __LINE__ + 1)
          def #{setter}
            #{'return if persisted?' if options[:persisted] == false}
            return unless self.__send__(:#{sym}).__send__(:#{options[:if] || 'nil?'})
            value = #{evaluator}#{'(self)' unless block.arity.zero?}
            self.__send__(:#{sym}=, value.duplicable? ? value.dup : value)
          rescue ActiveModel::MissingAttributeError
          end
        EVAL
        private evaluator, setter
      end
handle_leftover_options(options) click to toggle source
# File lib/data_mapper/shim.rb, line 713
def handle_leftover_options(options)
  return if options.empty?

  method = caller[0][/`.*'/][1..-2]
  message = "DataMapper::Shim##{method} doesn't yet handle #{options.inspect} (#{name})"

  if RAISE_ON_UNHANDLED_OPTIONS
    raise NotImplementedError, message
  else
    warn message
  end
end