restfulie 0.7.1 → 0.7.2
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/README.textile +24 -14
- data/Rakefile +1 -1
- data/lib/restfulie/common/builder/builder_base.rb +25 -10
- data/lib/restfulie/common/builder/marshalling/atom.rb +1 -1
- data/lib/restfulie/common/builder/marshalling/xml.rb +183 -0
- data/lib/restfulie/common/builder/rules/custom_attributes.rb +24 -0
- data/lib/restfulie/common/builder/rules/namespace.rb +11 -1
- data/lib/restfulie/common/builder/rules/rules_base.rb +2 -1
- data/lib/restfulie/common/builder.rb +1 -0
- data/lib/restfulie/common/representation/atom.rb +4 -2
- data/lib/restfulie/common/representation/json.rb +2 -1
- data/lib/restfulie/common/representation/xml.rb +3 -2
- data/lib/restfulie/server/action_view/helpers.rb +1 -1
- data/lib/restfulie/server/action_view/template_handlers/tokamak.rb +1 -2
- metadata +5 -3
data/README.textile
CHANGED
@@ -13,24 +13,18 @@ h2. Why would I use restfulie?
|
|
13
13
|
|
14
14
|
# Easy --> writing hypermedia and semantic meaningful media type aware clients
|
15
15
|
# Small -> it's not a bloated solution with a huge list of APIs
|
16
|
-
# HATEOAS --> clients you are unaware of will not bother if you change
|
17
|
-
# HATEOAS --> resources
|
16
|
+
# HATEOAS --> clients you are unaware of will not bother if you change
|
17
|
+
# HATEOAS --> consumed resources will not affect your software whenever they change their flow
|
18
18
|
# Adaptability --> clients are able to adapt to your changes
|
19
19
|
|
20
|
-
h2.
|
21
|
-
|
22
|
-
For client side usage, execute:
|
23
|
-
|
24
|
-
<pre>
|
25
|
-
gem install activesupport
|
26
|
-
gem install restfulie
|
27
|
-
</pre>
|
20
|
+
h2. Documentation
|
28
21
|
|
29
|
-
|
22
|
+
Appart from the simple server and client examples provided here, you can find the following links useful:
|
30
23
|
|
31
|
-
|
32
|
-
|
33
|
-
|
24
|
+
* "RDocs":http://rdoc.info/projects/caelum/restfulie
|
25
|
+
* "Official website":http://restfulie.caelumobjects.com
|
26
|
+
* "How-tos":http://restfulie.caelumobjects.com/rails
|
27
|
+
* "Buying through Rest: Rest to the enterprise (video)":guilhermesilveira.wordpress.com/2010/04/13/buying-through-rest-applying-rest-to-the-enterprise/
|
34
28
|
|
35
29
|
h2. Simple server example
|
36
30
|
|
@@ -84,6 +78,21 @@ You can view an entire application running Restfulie under *spec/integration/ord
|
|
84
78
|
|
85
79
|
"You can also download a full example of a REST based agent and server":http://github.com/caelum/mikyung using Restfulie and Mikyung, according to the Rest Architecture Maturity Model.
|
86
80
|
|
81
|
+
h2. Installing
|
82
|
+
|
83
|
+
For client side usage, execute:
|
84
|
+
|
85
|
+
<pre>
|
86
|
+
gem install activesupport
|
87
|
+
gem install restfulie
|
88
|
+
</pre>
|
89
|
+
|
90
|
+
For server side usage, execute:
|
91
|
+
|
92
|
+
<pre>
|
93
|
+
gem install restfulie
|
94
|
+
</pre>
|
95
|
+
|
87
96
|
h2. Building the project
|
88
97
|
|
89
98
|
If you want to build the project and run its tests, remember to install all (client and server) required gems and:
|
@@ -92,6 +101,7 @@ If you want to build the project and run its tests, remember to install all (cli
|
|
92
101
|
gem install rack-conneg
|
93
102
|
gem install responders_backport
|
94
103
|
gem install json_pure
|
104
|
+
gem install sqlite3-ruby
|
95
105
|
</pre>
|
96
106
|
|
97
107
|
<script type="text/javascript">
|
data/Rakefile
CHANGED
@@ -6,7 +6,7 @@ require 'spec/rake/spectask'
|
|
6
6
|
require 'rake/rdoctask'
|
7
7
|
|
8
8
|
GEM = "restfulie"
|
9
|
-
GEM_VERSION = "0.7.
|
9
|
+
GEM_VERSION = "0.7.2"
|
10
10
|
SUMMARY = "Hypermedia aware resource based library in ruby (client side) and ruby on rails (server side)."
|
11
11
|
AUTHOR = "Guilherme Silveira, Caue Guerra"
|
12
12
|
EMAIL = "guilherme.silveira@caelum.com.br"
|
@@ -19,11 +19,11 @@ class Restfulie::Common::Builder::Base
|
|
19
19
|
undef_method :to_json if respond_to?(:to_json)
|
20
20
|
|
21
21
|
def respond_to?(symbol, include_private = false)
|
22
|
-
|
22
|
+
marshalling_class(symbol) || super
|
23
23
|
end
|
24
24
|
|
25
25
|
def method_missing(symbol, *args)
|
26
|
-
unless (marshalling = marshalling_class(symbol)).nil?
|
26
|
+
unless (marshalling = marshalling_class!(symbol)).nil?
|
27
27
|
return builder(marshalling, *args)
|
28
28
|
end
|
29
29
|
super
|
@@ -43,16 +43,31 @@ private
|
|
43
43
|
marshalling.new(@object, rules_blocks).builder_collection(@options.merge(options))
|
44
44
|
end
|
45
45
|
|
46
|
+
def self.marshalling_classes(media_type)
|
47
|
+
{"application/atom+xml" => Restfulie::Common::Builder::Marshalling::Atom,
|
48
|
+
"application/xml" => Restfulie::Common::Builder::Marshalling::Xml::Marshaller,
|
49
|
+
"application/json" => Restfulie::Common::Builder::Marshalling::Json,
|
50
|
+
"atom" => Restfulie::Common::Builder::Marshalling::Atom, # test only
|
51
|
+
"xml" => Restfulie::Common::Builder::Marshalling::Xml::Marshaller # test only
|
52
|
+
}[media_type.downcase]
|
53
|
+
end
|
54
|
+
|
46
55
|
def marshalling_class(method)
|
56
|
+
if (marshalling_name = method.to_s.match(/to_(.*)/))
|
57
|
+
marshalling = marshalling_name[1].downcase
|
58
|
+
Restfulie::Common::Builder::Base.marshalling_classes(marshalling)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def marshalling_class!(method)
|
47
63
|
if marshalling_name = method.to_s.match(/to_(.*)/)
|
48
|
-
marshalling = marshalling_name[1].downcase
|
49
|
-
if Restfulie::Common::Builder::
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
raise Restfulie::Common::Error::UndefinedMarshallingError.new("Marshalling #{marshalling} not found.")
|
54
|
-
end
|
64
|
+
marshalling = marshalling_name[1].downcase
|
65
|
+
if (marshaller = Restfulie::Common::Builder::Base.marshalling_classes(marshalling))
|
66
|
+
marshaller
|
67
|
+
else
|
68
|
+
raise Restfulie::Common::Error::UndefinedMarshallingError.new("Marshalling #{marshalling} not found.")
|
55
69
|
end
|
56
70
|
end
|
57
71
|
end
|
58
|
-
|
72
|
+
|
73
|
+
end
|
@@ -189,7 +189,7 @@ private
|
|
189
189
|
end
|
190
190
|
|
191
191
|
def namespace_enhance(options)
|
192
|
-
if
|
192
|
+
if options[:namespace] && options[:namespace].kind_of?(String)
|
193
193
|
options[:namespace] = { :uri => options[:namespace], :eager_load => true }
|
194
194
|
end
|
195
195
|
options
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# support xml serialization for custom attributes
|
2
|
+
module Restfulie::Common::Builder::Rules::CustomAttributes
|
3
|
+
|
4
|
+
def to_xml(writer)
|
5
|
+
custom_attributes.each do |key, value|
|
6
|
+
writer.tag!(key, value)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
module Restfulie::Common::Builder::Marshalling::Xml
|
13
|
+
end
|
14
|
+
|
15
|
+
# Xml collection rule answering to to_xml and allowing any custom element to be inserted
|
16
|
+
# All links will be automatically inserted.
|
17
|
+
class Restfulie::Common::Builder::Marshalling::Xml::CollectionRule < Restfulie::Common::Builder::CollectionRule
|
18
|
+
|
19
|
+
include Restfulie::Common::Builder::Rules::CustomAttributes
|
20
|
+
|
21
|
+
def to_xml(writer)
|
22
|
+
super(writer)
|
23
|
+
links.each do |link|
|
24
|
+
writer.link(:rel => link.rel, :href => link.href, :type => (link.type || 'application/xml')) if link.href
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
# Xml member rule answering to to_xml and allowing any custom element to be inserted.
|
31
|
+
# All links will be automatically inserted.
|
32
|
+
class Restfulie::Common::Builder::Marshalling::Xml::MemberRule < Restfulie::Common::Builder::MemberRule
|
33
|
+
|
34
|
+
include ActionController::UrlWriter
|
35
|
+
include Restfulie::Common::Builder::Rules::CustomAttributes
|
36
|
+
|
37
|
+
def to_xml(object, writer)
|
38
|
+
super(writer)
|
39
|
+
# Transitions
|
40
|
+
links.each do |link|
|
41
|
+
atom_link = {:rel => link.rel, :href => link.href, :type => link.type}
|
42
|
+
|
43
|
+
# Self
|
44
|
+
if link.href.nil?
|
45
|
+
if link.rel == "self"
|
46
|
+
path = object
|
47
|
+
else
|
48
|
+
association = object.class.reflect_on_all_associations.find { |a| a.name.to_s == link.rel }
|
49
|
+
path = (association.macro == :has_many) ? [object, association.name] : object.send(association.name) unless association.nil?
|
50
|
+
end
|
51
|
+
atom_link[:href] = polymorphic_url(path, :host => host) rescue nil
|
52
|
+
atom_link[:type] = link.type || 'application/xml'
|
53
|
+
end
|
54
|
+
writer.link(:rel => atom_link[:rel], :href => atom_link[:href], :type => (link.type || 'application/xml')) if atom_link[:href]
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def host
|
61
|
+
# TODO: If we split restfulie into 2 separate gems, we may need not to use Restfulie::Server
|
62
|
+
# inside Restfulie::Common
|
63
|
+
Restfulie::Server::Configuration.host
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
class Restfulie::Common::Builder::Marshalling::Xml::Marshaller < Restfulie::Common::Builder::Marshalling::Base
|
69
|
+
include ActionController::UrlWriter
|
70
|
+
include Restfulie::Common::Builder::Helpers
|
71
|
+
include Restfulie::Common::Error
|
72
|
+
|
73
|
+
def initialize(object, rules)
|
74
|
+
@object = object
|
75
|
+
@rules = rules
|
76
|
+
end
|
77
|
+
|
78
|
+
def builder_collection(options = {})
|
79
|
+
builder_feed(@object, @rules, options)
|
80
|
+
end
|
81
|
+
|
82
|
+
def builder_member(options = {})
|
83
|
+
options[:indent] ||= 2
|
84
|
+
options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
|
85
|
+
options[:skip_types] = true
|
86
|
+
builder_entry(@object, options[:builder], @object.class.name.underscore, @rules, options)
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def builder_feed(objects, rules_blocks, options = {})
|
92
|
+
rule = Restfulie::Common::Builder::Marshalling::Xml::CollectionRule.new(rules_blocks)
|
93
|
+
|
94
|
+
rule.blocks.unshift(default_collection_rule) if options[:default_rule]
|
95
|
+
rule.apply(objects, options)
|
96
|
+
|
97
|
+
# setup code from Rails to_xml
|
98
|
+
|
99
|
+
options[:root] ||= objects.all? { |e| e.is_a?(objects.first.class) && objects.first.class.to_s != "Hash" } ? objects.first.class.to_s.underscore.pluralize : "records"
|
100
|
+
options[:children] ||= options[:root].singularize
|
101
|
+
options[:indent] ||= 2
|
102
|
+
options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
|
103
|
+
|
104
|
+
root = options.delete(:root).to_s
|
105
|
+
children = options.delete(:children)
|
106
|
+
|
107
|
+
if !options.has_key?(:dasherize) || options[:dasherize]
|
108
|
+
root = root.dasherize
|
109
|
+
end
|
110
|
+
|
111
|
+
options[:builder].instruct! unless options.delete(:skip_instruct)
|
112
|
+
|
113
|
+
opts = options.merge({ :root => children })
|
114
|
+
|
115
|
+
writer = options[:builder]
|
116
|
+
|
117
|
+
# Entries
|
118
|
+
options.delete(:values)
|
119
|
+
member_options = options.merge(rule.members_options || {})
|
120
|
+
member_options[:skip_instruct] = true
|
121
|
+
start_with_namespace(rule.namespaces, writer, root, options[:skip_types] ? {} : {}) do
|
122
|
+
rule.to_xml(writer)
|
123
|
+
yield writer if block_given?
|
124
|
+
|
125
|
+
objects.each { |e|
|
126
|
+
builder_entry(e, writer, children, rule.members_blocks || [], member_options)
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
def start_with_namespace(namespaces, writer, root, condition, &block)
|
133
|
+
spaces = condition.dup
|
134
|
+
namespaces.each do |ns|
|
135
|
+
ns.each do |key, value|
|
136
|
+
spaces["xmlns:#{ns.namespace}"] = ns.uri
|
137
|
+
end if ns.uri
|
138
|
+
end
|
139
|
+
result = writer.tag!(root, spaces) do |inner|
|
140
|
+
namespaces.each do |ns|
|
141
|
+
ns.each do |key, value|
|
142
|
+
tag = ns.namespace ? "#{key}" : "#{ns.namespace}:#{key}"
|
143
|
+
inner.tag! tag, value
|
144
|
+
end
|
145
|
+
end
|
146
|
+
block.call inner
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def default_collection_rule
|
151
|
+
Proc.new do |collection_rule, objects, options|
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def builder_entry(object, xml, children, rules_blocks, options)
|
156
|
+
rule = Restfulie::Common::Builder::Marshalling::Xml::MemberRule.new(rules_blocks)
|
157
|
+
options = namespace_enhance(options)
|
158
|
+
|
159
|
+
rule.blocks.unshift(default_member_rule) if options[:default_rule]
|
160
|
+
rule.apply(object, options)
|
161
|
+
|
162
|
+
start_with_namespace(rule.namespaces, xml, children, {}) do |inner_xml|
|
163
|
+
rule.to_xml(object, inner_xml)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def default_member_rule
|
168
|
+
Proc.new do |member_rule, object, options|
|
169
|
+
if options[:namespace]
|
170
|
+
member_rule.namespace(object, options[:namespace][:uri], options[:namespace])
|
171
|
+
else
|
172
|
+
member_rule.namespace(object, nil, options[:namespace] || {})
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def namespace_enhance(options)
|
178
|
+
if !options[:namespace].nil? && options[:namespace].kind_of?(String)
|
179
|
+
options[:namespace] = { :uri => options[:namespace], :eager_load => true }
|
180
|
+
end
|
181
|
+
options
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Rules that allow any type of content shall extend this one and provide valid serialization methods. i.e.: to_xml
|
2
|
+
module Restfulie::Common::Builder::Rules::CustomAttributes
|
3
|
+
|
4
|
+
# returns true if it's a valid attribute evaluation or
|
5
|
+
def method_missing(sym, *args)
|
6
|
+
if sym.to_s.last=="=" && args.size==1
|
7
|
+
custom_attributes[sym.to_s.chop] = args[0]
|
8
|
+
elsif custom_attributes[sym.to_s]
|
9
|
+
custom_attributes[sym.to_s]
|
10
|
+
else
|
11
|
+
super(sym, *args)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def respond_to?(sym)
|
16
|
+
super(sym) || (sym.to_s.last == "=") || custom_attributes[sym.to_s]
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def custom_attributes
|
22
|
+
@custom_attributes ||= {}
|
23
|
+
end
|
24
|
+
end
|
@@ -1,3 +1,14 @@
|
|
1
|
+
# Representation of a namespace. Allows any type of attribute setting and grabbing, i.e.:
|
2
|
+
#
|
3
|
+
# collection.namespace(:basket, "http://openbuy.com/basket") do |ns|
|
4
|
+
# ns.price = @basket.cost
|
5
|
+
# end
|
6
|
+
#
|
7
|
+
# or
|
8
|
+
#
|
9
|
+
# collection.describe_members(:namespaces => "http://localhost:3000/items") do |member, item|
|
10
|
+
# member.links << link( :rel => :self, :href => item_url(item))
|
11
|
+
# end
|
1
12
|
class Restfulie::Common::Builder::Rules::Namespace < Hash
|
2
13
|
attr_reader :namespace
|
3
14
|
attr_reader :uri
|
@@ -9,7 +20,6 @@ class Restfulie::Common::Builder::Rules::Namespace < Hash
|
|
9
20
|
end
|
10
21
|
|
11
22
|
def uri=(value)
|
12
|
-
raise Restfulie::Common::Error::NameSpaceError.new('Namespace can not be blank uri.') if value.blank?
|
13
23
|
@uri = value
|
14
24
|
end
|
15
25
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module Restfulie::Common::Builder::Rules; end
|
2
2
|
|
3
|
+
# Set of rules that can be used to describe a resource in a tokamak view.
|
3
4
|
class Restfulie::Common::Builder::Rules::Base
|
4
5
|
attr_accessor :blocks
|
5
6
|
attr_accessor :links
|
@@ -18,7 +19,7 @@ class Restfulie::Common::Builder::Rules::Base
|
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
21
|
-
# Use to register namespace
|
22
|
+
# Use to register a namespace
|
22
23
|
#
|
23
24
|
#==Example:
|
24
25
|
#
|
@@ -22,8 +22,10 @@ module Restfulie::Common::Representation
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
def marshal(
|
26
|
-
|
25
|
+
def marshal(entity, rel)
|
26
|
+
return entity if entity.kind_of? String
|
27
|
+
entity.to_xml
|
28
|
+
entity
|
27
29
|
end
|
28
30
|
|
29
31
|
# transforms this content into a parameter hash for rails (server-side usage)
|
@@ -11,7 +11,7 @@ module Restfulie::Server::ActionView::Helpers
|
|
11
11
|
#
|
12
12
|
# in partial:
|
13
13
|
#
|
14
|
-
# member.links << link(:rel => :
|
14
|
+
# member.links << link(:rel => :artists, :href => album_artists_url(album))
|
15
15
|
#
|
16
16
|
# Or passing local variables assing
|
17
17
|
#
|
@@ -3,13 +3,12 @@ module Restfulie::Server::ActionView::TemplateHandlers
|
|
3
3
|
class Tokamak < ActionView::TemplateHandler
|
4
4
|
include ActionView::TemplateHandlers::Compilable
|
5
5
|
|
6
|
-
# TODO: Implement error for code not return builder
|
7
6
|
def compile(template)
|
8
7
|
"extend Restfulie::Common::Builder::Helpers; " +
|
9
8
|
"extend Restfulie::Server::ActionView::Helpers; " +
|
10
9
|
"code_block = lambda { #{template.source} };" +
|
11
10
|
"builder = code_block.call; " +
|
12
|
-
"builder.
|
11
|
+
"builder.send \"to_\#{self.response.content_type}\" "
|
13
12
|
end
|
14
13
|
end
|
15
14
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 7
|
8
|
-
-
|
9
|
-
version: 0.7.
|
8
|
+
- 2
|
9
|
+
version: 0.7.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Guilherme Silveira, Caue Guerra
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-04-
|
17
|
+
date: 2010-04-19 00:00:00 -03:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -98,8 +98,10 @@ files:
|
|
98
98
|
- lib/restfulie/common/builder/marshalling/atom.rb
|
99
99
|
- lib/restfulie/common/builder/marshalling/base.rb
|
100
100
|
- lib/restfulie/common/builder/marshalling/json.rb
|
101
|
+
- lib/restfulie/common/builder/marshalling/xml.rb
|
101
102
|
- lib/restfulie/common/builder/marshalling.rb
|
102
103
|
- lib/restfulie/common/builder/rules/collection_rule.rb
|
104
|
+
- lib/restfulie/common/builder/rules/custom_attributes.rb
|
103
105
|
- lib/restfulie/common/builder/rules/link.rb
|
104
106
|
- lib/restfulie/common/builder/rules/links.rb
|
105
107
|
- lib/restfulie/common/builder/rules/member_rule.rb
|