roar 0.8.0 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -2
- data/README.textile +8 -2
- data/Rakefile +1 -1
- data/lib/roar/rails/representer_methods.rb +2 -1
- data/lib/roar/representer/base.rb +65 -0
- data/lib/roar/representer/feature/http_verbs.rb +6 -7
- data/lib/roar/representer/feature/hypermedia.rb +53 -13
- data/lib/roar/representer/feature/model_representing.rb +19 -27
- data/lib/roar/representer/json.rb +44 -14
- data/lib/roar/representer/xml.rb +48 -24
- data/lib/roar/version.rb +1 -1
- data/roar.gemspec +3 -3
- data/test/{http_verbs_test.rb → http_verbs_feature_test.rb} +3 -3
- data/test/hypermedia_feature_test.rb +132 -0
- data/test/integration_test.rb +8 -8
- data/test/json_representer_test.rb +31 -26
- data/test/model_representing_test.rb +21 -23
- data/test/order_representers.rb +5 -2
- data/test/representer_test.rb +35 -5
- data/test/test_helper.rb +28 -7
- data/test/xml_representer_test.rb +60 -90
- metadata +11 -17
- data/lib/roar/model.rb +0 -36
- data/lib/roar/model/representable.rb +0 -31
- data/lib/roar/representer.rb +0 -72
- data/test/hypermedia_test.rb +0 -35
- data/test/model_test.rb +0 -50
- data/test/proxy_test.rb +0 -89
- data/test/representable_test.rb +0 -49
- data/test/ruby_representation_test.rb +0 -144
- data/test/test_helper_test.rb +0 -59
- data/test/xml_hypermedia_test.rb +0 -47
data/lib/roar/model.rb
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
module Roar
|
2
|
-
# Basic methods needed to implement the ActiveModel API. Gives your model +#attributes+ and +model_name+.
|
3
|
-
# Include this for quickly converting an object to a ROAR-compatible monster.
|
4
|
-
#
|
5
|
-
# #DISCUSS: are Models used both on client- and server-side? I'd say hell yeah.
|
6
|
-
module Model
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
|
9
|
-
module ClassMethods
|
10
|
-
def model_name
|
11
|
-
ActiveSupport::Inflector.underscore(self) # We don't use AM::Naming for now.
|
12
|
-
end
|
13
|
-
|
14
|
-
def accessors(*names)
|
15
|
-
names.each do |name|
|
16
|
-
class_eval %Q{
|
17
|
-
def #{name}=(v)
|
18
|
-
attributes["#{name}"] = v
|
19
|
-
end
|
20
|
-
|
21
|
-
def #{name}
|
22
|
-
attributes["#{name}"]
|
23
|
-
end
|
24
|
-
}
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
|
30
|
-
attr_accessor :attributes
|
31
|
-
|
32
|
-
def initialize(attributes={})
|
33
|
-
@attributes = attributes
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
module Roar
|
2
|
-
module Model
|
3
|
-
module Representable
|
4
|
-
extend ActiveSupport::Concern
|
5
|
-
|
6
|
-
included do |base|
|
7
|
-
base.extend Hooks::InheritableAttribute
|
8
|
-
base.inheritable_attr :representable
|
9
|
-
base.representable = {} # FIXME: doesn't that break inheritance?
|
10
|
-
end
|
11
|
-
|
12
|
-
module ClassMethods
|
13
|
-
def represents(mime_type, options)
|
14
|
-
self.representable[mime_type] = options[:with]
|
15
|
-
end
|
16
|
-
|
17
|
-
def representer_class_for(mime_type)
|
18
|
-
representable[mime_type]
|
19
|
-
end
|
20
|
-
|
21
|
-
def from(mime_type, representation)
|
22
|
-
representer_class_for(mime_type).deserialize(self, mime_type, representation)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def to(mime_type)
|
27
|
-
self.class.representer_class_for(mime_type).new.serialize(self, mime_type)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
data/lib/roar/representer.rb
DELETED
@@ -1,72 +0,0 @@
|
|
1
|
-
require 'representable'
|
2
|
-
|
3
|
-
module Roar
|
4
|
-
module Representer
|
5
|
-
class Base
|
6
|
-
include Representable
|
7
|
-
|
8
|
-
class << self
|
9
|
-
alias_method :property, :representable_property
|
10
|
-
alias_method :collection, :representable_collection
|
11
|
-
|
12
|
-
# Creates a representer instance and fills it with +attributes+.
|
13
|
-
def from_attributes(attributes)
|
14
|
-
new.tap do |representer|
|
15
|
-
yield representer if block_given?
|
16
|
-
|
17
|
-
representable_attrs.each do |definition|
|
18
|
-
definition.populate(representer, attributes)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
|
25
|
-
def initialize(properties={})
|
26
|
-
properties.each { |p,v| send("#{p}=", v) } # DISCUSS: check if valid property?
|
27
|
-
end
|
28
|
-
|
29
|
-
# Convert representer's attributes to a nested attributes hash.
|
30
|
-
def to_attributes
|
31
|
-
{}.tap do |attributes|
|
32
|
-
self.class.representable_attrs.each do |definition|
|
33
|
-
value = public_send(definition.accessor)
|
34
|
-
|
35
|
-
if definition.typed?
|
36
|
-
value = definition.apply(value) do |v|
|
37
|
-
v.to_attributes # applied to each typed attribute (even in collections).
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
attributes[definition.accessor] = value
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
class LinksDefinition < Representable::Definition
|
48
|
-
def rel2block
|
49
|
-
@rel2block ||= []
|
50
|
-
end
|
51
|
-
|
52
|
-
def populate(representer, *args)
|
53
|
-
representer.links ||= []
|
54
|
-
|
55
|
-
rel2block.each do |link|
|
56
|
-
representer.links << sought_type.from_attributes({ # create Hyperlink representer.
|
57
|
-
"rel" => link[:rel],
|
58
|
-
"href" => representer.instance_exec(&link[:block])}) # DISCUSS: run block in representer context?
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# FIXME: move to some init asset.
|
67
|
-
Representable::Definition.class_eval do
|
68
|
-
# Populate the representer's attribute with the right value.
|
69
|
-
def populate(representer, attributes)
|
70
|
-
representer.public_send("#{accessor}=", attributes[accessor])
|
71
|
-
end
|
72
|
-
end
|
data/test/hypermedia_test.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
require 'roar/representer/feature/hypermedia'
|
3
|
-
|
4
|
-
class HypermediaTest
|
5
|
-
describe "Hypermedia" do
|
6
|
-
class Bookmarks
|
7
|
-
include Roar::Representer::Feature::Hypermedia
|
8
|
-
end
|
9
|
-
|
10
|
-
before do
|
11
|
-
Hyperlink = Roar::Representer::XML::Hyperlink # TODO: move to abstract module.
|
12
|
-
@b = Bookmarks.new
|
13
|
-
@b.links = [Hyperlink.from_attributes({"rel" => "self", "href" => "http://self"}), Hyperlink.from_attributes({"rel" => "next", "href" => "http://next"})]
|
14
|
-
end
|
15
|
-
|
16
|
-
describe "#links" do
|
17
|
-
it "returns links" do
|
18
|
-
assert_kind_of Roar::Representer::Feature::Hypermedia::LinkCollection, @b.links
|
19
|
-
assert_equal 2, @b.links.size
|
20
|
-
end
|
21
|
-
|
22
|
-
it "works with empty links set" do
|
23
|
-
assert_equal nil, Bookmarks.new.links # default empty array doesn't make sense.
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
|
28
|
-
it "responds to links #[]" do
|
29
|
-
assert_equal "http://self", @b.links["self"]
|
30
|
-
assert_equal "http://self", @b.links[:self]
|
31
|
-
assert_equal "http://next", @b.links[:next]
|
32
|
-
assert_equal nil, @b.links[:prev]
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
data/test/model_test.rb
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class ModelTest < MiniTest::Spec
|
4
|
-
describe "HttpVerbs" do
|
5
|
-
before do
|
6
|
-
@klass = Class.new(TestModel) do
|
7
|
-
include Roar::Model::HttpVerbs
|
8
|
-
|
9
|
-
self.resource_base = "http://localhost:9999/test/"
|
10
|
-
end
|
11
|
-
@o = @klass.new
|
12
|
-
end
|
13
|
-
|
14
|
-
it "has resource_base* accessors for setting the uri base path" do
|
15
|
-
assert_equal "http://localhost:9999/test/", @klass.resource_base
|
16
|
-
end
|
17
|
-
|
18
|
-
it ".get returns deserialized object from " do
|
19
|
-
assert_equal({"id" => "4711"}, @klass.get(4711).attributes)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
|
24
|
-
describe "The Model API" do
|
25
|
-
class Dog
|
26
|
-
include Roar::Model
|
27
|
-
|
28
|
-
accessors :name
|
29
|
-
end
|
30
|
-
|
31
|
-
before do
|
32
|
-
@klass = Dog
|
33
|
-
end
|
34
|
-
|
35
|
-
it "the constructor accepts attributes" do
|
36
|
-
assert_equal({"id" => "4711"}, @klass.new({"id" => "4711"}).attributes)
|
37
|
-
end
|
38
|
-
|
39
|
-
it "responds to .model_name" do
|
40
|
-
assert_equal "model_test/dog", @klass.model_name
|
41
|
-
end
|
42
|
-
|
43
|
-
it "lets .accessors create accessors" do
|
44
|
-
@o = @klass.new({"name" => "Joe"})
|
45
|
-
assert_equal "Joe", @o.name
|
46
|
-
@o.name= "Noe"
|
47
|
-
assert_equal "Noe", @o.name
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
data/test/proxy_test.rb
DELETED
@@ -1,89 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class ProxyTest < MiniTest::Spec
|
4
|
-
describe "Transport" do
|
5
|
-
before do
|
6
|
-
@klass = Class.new(Object) do
|
7
|
-
include Roar::Client::Transport
|
8
|
-
end
|
9
|
-
@o = @klass.new
|
10
|
-
end
|
11
|
-
|
12
|
-
it "#get_uri returns Restfulie response" do
|
13
|
-
assert_equal "<test><id>4711</id></test>", @o.get_uri("http://localhost:9999/test/4711").body
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
|
18
|
-
describe "The public Proxy API" do
|
19
|
-
before do
|
20
|
-
@klass = Class.new(TestModel) do
|
21
|
-
extend Roar::Client::Proxy
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
it "responds to .get_model" do
|
26
|
-
@o = @klass.get_model("http://localhost:9999/test/1", TestModel)
|
27
|
-
assert_kind_of TestModel, @o
|
28
|
-
assert_equal({"id" => "1"}, @o.attributes)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
describe "EntityProxy" do
|
33
|
-
before do
|
34
|
-
Proxy = Roar::Client::EntityProxy
|
35
|
-
@proxy_class = Proxy.class_for(:class => TestModel)
|
36
|
-
end
|
37
|
-
|
38
|
-
it ".class_for returns an EntityProxy subclass" do
|
39
|
-
@proxy = @proxy_class.new
|
40
|
-
assert_kind_of Proxy, @proxy
|
41
|
-
end
|
42
|
-
|
43
|
-
it "responds to .options" do
|
44
|
-
assert_equal({:class => TestModel}, @proxy_class.send(:options))
|
45
|
-
end
|
46
|
-
|
47
|
-
it "doesn't override superclass options" do
|
48
|
-
assert_equal nil, Proxy.options
|
49
|
-
end
|
50
|
-
|
51
|
-
it "responds to .from_attributes and responds to #original_attributes" do
|
52
|
-
assert_equal({:urn => "urn:item"}, @proxy_class.from_attributes(:urn => "urn:item").send(:original_attributes))
|
53
|
-
end
|
54
|
-
|
55
|
-
it "responds to .model_name with the proxied name" do
|
56
|
-
assert_equal "test", @proxy_class.model_name
|
57
|
-
end
|
58
|
-
|
59
|
-
# finalize!
|
60
|
-
describe "#finalize!" do
|
61
|
-
before do
|
62
|
-
@o = @proxy_class.from_attributes("uri" => "http://localhost:9999/test/1")
|
63
|
-
end
|
64
|
-
|
65
|
-
it "responds to #proxied_resource" do
|
66
|
-
assert_nil @o.send(:proxied_resource)
|
67
|
-
end
|
68
|
-
|
69
|
-
it "#finalize! retrieves proxied resource" do
|
70
|
-
@o.finalize!
|
71
|
-
assert_kind_of TestModel, @o.send(:proxied_resource)
|
72
|
-
end
|
73
|
-
|
74
|
-
# delegation:
|
75
|
-
it "#attributes are delegated" do
|
76
|
-
@o.finalize!
|
77
|
-
assert_equal({"id" => "1"}, @o.attributes)
|
78
|
-
end
|
79
|
-
|
80
|
-
it "#to_xml renders the unproxied entity" do
|
81
|
-
assert_equal "<test>\n <uri>http://localhost:9999/test/1</uri>\n</test>\n", @o.to_xml
|
82
|
-
end
|
83
|
-
|
84
|
-
it "#attributes_for_xml returns the unfinalized EntityProxy#attributes hash" do
|
85
|
-
assert_equal({"uri"=>"http://localhost:9999/test/1"}, @o.attributes_for_xml)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
data/test/representable_test.rb
DELETED
@@ -1,49 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
require 'roar/model/representable'
|
3
|
-
|
4
|
-
class RepresentableTest < MiniTest::Spec
|
5
|
-
|
6
|
-
class ThisAsAppXml < Roar::Representer::Base
|
7
|
-
def self.deserialize(represented_class, mime_type, data)
|
8
|
-
"#{represented_class.name}->#{mime_type}: #{data}"
|
9
|
-
end
|
10
|
-
|
11
|
-
def serialize(represented, mime_type)
|
12
|
-
"#{represented.class.name}->#{mime_type}"
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
describe "Representable" do
|
17
|
-
before do
|
18
|
-
@c = Class.new do
|
19
|
-
include Roar::Model::Representable
|
20
|
-
|
21
|
-
represents "application/xml", :with => ThisAsAppXml
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
|
26
|
-
describe ".representer_class_for" do
|
27
|
-
it "returns the class" do
|
28
|
-
assert_equal ThisAsAppXml, @c.representer_class_for("application/xml")
|
29
|
-
end
|
30
|
-
|
31
|
-
it "returns nil if unknown" do
|
32
|
-
assert_equal nil, @c.representer_class_for("text/html")
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
|
37
|
-
describe ".from" do
|
38
|
-
it "receives mime_type and content" do
|
39
|
-
assert_equal "->application/xml: <xml/>", @c.from("application/xml", "<xml/>")
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
describe "#to" do
|
44
|
-
it "receives mime_type" do
|
45
|
-
assert_equal "->application/xml", @c.new.to("application/xml")
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
@@ -1,144 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
module Roar
|
4
|
-
module Representer
|
5
|
-
class Ruby < Base
|
6
|
-
def serialize(attributes)
|
7
|
-
serializable_attributes = attributes.dup
|
8
|
-
|
9
|
-
serialize_typed_attributes(serializable_attributes)
|
10
|
-
Marshal.dump serializable_attributes
|
11
|
-
end
|
12
|
-
|
13
|
-
def deserialize(body)
|
14
|
-
deserialized_hash = Marshal.load body
|
15
|
-
deserialize_typed_attributes(deserialized_hash)
|
16
|
-
end
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
module ConfigurationDsl # FIXME: move to Representer::BaseDsl or so.
|
21
|
-
def has_many(name, options={})
|
22
|
-
collections[name] = options
|
23
|
-
end
|
24
|
-
alias_method :collection, :has_many
|
25
|
-
|
26
|
-
def has_one(name, options={})
|
27
|
-
typed_entities[name] = options
|
28
|
-
end
|
29
|
-
|
30
|
-
def has_proxied(name, options={})
|
31
|
-
has_one(name, {:class => EntityProxy.class_for(options)})
|
32
|
-
end
|
33
|
-
|
34
|
-
def has_many_proxied(name, options={})
|
35
|
-
has_many(name, {:class => EntityProxy.class_for(options)})
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
self.extend ConfigurationDsl
|
40
|
-
# FIXME: use one variable?
|
41
|
-
extend Hooks::InheritableAttribute
|
42
|
-
inheritable_attr :collections
|
43
|
-
self.collections = {}
|
44
|
-
inheritable_attr :typed_entities
|
45
|
-
self.typed_entities = {}
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
def serialize_typed_attributes(attributes)
|
50
|
-
filter_attributes_for(attributes, self.class.typed_entities) do |name, options|
|
51
|
-
next unless entity = attributes[name]
|
52
|
-
attributes[name] = entity.to(mime_type)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
# Attributes can be typecasted with +has_one+.
|
57
|
-
def deserialize_typed_attributes(attributes)
|
58
|
-
filter_attributes_for(attributes, self.class.typed_entities) do |name, options|
|
59
|
-
item = attributes.delete(name) # attributes[:sum]
|
60
|
-
item = options[:class].from(mime_type, item) # Sum.from_xml_attributes
|
61
|
-
attributes[name] = item
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def filter_attributes_for(attributes, config)
|
66
|
-
config.each do |name, options|
|
67
|
-
name = name.to_s
|
68
|
-
yield name, options
|
69
|
-
end
|
70
|
-
attributes
|
71
|
-
end
|
72
|
-
|
73
|
-
end
|
74
|
-
|
75
|
-
|
76
|
-
end
|
77
|
-
|
78
|
-
end
|
79
|
-
|
80
|
-
|
81
|
-
class RubyRepresenterFunctionalTest < MiniTest::Spec
|
82
|
-
class Item
|
83
|
-
attr_reader :value
|
84
|
-
|
85
|
-
def initialize(value)
|
86
|
-
@value = value
|
87
|
-
end
|
88
|
-
|
89
|
-
def to(mime_type)
|
90
|
-
raise unless mime_type == "ruby/serialized"
|
91
|
-
Marshal.dump(@value)
|
92
|
-
end
|
93
|
-
|
94
|
-
def self.from(mime_type, string)
|
95
|
-
raise unless mime_type == "ruby/serialized"
|
96
|
-
new Marshal.load(string)
|
97
|
-
end
|
98
|
-
|
99
|
-
def ==(b)
|
100
|
-
value == b.value
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
describe "RubyRepresenter" do
|
105
|
-
before do
|
106
|
-
@r = Class.new(Roar::Representer::Ruby).new("ruby/serialized")
|
107
|
-
@m = {:id => 1}
|
108
|
-
end
|
109
|
-
|
110
|
-
describe "without options" do
|
111
|
-
it "#serialize returns the serialized model" do
|
112
|
-
assert_equal "\x04\b{\x06:\aidi\x06", @r.serialize(@m)
|
113
|
-
end
|
114
|
-
|
115
|
-
it "#deserialize returns the attributes" do
|
116
|
-
assert_equal @m, @r.deserialize("\x04\b{\x06:\aidi\x06")
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
|
121
|
-
describe "has_one" do
|
122
|
-
before do
|
123
|
-
@r.class.instance_eval do
|
124
|
-
has_one :item, :class => Item
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
it "#serialize skips empty :item" do
|
129
|
-
assert_equal "\x04\b{\x06:\aidi\x06", @r.serialize(@m)
|
130
|
-
end
|
131
|
-
|
132
|
-
it "#serialize delegates to item#to" do
|
133
|
-
@m = {:id => 1, "item" => Item.new("beer")}
|
134
|
-
assert_equal "\x04\b{\a:\aidi\x06I\"\titem\x06:\x06EF\"\x13\x04\bI\"\tbeer\x06:\x06EF", @r.serialize(@m)
|
135
|
-
end
|
136
|
-
|
137
|
-
it "#deserialize typecasts :item" do
|
138
|
-
m = @r.deserialize("\x04\b{\a:\aidi\x06I\"\titem\x06:\x06EF\"\x13\x04\bI\"\tbeer\x06:\x06EF")
|
139
|
-
assert_equal({:id => 1, "item" => Item.new("beer")}, m)
|
140
|
-
end
|
141
|
-
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|