Class: JsonapiMapper::DocumentMapper

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(document, unscoped, rules, renames) ⇒ DocumentMapper

Returns a new instance of DocumentMapper.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/jsonapi_mapper.rb', line 23

def initialize(document, unscoped, rules, renames)
  self.document = document.deep_symbolize_keys
  self.renames = renames.deep_symbolize_keys
  self.unscoped = unscoped.map(&:to_sym)
  self.resources = {}
  setup_types(rules)
  
  main = if data = self.document[:data]
    if data.is_a?(Array)
      data.map{|r| build_resource(r) }.compact.collect(&:object)
    else
      build_resource(data).try(:object)
    end
  end

  rest = if included = self.document[:included]
    included.map{|r| build_resource(r) }.compact
  end

  resources.each{|_,r| assign_relationships(r) }

  self.data = main
  self.included = rest.try(:map, &:object) || []
end

Instance Attribute Details

#dataObject

Returns the value of attribute data.



20
21
22
# File 'lib/jsonapi_mapper.rb', line 20

def data
  @data
end

#documentObject

Returns the value of attribute document.



20
21
22
# File 'lib/jsonapi_mapper.rb', line 20

def document
  @document
end

#includedObject

Returns the value of attribute included.



20
21
22
# File 'lib/jsonapi_mapper.rb', line 20

def included
  @included
end

#renamesObject

Returns the value of attribute renames.



20
21
22
# File 'lib/jsonapi_mapper.rb', line 20

def renames
  @renames
end

#resourcesObject

Returns the value of attribute resources.



20
21
22
# File 'lib/jsonapi_mapper.rb', line 20

def resources
  @resources
end

#typesObject

Returns the value of attribute types.



20
21
22
# File 'lib/jsonapi_mapper.rb', line 20

def types
  @types
end

#unscopedObject

Returns the value of attribute unscoped.



20
21
22
# File 'lib/jsonapi_mapper.rb', line 20

def unscoped
  @unscoped
end

Instance Method Details

#allObject



170
171
172
# File 'lib/jsonapi_mapper.rb', line 170

def all
  (data_mappable + included)
end

#all_errorsObject



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/jsonapi_mapper.rb', line 203

def all_errors
  errors = []

  if collection?
    data.each_with_index do |resource, i|
      errors << serialize_errors_for("/data/#{i}", resource)
    end
  elsif data
    errors << serialize_errors_for("/data", data)
  end
  
  included.each_with_index do |resource, i|
    errors << serialize_errors_for("/included/#{i}", resource)
  end

  { errors: errors.flatten.compact }
end

#all_valid?Boolean

Returns:

  • (Boolean)


179
180
181
# File 'lib/jsonapi_mapper.rb', line 179

def all_valid?
  all.map(&:valid?).all? # This does not short-circuit, to get all errors.
end

#assign_relationships(resource) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/jsonapi_mapper.rb', line 128

def assign_relationships(resource)
  resource.relationships.each do |name, ids|
    if ids.is_a?(Array)
      ids.each do |id|
        next unless other = find_resource_object(id)
        resource.object.send(name).push(other)
      end
    else
      next unless other = find_resource_object(ids)
      resource.object.send("#{name}=", other)
    end
  end
end

#build_id(json) ⇒ Object



124
125
126
# File 'lib/jsonapi_mapper.rb', line 124

def build_id(json)
  Id.new(json[:type].to_sym, json[:id])
end

#build_resource(json) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/jsonapi_mapper.rb', line 86

def build_resource(json)
  return unless json.is_a? Hash
  return unless json.fetch(:relationships, {}).is_a?(Hash)
  return unless json.fetch(:attributes, {}).is_a?(Hash)
  return unless type = types[json[:type].try(:to_sym)]

  object = if json[:id].nil? || json[:id].to_s.starts_with?("@")
    type.class.new.tap do |o|
      type.rule.scope.each do |k,v|
        o.send("#{k}=", v)
      end
    end
  else
    type.class.where(type.rule.scope).find(json[:id])
  end

  relationships = {}
  json.fetch(:relationships, {}).each do |name, value|
    next unless type.rule.attributes.include?(name)
    next if value[:data].blank?
    relationships[renamed_attr(type.name, name)] = if value[:data].is_a?(Array)
      value[:data].map{|v| build_id(v) } 
    else
      build_id(value[:data])
    end
  end

  if new_values = json[:attributes]
    type.rule.attributes.each do |name|
      next unless new_values.has_key?(name)
      object.send("#{renamed_attr(type.name, name)}=", new_values[name]) 
    end
  end

  resource = Resource.new(object, relationships, build_id(json))
  resources[resource.id] = resource
