scotttam-RocketAMF 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/README.rdoc +45 -0
  2. data/Rakefile +54 -0
  3. data/lib/rocketamf.rb +128 -0
  4. data/lib/rocketamf/class_mapping.rb +231 -0
  5. data/lib/rocketamf/constants.rb +46 -0
  6. data/lib/rocketamf/pure.rb +28 -0
  7. data/lib/rocketamf/pure/deserializer.rb +419 -0
  8. data/lib/rocketamf/pure/io_helpers.rb +94 -0
  9. data/lib/rocketamf/pure/remoting.rb +134 -0
  10. data/lib/rocketamf/pure/serializer.rb +433 -0
  11. data/lib/rocketamf/remoting.rb +144 -0
  12. data/lib/rocketamf/values/array_collection.rb +13 -0
  13. data/lib/rocketamf/values/messages.rb +133 -0
  14. data/lib/rocketamf/values/typed_hash.rb +13 -0
  15. data/spec/amf/class_mapping_spec.rb +150 -0
  16. data/spec/amf/deserializer_spec.rb +367 -0
  17. data/spec/amf/remoting_spec.rb +132 -0
  18. data/spec/amf/serializer_spec.rb +384 -0
  19. data/spec/amf/values/array_collection_spec.rb +19 -0
  20. data/spec/amf/values/messages_spec.rb +31 -0
  21. data/spec/fixtures/objects/amf0-boolean.bin +1 -0
  22. data/spec/fixtures/objects/amf0-complexEncodedStringArray.bin +0 -0
  23. data/spec/fixtures/objects/amf0-date.bin +0 -0
  24. data/spec/fixtures/objects/amf0-ecma-ordinal-array.bin +0 -0
  25. data/spec/fixtures/objects/amf0-hash.bin +0 -0
  26. data/spec/fixtures/objects/amf0-null.bin +1 -0
  27. data/spec/fixtures/objects/amf0-number.bin +0 -0
  28. data/spec/fixtures/objects/amf0-object.bin +0 -0
  29. data/spec/fixtures/objects/amf0-ref-test.bin +0 -0
  30. data/spec/fixtures/objects/amf0-strict-array.bin +0 -0
  31. data/spec/fixtures/objects/amf0-string.bin +0 -0
  32. data/spec/fixtures/objects/amf0-typed-object.bin +0 -0
  33. data/spec/fixtures/objects/amf0-undefined.bin +1 -0
  34. data/spec/fixtures/objects/amf0-untyped-object.bin +0 -0
  35. data/spec/fixtures/objects/amf0-xmlDoc.bin +0 -0
  36. data/spec/fixtures/objects/amf3-0.bin +0 -0
  37. data/spec/fixtures/objects/amf3-arrayCollection.bin +2 -0
  38. data/spec/fixtures/objects/amf3-arrayRef.bin +1 -0
  39. data/spec/fixtures/objects/amf3-bigNum.bin +0 -0
  40. data/spec/fixtures/objects/amf3-byteArray.bin +0 -0
  41. data/spec/fixtures/objects/amf3-byteArrayRef.bin +1 -0
  42. data/spec/fixtures/objects/amf3-complexEncodedStringArray.bin +1 -0
  43. data/spec/fixtures/objects/amf3-date.bin +0 -0
  44. data/spec/fixtures/objects/amf3-datesRef.bin +0 -0
  45. data/spec/fixtures/objects/amf3-dictionary.bin +0 -0
  46. data/spec/fixtures/objects/amf3-dynObject.bin +2 -0
  47. data/spec/fixtures/objects/amf3-emptyArray.bin +1 -0
  48. data/spec/fixtures/objects/amf3-emptyArrayRef.bin +1 -0
  49. data/spec/fixtures/objects/amf3-emptyDictionary.bin +0 -0
  50. data/spec/fixtures/objects/amf3-emptyStringRef.bin +1 -0
  51. data/spec/fixtures/objects/amf3-encodedStringRef.bin +0 -0
  52. data/spec/fixtures/objects/amf3-false.bin +1 -0
  53. data/spec/fixtures/objects/amf3-float.bin +0 -0
  54. data/spec/fixtures/objects/amf3-graphMember.bin +0 -0
  55. data/spec/fixtures/objects/amf3-hash.bin +2 -0
  56. data/spec/fixtures/objects/amf3-largeMax.bin +0 -0
  57. data/spec/fixtures/objects/amf3-largeMin.bin +0 -0
  58. data/spec/fixtures/objects/amf3-max.bin +1 -0
  59. data/spec/fixtures/objects/amf3-min.bin +0 -0
  60. data/spec/fixtures/objects/amf3-mixedArray.bin +11 -0
  61. data/spec/fixtures/objects/amf3-null.bin +1 -0
  62. data/spec/fixtures/objects/amf3-objRef.bin +0 -0
  63. data/spec/fixtures/objects/amf3-primArray.bin +1 -0
  64. data/spec/fixtures/objects/amf3-string.bin +1 -0
  65. data/spec/fixtures/objects/amf3-stringRef.bin +0 -0
  66. data/spec/fixtures/objects/amf3-symbol.bin +1 -0
  67. data/spec/fixtures/objects/amf3-traitRef.bin +3 -0
  68. data/spec/fixtures/objects/amf3-true.bin +1 -0
  69. data/spec/fixtures/objects/amf3-typedObject.bin +2 -0
  70. data/spec/fixtures/objects/amf3-xml.bin +1 -0
  71. data/spec/fixtures/objects/amf3-xmlDoc.bin +1 -0
  72. data/spec/fixtures/objects/amf3-xmlRef.bin +1 -0
  73. data/spec/fixtures/request/acknowledge-response.bin +0 -0
  74. data/spec/fixtures/request/amf0-error-response.bin +0 -0
  75. data/spec/fixtures/request/commandMessage.bin +0 -0
  76. data/spec/fixtures/request/remotingMessage.bin +0 -0
  77. data/spec/fixtures/request/simple-response.bin +0 -0
  78. data/spec/fixtures/request/unsupportedCommandMessage.bin +0 -0
  79. data/spec/spec.opts +1 -0
  80. data/spec/spec_helper.rb +31 -0
  81. metadata +153 -0
