roar-rails 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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
- Need to use a representer with a different name than your model? Pass it in using the `:represent_with` option:
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
- include Roar::Rails::ControllerAdditions
34
- respond_to :json
50
+ represents :json, Musician
51
+ ```
52
+ This will use the `MusicianRepresenter` for models and `MusiciansRepresenter` for representing collections.
35
53
 
36
- def show
37
- singer = Musician.find_by_id(params[:id])
38
- respond_with singer, :represent_with => SingerRepresenter
39
- end
40
- end
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
- 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.
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 = 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 (?)
15
- extend_with_representer!(model)
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
@@ -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 extend_with_representer!(model, representer=nil)
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(:represent_with)
22
- # this is the new behaviour.
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
- representer = options.delete(:with_representer) and ActiveSupport::Deprecation.warn(":with_representer is deprecated and will be removed in roar-rails 1.0. Use :represent_with or :represent_items_with.")
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
@@ -1,5 +1,5 @@
1
1
  module Roar
2
2
  module Rails
3
- VERSION = "0.0.7"
3
+ VERSION = "0.0.8"
4
4
  end
5
5
  end
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
@@ -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 SingersController < ActionController::Base
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
- def get(&block)
28
- @controller.instance_eval do
29
- @block = block
117
+ class UnconfiguredControllerTest < ResponderTest
118
+ class SingersController < BaseController
30
119
  end
31
120
 
32
- super :execute, :format => 'json'
33
- end
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 = Musician.new("Bumi")
42
- respond_with singer, :with_representer => SingerRepresenter
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
- test "responder allows specifying representer" do # TODO: remove in 1.0.
49
- get do
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
- assert_equal singer.to_json, @response.body
55
- end
56
-
57
- test "responder finds representer by convention" do
58
- get do
59
- singer = Singer.new("Bumi")
60
- respond_with singer
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
- assert_equal singer.to_json, @response.body
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
- assert_equal singers.map(&:to_hash).to_json, @response.body
77
- end
78
-
79
- test "custom responder works with collections" do # TODO: remove in 1.0.
80
- get do
81
- singers = [Singer.new("Bumi"), Singer.new("Bjork"), Singer.new("Sinead")]
82
- respond_with singers, :with_representer => SingerAliasRepresenter
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
- test "use passed :represent_with representer for single model" do
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
- assert_equal singer.extend(SingerRepresenter).to_json, @response.body
97
- end
98
-
99
- test "use passed :represent_with representer for collection" do
100
- get do
101
- singers = [Singer.new("Bumi"), Singer.new("Bjork"), Singer.new("Sinead")]
102
- respond_with singers, :represent_with => SingersRepresenter
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
- assert_equal({:singers => singers.collect {|s| s.extend(SingerRepresenter).to_hash }}.to_json, @response.body)
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
- test "use passed :represent_items_with for collection items" do
109
- get do
110
- singers = [Singer.new("Bumi"), Singer.new("Bjork"), Singer.new("Sinead")]
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
- assert_equal(singers.collect {|s| s.extend(SingerRepresenter).to_hash }.to_json, @response.body)
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.7
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-10 00:00:00.000000000 Z
12
+ date: 2012-06-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: roar