roar-rails 0.0.14 → 0.0.15

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES.markdown CHANGED
@@ -1,3 +1,9 @@
1
+ h2. 0.0.14
2
+
3
+ * Moved logic to infer representer names from `ControllerAdditions` to `RepresenterComputer` class.
4
+ * Representer names passed to `::represents` are now constantized at runtime where they are actually needed, only. This fixes a bug where you were required to provide a `SingersRepresenter` (for collections) everywhere even when you just want to represent singular resources.
5
+ * You can now pass strings to `::represents` as representer names.
6
+
1
7
  h2. 0.0.13
2
8
 
3
9
  * Allow passing user options to both `#respond_with` and `#consume!`.
data/Gemfile CHANGED
@@ -2,3 +2,7 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in roar-rails.gemspec
4
4
  gemspec
5
+
6
+ group :test do
7
+ gem 'roar', ">= 0.11.17"
8
+ end
data/README.markdown CHANGED
@@ -45,7 +45,7 @@ end
45
45
 
46
46
  ### Represents Configuration
47
47
 
48
- 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.
48
+ 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.
49
49
 
50
50
  ```ruby
51
51
  class SingersController < ApplicationController
@@ -53,13 +53,16 @@ class SingersController < ApplicationController
53
53
  ```
54
54
  This will use the `MusicianRepresenter` for models and `MusiciansRepresenter` for representing collections.
55
55
 
56
- Note that `#represents` also allows fine-tuning.
56
+ Note that `::represents` also allows fine-tuning.
57
57
 
58
58
  ```ruby
59
59
  class SingersController < ApplicationController
60
60
  represents :json, :entity => MusicianRepresenter, :collection => MusicianCollectionRepresenter
61
61
  ```
62
62
 
63
+ You might pass strings as representer names to `::represents`, they will be constantized at run-time when needed.
64
+
65
+
63
66
  ### Old API Support
64
67
 
65
68
  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.
@@ -110,16 +113,22 @@ consume!(singer, :represent_with => MusicianRepresenter)
110
113
 
111
114
  ## Using Decorators
112
115
 
113
- If you prefer roar's decorator approach over extend, just go for it. roar-rails will figure out automatically which represent strategy to use.
116
+ If you prefer roar's decorator approach over extend, just go for it. roar-rails will figure out automatically which represent strategy to use. Be sure to use roar >= 0.11.17.
114
117
 
115
118
  ```ruby
116
119
  class SingerRepresenter < Roar::Decorator
117
120
  include Roar::Representer::JSON
118
121
 
119
122
  property :name
123
+
124
+ link :self do
125
+ singer_url(represented)
126
+ end
120
127
  end
121
128
  ```
122
129
 
130
+ In decorators' link blocks you currently have to use `represented` to get the actual represented model (this is `self` in module representers).
131
+
123
132
  ## Passing Options
124
133
 
125
134
  Both rendering and consuming support passing user options to the representer.
@@ -4,3 +4,4 @@ source "http://rubygems.org"
4
4
  gemspec :path => '../'
5
5
 
6
6
  gem 'railties', '~> 3.0.11'
7
+ gem 'roar', ">= 0.11.17"
@@ -4,3 +4,4 @@ source "http://rubygems.org"
4
4
  gemspec :path => '../'
5
5
 
6
6
  gem 'railties', '~> 3.2.0'
7
+ gem 'roar', ">= 0.11.17"
@@ -7,3 +7,4 @@ gem 'railties', '~> 4.0.0.beta1'
7
7
  gem 'actionpack', '~> 4.0.0.beta1'
8
8
 
9
9
  gem 'activemodel', '~> 4.0.0.beta1'
10
+ gem 'roar', ">= 0.11.17"
data/lib/roar-rails.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "roar/rails/version"
2
2
  require "roar/representer"
3
+ require "roar/decorator"
3
4
  require "roar/rails/railtie"
4
5
 
5
6
  module Roar::Representer
@@ -10,7 +11,6 @@ module Roar::Representer
10
11
  autoload("HAL", "roar/representer/json/hal")
11
12
  end
12
13
 
13
-
14
14
  module Feature
15
15
  autoload("Hypermedia", "roar/representer/feature/hypermedia")
16
16
  end
@@ -8,7 +8,7 @@ module Roar::Rails
8
8
  included do
9
9
  extend Hooks::InheritableAttribute
10
10
  inheritable_attr :represents_options
11
- self.represents_options ||= {}
11
+ self.represents_options ||= RepresenterComputer.new
12
12
  end
13
13
 
14
14
 
@@ -17,20 +17,8 @@ module Roar::Rails
17
17
  Class.new(super).send :include, Roar::Rails::Responder
