actionservice 0.2.100 → 0.2.102
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +3 -0
- data/README +39 -12
- data/Rakefile +7 -4
- data/TODO +12 -29
- data/examples/soap/app/controllers/search_controller.rb +1 -2
- data/examples/soap/lib/google_search_service.rb +5 -2
- data/lib/action_service/base.rb +2 -57
- data/lib/action_service/container.rb +88 -36
- data/lib/action_service/exporting.rb +140 -0
- data/lib/action_service/invocation.rb +46 -16
- data/lib/action_service/protocol/abstract.rb +80 -50
- data/lib/action_service/protocol/registry.rb +3 -5
- data/lib/action_service/protocol/soap.rb +156 -80
- data/lib/action_service/protocol/xmlrpc.rb +77 -47
- data/lib/action_service/router/action_controller.rb +41 -29
- data/lib/action_service/router/wsdl.rb +149 -132
- data/lib/action_service.rb +5 -0
- data/test/abstract_soap.rb +47 -0
- data/test/base_test.rb +31 -29
- data/test/container_test.rb +53 -32
- data/test/invocation_test.rb +97 -86
- data/test/protocol_registry_test.rb +9 -10
- data/test/protocol_soap_test.rb +125 -159
- data/test/protocol_xmlrpc_test.rb +61 -58
- data/test/router_action_controller_test.rb +52 -83
- data/test/router_wsdl_test.rb +31 -25
- data/test/struct_test.rb +11 -9
- metadata +15 -3
- data/CHANGELOG +0 -3
data/ChangeLog
ADDED
data/README
CHANGED
@@ -1,29 +1,54 @@
|
|
1
|
-
= Action Service --
|
1
|
+
= Action Service -- Serving APIs on rails
|
2
|
+
|
3
|
+
Action Service provides a way to publish interoperablel web service APIs with
|
4
|
+
Rails without wasting time delving into protocol details.
|
5
|
+
|
6
|
+
|
7
|
+
== Features
|
8
|
+
|
9
|
+
* SOAP RPC protocol support
|
10
|
+
* Dynamic WSDL generation
|
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
|
16
|
+
|
17
|
+
|
18
|
+
== Integration with Action Pack
|
19
|
+
|
20
|
+
Action Service can be integrated in two different dispatching modes, _Direct_ and
|
21
|
+
_Delegated_.
|
22
|
+
|
23
|
+
|
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.
|
2
30
|
|
3
|
-
FIXME
|
4
31
|
|
5
32
|
== Dependencies
|
6
33
|
|
7
|
-
Action Service requires that the Action Pack
|
8
|
-
or
|
34
|
+
Action Service requires that the Action Pack and Active Record are either
|
35
|
+
available to be required immediately or are accessible as GEMs.
|
9
36
|
|
10
|
-
It also requires a version of Ruby that includes SOAP support in the standard
|
11
|
-
least version 1.8.2 final is recommended.
|
37
|
+
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.
|
12
39
|
|
13
40
|
|
14
41
|
== Download
|
15
42
|
|
16
|
-
|
17
|
-
|
43
|
+
The latest Action Service version can be downloaded from
|
44
|
+
http://rubyforge.org/projects/actionservice
|
18
45
|
|
19
46
|
|
20
47
|
== Installation
|
21
48
|
|
22
49
|
You can install Action Service with the following command.
|
23
50
|
|
24
|
-
% [sudo] ruby
|
25
|
-
|
26
|
-
from its distribution directory.
|
51
|
+
% [sudo] ruby setup.rb
|
27
52
|
|
28
53
|
|
29
54
|
== License
|
@@ -33,4 +58,6 @@ Action Service is released under the MIT license.
|
|
33
58
|
|
34
59
|
== Support
|
35
60
|
|
36
|
-
|
61
|
+
The Ruby on Rails mailing list
|
62
|
+
|
63
|
+
Or, to contact the author, send mail to bitserf@gmail.com
|
data/Rakefile
CHANGED
@@ -8,7 +8,7 @@ require 'rake/contrib/rubyforgepublisher'
|
|
8
8
|
|
9
9
|
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
10
10
|
PKG_NAME = 'actionservice'
|
11
|
-
PKG_VERSION = '0.2.
|
11
|
+
PKG_VERSION = '0.2.102' + PKG_BUILD
|
12
12
|
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
13
13
|
|
14
14
|
desc "Default Task"
|
@@ -27,12 +27,13 @@ Rake::TestTask.new { |t|
|
|
27
27
|
Rake::RDocTask.new { |rdoc|
|
28
28
|
rdoc.rdoc_dir = 'doc'
|
29
29
|
rdoc.title = "Action Service -- Web services for Action Pack"
|
30
|
-
rdoc.options << '--line-numbers --inline-source --main README'
|
31
|
-
rdoc.rdoc_files.include('README'
|
30
|
+
rdoc.options << '--line-numbers --inline-source --main README --accessor class_inheritable_option=RW'
|
31
|
+
rdoc.rdoc_files.include('README')
|
32
32
|
rdoc.rdoc_files.include('lib/action_service.rb')
|
33
33
|
rdoc.rdoc_files.include('lib/action_service/*.rb')
|
34
34
|
rdoc.rdoc_files.include('lib/action_service/protocol/*.rb')
|
35
35
|
rdoc.rdoc_files.include('lib/action_service/router/*.rb')
|
36
|
+
rdoc.rdoc_files.include('lib/action_service/support/*.rb')
|
36
37
|
}
|
37
38
|
|
38
39
|
|
@@ -50,13 +51,14 @@ spec = Gem::Specification.new do |s|
|
|
50
51
|
s.homepage = "http://rubyforge.org/projects/actionservice"
|
51
52
|
|
52
53
|
s.add_dependency('actionpack', '>= 1.4.0')
|
54
|
+
s.add_dependency('activerecord', '>= 1.6.0')
|
53
55
|
|
54
56
|
s.has_rdoc = true
|
55
57
|
s.requirements << 'none'
|
56
58
|
s.require_path = 'lib'
|
57
59
|
s.autorequire = 'action_service'
|
58
60
|
|
59
|
-
s.files = [ "Rakefile", "setup.rb", "README", "TODO", "HACKING", "
|
61
|
+
s.files = [ "Rakefile", "setup.rb", "README", "TODO", "HACKING", "ChangeLog", "MIT-LICENSE" ]
|
60
62
|
s.files = s.files + Dir.glob( "examples/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
61
63
|
s.files = s.files + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
62
64
|
s.files = s.files + Dir.glob( "test/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
@@ -96,6 +98,7 @@ task :lines do
|
|
96
98
|
action_service
|
97
99
|
action_service/protocol
|
98
100
|
action_service/router
|
101
|
+
action_service/support
|
99
102
|
}.each do |dir|
|
100
103
|
Dir.foreach(File.join(prefix, dir)) { |file_name|
|
101
104
|
next unless file_name =~ /.*rb$/
|
data/TODO
CHANGED
@@ -1,30 +1,13 @@
|
|
1
|
-
=
|
2
|
-
-
|
3
|
-
|
4
|
-
|
5
|
-
=
|
6
|
-
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
Possible approaches:
|
13
|
-
Create a new router that maps an 'api' action to do dispatching using
|
14
|
-
itself as the target service.
|
15
|
-
|
16
|
-
This dispatching would:
|
17
|
-
|
18
|
-
* Unpack request parameters into @params
|
19
|
-
* Execute the action
|
20
|
-
* Use the return value of the action to send back to the client
|
21
|
-
|
22
|
-
= Warts
|
23
|
-
- Come up with a cleaner way for supporting XML-RPC introspection that doesn't
|
24
|
-
involve throwing a SystemExportNotHandledError exception.
|
25
|
-
- Come up with a cleaner approach for 'default_export' implementations.
|
26
|
-
But NOT method_missing!
|
27
|
-
- Create abstract SOAP test that provides helpers for doing SOAP method
|
28
|
-
calls, get rid of the copy & paste SOAP crap in the SOAP protocol tests
|
1
|
+
= Tasks
|
2
|
+
- add better type mapping tests for XML-RPC
|
3
|
+
- add tests for ActiveRecord support (with mock objects?)
|
4
|
+
|
5
|
+
= Refactoring
|
6
|
+
- Find an alternative way to map interesting types for SOAP (like ActiveRecord
|
7
|
+
model classes) that doesn't require creation of a sanitized copy object with data
|
8
|
+
copied from the real one. Ideally this would let us get rid of
|
9
|
+
ActionService::Struct altogether and provide a block that would yield the
|
10
|
+
attributes and values. "Filters" ? Not sure how to integrate with SOAP though.
|
11
|
+
|
29
12
|
- Don't have clean way to go from SOAP Class object to the xsd:NAME type
|
30
|
-
string
|
13
|
+
string -- NaHi possibly looking at remedying this situation
|
@@ -68,7 +68,10 @@ class GoogleSearchService < ActionService::Base
|
|
68
68
|
|
69
69
|
# For Mono, we have to clone objects if they're referenced by more than one place, otherwise
|
70
70
|
# the Ruby SOAP collapses them into one instance and uses references all over the
|
71
|
-
# place, confusing Mono
|
71
|
+
# place, confusing Mono.
|
72
|
+
#
|
73
|
+
# This has recently been fixed:
|
74
|
+
# http://bugzilla.ximian.com/show_bug.cgi?id=72265
|
72
75
|
result.directoryCategories = [
|
73
76
|
category("Web Development", "UTF-8"),
|
74
77
|
category("Programming", "US-ASCII"),
|
@@ -84,7 +87,7 @@ class GoogleSearchService < ActionService::Base
|
|
84
87
|
cat
|
85
88
|
end
|
86
89
|
|
87
|
-
export :doGetCachedPage, :returns => [String], :expects => [String, String]
|
90
|
+
export :doGetCachedPage, :returns => [String], :expects => [{:key=>String}, {:url=>String}]
|
88
91
|
export :doGetSpellingSuggestion, :returns => [String], :expects => [String, String]
|
89
92
|
export :doGoogleSearch, :returns => [GoogleSearchResult], :expects => [String, String, Integer, Integer, TrueClass, String, TrueClass, String, String, String]
|
90
93
|
end
|
data/lib/action_service/base.rb
CHANGED
@@ -5,63 +5,8 @@ module ActionService
|
|
5
5
|
end
|
6
6
|
|
7
7
|
class Base
|
8
|
-
|
8
|
+
# Whether to send back a detailed stack trace to the remote caller
|
9
|
+
# when an exception is thrown on the server
|
9
10
|
class_inheritable_option :report_exceptions, true
|
10
|
-
class_inheritable_option :logger
|
11
|
-
class_inheritable_option :default_export
|
12
|
-
|
13
|
-
class << self
|
14
|
-
def export(name, options={})
|
15
|
-
validate_options([:expects, :returns, :expects_and_returns], options.keys)
|
16
|
-
if options[:expects_and_returns]
|
17
|
-
expects = options[:expects_and_returns]
|
18
|
-
returns = options[:expects_and_returns]
|
19
|
-
else
|
20
|
-
expects = options[:expects]
|
21
|
-
returns = options[:returns]
|
22
|
-
end
|
23
|
-
name = name.to_sym
|
24
|
-
public_name = public_export_name(name)
|
25
|
-
info = { :expects => expects, :returns => returns }
|
26
|
-
write_inheritable_hash("action_service_exports", name => info)
|
27
|
-
write_inheritable_hash("action_service_public_exports", public_name => name)
|
28
|
-
end
|
29
|
-
|
30
|
-
def has_export?(name)
|
31
|
-
exports.has_key?(name)
|
32
|
-
end
|
33
|
-
|
34
|
-
def has_public_export?(name)
|
35
|
-
public_exports.has_key?(name)
|
36
|
-
end
|
37
|
-
|
38
|
-
def public_export_name(export_name)
|
39
|
-
if export_name_mangling
|
40
|
-
Inflector.camelize(export_name.to_s)
|
41
|
-
else
|
42
|
-
export_name.to_s
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def internal_export_name(public_name)
|
47
|
-
public_exports[public_name]
|
48
|
-
end
|
49
|
-
|
50
|
-
def exports
|
51
|
-
read_inheritable_attribute("action_service_exports") || {}
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
def public_exports
|
56
|
-
read_inheritable_attribute("action_service_public_exports") || {}
|
57
|
-
end
|
58
|
-
|
59
|
-
def validate_options(valid_option_keys, supplied_option_keys)
|
60
|
-
unknown_option_keys = supplied_option_keys - valid_option_keys
|
61
|
-
unless unknown_option_keys.empty?
|
62
|
-
raise(ActionServiceError, "Unknown options: #{unknown_option_keys}")
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
11
|
end
|
67
12
|
end
|
@@ -1,10 +1,14 @@
|
|
1
1
|
module ActionService
|
2
2
|
module Container
|
3
|
+
DirectDispatching = :direct
|
4
|
+
DelegatedDispatching = :delegated
|
5
|
+
|
3
6
|
class ContainerError < ActionService::ActionServiceError
|
4
7
|
end
|
5
8
|
|
6
9
|
def self.append_features(base)
|
7
10
|
super
|
11
|
+
base.class_inheritable_option(:service_dispatching_mode, DirectDispatching)
|
8
12
|
base.extend(ClassMethods)
|
9
13
|
base.send(:include, ActionService::Container::InstanceMethods)
|
10
14
|
end
|
@@ -25,7 +29,7 @@ module ActionService
|
|
25
29
|
end
|
26
30
|
|
27
31
|
def add_service_definition_callback(&block)
|
28
|
-
write_inheritable_array("
|
32
|
+
write_inheritable_array("service_definition_callbacks", [block])
|
29
33
|
end
|
30
34
|
|
31
35
|
def has_service?(name)
|
@@ -37,9 +41,9 @@ module ActionService
|
|
37
41
|
end
|
38
42
|
|
39
43
|
private
|
40
|
-
def call_service_definition_callbacks(
|
41
|
-
(read_inheritable_attribute("
|
42
|
-
block.call(
|
44
|
+
def call_service_definition_callbacks(container_class, service_name, service_info)
|
45
|
+
(read_inheritable_attribute("service_definition_callbacks") || []).each do |block|
|
46
|
+
block.call(container_class, service_name, service_info)
|
43
47
|
end
|
44
48
|
end
|
45
49
|
end
|
@@ -55,56 +59,104 @@ module ActionService
|
|
55
59
|
end
|
56
60
|
|
57
61
|
private
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
62
|
+
def dispatch_service_request(protocol_request)
|
63
|
+
case service_dispatching_mode
|
64
|
+
when DirectDispatching
|
65
|
+
dispatch_direct_service_request(protocol_request)
|
66
|
+
when DelegatedDispatching
|
67
|
+
dispatch_delegated_service_request(protocol_request)
|
68
|
+
else
|
69
|
+
raise(ContainerError, "unsupported dispatching mode '#{service_dispatching_mode}'")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def dispatch_direct_service_request(protocol_request)
|
74
|
+
public_method_name = protocol_request.public_method_name
|
75
|
+
method_name = self.class.internal_export_name(public_method_name)
|
76
|
+
export_info = self.class.exports[method_name]
|
77
|
+
protocol_request.type = Protocol::CheckedMessage
|
78
|
+
protocol_request.signature = export_info[:expects]
|
79
|
+
protocol_request.return_signature = export_info[:returns]
|
80
|
+
@invocation_params = protocol_request.unmarshal
|
81
|
+
if export_info[:expects]
|
82
|
+
expects = export_info[:expects]
|
83
|
+
@params ||= {}
|
84
|
+
(1..@invocation_params.size).each do |i|
|
85
|
+
i -= 1
|
86
|
+
if expects[i].is_a?(Hash)
|
87
|
+
@params[expects[i].keys.shift.to_s] = @invocation_params[i]
|
88
|
+
else
|
89
|
+
@params["param#{i}"] = @invocation_params[i]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
result = send(method_name)
|
94
|
+
protocol_request.marshal(result)
|
95
|
+
end
|
96
|
+
|
97
|
+
def dispatch_delegated_service_request(protocol_request)
|
98
|
+
service_name = protocol_request.service_name
|
99
|
+
service = service_object(service_name)
|
100
|
+
service_class = service.class
|
101
|
+
public_method_name = protocol_request.public_method_name
|
102
|
+
method_name = service_class.internal_export_name(public_method_name)
|
103
|
+
|
104
|
+
invocation = ActionService::Invocation::InvocationRequest.new(
|
105
|
+
ActionService::Invocation::ConcreteInvocation,
|
106
|
+
public_method_name,
|
107
|
+
method_name)
|
108
|
+
|
64
109
|
if method_name
|
65
|
-
|
66
|
-
|
110
|
+
protocol_request.type = Protocol::CheckedMessage
|
111
|
+
export_info = service_class.exports[method_name]
|
112
|
+
protocol_request.signature = export_info[:expects]
|
113
|
+
protocol_request.return_signature = export_info[:returns]
|
114
|
+
invocation.params = protocol_request.unmarshal
|
67
115
|
else
|
116
|
+
protocol_request.type = Protocol::UncheckedMessage
|
117
|
+
invocation.type = ActionService::Invocation::VirtualInvocation
|
68
118
|
system_exports = self.class.read_inheritable_attribute('default_system_exports') || {}
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
119
|
+
protocol = protocol_request.protocol
|
120
|
+
block = system_exports[protocol.class]
|
121
|
+
if block
|
122
|
+
invocation.block = block
|
123
|
+
invocation.block_params << service_class
|
73
124
|
else
|
74
|
-
method_name =
|
75
|
-
method_name
|
76
|
-
|
77
|
-
|
125
|
+
method_name = service_class.default_export
|
126
|
+
if method_name && service.respond_to?(method_name)
|
127
|
+
invocation.params = protocol_request.unmarshal
|
128
|
+
invocation.method_name = method_name.to_sym
|
129
|
+
else
|
130
|
+
raise(ContainerError, "no such method /#{service_name}##{public_method_name}")
|
78
131
|
end
|
79
132
|
end
|
80
|
-
export_info = {}
|
81
|
-
strict = false
|
82
133
|
end
|
83
|
-
|
84
|
-
params = params + protocol.unmarshal_request(info, export_info, strict)
|
134
|
+
|
85
135
|
canceled_reason = nil
|
86
136
|
canceled_block = lambda{|r| canceled_reason = r}
|
87
137
|
perform_invoke = lambda do
|
88
|
-
service.perform_invocation(
|
138
|
+
service.perform_invocation(invocation, &canceled_block)
|
89
139
|
end
|
90
|
-
|
140
|
+
try_default = true
|
141
|
+
result = nil
|
142
|
+
catch(:try_default_export) do
|
91
143
|
result = perform_invoke.call
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
144
|
+
try_default = false
|
145
|
+
end
|
146
|
+
if try_default
|
147
|
+
method_name = service_class.default_export
|
96
148
|
if method_name
|
97
|
-
|
98
|
-
|
99
|
-
|
149
|
+
protocol_request.type = Protocol::UncheckedMessage
|
150
|
+
invocation.params = protocol_request.unmarshal
|
151
|
+
invocation.method_name = method_name.to_sym
|
152
|
+
invocation.type = ActionService::Invocation::UnexportedConcreteInvocation
|
100
153
|
else
|
101
|
-
raise(ContainerError, "no such method /#{
|
154
|
+
raise(ContainerError, "no such method /#{service_name}##{public_method_name}")
|
102
155
|
end
|
103
156
|
result = perform_invoke.call
|
104
157
|
end
|
105
|
-
|
158
|
+
protocol_request.marshal(result)
|
106
159
|
end
|
107
|
-
|
108
160
|
end
|
109
161
|
end
|
110
162
|
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module ActionService
|
2
|
+
module Exporting
|
3
|
+
class ExportError < ActionService::ActionServiceError
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.append_features(base)
|
7
|
+
super
|
8
|
+
# Whether to mangle method names into friendly camelized names
|
9
|
+
# when declaring them to remote callers
|
10
|
+
base.class_inheritable_option :export_name_mangling, true
|
11
|
+
|
12
|
+
# If present, the name of a method to call when the remote caller
|
13
|
+
# tried to call a nonexistent method. Semantically equivalent to
|
14
|
+
# +method_missing+.
|
15
|
+
base.class_inheritable_option :default_export
|
16
|
+
|
17
|
+
base.extend(ClassMethods)
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
# Declares the named method to be available for remote execution.
|
22
|
+
#
|
23
|
+
# Example:
|
24
|
+
#
|
25
|
+
# class CalculatorService < ActionService::Base
|
26
|
+
# def add(a, b)
|
27
|
+
# a + b
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# export :add, :expects => [Integer, Integer], :returns => [Integer]
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
#
|
34
|
+
# You will need to provide type annotation options if the method will be
|
35
|
+
# receiving arguments, or returning values that will be used by remote
|
36
|
+
# callers.
|
37
|
+
#
|
38
|
+
# A type annotation is an Array, containing as elements one or more of:
|
39
|
+
#
|
40
|
+
# * A Class object for the desired type.
|
41
|
+
# * An Array containing a Class object. Declares that the argument at that
|
42
|
+
# position is to be an Array containing values of type Class.
|
43
|
+
# * A Hash containing the argument name as the key, and one of the above as
|
44
|
+
# value.
|
45
|
+
#
|
46
|
+
# Valid annotations:
|
47
|
+
# [<tt>:expects</tt>] The arguments that will be received by the method
|
48
|
+
# [<tt>:returns</tt>] The type of the method return value. May contain only one element.
|
49
|
+
# [<tt>:expects_and_returns</tt>] Shortcut for specifying <tt>:expects</tt> and <tt>:returns</tt> with the same signature.
|
50
|
+
#
|
51
|
+
# The public name of the method (that is, the name that must be used by
|
52
|
+
# remote callers) will be a _camelized_ version of +name+. Camelization is
|
53
|
+
# performed using Rails inflector camelization rules.
|
54
|
+
#
|
55
|
+
# Method name camelization can be turned off for a class by using the
|
56
|
+
# +export_name_mangling+ class option:
|
57
|
+
#
|
58
|
+
# class MyService < ActionService::Base
|
59
|
+
# export_name_mangling false
|
60
|
+
# end
|
61
|
+
def export(name, options={})
|
62
|
+
validate_options([:expects, :returns, :expects_and_returns], options.keys)
|
63
|
+
if options[:expects_and_returns]
|
64
|
+
expects = options[:expects_and_returns]
|
65
|
+
returns = options[:expects_and_returns]
|
66
|
+
else
|
67
|
+
expects = options[:expects]
|
68
|
+
returns = options[:returns]
|
69
|
+
end
|
70
|
+
if expects
|
71
|
+
expects.each do |klass|
|
72
|
+
klass = klass.values.shift if klass.is_a?(Hash)
|
73
|
+
klass = klass[0] if klass.is_a?(Array)
|
74
|
+
if klass.ancestors.include?(ActiveRecord::Base)
|
75
|
+
raise(ActionServiceError, "ActiveRecord model classes not allowed in :expects")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
name = name.to_sym
|
80
|
+
public_name = public_export_name(name)
|
81
|
+
info = { :expects => expects, :returns => returns }
|
82
|
+
write_inheritable_hash("action_service_exports", name => info)
|
83
|
+
write_inheritable_hash("action_service_public_exports", public_name => name)
|
84
|
+
call_export_definition_callbacks(self, name, info)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Whether the given name is exported by this service
|
88
|
+
def has_export?(name)
|
89
|
+
exports.has_key?(name)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Whether the given public method name (mangled) is exported by
|
93
|
+
# this service
|
94
|
+
def has_public_export?(name)
|
95
|
+
public_exports.has_key?(name)
|
96
|
+
end
|
97
|
+
|
98
|
+
# The mangled public method name for the given method name
|
99
|
+
def public_export_name(export_name)
|
100
|
+
if export_name_mangling
|
101
|
+
Inflector.camelize(export_name.to_s)
|
102
|
+
else
|
103
|
+
export_name.to_s
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# The internal method name for the given public method name
|
108
|
+
def internal_export_name(public_name)
|
109
|
+
public_exports[public_name]
|
110
|
+
end
|
111
|
+
|
112
|
+
# A Hash of method name to export information
|
113
|
+
def exports
|
114
|
+
read_inheritable_attribute("action_service_exports") || {}
|
115
|
+
end
|
116
|
+
|
117
|
+
def add_export_definition_callback(&block) # :nodoc:
|
118
|
+
write_inheritable_array("export_definition_callbacks", [block])
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
def call_export_definition_callbacks(service_class, export_name, export_info)
|
123
|
+
(read_inheritable_attribute("export_definition_callbacks") || []).each do |block|
|
124
|
+
block.call(service_class, export_name, export_info)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def public_exports
|
129
|
+
read_inheritable_attribute("action_service_public_exports") || {}
|
130
|
+
end
|
131
|
+
|
132
|
+
def validate_options(valid_option_keys, supplied_option_keys)
|
133
|
+
unknown_option_keys = supplied_option_keys - valid_option_keys
|
134
|
+
unless unknown_option_keys.empty?
|
135
|
+
raise(ActionServiceError, "Unknown options: #{unknown_option_keys}")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -1,5 +1,9 @@
|
|
1
1
|
module ActionService
|
2
2
|
module Invocation
|
3
|
+
ConcreteInvocation = :concrete
|
4
|
+
VirtualInvocation = :virtual
|
5
|
+
UnexportedConcreteInvocation = :unexported_concrete
|
6
|
+
|
3
7
|
class InvocationError < ActionService::ActionServiceError
|
4
8
|
end
|
5
9
|
|
@@ -95,29 +99,29 @@ module ActionService
|
|
95
99
|
end
|
96
100
|
end
|
97
101
|
|
98
|
-
def perform_invocation_with_interception(
|
99
|
-
return if before_invocation(
|
100
|
-
result = perform_invocation_without_interception(
|
101
|
-
after_invocation(
|
102
|
+
def perform_invocation_with_interception(invocation, &block)
|
103
|
+
return if before_invocation(invocation.method_name, invocation.params, &block) == false
|
104
|
+
result = perform_invocation_without_interception(invocation)
|
105
|
+
after_invocation(invocation.method_name, invocation.params, result)
|
102
106
|
result
|
103
107
|
end
|
104
108
|
|
105
|
-
def perform_invocation(
|
106
|
-
|
107
|
-
unless self.respond_to?(
|
108
|
-
raise InvocationError, "no such exported method '#{
|
109
|
+
def perform_invocation(invocation)
|
110
|
+
if invocation.concrete?
|
111
|
+
unless self.respond_to?(invocation.method_name) && self.class.has_export?(invocation.method_name)
|
112
|
+
raise InvocationError, "no such exported method '#{invocation.method_name}'"
|
109
113
|
end
|
110
114
|
end
|
111
|
-
|
112
|
-
if
|
113
|
-
|
114
|
-
|
115
|
-
|
115
|
+
params = invocation.params
|
116
|
+
if invocation.concrete? || invocation.unexported_concrete?
|
117
|
+
self.send(invocation.method_name, *params)
|
118
|
+
else
|
119
|
+
if invocation.block
|
120
|
+
params = invocation.block_params + params
|
121
|
+
invocation.block.call(invocation.public_method_name, *params)
|
116
122
|
else
|
117
|
-
self.send(
|
123
|
+
self.send(invocation.method_name, *params)
|
118
124
|
end
|
119
|
-
else
|
120
|
-
self.send(name, *args)
|
121
125
|
end
|
122
126
|
end
|
123
127
|
|
@@ -177,7 +181,33 @@ module ActionService
|
|
177
181
|
self.class.excluded_intercepted_methods[interceptor].include?(method_name)
|
178
182
|
end
|
179
183
|
end
|
184
|
+
end
|
185
|
+
|
186
|
+
class InvocationRequest
|
187
|
+
attr_accessor :type
|
188
|
+
attr :public_method_name
|
189
|
+
attr_accessor :method_name
|
190
|
+
attr_accessor :params
|
191
|
+
attr_accessor :block
|
192
|
+
attr :block_params
|
193
|
+
|
194
|
+
def initialize(type, public_method_name, method_name, params=nil)
|
195
|
+
@type = type
|
196
|
+
@public_method_name = public_method_name
|
197
|
+
@method_name = method_name
|
198
|
+
@params = params || []
|
199
|
+
@block = nil
|
200
|
+
@block_params = []
|
201
|
+
end
|
202
|
+
|
203
|
+
def concrete?
|
204
|
+
@type == ConcreteInvocation ? true : false
|
205
|
+
end
|
180
206
|
|
207
|
+
def unexported_concrete?
|
208
|
+
@type == UnexportedConcreteInvocation ? true : false
|
209
|
+
end
|
181
210
|
end
|
211
|
+
|
182
212
|
end
|
183
213
|
end
|