roar 0.12.9 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +8 -10
  4. data/CHANGES.markdown +24 -2
  5. data/Gemfile +5 -2
  6. data/README.markdown +132 -28
  7. data/TODO.markdown +9 -7
  8. data/examples/example.rb +2 -0
  9. data/examples/example_server.rb +19 -3
  10. data/gemfiles/Gemfile.representable-2.0 +5 -0
  11. data/gemfiles/Gemfile.representable-2.1 +5 -0
  12. data/lib/roar.rb +1 -1
  13. data/lib/roar/client.rb +38 -0
  14. data/lib/roar/{representer/feature/coercion.rb → coercion.rb} +2 -1
  15. data/lib/roar/decorator.rb +3 -11
  16. data/lib/roar/http_verbs.rb +88 -0
  17. data/lib/roar/hypermedia.rb +174 -0
  18. data/lib/roar/json.rb +55 -0
  19. data/lib/roar/json/collection.rb +3 -0
  20. data/lib/roar/{representer/json → json}/collection_json.rb +20 -20
  21. data/lib/roar/{representer/json → json}/hal.rb +33 -31
  22. data/lib/roar/json/hash.rb +3 -0
  23. data/lib/roar/json/json_api.rb +208 -0
  24. data/lib/roar/representer.rb +3 -36
  25. data/lib/roar/transport/faraday.rb +49 -0
  26. data/lib/roar/transport/net_http.rb +57 -0
  27. data/lib/roar/transport/net_http/request.rb +72 -0
  28. data/lib/roar/version.rb +1 -1
  29. data/lib/roar/xml.rb +54 -0
  30. data/roar.gemspec +5 -4
  31. data/test/client_test.rb +3 -3
  32. data/test/coercion_feature_test.rb +6 -3
  33. data/test/collection_json_test.rb +8 -10
  34. data/test/decorator_test.rb +27 -15
  35. data/test/faraday_http_transport_test.rb +13 -15
  36. data/test/hal_json_test.rb +16 -16
  37. data/test/hal_links_test.rb +3 -3
  38. data/test/http_verbs_test.rb +17 -22
  39. data/test/hypermedia_feature_test.rb +23 -45
  40. data/test/hypermedia_test.rb +11 -23
  41. data/test/integration/band_representer.rb +2 -2
  42. data/test/integration/runner.rb +4 -3
  43. data/test/integration/server.rb +13 -2
  44. data/test/integration/ssl_server.rb +1 -1
  45. data/test/json_api_test.rb +336 -0
  46. data/test/json_representer_test.rb +16 -12
  47. data/test/lib/runner.rb +134 -0
  48. data/test/lonely_test.rb +9 -0
  49. data/test/net_http_transport_test.rb +4 -4
  50. data/test/representer_test.rb +2 -2
  51. data/test/{lib/roar/representer/transport/net_http/request_test.rb → ssl_client_certs_test.rb} +43 -5
  52. data/test/test_helper.rb +12 -5
  53. data/test/xml_representer_test.rb +26 -166
  54. metadata +49 -29
  55. data/gemfiles/Gemfile.representable-1.7 +0 -6
  56. data/gemfiles/Gemfile.representable-1.8 +0 -6
  57. data/lib/roar/representer/feature/client.rb +0 -39
  58. data/lib/roar/representer/feature/http_verbs.rb +0 -95
  59. data/lib/roar/representer/feature/hypermedia.rb +0 -175
  60. data/lib/roar/representer/json.rb +0 -67
  61. data/lib/roar/representer/transport/faraday.rb +0 -50
  62. data/lib/roar/representer/transport/net_http.rb +0 -59
  63. data/lib/roar/representer/transport/net_http/request.rb +0 -75
  64. data/lib/roar/representer/xml.rb +0 -61
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec path: '../'
4
+
5
+ gem 'representable', '~> 2.0.0'
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec path: '../'
4
+
5
+ gem 'representable', '~> 2.1.0'
data/lib/roar.rb CHANGED
@@ -1,4 +1,4 @@
1
-
1
+ require 'roar/version'
2
2
  module Roar
3
3
  def self.root
4
4
  File.expand_path '../..', __FILE__