@@ -0,0 +1,45 @@
1
+ == DESCRIPTION:
2
+
3
+ RocketAMF is a full featured AMF0/3 serializer and deserializer with support for
4
+ Flash -> Ruby and Ruby -> Flash class mapping, custom serializers, remoting
5
+ gateway helpers that follow AMF0/3 messaging specs, and a suite of specs to
6
+ ensure adherence to the specification documents put out by Adobe. In addition,
7
+ it now fully supports Ruby 1.9.
8
+
9
+ == INSTALL:
10
+
11
+ gem install RocketAMF --source="http://gemcutter.org"
12
+
13
+ == SIMPLE EXAMPLE:
14
+
15
+ require 'rocketamf'
16
+
17
+ hash = {:apple => "Apfel", :red => "Rot", :eyes => "Augen"}
18
+ File.open("amf.dat", 'w') do |f|
19
+ f.write RocketAMF.serialize(hash, 3) # Use AMF3 encoding because it's smaller
20
+ end
21
+
22
+ == LICENSE:
23
+
24
+ (The MIT License)
25
+
26
+ Copyright (c) 2009 Stephen Augenstein and Jacob Smith
27
+
28
+ Permission is hereby granted, free of charge, to any person obtaining
29
+ a copy of this software and associated documentation files (the
30
+ 'Software'), to deal in the Software without restriction, including
31
+ without limitation the rights to use, copy, modify, merge, publish,
32
+ distribute, sublicense, and/or sell copies of the Software, and to
33
+ permit persons to whom the Software is furnished to do so, subject to
34
+ the following conditions:
35
+
36
+ The above copyright notice and this permission notice shall be
37
+ included in all copies or substantial portions of the Software.
38
+
39
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
40
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
41
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
42
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
43
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
44
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
45
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+
6
+ require 'spec/rake/spectask'
7
+
8
+ desc 'Default: run the specs.'
9
+ task :default => :spec
10
+
11
+ Spec::Rake::SpecTask.new do |t|
12
+ t.spec_opts = ['--options', 'spec/spec.opts']
13
+ end
14
+
15
+ desc 'Generate documentation for the RocketAMF plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'scotttam-RocketAMF'
19
+ rdoc.options << '--line-numbers' << '--main' << 'README.rdoc'
20
+ rdoc.rdoc_files.include('README.rdoc')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
23
+
24
+ spec = Gem::Specification.new do |s|
25
+ s.name = 'scotttam-RocketAMF'
26
+ s.version = '0.2.2'
27
+ s.summary = 'Fast AMF serializer/deserializer with remoting request/response wrappers to simplify integration'
28
+
29
+ s.files = FileList['README.rdoc', 'Rakefile', 'lib/**/*.rb', 'spec/**/*.rb', 'spec/**/*.bin', 'spec/spec.opts']
30
+ s.require_path = 'lib'
31
+ s.test_files = Dir[*['spec/**/*_spec.rb']]
32
+
33
+ s.has_rdoc = true
34
+ s.extra_rdoc_files = ['README.rdoc']
35
+ s.rdoc_options = ['--line-numbers', '--main', 'README.rdoc']
36
+
37
+ s.authors = ['Jacob Henry', 'Stephen Augenstein', "Joc O'Connor", "Scott Tamosunas"]
38
+ s.email = 'perl.programmer@gmail.com'
39
+ s.homepage = 'http://github.com/scotttam/rocket-amf'
40
+
41
+ s.platform = Gem::Platform::RUBY
42
+ end
43
+
44
+ Rake::GemPackageTask.new spec do |pkg|
45
+ pkg.need_tar = true
46
+ pkg.need_zip = true
47
+ end
48
+
49
+ desc 'Generate a gemspec file'
50
+ task :gemspec do
51
+ File.open("#{spec.name}.gemspec", 'w') do |f|
52
+ f.write spec.to_ruby
53
+ end
54
+ end
@@ -0,0 +1,128 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
+ $:.unshift "#{File.expand_path(File.dirname(__FILE__))}/rocketamf/"
3
+
4
+ require 'rocketamf/class_mapping'
5
+ require 'rocketamf/constants'
6
+ require 'rocketamf/remoting'
7
+
8
+ # Joc's monkeypatch for string bytesize (only available in 1.8.7+)
9
+ if !"amf".respond_to? :bytesize
10
+ class String
11
+ def bytesize
12
+ self.size
13
+ end
14
+ end
15
+ end
16
+
17
+ # RocketAMF is a full featured AMF0/3 serializer and deserializer with support
18
+ # for Flash -> Ruby and Ruby -> Flash class mapping, custom serializers,
19
+ # remoting gateway helpers that follow AMF0/3 messaging specs, and a suite of
20
+ # specs to ensure adherence to the specification documents put out by Adobe.
21
+ #
22
+ # == Serialization & Deserialization
23
+ #
24
+ # RocketAMF provides two main methods - <tt>RocketAMF.serialize(obj, amf_version=0)</tt>
25
+ # and <tt>RocketAMF.deserialize(source, amf_version=0)</tt>. To use, simple pass
26
+ # in the string to deserialize and the version if different from the default. To
27
+ # serialize an object, simply call <tt>RocketAMF.serialize</tt> with the object
28
+ # and the proper version. If you're working only with AS3, it is more effiecient
29
+ # to use the version 3 encoding, as it caches duplicate string to reduce
30
+ # serialized size. However for greater compatibility the default, AMF version 0,
31
+ # should work fine.
32
+ #
33
+ # == Mapping Classes Between Flash and Ruby
34
+ #
35
+ # RocketAMF provides a simple class mapping tool to facilitate serialization and
36
+ # deserialization of typed objects. Refer to the documentation of
37
+ # <tt>RocketAMF::ClassMapping</tt> for more details. If the provided class
38
+ # mapping tool is not sufficient for your needs, you also have the option to
39
+ # replace it with a class mapper of your own devising that matches the documented
40
+ # API.
41
+ #
42
+ # == Remoting
43
+ #
44
+ # You can use RocketAMF bare to write an AMF gateway using the following code.
45
+ # In addition, you can use rack-amf (http://github.com/warhammerkid/rack-amf)
46
+ # which simplifies the code necessary to set up a functioning AMF gateway.
47
+ #
48
+ # # helloworld.ru
49
+ # require 'rocketamf'
50
+ #
51
+ # class HelloWorldApp
52
+ # APPLICATION_AMF = 'application/x-amf'.freeze
53
+ #
54
+ # def call env
55
+ # if is_amf?(env)
56
+ # # Wrap request and response
57
+ # env['rack.input'].rewind
58
+ # request = RocketAMF::Envelope.new.populate_from_stream(env['rack.input'].read)
59
+ # response = RocketAMF::Envelope.new
60
+ #
61
+ # # Handle request
62
+ # response.each_method_call request do |method, args|
63
+ # raise "Service #{method} does not exists" unless method == 'App.helloWorld'
64
+ # 'Hello world'
65
+ # end
66
+ #
67
+ # # Pass back response
68
+ # response_str = response.serialize
69
+ # return [200, {'Content-Type' => APPLICATION_AMF, 'Content-Length' => response_str.length.to_s}, [response_str]]
70
+ # else
71
+ # return [200, {'Content-Type' => 'text/plain', 'Content-Length' => '16' }, ["Rack AMF gateway"]]
72
+ # end
73
+ # end
74
+ #
75
+ # private
76
+ # def is_amf? env
77
+ # return false unless env['CONTENT_TYPE'] == APPLICATION_AMF
78
+ # return false unless env['PATH_INFO'] == '/amf'
79
+ # return true
80
+ # end
81
+ # end
82
+ #
83
+ # run HelloWorldApp.new
84
+ module RocketAMF
85
+ begin
86
+ raise LoadError, 'C extensions not implemented'
87
+ rescue LoadError
88
+ require 'rocketamf/pure'
89
+ end
90
+
91
+ # Deserialize the AMF string _source_ of the given AMF version into a Ruby
92
+ # data structure and return it
93
+ def self.deserialize source, amf_version = 0
94
+ if amf_version == 0
95
+ RocketAMF::Deserializer.new.deserialize(source)
96
+ elsif amf_version == 3
97
+ RocketAMF::AMF3Deserializer.new.deserialize(source)
98
+ else
99
+ raise AMFError, "unsupported version #{amf_version}"
100
+ end
101
+ end
102
+
103
+ # Serialize the given Ruby data structure _obj_ into an AMF stream using the
104
+ # given AMF version
105
+ def self.serialize obj, amf_version = 0
106
+ if amf_version == 0
107
+ RocketAMF::Serializer.new.serialize(obj)
108
+ elsif amf_version == 3
109
+ RocketAMF::AMF3Serializer.new.serialize(obj)
110
+ else
111
+ raise AMFError, "unsupported version #{amf_version}"
112
+ end
113
+ end
114
+
115
+ # We use const_missing to define the active ClassMapper at runtime. This way,
116
+ # heavy modification of class mapping functionality is still possible without
117
+ # forcing extenders to redefine the constant.
118
+ def self.const_missing const #:nodoc:
119
+ if const == :ClassMapper
120
+ RocketAMF.const_set(:ClassMapper, RocketAMF::ClassMapping.new)
121
+ else
122
+ super(const)
123
+ end
124
+ end
125
+
126
+ # The base exception for AMF errors.
127
+ class AMFError < StandardError; end
128
+ end
@@ -0,0 +1,231 @@
1
+ require 'rocketamf/values/typed_hash'
2
+ require 'rocketamf/values/array_collection'
3
+ require 'rocketamf/values/messages'
4
+
5
+ module RocketAMF
6
+ # Handles class name mapping between actionscript and ruby and assists in
7
+ # serializing and deserializing data between them. Simply map an AS class to a
8
+ # ruby class and when the object is (de)serialized it will end up as the
9
+ # appropriate class.
10
+ #
11
+ # Example:
12
+ #
13
+ # RocketAMF::ClassMapper.define do |m|
14
+ # m.map :as => 'AsClass', :ruby => 'RubyClass'
15
+ # m.map :as => 'vo.User', :ruby => 'Model::User'
16
+ # end
17
+ #
18
+ # == Object Population/Serialization
19
+ #
20
+ # In addition to handling class name mapping, it also provides helper methods
21
+ # for populating ruby objects from AMF and extracting properties from ruby objects
22
+ # for serialization. Support for hash-like objects and objects using
23
+ # <tt>attr_accessor</tt> for properties is currently built in, but custom classes
24
+ # may need custom support. As such, it is possible to create a custom populator
25
+ # or serializer.
26
+ #
27
+ # Populators are processed in insert order and must respond to the <tt>can_handle?</tt>
28
+ # and <tt>populate</tt> methods.
29
+ #
30
+ # class CustomPopulator
31
+ # def can_handle? obj
32
+ # true
33
+ # end
34
+ #
35
+ # def populate obj, props, dynamic_props
36
+ # obj.merge! props
37
+ # obj.merge!(dynamic_props) if dynamic_props
38
+ # end
39
+ # end
40
+ # RocketAMF::ClassMapper.object_populators << CustomPopulator.new
41
+ #
42
+ #
43
+ # Serializers are also processed in insert order and must respond to the
44
+ # <tt>can_handle?</tt> and <tt>serialize</tt> methods.
45
+ #
46
+ # class CustomSerializer
47
+ # def can_handle? obj
48
+ # true
49
+ # end
50
+ #
51
+ # def serialize obj
52
+ # {}
53
+ # end
54
+ # end
55
+ # RocketAMF::ClassMapper.object_serializers << CustomSerializer.new
56
+ #
57
+ # == Complete Replacement
58
+ #
59
+ # In some cases, it may be beneficial to replace the default provider of class
60
+ # mapping completely. In this case, simply assign an instance of your own class
61
+ # mapper to <tt>RocketAMF::ClassMapper</tt> after loading RocketAMF. Through
62
+ # the magic of <tt>const_missing</tt>, <tt>ClassMapper</tt> is only defined after
63
+ # the first access by default, so you get no annoying warning messages. Custom
64
+ # class mappers must implement the following methods: <tt>get_as_class_name</tt>,
65
+ # <tt>get_ruby_obj</tt>, <tt>populate_ruby_obj</tt>, <tt>props_for_serialization</tt>.
66
+ #
67
+ # Example:
68
+ #
69
+ # require 'rubygems'
70
+ # require 'rocketamf'
71
+ #
72
+ # RocketAMF::ClassMapper = MyCustomClassMapper.new
73
+ # # No warning about already initialized constant ClassMapper
74
+ # RocketAMF::ClassMapper.class # MyCustomClassMapper
75
+ class ClassMapping
76
+ # Container for all mapped classes
77
+ class MappingSet
78
+ def initialize #:nodoc:
79
+ @as_mappings = {}
80
+ @ruby_mappings = {}
81
+
82
+ # Map defaults
83
+ map :as => 'flex.messaging.messages.AbstractMessage', :ruby => 'RocketAMF::Values::AbstractMessage'
84
+ map :as => 'flex.messaging.messages.RemotingMessage', :ruby => 'RocketAMF::Values::RemotingMessage'
85
+ map :as => 'flex.messaging.messages.AsyncMessage', :ruby => 'RocketAMF::Values::AsyncMessage'
86
+ map :as => 'flex.messaging.messages.CommandMessage', :ruby => 'RocketAMF::Values::CommandMessage'
87
+ map :as => 'flex.messaging.messages.AcknowledgeMessage', :ruby => 'RocketAMF::Values::AcknowledgeMessage'
88
+ map :as => 'flex.messaging.messages.ErrorMessage', :ruby => 'RocketAMF::Values::ErrorMessage'
89
+ map :as => 'flex.messaging.io.ArrayCollection', :ruby => 'RocketAMF::Values::ArrayCollection'
90
+ end
91
+
92
+ # Map a given AS class to a ruby class.
93
+ #
94
+ # Use fully qualified names for both.
95
+ #
96
+ # Example:
97
+ #
98
+ # m.map :as => 'com.example.Date', :ruby => 'Example::Date'
99
+ def map params
100
+ [:as, :ruby].each {|k| params[k] = params[k].to_s} # Convert params to strings
101
+ @as_mappings[params[:as]] = params[:ruby]
102
+ @ruby_mappings[params[:ruby]] = params[:as]
103
+ end
104
+
105
+ # Returns the AS class name for the given ruby class name, returing nil if
106
+ # not found
107
+ def get_as_class_name class_name #:nodoc:
108
+ @ruby_mappings[class_name.to_s]
109
+ end
110
+
111
+ # Returns the ruby class name for the given AS class name, returing nil if
112
+ # not found
113
+ def get_ruby_class_name class_name #:nodoc:
114
+ @as_mappings[class_name.to_s]
115
+ end
116
+ end
117
+
118
+ # Array of custom object populators.
119
+ attr_reader :object_populators
120
+
121
+ # Array of custom object serializers.
122
+ attr_reader :object_serializers
123
+
124
+ def initialize #:nodoc:
125
+ @object_populators = []
126
+ @object_serializers = []
127
+ end
128
+
129
+ # Define class mappings in the block. Block is passed a MappingSet object as
130
+ # the first parameter.
131
+ #
132
+ # Example:
133
+ #
134
+ # RocketAMF::ClassMapper.define do |m|
135
+ # m.map :as => 'AsClass', :ruby => 'RubyClass'
136
+ # end
137
+ def define #:yields: mapping_set
138
+ yield mappings
139
+ end
140
+
141
+ # Reset all class mappings except the defaults
142
+ def reset
143
+ @mappings = nil
144
+ end
145
+
146
+ # Returns the AS class name for the given ruby object. Will also take a string
147
+ # containing the ruby class name.
148
+ def get_as_class_name obj
149
+ # Get class name
150
+ if obj.is_a?(String)
151
+ ruby_class_name = obj
152
+ elsif obj.is_a?(Values::TypedHash)
153
+ ruby_class_name = obj.type
154
+ else
155
+ ruby_class_name = obj.class.name
156
+ end
157
+
158
+ # Get mapped AS class name
159
+ mappings.get_as_class_name ruby_class_name
160
+ end
161
+
162
+ # Instantiates a ruby object using the mapping configuration based on the
163
+ # source AS class name. If there is no mapping defined, it returns a
164
+ # <tt>RocketAMF::Values::TypedHash</tt> with the serialized class name.
165
+ def get_ruby_obj as_class_name
166
+ ruby_class_name = mappings.get_ruby_class_name as_class_name
167
+ if ruby_class_name.nil?
168
+ # Populate a simple hash, since no mapping
169
+ return Values::TypedHash.new(as_class_name)
170
+ else
171
+ ruby_class = ruby_class_name.split('::').inject(Kernel) {|scope, const_name| scope.const_get(const_name)}
172
+ return ruby_class.new
173
+ end
174
+ end
175
+
176
+ # Populates the ruby object using the given properties
177
+ def populate_ruby_obj obj, props, dynamic_props=nil
178
+ # Process custom populators
179
+ @object_populators.each do |p|
180
+ next unless p.can_handle?(obj)
181
+ p.populate obj, props, dynamic_props
182
+ return obj
183
+ end
184
+
185
+ # Fallback populator
186
+ props.merge! dynamic_props if dynamic_props
187
+ hash_like = obj.respond_to?("[]=")
188
+ props.each do |key, value|
189
+ if obj.respond_to?("#{key}=")
190
+ obj.send("#{key}=", value)
191
+ elsif hash_like
192
+ obj[key.to_sym] = value
193
+ end
194
+ end
195
+ obj
196
+ end
197
+
198
+ # Extracts all exportable properties from the given ruby object and returns
199
+ # them in a hash
200
+ def props_for_serialization ruby_obj
201
+ # Proccess custom serializers
202
+ @object_serializers.each do |s|
203
+ next unless s.can_handle?(ruby_obj)
204
+ return s.serialize(ruby_obj)
205
+ end
206
+
207
+ # Handle hashes
208
+ if ruby_obj.is_a?(Hash)
209
+ # Stringify keys to make it easier later on and allow sorting
210
+ h = {}
211
+ ruby_obj.each {|k,v| h[k.to_s] = v}
212
+ return h
213
+ end
214
+
215
+ # Fallback serializer
216
+ props = {}
217
+ @ignored_props ||= Object.new.public_methods
218
+ (ruby_obj.public_methods - @ignored_props).each do |method_name|
219
+ # Add them to the prop hash if they take no arguments
220
+ method_def = ruby_obj.method(method_name)
221
+ props[method_name.to_s] = ruby_obj.send(method_name) if method_def.arity == 0
222
+ end
223
+ props
224
+ end
225
+
226
+ private
227
+ def mappings
228
+ @mappings ||= MappingSet.new
229
+ end
230
+ end
231
+ end