Module: HasMetadata::ClassMethods

Defined in:
lib/has_metadata.rb

Overview

Class methods that are added to your model.

Instance Method Summary collapse

Instance Method Details

#has_metadata(fields) ⇒ Object

Defines a set of fields whose values exist in the associated Metadata record. 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`).

Examples:

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

(optional: true, required: { presence: true }, number: { type: Integer })

Parameters:

  • fields (Hash<Symbol, Hash>)

    A mapping of field names to their validation options (and/or the ‘:type` key).



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/has_metadata.rb', line 68

def (fields)
  raise "Can't define Rails-magic timestamped columns as metadata" if (fields.keys & [:created_at, :created_on, :updated_at, :updated_on]).any?

  if !respond_to?(:metadata_fields) then
    belongs_to :metadata, dependent: :destroy
    accepts_nested_attributes_for :metadata
    after_save :save_metadata, if: :metadata_changed?

    class_attribute :metadata_fields
    self. = fields.deep_clone

    define_method(:save_metadata) { .save! }
    define_method(:metadata_changed?) { .try :changed? }

    prepend WithMetadata
  elsif .slice(*fields.keys) != fields
    raise "Cannot redefine existing metadata fields: #{(fields.keys & self..keys).to_sentence}" unless (fields.keys & self..keys).empty?
    self. = self..merge(fields)
  end

  fields.each do |name, options|
    # delegate all attribute methods to the metadata
    attribute_method_matchers.each { |matcher| delegate matcher.method_name(name), to: :metadata! }

    if options.kind_of?(Hash) then
      type          = options.delete(:type)
      type_validate = !options.delete(:skip_type_validation)
      options.delete :default

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