@@ -0,0 +1,38 @@
1
+ require "roar/http_verbs"
2
+
3
+ module Roar
4
+ # Automatically add accessors for properties and collections. Also mixes in HttpVerbs.
5
+ module Client
6
+ include HttpVerbs
7
+
8
+ def self.extended(base)
9
+ base.instance_eval do
10
+ representable_attrs.each do |attr|
11
+ name = attr.name
12
+ next if name == "links" # ignore hyperlinks.
13
+
14
+ # TODO: could anyone please make this better?
15
+ instance_eval %Q{
16
+ def #{name}=(v)
17
+ @#{name} = v
18
+ end
19
+
20
+ def #{name}
21
+ @#{name}
22
+ end
23
+ }
24
+ end
25
+ end
26
+ end
27
+
28
+ def to_hash(options={})
29
+ options[:links] ||= false
30
+ super(options)
31
+ end
32
+
33
+ def to_xml(options={}) # sorry, but i'm not even sure if anyone uses this module.
34
+ options[:links] ||= false
35
+ super(options)
36
+ end
37
+ end
38
+ end
@@ -1,7 +1,8 @@
1
+ gem 'virtus'
1
2
  require 'virtus'
2
3
  require 'representable/coercion'
3
4
 
4
- module Roar::Representer::Feature
5
+ module Roar
5
6
  # Use the +:type+ option to specify the conversion type.
6
7
  # class ImmigrantSong
7
8
  # include Roar::Representer::JSON
@@ -2,18 +2,10 @@ require 'roar/representer'
2
2
  require 'representable/decorator'
3
3
 
4
4
  class Roar::Decorator < Representable::Decorator
5
- extend Roar::Representer::InheritableArray
6
-
7
5
  module HypermediaConsumer
8
- def links_array=(*args)
9
- super # TODO: this currently sets #links which is not obvious.
10
- represented.links = links
11
- end
12
-
13
- # TODO: what is the deal with #links_array and #links?
14
- def links=(*args)
15
- super
16
- represented.links = links
6
+ def links=(arr)
7
+ links = super
8
+ represented.instance_variable_set :@links, links
17
9
  end
18
10
  end
19
11
  end
