module Sequel
  module Plugins
    # The TypecastOnLoad plugin exists because most of Sequel's database adapters don't
    # have complete control over typecasting, and may return columns that aren't
    # typecast correctly (with correct being defined as how the model object
    # would typecast the same column values).
    #
    # This plugin modifies Model.load to call the setter methods (which typecast
    # by default) for all columns given.  You can either specify the columns to
    # typecast on load in the plugin call itself, or afterwards using 
    # add_typecast_on_load_columns:
    #
    #   Album.plugin :typecast_on_load, :release_date, :record_date
    #   # or:
    #   Album.plugin :typecast_on_load
    #   Album.add_typecast_on_load_columns :release_date, :record_date
    #
    # If the database returns release_date and record_date columns as strings
    # instead of dates, this will ensure that if you access those columns through
    # the model object, you'll get Date objects instead of strings.
    module TypecastOnLoad
      # Call add_typecast_on_load_columns on the passed column arguments.
      def self.configure(model, *columns, &block)
        model.instance_eval do
          @typecast_on_load_columns ||= []
          add_typecast_on_load_columns(*columns)
        end
      end

      module ClassMethods
        # The columns to typecast on load for this model.
        attr_reader :typecast_on_load_columns

        # Add additional columns to typecast on load for this model.
        def add_typecast_on_load_columns(*columns)
          @typecast_on_load_columns.concat(columns)
        end

        # Give the subclass a copy of the typecast on load columns.
        def inherited(subclass)
          super
          subclass.instance_variable_set(:@typecast_on_load_columns, typecast_on_load_columns.dup)
        end

        # Call the setter method for each of the typecast on load columns,
        # ensuring the model object will have the correct typecasting even
        # if the database doesn't typecast the columns correctly.
        def load(values)
          super.load_typecast
        end
      end

      module InstanceMethods
        def load_typecast
          model.typecast_on_load_columns.each do |c|
            if v = values[c]
              send("#{c}=", v)
            end
          end
          changed_columns.clear
          self
        end

        private

        def _refresh(dataset)
          super.load_typecast
        end
      end
    end
  end
end
