actionservice 0.2.102 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog CHANGED
@@ -1,3 +1,3 @@
1
- TBD
1
+ 0.3.0
2
2
 
3
3
  * First public release
data/README CHANGED
@@ -1,18 +1,16 @@
1
1
  = Action Service -- Serving APIs on rails
2
2
 
3
- Action Service provides a way to publish interoperablel web service APIs with
3
+ Action Service provides a way to publish interoperable web service APIs with
4
4
  Rails without wasting time delving into protocol details.
5
5
 
6
6
 
7
7
  == Features
8
8
 
9
9
  * SOAP RPC protocol support
10
- * Dynamic WSDL generation
10
+ * Dynamic WSDL generation for exported APIs
11
11
  * XML-RPC protocol support
12
- * Strong type signature hints to improve interoperability with static
13
- languages
14
- * Using Active Record derivatives in return signatures automatically generates
15
- a structured type equivalent that can be sent over the wire
12
+ * Type signature hints to improve interoperability with static languages
13
+ * Active Record model class support in signatures
16
14
 
17
15
 
18
16
  == Integration with Action Pack
@@ -21,21 +19,57 @@ Action Service can be integrated in two different dispatching modes, _Direct_ an
21
19
  _Delegated_.
22
20
 
23
21
 
24
- * _Direct_ mode refers to a dispatching mode where one controller represents a
25
- single API service, and actions in the controller represent API methods. This is
26
- the default mode.
27
- * _Delegated_ mode refers to a dispatching mode where a controller is a
28
- container for one or more API services, and an action on the controller
29
- serves as the entry point for an associated service.
22
+ == Direct dispatching
23
+
24
+ This is the default mode. In this mode, controller actions can be exported as
25
+ API methods, and parameters for incoming method calls will be placed in
26
+ <tt>@params</tt>. The return value of the action will be sent back as the
27
+ return value to the caller.
28
+
29
+ Requests from callers will be sent to <tt>/controller_name/api</tt> using
30
+ HTTP POST.
31
+
32
+ ==== Direct dispatching example
33
+
34
+ class PersonApiController < ApplicationController
35
+ def add
36
+ end
37
+
38
+ def remove
39
+ end
40
+
41
+ export :add
42
+ export :remove
43
+ end
44
+
45
+
46
+ == Delegated dispatching
47
+
48
+ This mode can be turned on by setting the +service_dispatching_mode+ option.
49
+ In this mode, a controller is a container for one or more service objects,
50
+ each implementing a seperate API.
51
+
52
+ Requests from callers will be sent to <tt>/controller_name/service_name</tt>
53
+ using HTTP POST.
54
+
55
+ ==== Delegated dispatching example
56
+
57
+ class ApiController < ApplicationController
58
+ service_dispatching_mode :delegated
59
+
60
+ service :person, PersonApi.new
61
+ service :customer, CustomerApi.new
62
+ end
30
63
 
31
64
 
32
65
  == Dependencies
33
66
 
34
- Action Service requires that the Action Pack and Active Record are either
67
+ Action Service requires that the Action Pack and Active Record are either
35
68
  available to be required immediately or are accessible as GEMs.
36
69
 
37
70
  It also requires a version of Ruby that includes SOAP support in the standard
38
- library. At least version 1.8.2 final (2004-12-25) of Ruby is recommended.
71
+ library. At least version 1.8.2 final (2004-12-25) of Ruby is recommended, this
72
+ is the version tested against.
39
73
 
40
74
 
41
75
  == Download
data/Rakefile CHANGED
@@ -5,10 +5,11 @@ require 'rake/rdoctask'
5
5
  require 'rake/packagetask'
6
6
  require 'rake/gempackagetask'
7
7
  require 'rake/contrib/rubyforgepublisher'
8
+ require 'fileutils'
8
9
 
9
10
  PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
10
11
  PKG_NAME = 'actionservice'
11
- PKG_VERSION = '0.2.102' + PKG_BUILD
12
+ PKG_VERSION = '0.3.0' + PKG_BUILD
12
13
  PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
13
14
 
14
15
  desc "Default Task"
@@ -70,20 +71,10 @@ Rake::GemPackageTask.new(spec) do |p|
70
71
  end
71
72
 
72
73
 
73
- desc "Publish the beta gem"
74
- task :pgem => [:package] do
75
- # Rake::SshFilePublisher.new("davidhh@comox.textdrive.com", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
76
- end
77
-
78
-
79
- desc "Publish the API documentation"
80
- task :pdoc => [:rdoc] do
81
- # Rake::SshDirPublisher.new("davidhh@comox.textdrive.com", "public_html/am", "doc").upload
82
- end
83
-
84
-
85
- desc "Publish to RubyForge"
86
- task :rubyforge do
74
+ desc "Publish API docs to RubyForge"
75
+ task :pdoc => [:rdoc] do
76
+ FileUtils.mkdir_p 'html'
77
+ FileUtils.mv 'doc', 'html/api'
87
78
  Rake::RubyForgePublisher.new('actionservice', 'ljb').upload
