roar-rails 0.0.14 → 0.0.15

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