wrest 0.0.5-java → 0.0.6-java

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.
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