88
79
  end
89
80
 
data/TODO CHANGED
@@ -1,4 +1,6 @@
1
1
  = Tasks
2
+ - add more direct dispatching tests (method exists, not exists, etc) in
3
+ container
2
4
  - add better type mapping tests for XML-RPC
3
5
  - add tests for ActiveRecord support (with mock objects?)
4
6
 
@@ -2,24 +2,39 @@
2
2
 
3
3
 
4
4
  This example shows how one would implement an API like Google
5
- Search, that uses lots of structured types.
5
+ Search that uses lots of structured types.
6
6
 
7
+ There are examples for "Direct" and "Delegated" dispatching
8
+ modes.
7
9
 
8
- = Testing
10
+
11
+ = Running
9
12
 
10
13
  1. Ensure you have the 'actionservice' Gem installed. You can generate it using
11
14
  this command:
12
15
 
13
16
  $ rake package
14
17
 
15
- 2. Copy lib/google_search_service.rb and app/controllers/search_controller.rb
16
- into a Rails project.
17
18
 
18
- 3. Edit config/environment.rb, and add the following line after the rest of the
19
+ 2. Edit config/environment.rb, and add the following line after the rest of the
19
20
  require_gem statements:
20
21
 
21
22
  require_gem 'actionservice'
22
23
 
24
+
25
+ 3. "Direct" example:
26
+
27
+ * Copy direct/search_controller.rb to "app/controllers"
28
+ in a Rails project.
29
+
30
+ "Delegated" example:
31
+
32
+ * Copy delegated/search_controller.rb to "app/controllers"
33
+ in a Rails project.
34
+ * Copy delegated/google_search_service.rb to "lib"
35
+ in a Rails project.
36
+
37
+
23
38
  4. Go to the url http://URL/search/wsdl in a browser, and check that it looks
24
39
  correct.
25
40
 
@@ -27,7 +42,9 @@ Search, that uses lots of structured types.
27
42
  and see how close (or not) the generated version is.
28
43
 
29
44
  Note that I used GoogleSearch as the canonical "best practice"
30
- interoperable example, which might explain extreme similarities :)
45
+ interoperable example when implementing WSDL/SOAP support, which might
46
+ explain extreme similarities :)
47
+
31
48
 
32
49
  5. Test that it works with .NET (Mono in this example):
33
50
 
@@ -79,8 +96,7 @@ Search, that uses lots of structured types.
79
96
 
80
97
 
81
98
  If you had the application running (on the same host you got
82
- the WSDL from), you should see something like this (values will match up
83
- with those in lib/google_search_service.rb):
99
+ the WSDL from), you should see something like this:
84
100
 
85
101
 
86
102
  documentFiltering: True
@@ -108,8 +124,8 @@ Search, that uses lots of structured types.
108
124
 
109
125
 
110
126
  Also, if an API method throws an exception, it will be sent back to the
111
- caller in SOAP exception format, so they'll get an exception thrown on
112
- their side with a meaningful error message.
127
+ caller in the protocol's exception format, so they should get an exception
128
+ thrown on their side with a meaningful error message.
113
129
 
114
130
  If you don't like this behaviour, you can do:
115
131
 
@@ -80,14 +80,27 @@ class GoogleSearchService < ActionService::Base
80
80
  result
81
81
  end
82
82
 
83
- def category(name, encoding)
84
- cat = DirectoryCategory.new
85
- cat.fullViewableName = name.dup
86
- cat.specialEncoding = encoding.dup
87
- cat
88
- end
89
-
90
83
  export :doGetCachedPage, :returns => [String], :expects => [{:key=>String}, {:url=>String}]
91
- export :doGetSpellingSuggestion, :returns => [String], :expects => [String, String]
92
- export :doGoogleSearch, :returns => [GoogleSearchResult], :expects => [String, String, Integer, Integer, TrueClass, String, TrueClass, String, String, String]
84
+ export :doGetSpellingSuggestion, :returns => [String], :expects => [{:key=>String}, {:phrase=>String}]
85
+
86
+ export :doGoogleSearch, :returns => [GoogleSearchResult], :expects => [
87
+ {:key=>String},
88
+ {:q=>String},
89
+ {:start=>Integer},
90
+ {:maxResults=>Integer},
91
+ {:filter=>TrueClass},
92
+ {:restrict=>String},
93
+ {:safeSearch=>TrueClass},
94
+ {:lr=>String},
95
+ {:ie=>String},
96
+ {:oe=>String}
97
+ ]
98
+
99
+ private
100
+ def category(name, encoding)
101
+ cat = DirectoryCategory.new
102
+ cat.fullViewableName = name.dup
103
+ cat.specialEncoding = encoding.dup
104
+ cat
105
+ end
93
106
  end
