roar 0.9.1 → 0.9.2

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.9.2
2
+
3
+ * Using representable-1.1.
4
+
1
5
  h2. 0.9.1
2
6
 
3
7
  * Removed @Representer#to_attributes@ and @#from_attributes@.
data/README.textile CHANGED
@@ -4,6 +4,7 @@ _Resource-Oriented Architectures in Ruby._
4
4
 
5
5
  "Lets make documents suit our models and not models fit to documents."
6
6
 
7
+ Questions? Need help? Free 1st Level Support on irc.freenode.org#roar !
7
8
 
8
9
  h2. Introduction
9
10
 
@@ -61,13 +62,22 @@ Representers are the key ingredience in Roar, so let's check them out!
61
62
 
62
63
  h2. Representers
63
64
 
64
- To render a representational document, the backend service has to define a representer.
65
+ Representers are most usable when defined in a module, and then mixed into a host class. In our example, the host class is the article.
66
+
67
+ <pre>
68
+ class Article
69
+ attr_accessor :title, :id
70
+ end
71
+ </pre>
72
+
73
+ To render a representational document from the article, the backend service has to define a representer.
65
74
 
66
75
  <pre>
67
76
  require 'roar/representer/json'
68
77
  require 'roar/representer/feature/hypermedia'
69
78
 
70
- class Article
79
+
80
+ module ArticleRepresenter
71
81
  include Roar::Representer::JSON
72
82
  include Roar::Representer::Feature::Hypermedia
73
83
 
@@ -75,22 +85,22 @@ class Article
75
85
  property :id
76
86
 
77
87
  link :self do
78
- article_url(represented)
88
+ article_url(self)
79
89
  end
80
90
  end
81
91
  </pre>
82
92
 
83
- Hooray, we can define plain properties and embedd links easily - and we can even use URL helpers (in Rails). There's even more, nesting, collections, but more on that later!
93
+ Hooray, we can define plain properties and embedd links easily - and we can even use URL helpers (in Rails, using the "roar-rails gem":https://github.com/apotonick/roar-rails). There's even more, nesting, collections, but more on that later!
84
94
 
85
95
 
86
96
  h3. Rendering Representations in the Service
87
97
 
88
98
  In order to *render* an actual document, the backend service would have to do a few steps: creating a representer, filling in data, and then serialize it.
89
99
 
90
- <pre>Article.new(
91
- title: "Lonestar",
92
- id: 666).
93
- to_json # => "{\"article\":{\"id\":666, ...
100
+ <pre>loney = Article.new.extend(ArticleRepresenter)
101
+ loney.title = "Lonestar"
102
+ loney.id = 666
103
+ loney.to_json # => "{\"article\":{\"id\":666, ...
94
104
  </pre>
95
105
 
96
106
  Articles itself are useless, so they may be placed into orders. This is the next example.
@@ -120,10 +130,18 @@ What if we wanted to check an existing order? We'd @GET http://orders/1@, right?
120
130
  }
121
131
  </pre>
122
132
 
133
+ The order model is simple.
134
+
135
+ <pre>
136
+ class Order
137
+ attr_accessor :id, :client_id, :articles
138
+ end
139
+ </pre>
140
+
123
141
  Since orders may contain a composition of articles, how would the order service define its representer?
124
142
 
125
143
  <pre>
126
- class Order
144
+ module OrderRepresenter
127
145
  include Roar::Representer::JSON
128
146
  include Roar::Representer::Feature::Hypermedia
129
147
 
@@ -142,6 +160,8 @@ class Order
142
160
  end
143
161
  </pre>
144
162
 
163
+ Representers don't have to be in modules, but can be
164
+
145
165
  The declarative @#collection@ method lets us define compositions of representers.
146
166
 
147
167
 
@@ -153,12 +173,13 @@ If we were to implement an endpoint for creating new orders, we'd allow POST to
153
173
 
154
174
  <pre>
155
175
  post "/orders" do
156
- incoming = Order.deserialize(request.body.string)
157
- puts incoming.to_attributes #=> {:client_id => 815}
176
+ order = Order.new.extend(OrderRepresenter)
177
+ order.from_json(request.body.string)
178
+ order.to_json
158
179
  end
159
180
  </pre>
