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 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