wrest 0.0.5-java → 0.0.6-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/README.rdoc +31 -17
  2. data/Rakefile +7 -2
  3. data/VERSION.yml +1 -1
  4. data/lib/wrest/components.rb +3 -1
  5. data/lib/wrest/components/attributes_container.rb +65 -10
  6. data/lib/wrest/components/mutators.rb +2 -1
  7. data/lib/wrest/components/mutators/base.rb +27 -11
  8. data/lib/wrest/components/mutators/camel_to_snake_case.rb +20 -0
  9. data/lib/wrest/components/mutators/xml_simple_type_caster.rb +25 -21
  10. data/lib/wrest/{translators.rb → components/translators.rb} +6 -6
  11. data/lib/wrest/{translators → components/translators}/content_types.rb +5 -5
  12. data/lib/wrest/components/translators/json.rb +22 -0
  13. data/lib/wrest/{translators → components/translators}/xml.rb +19 -17
  14. data/lib/wrest/components/typecast_helpers.rb +41 -0
  15. data/lib/wrest/core_ext/hash/conversions.rb +22 -3
  16. data/lib/wrest/exceptions.rb +6 -1
  17. data/lib/wrest/{translators/json.rb → exceptions/method_not_overridden_exception.rb} +11 -15
  18. data/lib/wrest/exceptions/unsupported_content_type_exception.rb +11 -9
  19. data/lib/wrest/resource/base.rb +5 -1
  20. data/lib/wrest/response.rb +2 -2
  21. data/lib/wrest/uri.rb +3 -3
  22. data/lib/wrest/version.rb +1 -1
  23. data/spec/spec_helper.rb +2 -1
  24. data/spec/wrest/components/attributes_container_spec.rb +66 -11
  25. data/spec/wrest/components/mutators/base_spec.rb +28 -8
  26. data/spec/wrest/components/mutators/camel_to_snake_spec.rb +22 -0
  27. data/spec/wrest/components/mutators/xml_simple_type_caster_spec.rb +14 -8
  28. data/spec/wrest/{translators → components/translators}/xml_spec.rb +3 -3
  29. data/spec/wrest/components/translators_spec.rb +9 -0
  30. data/spec/wrest/core_ext/hash/conversions_spec.rb +1 -1
  31. data/spec/wrest/resource/base_spec.rb +1 -1
  32. data/spec/wrest/response_spec.rb +3 -3
  33. data/spec/wrest/uri_spec.rb +2 -2
  34. metadata +61 -56
  35. data/spec/wrest/translators_spec.rb +0 -9
data/README.rdoc CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  (c) Copyright 2009 {Sidu Ponnappa}[http://blog.sidu.in]. All Rights Reserved.
4
4
 
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.
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
+
7
+ Since no single framework 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 with ease. Wrest::Resource is an example of this - an object oriented wrapper for the kind of REST APIs exposed by Rails applications built using Wrest Core.
6
8
 
7
9
  == Installation
8
10
 
@@ -10,13 +12,12 @@ The source is available at git://github.com/kaiwren/wrest.git
10
12
 
11
13
  To install as a Rails plugin, do <tt>script/plugin install git://github.com/kaiwren/wrest.git</tt>
12
14
 
13
- To install the Wrest gem, do <tt>sudo gem install wrest</tt>
15
+ To install the Wrest gem, do <tt>sudo gem install wrest</tt>; Wrest is also available as a gem for JRuby using the same command.
14
16
 
15
17
  == Wrest Core
16
18
 
17
19
  * Designed to be used as a library, not just a command line REST client
18
- * Provides basic infrastructure (including an interactive shell) and convenient HTTP wrappers
19
- * Makes it easy to extend and modify both serialisation, deserialisation and object creation
20
+ * Provides infrastructure components such as convenient HTTP wrappers api, caching, redirect handling, serialisation, deserialisation etc.
20
21
  * Isn't coupled to Rails (usable in a pure Ruby application to consume any REST api)
21
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)
22
23
 
@@ -46,14 +47,22 @@ A couple of ways to get the Yahoo news as hash map (needs the JSON gem - gem ins
46
47
  * 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.
47
48
  "http://search.yahooapis.com/NewsSearchService/V1/newsSearch?appid=YahooDemo&output=json&query=India&results=3&start=1".to_uri.get.deserialise
48
49
 
49
- * 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 use a custom deserialiser to produce a hash-map from the response.
50
- "http://search.yahooapis.com/NewsSearchService/V1/newsSearch".to_uri.get(
51
- :appid => 'YahooDemo',
52
- :output => 'xml',
53
- :query => 'India',
54
- :results=> '3',
55
- :start => '1'
56
- ).deserialise_using(Wrest::Translators::Xml)
50
+ * 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
+ require 'rubygems'
52
+ require 'wrest'
53
+
54
+ include Wrest::Components
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
+ )
57
66
 
