class TableSchema::Schema

Attributes

errors[R]

Public

Public Class Methods

new(descriptor, strict: false, case_insensitive_headers: false) click to toggle source
# File lib/tableschema/schema.rb, line 11
def initialize(descriptor, strict: false, case_insensitive_headers: false)
  self.merge! deep_symbolize_keys(parse_schema(descriptor))
  @case_insensitive_headers = case_insensitive_headers
  @strict = strict
  load_fields!
  load_validator!
  expand!
  @strict == true ? validate! : validate
  self
end

Public Instance Methods

add_field(descriptor) click to toggle source
# File lib/tableschema/schema.rb, line 61
def add_field(descriptor)
  self[:fields].push(descriptor)
  validate!
  descriptor
rescue TableSchema::SchemaException => e
  self[:fields].pop
  raise e if @strict
  nil
end
cast_row(row, fail_fast: true) click to toggle source
# File lib/tableschema/schema.rb, line 78
def cast_row(row, fail_fast: true)
  errors = Set.new
  handle_error = lambda { |e| fail_fast == true ? raise(e) : errors << e }
  row = row.fields if row.class == CSV::Row
  if row.count != self.fields.count
    handle_error.call(TableSchema::ConversionError.new("The number of items to convert (#{row.count}) does not match the number of headers in the schema (#{self.fields.count})"))
  end

  self.fields.each_with_index do |field, i|
    begin
      row[i] = field.cast_value(row[i])
    rescue TableSchema::Exception => e
      handle_error.call(e)
    end
  end

  unless errors.empty?
    raise(TableSchema::MultipleInvalid.new("There were errors parsing the data", errors))
  end
  row
end
descriptor() click to toggle source
# File lib/tableschema/schema.rb, line 35
def descriptor
  self.to_h
end
field_names() click to toggle source
# File lib/tableschema/schema.rb, line 51
def field_names
  fields.map { |f| transform(f[:name]) }
rescue NoMethodError
  []
end
Also aliased as: headers
fields() click to toggle source
# File lib/tableschema/schema.rb, line 47
def fields
  self[:fields]
end
foreign_keys() click to toggle source
# File lib/tableschema/schema.rb, line 43
def foreign_keys
  self[:foreignKeys] || []
end
get_constraints(field_name) click to toggle source
# File lib/tableschema/schema.rb, line 117
def get_constraints(field_name)
  get_field(field_name)[:constraints] || {}
end
get_field(field_name) click to toggle source
# File lib/tableschema/schema.rb, line 57
def get_field(field_name)
  fields.find { |f| f[:name] == field_name }
end
get_fields_by_type(type) click to toggle source
# File lib/tableschema/schema.rb, line 135
def get_fields_by_type(type)
  fields.select { |f| f[:type] == type }
end
get_type(field_name) click to toggle source
# File lib/tableschema/schema.rb, line 113
def get_type(field_name)
  get_field(field_name)[:type]
end
has_field?(field_name) click to toggle source
# File lib/tableschema/schema.rb, line 131
def has_field?(field_name)
  get_field(field_name) != nil
end
headers()

Deprecated

Alias for: field_names
missing_values() click to toggle source
# File lib/tableschema/schema.rb, line 109
def missing_values
  self.fetch(:missingValues, TableSchema::DEFAULTS[:missing_values])
end
primary_key() click to toggle source
# File lib/tableschema/schema.rb, line 39
def primary_key
  [self[:primaryKey]].flatten.reject { |k| k.nil? }
end
remove_field(field_name) click to toggle source
# File lib/tableschema/schema.rb, line 71
def remove_field(field_name)
  field = get_field(field_name)
  self[:fields].reject!{ |f| f.name == field_name }
  validate
  field
end
required_headers() click to toggle source
# File lib/tableschema/schema.rb, line 121
def required_headers
  fields.select { |f| f.fetch(:constraints, {}).fetch(:required, nil).to_s == 'true' }
        .map { |f| transform(f[:name]) }
end
save(target) click to toggle source
# File lib/tableschema/schema.rb, line 100
def save(target)
  File.open(target, "w") { |file| file << JSON.pretty_generate(self.descriptor) }
  true
end
unique_headers() click to toggle source
# File lib/tableschema/schema.rb, line 126
def unique_headers
  fields.select { |f| f.fetch(:constraints, {}).fetch(:unique, nil).to_s == 'true' }
        .map { |f| transform(f[:name]) }
