module Datoki

Constants

Actions
Char_Types
Invalid
Key_Not_Found
Numeric_Types
Schema_Conflict
Types
UTC_NOW
UTC_NOW_DATE
UTC_NOW_RAW

Attributes

error[R]
Instance Methods ===============
raw[R]
Instance Methods ===============

Public Class Methods

db(db = :return) click to toggle source
# File lib/datoki.rb, line 29
def db db = :return
  return @db if db == :return
  @db = db
  @tables = @db.tables
end
db_type_to_ruby(type, alt = nil) click to toggle source
# File lib/datoki.rb, line 35
def db_type_to_ruby type, alt = nil
  if Datoki::Types.include?( type.to_sym )
    type.to_sym
  elsif type['character varying']
    :varchar
  elsif Datoki::Types.include?(alt)
    alt
  else
    fail("Unknown db type: #{type.inspect}")
  end
end
included(klass) click to toggle source
# File lib/datoki.rb, line 24
def included klass
  klass.extend Def_Field
  klass.initialize_def_field
end
new(unknown = nil) click to toggle source
# File lib/datoki.rb, line 507
def initialize unknown = nil
  @data       = nil
  @field_name = nil
  @clean      = nil
  @error      = nil
  @skips      = {}
  @db_ops     = {} # Ex: :db_insert=>true, :db_update=>true

  if unknown
    is_record = unknown.keys.all? { |f|
      self.class.fields.has_key?(f) ||
        (self.class.schema && self.class.schema.has_key?(f)) 
    }

    if is_record
      @data = unknown
      @data.default_proc = Key_Not_Found
    else
      @raw = unknown
    end

  end

  if @raw

    schema = self.class.schema

    case
    when create?
      create
    when update?
      clean primary_key[:name]
      update
    when delete?
      delete
    end

    if @clean
      @clean.each { |k, v|
        # === Delete nil value if schema has a default value:
        @clean.delete(k) if @clean[k].nil? && schema[k] && schema[k][:default]
      }
    end

    fail "No clean values found." if (!@clean || @clean.empty?)

    if !@skips[:db] && !self.class.schema.empty?

      begin
        case

        when create?
          db_insert

        when update?
          db_update

        when delete?
          final = db_clean
          DB[self.class.table].
            where(primary_key[:name] => final.delete(primary_key[:name])).
            delete

        end

      rescue Sequel::UniqueConstraintViolation => e

        self.class.fields.each { |f, meta|
          if meta[:unique_index] && e.message[%^unique constraint "#{meta[:unique_index]}"^]
            field_name f
            fail! :unique, "{{English name}} already taken: #{meta[:name]}"
          end
        }
        raise e

      end # === begin/rescue
    end # === if !@skips[:db]
  end # === if @raw
end

Public Instance Methods

TABLE() click to toggle source
# File lib/datoki.rb, line 891
def TABLE
  self.class::TABLE
