roar-rails 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
+ - 2.0.0
4
5
  gemfile:
5
6
  - Gemfile
6
7
  - gemfiles/Gemfile.rails3-0
data/CHANGES.markdown CHANGED
@@ -1,3 +1,7 @@
1
+ h2. 0.1.1
2
+
3
+ * Added the `represented_formats: [...]` option to be passed to `#respond_with` for either suppressing roar-rails from extending/decorating models when rendering or to fine-tune so this will only happen on white-listed formats as `:hal`. This can also be set globally using `config.representer.represented_formats`.
4
+
1
5
  h2. 0.1.0
2
6
 
3
7
  * `ActiveRecord::Relation` is now detected as a collection and the appropriate representer should be found.
data/Gemfile CHANGED
@@ -4,5 +4,7 @@ source "http://rubygems.org"
4
4
  gemspec
5
5
 
6
6
  group :test do
7
- gem 'roar', ">= 0.11.17"
7
+ #gem 'roar', path: "../roar" #">= 0.11.17"
8
+ #gem 'representable', path: "../representable"
9
+ gem 'rake', '10.1.0'
8
10
  end
data/README.markdown CHANGED
@@ -145,6 +145,16 @@ Same goes with `#consume!`, passing options to `from_json`.
145
145
  consume! Singer.new, :current_user => current_user
