acts_as_having_settings.rb 2.17 KB
module ActsAsHavingSettings

  module ClassMethods
    def acts_as_having_settings(*args)
      options = args.last.is_a?(Hash) ? args.pop : {}
      
      settings_field = options[:field] || 'settings'

      class_eval <<-CODE
        serialize :#{settings_field}, Hash
        def self.settings_field
          #{settings_field.inspect}
        end
        def #{settings_field}
          self[:#{settings_field}] ||= Hash.new
        end

        def setting_changed?(setting_field)
          setting_field = setting_field.to_sym
          changed_settings = self.changes['#{settings_field}']
          return false if changed_settings.nil?

          old_setting_value = changed_settings.first.nil? ? nil : changed_settings.first[setting_field]
          new_setting_value = changed_settings.last[setting_field]
          old_setting_value != new_setting_value
        end

        before_save :symbolize_settings_keys
        private
        def symbolize_settings_keys
          self[:#{settings_field}] && self[:#{settings_field}].symbolize_keys!
        end
      CODE
      settings_items(*args)
    end

    def settings_items(*names)

      options = names.last.is_a?(Hash) ? names.pop : {}
      default = (!options[:default].nil?) ? options[:default].inspect : "val"
      data_type = options[:type] || :string

      names.each do |setting|
        class_eval <<-CODE
          def #{setting}
            val = send(self.class.settings_field)[:#{setting}]
            val.nil? ? (#{default}.is_a?(String) ? gettext(#{default}) : #{default}) : val
          end
          def #{setting}=(value)
            h = send(self.class.settings_field).clone
            h[:#{setting}] = self.class.acts_as_having_settings_type_cast(value, #{data_type.inspect})
            send(self.class.settings_field.to_s + '=', h)
          end
        CODE
      end
    end

    def acts_as_having_settings_type_cast(value, type)
      # FIXME creating a new instance at every call, will the garbage collector
      # be able to cope with it?
      ActiveRecord::ConnectionAdapters::Column.new(:dummy, nil, type.to_s).type_cast(value)
    end

  end

end

ActiveRecord::Base.send(:extend, ActsAsHavingSettings::ClassMethods)