hypermodel 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2012 YOURNAME
1
+ Copyright 2012 Codegram Technologies
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -0,0 +1,93 @@
1
+ require 'hypermodel/resource'
2
+ require 'hypermodel/traverse_ancestors'
3
+
4
+ module Hypermodel
5
+ # Public: Commands a collection of resources to build themselves in JSON-HAL
6
+ # format, with some links of the collection itself.
7
+ class Collection
8
+ # Public: Initializes a Collection.
9
+ #
10
+ # collection - An Array of Mongoid documents.
11
+ # controller - An ActionController instance.
12
+ #
13
+ # Returns nothing.
14
+ def initialize(collection, controller)
15
+ @collection = collection
16
+ @name = collection.first.class.name.downcase.pluralize
17
+ @controller = controller
18
+ end
19
+
20
+ # Public: Serialize the whole representation as JSON.
21
+ #
22
+ # Returns a String with the serialization.
23
+ def as_json(*opts)
24
+ links.update(embedded).as_json(*opts)
25
+ end
26
+
27
+ # Internal: Constructs the _links section of the response.
28
+ #
29
+ # Returns a Hash of the links of the collection. It will include, at least,
30
+ # a link to itself.
31
+ def links
32
+ _links = parent_link.update({
33
+ self: { href: @controller.polymorphic_url(collection_hierarchy) }
34
+ })
35
+
36
+ { _links: _links }
37
+ end
38
+
39
+ # Internal: Constructs the _embedded section of the response.
40
+ #
41
+ # Returns a Hash of the collection members, decorated as Resources.
42
+ def embedded
43
+ {
44
+ _embedded: {
45
+ @name => decorated_collection
46
+ }
47
+ }
48
+ end
49
+
50
+ #######
51
+ private
52
+ #######
53
+
54
+ # Internal: Returns a Hash with a link to the parent of the collection, if
55
+ # it exists, or an empty Hash otherwise.
56
+ def parent_link
57
+ link = {}
58
+ if collection_hierarchy.length > 1
59
+ parent_name = collection_hierarchy[-2].class.name.downcase
60
+ link[parent_name] = {
61
+ href: @controller.polymorphic_url(collection_hierarchy[0..-2])
62
+ }
63
+ end
64
+ link
65
+ end
66
+
67
+ # Internal: Returns a copy of the collection with its members decorated as
68
+ # Hypermodel Resources.
69
+ def decorated_collection
70
+ @collection.map do |element|
71
+ Resource.new(element, @controller)
72
+ end
73
+ end
74
+
75
+ # Internal: Returns an Array with the ancestor hierarchy of the
76
+ # collection, used mainly to construct URIs.
77
+ #
78
+ # The last element is always the plural name of the collection as a String.
79
+ #
80
+ # Examples
81
+ #
82
+ # collection = Hypermodel::Collection.new(post.comments)
83
+ # collection.send :collection_hierarchy
84
+ # # => [#<Blog:0x123456>, #<Post:0x456789>, "comments"]
85
+ #
86
+ def collection_hierarchy
87
+ @collection_hierarchy ||=
88
+ TraverseAncestors[@collection.first][0..-2].tap do |fields|
89
+ fields << @name
90
+ end
91
+ end
92
+ end
93
+ end
@@ -1,4 +1,6 @@
1
- require 'hypermodel/serializers/mongoid'
1
+ require 'forwardable'
2
+ require 'hypermodel/traverse_ancestors'
3
+ require 'hypermodel/serializer'
2
4
 
3
5
  module Hypermodel
4
6
  # Public: Responsible for building the response in JSON-HAL format. It is
@@ -13,27 +15,6 @@ module Hypermodel
13
15
  :sub_resources, :embedded_resources,
14
16
  :embedding_resources
15
17
 
16
- # Public: Recursive functino that traverses a record's referential
17
- # hierarchy upwards.
18
- #
19
- # Returns a flattened Array with the hierarchy of records.
20
- TraverseUpwards = lambda do |record|
21
- serializer = Serializers::Mongoid.new(record)
22
-
23
- parent_name, parent_resource = (
24
- serializer.embedding_resources.first || serializer.resources.first
25
- )
26
-
27
- # If we have a parent
28
- if parent_resource
29
- # Recurse over parent hierarchies
30
- [TraverseUpwards[parent_resource], record].flatten
31
- else
32
- # Final case, we are the topmost parent: return ourselves
33
- [record]
34
- end
35
- end
36
-
37
18
  # Public: Initializes a Resource.
38
19
  #
39
20
  # record - A Mongoid instance of a model.
@@ -43,15 +24,15 @@ module Hypermodel
43
24
  # choose the corresponding serializer.
44
25
  def initialize(record, controller)
45
26
  @record = record
46
- @serializer = Serializers::Mongoid.new(record)
27
+ @serializer = Serializer.build(record)
47
28
  @controller = controller
48
29
  end
49
30
 
50
31
  # Public: Returns a Hash of the resource in JSON-HAL.
51
32
  #
52
- # opts - Options to pass to the resource to_json.
53
- def to_json(*opts)
54
- attributes.update(links).update(embedded).to_json(*opts)
33
+ # opts - Options to pass to the resource as_json.
34
+ def as_json(*opts)
35
+ attributes.update(links).update(embedded).as_json(*opts)
55
36
  end
56
37
 
