api-presenter 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +11 -24
- data/api-presenter.gemspec +0 -3
- data/lib/api/presenter/collection_resource.rb +7 -8
- data/lib/api/presenter/hypermedia.rb +39 -40
- data/lib/api/presenter/resource.rb +24 -7
- data/lib/api/presenter/search_resource.rb +3 -9
- data/lib/api/presenter/version.rb +1 -1
- data/spec/hypertext_presenter_mocks.rb +5 -6
- data/spec/hypertext_presenter_spec.rb +32 -28
- data/spec/spec_helper.rb +0 -1
- metadata +2 -34
data/README.md
CHANGED
@@ -50,12 +50,9 @@ In order to be able to represent this resource we need to have a PersonResource
|
|
50
50
|
|
51
51
|
```ruby
|
52
52
|
class PersonResource < Api::Presenter::Resource
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
resource: []
|
57
|
-
}
|
58
|
-
end
|
53
|
+
|
54
|
+
property :name
|
55
|
+
property :age
|
59
56
|
|
60
57
|
def self_link
|
61
58
|
"/person/#{@resource.name}"
|
@@ -63,10 +60,7 @@ class PersonResource < Api::Presenter::Resource
|
|
63
60
|
end
|
64
61
|
```
|
65
62
|
|
66
|
-
|
67
|
-
|
68
|
-
* simple: An array containing method names that represent simple data (integers, strings, dates, etc.).
|
69
|
-
* resource: An array containing method names thar represent related resources.
|
63
|
+
We should use the property method to define each of our resource properties.
|
70
64
|
|
71
65
|
The self_link definition tell which is the link that represents itself.
|
72
66
|
|
@@ -88,7 +82,7 @@ data = Person.new("Alvaro", 27)
|
|
88
82
|
|
89
83
|
resource = data.to_resource # or just PersonResource.new(data)
|
90
84
|
|
91
|
-
Api::Presenter::Hypermedia.present resource
|
85
|
+
resource.present # or Api::Presenter::Hypermedia.present resource
|
92
86
|
```
|
93
87
|
|
94
88
|
It will look like this:
|
@@ -115,12 +109,8 @@ Using the example above, now our person has a dog. So:
|
|
115
109
|
|
116
110
|
```ruby
|
117
111
|
class DogResource < Api::Presenter::Resource
|
118
|
-
|
119
|
-
|
120
|
-
simple: [:name],
|
121
|
-
resource: [:owner]
|
122
|
-
}
|
123
|
-
end
|
112
|
+
property name
|
113
|
+
property owner
|
124
114
|
|
125
115
|
def self_link
|
126
116
|
"/dog/#{@resource.name}"
|
@@ -141,12 +131,9 @@ class Dog
|
|
141
131
|
end
|
142
132
|
|
143
133
|
class PersonResource < Api::Presenter::Resource
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
resource: [:dog]
|
148
|
-
}
|
149
|
-
end
|
134
|
+
property :name
|
135
|
+
property :age
|
136
|
+
property :dog
|
150
137
|
end
|
151
138
|
```
|
152
139
|
|
@@ -159,7 +146,7 @@ dog = Dog.new("Cleo", person)
|
|
159
146
|
|
160
147
|
person_resource = person.to_resource # or just PersonResource.new(person)
|
161
148
|
|
162
|
-
Api::Presenter::Hypermedia.present person_resource
|
149
|
+
person_resource.present # Api::Presenter::Hypermedia.present person_resource
|
163
150
|
```
|
164
151
|
|
165
152
|
It will look like this:
|
data/api-presenter.gemspec
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
module Api
|
2
2
|
module Presenter
|
3
|
-
class CollectionResource < Resource
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
class CollectionResource < Resource
|
4
|
+
|
5
|
+
property :offset
|
6
|
+
property :limit
|
7
|
+
property :total
|
8
|
+
property :entries
|
9
|
+
|
11
10
|
def entries
|
12
11
|
@resource
|
13
12
|
end
|
@@ -1,54 +1,53 @@
|
|
1
1
|
module Api
|
2
2
|
module Presenter
|
3
3
|
class Hypermedia
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
class << self
|
5
|
+
def present(resource)
|
6
|
+
# Initialize representation with links
|
7
|
+
representation = build_links resource
|
7
8
|
|
8
|
-
|
9
|
+
present_properties resource, representation
|
9
10
|
|
10
|
-
|
11
|
-
end
|
12
|
-
|
13
|
-
# Process basic information from the resource such as
|
14
|
-
# simlple fields(Dates, numbers, etc.) and also more
|
15
|
-
# complex ones that may be subject of expansion.
|
16
|
-
def self.present_properties(resource, representation)
|
17
|
-
properties = resource.class.hypermedia_properties
|
18
|
-
|
19
|
-
present_simple_properties resource, properties, representation
|
20
|
-
|
21
|
-
present_resource_properties resource, properties, representation
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.present_simple_properties(resource, properties, representation)
|
25
|
-
entries_property = properties[:simple].delete(:entries)
|
26
|
-
|
27
|
-
if entries_property
|
28
|
-
representation[entries_property.to_s] = []
|
29
|
-
resource.send(entries_property).each do |nested_resource|
|
30
|
-
representation[entries_property.to_s] << build_links(nested_resource.to_resource, embed: true)
|
31
|
-
end
|
11
|
+
representation
|
32
12
|
end
|
33
13
|
|
34
|
-
|
35
|
-
|
14
|
+
# Process basic information from the resource such as
|
15
|
+
# simlple fields(Dates, numbers, etc.) and also more
|
16
|
+
# complex ones that may be subject of expansion.
|
17
|
+
def present_properties(resource, representation)
|
18
|
+
resource_properties = resource.class.properties
|
19
|
+
|
20
|
+
# Special treatment for entries
|
21
|
+
entries_property = resource_properties.delete(:entries)
|
22
|
+
|
23
|
+
if entries_property
|
24
|
+
representation[entries_property.to_s] = []
|
25
|
+
resource.send(entries_property).each do |nested_resource|
|
26
|
+
representation[entries_property.to_s] << build_links(nested_resource.to_resource, embed: true)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Now the other muggles
|
31
|
+
resource_properties.each do |property_name|
|
32
|
+
property_value = resource.send(property_name)
|
33
|
+
|
34
|
+
if property_value.kind_of?(Resource) || property_value.respond_to?(:to_resource)
|
35
|
+
# Resource like properties
|
36
|
+
property_value = property_value.to_resource if property_value.respond_to? :to_resource
|
37
|
+
representation["links"][property_name.to_s] = property_value.links(embed: resource.kind_of?(CollectionResource))
|
38
|
+
# we only need the "self" contents
|
39
|
+
representation["links"][property_name.to_s] = representation["links"][property_name.to_s]["self"] if representation["links"][property_name.to_s]["self"]
|
40
|
+
else
|
41
|
+
# Non-Resource like properties
|
42
|
+
representation[property_name.to_s] = property_value
|
43
|
+
end
|
44
|
+
end
|
36
45
|
end
|
37
|
-
end
|
38
46
|
|
39
|
-
|
40
|
-
|
41
|
-
relation = resource.send(property)
|
42
|
-
relation_resource = (relation.kind_of? Resource) ? relation : relation.to_resource
|
43
|
-
representation["links"][property.to_s] = relation_resource.links(embed: resource.kind_of?(CollectionResource))
|
44
|
-
# we only need the "self" contents
|
45
|
-
representation["links"][property.to_s] = representation["links"][property.to_s]["self"] if representation["links"][property.to_s]["self"]
|
47
|
+
def build_links(resource, options = {})
|
48
|
+
{ "links" => resource.links(options) }
|
46
49
|
end
|
47
50
|
end
|
48
|
-
|
49
|
-
def self.build_links(resource, options = {})
|
50
|
-
{ "links" => resource.links(options) }
|
51
|
-
end
|
52
51
|
end
|
53
52
|
end
|
54
53
|
end
|
@@ -4,17 +4,30 @@ module Api
|
|
4
4
|
def initialize(resource)
|
5
5
|
@resource = resource
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def property(value)
|
10
|
+
properties << value unless properties.include? value
|
11
|
+
end
|
12
|
+
|
13
|
+
def properties
|
14
|
+
@properties ||= []
|
15
|
+
end
|
16
|
+
|
17
|
+
def inherited(subclass)
|
18
|
+
subclass.properties << properties
|
19
|
+
subclass.properties.flatten!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
8
23
|
def method_missing(method, *args, &block)
|
9
|
-
|
10
|
-
|
11
|
-
if allowed_methods.include? method
|
24
|
+
if self.class.properties.include? method
|
12
25
|
@resource.send(method, *args, &block)
|
13
26
|
else
|
14
27
|
super
|
15
28
|
end
|
16
29
|
end
|
17
|
-
|
30
|
+
|
18
31
|
def links(options = {})
|
19
32
|
links = {}
|
20
33
|
|
@@ -22,13 +35,17 @@ module Api
|
|
22
35
|
link_name = link_method.to_s.split("_").first
|
23
36
|
links[link_name] = { "href" => self.send(link_method) } if self.send(link_method.to_s + "?", options)
|
24
37
|
end
|
25
|
-
|
38
|
+
|
26
39
|
links
|
27
40
|
end
|
28
|
-
|
41
|
+
|
29
42
|
def self_link?(options = {})
|
30
43
|
true
|
31
44
|
end
|
45
|
+
|
46
|
+
def present
|
47
|
+
Api::Presenter::Hypermedia.present self
|
48
|
+
end
|
32
49
|
end
|
33
50
|
end
|
34
51
|
end
|
@@ -2,20 +2,14 @@ module Api
|
|
2
2
|
module Presenter
|
3
3
|
class SearchResource < CollectionResource
|
4
4
|
attr_reader :query
|
5
|
-
|
6
|
-
def initialize(resource, query
|
5
|
+
|
6
|
+
def initialize(resource, query)
|
7
7
|
@resource = resource
|
8
8
|
@query = query
|
9
9
|
end
|
10
10
|
|
11
|
-
|
12
|
-
properties = super
|
11
|
+
property :query
|
13
12
|
|
14
|
-
properties[:simple] = properties[:simple].concat [:query]
|
15
|
-
|
16
|
-
properties
|
17
|
-
end
|
18
|
-
|
19
13
|
def query_string
|
20
14
|
result = self.class.hypermedia_query_parameters.inject([]) { |col, query_parameter| col << "query[#{query_parameter}]=#{@query[query_parameter]}" }
|
21
15
|
"?" + result.join("&")
|
@@ -36,12 +36,11 @@ module HypertextPresenterMocks
|
|
36
36
|
end
|
37
37
|
|
38
38
|
class MockSingleResource < Api::Presenter::Resource
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
39
|
+
|
40
|
+
property :number
|
41
|
+
property :string
|
42
|
+
property :date
|
43
|
+
property :sibling
|
45
44
|
|
46
45
|
def self_link
|
47
46
|
"/path/to/single_resource/#{@resource.number}"
|
@@ -59,6 +59,7 @@ describe Api::Presenter::Hypermedia do
|
|
59
59
|
]
|
60
60
|
}
|
61
61
|
end
|
62
|
+
|
62
63
|
let(:single_resource_standard) do
|
63
64
|
{
|
64
65
|
"links" =>
|
@@ -78,9 +79,10 @@ describe Api::Presenter::Hypermedia do
|
|
78
79
|
},
|
79
80
|
"number" => 1,
|
80
81
|
"string" => 'This is a string',
|
81
|
-
"date" => Date.today
|
82
|
+
"date" => Date.today
|
82
83
|
}
|
83
84
|
end
|
85
|
+
|
84
86
|
let(:search_resource_standard)do
|
85
87
|
{
|
86
88
|
"links" =>
|
@@ -147,37 +149,39 @@ describe Api::Presenter::Hypermedia do
|
|
147
149
|
describe "when presenting a single resource" do
|
148
150
|
|
149
151
|
let(:mock_data) { MockData.new(number: 1, string: 'This is a string', date: Date.today) }
|
150
|
-
let(:single_resource){ MockSingleResource.new mock_data }
|
151
|
-
let(:presented_single_resource){
|
152
|
+
let(:single_resource) { MockSingleResource.new mock_data }
|
153
|
+
let(:presented_single_resource) { single_resource.present }
|
152
154
|
|
153
155
|
it "must respect the standard" do
|
154
|
-
|
156
|
+
presented_single_resource.must_equal single_resource_standard
|
155
157
|
end
|
156
158
|
end
|
157
159
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
160
|
+
describe "presenting a collection resource" do
|
161
|
+
|
162
|
+
let(:mock_data_collection) do
|
163
|
+
collection = []
|
164
|
+
4.times { |number| collection << MockData.new(number: number + 1, string: 'This is a string', date: Date.today) }
|
165
|
+
Collection.new(collection)
|
166
|
+
end
|
167
|
+
|
168
|
+
let(:collection_resource) { MockCollectionResource.new mock_data_collection }
|
169
|
+
|
170
|
+
let(:presented_collection_resource) { collection_resource.present }
|
171
|
+
|
172
|
+
it "must respect the standard" do
|
173
|
+
presented_collection_resource.must_equal collection_resource_standard
|
174
|
+
end
|
175
|
+
|
176
|
+
describe "presenting a search resource" do
|
177
|
+
|
178
|
+
let(:search_resource) { MockSearchResource.new mock_data_collection, "page" => 1, "param1" => 1, "param2" => "string" }
|
179
|
+
|
180
|
+
let(:presented_search_resource) { search_resource.present }
|
181
|
+
|
182
|
+
it "must respect the standard" do
|
183
|
+
presented_search_resource.must_equal search_resource_standard
|
184
|
+
end
|
185
|
+
end
|
181
186
|
end
|
182
|
-
end
|
183
187
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: api-presenter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-06-
|
12
|
+
date: 2013-06-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -43,38 +43,6 @@ dependencies:
|
|
43
43
|
- - ! '>='
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '0'
|
46
|
-
- !ruby/object:Gem::Dependency
|
47
|
-
name: multi_json
|
48
|
-
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
|
-
requirements:
|
51
|
-
- - ~>
|
52
|
-
- !ruby/object:Gem::Version
|
53
|
-
version: 1.7.4
|
54
|
-
type: :runtime
|
55
|
-
prerelease: false
|
56
|
-
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
|
-
requirements:
|
59
|
-
- - ~>
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: 1.7.4
|
62
|
-
- !ruby/object:Gem::Dependency
|
63
|
-
name: json
|
64
|
-
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
|
-
requirements:
|
67
|
-
- - ~>
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: 1.8.0
|
70
|
-
type: :runtime
|
71
|
-
prerelease: false
|
72
|
-
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
|
-
requirements:
|
75
|
-
- - ~>
|
76
|
-
- !ruby/object:Gem::Version
|
77
|
-
version: 1.8.0
|
78
46
|
description: A JSON resource presenter for a specific api media type
|
79
47
|
email:
|
80
48
|
- alvarola@gmail.com
|