Class: Fopost::Model

Inherits:
Object
  • Object
show all
Defined in:
lib/fopost/model.rb

Overview

Base for every response model.

The API is not consistent about its wire casing — posts come back snake_case, accounts camelCase, workspaces a mix — so every key is normalised to a snake_case symbol before it is read. Unknown keys are kept on #raw rather than dropped, so a server-side addition never breaks a client.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data = {}) ⇒ Model

Returns a new instance of Model.



64
65
66
67
68
69
70
71
72
# File 'lib/fopost/model.rb', line 64

def initialize(data = {})
  @raw = data.is_a?(Hash) ? data : {}
  normalized = self.class.normalize(@raw)
  @attributes = {}
  self.class.attribute_types.each do |name, type|
    @attributes[name] = self.class.coerce(type, normalized[name])
  end
  @normalized = normalized
end

Instance Attribute Details

#rawObject (readonly)

The decoded body exactly as the API sent it.



62
63
64
# File 'lib/fopost/model.rb', line 62

def raw
  @raw
end

Class Method Details

.attribute(name, type = nil) ⇒ Object

type is nil (pass through), :time, :hash, a Model subclass, or a one-element array holding a Model subclass.



21
22
23
24
# File 'lib/fopost/model.rb', line 21

def attribute(name, type = nil)
  attribute_types[name] = type
  define_method(name) { @attributes[name] }
end

.attribute_typesObject



15
16
17
# File 'lib/fopost/model.rb', line 15

def attribute_types
  @attribute_types ||= superclass.respond_to?(:attribute_types) ? superclass.attribute_types.dup : {}
end

.coerce(type, value) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/fopost/model.rb', line 40

def coerce(type, value)
  case type
  when nil then value
  when :time then parse_time(value)
  when :hash then value.is_a?(Hash) ? value : {}
  when Array
    member = type.first
    value.is_a?(Array) ? value.map { |item| member.new(item) } : []
  else
    value.is_a?(Hash) ? type.new(value) : nil
  end
end

.normalize(data) ⇒ Object



34
35
36
37
38
# File 'lib/fopost/model.rb', line 34

def normalize(data)
  return {} unless data.is_a?(Hash)

  data.each_with_object({}) { |(key, value), out| out[snake(key)] = value }
end

.parse_time(value) ⇒ Object



53
54
55
56
57
58
# File 'lib/fopost/model.rb', line 53

def parse_time(value)
  case value
  when Time then value
  when String then Time.iso8601(value) rescue (Time.parse(value) rescue nil)
  end
end

.snake(key) ⇒ Object



26
27
28
29
30
31
32
# File 'lib/fopost/model.rb', line 26

def snake(key)
  key.to_s
     .gsub(/([a-z\d])([A-Z])/, '\1_\2')
     .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
     .downcase
     .to_sym
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?



86
87
88
# File 'lib/fopost/model.rb', line 86

def ==(other)
  other.class == self.class && other.raw == raw
end

#[](key) ⇒ Object

Read a field the SDK does not model yet, by either wire spelling.



75
76
77
78
79
80
# File 'lib/fopost/model.rb', line 75

def [](key)
  name = self.class.snake(key)
  return @attributes[name] if @attributes.key?(name)

  @normalized[name]
end

#hashObject



91
92
93
# File 'lib/fopost/model.rb', line 91

def hash
  [self.class, raw].hash
end

#inspectObject



95
96
97
98
99
# File 'lib/fopost/model.rb', line 95

def inspect
  shown = @attributes.reject { |_, value| value.nil? || value == [] || value == {} }
  fields = shown.map { |name, value| "#{name}=#{value.inspect}" }.join(' ')
  "#<#{self.class.name}#{" #{fields}" unless fields.empty?}>"
end

#to_hObject



82
83
84
# File 'lib/fopost/model.rb', line 82

def to_h
  @attributes.dup
end