module Conglomerate module Serializer def self.included(descendant) descendant.extend(ClassMethods) end def initialize(objects, options = {}) self.objects = [*objects].compact self.context = options.fetch(:context, nil) end def serialize { "collection" => actions.inject({}) do |collection, action| send("apply_#{action}", collection) end } end private attr_accessor :objects, :context def actions [:version, :href, :queries, :commands, :items, :template, :links] end def apply_version(collection) collection.merge({"version" => "1.0"}) end def apply_href(collection, options = {}) proc = options.fetch(:proc, self.class._href) object = options.fetch(:object, nil) if proc if object collection.merge({"href" => context.instance_exec(object, &proc)}) else collection.merge({"href" => context.instance_eval(&proc)}) end else collection end end def apply_data(collection, options = {}) data = options.fetch(:data, []) object = options.fetch(:object, nil) default_value = options.fetch(:default_value, nil) build_template = options.fetch(:build_template, false) data = data.map do |datum| name = datum[:name] type = datum.fetch(:type, :value) prompt = datum.fetch(:prompt, nil) value = sanitize_value( object, :name => name, :type => type, :default_value => default_value ) value = format_value(value) {"name" => name.to_s, type.to_s => value}.tap do |d| d["prompt"] = prompt if build_template && prompt end end if data.empty? collection else collection.merge({"data" => data}) end end def apply_queries(collection) queries = self.class._queries.map do |query| build_query(query[:rel], query[:data], query[:block]) end if queries.empty? collection else collection.merge({"queries" => queries}) end end def apply_commands(collection) commands = self.class._commands.map do |command| build_command( command[:rel], command[:data], command[:prompt], command[:block] ) end if commands.empty? collection else collection.merge({"commands" => commands}) end end def apply_items(collection) items = objects.map do |object| item = {} if self.class._item_href item = apply_href( item, :proc => self.class._item_href, :object => object ) end attributes = self.class._attributes item = apply_data(item, :data => attributes, :object => object) links = self.class._attributes .select { |attr| attr[:block] } links += self.class._item_links if links.empty? item.empty? ? nil : item else apply_links(item, :links => links, :object => object) end end if items.compact.empty? collection else collection.merge({"items" => items}) end end def apply_template(collection) attrs = self.class._attributes .select { |attr| attr[:template] } attrs += self.class._templates if attrs.empty? collection else collection.merge( { "template" => apply_data( {}, :data => attrs, :default_value => "", :build_template => true ) } ) end end def apply_links(collection, options = {}) links = options.fetch(:links, self.class._links) object = options.fetch(:object, nil) if object && !links.empty? links = links.map do |link| if !link.has_key?(:name) || present?(object.send(link[:name])) build_item_link( link[:rel], :proc => link[:block], :object => object ) else nil end end.compact.reject { |link| link["href"].nil? } if links.empty? collection else collection.merge({"links" => links}) end elsif !links.empty? collection.merge( { "links" => links.map do |link| { "rel" => link[:rel].to_s, "href" => context.instance_eval(&link[:block]) } end.reject { |link| link["href"].nil? } } ) else collection end end def build_query(rel, data, block) query = {"rel" => rel.to_s} query = apply_href(query, :proc => block) apply_data(query, :data => data, :default_value => "") end def build_command(rel, data, prompt, block) command = {"rel" => rel.to_s} command = apply_href(command, :proc => block) command["prompt"] = prompt if prompt apply_data(command, :data => data, :default_value => "") end def build_item_link(rel, options = {}) proc = options.fetch(:proc, nil) object = options.fetch(:object, nil) link = {"rel" => rel.to_s} apply_href(link, :proc => proc, :object => object) end def sanitize_value(object, options = {}) name = options.fetch(:name) type = options.fetch(:type, :value) default_value = options.fetch(:default_value, nil) if object.nil? || object.send(name).nil? if type == :array [] elsif type == :object {} else default_value end else object.send(name) end end def format_value(value) case value when DateTime value.to_time.utc.iso8601.sub(/\+00:00$/, "Z") when Time value.utc.iso8601.sub(/\+00:00$/, "Z") when Date value.strftime("%Y-%m-%d") else value end end def blank?(value) if value.is_a?(String) value !~ /[^[:space:]]/ else value.respond_to?(:empty?) ? value.empty? : !value end end def present?(value) !blank?(value) end module ClassMethods def href(&block) self._href = block end def item_href(&block) self._item_href = block end def query(rel, options = {}, &block) data = options.fetch(:data, []) data = [*data] data = data.map { |datum| {:name => datum} } self._queries = self._queries << { :rel => rel, :data => data, :block => block } end def command(rel, options = {}, &block) data = options.fetch(:data, []) prompt = options.fetch(:prompt, nil) data = [*data] data = data.map { |datum| {:name => datum} } self._commands = self._commands << { :rel => rel, :data => data, :prompt => prompt, :block => block } end def attribute(name, options = {}, &block) template = options.fetch(:template, false) rel = options.fetch(:rel, nil) type = options.fetch(:type, :value) prompt = options.fetch(:prompt, nil) self._attributes = self._attributes << { :name => name, :template => template, :rel => rel, :type => type, :prompt => prompt, :block => block } end def link(rel, &block) self._links = self._links << { :rel => rel, :block => block } end def item_link(rel, &block) self._item_links = self._item_links << { :rel => rel, :block => block } end def template(name, options = {}) type = options.fetch(:type, :value) prompt = options.fetch(:prompt, nil) self._templates = self._templates << { :name => name, :type => type, :prompt => prompt, :template => true } end attr_writer :_href, :_item_href, :_queries, :_attributes, :_links, :_item_links, :_templates, :_commands def _href @_href || nil end def _item_href @_item_href || nil end def _queries @_queries || [] end def _commands @_commands || [] end def _attributes @_attributes || [] end def _links @_links || [] end def _item_links @_item_links || [] end def _templates @_templates || [] end end end end