module DataMapper::Shim::ClassMethods
Constants
- EmailAddress
RFC2822 (No attribution reference available)
Public Class Methods
Substitute enum values in where clauses Note, we don’t define this on ActiveRecord::Relation
# 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
# 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
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
Finders
# File lib/data_mapper/shim.rb, line 159 def all(*args) if args.empty? scoped else where(*args) end end
# 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
# 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
# File lib/data_mapper/shim.rb, line 167 def first(*args) if args.empty? scoped.first else where(*args).first end end
# File lib/data_mapper/shim.rb, line 175 def get(*args) find_by_id(*args) end
Relationships
# 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
As in ‘has n, :messages’
# File lib/data_mapper/shim.rb, line 125 def n 1/0.0 end
# File lib/data_mapper/shim.rb, line 138 def properties @__datamapper_shim_properties ||= Set.new end
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
# 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
# File lib/data_mapper/shim.rb, line 152 def timestamps(suffix) # ActiveRecord handles timestamps (created_at, updated_at) # automatically if the columns exist. end
# 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
# 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
# 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
# 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
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
# 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
# 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
# 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
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
# 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