@@ -0,0 +1,88 @@
1
+ require 'roar/transport/net_http'
2
+
3
+ module Roar
4
+ # Gives HTTP-power to representers. They can serialize, send, process and deserialize HTTP-requests.
5
+ module HttpVerbs
6
+
7
+ class << self
8
+ attr_accessor :transport_engine
9
+
10
+ def included(base)
11
+ base.extend ClassMethods
12
+ end
13
+ end
14
+ self.transport_engine = ::Roar::Transport::NetHTTP
15
+
16
+
17
+ module ClassMethods
18
+ # GETs +url+ with +format+ and returns deserialized represented object.
19
+ def get(*args)
20
+ new.get(*args)
21
+ end
22
+ end
23
+
24
+
25
+ attr_writer :transport_engine
26
+ def transport_engine
27
+ @transport_engine || HttpVerbs.transport_engine
28
+ end
29
+
30
+ # Serializes the object, POSTs it to +url+ with +format+, deserializes the returned document
31
+ # and updates properties accordingly.
32
+ def post(options={}, &block)
33
+ response = http.post_uri(options.merge(:body => serialize), &block)
34
+ handle_response(response)
35
+ end
36
+
37
+ # GETs +url+ with +format+, deserializes the returned document and updates properties accordingly.
38
+ def get(options={}, &block)
39
+ response = http.get_uri(options, &block)
40
+ handle_response(response)
41
+ end
42
+
43
+ # Serializes the object, PUTs it to +url+ with +format+, deserializes the returned document
44
+ # and updates properties accordingly.
45
+ def put(options={}, &block)
46
+ response = http.put_uri(options.merge(:body => serialize), &block)
47
+ handle_response(response)
48
+ self
49
+ end
50
+
51
+ def patch(options={}, &block)
52
+ response = http.patch_uri(options.merge(:body => serialize), &block)
53
+ handle_response(response)
54
+ self
55
+ end
56
+
57
+ def delete(options, &block)
58
+ http.delete_uri(options, &block)
59
+ self
60
+ end
61
+
62
+ private
63
+ def handle_response(response)
64
+ document = response.body
65
+ deserialize(document)
66
+ end
67
+
68
+ def http
69
+ transport_engine.new
70
+ end
71
+
72
+ def handle_deprecated_args(body, *args) # TODO: remove in 1.0.
73
+ options = args.first
74
+
75
+ if args.size > 1
76
+ warn %{DEPRECATION WARNING: #get, #post, #put, #delete and #patch no longer accept positional arguments. Please call them as follows:
77
+ get(uri: "http://localhost/songs", as: "application/json")
78
+ post(uri: "http://localhost/songs", as: "application/json")
79
+ Thank you and have a beautiful day.}
80
+ options = {:uri => args[0], :as => args[1]} if args.size == 2
81
+ options = {:uri => args[0], :as => args[2]}
82
+ end
83
+
84
+ options[:body] = body
85
+ options
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,174 @@
1
+ module Roar
2
+ # Define hypermedia links in your representations.
3
+ #
4
+ # Example:
5
+ #
6
+ # class Order
7
+ # include Roar::Representer::JSON
8
+ #
9
+ # property :id
10
+ #
11
+ # link :self do
12
+ # "http://orders/#{id}"
13
+ # end
14
+ #
15
+ # If you want more attributes, just pass a hash to #link.
16
+ #
17
+ # link :rel => :next, :title => "Next, please!" do
18
+ # "http://orders/#{id}"
19
+ # end
20
+ #
21
+ # If you need dynamic attributes, the block can return a hash.
22
+ #
23
+ # link :preview do
24
+ # {:href => image.url, :title => image.name}
25
+ # end
26
+ #
27
+ # Sometimes you need values from outside when the representation links are rendered. Just pass them
28
+ # to the render method, they will be available as block parameters.
29
+ #
30
+ # link :self do |opts|
31
+ # "http://orders/#{opts[:id]}"
32
+ # end
33
+ #
34
+ # model.to_json(:id => 1)
35
+ module Hypermedia
36
+ # links= [Hyperlink, Hyperlink] is where parsing happens.
37
+ def self.included(base)
38
+ base.extend ClassMethods
39
+ end
40
+
41
+ def links=(arr) # called when assigning parsed links.
42
+ @links = LinkCollection[*arr]
43
+ end
44
+
45
+ attr_reader :links # this is only useful after parsing.
46
+
47
+
48
+ module LinkConfigsMethod
49
+ def link_configs # we could store the ::link configs in links Definition.
50
+ representable_attrs[:links] ||= Representable::Inheritable::Array.new
51
+ end
52
+ end
53
+
54
+ include LinkConfigsMethod
55
+
56
+ private
57
+ # Create hypermedia links for this instance by invoking their blocks.
58
+ # This is called in links: getter: {}.
59
+ def prepare_links!(options)
60
+ return [] if options[:links] == false
61
+
62
+ LinkCollection[*compile_links_for(link_configs, options)]
63
+ end
64
+
65
+ def compile_links_for(configs, *args)
66
+ configs.collect do |config|
67
+ options, block = config.first, config.last
68
+ href = run_link_block(block, *args) or next
69
+
70
+ prepare_link_for(href, options)
71
+ end.compact # FIXME: make this less ugly.
72
+ end
73
+
74
+ def prepare_link_for(href, options)
75
+ options = options.merge(href.is_a?(::Hash) ? href : {:href => href})
76
+ Hyperlink.new(options)
77
+ end
78
+
79
+ def run_link_block(block, *args)
80
+ instance_exec(*args, &block)
81
+ end
82
+
83
+
84
+ # LinkCollection keeps an array of Hyperlinks to be rendered (setup in #prepare_links!)
85
+ # or parsed (array is passed to #links= which transforms it into a LinkCollection).
86
+ # It is implemented as a hash and keys links by their rel value.
87
+ #
88
+ # {"self" => <Hyperlink ..>, ..}
89
+ class LinkCollection < ::Hash
90
+ # The only way to create is LinkCollection[<Hyperlink>, <Hyperlink>]
91
+ def self.[](*arr)
92
+ super(arr.collect { |link| [link.rel, link] })
93
+ end
94
+
95
+ def [](rel)
96
+ super(rel.to_s)
97
+ end
98
+
99
+ # Iterating links. Block parameters: |link| or |rel, link|.
100
+ # This is used Hash::HashBinding#serialize.
101
+ def collect(&block)
102
+ return values.collect(&block) if block.arity == 1
103
+ super(&block)
104
+ end
105
+ end
106
+
107
+
108
+ module ClassMethods
109
+ # Declares a hypermedia link in the document.
110
+ #
111
+ # Example:
112
+ #
113
+ # link :self do
114
+ # "http://orders/#{id}"
115
+ # end
116
+ #
117
+ # The block is executed in instance context, so you may call properties or other accessors.
118
+ # Note that you're free to put decider logic into #link blocks, too.
119
+ def link(options, &block)
120
+ create_links_definition! # this assures the links are rendered at the right position.
121
+
122
+ options = {:rel => options} unless options.is_a?(::Hash)
123
+ link_configs << [options, block]
124
+ end
125
+
126
+ include LinkConfigsMethod
127
+
128
+ private
129
+ # Add a :links Definition to the representable_attrs so they get rendered/parsed.
130
+ def create_links_definition!
131
+ return if representable_attrs.get(:links) # only create it once.
132
+
133
+ options = links_definition_options
134
+ options.merge!(:getter => lambda { |opts| prepare_links!(opts) })
135
+
136
+ representable_attrs.add(:links, options)
137
+ end
138
+ end
139
+
140
+
141
+ # An abstract hypermedia link with arbitrary attributes.
142
+ class Hyperlink
143
+ extend Forwardable
144
+ def_delegators :@attrs, :each, :collect
145
+
146
+ def initialize(attrs={})
147
+ @attrs = attributes!(attrs)
148
+ end
149
+
150
+ def replace(attrs) # makes it work with Hash::Hash.
151
+ @attrs = attributes!(attrs)
152
+ self
153
+ end
154
+
155
+ # Only way to write to Hyperlink after creation.
156
+ def merge!(attrs)
157
+ @attrs.merge!(attributes!(attrs))
158
+ end
159
+
160
+ private
161
+ def method_missing(name)
162
+ @attrs[name.to_s]
163
+ end
164
+
165
+ # Converts keys to strings.
166
+ def attributes!(attrs)
167
+ attrs.inject({}) { |hsh, kv| hsh[kv.first.to_s] = kv.last; hsh }.tap do |hsh|
168
+ hsh["rel"] = hsh["rel"].to_s if hsh["rel"]
169
+ end
170
+ # raise "Hyperlink without rel doesn't work!" unless @attrs["rel"]
171
+ end
172
+ end
173
+ end
174
+ end
data/lib/roar/json.rb ADDED
@@ -0,0 +1,55 @@
1
+ require 'roar/representer'
2
+ require 'roar/hypermedia'
3
+ require 'representable/json'
4
+
5
+ module Roar
6
+ module JSON
7
+ def self.included(base)
8
+ base.class_eval do
9
+ include Representer
10
+ include Representable::JSON
11
+
12
+ extend ClassMethods
13
+ include InstanceMethods # otherwise Representable overrides our #to_json.
14
+ end
15
+ end
16
+
17
+ module InstanceMethods
18
+ def from_json(document, options={})
19
+ document = '{}' if document.nil? or document.empty?
20
+
21
+ super
22
+ end
23
+
24
+ # Generic entry-point for rendering.
25
+ def serialize(*args)
26
+ to_json(*args)
27
+ end
28
+
29
+ def deserialize(*args)
30
+ from_json(*args)
31
+ end
32
+ end
33
+
34
+
35
+ module ClassMethods
36
+ # TODO: move to instance method, or remove?
37
+ def links_definition_options
38
+ # FIXME: this doesn't belong into the generic JSON representer.
39
+ {
40
+ :collection => true,
41
+ :class => Hypermedia::Hyperlink,
42
+ :extend => HyperlinkRepresenter,
43
+ :exec_context => :decorator,
44
+ }
45
+ end
46
+ end
47
+
48
+
49
+ require "representable/json/hash"
50
+ # Represents a hyperlink in standard roar+json hash representation.
51
+ module HyperlinkRepresenter
52
+ include Representable::JSON::Hash
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,3 @@
1
+ require "representable/json/collection"
2
+
3
+ Roar::JSON::Collection = Representable::JSON::Collection
@@ -1,4 +1,4 @@
1
- module Roar::Representer::JSON
1
+ module Roar::JSON
2
2
  # Implementation of the Collection+JSON media format, http://amundsen.com/media-types/collection/format/
