module Submodel::ActiveRecord::ClassMethods

Public Instance Methods

submodel(attr, klass, validation_options = {}, &block) click to toggle source
# File lib/submodel/active_record.rb, line 13
def submodel(attr, klass, validation_options = {}, &block)
  return unless self.table_exists?

  column = columns_hash[attr.to_s]

  augmented_klass = Class.new(klass) do
    define_singleton_method :name do
      klass.name
    end

    define_method :inspect do
      attrs = Submodel.values(self).map { |k,v| "#{k}=#{v.inspect}" }.join(' ').presence
      string = [klass.name, attrs].compact.join(' ')
      "#<#{string}>"
    end
    alias_method :to_s, :inspect

    define_method :blank? do
      Submodel.values(self).blank?
    end

    define_method :== do |other|
      hash = Submodel.values(self)

      if other.is_a? klass
        hash == Submodel.values(other)
      elsif other.is_a? Hash
        hash == other.stringify_keys
      else
        hash == other
      end
    end
  end

  if block_given?
    augmented_klass.class_eval &block
  end

  serialize attr, Module.new {
    define_singleton_method :load do |value|
      if value.is_a? String
        case column.type
        when :hstore
          value = ::ActiveRecord::ConnectionAdapters::PostgreSQLColumn.string_to_hstore(value)
        when :json
          value = JSON.parse(value)
        else
          value = YAML.load(value)
        end
      end
      value.present? ? augmented_klass.new(value) : nil
    end

    define_singleton_method :dump do |object|
      if hash = Submodel.values(object).presence
        case column.type
        when :hstore, :json then hash
        else YAML.dump(hash)
        end
      else
        nil
      end
    end
  }

  # Include as module so we can override accessors and use `super`
  include Module.new {
    define_method attr do
      self[attr] ||= augmented_klass.new
    end

    define_method :"#{attr}=" do |value|
      if value.nil?
        self[attr] = nil
      elsif value.is_a? klass
        self[attr] = value.dup
      else
        self[attr] = augmented_klass.new(value)
      end
    end
    alias_method :"#{attr}_attributes=", :"#{attr}="
  }

  validates_each(attr, validation_options) do |record, attribute, object|
    if object.try(:invalid?)
      record.errors.add(attribute, object.errors.full_messages.to_sentence.downcase)
    end
  end
end