end
validate() click to toggle source
# File lib/tableschema/schema.rb, line 22
def validate
  @errors = Set.new(JSON::Validator.fully_validate(@profile, self))
  check_primary_key
  check_foreign_keys
  @errors.empty?
end
validate!() click to toggle source
# File lib/tableschema/schema.rb, line 29
def validate!
  validate
  raise SchemaException.new(@errors.first) unless @errors.empty?
  true
end

Private Instance Methods

add_error(error) click to toggle source
# File lib/tableschema/schema.rb, line 221
def add_error(error)
  @errors << error
end
check_field_value(key, type) click to toggle source
# File lib/tableschema/schema.rb, line 203
def check_field_value(key, type)
  if headers.select { |f| key == f }.count == 0
    add_error("The TableSchema #{type} value `#{key}` is not found in any of the schema's field names")
  end
end
check_foreign_keys() click to toggle source
# File lib/tableschema/schema.rb, line 187
def check_foreign_keys
  return if self[:foreignKeys].nil?
  self[:foreignKeys].each do |key|
    if field_type_mismatch?(key)
      add_error("A TableSchema `foreignKey.fields` value must be the same type as `foreignKey.reference.fields`")
    end
    if field_count_mismatch?(key)
      add_error("A TableSchema `foreignKey.fields` must contain the same number of entries as `foreignKey.reference.fields`")
    end
    foreign_key_fields(key).each { |fk| check_field_value(fk, 'foreignKey.fields') }
    if key.fetch(:reference).fetch(:resource).empty?
      foreign_key_fields(key.fetch(:reference)).each { |fk| check_field_value(fk, 'foreignKey.reference.fields')}
    end
  end
end
check_primary_key() click to toggle source
# File lib/tableschema/schema.rb, line 182
def check_primary_key
  return if self[:primaryKey].nil?
  primary_key.each { |pk| check_field_value(pk, 'primaryKey') }
end
expand!() click to toggle source
# File lib/tableschema/schema.rb, line 166
def expand!
  (self[:fields] || []).each do |f|
    f[:type] = TableSchema::DEFAULTS[:type] if f[:type] == nil
    f[:format] = TableSchema::DEFAULTS[:format] if f[:format] == nil
  end
end
field_count_mismatch?(key) click to toggle source
# File lib/tableschema/schema.rb, line 213
def field_count_mismatch?(key)
  foreign_key_fields(key).count != foreign_key_fields(key.fetch(:reference)).count
end
field_type_mismatch?(key) click to toggle source
# File lib/tableschema/schema.rb, line 217
def field_type_mismatch?(key)
  key.fetch(:fields).class.name != key.fetch(:reference).fetch(:fields).class.name
end
foreign_key_fields(key) click to toggle source
# File lib/tableschema/schema.rb, line 209
def foreign_key_fields(key)
  [key.fetch(:fields)].flatten
end
load_fields!() click to toggle source
# File lib/tableschema/schema.rb, line 173
def load_fields!
  self[:fields] = (self[:fields] || []).map { |f| TableSchema::Field.new(f, missing_values) }
end
load_validator!() click to toggle source
# File lib/tableschema/schema.rb, line 177
def load_validator!
  filepath = File.join(File.dirname(__FILE__), '..', 'profiles', 'table-schema.json')
  @profile ||= JSON.parse(File.read(filepath), symbolize_names: true)
end
parse_schema(descriptor) click to toggle source

Private

# File lib/tableschema/schema.rb, line 143
def parse_schema(descriptor)
  if descriptor.class == Hash
    descriptor
  elsif descriptor.class == String
    begin
      JSON.parse(open(descriptor).read, symbolize_names: true)
    rescue Errno::ENOENT
      raise SchemaException.new("File not found at `#{descriptor}`")
    rescue OpenURI::HTTPError => e
      raise SchemaException.new("URL `#{descriptor}` returned #{e.message}")
    rescue JSON::ParserError
      raise SchemaException.new("File at `#{descriptor}` is not valid JSON")
    end
  else
    raise SchemaException.new("A schema must be a hash, path or URL")
  end
end
transform(name) click to toggle source
# File lib/tableschema/schema.rb, line 161
def transform(name)
  name.downcase! if @case_insensitive_headers == true
  name
end