3
3
  #
4
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
@@ -9,8 +9,8 @@ module Roar::Representer::JSON
9
9
  def self.included(base)
10
10
  base.class_eval do
11
11
  include Roar::Representer
12
- include Roar::Representer::JSON
13
- include Roar::Representer::Feature::Hypermedia
12
+ include Roar::JSON
13
+ include Roar::Hypermedia
14
14
 
15
15
  extend ClassMethods
16
16
 
@@ -21,7 +21,7 @@ module Roar::Representer::JSON
21
21
  OpenStruct.new # TODO: handle preset values.
22
22
  end
23
23
 
24
- collection :queries, :extend => Roar::Representer::JSON::HyperlinkRepresenter
24
+ collection :queries, :extend => Roar::JSON::HyperlinkRepresenter, :class => lambda { |fragment,*| ::Hash }
25
25
  def queries
26
26
  compile_links_for(representable_attrs.collection_representers[:queries].link_configs)
27
27
  end
@@ -45,9 +45,9 @@ module Roar::Representer::JSON
45
45
  def __href
46
46
  compile_links_for(representable_attrs.collection_representers[:href].link_configs).first.href
47
47
  end
48
-
49
48
 
50
-
49
+
50
+
51
51
  include ClientMethods
52
52
  end
53
53
  end