58
67
 
59
68
  === Logging
@@ -67,12 +76,13 @@ Standard options are available and can be listed using <tt>rake -T</tt>. Use rak
67
76
 
68
77
  == Documentation
69
78
 
70
- Wrest RDocs can be found at http://wrest.rubyforge.org/
79
+ Wrest RDocs can be found at http://wrest.rubyforge.org
71
80
 
72
81
  == Wrest::Resource
73
82
 
74
- Wrest::Resource is an alternative to ActiveResource which targets Rails REST services; it is currently under development.
83
+ Wrest::Resource is an alternative to ActiveResource. It targets Rails REST services and is currently under development.
75
84
 
85
+ * No more pretending that REST resources are the same as database records
76
86
  * Out of the box support for collections
77
87
  * Out of the box support for collection pagination (including support for WillPaginate), both header based and xml attribute based
78
88
  * Out of the box support for operations on all the records on the collection
@@ -85,13 +95,17 @@ Wrest::Resource is an alternative to ActiveResource which targets Rails REST ser
85
95
 
86
96
  == Dependencies
87
97
 
98
+ === Source
88
99
  * gems
89
100
  * xmlsimple
90
- * json
91
- * rspec
92
- * rcov
101
+ * json (json-jruby on JRuby)
93
102
  * active_support
94
103
 
104
+ === Build
105
+ * rspec
106
+ * rcov (unsupported on JRuby)
107
+ * jeweler
108
+
95
109
  == Support
96
110
 
97
111
  This project uses Assembla for ticketing: http://www.assembla.com/spaces/wrest/tickets
data/Rakefile CHANGED
@@ -10,7 +10,6 @@
10
10
  require 'rubygems'
11
11
  gem 'rspec'
12
12
  require 'rake'
13
- require 'rake/rdoctask'
14
13
  require 'spec'
15
14
  require 'spec/rake/spectask'
16
15
 
@@ -25,10 +24,16 @@ Spec::Rake::SpecTask.new(:spec) do |task|
25
24
  task.spec_opts = ['--options', 'spec/spec.opts']
26
25
  end
27
26
 
27
+ begin
28
+ require 'hanna/rdoctask'
29
+ rescue LoadError
30
+ puts 'Hanna not available, using standard Rake rdoctask. Fix this by running gem install mislav-hanna.'
31
+ require 'rake/rdoctask'
32
+ end
28
33
  desc 'Generate documentation for Wrest'
29
34
  Rake::RDocTask.new(:rdoc) do |rdoc|
30
35
  rdoc.rdoc_dir = 'rdoc'
31
- rdoc.title = 'WRest'
36
+ rdoc.title = 'Wrest Documentation'
32
37
  rdoc.options << '--line-numbers' << '--inline-source'
33
38
  rdoc.rdoc_files.include('README.rdoc')
34
39
  rdoc.rdoc_files.include('lib/**/*.rb')
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :patch: 5
2
+ :patch: 6
3
3
  :minor: 0
4
4
  :major: 0
@@ -15,5 +15,7 @@ module Wrest #:nodoc:
15
15
  end
16
16
  end
17
17
 
18
+ require "#{WREST_ROOT}/wrest/components/typecast_helpers"
18
19
  require "#{WREST_ROOT}/wrest/components/attributes_container"
19
- require "#{WREST_ROOT}/wrest/components/mutators"
20
+ require "#{WREST_ROOT}/wrest/components/mutators"
21
+ require "#{WREST_ROOT}/wrest/components/translators"
@@ -17,26 +17,39 @@ module Wrest::Components #:nodoc:
17
17
  # <tt>respond_to?</tt> however will respond as though
18
18
  # they are all already present.
19
19
  # This means that two different instances of the same
20
- # <tt>AttributesContainer</tt> could well have
20
+ # AttributesContainer could well have
21
21
  # different attribute getters/setters/query methods.
22
- #
23
- # Note that this means the first call to a particular
22
+ #
23
+ # Note that the first call to a particular getter/setter/query
24
24
  # method will be slower because the method is defined
25
25
  # at that point; subsequent calls will be much faster.
26
26
  #
27
- # If you're implementing your own initialize method
28
- # remember to delegate to the default initialize
29
- # of AttributesContainer by invoking <tt>super(attributes)</tt>
30
27
  # Also keep in mind that attribute getter/setter/query methods
