module HasMetadataColumn::ClassMethods

Class methods that are added to your model.

Public Class Methods

define_attribute_methods() click to toggle source
Calls superclass method
# File lib/has_metadata_column.rb, line 136
def define_attribute_methods
  super
  metadata_column_fields.keys.each { |field| define_attribute_method field.to_s }
end
define_method_attribute(attr_name) click to toggle source
Calls superclass method
# File lib/has_metadata_column.rb, line 141
        def define_method_attribute(attr_name)
          return super unless metadata_column_fields.include?(attr_name.to_sym)
          generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
            def __temp__#{attr_name}
              options = self.class.metadata_column_fields[:#{attr_name}] || {}
              default = options.include?(:default) ? options[:default] : nil
              _metadata_hash.include?('#{attr_name}') ? HasMetadataColumn.metadata_typecast(_metadata_hash['#{attr_name}'], options[:type]) : default
            end
          RUBY
        end
define_method_attribute=(attr_name) click to toggle source
Calls superclass method
# File lib/has_metadata_column.rb, line 152
        def define_method_attribute=(attr_name)
          return super unless metadata_column_fields.include?(attr_name.to_sym)
          generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
            def __temp__#{attr_name}=(value)
              attribute_will_change! :#{attr_name}
              old = _metadata_hash['#{attr_name}']
              send (self.class.metadata_column + '='), _metadata_hash.merge('#{attr_name}' => value).to_json
              @_metadata_hash          = nil
              value
            end
          RUBY
        end

Public Instance Methods

has_metadata_column(*args) click to toggle source

Defines a set of fields whose values exist in the JSON metadata column. Each key in the `fields` hash is the name of a metadata field, and the value is a set of options to pass to the `validates` method. If you do not want to perform any validation on a field, simply pass `true` as its key value.

In addition to the normal `validates` keys, you can also include a `:type` key to restrict values to certain classes, or a `:default` key to specify a value to return for the getter should none be set (normal default is `nil`). See {TYPES} for a list of valid values.

@overload has_metadata_column(column, fields)

@param [Symbol] column (:metadata) The column containing the metadata
  information.
@param [Hash<Symbol, Hash>] fields A mapping of field names to their
  validation options (and/or the `:type` key).

@raise [ArgumentError] If invalid arguments are given, or an invalid

class for the `:type` key.

@raise [StandardError] If invalid field names are given (see source).

@example Three metadata fields, one basic, one validated, and one type-checked.

has_metadata_column(optional: true, required: { presence: true }, number: { type: Fixnum })
# File lib/has_metadata_column.rb, line 93
    def has_metadata_column(*args)
      fields = args.extract_options!
      column = args.shift

      raise ArgumentError, "has_metadata_column takes a column name and a hash of fields" unless args.empty?
      raise "Can't define Rails-magic timestamped columns as metadata" if Rails.version >= '3.2.0' && (fields.keys & [:created_at, :created_on, :updated_at, :updated_on]).any?
      classes = fields.values.select { |o| o[:type] && !TYPES.include?(o[:type]) }
      raise ArgumentError, "#{classes.to_sentence} cannot be serialized to JSON" if classes.any?

      if !respond_to?(:metadata_column_fields) then
        class_attribute :metadata_column_fields
        self.metadata_column_fields = fields.deep_clone
        class_attribute :metadata_column
        self.metadata_column = column || :metadata
      else
        raise "Cannot redefine existing metadata column #{self.metadata_column}" if column && column != self.metadata_column
        if metadata_column_fields.slice(*fields.keys) != fields
          raise "Cannot redefine existing metadata fields: #{(fields.keys & self.metadata_column_fields.keys).to_sentence}" unless (fields.keys & self.metadata_column_fields.keys).empty?
          self.metadata_column_fields = self.metadata_column_fields.merge(fields)
        end
      end

      fields.each do |name, options|
        if options.kind_of?(Hash) then
          type          = options.delete(:type)
          type_validate = !options.delete(:skip_type_validation)
          options.delete :default

          attribute name

          validate do |obj|
            value = obj.send(name)
            if !HasMetadataColumn.metadata_typecast(value, type).kind_of?(type) &&
                (!options[:allow_nil] || (options[:allow_nil] && !value.nil?)) &&
                (!options[:allow_blank] || (options[:allow_blank] && !value.blank?))
              errors.add(name, :incorrect_type)
            end
          end if type && type_validate
          validates(name, options) unless options.empty? or (options.keys - [:allow_nil, :allow_blank]).empty?
        end
      end

      class << self
        def define_attribute_methods
          super
          metadata_column_fields.keys.each { |field| define_attribute_method field.to_s }
        end

        def define_method_attribute(attr_name)
          return super unless metadata_column_fields.include?(attr_name.to_sym)
          generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
            def __temp__#{attr_name}
              options = self.class.metadata_column_fields[:#{attr_name}] || {}
              default = options.include?(:default) ? options[:default] : nil
              _metadata_hash.include?('#{attr_name}') ? HasMetadataColumn.metadata_typecast(_metadata_hash['#{attr_name}'], options[:type]) : default
            end
          RUBY
        end

        def define_method_attribute=(attr_name)
          return super unless metadata_column_fields.include?(attr_name.to_sym)
          generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
            def __temp__#{attr_name}=(value)
              attribute_will_change! :#{attr_name}
              old = _metadata_hash['#{attr_name}']
              send (self.class.metadata_column + '='), _metadata_hash.merge('#{attr_name}' => value).to_json
              @_metadata_hash          = nil
              value
            end
          RUBY
        end
      end
    end