roar 0.11.8 → 0.11.9

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,8 @@
1
+ ## 0.11.9
2
+
3
+ * When using `Feature::Client` hyperlinks are no longer rendered in POST and PUT since we pass `links: false`.
4
+ * `Transport::NetHttp` now sets both `Accept:` and `Content-type:` header since Rails services seems to get confused.
5
+
1
6
  ## 0.11.8
2
7
 
3
8
  * Fixed `JSON::HAL::Links` so that it keys links with `links` and not `_links`. The latter is still done by `JSON::HAL`.
data/Gemfile CHANGED
@@ -3,7 +3,7 @@ source "http://rubygems.org"
3
3
  # Specify your gem's dependencies in roar.gemspec
4
4
  gemspec
5
5
 
6
- gem "representable", "~> 1.2.9"
6
+ gem "representable", ">= 1.3.1"
7
7
 
8
8
 
9
9
  group :test do
data/TODO.markdown CHANGED
@@ -1 +1,3 @@
1
1
  * Add proxies, so nested models can be lazy-loaded.
2
+ * move #prepare_links! call to #_links or something so it doesn't need to be called in #serialize.
3
+ * alias Roar::Representer to Representer
@@ -2,7 +2,7 @@ source "http://rubygems.org"
2
2
 
3
3
  gemspec :path => '../'
4
4
 
5
- gem 'representable', '1.3.0'
5
+ gem 'representable', '>= 1.3.1'
6
6
  group :test do
7
7
  gem 'faraday'
8
8
  gem 'turn'
@@ -26,6 +26,10 @@ module Roar
26
26
  end
27
27
  end
28
28
  end
29
+
30
+ def before_serialize(options={})
31
+ super({:links => false}.merge!(options))
32
+ end
29
33
  end
30
34
  end
31
35
  end
@@ -57,9 +57,7 @@ module Roar
57
57
  def links_array=(ary)
58
58
  # FIXME: move to LinkCollection
59
59
  self.links= LinkCollection.new
60
- ary.each do |lnk|
61
- self.links[lnk.rel.to_s] = lnk
62
- end
60
+ ary.each { |lnk| links.add(lnk) }
63
61
  end
64
62
 
65
63
  private
@@ -69,12 +67,19 @@ module Roar
69
67
 
70
68
  # Setup hypermedia links by invoking their blocks. Usually called by #serialize.
71
69
  def prepare_links!(*args)
72
- links_definition.each do |config| # config is [{..}, block]
70
+ # TODO: move this method to _links or something so it doesn't need to be called in #serialize.
71
+ compile_links_for(links_definition, *args).each do |lnk|
72
+ links.add(lnk) # TODO: move to LinkCollection.new.
73
+ end
74
+ end
75
+
76
+ def compile_links_for(configs, *args)
77
+ configs.collect do |config|
73
78
  options, block = config.first, config.last
74
79
  href = run_link_block(block, *args) or next
75
80
 
76
- links.add(prepare_link_for(href, options))
77
- end
81
+ prepare_link_for(href, options)
82
+ end.compact # FIXME: make this less ugly.
78
83
  end
79
84
 
80
85
  def prepare_link_for(href, options)
@@ -93,7 +98,7 @@ module Roar
93
98
  self.fetch(rel.to_s, nil)
94
99
  end
95
100
 
96
- def add(link) # FIXME: use Hash API.
101
+ def add(link)
97
102
  self[link.rel.to_s] = link
98
103
  end
99
104
  end