end

#collection?Boolean

Returns:

  • (Boolean)


183
184
185
# File 'lib/jsonapi_mapper.rb', line 183

def collection?
  data.is_a?(Array)
end

#data_mappableObject



199
200
201
# File 'lib/jsonapi_mapper.rb', line 199

def data_mappable
  collection? ? data : [data].compact
end

#find_resource_object(id) ⇒ Object



142
143
144
145
146
147
148
149
# File 'lib/jsonapi_mapper.rb', line 142

def find_resource_object(id)
  return unless type = types[id.type]

  resources[id].try(:object) ||
    type.class.where(type.rule.scope).find(id.raw) or
    raise ActiveRecord::RecordNotFound
      .new("Couldn't find #{id.type} with id=#{id.raw}")
end

#map_all(cls, &blk) ⇒ Object



195
196
197
# File 'lib/jsonapi_mapper.rb', line 195

def map_all(cls, &blk)
  all.select{|o| o.is_a?(cls)}.map(&blk)
end

#map_data(cls, &blk) ⇒ Object



191
192
193
# File 'lib/jsonapi_mapper.rb', line 191

def map_data(cls, &blk)
  data_mappable.select{|o| o.is_a?(cls)}.map(&blk)
end

#renamed_attr(type, attr) ⇒ Object



161
162
163
# File 'lib/jsonapi_mapper.rb', line 161

def renamed_attr(type, attr)
  renames.fetch(:attributes, {}).fetch(type, {}).fetch(attr, attr)
end

#renamed_type(type_name) ⇒ Object



151
152
153
154
# File 'lib/jsonapi_mapper.rb', line 151

def renamed_type(type_name)
  renames.fetch(:types, {})[type_name] ||
    type_name.to_s.singularize.camelize.constantize
end

#save_allObject



174
175
176
177
# File 'lib/jsonapi_mapper.rb', line 174

def save_all
  return false unless all.all?(&:valid?)
  all.collect(&:save).all?
end

#setup_types(rules) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/jsonapi_mapper.rb', line 48

def setup_types(rules)
  self.types = {}
  rules.each do |type_name, ruleset|
    type_name = type_name.to_sym

    attrs, scope = if ruleset.last.is_a?(Hash)
      [ruleset[0..-2], ruleset.last]
    else
      unless unscoped.map(&:to_sym).include?(type_name)
        raise RulesError.new("Missing Scope for #{type_name}")
      end
      [ruleset, {}]
    end

    unless attrs.all?{|v| v.is_a?(Symbol) || v.is_a?(String) } 
      raise RulesError.new('Attributes must be Strings or Symbols')
    end

    attrs = attrs.map(&:to_sym)
    scope.symbolize_keys!

    danger = scope.keys.to_set & attrs.map{|a| renamed_attr(type_name, a) }.to_set
    if danger.count > 0
      raise RulesError.new("Don't let user set the scope: #{danger.to_a}")
    end

    cls = renamed_type(type_name)

    attrs.map{|a| renamed_attr(type_name, a) }.each do |attr|
      unless cls.new.respond_to?(attr)
        raise NoMethodError.new("undefined method #{attr} for #{cls}")
      end
    end

    types[type_name] = Type.new(type_name, cls, Rule.new(attrs, scope))
  end
end

#single?Boolean

Returns:

  • (Boolean)


187
188
189
# File 'lib/jsonapi_mapper.rb', line 187

def single?
  !collection?
end

#unrenamed_attr(type_name, attr) ⇒ Object



165
166
167
168
# File 'lib/jsonapi_mapper.rb', line 165

def unrenamed_attr(type_name, attr)
  renames.fetch(:attributes, {}).fetch(type_name, {})
    .find{|k,v| v == attr }.try(:first) || attr
end

#unrenamed_type(type) ⇒ Object



156
157
158
159
# File 'lib/jsonapi_mapper.rb', line 156

def unrenamed_type(type)
  type_name = type.to_s.underscore.pluralize
  renames.fetch(:types, {}).find{|k,v| v == type }.try(:first) || type_name
end