end
clean(*args) click to toggle source
# File lib/datoki.rb, line 617
def clean *args
  @clean ||= {}

  return @clean if args.empty?

  # === Handle required fields:
  # Example:
  #   :name!, :age!
  if args.size > 1
    return args.each { |f| clean f }
  end

  name = args.first

  if (real_name = self.class.fields_as_required[name])
    return(clean! real_name) 
  end

  @clean[name] = @raw[name] if !clean.has_key?(name) && @raw.has_key?(name)

  # === Skip cleaning if key is not set:
  return nil unless @clean.has_key?(name)

  field_name(name)
  f_meta = self.class.fields[name]

  # === Strip the value:
  if clean[name].is_a?(String) && field[:allow][:strip]
    clean[name].strip!
  end

  if field?(:chars) && !field.has_key?(:min) && clean[name].is_a?(String) && field[:allow][:null]
    clean[name] = nil
  end

  if field?(:numeric) && clean[name].is_a?(String)
    clean_val = Integer(clean[name]) rescue String
    if clean_val == String
      fail! :wrong_type, "{{English name}} must be numeric."
    else
      clean[name] = clean_val
    end
  end

  if field?(:text) && clean[name].is_a?(String) && clean[name].empty? && field[:min].to_i > 0
    fail! :required, "{{English name}} is required."
  end
  # ================================

  # === check min, max ======
  if clean[name].is_a?(String) || clean[name].is_a?(Numeric)
    case [field[:min], field[:max]].map(&:class)

    when [NilClass, NilClass]
      # do nothing

    when [NilClass, Fixnum]
      case
      when clean[name].is_a?(String) && clean[name].size > field[:max]
        fail! :big, "{{English name}} can't be longer than {{max}} characters."
      when clean[name].is_a?(Numeric) && clean[name] > field[:max]
        fail! :big, "{{English name}} can't be higher than {{max}}."
      end

    when [Fixnum, NilClass]
      case
      when clean[name].is_a?(String) && clean[name].size < field[:min]
        fail! :short, "{{English name}} can't be shorter than {{min}} characters."
      when clean[name].is_a?(Numeric) && clean[name] < field[:min]
        fail! :short, "{{English name}} can't be less than {{min}."
      end

    when [Fixnum, Fixnum]
      case
      when field?(:chars) && clean[name].size > field[:max]
        fail! :big, "{{English name}} must be between {{min}} and {{max}} characters."
      when field?(:chars) && clean[name].size < field[:min]
        fail! :small, "{{English name}} must be between {{min}} and {{max}} characters."

      when field?(:numeric) && clean[name] > field[:max]
        fail! :big, "{{English name}} must be between {{min}} and {{max}}."
      when field?(:numeric) && clean[name] < field[:min]
        fail! :small, "{{English name}} must be between {{min}} and {{max}}."
      end

    else
      fail "Unknown values for :min, :max: #{field[:min].inspect}, #{field[:max].inspect}"
    end
  end # === if
  # ================================

  # === to_i if necessary ==========
  if field?(:numeric)
    if clean[name].nil? && !field[:allow][:null]
      clean[name] = clean[name].to_i
    end
  end
  # ================================

  # === :strip if necessary ========
  if field?(:chars) && field[:allow][:strip] && clean[name].is_a?(String)
    clean[name] = clean[name].strip
  end
  # ================================

  # === Is value in options? =======
  if field[:options]
    if !field[:options].include?(clean[name])
      fail! :mis_match, "{{English name}} can only be: #{field[:options].map(&:inspect).join ', '}"
    end
  end
  # ================================

  field[:cleaners].each { |cleaner, args|
    next if args === false # === cleaner has been disabled.

      case cleaner

      when :type
        case
        when field?(:numeric) && !clean[name].is_a?(Integer)
          fail! :wrong_type, "{{English name}} needs to be an integer."
        when field?(:chars) && !clean[name].is_a?(String)
          fail! :wrong_type, "{{English name}} needs to be a String."
        end

      when :exact_size
        if clean[name].size != field[:exact_size]
          case
          when field?(:chars) || clean[name].is_a?(String)
            fail! :mis_match, "{{English name}} needs to be {{exact_size}} in length."
          else
            fail! :mis_match, "{{English name}} can only be {{exact_size}} in size."
          end
        end

      when :set_to
        args.each { |meth|
          clean[name] = (meth.is_a?(Symbol) ? send(meth) : meth.call(self, clean[name]))
        }

      when :equal_to
        args.each { |pair|
          meth, msg, other = pair
          target = send(meth)
          fail!(msg || "{{English name}} must be equal to: #{target.inspect}") unless clean[name] == target
        }

      when :included_in
        arr, msg, other = args
        fail!(msg || "{{English name}} must be one of these: #{arr.join ', '}") unless arr.include?(clean[name])

      when :upcase
        clean[name] = clean[name].upcase

      when :match
        args.each { |regex|
          case regex
          when Regexp
            if clean[name] !~ regex
              fail!(:mis_match, "{{English name}} is invalid.")
            end

          when Proc
            if !regex.call(self, clean[name])
              fail!(:mis_match, "{{English name}} is invalid.")
            end

          else
            fail ArgumentError, "Unknown matcher: #{regex.inspect}"
          end
        }

      else
        fail "Cleaner not implemented: #{cleaner.inspect}"
      end # === case cleaner
  } # === field[:cleaners].each
end
clean!(*args) click to toggle source
# File lib/datoki.rb, line 607
def clean! *args
  args.each { |name|
    if @raw[name].nil? && (!@clean || @clean[name].nil?)
      fail ArgumentError, "#{name.inspect} is not set."
    else
      clean name
    end
  }
end
create?() click to toggle source
# File lib/datoki.rb, line 875
def create?
  !!(@raw.has_key?(:create) && @raw[:create])