@@ -0,0 +1,208 @@
1
+ module Roar::Representer::JSON
2
+ # Implementation of the Collection+JSON media format, http://amundsen.com/media-types/collection/format/
3
+ #
4
+ # When clients want to add or update an item the collection's filled out template is POSTed or PUT. You can parse that using the
5
+ # +SongCollectionRepresenter::template_representer+ module.
6
+ #
7
+ # Song.new.extend(SongCollectionRepresenter.template_representer).from_json("{template: ...")
8
+ module CollectionJSON
9
+ def self.included(base)
10
+ base.class_eval do
11
+ include Roar::Representer
12
+ include Roar::Representer::JSON
13
+ include Roar::Representer::Feature::Hypermedia
14
+
15
+ extend ClassMethods
16
+
17
+ self.representation_wrap= :collection # FIXME: move outside of this block for inheritance!
18
+
19
+ property :__template, :as => :template, :extend => lambda {|*| representable_attrs.collection_representers[:template] }, :class => Array
20
+ def __template
21
+ OpenStruct.new # TODO: handle preset values.
22
+ end
23
+
24
+ collection :queries, :extend => Roar::Representer::JSON::HyperlinkRepresenter
25
+ def queries
26
+ compile_links_for(representable_attrs.collection_representers[:queries].representable_attrs.first)
27
+ end
28
+ def queries=(v)
29
+ end
30
+
31
+
32
+ def items
33
+ self
34
+ end
35
+ def items=(v)
36
+ replace(v)
37
+ end
38
+
39
+ property :__version, :as => :version
40
+ def __version
41
+ representable_attrs.collection_representers[:version]
42
+ end
43
+
44
+ property :__href, :as => :href
45
+ def __href
46
+ compile_links_for(representable_attrs.collection_representers[:href].representable_attrs.first).first.href
47
+ end
48
+
49
+
50
+
51
+ include ClientMethods
52
+ end
53
+ end
54
+
55
+ module ClassMethods
56
+ module PropertyWithRenderNil
57
+ def property(name, options={})
58
+ super(name, options.merge!(:render_nil => true))
59
+ end
60
+ end
61
+
62
+ # TODO: provide automatic copying from the ItemRepresenter here.
63
+ def template(&block)
64
+ mod = representable_attrs.collection_representers[:object_template] = Module.new do
65
+ include Roar::Representer::JSON
66
+ include Roar::Representer::JSON::CollectionJSON::DataMethods
67
+
68
+ extend PropertyWithRenderNil
69
+
70
+ module_exec(&block)
71
+
72
+ #self.representation_wrap = :template
73
+ def from_hash(hash, *args) # overridden in :template representer.
74
+ super(hash["template"])
75
+ end
76
+ end
77
+
78
+ representable_attrs.collection_representers[:template] = Module.new do
79
+ include Roar::Representer::JSON
80
+ include mod
81
+
82
+ #self.representation_wrap = false
83
+
84
+ # DISCUSS: currently we skip real deserialization here and just store the :data hash.
85
+ def from_hash(hash, *args)
86
+ replace(hash["data"])
87
+ end
88
+ end
89
+ end
90
+ def template_representer
91
+ representable_attrs.collection_representers[:object_template]
92
+ end
93
+
94
+ def queries(&block)
95
+ mod = representable_attrs.collection_representers[:queries] = Module.new do
96
+ include Roar::Representer::JSON
97
+ include Roar::Representer::Feature::Hypermedia
98
+
99
+ module_exec(&block)
100
+
101
+ def to_hash(*)
102
+ hash = super
103
+ hash["links"] # TODO: make it easier to render collection of links.
104
+ end
105
+ end
106
+ end
107
+
108
+ def items(options={}, &block)
109
+ collection :items, { :extend => lambda {|*| representable_attrs.collection_representers[:items] } }.merge!(options)
110
+
111
+ mod = representable_attrs.collection_representers[:items] = Module.new do
112
+ include Roar::Representer::JSON
113
+ include Roar::Representer::Feature::Hypermedia
114
+ include Roar::Representer::JSON::CollectionJSON::DataMethods
115
+ extend SharedClassMethodsBullshit
116
+
117
+ module_exec(&block)
118
+
119
+ # TODO: share with main module!
120
+ property :__href, :as => :href
121
+ def __href
122
+ compile_links_for(representable_attrs.collection_representers[:href].representable_attrs.first).first.href
123
+ end
124
+ def __href=(v)
125
+ @__href = Roar::Representer::Feature::Hypermedia::Hyperlink.new(:href => v)
126
+ end
127
+ def href
128
+ @__href
129
+ end
130
+ end
131
+ end
132
+
133
+ def version(v)
134
+ representable_attrs.collection_representers[:version] = v
135
+ end
136
+
137
+ module SharedClassMethodsBullshit
138
+ def href(&block)
139
+ mod = representable_attrs.collection_representers[:href] = Module.new do
140
+ include Roar::Representer::JSON
141
+ include Roar::Representer::Feature::Hypermedia
142
+
143
+
144
+ link(:href, &block)
145
+ end
146
+ end
147
+
148
+ def representable_attrs
149
+ super.tap do |attrs|
150
+ attrs.instance_eval do # FIXME: of course, this is WIP.
151
+ def collection_representers
152
+ @collection_representers ||= {}
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+ include SharedClassMethodsBullshit
159
+ end
160
+
161
+ module DataMethods
162
+ def to_hash(*)
163
+ hash = super.tap do |hsh|
164
+ data = []
165
+ hsh.keys.each do |n|
166
+ next if ["href", "links"].include?(n)
167
+
168
+ v = hsh.delete(n.to_s)
169
+ data << {:name => n, :value => v} # TODO: get :prompt from Definition.
170
+ end
171
+ hsh[:data] = data
172
+ end
173
+ end
174
+
175
+ def from_hash(hash, *args)
176
+ data = {}
177
+ hash.delete("data").collect do |item|
178
+ data[item["name"]] = item["value"]
179
+ end
180
+ super(hash.merge!(data))
181
+ end
182
+ end
183
+
184
+ module ClientMethods
185
+ def __version=(v)
186
+ @__version = v
187
+ end
188
+ def version
189
+ @__version
190
+ end
191
+
192
+ def __href=(v)
193
+ @__href = Roar::Representer::Feature::Hypermedia::Hyperlink.new(:href => v)
194
+ end
195
+ def href
196
+ @__href
197
+ end
198
+
199
+ def __template=(v)
200
+ @__template = v
201
+ end
202
+ # DISCUSS: this might return a Template instance, soon.
203
+ def template
204
+ @__template
205
+ end
206
+ end
207
+ end
208
+ end
@@ -35,6 +35,7 @@ module Roar
35
35
  http = Net::HTTP.new(uri.host, uri.port)
