roar-rails 0.0.7 → 0.0.8
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/CHANGES.markdown +4 -0
- data/README.markdown +35 -9
- data/lib/roar/rails/controller_additions.rb +43 -3
- data/lib/roar/rails/responder.rb +14 -26
- data/lib/roar/rails/version.rb +1 -1
- data/test/consume_test.rb +51 -1
- data/test/responder_test.rb +157 -66
- metadata +2 -2
data/CHANGES.markdown
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
h2. 0.0.8
|
2
|
+
|
3
|
+
* Added `#represents` to configure consuming and rendering on controller class layer. This also calls `respond_to`.
|
4
|
+
|
1
5
|
h2. 0.0.7
|
2
6
|
|
3
7
|
* Introduce `:represent_with` and `:represent_items_with` for `#respond_with`. In turn, deprecate the old behaviour since it will change in 1.0.
|
data/README.markdown
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
_Makes using Roar's representers in your Rails app fun._
|
4
4
|
|
5
|
+
Roar is a framework for parsing and rendering REST documents. For a better overview about representers please check the [roar repository](https://github.com/apotonick/roar#roar).
|
6
|
+
|
5
7
|
## Features
|
6
8
|
|
7
9
|
* Rendering with responders
|
@@ -12,6 +14,10 @@ _Makes using Roar's representers in your Rails app fun._
|
|
12
14
|
|
13
15
|
## Rendering with #respond_with
|
14
16
|
|
17
|
+
roar-rails provides a number of baked-in rendering methods.
|
18
|
+
|
19
|
+
### Conventional Rendering
|
20
|
+
|
15
21
|
Easily render resources using representers with the built-in responder.
|
16
22
|
|
17
23
|
```ruby
|
@@ -26,21 +32,35 @@ class SingersController < ApplicationController
|
|
26
32
|
end
|
27
33
|
```
|
28
34
|
|
29
|
-
|
35
|
+
The representer name will be infered from the passed model class (e.g. a `Singer` instance gets the `SingerRepresenter`). If the passed model is a collection it will be extended using a representer. The representer name will be computed from the controller name (e.g. a `SingersController` uses the `SingersRepresenter`).
|
36
|
+
|
37
|
+
Need to use a representer with a different name than your model? You may always pass it in using the `:represent_with` option:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
respond_with singers, :represent_with => MusicianCollectionRepresenter
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
### Represents Configuration
|
45
|
+
|
46
|
+
If you don't want to use conventions or pass representers you can configure them on the class level using `#represents`. This will also call `respond_to` for you.
|
30
47
|
|
31
48
|
```ruby
|
32
49
|
class SingersController < ApplicationController
|
33
|
-
|
34
|
-
|
50
|
+
represents :json, Musician
|
51
|
+
```
|
52
|
+
This will use the `MusicianRepresenter` for models and `MusiciansRepresenter` for representing collections.
|
35
53
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
54
|
+
Note that `#represents` also allows fine-tuning.
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
class SingersController < ApplicationController
|
58
|
+
represents :json, :entity => MusicianRepresenter, :collection => MusicianCollectionRepresenter
|
41
59
|
```
|
42
60
|
|
43
|
-
|
61
|
+
### Old API Support
|
62
|
+
|
63
|
+
If you don't want to write a dedicated representer for a collection of items (highly recommended, thou) but rather use a representer for each item, use the `:represent_items_with` option.
|
44
64
|
|
45
65
|
```ruby
|
46
66
|
class SingersController < ApplicationController
|
@@ -96,6 +116,12 @@ singer.
|
|
96
116
|
|
97
117
|
So, `#consume!` helps you figuring out the representer module and reading the incoming document.
|
98
118
|
|
119
|
+
Note that it respects settings from `#represents`. It uses the same mechanics known from `#respond_with` to choose a representer.
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
consume!(singer, :represent_with => MusicianRepresenter)
|
123
|
+
```
|
124
|
+
|
99
125
|
## URL Helpers
|
100
126
|
|
101
127
|
Any URL helpers from the Rails app are automatically available in representers.
|
@@ -3,23 +3,63 @@ module Roar::Rails
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
include ModelMethods
|
5
5
|
|
6
|
+
included do
|
7
|
+
class_attribute :represents_options
|
8
|
+
self.represents_options ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
|
6
12
|
module ClassMethods
|
7
13
|
def responder
|
8
14
|
Class.new(super).send :include, Roar::Rails::Responder
|
9
15
|
end
|
16
|
+
|
17
|
+
def add_representer_suffix(prefix)
|
18
|
+
"#{prefix}Representer"
|
19
|
+
end
|
20
|
+
|
21
|
+
def represents(format, options)
|
22
|
+
unless options.is_a?(Hash)
|
23
|
+
model = options
|
24
|
+
options = {
|
25
|
+
:entity => add_representer_suffix(model.name).constantize,
|
26
|
+
:collection => add_representer_suffix(model.name.pluralize).constantize
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
represents_options[format] = options
|
31
|
+
respond_to format
|
32
|
+
end
|
10
33
|
end
|
11
34
|
|
12
35
|
|
13
|
-
def consume!(model)
|
14
|
-
format
|
15
|
-
|
36
|
+
def consume!(model, options={})
|
37
|
+
format = formats.first # FIXME: i expected request.content_mime_type to do the job. copied from responder.rb. this will return the wrong format when the controller responds to :json and :xml and the Content-type is :xml (?)
|
38
|
+
representer = representer_for(format, model, options)
|
39
|
+
extend_with!(model, representer)
|
16
40
|
model.send(compute_parsing_method(format), request.body.string) # e.g. from_json("...")
|
17
41
|
model
|
18
42
|
end
|
19
43
|
|
44
|
+
# Central entry-point for finding the appropriate representer.
|
45
|
+
def representer_for(format, model, options={})
|
46
|
+
options.delete(:represent_with) || representer_name_for(format, model)
|
47
|
+
end
|
48
|
+
|
20
49
|
private
|
21
50
|
def compute_parsing_method(format)
|
22
51
|
"from_#{format}"
|
23
52
|
end
|
53
|
+
|
54
|
+
def representer_name_for(format, model) # DISCUSS: should we pass and process options here?
|
55
|
+
if self.class.represents_options[format.to_sym].blank? # TODO: test to_sym?
|
56
|
+
model_name = model.class.name
|
57
|
+
model_name = controller_path.camelize if model.kind_of?(Array)
|
58
|
+
return self.class.add_representer_suffix(model_name).constantize
|
59
|
+
end
|
60
|
+
|
61
|
+
return self.class.represents_options[format][:collection] if model.kind_of?(Array)
|
62
|
+
self.class.represents_options[format][:entity]
|
63
|
+
end
|
24
64
|
end
|
25
65
|
end
|
data/lib/roar/rails/responder.rb
CHANGED
@@ -1,16 +1,9 @@
|
|
1
1
|
module Roar::Rails
|
2
2
|
module ModelMethods
|
3
3
|
# DISCUSS: move this into a generic namespace as we could need that in Sinatra as well.
|
4
|
-
def
|
5
|
-
representer ||= representer_for_model(model)
|
4
|
+
def extend_with!(model, representer)
|
6
5
|
model.extend(representer)
|
7
6
|
end
|
8
|
-
|
9
|
-
private
|
10
|
-
def representer_for_model(model)
|
11
|
-
class_name = model.class.name
|
12
|
-
"#{class_name}Representer".constantize
|
13
|
-
end
|
14
7
|
end
|
15
8
|
|
16
9
|
module Responder
|
@@ -18,28 +11,23 @@ module Roar::Rails
|
|
18
11
|
|
19
12
|
# DISCUSS: why THE FUCK is options not passed as a method argument but kept as an internal instance variable in the responder? this is something i will never understand about Rails.
|
20
13
|
def display(model, *args)
|
21
|
-
if representer = options.delete(:
|
22
|
-
#
|
23
|
-
model.extend(representer) # FIXME: move to method.
|
14
|
+
if representer = options.delete(:represent_items_with)
|
15
|
+
render_items_with(model, representer) # convenience API, not recommended since it's missing hypermedia.
|
24
16
|
return super
|
25
17
|
end
|
26
18
|
|
27
|
-
|
28
|
-
|
29
|
-
representer ||= options.delete(:represent_items_with) # new API.
|
30
|
-
|
31
|
-
if model.respond_to?(:map!)
|
32
|
-
ActiveSupport::Deprecation.warn("Calling #respond_with with a collection will misbehave in future versions of roar-rails. Use :represent_items_with to get the old behaviour.")
|
33
|
-
|
34
|
-
model.map! do |m|
|
35
|
-
extend_with_representer!(m, representer)
|
36
|
-
m.to_hash
|
37
|
-
end
|
38
|
-
else
|
39
|
-
extend_with_representer!(model, representer)
|
40
|
-
end
|
41
|
-
|
19
|
+
representer = controller.representer_for(format, model, options)
|
20
|
+
extend_with!(model, representer)
|
42
21
|
super
|
43
22
|
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def render_items_with(collection, representer)
|
26
|
+
collection.map! do |m| # DISCUSS: i don't like changing the method argument here.
|
27
|
+
extend_with!(m, representer)
|
28
|
+
m.to_hash # FIXME: huh? and what about XML?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
44
32
|
end
|
45
33
|
end
|
data/lib/roar/rails/version.rb
CHANGED
data/test/consume_test.rb
CHANGED
@@ -19,9 +19,59 @@ class ConsumeTest < ActionController::TestCase
|
|
19
19
|
post :consume_json, "{\"name\": \"Bumi\"}", :format => 'json'
|
20
20
|
assert_equal singer.to_json, @response.body
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
def singer(name="Bumi")
|
24
24
|
singer = Musician.new(name)
|
25
25
|
singer.extend SingerRepresenter
|
26
26
|
end
|
27
27
|
end
|
28
|
+
|
29
|
+
class ConsumeWithConfigurationTest < ConsumeTest
|
30
|
+
include Roar::Rails::TestCase
|
31
|
+
|
32
|
+
module MusicianRepresenter
|
33
|
+
include Roar::Representer::JSON
|
34
|
+
property :name, :from => :called
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
class SingersController < ActionController::Base
|
39
|
+
include Roar::Rails::ControllerAdditions
|
40
|
+
respond_to :json
|
41
|
+
represents "json", :entity => MusicianRepresenter
|
42
|
+
|
43
|
+
def consume_json
|
44
|
+
singer = consume!(Singer.new)
|
45
|
+
render :text => singer.to_json
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
tests SingersController
|
50
|
+
|
51
|
+
test "#consume uses #represents config to parse incoming document" do
|
52
|
+
post :consume_json, "{\"name\": \"Bumi\"}", :format => :json
|
53
|
+
assert_equal singer.to_json, @response.body
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class ConsumeWithOptionsOverridingConfigurationTest < ConsumeTest
|
58
|
+
include Roar::Rails::TestCase
|
59
|
+
|
60
|
+
class SingersController < ActionController::Base
|
61
|
+
include Roar::Rails::ControllerAdditions
|
62
|
+
respond_to :json
|
63
|
+
represents :json, :entity => Object
|
64
|
+
|
65
|
+
def consume_json
|
66
|
+
singer = consume!(Singer.new, :represent_with => SingerRepresenter)
|
67
|
+
render :text => singer.to_json
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
tests SingersController
|
72
|
+
|
73
|
+
test "#consume uses #represents config to parse incoming document" do
|
74
|
+
post :consume_json, "{\"name\": \"Bumi\"}", :format => :json
|
75
|
+
assert_equal singer.to_json, @response.body
|
76
|
+
end
|
77
|
+
end
|
data/test/responder_test.rb
CHANGED
@@ -11,112 +11,203 @@ module SingersRepresenter
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
module ObjectRepresenter
|
15
|
+
end
|
16
|
+
module ObjectsRepresenter
|
17
|
+
end
|
18
|
+
|
19
|
+
class RepresentsTest < MiniTest::Spec
|
20
|
+
class SingersController
|
21
|
+
end
|
22
|
+
|
23
|
+
before do
|
24
|
+
@controller = Class.new do
|
25
|
+
include Roar::Rails::ControllerAdditions
|
26
|
+
end.new
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "representer_for" do
|
30
|
+
describe "nothing configured" do
|
31
|
+
before do
|
32
|
+
@controller = class ::SingersController
|
33
|
+
include Roar::Rails::ControllerAdditions
|
34
|
+
self
|
35
|
+
end.new
|
36
|
+
end
|
37
|
+
|
38
|
+
it "uses model class" do
|
39
|
+
assert_equal SingerRepresenter, @controller.representer_for(:json, Singer.new)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "uses plural controller name when collection" do
|
43
|
+
assert_equal SingersRepresenter, @controller.representer_for(:json, [])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "represents :json, Singer" do
|
48
|
+
before do
|
49
|
+
@controller = class ::WhateverController < ActionController::Base
|
50
|
+
include Roar::Rails::ControllerAdditions
|
51
|
+
represents :json, Object
|
52
|
+
self
|
53
|
+
end.new
|
54
|
+
end
|
55
|
+
|
56
|
+
it "uses defined class for item" do
|
57
|
+
assert_equal ObjectRepresenter, @controller.representer_for(:json, Singer.new)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "uses plural name when collection" do
|
61
|
+
assert_equal ObjectsRepresenter, @controller.representer_for(:json, [])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
describe "represents :json, :entity => SingerRepresenter" do
|
67
|
+
before do
|
68
|
+
@controller = class ::FooController < ActionController::Base
|
69
|
+
include Roar::Rails::ControllerAdditions
|
70
|
+
represents :json, :entity => "ObjectRepresenter"
|
71
|
+
self
|
72
|
+
end.new
|
73
|
+
end
|
74
|
+
|
75
|
+
it "returns :entity representer name" do
|
76
|
+
assert_equal "ObjectRepresenter", @controller.representer_for(:json, Singer.new)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "doesn't infer collection representer" do
|
80
|
+
assert_equal nil, @controller.representer_for(:json, [])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "represents :json, :entity => SingerRepresenter, :collection => SingersRepresenter" do
|
85
|
+
before do
|
86
|
+
@controller = class ::BooController < ActionController::Base
|
87
|
+
include Roar::Rails::ControllerAdditions
|
88
|
+
represents :json, :entity => "ObjectRepresenter", :collection => "SingersRepresenter"
|
89
|
+
self
|
90
|
+
end.new
|
91
|
+
end
|
92
|
+
|
93
|
+
it "uses defined class for item" do
|
94
|
+
assert_equal "ObjectRepresenter", @controller.representer_for(:json, Singer.new)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "uses defined class when collection" do
|
98
|
+
assert_equal "SingersRepresenter", @controller.representer_for(:json, [])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
14
104
|
|
15
105
|
class ResponderTest < ActionController::TestCase
|
16
106
|
include Roar::Rails::TestCase
|
17
107
|
|
18
|
-
class
|
108
|
+
class BaseController < ActionController::Base
|
19
109
|
include Roar::Rails::ControllerAdditions
|
20
110
|
respond_to :json
|
21
|
-
|
111
|
+
|
22
112
|
def execute
|
23
113
|
instance_exec &@block
|
24
114
|
end
|
25
115
|
end
|
26
116
|
|
27
|
-
|
28
|
-
|
29
|
-
@block = block
|
117
|
+
class UnconfiguredControllerTest < ResponderTest
|
118
|
+
class SingersController < BaseController
|
30
119
|
end
|
31
120
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
tests SingersController
|
37
|
-
|
38
|
-
test ":with_representer is deprecated" do
|
39
|
-
assert_deprecated do
|
121
|
+
tests SingersController
|
122
|
+
|
123
|
+
test "responder finds SingerRepresenter representer by convention" do
|
40
124
|
get do
|
41
|
-
singer =
|
42
|
-
respond_with singer
|
125
|
+
singer = Singer.new("Bumi")
|
126
|
+
respond_with singer
|
43
127
|
end
|
128
|
+
|
129
|
+
assert_equal singer.to_json, @response.body
|
130
|
+
end
|
131
|
+
|
132
|
+
test "responder finds SingersRepresenter for collections by convention" do
|
133
|
+
get do
|
134
|
+
singers = [Singer.new("Bumi"), Singer.new("Bjork"), Singer.new("Sinead")]
|
135
|
+
respond_with singers
|
136
|
+
end
|
137
|
+
|
138
|
+
assert_equal({:singers => singers.collect {|s| s.extend(SingerRepresenter).to_hash }}.to_json, @response.body)
|
44
139
|
end
|
45
140
|
end
|
46
141
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
singer = Musician.new("Bumi")
|
51
|
-
respond_with singer, :with_representer => SingerRepresenter
|
142
|
+
class RespondToOptionsOverridingConfigurationTest < ResponderTest
|
143
|
+
class SingersController < BaseController
|
144
|
+
represents :json, Object
|
52
145
|
end
|
53
146
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
147
|
+
tests SingersController
|
148
|
+
|
149
|
+
test "responder uses passed representer" do
|
150
|
+
get do
|
151
|
+
singer = Singer.new("Bumi")
|
152
|
+
respond_with singer, :represent_with => SingerRepresenter
|
153
|
+
end
|
154
|
+
|
155
|
+
assert_equal singer.to_json, @response.body
|
61
156
|
end
|
62
157
|
|
63
|
-
|
64
|
-
end
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
test "responder works with collections" do # TODO: remove in 1.0.
|
69
|
-
assert_deprecated do
|
158
|
+
test "responder uses passed representer for collection" do
|
70
159
|
get do
|
71
160
|
singers = [Singer.new("Bumi"), Singer.new("Bjork"), Singer.new("Sinead")]
|
72
|
-
respond_with singers
|
161
|
+
respond_with singers, :represent_with => SingersRepresenter
|
73
162
|
end
|
163
|
+
|
164
|
+
assert_equal({:singers => singers.collect {|s| s.extend(SingerRepresenter).to_hash }}.to_json, @response.body)
|
74
165
|
end
|
75
166
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
167
|
+
test "responder uses passed representer for collection items when :represent_items_with set" do
|
168
|
+
get do
|
169
|
+
singers = [Singer.new("Bumi"), Singer.new("Bjork"), Singer.new("Sinead")]
|
170
|
+
respond_with singers, :represent_items_with => SingerRepresenter
|
171
|
+
end
|
172
|
+
|
173
|
+
assert_equal(singers.collect {|s| s.extend(SingerRepresenter).to_hash }.to_json, @response.body)
|
83
174
|
end
|
84
|
-
|
85
|
-
assert_equal singers.map {|s| s.extend(SingerAliasRepresenter).to_hash }.to_json, @response.body
|
86
175
|
end
|
87
176
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
get do
|
92
|
-
singer = Musician.new("Bumi")
|
93
|
-
respond_with singer, :with_representer => SingerRepresenter
|
177
|
+
class ConfiguredControllerTest < ResponderTest
|
178
|
+
class MusicianController < BaseController
|
179
|
+
represents :json, :entity => SingerRepresenter, :collection => SingersRepresenter
|
94
180
|
end
|
95
181
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
182
|
+
tests MusicianController
|
183
|
+
|
184
|
+
test "responder uses configured representer" do
|
185
|
+
get do
|
186
|
+
singer = Singer.new("Bumi")
|
187
|
+
respond_with singer
|
188
|
+
end
|
189
|
+
|
190
|
+
assert_equal singer.to_json, @response.body
|
103
191
|
end
|
104
192
|
|
105
|
-
|
193
|
+
test "responder uses configured representer for collection" do
|
194
|
+
get do
|
195
|
+
singers = [Singer.new("Bumi"), Singer.new("Bjork"), Singer.new("Sinead")]
|
196
|
+
respond_with singers
|
197
|
+
end
|
198
|
+
|
199
|
+
assert_equal({:singers => singers.collect {|s| s.extend(SingerRepresenter).to_hash }}.to_json, @response.body)
|
200
|
+
end
|
106
201
|
end
|
107
202
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
respond_with singers, :represent_items_with => SingerRepresenter
|
203
|
+
def get(&block)
|
204
|
+
@controller.instance_eval do
|
205
|
+
@block = block
|
112
206
|
end
|
113
207
|
|
114
|
-
|
208
|
+
super :execute, :format => 'json'
|
115
209
|
end
|
116
210
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
211
|
def singer(name="Bumi")
|
121
212
|
singer = Musician.new(name)
|
122
213
|
singer.extend SingerRepresenter
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: roar-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
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: 2012-06-
|
12
|
+
date: 2012-06-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: roar
|