wrest 0.0.6-java → 0.0.7-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 (34) hide show
  1. data/README.rdoc +51 -19
  2. data/Rakefile +2 -3
  3. data/VERSION.yml +1 -1
  4. data/lib/wrest.rb +11 -2
  5. data/lib/wrest/components.rb +1 -2
  6. data/lib/wrest/components/attributes_container.rb +31 -69
  7. data/lib/wrest/components/attributes_container/typecaster.rb +121 -0
  8. data/lib/wrest/components/mutators.rb +18 -1
  9. data/lib/wrest/components/mutators/base.rb +52 -39
  10. data/lib/wrest/components/mutators/camel_to_snake_case.rb +7 -5
  11. data/lib/wrest/components/mutators/xml_mini_type_caster.rb +43 -0
  12. data/lib/wrest/components/mutators/xml_simple_type_caster.rb +22 -20
  13. data/lib/wrest/components/translators.rb +20 -17
  14. data/lib/wrest/components/translators/content_types.rb +2 -2
  15. data/lib/wrest/components/translators/json.rb +11 -8
  16. data/lib/wrest/components/translators/xml.rb +9 -12
  17. data/lib/wrest/core_ext/hash/conversions.rb +1 -1
  18. data/lib/wrest/resource/base.rb +25 -13
  19. data/lib/wrest/resource/state.rb +6 -0
  20. data/lib/wrest/response.rb +4 -0
  21. data/lib/wrest/uri.rb +5 -1
  22. data/lib/wrest/version.rb +1 -1
  23. data/spec/spec.opts +1 -1
  24. data/spec/spec_helper.rb +8 -1
  25. data/spec/wrest/components/attributes_container/typecaster_spec.rb +63 -0
  26. data/spec/wrest/components/attributes_container_spec.rb +6 -61
  27. data/spec/wrest/components/mutators/base_spec.rb +5 -1
  28. data/spec/wrest/components/mutators/xml_mini_type_caster_spec.rb +75 -0
  29. data/spec/wrest/components/mutators_spec.rb +21 -0
  30. data/spec/wrest/components/translators/xml_spec.rb +1 -1
  31. data/spec/wrest/components/translators_spec.rb +9 -0
  32. data/spec/wrest/uri_spec.rb +16 -4
  33. metadata +14 -15
  34. data/lib/wrest/components/typecast_helpers.rb +0 -41
data/README.rdoc CHANGED
@@ -40,30 +40,56 @@ You can launch the interactive Wrest shell by running bin/wrest if you have the
40
40
  )
41
41
  === Basic Http Calls
42
42
 
43
- ==== Get
43
+ ==== GET
44
44
 
45
- A couple of ways to get the Yahoo news as hash map (needs the JSON gem - gem install json).
45
+ A couple of ways to get the Yahoo news as hash map.
46
46
 
47
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.
48
48
  "http://search.yahooapis.com/NewsSearchService/V1/newsSearch?appid=YahooDemo&output=json&query=India&results=3&start=1".to_uri.get.deserialise
49
49
 
50
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'
51
+ require 'rubygems'
52
+ require 'wrest'
53
+ include Wrest::Components
54
+ y "http://search.yahooapis.com/NewsSearchService/V1/newsSearch".to_uri.get(
55
+ :appid => 'YahooDemo',
56
+ :output => 'xml',
57
+ :query => 'India',
58
+ :results=> '3',
59
+ :start => '1'
60
+ ).deserialise_using(
61
+ Translators::Xml
62
+ ).mutate_using(
63
+ Mutators::XmlSimpleTypeCaster.new
64
+ )
65
+
66
+ ==== OPTIONS
67
+
68
+ To find out what actions are permitted on a URI:
69
+
70
+ 'http://www.yahoo.com'.to_uri.options.headers['allow']
71
+
72
+
73
+ === Attributes Container
74
+
75
+ 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.
76
+
77
+ Example:
78
+
79
+ class Demon
80
+ include Wrest::Components::AttributesContainer
81
+ include Wrest::Components::AttributesContainer::Typecaster
53
82
 
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
- )
83
+ always_has :id
84
+ typecast :age => as_integer,
85
+ :chi => lambda{|chi| Chi.new(chi)}
86
+ end
66
87
 