31
28
  # will _not_ override any existing methods on the class.
32
29
  #
33
30
  # In situations where this is a problem, such as a client consuming Rails
34
31
  # REST services where <tt>id</tt> is a common attribute and clashes with
35
- # Object#id it is recommended to create getter/setter/query methods
36
- # on the class (which affects all instances) using the <tt>has_attributes</tt> macro.
32
+ # Object#id, it is recommended to create getter/setter/query methods
33
+ # on the class (which affects all instances) using the +has_attributes+ macro.
34
+ #
35
+ # If you're implementing your own initialize method
36
+ # remember to delegate to the default initialize
37
+ # of AttributesContainer by invoking <tt>super(attributes)</tt>
38
+ #
39
+ # Example:
40
+ # class ShenCoin
41
+ # include Wrest::Components::AttributesContainer
42
+ #
43
+ # has_attributes :id
44
+ # typecast :id => as_integer
45
+ # end
46
+ # coin = ShenCoin.new(:id => '5', :chi_count => 500, :owner => 'Kai Wren')
47
+ # coin.id # => 5
48
+ # coin.owner # => 'Kai Wren'
37
49
  module AttributesContainer
38
50
  def self.included(klass) #:nodoc:
39
51
  klass.extend AttributesContainer::ClassMethods
52
+ klass.extend TypecastHelpers
40
53
  klass.class_eval{ include AttributesContainer::InstanceMethods }
41
54
  end
42
55
 
@@ -59,8 +72,7 @@ module Wrest::Components #:nodoc:
59
72
  # an example would be Rails REST services which frequently make use
60
73
  # an attribute named <tt>id</tt> which clashes with Object#id. Also,
61
74
  # this can be used as a performance optimisation if the incoming
62
- # attributes are known beforehand, as defining methods on the first
63
- # invocation is no longer necessary.
75
+ # attributes are known beforehand.
64
76
  def has_attributes(*attribute_names)
65
77
  attribute_names.each do |attribute_name|
66
78
  self.class_eval(
@@ -70,6 +82,45 @@ module Wrest::Components #:nodoc:
70
82
  )
71
83
  end
72
84
  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
73
124
  end
74
125
 
75
126
  module InstanceMethods
@@ -81,6 +132,10 @@ module Wrest::Components #:nodoc:
81
132
  # own class.
82
133
  def initialize(attributes = {})
83
134
  @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
84
139
  @interface = Module.new
85
140
  self.extend @interface
86
141
  end
@@ -18,4 +18,5 @@ module Wrest #:nodoc:
18
18
  end
19
19
 
20
20
  require "#{WREST_ROOT}/wrest/components/mutators/base"
21
- require "#{WREST_ROOT}/wrest/components/mutators/xml_simple_type_caster"
21
+ require "#{WREST_ROOT}/wrest/components/mutators/xml_simple_type_caster"
22
+ require "#{WREST_ROOT}/wrest/components/mutators/camel_to_snake_case"
@@ -7,21 +7,37 @@
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
- # This is a Null Object implementation of a
11
- # hash mutator that will leave the contents
12
- # of any hash it is applied to unchanged.
10
+ # This is a base implementation of a
11
+ # hash mutator that ensures that the <tt>mutate</tt> method
12
+ # will chain to the next mutator by using a
13
+ # template method.
13
14
  class Wrest::Components::Mutators::Base
14
- # This method operates on a tuple (well, pair)
15
- # from a hash map.
16
- # Iterating over any hash using each injects
15
+ attr_reader :next_mutator
16
+
17
+ def initialize(next_mutator = nil)
18
+ @next_mutator = next_mutator
19
+ end
20
+
21
+ # This is a template method which operates on a tuple (well, pair)
22
+ # from a hash map and guarantees mutator chaining.
23
+ #
24
+ # Iterating over any hash using <tt>each</tt> injects
17
25
  # each key/value pair from the hash in the
18
26
  # form of an array.
19
- # Thus the tuple this method expects
20
- # is simply [:key, :value]
27
+ # This method expects of this form as an argument, i.e.
28
+ # an array with the structure [:key, :value]
21
29
  #
22
- # Since this is a Null Object, this method
23
- # simply returns the tuple unchanged
30
+ # The implementation of the mutation is achieved by
31
+ # overriding the <tt>do_mutate</tt> method in a subclass.
32
+ # Note that failing to do so will result in an exception
33
+ # at runtime.
24
34
  def mutate(tuple)