160
181
 
161
- Look how the @#to_attributes@ method helps extracting data from the incoming document and, again, @#to_json@ returns the freshly created order's representation. Roar's representers are truely working in both directions, rendering and parsing and thus prevent you from redundant knowledge sharing.
182
+ Look how the @#from_json@ method helps extracting data from the incoming document and, again, @#to_json@ returns the freshly created order's representation. Roar's representers are truely working in both directions, rendering and parsing and thus prevent you from redundant knowledge sharing.
162
183
 
163
184
 
164
185
  h2. Representers in the Client
data/Rakefile CHANGED
@@ -10,9 +10,3 @@ Rake::TestTask.new(:test) do |test|
10
10
  test.test_files = FileList['test/*_test.rb'] - ['test/integration_test.rb', 'test/active_record_integration_test.rb']
11
11
  test.verbose = true
12
12
  end
13
-
14
- Rake::TestTask.new(:testrails) do |test|
15
- test.libs << 'test'
16
- test.test_files = FileList['test/rails/*_test.rb']
17
- test.verbose = true
18
- end
@@ -27,7 +27,7 @@ module Roar
27
27
  # and updates properties accordingly.
28
28
  def post(url, format)
29
29
  # DISCUSS: what if a redirect happens here?
30
- document = http.post_uri(url, serialize, format).body
30
+ document = http.post_uri(url, serialize(:links => false), format).body
31
31
  deserialize(document)
32
32
  end
33
33
 
data/lib/roar/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Roar
2
- VERSION = "0.9.1"
2
+ VERSION = "0.9.2"
3
3
  end
data/roar.gemspec CHANGED
@@ -19,9 +19,9 @@ Gem::Specification.new do |s|
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
21
 
22
- s.add_runtime_dependency "representable", "~> 1.0.1"
22
+ s.add_runtime_dependency "representable", "~> 1.1"
23
23
 
24
24
  s.add_development_dependency "test_xml"
25
- s.add_development_dependency "minitest", "~> 1.6.0"
26
- s.add_development_dependency "sinatra", "~> 1.2.6"
25
+ s.add_development_dependency "minitest", ">= 2.8.1"
26
+ s.add_development_dependency "sinatra", "~> 1.2.6"
27
27
  end
data/test/Gemfile CHANGED
@@ -1,7 +1,4 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gem "sinatra"
4
- gem "sinatra-contrib", :path => "./sinatra-contrib"
5
- gem "sinatra-activerecord"
6
4
  gem "roar", :path => ".."
7
- gem "representable", :path => "../../representable"
data/test/fake_server.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  require "bundler/setup"
2
2
  require 'sinatra/base'
3
- require 'sinatra/reloader'
4
-
5
3
 
6
4
  class FakeServer < Sinatra::Base
7
5
  get "/method" do
data/test/test_helper.rb CHANGED
@@ -8,29 +8,6 @@ require 'roar/representer'
8
8
  require 'roar/representer/feature/hypermedia'
9
9
  require 'roar/representer/feature/http_verbs'
10
10
 
11
- # TODO: 2BRM.
12
- module TestModel
13
- def self.included(base)
14
- base.extend ClassMethods
15
- end
16
-
17
-
18
- module ClassMethods
19
- def accessors(*names)
20
- names.each do |name|
21
- attr_accessor name
22
- end
23
- end
24
- end
25
-
26
- attr_accessor :attributes
27
-
28
- def initialize(attributes={})
29
- attributes.each do |k,v|
30
- send("#{k}=", v)
31
- end
32
- end
33
- end
34
11
 
35
12
  module AttributesContructor
36
13
  def initialize(attrs={})
@@ -40,33 +17,19 @@ module AttributesContructor
40
17
  end
41
18
  end
42
19
 
43
-
44
-
45
20
  class Item
46
- include TestModel
47
- accessors :value
48
-
49
- def self.model_name
50
- "item"
51
- end
21
+ include AttributesContructor
22
+ attr_accessor :value
52
23
  end
53
24
 
54
25
  class Position
55
- include TestModel
56
- accessors :id, :item
57
-
58
- def self.model_name
59
- :order
60
- end
26
+ include AttributesContructor
27
+ attr_accessor :id, :item
61
28
  end