88
+ kai_wren = Demon.new('id => '1', 'age' => '1500', 'chi' => '1024', 'teacher' => 'Viss')
89
+ kai_wren.id # => '1'
90
+ kai_wren.age # => 1500
91
+ kai_wren.chi # => #<Chi:0x113af8c @count="1024">
92
+ kai_wren.teacher # => 'Viss'
67
93
 
68
94
  === Logging
69
95
 
@@ -80,9 +106,14 @@ Wrest RDocs can be found at http://wrest.rubyforge.org
80
106
 
81
107
  == Wrest::Resource
82
108
 
83
- Wrest::Resource is an alternative to ActiveResource. It targets Rails REST services and is currently under development.
109
+ Wrest::Resource is an alternative to ActiveResource. It targets Rails REST (well, POX - turns out Rails isn't really REST) services and is currently under development.
84
110
 
85
- * No more pretending that REST resources are the same as database records
111
+ * No more pretending that REST resources are the same as records in a database (yeah, no more freaking ActiveResource::Connection)
112
+ * Treat put as 'create or update,' not just 'update'
113
+ * Response codes result in user defined state transitions; favours state transitions based on response code over arbitrary ones
114
+ * Supports moving toward hypermedia links as opposed to client server collusion through URI templates
115
+ * The header is now exposed as metadata, rather being than something you have no control over
116
+ * Out of the box support for If-Unmodified-Since/If-Match+Etag
86
117
  * Out of the box support for collections
87
118
  * Out of the box support for collection pagination (including support for WillPaginate), both header based and xml attribute based
88
119
  * Out of the box support for operations on all the records on the collection
@@ -92,14 +123,15 @@ Wrest::Resource is an alternative to ActiveResource. It targets Rails REST servi
92
123
  * More natural mapping of deserialised entities to existing classes
93
124
  * No communication via exceptions for http error status codes
94
125
  * Better extensibility - allows access to request/response objects, avoids class variables, favours symbols over strings etc.
126
+ * Consider support for OPTIONS and response codes 100/417
95
127
 
96
128
  == Dependencies
97
129
 
98
130
  === Source
99
131
  * gems
100
- * xmlsimple
101
132
  * json (json-jruby on JRuby)
102
133
  * active_support
134
+ * 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
135
 
104
136
  === Build
105
137
  * rspec
data/Rakefile CHANGED
@@ -67,12 +67,11 @@ begin
67
67
  gemspec.homepage = "http://github.com/kaiwren/wrest"
68
68
  gemspec.has_rdoc = true
69
69
  gemspec.rubyforge_project = 'wrest'
70
- gemspec.executables = ['wrest']
70
+ gemspec.executables = ['wrest', 'jwrest']
71
71
  gemspec.require_path = "lib"
72
72
  gemspec.files.exclude 'spec/wrest/meh_spec.rb'
73
73
  gemspec.test_files.exclude 'spec/wrest/meh_spec.rb'
74
- gemspec.add_dependency('activesupport', '>= 2.1.0')
75
- gemspec.add_dependency('xml-simple', '>= 1.0.11')
74
+ gemspec.add_dependency('activesupport', '>= 2.3.2')
76
75
  case RUBY_PLATFORM
77
76
  when /java/
78
77
  gemspec.add_dependency('json-jruby', '>= 1.1.3')
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :patch: 6
2
+ :patch: 7
3
3
  :minor: 0
4
4
  :major: 0
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 = Logger.new(STDOUT)
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|
@@ -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 #:nodoc:
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::Components #:nodoc:
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 +has_attributes+ macro.
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
- # has_attributes :id
44
- # typecast :id => as_integer
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 has_attributes(*attribute_names)
83
+ def always_has(*attribute_names)
77
84
  attribute_names.each do |attribute_name|
78
85
  self.class_eval(
79
- AttributesContainer.build_attribute_getter(attribute_name) +
80
- AttributesContainer.build_attribute_setter(attribute_name) +
81
- AttributesContainer.build_attribute_queryer(attribute_name)
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,18 @@ 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
103
  @interface = Module.new
140
104
  self.extend @interface
141
105
  end
142
-
106
+
143
107
  def [](key)
144
108
  @attributes[key.to_sym]
145
109
  end
146
-
110
+
147
111
  def []=(key, value)
148
112
  @attributes[key.to_sym] = value
149
113
  end
150
-
114
+
151
115
  def respond_to?(method_name, include_private = false)
152
116
  super(method_name, include_private) ? true : @attributes.include?(method_name.to_s.gsub(/(\?$)|(=$)/, '').to_sym)
153
117
  end
@@ -157,7 +121,6 @@ module Wrest::Components #:nodoc:
157
121
  def method_missing(method_sym, *arguments)
158
122
  method_name = method_sym.to_s
159
123
  attribute_name = method_name.gsub(/(\?$)|(=$)/, '')
160
-
161
124
  if @attributes.include?(attribute_name.to_sym) || method_name.last == '='
162
125
  case method_name.last
163
126
  when '='
@@ -172,7 +135,6 @@ module Wrest::Components #:nodoc:
172
135
  super(method_sym, *arguments)
173
136
  end
174
137
  end
175
-
176
138
  end
177
139
  end
178
140
  end
@@ -0,0 +1,121 @@
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
11
+ module Components::AttributesContainer
12
+ # An extension to AttributesContainer that adds support for
13
+ # specifying how the values associated with certain attribute keys
14
+ # should be typecast.
15
+ #
16
+ # This extension can be used in situations where the attributes
17
+ # hash consists of just strings with no associated tup information.
18
+ # For example, params recieved from a web browser may contain
19
+ # attributes like
20
+ # 'id' => '4', 'dateofbirth' => '1984-04-05'
21
+ # and we'd like to have these cast to an integer and a date
22
+ # respectively, rather than have to deal with them as strings.
23
+ module Typecaster
24
+ def self.included(klass) #:nodoc:
25
+ klass.extend Typecaster::ClassMethods
26
+ klass.class_eval{ include Typecaster::InstanceMethods }
27
+ klass.alias_method_chain :initialize, :typecasting
28
+ end
29
+
30
+ module ClassMethods
31
+ # Accepts a set of attribute-name/lambda pairs which are used
32
+ # to typecast string values injected through the constructor.
33
+ # Typically needed when populating an +AttributesContainer+
34
+ # directly from request params. Typecasting kicks in for
35
+ # a given value _only_ if it is a string.
36
+ #
37
+ # Typecast information is inherited by subclasses; however be
38
+ # aware that explicitly invoking +typecast+ in a subclass will
39
+ # discard inherited typecast information leaving only the casts
40
+ # defined in the subclass.
41
+ #
42
+ # Common typecasts such as integer, float, datetime etc. are
43
+ # available through predefined helpers. See TypecastHelpers
44
+ # for a full list.
45
+ #
46
+ # Example:
47
+ #
48
+ # class Demon
49
+ # include Wrest::Components::AttributesContainer
50
+ # include Wrest::Components::AttributesContainer::Typecaster
51
+ #
52
+ # typecast :age => as_integer,
53
+ # :chi => lambda{|chi| Chi.new(chi)}
54
+ # end
55
+ #
56
+ # kai_wren = Demon.new('age' => '1500', 'chi' => '1024')
57
+ # kai_wren.age # => 1500
58
+ # kai_wren.chi # => #<Chi:0x113af8c @count="1024">
59
+ def typecast(cast_map)
60
+ @typecast_map = @typecast_map ? @typecast_map.merge(cast_map.symbolize_keys) : cast_map.symbolize_keys
61
+ end
62
+
63
+ def typecast_map #:nodoc:
64
+ if defined?(@typecast_map)
65
+ @typecast_map
66
+ elsif superclass != Object && superclass.respond_to?(:typecast_map)
67
+ superclass.typecast_map
68
+ else
69
+ {}
70
+ end
71
+ end
72
+
73
+ def as_base64Binary
74
+ ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING['base64Binary']
75
+ end
76
+
77
+ def as_boolean
78
+ ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING['boolean']
79
+ end
80
+
81
+ def as_decimal
82
+ ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING['decimal']
83
+ end
84
+
85
+ def as_date
86
+ ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING['date']
87
+ end
88
+
89
+ def as_datetime
90
+ ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING['datetime']
91
+ end
92
+
93
+ def as_float
94
+ ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING['float']
95
+ end
96
+
97
+ def as_integer
98
+ ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING['integer']
99
+ end
100
+
101
+ def as_symbol
102
+ ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING['symbol']
103
+ end
104
+
105
+ def as_yaml
106
+ ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING['yaml']
107
+ end
108
+ end
109
+
110
+ module InstanceMethods # :nodoc:
111
+ def initialize_with_typecasting(attributes = {}) # :nodoc:
112
+ initialize_without_typecasting(attributes)
113
+ self.class.typecast_map.each do |key, typecaster|
114
+ value = @attributes[key]
115
+ @attributes[key] = typecaster.call(value) if value.is_a?(String)
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end