146
146
  ```
147
147
 
148
+ Note: If you pass in options to a representer, you must process them youself. For rendering, use `:getter` in the representer.
149
+
150
+ ```ruby
151
+ property :username, getter: lambda { |args| args[:current_user].name }
152
+ ```
153
+
154
+ That'll render the `current_user`'s name as the `username` property.
155
+
156
+ More docs about passing and processing option can be found [here](https://github.com/apotonick/representable/#passing-options).
157
+
148
158
 
149
159
  ## URL Helpers
150
160
 
@@ -168,6 +178,39 @@ config.representer.default_url_options = {:host => "127.0.0.1:3000"}
168
178
 
169
179
  Attention: If you are using representers from a gem your Rails URL helpers might not work in these modules. This is due to a [loading order problem](https://groups.google.com/forum/?fromgroups#!topic/rubyonrails-core/5tG5unZ8jDQ) in Rails. As a workaround, don't require the representers in the gem but load them as lately as possible, usually it works when you require in the controller. We are working on fixing that problem.
170
180
 
181
+ ## Representing Formats Exclusively
182
+
183
+ By default, roar-rails will extend/decorate any model passed to `respond_with` for any request format. When adding roar-rails to a legacy project, you might want to restrict roar-rails' representing and fall back to the old behavior for certain formats. This can be configured both globally and on a per action basis.
184
+
185
+ To restrict representing globally to a particular format you can set the `config.representer.represented_formats` in your environment's configuration to an array of formats. For example the following will only represent hal and json requests.
186
+
187
+ ```ruby
188
+ config.representer.represented_formats = [:hal, :json]
189
+ ```
190
+
191
+ The global configuration (or lack thereof) can be overridden by supplying the `:represented_formats` array when calling `respond_with`. The following will only represent `@resource` for the hal format in the `show` action. For any other format, it will expose the resource using Rails' old behavior.
192
+
193
+
194
+ ```ruby
195
+ class MyController < ApplicationController
196
+ def show
197
+ ...
198
+ respond_with @resource, :represented_formats => [:hal]
199
+ end
200
+ end
201
+ ```
202
+
203
+ You can entirely suppress roar-rails in `respond_with` by passing in an empty array.
204
+
205
+ ```ruby
206
+ class MyController < ApplicationController
207
+ def show
208
+ ...
209
+ respond_with @resource, :represented_formats => []
210
+ end
211
+ end
212
+ ```
213
+
171
214
  ## Testing
172
215
 
173
216
  ## Autoloading
@@ -183,4 +226,4 @@ Put your representers in `app/representers` and they will be autoloaded by Rails
183
226
 
184
227
  ## License
185
228
 
186
- Roar-rails is released under the [MIT License](http://www.opensource.org/licenses/MIT).
229
+ Roar-rails is released under the [MIT License](http://www.opensource.org/licenses/MIT).
@@ -3,5 +3,8 @@ source "http://rubygems.org"
3
3
  # Specify your gem's dependencies in roar-rails.gemspec
4
4
  gemspec :path => '../'
5
5
 
6
- gem 'railties', '~> 3.2.0'
6
+
7
+ gem 'railties', '~> 3.2.13'
8
+ gem 'activerecord', '~> 3.2.13'
9
+
7
10
  gem 'roar', ">= 0.11.17"
@@ -3,10 +3,10 @@ source "http://rubygems.org"
3
3
  # Specify your gem's dependencies in roar-rails.gemspec
4
4
  gemspec :path => '../'
5
5
 
6
- gem 'railties', '~> 4.0.0.beta1'
7
- gem 'actionpack', '~> 4.0.0.beta1'
6
+ gem 'railties', '~> 4.0.0'
7
+ gem 'actionpack', '~> 4.0.0'
8
8
 
9
- gem 'activemodel', '~> 4.0.0.beta1'
10
- gem 'activerecord', '~> 4.0.0.beta1'
9
+ gem 'activemodel', '~> 4.0.0'
10
+ gem 'activerecord', '~> 4.0.0'
11
11
 
12
12
  gem 'roar', ">= 0.11.17"
data/lib/roar-rails.rb CHANGED
@@ -8,7 +8,7 @@ module Roar::Representer
8
8
  autoload("JSON", "roar/representer/json")
9
9
 
10
10
  module JSON
11
- autoload("HAL", "roar/representer/json/hal")
11
+ autoload("HAL", "roar/rails/hal")
12
12
  end
13
13
 
14
14
  module Feature
@@ -51,6 +51,15 @@ module Roar::Rails
51
51
  request.body.read
52
52
  end
53
53
 
54
+ def render_to_body(options)
55
+ if res = options[formats.first] and res.is_a?(Roar::Rails::Responder::Response)
56
+ response.content_type = res.content_type
57
+ return res.body
58
+ end
59
+
60
+ super
61
+ end
62
+
54
63
 
55
64
  class RepresenterComputer < Hash
56
65
  def add(format, opts)
@@ -0,0 +1,6 @@
1
+ require 'roar/representer/json/hal'
2
+
3
+ Roar::Representer::JSON::HAL.class_eval do
4
+ def to_hal(*args); to_json(*args); end
5
+ def from_hal(*args); from_json(*args); end
6
+ end
@@ -1,23 +1,54 @@
1
1
  module Roar::Rails
2
2
  module Responder
3
3
  def display(model, *args)
4
- if representer = options.delete(:represent_items_with)
5
- render_items_with(model, representer) # convenience API, not recommended since it's missing hypermedia.
6
- return super
7
- end
4
+ if represent_format?
5
+ handle_lonely_collection!(model) and return super # :represent_items_with
8
6
 
9
- model = prepare_model_for(format, model, options)
7
+ model = prepare_model_for(format, model, options)
8
+ end
10
9
 
11
- super
10
+ super(create_response(model), *args) # AC::Responder: this calls controller.render which calls render_to_body which calls renderer[:json] which calls model.to_json.
12
11
  end
13
12
 
14
13
  private
15
- def render_items_with(collection, representer)
14
+
15
+ def default_represented_formats
16
+ Rails.application.config.representer.represented_formats
17
+ end
18
+
19
+ # First check for user option overrides, then check for defaults, then trigger
20
+ # previous behavior and always represent for the requested format.
21
+ def represent_format?
22
+ formats = Array((options[:represented_formats] || default_represented_formats || [format]))
23
+ formats.include?(format)
24
+ end
25
+
26
+ def resourceful?
27
+ # FIXME: find out if we have a representer? what about old behaviour?
28
+ #resource.respond_to?("to_#{format}")
29
+ true
30
+ end
31
+
32
+ def create_response(model)
33
+ render_method = "to_#{format}"
34
+ media_format = Mime[format]
35
+ Response.new(model.send(render_method, options), media_format)
36
+ end
37
+
38
+ def handle_lonely_collection!(collection)
39
+ return false unless representer = options.delete(:represent_items_with)
40
+
41
+ setup_items_with(collection, representer) # convenience API, not recommended since it's missing hypermedia.
42
+ true
43
+ end
44
+
45
+ def setup_items_with(collection, representer)
16
46
  collection.map! do |mdl| # DISCUSS: i don't like changing the method argument here.
17
47
  representer.prepare(mdl).to_hash(options) # FIXME: huh? and what about XML?
18
48
  end
19
49
  end
20
50
 
51
+
21
52
  module PrepareModel
22
53
  def prepare_model_for(format, model, options) # overwritten in VersionStrategy/3.0.
23
54
  controller.prepare_model_for(format, model, options)
@@ -25,5 +56,15 @@ module Roar::Rails
25
56
  end
26
57
  include PrepareModel
27
58
  include VersionStrategy
59
+
60
+
61
+ class Response
62
+ attr_reader :content_type, :body # DISCUSS: how to add more header fields? #headers?
63
+
64
+ def initialize(body, content_type)
65
+ @body = body
66
+ @content_type = content_type
67
+ end
68
+ end
28
69
  end
29
70
  end
@@ -1,5 +1,5 @@
1
1
  module Roar
2
2
  module Rails
3
- VERSION = "0.1.0"
3
+ VERSION = "0.1.1"
4
4
  end
5
5
  end
data/roar-rails.gemspec CHANGED
@@ -19,7 +19,6 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ["lib"]
20
20
 
21
21
  s.add_runtime_dependency "roar", "~> 0.11.13"
22
- s.add_runtime_dependency "representable", "~> 1.4"
23
22
  s.add_runtime_dependency "test_xml", ">= 0.1.6" # TODO: remove dependency as most people don't use XML.
24
23
  s.add_runtime_dependency "actionpack"
25
24
  s.add_runtime_dependency "railties", ">= 3.0.0"
@@ -0,0 +1,35 @@
1
+ require 'test_helper'
2
+
3
+ Mime::Type.register 'application/json+hal', :hal
4
+ class HalRendererTest < ActionController::TestCase
5
+ include Roar::Rails::TestCase
6
+
7
+ class SingersController < ActionController::Base
8
+ module HalSingerRepresenter
9
+ include Roar::Representer::JSON::HAL
10
+
11
+ property :name
12
+ link(:self) { "http://#{name}" }
13
+ end
14
+
15
+ include Roar::Rails::ControllerAdditions
16
+ represents :hal, :entity => HalSingerRepresenter
17
+
18
+ def show
19
+ singer = Musician.new("Bumi")
20
+ respond_with singer
21
+ end
22
+ end
23
+
24
+ tests SingersController
25
+
26
+ test "should render correctly in response to a application/json+hal request" do
27
+ get :show, :id => "bumi", :format => :hal
28
+ assert_body '{"name":"Bumi","_links":{"self":{"href":"http://Bumi"}}}'
29
+ end
30
+
31
+ test "should have a content_type of application/json+hal" do
32
+ get :show, :id => "bumi", :format => :hal
33
+ assert_equal response.content_type, 'application/json+hal'
34
+ end
35
+ end
@@ -45,6 +45,117 @@ class ResponderTest < ActionController::TestCase
45
45
  end
46
46
  end
47
47
 
48
+ class JsonResponseTest < ResponderTest
49
+ SingerRepresenter = ::SingerRepresenter
50
+ class SingersController < BaseController
51
+ end
52
+ tests SingersController
53
+
54
+ test "set Content-type to json" do
55
+ get do
56
+ singer = Singer.new("Bumi")
57
+ respond_with singer
58
+ end
59
+
60
+ @response.body.must_equal "{\"name\":\"Bumi\",\"links\":[{\"rel\":\"self\",\"href\":\"http://roar.apotomo.de/singers/Bumi\"}]}"
61
+ @response.headers["Content-Type"].must_match "application/json"
62
+ end
63
+ end
64
+
65
+ class SuppressingRepresenterForFormatTest < ResponderTest
66
+ class SingersController < BaseController
67
+ end
68
+ tests SingersController
69
+
70
+ test "returns non-represented json of model by falling back to Rails default responding when supressed in respond_with" do
71
+ singer = Singer.new('Bumi')
72
+
73
+ get do
74
+ respond_with singer, :represented_formats => []
75
+ end
76
+
77
+ assert_equal bumi_json, @response.body
78
+ end
79
+ end
80
+
81
+ class ProvidingRepresenterForFormatTest < ResponderTest
82
+ SingerRepresenter = ::SingerRepresenter
83
+ class SingersController < BaseController
84
+ represents :json, :entity => SingerRepresenter
85
+ end
86
+ tests SingersController
87
+
88
+ test "returns represented json of model when set in respond_with" do
89
+ singer = Singer.new('Bumi')
90
+
91
+ get do
92
+ respond_with singer, :represented_formats => [:json]
93
+ end
94
+
95
+ assert_equal %{{"name":"Bumi","links":[{"rel":"self","href":"http://roar.apotomo.de/singers/Bumi"}]}}, @response.body
96
+ end
97
+
98
+ test "return represented json model by using configured default" do
99
+ singer = Singer.new('Bumi')
100
+
101
+ Rails.application.config.representer.represented_formats = [:json]
102
+ get do
103
+ respond_with singer
104
+ end
105
+ Rails.application.config.representer.represented_formats = nil
106
+
107
+ assert_equal %{{"name":"Bumi","links":[{"rel":"self","href":"http://roar.apotomo.de/singers/Bumi"}]}}, @response.body
108
+ end
109
+
110
+ test "return non-represented json model by falling back to Rails default responding when supressed in the configuration" do
111
+ singer = Singer.new('Bumi')
112
+
113
+ Rails.application.config.representer.represented_formats = []
114
+ get do
115
+ respond_with singer
116
+ end
117
+ Rails.application.config.representer.represented_formats = nil
118
+
119
+ assert_equal bumi_json, @response.body
120
+ end
121
+
122
+ test "represented_formats passed to respond_with override global directive" do
123
+ singer = Singer.new('Bumi')
124
+
125
+ Rails.application.config.representer.represented_formats = [:hal]
126
+ get do
127
+ respond_with singer, :represented_formats => [:json]
128
+ end
129
+ Rails.application.config.representer.represented_formats = nil
130
+
131
+ assert_equal %{{"name":"Bumi","links":[{"rel":"self","href":"http://roar.apotomo.de/singers/Bumi"}]}}, @response.body
132
+ end
133
+ end
134
+
135
+ class XmlResponseTest < ResponderTest
136
+ module SingerRepresenter
137
+ include Roar::Representer::XML
138
+
139
+ property :name
140
+ self.representation_wrap = :singer
141
+ end
142
+
143
+ class SingersController < BaseController
144
+ respond_to :xml
145
+ end
146
+ tests SingersController
147
+
148
+ test "set Content-type to xml" do
149
+ get :xml do
150
+ singer = Singer.new("Bumi")
151
+ respond_with singer
152
+ end
153
+
154
+ @response.body.must_equal_xml '<singer><name>Bumi</name></singer>'
155
+ @response.headers["Content-Type"].must_match "application/xml"
156
+ end
157
+ end
158
+
48
159
  class UnconfiguredControllerTest < ResponderTest
49
160
  SingersRepresenter = ::SingersRepresenter
50
161
  SingerRepresenter = ::SingerRepresenter
@@ -60,7 +171,7 @@ class ResponderTest < ActionController::TestCase
60
171
  respond_with singer
61
172
  end
62
173
 
63
- @response.body.must_equal singer.to_json
174
+ @response.body.must_equal "{\"name\":\"Bumi\",\"links\":[{\"rel\":\"self\",\"href\":\"http://roar.apotomo.de/singers/Bumi\"}]}"
64
175
  end
65
176
 
66
177
  test "responder finds SingersRepresenter for collections by convention" do
@@ -95,7 +206,7 @@ class ResponderTest < ActionController::TestCase
95
206
  respond_with singer, :represent_with => SingerRepresenter
96
207
  end
97
208
 
98
- assert_equal singer.to_json, @response.body
209
+ @response.body.must_equal "{\"name\":\"Bumi\",\"links\":[{\"rel\":\"self\",\"href\":\"http://roar.apotomo.de/singers/Bumi\"}]}"
99
210
  end
100
211
 
101
212
  test "responder uses passed representer for collection" do
@@ -130,7 +241,7 @@ class ResponderTest < ActionController::TestCase
130
241
  respond_with singer
131
242
  end
132
243
 
133
- @response.body.must_equal singer.to_json
244
+ @response.body.must_equal "{\"name\":\"Bumi\",\"links\":[{\"rel\":\"self\",\"href\":\"http://roar.apotomo.de/singers/Bumi\"}]}"
134
245
  end
135
246
 
136
247
  test "responder uses configured representer for collection" do
@@ -224,21 +335,37 @@ class ResponderTest < ActionController::TestCase
224
335
  end
225
336
  end
226
337
 
338
+ class FallbackTest < ResponderTest
339
+ class MusicianController < BaseController
340
+ represents :json, Object
341
+ end
227
342
 
343
+ tests MusicianController
228
344
 
229
- def get(&block)
345
+ test "passes options to entity representer" do
346
+ get do
347
+ render :text => "Rendered template"
348
+ end
349
+
350
+ @response.body.must_equal("Rendered template")
351
+ end
352
+ end
353
+
354
+
355
+
356
+ def get(format=:json, &block)
230
357
  @controller.instance_eval do
231
358
  @block = block
232
359
  end
233
360
 
234
- super :execute, :format => 'json'
361
+ super :execute, :format => format
235
362
  end
236
363
 
237
- def put(body="", &block)
364
+ def put(body="", format=:json, &block)
238
365
  @controller.instance_eval do
239
366
  @block = block
240
367
  end
241
- super :execute, body, :format => 'json'
368
+ super :execute, body, :format => format
242
369
  end
243
370
 
244
371
  def singer(name="Bumi")
@@ -249,4 +376,9 @@ class ResponderTest < ActionController::TestCase
249
376
  def singers
250
377
  [singer("Bumi"), singer("Bjork"), singer("Sinead")]
251
378
  end
379
+
380
+ def bumi_json
381
+ return "[\"Bumi\"]" if Roar::Rails.rails3_0?
382
+ "{\"name\":\"Bumi\"}"
383
+ end
252
384
  end
data/test/test_helper.rb CHANGED
@@ -1,8 +1,6 @@
1
- require 'bundler'
2
- Bundler.setup
3
-
4
1
  require 'test/unit'
5
2
  require 'minitest/spec'
3
+ require 'test_xml/mini_test'
6
4
 
7
5
  ENV['RAILS_ENV'] = 'test'
8
6
  require "dummy/config/environment"
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.1.0
4
+ version: 0.1.1
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-05-16 00:00:00.000000000 Z
12
+ date: 2013-07-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: roar
@@ -27,22 +27,6 @@ dependencies:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
29
  version: 0.11.13
30
- - !ruby/object:Gem::Dependency
31
- name: representable
32
- requirement: !ruby/object:Gem::Requirement
33
- none: false
34
- requirements:
35
- - - ~>
36
- - !ruby/object:Gem::Version
37
- version: '1.4'
38
- type: :runtime
39
- prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ~>
44
- - !ruby/object:Gem::Version
45
- version: '1.4'
46
30
  - !ruby/object:Gem::Dependency
47
31
  name: test_xml
48
32
  requirement: !ruby/object:Gem::Requirement
@@ -206,6 +190,7 @@ files:
206
190
  - gemfiles/Gemfile.rails4-0
207
191
  - lib/roar-rails.rb
208
192
  - lib/roar/rails/controller_additions.rb
193
+ - lib/roar/rails/hal.rb
209
194
  - lib/roar/rails/rails3_0_strategy.rb
210
195
  - lib/roar/rails/rails3_1_strategy.rb
211
196
  - lib/roar/rails/railtie.rb
@@ -254,6 +239,7 @@ files:
254
239
  - test/dummy/script/rails
255
240
  - test/dummy/tmp/app/cells/blog/post/latest.html.erb
256
241
  - test/dummy/tmp/app/cells/blog/post_cell.rb
242
+ - test/json_hal_renderer_test.rb
257
243
  - test/representer_computer_test.rb
258
244
  - test/representer_test.rb
259
245
  - test/responder_test.rb