62
29
 
63
30
  class Order
64
- include TestModel
65
- accessors :id, :items
66
-
67
- def self.model_name
68
- :order
69
- end
31
+ include AttributesContructor
32
+ attr_accessor :id, :items
70
33
  end
71
34
 
72
35
  require "test_xml/mini_test"
@@ -42,12 +42,8 @@ end
42
42
 
43
43
  class XMLRepresenterFunctionalTest < MiniTest::Spec
44
44
  class GreedyOrder
45
- include TestModel
46
- accessors :id, :items
47
-
48
- def self.model_name
49
- :order
50
- end
45
+ include AttributesContructor
46
+ attr_accessor :id, :items
51
47
  end
52
48
 
53
49
  class TestXmlRepresenter
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 9
8
- - 1
9
- version: 0.9.1
8
+ - 2
9
+ version: 0.9.2
10
10
  platform: ruby
11
11
  authors:
12
12
  - Nick Sutterer
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-12-28 00:00:00 +01:00
17
+ date: 2012-02-06 00:00:00 +01:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -27,9 +27,8 @@ dependencies:
27
27
  - !ruby/object:Gem::Version
28
28
  segments:
29
29
  - 1
30
- - 0
31
30
  - 1
32
- version: 1.0.1
31
+ version: "1.1"
33
32
  type: :runtime
34
33
  version_requirements: *id001
35
34
  - !ruby/object:Gem::Dependency
@@ -51,13 +50,13 @@ dependencies:
51
50
  requirement: &id003 !ruby/object:Gem::Requirement
52
51
  none: false
53
52
  requirements:
54
- - - ~>
53
+ - - ">="
55
54
  - !ruby/object:Gem::Version
56
55
  segments:
56
+ - 2
57
+ - 8
57
58
  - 1
58
- - 6
59
- - 0
60
- version: 1.6.0
59
+ version: 2.8.1
61
60
  type: :development
62
61
  version_requirements: *id003
63
62
  - !ruby/object:Gem::Dependency
@@ -93,9 +92,6 @@ files:
93
92
  - TODO.markdown
94
93
  - lib/roar.rb
95
94
  - lib/roar/rails.rb
96
- - lib/roar/rails/controller_methods.rb
97
- - lib/roar/rails/representer_methods.rb
98
- - lib/roar/rails/test_case.rb
99
95
  - lib/roar/representer.rb
100
96
  - lib/roar/representer/feature/http_verbs.rb
101
97
  - lib/roar/representer/feature/hypermedia.rb