18
18
  end
19
19
 
20
- def add_representer_suffix(prefix)
21
- "#{prefix}Representer"
22
- end
23
-
24
20
  def represents(format, options)
25
- unless options.is_a?(Hash)
26
- model = options
27
- options = {
28
- :entity => add_representer_suffix(model.name).constantize,
29
- :collection => add_representer_suffix(model.name.pluralize).constantize
30
- }
31
- end
32
-
33
- represents_options[format] = options
21
+ represents_options.add(format,options)
34
22
  respond_to format
35
23
  end
36
24
  end
@@ -51,7 +39,7 @@ module Roar::Rails
51
39
 
52
40
  # Central entry-point for finding the appropriate representer.
53
41
  def representer_for(format, model, options={})
54
- options.delete(:represent_with) || representer_name_for(format, model)
42
+ options.delete(:represent_with) || self.class.represents_options.for(format, model, controller_path)
55
43
  end
56
44
 
57
45
  private
@@ -63,15 +51,44 @@ module Roar::Rails
63
51
  request.body.read
64
52
  end
65
53
 
66
- def representer_name_for(format, model) # DISCUSS: should we pass and process options here?
67
- if self.class.represents_options[format.to_sym].blank? # TODO: test to_sym?
68
- model_name = model.class.name
69
- model_name = controller_path.camelize if model.kind_of?(Array)
70
- return self.class.add_representer_suffix(model_name).constantize
54
+
55
+ class RepresenterComputer < Hash
56
+ def add(format, opts)
57
+ # FIXME: use controller_path here as well!
58
+ # by pre-computing the representer name we allow "one-step inheritance": if B doesn't call ::represents it "inherits" A's settings.
59
+ unless opts.is_a?(Hash)
60
+ model = opts
61
+ opts = {
62
+ :entity => add_representer_suffix(model.name),
63
+ :collection => add_representer_suffix(model.name.pluralize)
64
+ }
65
+ end
66
+
67
+ self[format] = opts
71
68
  end
72
69
 
73
- return self.class.represents_options[format][:collection] if model.kind_of?(Array)
74
- self.class.represents_options[format][:entity]
70
+ def for(*args)
71
+ name = name_for(*args) or return
72
+
73
+ return name if name.is_a?(Module) # i hate is_a? but this is really handy here.
74
+ name.constantize
75
+ end
76
+
77
+ private
78
+ def name_for(format, model, controller_path) # DISCUSS: should we pass and process options here?
79
+ if self[format.to_sym].blank? # TODO: test to_sym?
80
+ model_name = model.class.name
81
+ model_name = controller_path.camelize if model.kind_of?(Array)
82
+ return add_representer_suffix(model_name).constantize
83
+ end
84
+
85
+ return self[format][:collection] if model.kind_of?(Array)
86
+ self[format][:entity]
87
+ end
88
+
89
+ def add_representer_suffix(prefix)
90
+ "#{prefix}Representer"
91
+ end
75
92
  end
76
93
  end
77
94
  end
@@ -1,5 +1,5 @@
1
1
  module Roar
2
2
  module Rails
3
- VERSION = "0.0.14"
3
+ VERSION = "0.0.15"
4
4
  end
5
5
  end
@@ -0,0 +1,10 @@
1
+ Band = Struct.new(:name)
2
+
3
+ class BandsController < ActionController::Base
4
+ include Roar::Rails::ControllerAdditions
5
+ represents :json, Band
6
+
7
+ def show
8
+ respond_with Band.new("Bodyjar")
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ class BandRepresenter < Roar::Decorator
2
+ include Roar::Representer::JSON
3
+ include Roar::Representer::Feature::Hypermedia
4
+
5
+ property :name
6
+
7
+ link :self do
8
+ band_url(represented.name)
9
+ end
10
+ end
@@ -29,6 +29,6 @@ Dummy::Application.configure do
29
29
  # This is necessary if your schema can't be completely dumped by the schema dumper,
30
30
  # like if you have constraints or database-specific column types
31
31
  # config.active_record.schema_format = :sql
32
-
33
- config.representer.default_url_options = {:host => "http://roar.apotomo.de"}
32
+
33
+ config.representer.default_url_options = {:host => "roar.apotomo.de"}
34
34
  end
@@ -4,4 +4,5 @@ Dummy::Application.routes.draw do
4
4
  put ':controller(/:action(/:id(.:format)))'
5
5
  delete ':controller(/:action(/:id(.:format)))'
6
6
  resources :singers
7
+ resources :bands
7
8
  end
