lib/onsi/resource.rb in onsi-0.3.0 vs lib/onsi/resource.rb in onsi-0.4.0

- old
+ new

@@ -1,48 +1,152 @@ module Onsi class Resource - attr_reader :object, :version + attr_reader :object, :version, :includes + TYPE_KEY = 'type'.freeze + ID_KEY = 'id'.freeze + ATTRIBUTES_KEY = 'attributes'.freeze + RELATIONSHIPS_KEY = 'relationships'.freeze + META_KEY = 'meta'.freeze + DATA_KEY = 'data'.freeze + INCLUDED_KEY = 'included'.freeze + class InvalidResourceError < StandardError; end - def initialize(object, version = Model::DEFAULT_API_VERSION) + class << self + def as_resource(resource, version) + case resource + when Onsi::Resource + resource + when Enumerable + resource.map { |res| as_resource(res, version) } + else + Onsi::Resource.new(resource, version) + end + end + + def render(resource, version) + resources = as_resource(resource, version) + {}.tap do |root| + root[DATA_KEY] = resources.as_json + included = all_included(resources) + if included.any? + root[INCLUDED_KEY] = included + end + end + end + + def all_included(resources) + Array(resources).map(&:flat_includes).flatten.uniq do |res| + "#{res[TYPE_KEY]}-#{res[ID_KEY]}" + end + end + end + + def initialize(object, version = nil, includes: nil) @object = object - @version = version + @version = version || Model::DEFAULT_API_VERSION + @includes = includes validate! end def as_json(_opts = {}) {}.tap do |root| - root['type'] = type - root['id'] = object.id.to_s - root['attributes'] = generate_attributes - rela = generate_relationships - root['relationships'] = rela if rela.any? - meta = generate_metadata - root['meta'] = meta if meta.any? + root[TYPE_KEY] = type + root[ID_KEY] = object.id.to_s + root[ATTRIBUTES_KEY] = generate_attributes + append_relationships(root) + append_meta(root) + append_includes(root) end end + def rendered_includes + @rendered_includes ||= perform_render_includes + end + + def flat_includes + rendered_includes.values.map { |root| root[DATA_KEY] }.flatten + end + private def validate! - return if object.class.included_modules.include?(Onsi::Model) - raise InvalidResourceError, "Trying to render a #{object.class.name}. But it doesn't include Onsi::Model" + unless object.class.included_modules.include?(Onsi::Model) + raise InvalidResourceError, "Trying to render a #{object.class.name}. But it doesn't include Onsi::Model" + end + if includes.present? && !includes.is_a?(Onsi::Includes) + raise InvalidResourceError, "Included resources in #{self} is not a Onsi::Includes" + end end def type object.class.api_renderer(version, for_render: true).type || object.class.name.underscore end + def append_relationships(root) + relationships = generate_relationships + return unless relationships.any? + root[RELATIONSHIPS_KEY] = relationships + end + + def append_meta(root) + meta = generate_metadata + return unless meta.any? + root[META_KEY] = meta + end + + def append_includes(root) + includes = generate_includes + return unless includes.any? + root[RELATIONSHIPS_KEY] ||= {} + root[RELATIONSHIPS_KEY].merge!(includes) + end + def generate_attributes object.class.api_renderer(version, for_render: true).render_attributes(object) end def generate_relationships object.class.api_renderer(version, for_render: true).render_relationships(object) end def generate_metadata object.class.api_renderer(version, for_render: true).render_metadata(object) + end + + def generate_includes + {}.tap do |root| + rendered_includes.each do |key, values| + included = values[DATA_KEY] + case included + when Array + root[key] = {}.tap do |subobj| + subobj[DATA_KEY] = included.map do |inc| + { + TYPE_KEY => inc[TYPE_KEY], + ID_KEY => inc[ID_KEY] + } + end + end + when Hash + root[key] = { DATA_KEY => { TYPE_KEY => included[TYPE_KEY], ID_KEY => included[ID_KEY] } } + end + end + end + end + + def perform_render_includes + included = includes&.load_included + {}.tap do |root| + Hash(included).each do |key, results| + root[key.to_s] = {} + root[key.to_s][DATA_KEY] = render_included(results) + end + end + end + + def render_included(resources) + Resource.as_resource(resources, version).as_json end end end