roar 0.8.0 → 0.8.1
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/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
|