@@ -1,71 +0,0 @@
1
- require 'active_support/core_ext/class/attribute'
2
-
3
- module Roar
4
- module Rails
5
- module ControllerMethods
6
- extend ActiveSupport::Concern
7
-
8
- included do |base|
9
- base.responder = Responder
10
- base.class_attribute :represented_class
11
- end
12
-
13
- module ClassMethods
14
- # Sets the represented class for the controller.
15
- def represents(model_class)
16
- self.represented_class = model_class
17
- end
18
- end
19
-
20
- #private
21
- def representer_class_for(model_class, format)
22
- # DISCUSS: upcase and static namespace is not cool, but works for now.
23
- "Representer::#{format.to_s.upcase}::#{model_class}".constantize
24
- end
25
-
26
- # Returns a representer instance that has parsed the request body.
27
- def incoming
28
- representer = representer_class_for(self.class.represented_class, formats.first).deserialize(request.raw_post)
29
- end
30
-
31
-
32
- # Returns the deserialized representation as a hash suitable for #create and #update_attributes.
33
- def representation
34
- incoming.to_nested_attributes
35
- end
36
-
37
-
38
- class Responder < ActionController::Responder
39
- def display(resource, given_options={})
40
- # TODO: find the correct representer for #format.
41
- # TODO: should we infer the represented class per default?
42
- # TODO: unit-test this method.
43
- #representer = controller.representer_class_for(resource.class, format)
44
- representer = controller.representer_class_for(controller.represented_class, format)
45
-
46
- # DISCUSS: do that here?
47
- #representer.extend(RepresenterMethods::ClassMethods)
48
-
49
- controller.render given_options.merge!(options).merge!(
50
- format => representer.serialize_model_with_controller(resource, controller)
51
- )
52
- end
53
-
54
- # This is the common behavior for formats associated with APIs, such as :xml and :json.
55
- def api_behavior(error)
56
- if has_errors?
57
- controller.render :text => resource.errors, :status => :unprocessable_entity # TODO: which media format? use an ErrorRepresenter shipped with Roar.
58
- elsif get?
59
- display resource
60
- elsif post?
61
- display resource, :status => :created, :location => api_location
62
- elsif put?
63
- display resource
64
- else
65
- head :ok
66
- end
67
- end
68
- end
69
- end
70
- end
71
- end
@@ -1,53 +0,0 @@
1
- module Roar
2
- module Rails
3
- # Makes Rails URL helpers work in representers. Dependent on Rails.application.
4
- module RepresenterMethods
5
- extend ActiveSupport::Concern
6
-
7
- included do |base|
8
- base.class_eval do
9
- attr_accessor :_controller
10
- delegate :request, :env, :to => :_controller
11
-
12
- include ActionController::UrlFor
13
- include ::Rails.application.routes.url_helpers
14
-
15
- extend Conventions
16
- end
17
- end
18
-
19
- module ClassMethods
20
- # TODO: test?
21
- def for_model_with_controller(represented, controller)
22
- # DISCUSS: use #for_model_attributes for overriding?
23
- from_attributes(compute_attributes_for(represented)) do |rep|
24
- rep.represented = represented
25
- rep._controller = controller
26
- end
27
- end
28
-
29
- # TODO: test?
30
- def serialize_model_with_controller(represented, controller)
31
- for_model_with_controller(represented, controller).serialize
32
- end
33
- end
34
-
35
- # Introduces strongly opinionated convenience methods in Representer.
36
- module Conventions
37
- def representation_name
38
- super.to_s.singularize
39
- end
40
-
41
- def collection(name, options={})
42
- namespace = self.name.split("::")[-2] # FIXME: this assumption is pretty opinionated.
43
- singular_name = name.to_s.singularize
44
-
45
- super name, options.reverse_merge(
46
- :class => "representer/#{namespace}/#{singular_name}".classify.constantize,
47
- #:tag => singular_name # FIXME: how/where to decide if singular TAG or not?
48
- )
49
- end
50
- end
51
- end
52
- end
53
- end
@@ -1,69 +0,0 @@
1
- #require 'test_xml/test_unit'
2
- require 'action_controller/test_case'
3
-
4
- module Roar::Rails
5
- module TestCase
6
- def process(action, *args)
7
- raise
8
- if args.first.is_a?(String)
9
- puts "YO"
10
- request.env['RAW_POST_DATA'] = args.shift
11
- method = args.pop
12
- args << nil
13
- args << method
14
- end
15
-
16
- super
17
- end
18
-
19
- def assert_response(status, headers={}) # FIXME: allow message.
20
- super
21
-
22
- if headers.is_a?(Hash)
23
- assert_headers(headers)
24
- else
25
- assert_body(headers)
26
- end
27
- end
28
-
29
- def assert_headers(headers)
30
- headers.each_pair do |k,v|
31
- assert_equal v, @response.headers[k]
32
- end
33
- end
34
-
35
- def assert_body(body, options={})
36
- return assert_xml_equal body, @response.body if options[:format] == :xml # FIXME: how do we know whether assert_xml is appropriate?
37
- assert_equal body, @response.body
38
- end
39
- end
40
- end
41
-
42
-
43
- ActionController::TestCase::Behavior.class_eval do
44
- # FIXME: ugly monkey-patching.
45
- # TODO: test:
46
- # put :create
47
- # put :create, :format => :xml
48
- # put :create, "<order/>", :format => :xml
49
- # put :create, "<order/>"
50
-
51
-
52
- include Roar::Rails::TestCase
53
- end
54
-
55
- RSpec::Rails::ControllerExampleGroup.class_eval do
56
- #include Roar::Rails::TestCase
57
- # FIXME: include module!
58
-
59
- def process(action, *args)
60
- if args.first.is_a?(String)
61
- request.env['RAW_POST_DATA'] = args.shift
62
- method = args.pop
63
- args << nil
64
- args << method
65
- end
66
-
67
- super
68
- end
69
- end