25
- tuple
35
+ out_tuple = do_mutate(tuple)
36
+ next_mutator ? next_mutator.mutate(out_tuple) : out_tuple
37
+ end
38
+
39
+ protected
40
+ def do_mutate(tuple)
41
+ raise Wrest::Exceptions::MethodNotOverriddenException
26
42
  end
27
43
  end
@@ -0,0 +1,20 @@
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
+ module Wrest::Components
11
+ class Mutators::CamelToSnakeCase < Mutators::Base
12
+ # Converts the key to snake case
13
+ #
14
+ # Example:
15
+ # Mutators::CamelToSnakeCase.new.mutate(['Spirit-Sword', 'true']) # => ['spirit_sword', 'true']
16
+ def do_mutate(tuple)
17
+ [tuple.first.underscore, tuple.last]
18
+ end
19
+ end
20
+ end
@@ -1,31 +1,35 @@
1
1
  # Copyright 2009 Sidu Ponnappa
2
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.
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
9
 
10
10
  # This mutator undertands how do type casting
11
11
  # using the type data embedded in a hash
12
12
  # created by deserialising an xml using
13
13
  # xml-simple
14
- class Wrest::Components::Mutators::XmlSimpleTypeCaster
15
- def mutate(tuple)
16
- out_key = tuple.first.underscore
17
- in_value = tuple.last[0]
18
- out_value = in_value
19
-
20
- case in_value
21
- when Hash
22
- if in_value["nil"] == "true"
23
- out_value = nil
24
- elsif in_value["type"] == "integer"
25
- out_value = in_value["content"].to_i
14
+ module Wrest::Components
15
+ class Mutators::XmlSimpleTypeCaster < Mutators::Base
16
+ def do_mutate(tuple)
17
+ out_key = tuple.first
18
+ in_value = tuple.last[0]
19
+ out_value = in_value
20
+
21
+ case in_value
22
+ when Hash
23
+ if in_value['nil'] == 'true'
24
+ out_value = nil
25
+ elsif in_value.key?('type')
26
+ out_value = ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING[in_value['type']].call(in_value['content'])
27
+ else
28
+ out_value = in_value.mutate_using(self)
29
+ end
26
30
  end
31
+
32
+ [out_key, out_value]
27
33
  end
28
-
29
- [out_key, out_value]
30
34
  end
31
- end
35
+ end
@@ -7,19 +7,19 @@
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::Components #:nodoc:
11
11
  # Contains strategies/lambdas which know how to deserialise
12
12
  # different content types.
13
13
  module Translators
14
14
  # Loads the appropriate desirialisation strategy based on
15
15
  # the content type
16
- def self.load(content_type)
16
+ def self.lookup(content_type)
17
17
  translator = CONTENT_TYPES[content_type]
18
- translator || (raise UnsupportedContentTypeException.new("Unsupported content type #{content_type}"))
18
+ translator || (raise Wrest::Exceptions::UnsupportedContentTypeException.new("Unsupported content type #{content_type}"))
19
19
  end
20
20
  end
21
21
  end
22
22
 
23
- require "#{WREST_ROOT}/wrest/translators/xml"
24
- require "#{WREST_ROOT}/wrest/translators/json"
25
- require "#{WREST_ROOT}/wrest/translators/content_types"
23
+ require "#{WREST_ROOT}/wrest/components/translators/xml"
24
+ require "#{WREST_ROOT}/wrest/components/translators/json"
25
+ require "#{WREST_ROOT}/wrest/components/translators/content_types"
@@ -7,14 +7,14 @@
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::Components
11
11
  module Translators
12
12
  # Maps content types to deserialisers
13
13
  CONTENT_TYPES = {
14
- 'application/xml' => Wrest::Translators::Xml,
15
- 'text/xml' => Wrest::Translators::Xml,
16
- 'application/json' => Wrest::Translators::Json,
17
- 'text/javascript' => Wrest::Translators::Json
14
+ 'application/xml' => Wrest::Components::Translators::Xml,
15
+ 'text/xml' => Wrest::Components::Translators::Xml,
16
+ 'application/json' => Wrest::Components::Translators::Json,
17
+ 'text/javascript' => Wrest::Components::Translators::Json
18
18
  }
19
19
  end
20
20
  end
@@ -0,0 +1,22 @@
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
+ module Wrest::Components::Translators
11
+ module Json
12
+ extend self
13
+
14
+ def deserialise(response)
15
+ ActiveSupport::JSON.decode(response.body)
16
+ end
17
+
18
+ def serialise(hash)
19
+ ActiveSupport::JSON.encode(hash)
20
+ end
21
+ end
22
+ end