@@ -62,9 +62,9 @@ module Roar::Representer::JSON
62
62
  # TODO: provide automatic copying from the ItemRepresenter here.
63
63
  def template(&block)
64
64
  mod = representable_attrs.collection_representers[:object_template] = Module.new do
65
- include Roar::Representer::JSON
66
- include Roar::Representer::JSON::CollectionJSON::DataMethods
67
-
65
+ include Roar::JSON
66
+ include Roar::JSON::CollectionJSON::DataMethods
67
+
68
68
  extend PropertyWithRenderNil
69
69
 
70
70
  module_exec(&block)
@@ -76,7 +76,7 @@ module Roar::Representer::JSON
76
76
  end
77
77
 
78
78
  representable_attrs.collection_representers[:template] = Module.new do
79
- include Roar::Representer::JSON
79
+ include Roar::JSON
80
80
  include mod
81
81
 
82
82
  #self.representation_wrap = false
@@ -93,9 +93,9 @@ module Roar::Representer::JSON
93
93
 
94
94
  def queries(&block)
95
95
  mod = representable_attrs.collection_representers[:queries] = Module.new do
96
- include Roar::Representer::JSON
97
- include Roar::Representer::Feature::Hypermedia
98
-
96
+ include Roar::JSON
97
+ include Roar::Hypermedia
98
+
99
99
  module_exec(&block)
100
100
 
101
101
  def to_hash(*)
@@ -109,9 +109,9 @@ module Roar::Representer::JSON
109
109
  collection :items, { :extend => lambda {|*| representable_attrs.collection_representers[:items] } }.merge!(options)
110
110
 
111
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
112
+ include Roar::JSON
113
+ include Roar::Hypermedia
114
+ include Roar::JSON::CollectionJSON::DataMethods
115
115
  extend SharedClassMethodsBullshit
116
116
 
117
117
  module_exec(&block)
@@ -122,7 +122,7 @@ module Roar::Representer::JSON
122
122
  compile_links_for(representable_attrs.collection_representers[:href].link_configs).first.href
123
123
  end
124
124
  def __href=(v)
125
- @__href = Roar::Representer::Feature::Hypermedia::Hyperlink.new(:href => v)
125
+ @__href = Roar::Hypermedia::Hyperlink.new(:href => v)
126
126
  end
127
127
  def href
128
128
  @__href
@@ -137,8 +137,8 @@ module Roar::Representer::JSON
137
137
  module SharedClassMethodsBullshit
138
138
  def href(&block)
139
139
  mod = representable_attrs.collection_representers[:href] = Module.new do
140
- include Roar::Representer::JSON
141
- include Roar::Representer::Feature::Hypermedia
140
+ include Roar::JSON
141
+ include Roar::Hypermedia
142
142
 
143
143
 
144
144
  link(:href, &block)
@@ -190,7 +190,7 @@ module Roar::Representer::JSON
190
190
  end
191
191
 
192
192
  def __href=(v)
193
- @__href = Roar::Representer::Feature::Hypermedia::Hyperlink.new(:href => v)
193
+ @__href = Roar::Hypermedia::Hyperlink.new(:href => v)
194
194
  end
195
195
  def href
196
196
  @__href