actionservice 0.2.100 → 0.2.102
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.
- 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
|