@@ -0,0 +1,106 @@
1
+ class DirectoryCategory < ActionService::Struct
2
+ member :fullViewableName, String
3
+ member :specialEncoding, String
4
+ end
5
+
6
+ class ResultElement < ActionService::Struct
7
+ member :summary, String
8
+ member :URL, String
9
+ member :snippet, String
10
+ member :title, String
11
+ member :cachedSize, String
12
+ member :relatedInformationPresent, TrueClass
13
+ member :hostName, String
14
+ member :directoryCategory, DirectoryCategory
15
+ member :directoryTitle, String
16
+ end
17
+
18
+ class GoogleSearchResult < ActionService::Struct
19
+ member :documentFiltering, TrueClass
20
+ member :searchComments, String
21
+ member :estimatedTotalResultsCount, Integer
22
+ member :estimateIsExact, TrueClass
23
+ member :resultElements, [ResultElement]
24
+ member :searchQuery, String
25
+ member :startIndex, Integer
26
+ member :endIndex, Integer
27
+ member :searchTips, String
28
+ member :directoryCategories, [DirectoryCategory]
29
+ member :searchTime, Float
30
+ end
31
+
32
+
33
+ class SearchController < ApplicationController
34
+ wsdl_service_name 'GoogleSearch'
35
+ export_name_mangling false
36
+
37
+ def doGetCachedPage
38
+ "<html><body>i am a cached page. my key was %s, url was %s</body></html>" % [@params['key'], @params['url']]
39
+ end
40
+
41
+ def doSpellingSuggestion
42
+ "%s: Did you mean '%s'?" % [@params['key'], @params['phrase']]
43
+ end
44
+
45
+ def doGoogleSearch
46
+ resultElement = ResultElement.new
47
+ resultElement.summary = "ONlamp.com: Rolling with Ruby on Rails"
48
+ resultElement.URL = "http://www.onlamp.com/pub/a/onlamp/2005/01/20/rails.html"
49
+ resultElement.snippet = "Curt Hibbs shows off Ruby on Rails by building a simple application that requires " +
50
+ "almost no Ruby experience. ... Rolling with Ruby on Rails. ..."
51
+ resultElement.title = "Teh Railz0r"
52
+ resultElement.cachedSize = "Almost no lines of code!"
53
+ resultElement.relatedInformationPresent = true
54
+ resultElement.hostName = "rubyonrails.com"
55
+ resultElement.directoryCategory = category("Web Development", "UTF-8")
56
+
57
+ result = GoogleSearchResult.new
58
+ result.documentFiltering = @params['filter']
59
+ result.searchComments = ""
60
+ result.estimatedTotalResultsCount = 322000
61
+ result.estimateIsExact = false
62
+ result.resultElements = [resultElement]
63
+ result.searchQuery = "http://www.google.com/search?q=ruby+on+rails"
64
+ result.startIndex = @params['start']
65
+ result.endIndex = @params['start'] + @params['maxResults']
66
+ result.searchTips = "\"on\" is a very common word and was not included in your search [details]"
67
+ result.searchTime = 0.000001
68
+
69
+ # For Mono, we have to clone objects if they're referenced by more than one place, otherwise
70
+ # the Ruby SOAP collapses them into one instance and uses references all over the
71
+ # place, confusing Mono.
72
+ #
73
+ # This has recently been fixed:
74
+ # http://bugzilla.ximian.com/show_bug.cgi?id=72265
75
+ result.directoryCategories = [
76
+ category("Web Development", "UTF-8"),
77
+ category("Programming", "US-ASCII"),
78
+ ]
79
+
80
+ result
81
+ end
82
+
83
+ export :doGetCachedPage, :returns => [String], :expects => [{:key=>String}, {:url=>String}]
84
+ export :doGetSpellingSuggestion, :returns => [String], :expects => [{:key=>String}, {:phrase=>String}]
85
+
86
+ export :doGoogleSearch, :returns => [GoogleSearchResult], :expects => [
87
+ {:key=>String},
88
+ {:q=>String},
89
+ {:start=>Integer},
90
+ {:maxResults=>Integer},
91
+ {:filter=>TrueClass},
92
+ {:restrict=>String},
93
+ {:safeSearch=>TrueClass},
94
+ {:lr=>String},
95
+ {:ie=>String},
96
+ {:oe=>String}
97
+ ]
98
+
99
+ private
100
+ def category(name, encoding)
101
+ cat = DirectoryCategory.new
102
+ cat.fullViewableName = name.dup
103
+ cat.specialEncoding = encoding.dup
104
+ cat
105
+ end
106
+ end
@@ -0,0 +1,28 @@
1
+ = metaWeblog example
2
+
3
+
4
+ This example shows how one might begin to go about adding metaWeblog
5
+ (http://www.xmlrpc.com/metaWeblogApi) API support to a Rails-based
6
+ blogging application.
7
+
8
+
9
+ = Running
10
+
11
+ 1. Ensure you have the 'actionservice' Gem installed. You can generate it using
12
+ this command:
13
+
14
+ $ rake package
15
+
16
+
17
+ 2. Edit config/environment.rb, and add the following line after the rest of the
18
+ require_gem statements:
19
+
20
+ require_gem 'actionservice'
21
+
22
+
23
+ 3. Copy blog_controller.rb to "app/controllers" in a Rails project.
24
+
25
+
26
+ 4. Fire up a desktop blogging application (such as BloGTK on Linux),
27
+ point it at http://localhost:3000/blog/api, and try creating or
28
+ editing blog posts.
@@ -0,0 +1,117 @@
1
+ # structures as defined by the metaWeblog/blogger
2
+ # specifications.
3
+ module Blog
4
+ class Enclosure < ActionService::Struct
5
+ member :url, String
6
+ member :length, Integer
7
+ member :type, String
8
+ end
9
+
10
+ class Source < ActionService::Struct
11
+ member :url, String
12
+ member :name, String
13
+ end
14
+
15
+ class Post < ActionService::Struct
16
+ member :title, String
17
+ member :link, String
18
+ member :description, String
19
+ member :author, String
20
+ member :category, String
21
+ member :comments, String
22
+ member :enclosure, Enclosure
23
+ member :guid, String
24
+ member :pubDate, String
25
+ member :source, Source
26
+ end
27
+
28
+ class Blog < ActionService::Struct
29
+ member :url, String
30
+ member :blogid, String
31
+ member :blogName, String
32
+ end
33
+ end
34
+
35
+
36
+ class BlogController < ApplicationController
37
+ export_name_mangling false
38
+
39
+ def initialize
40
+ @postid = 0
41
+ end
42
+
43
+ def newPost
44
+ $stderr.puts 'Creating post: username=%s password=%s struct=%s' % [
45
+ @params['username'],
46
+ @params['password'],
47
+ @params['struct'].inspect
48
+ ]
49
+ (@postid += 1).to_s
50
+ end
51
+
52
+ def editPost
53
+ $stderr.puts 'Editing post: username=%s password=%s struct=%s' % [
54
+ @params['username'],
55
+ @params['password'],
56
+ @params['struct'].inspect
57
+ ]
58
+ true
59
+ end
60
+
61
+ def getUsersBlogs
62
+ $stderr.puts "Returning user %s's blogs" % @params['username']
63
+ blog = Blog::Blog.new
64
+ blog.url = 'http://blog.xeraph.org'
65
+ blog.blogid = 'sttm'
66
+ blog.blogName = 'slave to the machine'
67
+ [blog]
68
+ end
69
+
70
+ def getRecentPosts
71
+ $stderr.puts "Returning recent posts (%d requested)" % @params['numberOfPosts']
72
+ post1 = Blog::Post.new
73
+ post1.title = 'first post!'
74
+ post1.link = 'http://blog.xeraph.org/testOne.html'
75
+ post1.description = 'this is the first post'
76
+ post2 = Blog::Post.new
77
+ post2.title = 'second post!'
78
+ post2.link = 'http://blog.xeraph.org/testTwo.html'
79
+ post2.description = 'this is the second post'
80
+ [post1, post2]
81
+ end
82
+
83
+ export :newPost, :returns => [String], :expects => [
84
+ {:blogid=>String},
85
+ {:username=>String},
86
+ {:password=>String},
87
+ {:struct=>Blog::Post},
88
+ {:publish=>TrueClass},
89
+ ]
90
+
91
+ export :editPost, :returns => [TrueClass], :expects => [
92
+ {:postid=>String},
93
+ {:username=>String},
94
+ {:password=>String},
95
+ {:struct=>Blog::Post},
96
+ {:publish=>TrueClass},
97
+ ]
98
+
99
+ export :getPost, :returns => [Blog::Post], :expects => [
100
+ {:postid=>String},
101
+ {:username=>String},
102
+ {:password=>String},
103
+ ]
104
+
105
+ export :getUsersBlogs, :returns => [[Blog::Blog]], :expects => [
106
+ {:appkey=>String},
107
+ {:username=>String},
108
+ {:password=>String},
109
+ ]
110
+
111
+ export :getRecentPosts, :returns => [[Blog::Post]], :expects => [
112
+ {:blogid=>String},
113
+ {:username=>String},
114
+ {:password=>String},
115
+ {:numberOfPosts=>Integer},
116
+ ]
117
+ end