@@ -0,0 +1,73 @@
1
+ require 'test_helper'
2
+
3
+ module ObjectRepresenter
4
+ end
5
+ module ObjectsRepresenter
6
+ end
7
+
8
+ class RepresenterComputerTest < MiniTest::Spec
9
+ let (:subject) { Roar::Rails::ControllerAdditions::RepresenterComputer.new }
10
+
11
+ describe "nothing configured" do
12
+
13
+
14
+ it "uses model class" do
15
+ subject.for(:json, Singer.new, "bands").must_equal SingerRepresenter
16
+ end
17
+
18
+ it "uses plural controller name when collection" do
19
+ subject.for(:json, [Singer.new], "objects").must_equal ObjectsRepresenter
20
+ end
21
+ end
22
+
23
+ describe "represents :json, Singer" do
24
+ before { subject.add(:json, Object) }
25
+
26
+ it "uses defined class for item" do
27
+ subject.for(:json, Singer.new, "bands").must_equal ObjectRepresenter
28
+ end
29
+
30
+ it "uses plural name when collection" do
31
+ subject.for(:json, [], "bands").must_equal ObjectsRepresenter
32
+ end
33
+ end
34
+
35
+ describe "represents :json, :entity => SingerRepresenter" do
36
+ before { subject.add(:json, :entity => ObjectRepresenter) }
37
+
38
+ it "returns :entity representer constant" do
39
+ subject.for(:json, Singer.new, "bands").must_equal ObjectRepresenter
40
+ end
41
+
42
+ it "doesn't infer collection representer" do
43
+ subject.for(:json, [], "bands").must_equal nil
44
+ end
45
+ end
46
+
47
+ describe "represents :json, :entity => SingerRepresenter, :collection => SingersRepresenter" do
48
+ before { subject.add(:json, :entity => ObjectRepresenter,
49
+ :collection => SingersRepresenter) }
50
+
51
+ it "returns :entity representer constant" do
52
+ subject.for(:json, Singer.new, "bands").must_equal ObjectRepresenter
53
+ end
54
+
55
+ it "doesn't infer collection representer" do
56
+ subject.for(:json, [], "bands").must_equal SingersRepresenter
57
+ end
58
+ end
59
+
60
+ describe "#add" do
61
+ it "doesn't constantize" do
62
+ subject.add(:json, :entity => "ObjectRepresenter")
63
+ subject.send(:name_for, :json, Object.new, "bands").must_equal "ObjectRepresenter"
64
+ end
65
+ end
66
+
67
+ describe "#for" do
68
+ it "constantizes strings" do
69
+ subject.add(:json, :entity => "ObjectRepresenter")
70
+ subject.for(:json, Object.new, "bands").must_equal ObjectRepresenter
71
+ end
72
+ end
73
+ end
@@ -7,12 +7,12 @@ class RepresenterTest < ActionController::TestCase
7
7
 
8
8
  test "representers can use URL helpers" do
9
9
  get :show, :id => "bumi"
10
- assert_body "{\"name\":\"Bumi\",\"links\":[{\"rel\":\"self\",\"href\":\"http://http://roar.apotomo.de/singers/Bumi\"}]}"
10
+ assert_body "{\"name\":\"Bumi\",\"links\":[{\"rel\":\"self\",\"href\":\"http://roar.apotomo.de/singers/Bumi\"}]}"
11
11
  end
12
12
 
13
13
  test "it works with uninitialized config.representer.default_url_options" do
14
14
  url_options = Rails.application.config.representer.default_url_options
15
-
15
+
16
16
  begin
17
17
  Rails.application.config.representer.default_url_options = nil
18
18
  assert_raises RuntimeError, ArgumentError do
@@ -21,7 +21,19 @@ class RepresenterTest < ActionController::TestCase
21
21
  assert $!.message =~ /Missing host to link to/
22
22
  rescue
23
23
  ensure
24
- Rails.application.config.representer.default_url_options = url_options
24
+ Rails.application.config.representer.default_url_options = url_options
25
25
  end
26
26
  end
27
27
  end
28
+
29
+
30
+ class DecoratorTest < ActionController::TestCase
31
+ include Roar::Rails::TestCase
32
+
33
+ tests BandsController
34
+
35
+ test "it renders URLs using the decorator" do
36
+ get :show, :id => 1, :format => :json
37
+ assert_body "{\"name\":\"Bodyjar\",\"links\":[{\"rel\":\"self\",\"href\":\"http://roar.apotomo.de/bands/Bodyjar\"}]}"
38
+ end
39
+ end
@@ -11,96 +11,6 @@ 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
-
104
14
 
105
15
  class ResponderTest < ActionController::TestCase
