Permalink
Browse files

Merge pull request #2 from codegram/badly_implemented_empty_collections

Handle empty collections gracefully (ACHTUNG: WIP WIP WIP WIP WIP)
  • Loading branch information...
txus committed May 28, 2012
2 parents d7d807f + 3c9d288 commit 251e383e6f85f5c555529a2bc888ec09065d68cf
Showing with 101 additions and 5 deletions.
  1. +78 −0 lib/hypermodel/empty_collection.rb
  2. +11 −5 lib/hypermodel/responder.rb
  3. +12 −0 test/collection_test.rb
@@ -0,0 +1,78 @@
+require 'hypermodel/resource'
+
+module Hypermodel
+ # Public: Represents an empty Collection.
+ #
+ # FIXME: LET THIS BURN IN HELL FOR ALL ETERNITY
+ class EmptyCollection
+ # Public: Initializes a Collection.
+ #
+ # collection - An Array of Mongoid documents.
+ # controller - An ActionController instance.
+ #
+ # Returns nothing.
+ def initialize(resource_name, controller)
+ @name = resource_name
+ @controller = controller
+ end
+
+ # Public: Serialize the whole representation as JSON.
+ #
+ # Returns a String with the serialization.
+ def as_json(*opts)
+ links.update(embedded).as_json(*opts)
+ end
+
+ # Internal: Constructs the _links section of the response.
+ #
+ # Returns a Hash of the links of the collection. It will include, at least,
+ # a link to itself.
+ #
+ # TODO: Discover parents and include them in _links.
+ def links
+ _links = {
+ self: { href: @controller.polymorphic_url(collection_hierarchy) }
+ }
+
+ { _links: _links }
+ end
+
+ def embedded
+ { _embedded: { @name => [] } }
+ end
+
+ #######
+ private
+ #######
+
+ def collection_hierarchy
+ params = @controller.params.select { |k, v| k =~ /_id/ }
+
+ traverse = lambda do |name|
+ klass = self.class.const_get(name.singularize.capitalize)
+ assoc = klass.associations.detect do |name, metadata|
+ types = [Mongoid::Relations::Embedded::In, Mongoid::Relations::Referenced::In]
+ params["#{name}_id"] && types.include?(metadata.relation)
+ end
+
+ if assoc
+ parent_id = params["#{assoc.first}_id"]
+ parent_name = assoc.first
+
+ id = params["#{name}_id"]
+ object = id ? self.class.const_get(name.singularize.capitalize).find(id) : name
+
+ parent = self.class.const_get(parent_name.capitalize).find(parent_id)
+ [traverse[parent_name], object].flatten
+ else
+ id = params["#{name}_id"]
+ object = self.class.const_get(name.capitalize).find(id)
+ [object]
+ end
+ end
+
+ traverse[@name]
+ end
+ end
+end
+
@@ -1,5 +1,6 @@
require 'hypermodel/resource'
require 'hypermodel/collection'
+require 'hypermodel/empty_collection'
module Hypermodel
# Public: Responsible for exposing a resource in JSON-HAL format.
@@ -29,11 +30,16 @@ def self.call(*args)
def initialize(resource_name, action, record, controller)
@resource_name = resource_name
@action = action
- @resource = if record.respond_to?(:each)
- Collection.new(record, controller)
- else
- Resource.new(record, controller)
- end
+
+ if record.respond_to?(:each)
+ @resource = if record.empty?
+ EmptyCollection.new(resource_name, controller)
+ else
+ Collection.new(record, controller)
+ end
+ else
+ @resource = Resource.new(record, controller)
+ end
end
def to_json(*opts)
View
@@ -53,4 +53,16 @@ def setup
body = JSON.load(response.body)
assert_equal blog_post_url(@blog, @post), body['_links']['post']['href']
end
+
+ test "it handles empty collections gracefully" do
+ blog = Blog.create(title: 'Hey')
+ _post = blog.posts.create(title: 'hey') # Post with no comments
+
+ post :index, { blog_id: blog.id, post_id: _post.id, format: :json }
+
+ body = JSON.load(response.body)
+
+ assert response.successful?
+ assert_equal [], body['_embedded']['comments']
+ end
end

0 comments on commit 251e383

Please sign in to comment.