Permalink
Browse files

Document and minor refactors

  • Loading branch information...
1 parent ef4ce7b commit 1656190da5abc2d0865d28bfe163ae71ce228554 @oriolgual oriolgual committed May 20, 2012
Showing with 116 additions and 27 deletions.
  1. +2 −1 lib/hypermodel.rb
  2. +42 −12 lib/hypermodel/resource.rb
  3. +12 −0 lib/hypermodel/responder.rb
  4. +60 −14 lib/hypermodel/serializers/mongoid.rb
View
@@ -1,5 +1,6 @@
+# Public: A Hypermodel is a representation of a resource in a JSON-HAL format.
+# To learn more about JSON HAL see http://stateless.co/hal_specification.html
module Hypermodel
end
require 'hypermodel/responder'
-
View
@@ -1,34 +1,64 @@
require 'hypermodel/serializers/mongoid'
-# This class is ORM agnostic. Wohoo!
-# Next step is to select which fields to include in the output.
+
module Hypermodel
+ # Public: Responsible for building the response in JSON-HAL format. It is
+ # meant to be used by Hypermodel::Responder.
+ #
+ # In future versions one will be able to subclass it and personalize a
+ # Resource for each diffent model, i.e. creating a PostResource.
class Resource
- # TODO: Detect resource type (AR, DM, Mongoid, etc..) and create the
- # corresponding serializer.
+ extend Forwardable
+
+ def_delegators :@serializer, :attributes, :record, :resources,
+ :sub_resources, :embedded_resources
+
+ # Public: Initializes a Resource.
+ #
+ # record - A Mongoid instance of a model.
+ # controller - An ActionController instance.
+ #
+ # TODO: Detect record type (ActiveRecord, DataMapper, Mongoid, etc..) and
+ # choose the corresponding serializer.
def initialize(record, controller)
@serializer = Serializers::Mongoid.new(record)
@controller = controller
end
+ # Public: Returns a Hash of the resource in JSON-HAL.
+ #
+ # opts - Options to pass to the resource to_json.
def to_json(*opts)
- @serializer.attributes.update(links).update(embedded).to_json(*opts)
+ attributes.update(links).update(embedded).to_json(*opts)
end
+ # Private: Constructs the _links section of the response.
+ #
+ # Returns: A Hash of the links of the resource. It will include, at least,
+ # a link to itself.
def links
- hash = { self: { href: @controller.polymorphic_url(@serializer.record) } }
- @serializer.resources.each do |name, resource|
- hash.update(name => {href: @controller.polymorphic_url(resource)})
+ _links = { self: polymorphic_url(record) }
+
+ resources.each do |name, resource|
+ _links.update(name => polymorphic_url(resource))
end
- @serializer.sub_resources.each do |sub_resource|
- hash.update(sub_resource => {href: @controller.polymorphic_url([@serializer.record, sub_resource])})
+ sub_resources.each do |sub_resource|
+ _links.update(sub_resource => polymorphic_url([record, sub_resource]))
end
- { _links: hash }
+ { _links: _links }
end
+ # Private: Constructs the _embedded section of the response.
+ #
+ # Returns: A Hash of the embedded resources of the resource.
def embedded
- @serializer.embedded_resources
+ { _embedded: embedded_resources }
+ end
+
+ # Private: Returns the url wrapped in a Hash with in a HAL way.
+ def polymorphic_url(record_or_hash_or_array, options = {})
+ { href: @controller.polymorphic_url(record_or_hash_or_array, options = {}) }
end
end
end
@@ -1,6 +1,18 @@
require 'hypermodel/resource'
module Hypermodel
+ # Public: Responsible for exposing a resource in JSON-HAL format.
+ #
+ # Examples
+ #
+ # class PostsController < ApplicationController
+ # respond_to :json
+ #
+ # def show
+ # @post = Post.find params[:id]
+ # respond_with(@post, responder: Hypermodel::Responder)
+ # end
+ # end
class Responder
def self.call(*args)
controller = args[0]
@@ -1,12 +1,37 @@
module Hypermodel
module Serializers
+ # Private: A Mongoid serializer that complies with the Hypermodel
+ # Serializer API.
+ #
+ # It is used by Hypermodel::Resource to extract the attributes and
+ # resources of a given record.
class Mongoid
- attr_reader :record, :attributes
+
+ # Public: Returns the Mongoid instance
+ attr_reader :record
+
+ # Public: Returns the attributes of the Mongoid instance
+ attr_reader :attributes
+
+ # Public: Initializes a Serializer::Mongoid.
+ #
+ # record - A Mongoid instance of a model.
def initialize(record)
- @record = record
+ @record = record
@attributes = record.attributes.dup
end
+ # Public: Returns a Hash with the resources that are linked to the
+ # record. It will be used by Hypermodel::Resource.
+ #
+ # An example of a linked resource could be the author of a post. Think of
+ # `/authors/:author_id`
+ #
+ # The format of the returned Hash must be the following:
+ #
+ # {resource_name: resource_instance}
+ #
+ # `resource_name` can be either a Symbol or a String.
def resources
relations = select_relations_by_type(::Mongoid::Relations::Referenced::In)
@@ -15,17 +40,41 @@ def resources
end
end
+ # Public: Returns a Hash with the sub resources that are linked to the
+ # record. It will be used by Hypermodel::Resource. These resources need
+ # to be differentiated so Hypermodel::Resource can build the url.
+ #
+ # An example of a linked sub resource could be comments of a post.
+ # Think of `/posts/:id/comments`
+ #
+ # The format of the returned Hash must be the following:
+ #
+ # {:sub_resource, :another_subresource}
def sub_resources
select_relations_by_type(::Mongoid::Relations::Referenced::Many).keys
end
+ # Public: Returns a Hash with the embedded resources attributes. It will
+ # be used by Hypermodel::Resource.
+ #
+ # An example of an embedded resource could be the reviews of a post, or
+ # the addresses of a company. But you can really embed whatever you like.
+ #
+ # An example of the returning Hash could be the following:
+ #
+ # {"comments"=>
+ # [
+ # {"_id"=>"4fb941cb82b4d46162000007", "body"=>"Comment 1"},
+ # {"_id"=>"4fb941cb82b4d46162000008", "body"=>"Comment 2"}
+ # ]
+ # }
def embedded_resources
return {} if embedded_relations.empty?
- embedded_relations.inject({ _embedded: {} }) do |acc, (name, metadata)|
- if attributes = extract_embedded_attributes(name, metadata)
+ embedded_relations.inject({}) do |acc, (name, metadata)|
+ if attributes = extract_embedded_attributes(name)
@attributes.delete(name)
- acc[:_embedded][name] = attributes
+ acc.update(name => attributes)
end
acc
end
@@ -38,16 +87,13 @@ def select_relations_by_type(type)
end
end
- def extract_embedded_attributes(name, metadata)
- relation = metadata.relation
+ def extract_embedded_attributes(name)
+ embedded = @record.send(name)
- if relation == ::Mongoid::Relations::Embedded::Many
- @record.send(name).map { |embedded| embedded.attributes }
- elsif relation == ::Mongoid::Relations::Embedded::One
- (embedded = @record.send(name)) ? embedded.attributes : nil
- else
- raise "Embedded relation type not implemented: #{relation}"
- end
+ return {} unless embedded
+ return embedded.map(&:attributes) if embedded.respond_to?(:map)
+
+ embedded.attributes
end
def embedded_relations

0 comments on commit 1656190

Please sign in to comment.