module Frenchy::Model::ClassMethods

Public Instance Methods

defaults() click to toggle source
# File lib/frenchy/model.rb, line 58
def defaults; @defaults; end
defaults=(value) click to toggle source
# File lib/frenchy/model.rb, line 60
def defaults=(value); @defaults = value; end
fields() click to toggle source

Class accessors

# File lib/frenchy/model.rb, line 57
def fields; @fields; end
fields=(value) click to toggle source
# File lib/frenchy/model.rb, line 59
def fields=(value); @fields = value; end

Protected Instance Methods

embed(name, options={}, &block) click to toggle source

Macro to create a subtype and associated field

# File lib/frenchy/model.rb, line 90
def embed(name, options={}, &block)
  type(name, &block)
  field(name, options.merge({type: name}))
end
enum(name, &block) click to toggle source

Macro to add an enum type

# File lib/frenchy/model.rb, line 81
def enum(name, &block)
  klass = Class.new(self) do
    include Frenchy::Enum
  end
  const_set(name.to_s.camelize, klass)
  klass.class_eval(&block)
end
field(name, options={}) click to toggle source

Macro to add a field

# File lib/frenchy/model.rb, line 96
def field(name, options={})
  name = name.to_s
  options.stringify_keys!

  if options["enum"]
    options["type"] = "enum"
    options["class_name"] ||= options["enum"].to_s.camelize
  end

  type = (options["type"] || "string").to_s
  aliases = (options["aliases"] || [])

  aliases.each do |a|
    define_method("#{a}") do
      send(name)
    end
  end

  case type
  when "string"
    # Convert value to a String.
    define_method("#{name}=") do |v|
      set(name, String(v))
    end

  when "integer"
    # Convert value to an Integer.
    define_method("#{name}=") do |v|
      set(name, Integer(v))
    end

  when "float"
    # Convert value to a Float.
    define_method("#{name}=") do |v|
      set(name, Float(v))
    end

  when "bool"
    # Accept truthy values as true.
    define_method("#{name}=") do |v|
      set(name, ["true", "1", 1, true].include?(v))
    end

    # Alias a predicate method.
    define_method("#{name}?") do
      send(name)
    end

  when "time"
    # Convert value to a Time or DateTime. Numbers are treated as unix timestamps,
    # other values are parsed with DateTime.parse.
    define_method("#{name}=") do |v|
      if v.is_a?(Integer)
        set(name, Time.at(v).to_datetime)
      elsif v.is_a?(DateTime)
        set(name, v)
      elsif v.is_a?(Time)
        set(name, v.to_datetime)
      else
        set(name, DateTime.parse(v))
      end
    end

  when "array"
    # Arrays always have a default of []
    options["default"] ||= []

    # Convert value to an Array.
    define_method("#{name}=") do |v|
      set(name, Array(v))
    end

  when "hash"
    # Hashes always have a default of {}
    options["default"] ||= {}

    # Convert value to a Hash
    define_method("#{name}=") do |v|
      set(name, Hash[v])
    end

  when "enum"
    # Resolve the class name
    klass = const_get(options["class_name"])

    # Convert value to enum class
    define_method("#{name}=") do |v|
      set(name, klass.find(v.to_i))
    end

  else
    # Unknown types have their type constantized and initialized with the value. This
    # allows us to support things like other Frenchy::Model classes, ActiveRecord models, etc.
    klass = const_get(options["class_name"] || type.camelize)

    # Fields with many values have a default of [] (unless previously set above)
    if options["many"]
      options["default"] ||= []
    end

    # Convert value using the constantized class. Fields with many values are mapped to a
    # Frenchy::Collection containing mapped values.
    define_method("#{name}=") do |v|
      if options["many"]
        set(name, Frenchy::Collection.new(Array(v).map {|vv| klass.new(vv)}))
      else
        if v.is_a?(Hash)
          set(name, klass.new(v))
        else
          set(name, v)
        end
      end
    end
  end

  # Store a reference to the field
  self.fields[name] = options

  # Store a default value if present
  if options["default"]
    self.defaults[name] = options["default"]
  end

  # Create an accessor for the field
  attr_reader name
end
key(name) click to toggle source

Macro to add primary key

# File lib/frenchy/model.rb, line 65
def key(name)
  define_method(:to_param) do
    send(name).to_s
  end
end
type(name, &block) click to toggle source

Macro to create a subtype

# File lib/frenchy/model.rb, line 72
def type(name, &block)
  klass = Class.new(self) do
    include Frenchy::Model
  end
  const_set(name.to_s.camelize, klass)
  klass.class_eval(&block)
end