RocketAMF 0.0.5
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/README.rdoc +36 -0
- data/Rakefile +54 -0
- data/lib/rocketamf.rb +12 -0
- data/lib/rocketamf/class_mapping.rb +211 -0
- data/lib/rocketamf/common.rb +35 -0
- data/lib/rocketamf/constants.rb +47 -0
- data/lib/rocketamf/pure.rb +30 -0
- data/lib/rocketamf/pure/deserializer.rb +361 -0
- data/lib/rocketamf/pure/io_helpers.rb +94 -0
- data/lib/rocketamf/pure/remoting.rb +89 -0
- data/lib/rocketamf/pure/serializer.rb +327 -0
- data/lib/rocketamf/remoting.rb +133 -0
- data/lib/rocketamf/values/array_collection.rb +9 -0
- data/lib/rocketamf/values/messages.rb +133 -0
- data/lib/rocketamf/values/typed_hash.rb +13 -0
- data/lib/rocketamf/version.rb +9 -0
- data/spec/amf/class_mapping_set_spec.rb +34 -0
- data/spec/amf/class_mapping_spec.rb +120 -0
- data/spec/amf/deserializer_spec.rb +311 -0
- data/spec/amf/request_spec.rb +25 -0
- data/spec/amf/response_spec.rb +68 -0
- data/spec/amf/serializer_spec.rb +328 -0
- data/spec/amf/values/array_collection_spec.rb +6 -0
- data/spec/amf/values/messages_spec.rb +31 -0
- data/spec/fixtures/objects/amf0-boolean.bin +1 -0
- data/spec/fixtures/objects/amf0-date.bin +0 -0
- data/spec/fixtures/objects/amf0-ecma-ordinal-array.bin +0 -0
- data/spec/fixtures/objects/amf0-hash.bin +0 -0
- data/spec/fixtures/objects/amf0-null.bin +1 -0
- data/spec/fixtures/objects/amf0-number.bin +0 -0
- data/spec/fixtures/objects/amf0-object.bin +0 -0
- data/spec/fixtures/objects/amf0-ref-test.bin +0 -0
- data/spec/fixtures/objects/amf0-strict-array.bin +0 -0
- data/spec/fixtures/objects/amf0-string.bin +0 -0
- data/spec/fixtures/objects/amf0-typed-object.bin +0 -0
- data/spec/fixtures/objects/amf0-undefined.bin +1 -0
- data/spec/fixtures/objects/amf0-untyped-object.bin +0 -0
- data/spec/fixtures/objects/amf3-0.bin +0 -0
- data/spec/fixtures/objects/amf3-arrayRef.bin +1 -0
- data/spec/fixtures/objects/amf3-bigNum.bin +0 -0
- data/spec/fixtures/objects/amf3-date.bin +0 -0
- data/spec/fixtures/objects/amf3-datesRef.bin +0 -0
- data/spec/fixtures/objects/amf3-dynObject.bin +2 -0
- data/spec/fixtures/objects/amf3-emptyArray.bin +1 -0
- data/spec/fixtures/objects/amf3-emptyArrayRef.bin +1 -0
- data/spec/fixtures/objects/amf3-emptyStringRef.bin +1 -0
- data/spec/fixtures/objects/amf3-false.bin +1 -0
- data/spec/fixtures/objects/amf3-graphMember.bin +0 -0
- data/spec/fixtures/objects/amf3-hash.bin +2 -0
- data/spec/fixtures/objects/amf3-largeMax.bin +0 -0
- data/spec/fixtures/objects/amf3-largeMin.bin +0 -0
- data/spec/fixtures/objects/amf3-max.bin +1 -0
- data/spec/fixtures/objects/amf3-min.bin +0 -0
- data/spec/fixtures/objects/amf3-mixedArray.bin +11 -0
- data/spec/fixtures/objects/amf3-null.bin +1 -0
- data/spec/fixtures/objects/amf3-objRef.bin +0 -0
- data/spec/fixtures/objects/amf3-primArray.bin +1 -0
- data/spec/fixtures/objects/amf3-string.bin +1 -0
- data/spec/fixtures/objects/amf3-stringRef.bin +0 -0
- data/spec/fixtures/objects/amf3-symbol.bin +1 -0
- data/spec/fixtures/objects/amf3-true.bin +1 -0
- data/spec/fixtures/objects/amf3-typedObject.bin +2 -0
- data/spec/fixtures/request/acknowledge-response.bin +0 -0
- data/spec/fixtures/request/amf0-error-response.bin +0 -0
- data/spec/fixtures/request/commandMessage.bin +0 -0
- data/spec/fixtures/request/remotingMessage.bin +0 -0
- data/spec/fixtures/request/simple-response.bin +0 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +32 -0
- metadata +133 -0
@@ -0,0 +1,133 @@
|
|
1
|
+
module RocketAMF
|
2
|
+
# Container for the AMF request.
|
3
|
+
class Request
|
4
|
+
attr_reader :amf_version, :headers, :messages
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@amf_version = 0
|
8
|
+
@headers = []
|
9
|
+
@messages = []
|
10
|
+
end
|
11
|
+
|
12
|
+
# Populates the request from the given stream or string. Returns self for easy
|
13
|
+
# chaining
|
14
|
+
#
|
15
|
+
# Example:
|
16
|
+
#
|
17
|
+
# req = RocketAMF::Request.new.populate_from_stream(env['rack.input'].read)
|
18
|
+
#--
|
19
|
+
# Implemented in pure/remoting.rb RocketAMF::Pure::Request
|
20
|
+
def populate_from_stream stream
|
21
|
+
raise AMFError, 'Must load "rocketamf/pure"'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Container for the response of the AMF call. Includes serialization and request
|
26
|
+
# handling code.
|
27
|
+
class Response
|
28
|
+
attr_accessor :amf_version, :headers, :messages
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
@amf_version = 0
|
32
|
+
@headers = []
|
33
|
+
@messages = []
|
34
|
+
end
|
35
|
+
|
36
|
+
# Serializes the response to a string and returns it.
|
37
|
+
#--
|
38
|
+
# Implemented in pure/remoting.rb RocketAMF::Pure::Response
|
39
|
+
def serialize
|
40
|
+
raise AMFError, 'Must load "rocketamf/pure"'
|
41
|
+
end
|
42
|
+
|
43
|
+
# Builds response from the request, iterating over each method call and using
|
44
|
+
# the return value as the method call's return value
|
45
|
+
#--
|
46
|
+
# Iterate over all the sent messages. If they're somthing we can handle, like
|
47
|
+
# a command message, then simply add the response message ourselves. If it's
|
48
|
+
# a method call, then call the block with the method and args, catching errors
|
49
|
+
# for handling. Then create the appropriate response message using the return
|
50
|
+
# value of the block as the return value for the method call.
|
51
|
+
def each_method_call request, &block
|
52
|
+
raise 'Response already constructed' if @constructed
|
53
|
+
|
54
|
+
# Set version from response
|
55
|
+
# Can't just copy version because FMS sends version as 1
|
56
|
+
@amf_version = request.amf_version == 3 ? 3 : 0
|
57
|
+
|
58
|
+
request.messages.each do |m|
|
59
|
+
# What's the request body?
|
60
|
+
case m.data
|
61
|
+
when Values::CommandMessage
|
62
|
+
# Pings should be responded to with an AcknowledgeMessage built using the ping
|
63
|
+
# Everything else is unsupported
|
64
|
+
command_msg = m.data
|
65
|
+
if command_msg.operation == Values::CommandMessage::CLIENT_PING_OPERATION
|
66
|
+
response_value = Values::AcknowledgeMessage.new(command_msg)
|
67
|
+
else
|
68
|
+
response_value = Values::ErrorMessage.new(Exception.new("CommandMessage #{command_msg.operation} not implemented"), command_msg)
|
69
|
+
end
|
70
|
+
when Values::RemotingMessage
|
71
|
+
# Using RemoteObject style message calls
|
72
|
+
remoting_msg = m.data
|
73
|
+
acknowledge_msg = Values::AcknowledgeMessage.new(remoting_msg)
|
74
|
+
body = dispatch_call :method => remoting_msg.source+'.'+remoting_msg.operation, :args => remoting_msg.body, :source => remoting_msg, :block => block
|
75
|
+
|
76
|
+
# Response should be the bare ErrorMessage if there was an error
|
77
|
+
if body.is_a?(Values::ErrorMessage)
|
78
|
+
response_value = body
|
79
|
+
else
|
80
|
+
acknowledge_msg.body = body
|
81
|
+
response_value = acknowledge_msg
|
82
|
+
end
|
83
|
+
else
|
84
|
+
# Standard response message
|
85
|
+
response_value = dispatch_call :method => m.target_uri, :args => m.data, :source => m, :block => block
|
86
|
+
end
|
87
|
+
|
88
|
+
target_uri = m.response_uri
|
89
|
+
target_uri += response_value.is_a?(Values::ErrorMessage) ? '/onStatus' : '/onResult'
|
90
|
+
@messages << ::RocketAMF::Message.new(target_uri, '', response_value)
|
91
|
+
end
|
92
|
+
|
93
|
+
@constructed = true
|
94
|
+
end
|
95
|
+
|
96
|
+
# Return the serialized response as a string
|
97
|
+
def to_s
|
98
|
+
serialize
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
def dispatch_call p
|
103
|
+
begin
|
104
|
+
p[:block].call(p[:method], p[:args])
|
105
|
+
rescue Exception => e
|
106
|
+
# Create ErrorMessage object using the source message as the base
|
107
|
+
Values::ErrorMessage.new(p[:source], e)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# RocketAMF::Request or RocketAMF::Response header
|
113
|
+
class Header
|
114
|
+
attr_accessor :name, :must_understand, :data
|
115
|
+
|
116
|
+
def initialize name, must_understand, data
|
117
|
+
@name = name
|
118
|
+
@must_understand = must_understand
|
119
|
+
@data = data
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# RocketAMF::Request or RocketAMF::Response message
|
124
|
+
class Message
|
125
|
+
attr_accessor :target_uri, :response_uri, :data
|
126
|
+
|
127
|
+
def initialize target_uri, response_uri, data
|
128
|
+
@target_uri = target_uri
|
129
|
+
@response_uri = response_uri
|
130
|
+
@data = data
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module RocketAMF
|
2
|
+
module Values #:nodoc:
|
3
|
+
# Base class for all special AS3 response messages. Maps to
|
4
|
+
# <tt>flex.messaging.messages.AbstractMessage</tt>
|
5
|
+
class AbstractMessage
|
6
|
+
attr_accessor :clientId
|
7
|
+
attr_accessor :destination
|
8
|
+
attr_accessor :messageId
|
9
|
+
attr_accessor :timestamp
|
10
|
+
attr_accessor :timeToLive
|
11
|
+
attr_accessor :headers
|
12
|
+
attr_accessor :body
|
13
|
+
|
14
|
+
protected
|
15
|
+
def rand_uuid
|
16
|
+
[8,4,4,4,12].map {|n| rand_hex_3(n)}.join('-').to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
def rand_hex_3(l)
|
20
|
+
"%0#{l}x" % rand(1 << l*4)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Maps to <tt>flex.messaging.messages.RemotingMessage</tt>
|
25
|
+
class RemotingMessage < AbstractMessage
|
26
|
+
# The name of the service to be called including package name
|
27
|
+
attr_accessor :source
|
28
|
+
|
29
|
+
# The name of the method to be called
|
30
|
+
attr_accessor :operation
|
31
|
+
|
32
|
+
# The arguments to call the method with
|
33
|
+
attr_accessor :parameters
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@clientId = rand_uuid
|
37
|
+
@destination = nil
|
38
|
+
@messageId = rand_uuid
|
39
|
+
@timestamp = Time.new.to_i*100
|
40
|
+
@timeToLive = 0
|
41
|
+
@headers = {}
|
42
|
+
@body = nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Maps to <tt>flex.messaging.messages.AsyncMessage</tt>
|
47
|
+
class AsyncMessage < AbstractMessage
|
48
|
+
attr_accessor :correlationId
|
49
|
+
end
|
50
|
+
|
51
|
+
# Maps to <tt>flex.messaging.messages.CommandMessage</tt>
|
52
|
+
class CommandMessage < AsyncMessage
|
53
|
+
SUBSCRIBE_OPERATION = 0
|
54
|
+
UNSUSBSCRIBE_OPERATION = 1
|
55
|
+
POLL_OPERATION = 2
|
56
|
+
CLIENT_SYNC_OPERATION = 4
|
57
|
+
CLIENT_PING_OPERATION = 5
|
58
|
+
CLUSTER_REQUEST_OPERATION = 7
|
59
|
+
LOGIN_OPERATION = 8
|
60
|
+
LOGOUT_OPERATION = 9
|
61
|
+
SESSION_INVALIDATE_OPERATION = 10
|
62
|
+
MULTI_SUBSCRIBE_OPERATION = 11
|
63
|
+
DISCONNECT_OPERATION = 12
|
64
|
+
UNKNOWN_OPERATION = 10000
|
65
|
+
|
66
|
+
attr_accessor :operation
|
67
|
+
|
68
|
+
def initialize
|
69
|
+
@operation = UNKNOWN_OPERATION
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Maps to <tt>flex.messaging.messages.AcknowledgeMessage</tt>
|
74
|
+
class AcknowledgeMessage < AsyncMessage
|
75
|
+
def initialize message=nil
|
76
|
+
@clientId = rand_uuid
|
77
|
+
@destination = nil
|
78
|
+
@messageId = rand_uuid
|
79
|
+
@timestamp = Time.new.to_i*100
|
80
|
+
@timeToLive = 0
|
81
|
+
@headers = {}
|
82
|
+
@body = nil
|
83
|
+
|
84
|
+
if message.is_a?(AbstractMessage)
|
85
|
+
@correlationId = message.messageId
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Maps to <tt>flex.messaging.messages.ErrorMessage</tt> in AMF3 mode
|
91
|
+
class ErrorMessage < AcknowledgeMessage
|
92
|
+
# Extended data that will facilitate custom error processing on the client
|
93
|
+
attr_accessor :extendedData
|
94
|
+
|
95
|
+
# The fault code for the error, which defaults to the class name of the
|
96
|
+
# causing exception
|
97
|
+
attr_accessor :faultCode
|
98
|
+
|
99
|
+
# Detailed description of what caused the error
|
100
|
+
attr_accessor :faultDetail
|
101
|
+
|
102
|
+
# A simple description of the error
|
103
|
+
attr_accessor :faultString
|
104
|
+
|
105
|
+
# Optional "root cause" of the error
|
106
|
+
attr_accessor :rootCause
|
107
|
+
|
108
|
+
def initialize message, exception
|
109
|
+
super message
|
110
|
+
|
111
|
+
@e = exception
|
112
|
+
@faultCode = @e.class.name
|
113
|
+
@faultDetail = @e.backtrace.join("\n")
|
114
|
+
@faultString = @e.message
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_amf serializer
|
118
|
+
stream = ""
|
119
|
+
if serializer.version == 0
|
120
|
+
data = {
|
121
|
+
:faultCode => @faultCode,
|
122
|
+
:faultDetail => @faultDetail,
|
123
|
+
:faultString => @faultString
|
124
|
+
}
|
125
|
+
serializer.write_hash(data, stream)
|
126
|
+
else
|
127
|
+
serializer.write_object(self, stream)
|
128
|
+
end
|
129
|
+
stream
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module RocketAMF
|
2
|
+
module Values #:nodoc:
|
3
|
+
# Hash-like object that can store a type string. Used to preserve type information
|
4
|
+
# for unmapped objects after deserialization.
|
5
|
+
class TypedHash < Hash
|
6
|
+
attr_reader :type
|
7
|
+
|
8
|
+
def initialize type
|
9
|
+
@type = type
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module RocketAMF
|
2
|
+
# AMF version
|
3
|
+
VERSION = '0.0.4'
|
4
|
+
VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc:
|
5
|
+
VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
|
6
|
+
VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
|
7
|
+
VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
|
8
|
+
VARIANT_BINARY = false
|
9
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
|
+
|
3
|
+
describe RocketAMF::ClassMapping::MappingSet do
|
4
|
+
before :each do
|
5
|
+
@config = RocketAMF::ClassMapping::MappingSet.new
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should retrieve AS mapping for ruby class" do
|
9
|
+
@config.map :as => 'ASTest', :ruby => 'RubyTest'
|
10
|
+
@config.get_as_class_name('RubyTest').should == 'ASTest'
|
11
|
+
@config.get_as_class_name('BadClass').should be_nil
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should retrive ruby class name mapping for AS class" do
|
15
|
+
@config.map :as => 'ASTest', :ruby => 'RubyTest'
|
16
|
+
@config.get_ruby_class_name('ASTest').should == 'RubyTest'
|
17
|
+
@config.get_ruby_class_name('BadClass').should be_nil
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should map special classes by default" do
|
21
|
+
SPECIAL_CLASSES = [
|
22
|
+
'flex.messaging.messages.AcknowledgeMessage',
|
23
|
+
'flex.messaging.messages.ErrorMessage',
|
24
|
+
'flex.messaging.messages.CommandMessage',
|
25
|
+
'flex.messaging.messages.ErrorMessage',
|
26
|
+
'flex.messaging.messages.RemotingMessage',
|
27
|
+
'flex.messaging.io.ArrayCollection'
|
28
|
+
]
|
29
|
+
|
30
|
+
SPECIAL_CLASSES.each do |as_class|
|
31
|
+
@config.get_ruby_class_name(as_class).should_not be_nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
|
+
|
3
|
+
describe RocketAMF::ClassMapping do
|
4
|
+
before(:all) do
|
5
|
+
class ClassMappingTest
|
6
|
+
attr_accessor :prop_a
|
7
|
+
attr_accessor :prop_b
|
8
|
+
attr_accessor :prop_c
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
before :each do
|
13
|
+
@mapper = RocketAMF::ClassMapping.new
|
14
|
+
@mapper.define do |m|
|
15
|
+
m.map :as => 'ASClass', :ruby => 'ClassMappingTest'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return AS class name for ruby objects" do
|
20
|
+
@mapper.get_as_class_name(ClassMappingTest.new).should == 'ASClass'
|
21
|
+
@mapper.get_as_class_name('ClassMappingTest').should == 'ASClass'
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should allow config modification" do
|
25
|
+
@mapper.define do |m|
|
26
|
+
m.map :as => 'SecondClass', :ruby => 'ClassMappingTest'
|
27
|
+
end
|
28
|
+
@mapper.get_as_class_name(ClassMappingTest.new).should == 'SecondClass'
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "ruby object generator" do
|
32
|
+
it "should instantiate a ruby class" do
|
33
|
+
@mapper.get_ruby_obj('ASClass').should be_a(ClassMappingTest)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should properly instantiate namespaced classes" do
|
37
|
+
module ANamespace; class TestRubyClass; end; end
|
38
|
+
@mapper.define {|m| m.map :as => 'ASClass', :ruby => 'ANamespace::TestRubyClass'}
|
39
|
+
@mapper.get_ruby_obj('ASClass').should be_a(ANamespace::TestRubyClass)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should return a hash with original type if not mapped" do
|
43
|
+
obj = @mapper.get_ruby_obj('UnmappedClass')
|
44
|
+
obj.should be_a(RocketAMF::Values::TypedHash)
|
45
|
+
obj.type.should == 'UnmappedClass'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "ruby object populator" do
|
50
|
+
it "should populate a ruby class" do
|
51
|
+
obj = @mapper.populate_ruby_obj ClassMappingTest.new, {:prop_a => 'Data'}
|
52
|
+
obj.prop_a.should == 'Data'
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should populate a typed hash" do
|
56
|
+
obj = @mapper.populate_ruby_obj RocketAMF::Values::TypedHash.new('UnmappedClass'), {:prop_a => 'Data'}
|
57
|
+
obj[:prop_a].should == 'Data'
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should allow custom populators" do
|
61
|
+
class CustomPopulator
|
62
|
+
def can_handle? obj
|
63
|
+
true
|
64
|
+
end
|
65
|
+
def populate obj, props, dynamic_props
|
66
|
+
obj[:populated] = true
|
67
|
+
obj.merge! props
|
68
|
+
obj.merge! dynamic_props if dynamic_props
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
@mapper.object_populators << CustomPopulator.new
|
73
|
+
obj = @mapper.populate_ruby_obj({}, {:prop_a => 'Data'})
|
74
|
+
obj[:populated].should == true
|
75
|
+
obj[:prop_a].should == 'Data'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "property extractor" do
|
80
|
+
it "should extract hash properties" do
|
81
|
+
hash = {:a => 'test1', :b => 'test2'}
|
82
|
+
props = @mapper.props_for_serialization(hash)
|
83
|
+
props.should == {'a' => 'test1', 'b' => 'test2'}
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should extract object properties" do
|
87
|
+
obj = ClassMappingTest.new
|
88
|
+
obj.prop_a = 'Test A'
|
89
|
+
obj.prop_b = 'Test B'
|
90
|
+
|
91
|
+
hash = @mapper.props_for_serialization obj
|
92
|
+
hash.should == {'prop_a' => 'Test A', 'prop_b' => 'Test B', 'prop_c' => nil}
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should extract inherited object properties" do
|
96
|
+
class ClassMappingTest2 < ClassMappingTest
|
97
|
+
end
|
98
|
+
obj = ClassMappingTest2.new
|
99
|
+
obj.prop_a = 'Test A'
|
100
|
+
obj.prop_b = 'Test B'
|
101
|
+
|
102
|
+
hash = @mapper.props_for_serialization obj
|
103
|
+
hash.should == {'prop_a' => 'Test A', 'prop_b' => 'Test B', 'prop_c' => nil}
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should allow custom serializers" do
|
107
|
+
class CustomSerializer
|
108
|
+
def can_handle? obj
|
109
|
+
true
|
110
|
+
end
|
111
|
+
def serialize obj
|
112
|
+
{:success => true}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
@mapper.object_serializers << CustomSerializer.new
|
117
|
+
@mapper.props_for_serialization(nil).should == {:success => true}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|