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.
Files changed (51) hide show
  1. data/README.rdoc +83 -31
  2. data/Rakefile +29 -13
  3. data/VERSION.yml +1 -1
  4. data/examples/delicious.rb +58 -0
  5. data/examples/wow_realm_status.rb +57 -0
  6. data/lib/wrest.rb +11 -2
  7. data/lib/wrest/components.rb +1 -2
  8. data/lib/wrest/components/attributes_container.rb +34 -74
  9. data/lib/wrest/components/attributes_container/typecaster.rb +121 -0
  10. data/lib/wrest/components/mutators.rb +18 -1
  11. data/lib/wrest/components/mutators/base.rb +52 -39
  12. data/lib/wrest/components/mutators/camel_to_snake_case.rb +7 -5
  13. data/lib/wrest/components/mutators/xml_mini_type_caster.rb +43 -0
  14. data/lib/wrest/components/mutators/xml_simple_type_caster.rb +22 -20
  15. data/lib/wrest/components/translators.rb +20 -17
  16. data/lib/wrest/components/translators/content_types.rb +2 -2
  17. data/lib/wrest/components/translators/json.rb +11 -8
  18. data/lib/wrest/components/translators/xml.rb +9 -12
  19. data/lib/wrest/core_ext/hash/conversions.rb +1 -1
  20. data/lib/wrest/core_ext/string/conversions.rb +2 -2
  21. data/lib/wrest/http.rb +24 -0
  22. data/lib/wrest/http/delete.rb +23 -0
  23. data/lib/wrest/http/get.rb +23 -0
  24. data/lib/wrest/http/options.rb +23 -0
  25. data/lib/wrest/http/post.rb +23 -0
  26. data/lib/wrest/http/put.rb +23 -0
  27. data/lib/wrest/http/request.rb +48 -0
  28. data/lib/wrest/http/response.rb +44 -0
  29. data/lib/wrest/resource/base.rb +52 -25
  30. data/lib/wrest/resource/state.rb +6 -0
  31. data/lib/wrest/uri.rb +85 -29
  32. data/lib/wrest/uri_template.rb +18 -1
  33. data/lib/wrest/version.rb +1 -1
  34. data/spec/spec.opts +1 -1
  35. data/spec/spec_helper.rb +8 -1
  36. data/spec/wrest/components/attributes_container/typecaster_spec.rb +63 -0
  37. data/spec/wrest/components/attributes_container_spec.rb +17 -61
  38. data/spec/wrest/components/mutators/base_spec.rb +5 -1
  39. data/spec/wrest/components/mutators/xml_mini_type_caster_spec.rb +75 -0
  40. data/spec/wrest/components/mutators_spec.rb +21 -0
  41. data/spec/wrest/components/translators/xml_spec.rb +1 -1
  42. data/spec/wrest/components/translators_spec.rb +9 -0
  43. data/spec/wrest/core_ext/string/conversions_spec.rb +9 -0
  44. data/spec/wrest/http/request_spec.rb +22 -0
  45. data/spec/wrest/{response_spec.rb → http/response_spec.rb} +4 -4
  46. data/spec/wrest/resource/base_spec.rb +126 -11
  47. data/spec/wrest/uri_spec.rb +124 -20
  48. data/spec/wrest/uri_template_spec.rb +11 -1
  49. metadata +33 -20
  50. data/lib/wrest/components/typecast_helpers.rb +0 -41
  51. data/lib/wrest/response.rb +0 -38
@@ -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
- 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.
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>; Wrest is also available as a gem for JRuby using the same command.
26
+ To install the Wrest gem, do <tt>(sudo) gem install wrest</tt>
16
27
 
17
- == Wrest Core
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
- >> "http://search.yahooapis.com/NewsSearchService/V1/newsSearch?appid=YahooDemo&output=json&query=India&results=3&start=1".to_uri.get
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
- ==== Get
49
+ ==== GET
44
50
 
45
- A couple of ways to get the Yahoo news as hash map (needs the JSON gem - gem install json).
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
- require 'rubygems'
52
- require 'wrest'
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
- 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
- )
72
+ ==== DELETE
66
73
 
74
+ To delete a resource:
67
75
 
68
- === Logging
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 records
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.1.0')
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: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
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
@@ -1,4 +1,4 @@
1
1
  ---
2
- :patch: 6
2
+ :patch: 8
3
3
  :minor: 0
4
4
  :major: 0
@@ -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}" }
@@ -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,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
- @interface.module_eval AttributesContainer.build_attribute_setter(attribute_name)
125
+ self.instance_eval AttributesContainer.build_attribute_setter(attribute_name)
165
126
  when '?'
166
- @interface.module_eval AttributesContainer.build_attribute_queryer(attribute_name)
127
+ self.instance_eval AttributesContainer.build_attribute_queryer(attribute_name)
167
128
  else
168
- @interface.module_eval AttributesContainer.build_attribute_getter(attribute_name)
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