36
36
  req = what.new(uri.request_uri)
37
37
  req.content_type = as
38
+ req["accept"] = as # TODO: test me. # DISCUSS: if Accept is not set, rails treats this request as as "text/html".
38
39
  req.body = body if body
39
40
  http.request(req)
40
41
  end
data/lib/roar/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Roar
2
- VERSION = "0.11.8"
2
+ VERSION = "0.11.9"
3
3
  end
data/test/client_test.rb CHANGED
@@ -2,23 +2,29 @@ require 'test_helper'
2
2
  require 'roar/representer/feature/client'
3
3
 
4
4
  class ClientTest < MiniTest::Spec
5
- describe "Client" do
6
- before do
7
- @representer = Module.new do
8
- include Roar::Representer
9
- property :name
10
- property :band
11
- end
5
+ representer_for([Roar::Representer]) do
6
+ property :name
7
+ property :band
8
+ end
9
+
10
+ let(:song) { Object.new.extend(rpr).extend(Roar::Representer::Feature::Client) }
11
+
12
+ it "adds accessors" do
13
+ song.name = "Social Suicide"
14
+ song.band = "Bad Religion"
15
+ assert_equal "Social Suicide", song.name
16
+ assert_equal "Bad Religion", song.band
17
+ end
12
18
 
13
- @song = Object.new.extend(@representer)
19
+ describe "links" do
20
+ representer_for([Roar::Representer::JSON, Roar::Representer::Feature::Hypermedia]) do
21
+ property :name
22
+ link(:self) { never_call_me! }
14
23
  end
15
24
 
