kaiwren-wrest 0.0.6 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +83 -31
- data/Rakefile +29 -13
- data/VERSION.yml +1 -1
- data/examples/delicious.rb +58 -0
- data/examples/wow_realm_status.rb +57 -0
- data/lib/wrest.rb +11 -2
- data/lib/wrest/components.rb +1 -2
- data/lib/wrest/components/attributes_container.rb +34 -74
- data/lib/wrest/components/attributes_container/typecaster.rb +121 -0
- data/lib/wrest/components/mutators.rb +18 -1
- data/lib/wrest/components/mutators/base.rb +52 -39
- data/lib/wrest/components/mutators/camel_to_snake_case.rb +7 -5
- data/lib/wrest/components/mutators/xml_mini_type_caster.rb +43 -0
- data/lib/wrest/components/mutators/xml_simple_type_caster.rb +22 -20
- data/lib/wrest/components/translators.rb +20 -17
- data/lib/wrest/components/translators/content_types.rb +2 -2
- data/lib/wrest/components/translators/json.rb +11 -8
- data/lib/wrest/components/translators/xml.rb +9 -12
- data/lib/wrest/core_ext/hash/conversions.rb +1 -1
- data/lib/wrest/core_ext/string/conversions.rb +2 -2
- data/lib/wrest/http.rb +24 -0
- data/lib/wrest/http/delete.rb +23 -0
- data/lib/wrest/http/get.rb +23 -0
- data/lib/wrest/http/options.rb +23 -0
- data/lib/wrest/http/post.rb +23 -0
- data/lib/wrest/http/put.rb +23 -0
- data/lib/wrest/http/request.rb +48 -0
- data/lib/wrest/http/response.rb +44 -0
- data/lib/wrest/resource/base.rb +52 -25
- data/lib/wrest/resource/state.rb +6 -0
- data/lib/wrest/uri.rb +85 -29
- data/lib/wrest/uri_template.rb +18 -1
- data/lib/wrest/version.rb +1 -1
- data/spec/spec.opts +1 -1
- data/spec/spec_helper.rb +8 -1
- data/spec/wrest/components/attributes_container/typecaster_spec.rb +63 -0
- data/spec/wrest/components/attributes_container_spec.rb +17 -61
- data/spec/wrest/components/mutators/base_spec.rb +5 -1
- data/spec/wrest/components/mutators/xml_mini_type_caster_spec.rb +75 -0
- data/spec/wrest/components/mutators_spec.rb +21 -0
- data/spec/wrest/components/translators/xml_spec.rb +1 -1
- data/spec/wrest/components/translators_spec.rb +9 -0
- data/spec/wrest/core_ext/string/conversions_spec.rb +9 -0
- data/spec/wrest/http/request_spec.rb +22 -0
- data/spec/wrest/{response_spec.rb → http/response_spec.rb} +4 -4
- data/spec/wrest/resource/base_spec.rb +126 -11
- data/spec/wrest/uri_spec.rb +124 -20
- data/spec/wrest/uri_template_spec.rb +11 -1
- metadata +33 -20
- data/lib/wrest/components/typecast_helpers.rb +0 -41
- data/lib/wrest/response.rb +0 -38
data/README.rdoc
CHANGED
@@ -1,10 +1,21 @@
|
|
1
|
-
= Wrest
|
1
|
+
= Wrest
|
2
2
|
|
3
3
|
(c) Copyright 2009 {Sidu Ponnappa}[http://blog.sidu.in]. All Rights Reserved.
|
4
4
|
|
5
5
|
Wrest is a ruby REST client library which allows you to quickly build object oriented wrappers around any web service. It has two components - Wrest Core and Wrest::Resource.
|
6
6
|
|
7
|
-
|
7
|
+
If you were wondering why the words 'demon', 'chi' and 'puppies' (among others) show up in nearly every example and spec, it's because they all refer to characters and ideas from {Roger Zelazny's}[http://en.wikipedia.org/wiki/Roger_Zelazny] last book, 'Lord Demon.'
|
8
|
+
|
9
|
+
== Wrest Core: Features
|
10
|
+
|
11
|
+
* Provides convenient HTTP wrappers, serialisation and deserialisation with caching and redirect handling in the works.
|
12
|
+
* Designed to be used as a library, not just a command line REST client (fewer class methods, more object oriented)
|
13
|
+
* Isn't coupled to Rails (usable in a pure Ruby application to consume any HTTP/REST api)
|
14
|
+
* Can be used both stand alone as well as to build object oriented abstractions around resources and web services (Wrest::Resource is an example of the latter)
|
15
|
+
|
16
|
+
== Examples
|
17
|
+
|
18
|
+
See http://github.com/kaiwren/wrest/tree/master/examples
|
8
19
|
|
9
20
|
== Installation
|
10
21
|
|
@@ -12,20 +23,15 @@ The source is available at git://github.com/kaiwren/wrest.git
|
|
12
23
|
|
13
24
|
To install as a Rails plugin, do <tt>script/plugin install git://github.com/kaiwren/wrest.git</tt>
|
14
25
|
|
15
|
-
To install the Wrest gem, do <tt>sudo gem install wrest</tt
|
26
|
+
To install the Wrest gem, do <tt>(sudo) gem install wrest</tt>
|
16
27
|
|
17
|
-
|
18
|
-
|
19
|
-
* Designed to be used as a library, not just a command line REST client
|
20
|
-
* Provides infrastructure components such as convenient HTTP wrappers api, caching, redirect handling, serialisation, deserialisation etc.
|
21
|
-
* Isn't coupled to Rails (usable in a pure Ruby application to consume any REST api)
|
22
|
-
* Can be used both stand alone as well as to build object oriented abstractions around web services (Wrest::Resource is an example of the latter)
|
28
|
+
Wrest is also available as a gem for JRuby; you can instally it by running <tt>(sudo) jruby -S gem install wrest</tt>.
|
23
29
|
|
24
30
|
=== Usage: Shell
|
25
31
|
|
26
32
|
You can launch the interactive Wrest shell by running bin/wrest if you have the source or invoking <tt>wrest</tt> from your prompt if you've installed the gem.
|
27
33
|
$ wrest
|
28
|
-
>>
|
34
|
+
>> y 'http://twitter.com/statuses/public_timeline.json'.to_uri.get.deserialise
|
29
35
|
|
30
36
|
=== Usage: Library
|
31
37
|
|
@@ -38,34 +44,72 @@ You can launch the interactive Wrest shell by running bin/wrest if you have the
|
|
38
44
|
:results=> '3',
|
39
45
|
:start => '1'
|
40
46
|
)
|
41
|
-
=== Basic Http Calls
|
47
|
+
=== Usage: Basic Http Calls
|
42
48
|
|
43
|
-
====
|
49
|
+
==== GET
|
44
50
|
|
45
|
-
A couple of ways to get
|
51
|
+
A couple of ways to get Yahoo news as a hash map.
|
46
52
|
|
47
53
|
* This example simply does a get on a uri and figures out the appropriate deserialiser using the content-type (in this case 'text/javascript', which uses Wrest::Translators::Json). See content_types.rb under lib/wrest/mappers/translators.
|
48
54
|
"http://search.yahooapis.com/NewsSearchService/V1/newsSearch?appid=YahooDemo&output=json&query=India&results=3&start=1".to_uri.get.deserialise
|
49
55
|
|
50
56
|
* This example does a get on a base uri with several parameters passed to it, resulting in a uri essentially the same as the one above. It also shows how you can specify a custom deserialiser to produce a hash-map from the response, as well as a hash mutator to clean up the deserialised hash.
|
51
|
-
|
52
|
-
|
57
|
+
require 'rubygems'
|
58
|
+
require 'wrest'
|
59
|
+
include Wrest::Components
|
60
|
+
y "http://search.yahooapis.com/NewsSearchService/V1/newsSearch".to_uri.get(
|
61
|
+
:appid => 'YahooDemo',
|
62
|
+
:output => 'xml',
|
63
|
+
:query => 'India',
|
64
|
+
:results=> '3',
|
65
|
+
:start => '1'
|
66
|
+
).deserialise_using(
|
67
|
+
Translators::Xml
|
68
|
+
).mutate_using(
|
69
|
+
Mutators::XmlMiniTypeCaster.new
|
70
|
+
)
|
53
71
|
|
54
|
-
|
55
|
-
p "http://search.yahooapis.com/NewsSearchService/V1/newsSearch".to_uri.get(
|
56
|
-
:appid => 'YahooDemo',
|
57
|
-
:output => 'xml',
|
58
|
-
:query => 'India',
|
59
|
-
:results=> '3',
|
60
|
-
:start => '1'
|
61
|
-
).deserialise_using(
|
62
|
-
Translators::Xml
|
63
|
-
).mutate_using(
|
64
|
-
Mutators::XmlSimpleTypeCaster.new
|
65
|
-
)
|
72
|
+
==== DELETE
|
66
73
|
|
74
|
+
To delete a resource:
|
67
75
|
|
68
|
-
|
76
|
+
'https://api.del.icio.us/v1/posts/delete'.to_uri(
|
77
|
+
:username => 'kaiwren',
|
78
|
+
:password => 'fupupp1es'
|
79
|
+
).delete(
|
80
|
+
:url => 'http://c2.com'
|
81
|
+
)
|
82
|
+
|
83
|
+
|
84
|
+
==== OPTIONS
|
85
|
+
|
86
|
+
To find out what actions are permitted on a URI:
|
87
|
+
|
88
|
+
'http://www.yahoo.com'.to_uri.options.headers['allow']
|
89
|
+
|
90
|
+
|
91
|
+
=== Usage: Attributes Container
|
92
|
+
|
93
|
+
Allows any class to hold an attributes hash, somewhat like ActiveResource. It also supports several extensions to this base fuctionality such as support for typecasting attribute values.
|
94
|
+
|
95
|
+
Example:
|
96
|
+
|
97
|
+
class Demon
|
98
|
+
include Wrest::Components::AttributesContainer
|
99
|
+
include Wrest::Components::AttributesContainer::Typecaster
|
100
|
+
|
101
|
+
always_has :id
|
102
|
+
typecast :age => as_integer,
|
103
|
+
:chi => lambda{|chi| Chi.new(chi)}
|
104
|
+
end
|
105
|
+
|
106
|
+
kai_wren = Demon.new('id => '1', 'age' => '1500', 'chi' => '1024', 'teacher' => 'Viss')
|
107
|
+
kai_wren.id # => '1'
|
108
|
+
kai_wren.age # => 1500
|
109
|
+
kai_wren.chi # => #<Chi:0x113af8c @count="1024">
|
110
|
+
kai_wren.teacher # => 'Viss'
|
111
|
+
|
112
|
+
=== Usage: Logging
|
69
113
|
|
70
114
|
The Wrest logger can be set and accessed through Wrest.logger and is configured by default to log to STDOUT. If you're using Wrest in a Rails application, you can configure logging by placing the following line in your environment file:
|
71
115
|
Wrest.logger = ActiveRecord::Base.logger
|
@@ -80,9 +124,15 @@ Wrest RDocs can be found at http://wrest.rubyforge.org
|
|
80
124
|
|
81
125
|
== Wrest::Resource
|
82
126
|
|
83
|
-
Wrest::Resource is an alternative to ActiveResource. It targets Rails REST services and is currently under development.
|
127
|
+
Wrest::Resource is an alternative to Rails' ActiveResource. It targets Rails REST (well, POX, since Rails isn't really RESTful) services and is currently under development. Since no single REST library can provide an object oriented wrapper suitable for _all_ available web services, it follows that Wrest should focus on providing you with the tools to help you roll your own. Wrest::Resource is an example of this - an object oriented wrapper for the kind of REST APIs exposed by Rails applications, that you would otherwise use ActiveResource to consume.
|
128
|
+
|
84
129
|
|
85
|
-
* No more pretending that REST resources are the same as database
|
130
|
+
* No more pretending that REST resources are the same as records in a database (yeah, no more freaking ActiveResource::Connection)
|
131
|
+
* Treat put as 'create or update,' not just 'update'
|
132
|
+
* Response codes result in user defined state transitions; favours state transitions based on response code over arbitrary ones
|
133
|
+
* Supports moving toward hypermedia links as opposed to client server collusion through URI templates
|
134
|
+
* The header is now exposed as metadata, rather being than something you have no control over
|
135
|
+
* Out of the box support for If-Unmodified-Since/If-Match+Etag
|
86
136
|
* Out of the box support for collections
|
87
137
|
* Out of the box support for collection pagination (including support for WillPaginate), both header based and xml attribute based
|
88
138
|
* Out of the box support for operations on all the records on the collection
|
@@ -92,14 +142,16 @@ Wrest::Resource is an alternative to ActiveResource. It targets Rails REST servi
|
|
92
142
|
* More natural mapping of deserialised entities to existing classes
|
93
143
|
* No communication via exceptions for http error status codes
|
94
144
|
* Better extensibility - allows access to request/response objects, avoids class variables, favours symbols over strings etc.
|
145
|
+
* Content Types in request headers
|
146
|
+
* Consider support for OPTIONS and response codes 100/417
|
95
147
|
|
96
148
|
== Dependencies
|
97
149
|
|
98
150
|
=== Source
|
99
151
|
* gems
|
100
|
-
* xmlsimple
|
101
152
|
* json (json-jruby on JRuby)
|
102
153
|
* active_support
|
154
|
+
* ruby-libxml (Recommended, will fall back to REXML if absent; be aware that Wrest uses ActiveSupport::XmlMini, so if you're using Wrest as a plugin in a Rails application, all xml parsing across the application will switch to using libxml if it's available. You're free to change this by hand by using the ActiveSupport::XmlMini.backend= method.)
|
103
155
|
|
104
156
|
=== Build
|
105
157
|
* rspec
|
data/Rakefile
CHANGED
@@ -7,14 +7,24 @@
|
|
7
7
|
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
8
8
|
# See the License for the specific language governing permissions and limitations under the License.
|
9
9
|
|
10
|
-
require 'rubygems'
|
11
|
-
gem 'rspec'
|
12
|
-
require 'rake'
|
13
|
-
require 'hanna/rdoctask'
|
14
|
-
require 'spec'
|
15
|
-
require 'spec/rake/spectask'
|
16
|
-
|
17
10
|
puts "Building on Ruby #{RUBY_VERSION}, #{RUBY_RELEASE_DATE}, #{RUBY_PLATFORM}"
|
11
|
+
puts "Note that some optional libraries/gems that the build (not Wrest itself) uses may not be available on all implementations of Ruby."
|
12
|
+
|
13
|
+
if Object.const_defined?('RAILS_ROOT')
|
14
|
+
require File.dirname(__FILE__) + '/../../../config/environment'
|
15
|
+
else
|
16
|
+
require 'rubygems'
|
17
|
+
gem 'rspec'
|
18
|
+
require 'rake'
|
19
|
+
require 'spec'
|
20
|
+
require 'spec/rake/spectask'
|
21
|
+
|
22
|
+
begin
|
23
|
+
require 'metric_fu'
|
24
|
+
rescue LoadError
|
25
|
+
puts 'metric_fu is not available. Install it with: gem install jscruggs-metric_fu -s http://gems.github.com'
|
26
|
+
end
|
27
|
+
end
|
18
28
|
|
19
29
|
desc 'Default: run spec tests.'
|
20
30
|
task :default => :spec
|
@@ -25,6 +35,12 @@ Spec::Rake::SpecTask.new(:spec) do |task|
|
|
25
35
|
task.spec_opts = ['--options', 'spec/spec.opts']
|
26
36
|
end
|
27
37
|
|
38
|
+
begin
|
39
|
+
require 'hanna/rdoctask'
|
40
|
+
rescue LoadError
|
41
|
+
puts 'Hanna not available, using standard Rake rdoctask. Install it with: gem install mislav-hanna.'
|
42
|
+
require 'rake/rdoctask'
|
43
|
+
end
|
28
44
|
desc 'Generate documentation for Wrest'
|
29
45
|
Rake::RDocTask.new(:rdoc) do |rdoc|
|
30
46
|
rdoc.rdoc_dir = 'rdoc'
|
@@ -62,23 +78,23 @@ begin
|
|
62
78
|
gemspec.homepage = "http://github.com/kaiwren/wrest"
|
63
79
|
gemspec.has_rdoc = true
|
64
80
|
gemspec.rubyforge_project = 'wrest'
|
65
|
-
gemspec.executables = ['wrest']
|
81
|
+
gemspec.executables = ['wrest', 'jwrest']
|
66
82
|
gemspec.require_path = "lib"
|
67
83
|
gemspec.files.exclude 'spec/wrest/meh_spec.rb'
|
68
84
|
gemspec.test_files.exclude 'spec/wrest/meh_spec.rb'
|
69
|
-
gemspec.add_dependency('activesupport', '>= 2.
|
70
|
-
gemspec.add_dependency('xml-simple', '>= 1.0.11')
|
85
|
+
gemspec.add_dependency('activesupport', '>= 2.3.2')
|
71
86
|
case RUBY_PLATFORM
|
72
87
|
when /java/
|
73
|
-
gemspec.add_dependency('json-jruby', '>= 1.1.3')
|
88
|
+
gemspec.add_dependency('json-jruby', '>= 1.1.3')
|
74
89
|
gemspec.platform = 'java'
|
75
90
|
else
|
76
|
-
gemspec.add_dependency('json', '>= 1.1.3')
|
91
|
+
gemspec.add_dependency('json', '>= 1.1.3')
|
77
92
|
gemspec.platform = Gem::Platform::RUBY
|
78
93
|
end
|
79
94
|
end
|
80
95
|
rescue LoadError
|
81
|
-
puts "Jeweler not available. Install it with:
|
96
|
+
puts "Jeweler not available. Install it with: gem install technicalpickles-jeweler -s http://gems.github.com"
|
97
|
+
puts "If you're using JRuby and are having trouble installing jeweler, try installing the git (gem install git) and rubyforge (gem install rubyforge) gems by hand. Also remember to update gems itself (jruby -S gem update --system)."
|
82
98
|
end
|
83
99
|
|
84
100
|
begin
|
data/VERSION.yml
CHANGED
@@ -0,0 +1,58 @@
|
|
1
|
+
# Copyright 2009 Sidu Ponnappa
|
2
|
+
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
# Unless required by applicable law or agreed to in writing, software distributed under the License
|
7
|
+
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
8
|
+
# See the License for the specific language governing permissions and limitations under the License.
|
9
|
+
|
10
|
+
require File.expand_path(File.dirname(__FILE__) + "/../lib/wrest")
|
11
|
+
require 'pp'
|
12
|
+
|
13
|
+
Wrest.logger = Logger.new(STDOUT)
|
14
|
+
Wrest.logger.level = Logger::DEBUG # Set this to Logger::INFO or higher to disable request logging
|
15
|
+
|
16
|
+
# API reference: http://delicious.com/help/api
|
17
|
+
class Delicious
|
18
|
+
def initialize(options)
|
19
|
+
@uri = "https://api.del.icio.us/v1/posts".to_uri(options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def bookmarks(parameters = {})
|
23
|
+
@uri['/get'].get(parameters)
|
24
|
+
end
|
25
|
+
|
26
|
+
def recent(parameters = {})
|
27
|
+
@uri['/recent'].get(parameters)
|
28
|
+
end
|
29
|
+
|
30
|
+
def bookmark(parameters)
|
31
|
+
@uri['/add'].post('', {}, parameters)
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete(parameters)
|
35
|
+
@uri['/delete'].delete(parameters)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
account = Delicious.new :username => 'kaiwren', :password => 'fupupp1es'
|
40
|
+
|
41
|
+
pp account.bookmark(
|
42
|
+
:url => 'http://blog.sidu.in/search/label/ruby',
|
43
|
+
:description => 'The Ruby related posts on my blog!',
|
44
|
+
:extended => "All posts tagged with 'ruby'",
|
45
|
+
:tags => 'ruby hacking'
|
46
|
+
).deserialise
|
47
|
+
|
48
|
+
puts '----------'
|
49
|
+
|
50
|
+
pp account.bookmarks(:tag => 'rails', :dt => '20090712').deserialise
|
51
|
+
|
52
|
+
puts '----------'
|
53
|
+
|
54
|
+
pp recently_saved_uris = account.recent(:tag => 'ruby').deserialise["posts"]["post"].collect{|bookmark| bookmark['href']}
|
55
|
+
|
56
|
+
puts '----------'
|
57
|
+
|
58
|
+
pp account.delete(:url => 'http://blog.sidu.in/search/label/ruby').deserialise
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Copyright 2009 Sidu Ponnappa
|
2
|
+
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
# Unless required by applicable law or agreed to in writing, software distributed under the License
|
7
|
+
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
8
|
+
# See the License for the specific language governing permissions and limitations under the License.
|
9
|
+
|
10
|
+
require File.expand_path(File.dirname(__FILE__) + "/../lib/wrest")
|
11
|
+
|
12
|
+
Wrest.logger = Logger.new(STDOUT)
|
13
|
+
Wrest.logger.level = Logger::DEBUG # Set this to Logger::INFO or higher to disable request logging
|
14
|
+
|
15
|
+
include Wrest
|
16
|
+
|
17
|
+
class Realm
|
18
|
+
include Components::AttributesContainer
|
19
|
+
include Components::AttributesContainer::Typecaster
|
20
|
+
|
21
|
+
typecast :t => lambda{|type|
|
22
|
+
case type
|
23
|
+
when '1' then 'Normal'
|
24
|
+
when '2' then 'PvP'
|
25
|
+
when '3' then 'RP'
|
26
|
+
when '4' then 'RP PvP'
|
27
|
+
end
|
28
|
+
},
|
29
|
+
:s => lambda{|status|
|
30
|
+
case status
|
31
|
+
when '1' then 'Available'
|
32
|
+
else 'Unavailable'
|
33
|
+
end
|
34
|
+
},
|
35
|
+
:l => lambda{|load|
|
36
|
+
case load
|
37
|
+
when '1' then 'Low'
|
38
|
+
when '2' then 'Normal'
|
39
|
+
when '3' then 'High'
|
40
|
+
when '4' then 'Max'
|
41
|
+
end
|
42
|
+
}
|
43
|
+
|
44
|
+
def available?
|
45
|
+
self.s == 'Available'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
realms = "http://www.worldofwarcraft.com/realmstatus/status.xml".to_uri.get.deserialise['page']['rs']['r'].collect{|data| Realm.new(data)}
|
50
|
+
|
51
|
+
puts "Status of Nagrand: #{realms.find{|realm| realm.n == 'Nagrand'}.s}"
|
52
|
+
puts
|
53
|
+
puts "All Available Realms:"
|
54
|
+
puts
|
55
|
+
puts "Realm\tLoad\tType"
|
56
|
+
puts "-----------"
|
57
|
+
realms.select(&:available?).each{|realm| puts "#{realm.n}\t#{realm.l}\t#{realm.t}" }
|
data/lib/wrest.rb
CHANGED
@@ -7,6 +7,8 @@
|
|
7
7
|
# See the License for the specific language governing permissions and limitations under the License.
|
8
8
|
|
9
9
|
require 'rubygems'
|
10
|
+
gem 'activesupport', '>= 2.3.2'
|
11
|
+
|
10
12
|
require 'net/http'
|
11
13
|
require 'net/https'
|
12
14
|
require 'forwardable'
|
@@ -19,7 +21,7 @@ require 'active_support'
|
|
19
21
|
|
20
22
|
WREST_ROOT = File.dirname(__FILE__)
|
21
23
|
|
22
|
-
module Wrest
|
24
|
+
module Wrest #:nodoc:
|
23
25
|
def self.logger=(logger)
|
24
26
|
@logger = logger
|
25
27
|
end
|
@@ -29,9 +31,16 @@ module Wrest
|
|
29
31
|
end
|
30
32
|
end
|
31
33
|
|
32
|
-
Wrest.logger =
|
34
|
+
Wrest.logger = ActiveSupport::BufferedLogger.new(STDOUT)
|
33
35
|
Wrest.logger.level = Logger::DEBUG
|
34
36
|
|
37
|
+
begin
|
38
|
+
gem 'libxml-ruby', '>= 1.1.3'
|
39
|
+
ActiveSupport::XmlMini.backend='LibXML'
|
40
|
+
rescue Gem::LoadError
|
41
|
+
Wrest.logger.warn "LibXML >= 1.1.3 not found, falling back to #{ActiveSupport::XmlMini.backend}. To install LibXML run `sudo gem install ruby-libxml`"
|
42
|
+
end
|
43
|
+
|
35
44
|
source_dirs = ["/wrest/core_ext/*.rb", "/wrest/*.rb"]
|
36
45
|
|
37
46
|
source_dirs.each{|directory|
|
data/lib/wrest/components.rb
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
8
8
|
# See the License for the specific language governing permissions and limitations under the License.
|
9
9
|
|
10
|
-
module Wrest
|
10
|
+
module Wrest
|
11
11
|
# A component is a building block that can
|
12
12
|
# be used while building an object oriented wrapper
|
13
13
|
# around a REST service
|
@@ -15,7 +15,6 @@ module Wrest #:nodoc:
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
require "#{WREST_ROOT}/wrest/components/typecast_helpers"
|
19
18
|
require "#{WREST_ROOT}/wrest/components/attributes_container"
|
20
19
|
require "#{WREST_ROOT}/wrest/components/mutators"
|
21
20
|
require "#{WREST_ROOT}/wrest/components/translators"
|
@@ -7,16 +7,23 @@
|
|
7
7
|
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
8
8
|
# See the License for the specific language governing permissions and limitations under the License.
|
9
9
|
|
10
|
-
module Wrest
|
10
|
+
module Wrest
|
11
|
+
module Components::AttributesContainer
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
require "#{WREST_ROOT}/wrest/components/attributes_container/typecaster"
|
16
|
+
|
17
|
+
module Wrest::Components
|
11
18
|
|
12
19
|
# Adds behaviour allowing a class to
|
13
20
|
# contain attributes and providing support
|
14
21
|
# for dynamic getters, setters and query methods.
|
15
22
|
# These methods are added at runtime, on the first
|
16
|
-
# invocation and on a per instance basis.
|
17
|
-
# <tt>respond_to?</tt> however will respond as though
|
23
|
+
# invocation and on a per instance basis.
|
24
|
+
# <tt>respond_to?</tt> however will respond as though
|
18
25
|
# they are all already present.
|
19
|
-
# This means that two different instances of the same
|
26
|
+
# This means that two different instances of the same
|
20
27
|
# AttributesContainer could well have
|
21
28
|
# different attribute getters/setters/query methods.
|
22
29
|
#
|
@@ -30,99 +37,60 @@ module Wrest::Components #:nodoc:
|
|
30
37
|
# In situations where this is a problem, such as a client consuming Rails
|
31
38
|
# REST services where <tt>id</tt> is a common attribute and clashes with
|
32
39
|
# Object#id, it is recommended to create getter/setter/query methods
|
33
|
-
# on the class (which affects all instances) using the +
|
40
|
+
# on the class (which affects all instances) using the +always_has+ macro.
|
34
41
|
#
|
35
42
|
# If you're implementing your own initialize method
|
36
|
-
# remember to delegate to the default initialize
|
43
|
+
# remember to delegate to the default initialize
|
37
44
|
# of AttributesContainer by invoking <tt>super(attributes)</tt>
|
38
45
|
#
|
39
46
|
# Example:
|
40
47
|
# class ShenCoin
|
41
48
|
# include Wrest::Components::AttributesContainer
|
49
|
+
# include Wrest::Components::AttributesContainer::Typecaster
|
42
50
|
#
|
43
|
-
#
|
44
|
-
# typecast
|
45
|
-
# end
|
51
|
+
# always_has :id
|
52
|
+
# typecast :id => as_integer
|
53
|
+
# end
|
46
54
|
# coin = ShenCoin.new(:id => '5', :chi_count => 500, :owner => 'Kai Wren')
|
47
55
|
# coin.id # => 5
|
48
56
|
# coin.owner # => 'Kai Wren'
|
49
57
|
module AttributesContainer
|
50
58
|
def self.included(klass) #:nodoc:
|
51
59
|
klass.extend AttributesContainer::ClassMethods
|
52
|
-
klass.extend TypecastHelpers
|
53
60
|
klass.class_eval{ include AttributesContainer::InstanceMethods }
|
54
61
|
end
|
55
|
-
|
62
|
+
|
56
63
|
def self.build_attribute_getter(attribute_name) #:nodoc:
|
57
64
|
"def #{attribute_name};@attributes[:#{attribute_name}];end;"
|
58
65
|
end
|
59
|
-
|
66
|
+
|
60
67
|
def self.build_attribute_setter(attribute_name) #:nodoc:
|
61
68
|
"def #{attribute_name}=(value);@attributes[:#{attribute_name}] = value;end;"
|
62
69
|
end
|
63
|
-
|
70
|
+
|
64
71
|
def self.build_attribute_queryer(attribute_name) #:nodoc:
|
65
72
|
"def #{attribute_name}?;not @attributes[:#{attribute_name}].nil?;end;"
|
66
73
|
end
|
67
|
-
|
74
|
+
|
68
75
|
module ClassMethods
|
69
76
|
# This macro explicitly creates getter, setter and query methods on
|
70
|
-
# a class, overriding any exisiting methods with the same names.
|
77
|
+
# a class, overriding any exisiting methods with the same names.
|
71
78
|
# This can be used when attribute names clash with method names;
|
72
79
|
# an example would be Rails REST services which frequently make use
|
73
80
|
# an attribute named <tt>id</tt> which clashes with Object#id. Also,
|
74
81
|
# this can be used as a performance optimisation if the incoming
|
75
82
|
# attributes are known beforehand.
|
76
|
-
def
|
83
|
+
def always_has(*attribute_names)
|
77
84
|
attribute_names.each do |attribute_name|
|
78
85
|
self.class_eval(
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
)
|
86
|
+
AttributesContainer.build_attribute_getter(attribute_name) +
|
87
|
+
AttributesContainer.build_attribute_setter(attribute_name) +
|
88
|
+
AttributesContainer.build_attribute_queryer(attribute_name)
|
89
|
+
)
|
83
90
|
end
|
84
91
|
end
|
85
|
-
|
86
|
-
# Accepts a set of attribute-name/lambda pairs which are used
|
87
|
-
# to typecast string values injected through the constructor.
|
88
|
-
# Typically needed when populating an +AttributesContainer+
|
89
|
-
# directly from request params. Typecasting kicks in for
|
90
|
-
# a given value _only_ if it is a string.
|
91
|
-
#
|
92
|
-
# Typcast information is inherited by subclasses; however be
|
93
|
-
# aware that explicitly invoking +typecast+ in a subclass will
|
94
|
-
# discard inherited typecast information leaving only the casts
|
95
|
-
# defined in the subclass.
|
96
|
-
#
|
97
|
-
# Common typecasts such as integer, float, datetime etc. are
|
98
|
-
# available through predefined helpers. See TypecastHelpers
|
99
|
-
# for a full list.
|
100
|
-
#
|
101
|
-
# Example:
|
102
|
-
#
|
103
|
-
# class Demon
|
104
|
-
# include Wrest::Components::AttributesContainer
|
105
|
-
# typecast :age => as_integer,
|
106
|
-
# :chi => lambda{|chi| Chi.new(chi)}
|
107
|
-
# end
|
108
|
-
# kai_wren = Demon.new('age' => '1500', 'chi' => '1024')
|
109
|
-
# kai_wren.age # => 1500
|
110
|
-
# kai_wren.chi # => #<Chi:0x113af8c @count="1024">
|
111
|
-
def typecast(cast_map)
|
112
|
-
@typecast_map = @typecast_map ? @typecast_map.merge(cast_map.symbolize_keys) : cast_map.symbolize_keys
|
113
|
-
end
|
114
|
-
|
115
|
-
def typecast_map #:nodoc:
|
116
|
-
if defined?(@typecast_map)
|
117
|
-
@typecast_map
|
118
|
-
elsif superclass != Object && superclass.respond_to?(:typecast_map)
|
119
|
-
superclass.typecast_map
|
120
|
-
else
|
121
|
-
{}
|
122
|
-
end
|
123
|
-
end
|
124
92
|
end
|
125
|
-
|
93
|
+
|
126
94
|
module InstanceMethods
|
127
95
|
# Sets up any class to act like
|
128
96
|
# an attributes container by creating
|
@@ -132,22 +100,16 @@ module Wrest::Components #:nodoc:
|
|
132
100
|
# own class.
|
133
101
|
def initialize(attributes = {})
|
134
102
|
@attributes = attributes.symbolize_keys
|
135
|
-
self.class.typecast_map.each do |key, typecaster|
|
136
|
-
value = @attributes[key]
|
137
|
-
@attributes[key] = typecaster.call(value) if value.is_a?(String)
|
138
|
-
end
|
139
|
-
@interface = Module.new
|
140
|
-
self.extend @interface
|
141
103
|
end
|
142
|
-
|
104
|
+
|
143
105
|
def [](key)
|
144
106
|
@attributes[key.to_sym]
|
145
107
|
end
|
146
|
-
|
108
|
+
|
147
109
|
def []=(key, value)
|
148
110
|
@attributes[key.to_sym] = value
|
149
111
|
end
|
150
|
-
|
112
|
+
|
151
113
|
def respond_to?(method_name, include_private = false)
|
152
114
|
super(method_name, include_private) ? true : @attributes.include?(method_name.to_s.gsub(/(\?$)|(=$)/, '').to_sym)
|
153
115
|
end
|
@@ -157,22 +119,20 @@ module Wrest::Components #:nodoc:
|
|
157
119
|
def method_missing(method_sym, *arguments)
|
158
120
|
method_name = method_sym.to_s
|
159
121
|
attribute_name = method_name.gsub(/(\?$)|(=$)/, '')
|
160
|
-
|
161
122
|
if @attributes.include?(attribute_name.to_sym) || method_name.last == '='
|
162
123
|
case method_name.last
|
163
124
|
when '='
|
164
|
-
|
125
|
+
self.instance_eval AttributesContainer.build_attribute_setter(attribute_name)
|
165
126
|
when '?'
|
166
|
-
|
127
|
+
self.instance_eval AttributesContainer.build_attribute_queryer(attribute_name)
|
167
128
|
else
|
168
|
-
|
129
|
+
self.instance_eval AttributesContainer.build_attribute_getter(attribute_name)
|
169
130
|
end
|
170
131
|
send(method_sym, *arguments)
|
171
132
|
else
|
172
133
|
super(method_sym, *arguments)
|
173
134
|
end
|
174
135
|
end
|
175
|
-
|
176
136
|
end
|
177
137
|
end
|
178
138
|
end
|