57
38
  # Internal: Constructs the _links section of the response.
@@ -84,7 +65,9 @@ module Hypermodel
84
65
  { href: @controller.polymorphic_url(record_or_hash_or_array, options = {}) }
85
66
  end
86
67
 
68
+ #######
87
69
  private
70
+ #######
88
71
 
89
72
  # Internal: Returns a flattened array of records representing the ancestor
90
73
  # chain of a given record, including itself at the end.
@@ -93,7 +76,7 @@ module Hypermodel
93
76
  #
94
77
  # record - the record whose ancestor chain we'd like to retrieve.
95
78
  def record_with_ancestor_chain(record)
96
- TraverseUpwards[record]
79
+ TraverseAncestors[record]
97
80
  end
98
81
  end
99
82
  end
@@ -1,4 +1,5 @@
1
1
  require 'hypermodel/resource'
2
+ require 'hypermodel/collection'
2
3
 
3
4
  module Hypermodel
4
5
  # Public: Responsible for exposing a resource in JSON-HAL format.
@@ -28,7 +29,11 @@ module Hypermodel
28
29
  def initialize(resource_name, action, record, controller)
29
30
  @resource_name = resource_name
30
31
  @action = action
31
- @resource = Resource.new(record, controller)
32
+ @resource = if record.respond_to?(:each)
33
+ Collection.new(record, controller)
34
+ else
35
+ Resource.new(record, controller)
36
+ end
32
37
  end
33
38
 
34
39
  def to_json(*opts)
@@ -0,0 +1,13 @@
1
+ require 'hypermodel/serializers/mongoid'
2
+
3
+ module Hypermodel
4
+ # Private: Responsible for instantiating the correct serializer for a given
5
+ # record. Right now only works with Mongoid.
6
+ class Serializer
7
+
8
+ # Public: Returns a matching Serializer inspecting the ORM of the record.
9
+ def self.build(record)
10
+ Serializers::Mongoid.new(record)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ require 'hypermodel/serializers/mongoid'
2
+
3
+ module Hypermodel
4
+ # Public: Recursive function that traverses a record's referential
5
+ # hierarchy upwards.
6
+ #
7
+ # Returns a flattened Array with the hierarchy of records.
8
+ TraverseAncestors = lambda do |record|
9
+ serializer = Serializers::Mongoid.new(record)
10
+
11
+ parent_name, parent_resource = (
12
+ serializer.embedding_resources.first || serializer.resources.first
13
+ )
14
+
15
+ # If we have a parent
16
+ if parent_resource
17
+ # Recurse over parent hierarchies
18
+ [TraverseAncestors[parent_resource], record].flatten
19
+ else
20
+ # Final case, we are the topmost parent: return ourselves
21
+ [record]
22
+ end
23
+ end
24
+ end
@@ -1,3 +1,3 @@
1
1
  module Hypermodel
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,56 @@
1
+ require 'test_helper'
2
+ require 'json'
3
+
4
+ class CollectionTest < ActionController::TestCase
5
+ tests CommentsController
6
+
7
+ def setup
8
+ @blog = Blog.create(title: 'Rants')
9
+ @post = Post.create(
10
+ title: "My post",
11
+ body: "Foo bar baz.",
12
+ comments: [
13
+ Comment.new(body: 'Comment 1'),
14
+ Comment.new(body: 'Comment 2'),
15
+ ],
16
+ author: Author.create(name: 'John Smith'),
17
+ reviews: [
18
+ Review.create(body: 'This is bad'),
19
+ Review.create(body: 'This is good'),
20
+ ],
21
+ blog_id: @blog.id
22
+ )
23
+ @comments = @post.comments
24
+
25
+ post :index, { blog_id: @blog.id, post_id: @post.id, format: :json }
26
+ end
27
+
28
+ test "it returns a successful response" do
29
+ assert response.successful?
30
+ end
31
+
32
+ test "returns the self link" do
33
+ body = JSON.load(response.body)
34
+ assert_match %r{/comments}, body['_links']['self']['href']
35
+ end
36
+
37
+ test "returns the comments" do
38
+ body = JSON.load(response.body)
39
+
40
+ comments = body['_embedded']['comments']
41
+
42
+ first = @comments.first
43
+ last = @comments.last
44
+
45
+ assert_equal first.id.to_s, comments.first['_id']
46
+ assert_equal first.body, comments.first['body']
47
+
48
+ assert_equal last.id.to_s, comments.last['_id']
49
+ assert_equal last.body, comments.last['body']
50
+ end
51
+
52
+ test "returns the parent post" do
53
+ body = JSON.load(response.body)
54
+ assert_equal blog_post_url(@blog, @post), body['_links']['post']['href']
55
+ end
56
+ end
@@ -0,0 +1,11 @@
1
+ class CommentsController < ApplicationController
2
+ respond_to :json
3
+
4
+ def index
5
+ @blog = Blog.find params[:blog_id]
6
+ @post = @blog.posts.find params[:post_id]
7
+ @comments = @post.comments
8
+
9
+ respond_with(@comments, responder: Hypermodel::Responder)
10
+ end
11
+ end
@@ -2,6 +2,7 @@ Dummy::Application.routes.draw do
2
2
  resources :blogs do
3
3
  resources :posts do
4
4
  resources :reviews
5
+ resources :comments
5
6
  end
6
7
  end
7
8
  resources :authors