16
- it "should add accessors" do
17
- @song.extend Roar::Representer::Feature::Client
18
- @song.name = "Social Suicide"
19
- @song.band = "Bad Religion"
20
- assert_equal "Social Suicide", @song.name
21
- assert_equal "Bad Religion", @song.band
25
+ it "suppresses rendering" do
26
+ song.name = "Silenced"
27
+ song.to_json.must_equal %{{\"name\":\"Silenced\",\"links\":[]}}
22
28
  end
23
29
  end
24
30
  end
@@ -0,0 +1,130 @@
1
+ require 'test_helper'
2
+ require 'roar/representer/json/collection_json'
3
+
4
+ class CollectionJsonTest < MiniTest::Spec
5
+ let(:song) { OpenStruct.new(:title => "scarifice", :length => 43) }
6
+
7
+ representer_for([Roar::Representer::JSON::CollectionJSON]) do
8
+ version "1.0"
9
+ href { "//songs/" }
10
+
11
+ link(:feed) { "//songs/feed" }
12
+
13
+ items(:class => Song) do
14
+ href { "//songs/scarifice" }
15
+
16
+ property :title, :prompt => "Song title"
17
+ property :length, :prompt => "Song length"
18
+
19
+ link(:download) { "//songs/scarifice.mp3" }
20
+ link(:stats) { "//songs/scarifice/stats" }
21
+ end
22
+
23
+ template do
24
+ property :title, :prompt => "Song title"
25
+ property :length, :prompt => "Song length"
26
+ end
27
+
28
+ queries do
29
+ link :search do
30
+ {:href => "//search", :data => [{:name => "q", :value => ""}]}
31
+ end
32
+ end
33
+ end
34
+
35
+ describe "#to_json" do
36
+ it "renders document" do
37
+ [song].extend(rpr).to_hash.must_equal(
38
+ {
39
+ :collection=>{
40
+ "version"=>"1.0",
41
+ "href"=>"//songs/",
42
+
43
+ "template"=>{
44
+ :data=>[
45
+ {:name=>"title", :value=>nil},
46
+ {:name=>"length", :value=>nil}
47
+ ]
48
+ },
49
+
50
+ "queries"=>[
51
+ {:rel=>:search, :href=>"//search",
52
+ :data=>[
53
+ {:name=>"q", :value=>""}
54
+ ]
55
+ }
56
+ ],
57
+
58
+ "items"=>[
59
+ {
60
+ "links"=>[
61
+ {:rel=>:download, :href=>"//songs/scarifice.mp3"},
62
+ {:rel=>:stats, :href=>"//songs/scarifice/stats"}
63
+ ],
64
+ "href"=>"//songs/scarifice",
65
+ :data=>[
66
+ {:name=>"title", :value=>"scarifice"},
67
+ {:name=>"length", :value=>43}
68
+ ]
69
+ }
70
+ ],
71
+
72
+ "links"=>[
73
+ {:rel=>:feed, :href=>"//songs/feed"}
74
+ ]
75
+ }
76
+ })# %{{"collection":{"version":"1.0","href":"//songs/","items":[{"href":"//songs/scarifice","links":[{"rel":"download","href":"//songs/scarifice.mp3"},{"rel":"stats","href":"//songs/scarifice/stats"}],"data":[{"name":"title","value":"scarifice"},{"name":"length","value":43}]}],"template":{"data":[{"name":"title","value":null},{"name":"length","value":null}]},"queries":[{"rel":"search","href":"//search","data":[{"name":"q","value":""}]}],"links":[{"rel":"feed","href":"//songs/feed"}]}}}
77
+ end
78
+ end
79
+
80
+ describe "#from_json" do
81
+ subject { [].extend(rpr).from_json [song].extend(rpr).to_json }
82
+
83
+ it "provides #version" do
84
+ subject.version.must_equal "1.0"
85
+ end
86
+
87
+ it "provides #href" do
88
+ subject.href.must_equal link(:href => "//songs/")
89
+ end
90
+
91
+ it "provides #template" do
92
+ # DISCUSS: this might return a Template instance, soon.
93
+ subject.template.must_equal([
94
+ {"name"=>"title", "value"=>nil},
95
+ {"name"=>"length", "value"=>nil}])
96
+ end
97
+
98
+ it "provides #queries" do
99
+ # DISCUSS: this might return CollectionJSON::Hyperlink instances that support some kind of substitution operation for the :data attribute.
100
+ # FIXME: this is currently _not_ parsed!
101
+ subject.queries.must_equal([link(:rel => :search, :href=>"//search", :data=>[{:name=>"q", :value=>""}])])
102
+ end
103
+
104
+ it "provides #items" do
105
+ subject.items.must_equal([Song.new(:title => "scarifice", :length => "43")])
106
+ song = subject.items.first
107
+ song.title.must_equal "scarifice"
108
+ song.length.must_equal 43
109
+ song.links.must_equal("download" => link({:rel=>:download, :href=>"//songs/scarifice.mp3"}), "stats" => link({:rel=>:stats, :href=>"//songs/scarifice/stats"}))
110
+ song.href.must_equal link(:href => "//songs/scarifice")
111
+ end
112
+
113
+ it "provides #links" do
114
+ subject.links.must_equal({"feed" => link(:rel => :feed, :href => "//songs/feed")})
115
+ end
116
+ end
117
+
118
+ describe "template_representer#from_json" do
119
+ it "parses object" do
120
+ song = OpenStruct.new.extend(rpr.template_representer).from_hash(
121
+ "template"=>{
122
+ "data"=>[
123
+ {"name"=>"title", "value"=>"Black Star"},
124
+ {"name"=>"length", "value"=>"4.53"}
125
+ ]
126
+ })
127
+ song.title.must_equal "Black Star"
128
+ end
129
+ end
130
+ end
data/test/test_helper.rb CHANGED
@@ -26,6 +26,7 @@ module AttributesConstructor # TODO: remove me.
26
26
  end
27
27
  end
28
28
 
29
+ # FIXME: provide a real #== for OpenStruct.
29
30
  class Song < OpenStruct
30
31
  def ==(other)
31
32
  name == other.name and track == other.track
@@ -55,7 +56,7 @@ MiniTest::Spec.class_eval do
55
56
  Module.new do
56
57
  include *modules.reverse
57
58
 
58
- instance_exec(&block)
59
+ module_exec(&block)
59
60
  end
60
61
  end
61
62
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roar
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.8
4
+ version: 0.11.9
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-01-25 00:00:00.000000000 Z
12
+ date: 2013-02-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: representable
@@ -98,6 +98,7 @@ files:
98
98
  - lib/roar/representer/feature/http_verbs.rb
99
99
  - lib/roar/representer/feature/hypermedia.rb
100
100
  - lib/roar/representer/json.rb
101
+ - lib/roar/representer/json/collection_json.rb
101
102
  - lib/roar/representer/json/hal.rb
102
103
  - lib/roar/representer/transport/faraday.rb
103
104
  - lib/roar/representer/transport/net_http.rb
@@ -107,6 +108,7 @@ files:
107
108
  - test/Gemfile
108
109
  - test/client_test.rb
109
110
  - test/coercion_feature_test.rb
111
+ - test/collection_json_test.rb
110
112
  - test/fake_server.rb
111
113
  - test/faraday_http_transport_test.rb
112
114
  - test/hal_json_test.rb