106
16
  include Roar::Rails::TestCase
@@ -114,15 +24,26 @@ class ResponderTest < ActionController::TestCase
114
24
  end
115
25
  end
116
26
 
117
- class UniqueRepresentsOptionsTest < ResponderTest
27
+ class UniqueRepresentsOptionsTest < MiniTest::Spec
118
28
  class One < BaseController
119
29
  represents :json, Object
120
30
  end
121
31
  class Two < BaseController
122
32
  represents :json, Singer
123
33
  end
124
- test "each subclass of a roar-augmented controller can represent different things" do
125
- assert_not_equal One.represents_options, Two.represents_options
34
+
35
+ it "each subclass of a roar-augmented controller can represent different things" do
36
+ One.represents_options.wont_equal Two.represents_options
37
+ end
38
+
39
+ it "does not share RepresenterComputer instances when inheriting" do
40
+ Class.new(One) do
41
+ represents :json, Singer
42
+ end.represents_options.wont_equal One.represents_options
43
+ end
44
+
45
+ it "inherits when subclass doesn't call ::represents" do
46
+ Class.new(One).represents_options.must_equal One.represents_options
126
47
  end
127
48
  end
128
49
 
@@ -139,7 +60,7 @@ class ResponderTest < ActionController::TestCase
139
60
  respond_with singer
140
61
  end
141
62
 
142
- assert_equal singer.to_json, @response.body
63
+ @response.body.must_equal singer.to_json
143
64
  end
144
65
 
145
66
  test "responder finds SingersRepresenter for collections by convention" do
@@ -209,7 +130,7 @@ class ResponderTest < ActionController::TestCase
209
130
  respond_with singer
210
131
  end
211
132
 
212
- assert_equal singer.to_json, @response.body
133
+ @response.body.must_equal singer.to_json
213
134
  end
214
135
 
215
136
  test "responder uses configured representer for collection" do
@@ -259,13 +180,13 @@ class ResponderTest < ActionController::TestCase
259
180
 
260
181
  class PassingUserOptionsTest < ResponderTest
261
182
  # FIXME: should be in generic roar-rails test.
262
- module SingerRepresenter
183
+ module DynamicSingerRepresenter
263
184
  include Roar::Representer::JSON
264
185
  property :name, :setter => lambda { |val, opts| self.name = "#{opts[:title]} #{val}" },
265
186
  :getter => lambda { |opts| "#{opts[:title]} #{name}" }
266
187
  end
267
188
  class MusicianController < BaseController
268
- represents :json, :entity => SingerRepresenter, :collection => SingersRepresenter
189
+ represents :json, :entity => DynamicSingerRepresenter, :collection => SingersRepresenter
269
190
  end
270
191
 
271
192
  tests MusicianController
@@ -281,7 +202,7 @@ class ResponderTest < ActionController::TestCase
281
202
 
282
203
  test "passes options to explicit collection representer" do
283
204
  get do
284
- respond_with [Singer.new("Bumi"), Singer.new("Iggy")], :title => "Mr.", :represent_items_with => SingerRepresenter
205
+ respond_with [Singer.new("Bumi"), Singer.new("Iggy")], :title => "Mr.", :represent_items_with => DynamicSingerRepresenter
285
206
  end
286
207
 
287
208
  @response.body.must_equal("[{\"name\":\"Mr. Bumi\"},{\"name\":\"Mr. Iggy\"}]")
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.14
4
+ version: 0.0.15
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-04-29 00:00:00.000000000 Z
12
+ date: 2013-05-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: roar
@@ -186,8 +186,10 @@ files:
186
186
  - test/consume_test.rb
187
187
  - test/dummy/Rakefile
188
188
  - test/dummy/app/controllers/application_controller.rb
189
+ - test/dummy/app/controllers/bands_controller.rb
189
190
  - test/dummy/app/controllers/singers_controller.rb
190
191
  - test/dummy/app/helpers/application_helper.rb
192
+ - test/dummy/app/representers/band_representer.rb
191
193
  - test/dummy/app/representers/singer_alias_representer.rb
192
194
  - test/dummy/app/representers/singer_representer.rb
193
195
  - test/dummy/app/views/layouts/application.html.erb
@@ -219,6 +221,7 @@ files:
219
221
  - test/dummy/script/rails
220
222
  - test/dummy/tmp/app/cells/blog/post/latest.html.erb
221
223
  - test/dummy/tmp/app/cells/blog/post_cell.rb
224
+ - test/representer_computer_test.rb
222
225
  - test/representer_test.rb
223
226
  - test/responder_test.rb
224
227
  - test/test_case_test.rb