end
data() click to toggle source
# File lib/datoki.rb, line 587
def data
  fail "Data not set." unless @data
  @data
end
db_clean() click to toggle source
# File lib/datoki.rb, line 600
def db_clean
  @clean.select { |k, v|
    meta = self.class.fields[k]
    !meta || !meta[:pseudo]
  }
end
db_insert() click to toggle source
# File lib/datoki.rb, line 899
def db_insert
  k = :db_insert
  fail "Already inserted." if @db_ops[k]

  final = db_clean
  new_data = TABLE().returning(*returning_fields).insert(final).first
  @data = (@data || {}).merge(new_data)
  @db_ops[k] = true
end
db_update() click to toggle source
# File lib/datoki.rb, line 909
def db_update
  k = :db_update
  fail "Already updated" if @db_ops[k]

  final = db_clean
  new_data = TABLE().
    returning(*returning_fields).
    where(primary_key[:name] => final.delete(primary_key[:name])).
    update(final).
    first

  @data = (@data || {}).merge(new_data)
  @db_ops[:db_update]  = true
end
delete?() click to toggle source
# File lib/datoki.rb, line 887
def delete?
  !!(@raw.has_key?(:delete) && !@raw[:delete])
end
error?() click to toggle source
# File lib/datoki.rb, line 596
def error?
  @error && !@error.empty?
end
error_msg(type) click to toggle source
# File lib/datoki.rb, line 796
def error_msg type
  field[:error_msgs] && field[:error_msgs][type]
end
fail!(*args) click to toggle source
# File lib/datoki.rb, line 800
def fail! *args
  case args.size
  when 1
    msg = args.shift
  when 2
    msg = error_msg(args.shift) || args.shift
  else
    fail ArgumentError, "Unknown args: #{args.inspect}"
  end

  err_msg = msg.gsub(/\{\{([a-z\_\-\ ]+)\}\}/i) { |raw|
    name = $1
    case name
    when "English name"
      self.class.fields[field_name][:english_name].capitalize.gsub('_', ' ')
    when "ENGLISH NAME"
      self.class.fields[field_name][:english_name].upcase.gsub('_', ' ')
    when "max", "min", "exact_size"
      self.class.fields[field_name][name.downcase.to_sym]
    when "val"
      clean[field_name]
    else
      fail "Unknown value: #{name}"
    end
  }

  @error = {:field_name=>field_name, :msg=>err_msg, :value=>clean[field_name]}
  throw :invalid, self
end
field(*args) click to toggle source
# File lib/datoki.rb, line 843
def field *args
  case args.size
  when 0
    self.class.fields[field_name]
  when 1
    self.class.fields[args.first]
  else
    fail "Unknown args: #{args.inspect}"
  end
end
field?(*args) click to toggle source
# File lib/datoki.rb, line 854
def field? *args
  self.class.inspect_field? :type, field_name, *args
end
field_name(*args) click to toggle source
# File lib/datoki.rb, line 830
def field_name *args
  case args.size
  when 0
    fail "Field name not set." unless @field_name
    @field_name
  when 1
    fail ArgumentError, "Unknown field: #{args.first.inspect}" unless self.class.fields[args.first]
    @field_name = args.first
  else
    fail "Unknown args: #{args.inspect}"
  end
end
id() click to toggle source
# File lib/datoki.rb, line 858
def id
  d = data
  pk = primary_key[:name]
  fail "No primary key set yet." unless d.has_key?(pk)
  d[pk]
end
new?() click to toggle source
# File lib/datoki.rb, line 871
def new?
  !@data
end
primary_key() click to toggle source
# File lib/datoki.rb, line 865
def primary_key
  name, meta = self.class.fields.detect { |k, v| v[:primary_key] }
  fail "Primary key not found." unless meta
  meta
end
read?() click to toggle source
# File lib/datoki.rb, line 879
def read?
  !!(@raw.has_key?(:read) && @raw[:read])
end
returning_fields() click to toggle source
# File lib/datoki.rb, line 895
def returning_fields
  self.class.returning_fields
end
skip(name) click to toggle source
# File lib/datoki.rb, line 592
def skip name
  @skips[name] = true
end
update?() click to toggle source
# File lib/datoki.rb, line 883
def update?
  !!(@raw.has_key?(:update) && @raw[:update])
end