roar 0.12.9 → 1.0.0.beta1

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