fnando/defaults

View on GitHub
lib/defaults.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

module Defaults
  require "defaults/version"

  def self.execute_callable(value, record)
    callable = value.is_a?(Proc) ? value : value.method(:call)
    callable.arity == 1 ? value.call(record) : value.call
  end

  def self.default_value_for_column(model, attribute)
    column_info = model.columns_hash[attribute.to_s]

    return unless column_info

    ActiveRecord::Type.lookup(column_info.type).cast(column_info.default)
  end

  def self.default_value(model, record, attribute)
    value = model.default_values[attribute]
    value = Defaults.execute_callable(value, record) if value.respond_to?(:call)
    value
  end

  def self.resolve_default_value(model, record, attribute)
    attribute = attribute.to_sym

    # Check if value has been defined by `defaults`.
    # If it is, try to resolve by checking if value is callable.
    # Otherwise returns the default value of that column.
    if model.default_values.key?(attribute)
      Defaults.default_value(model, record, attribute)
    else
      Defaults.default_value_for_column(model, attribute)
    end
  end

  def self.included(base)
    base.extend ClassMethods

    class << base
      attr_accessor :default_values
    end
  end

  module ClassMethods
    def defaults(attrs)
      unless attrs.is_a?(Hash)
        raise ArgumentError, "Hash expected; #{attrs.class} given."
      end

      include InstanceMethods

      self.default_values ||= {}
      self.default_values.merge!(attrs)

      after_initialize :set_default_attributes
    end
  end

  module InstanceMethods
    def default_for(name)
      Defaults.resolve_default_value(self.class, self, name)
    end

    private def set_default_attributes
      return unless new_record?

      self.class.default_values.keys.each do |name|
        value = read_attribute(name) if changes[name]
        value = default_for(name) if value.blank?

        public_send "#{name}=", value
      end
    end
  end
end

ActiveRecord::Base.include Defaults