restfulx 1.2.5 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +3 -3
- data/Rakefile +17 -2
- data/VERSION.yml +2 -3
- data/bin/rx-gen +1 -1
- data/ext/restfulx/ext/amf/serializer/extconf.rb +3 -0
- data/lib/restfulx.rb +43 -81
- data/lib/restfulx/active_record_tasks.rb +8 -6
- data/lib/restfulx/active_record_uuid_helper.rb +9 -10
- data/lib/restfulx/amf.rb +14 -0
- data/lib/restfulx/amf/class_mapping.rb +106 -0
- data/lib/restfulx/amf/ext.rb +11 -0
- data/lib/restfulx/amf/ext/serializer.bundle +0 -0
- data/lib/restfulx/amf/pure.rb +11 -0
- data/lib/restfulx/amf/pure/io_helpers.rb +52 -0
- data/lib/restfulx/amf/pure/serializer.rb +304 -0
- data/lib/restfulx/configuration.rb +16 -12
- data/lib/restfulx/{rails/recipes.rb → recipes.rb} +1 -2
- data/lib/restfulx/rx_action_controller.rb +34 -0
- data/lib/restfulx/rx_active_record.rb +360 -0
- data/lib/restfulx/rx_active_support.rb +139 -0
- data/lib/restfulx/{datamapper_foo.rb → rx_datamapper.rb} +0 -2
- data/lib/restfulx/{rails/schema_to_yaml.rb → schema_to_rx_yaml.rb} +88 -5
- data/lib/restfulx/swf_helper.rb +61 -0
- data/lib/restfulx/tasks.rb +5 -3
- data/rails_generators/rx_config/rx_config_generator.rb +2 -2
- data/rails_generators/rx_config/templates/mainapp.mxml +1 -1
- data/rails_generators/rx_config/templates/restfulx.erb +5 -3
- data/rails_generators/rx_main_app/rx_main_app_generator.rb +2 -2
- data/rails_generators/rx_scaffold/rx_scaffold_generator.rb +7 -7
- data/rails_generators/rx_yaml_scaffold/rx_yaml_scaffold_generator.rb +5 -3
- data/rxgen_generators/rx_config/rx_config_generator.rb +1 -1
- data/test/rails/fixtures/locations.yml +5 -6
- data/test/rails/fixtures/notes.yml +5 -15
- data/test/rails/fixtures/projects.yml +7 -23
- data/test/rails/fixtures/tasks.yml +17 -43
- data/test/rails/fixtures/users.yml +7 -11
- data/test/rails/helpers/functional_test_helper.rb +5 -4
- data/test/rails/helpers/performance_test_helper.rb +5 -0
- data/test/rails/helpers/test_helper.rb +27 -0
- data/test/rails/helpers/unit_test_helper.rb +3 -15
- data/test/rails/test_active_foo.rb +21 -25
- data/test/rails/{test_rails_integration_functional.rb → test_notes_controller_functional.rb} +5 -5
- data/test/rails/test_serialiazation_performance.rb +32 -0
- data/test/rails/test_to_amf.rb +30 -0
- data/test/rails/test_to_fxml.rb +18 -15
- data/test/rails/test_to_json.rb +2 -14
- metadata +30 -19
- data/lib/restfulx/active_foo.rb +0 -178
- data/lib/restfulx/rails/schema_to_yaml/extensions/enumerable.rb +0 -8
- data/lib/restfulx/rails/schema_to_yaml/settings/config.rb +0 -17
- data/lib/restfulx/rails/schema_to_yaml/settings/core.rb +0 -73
- data/lib/restfulx/rails/swf_helper.rb +0 -59
@@ -0,0 +1,304 @@
|
|
1
|
+
require 'amf/pure/io_helpers'
|
2
|
+
|
3
|
+
module RestfulX::AMF
|
4
|
+
module Pure #:nodoc: all
|
5
|
+
# AMF3 Type Markers
|
6
|
+
AMF3_UNDEFINED_MARKER = 0x00 #"\000"
|
7
|
+
AMF3_NULL_MARKER = 0x01 #"\001"
|
8
|
+
AMF3_FALSE_MARKER = 0x02 #"\002"
|
9
|
+
AMF3_TRUE_MARKER = 0x03 #"\003"
|
10
|
+
AMF3_INTEGER_MARKER = 0x04 #"\004"
|
11
|
+
AMF3_DOUBLE_MARKER = 0x05 #"\005"
|
12
|
+
AMF3_STRING_MARKER = 0x06 #"\006"
|
13
|
+
AMF3_XML_DOC_MARKER = 0x07 #"\a"
|
14
|
+
AMF3_DATE_MARKER = 0x08 #"\b"
|
15
|
+
AMF3_ARRAY_MARKER = 0x09 #"\t"
|
16
|
+
AMF3_OBJECT_MARKER = 0x0A #"\n"
|
17
|
+
AMF3_XML_MARKER = 0x0B #"\v"
|
18
|
+
AMF3_BYTE_ARRAY_MARKER = 0x0C #"\f"
|
19
|
+
|
20
|
+
# Other AMF3 Markers
|
21
|
+
AMF3_EMPTY_STRING = 0x01
|
22
|
+
AMF3_ANONYMOUS_OBJECT = 0x01
|
23
|
+
AMF3_DYNAMIC_OBJECT = 0x0B
|
24
|
+
AMF3_CLOSE_DYNAMIC_OBJECT = 0x01
|
25
|
+
AMF3_CLOSE_DYNAMIC_ARRAY = 0x01
|
26
|
+
|
27
|
+
# Other Constants
|
28
|
+
MAX_INTEGER = 268435455
|
29
|
+
MIN_INTEGER = -268435456
|
30
|
+
|
31
|
+
class SerializerCache < Hash
|
32
|
+
attr_accessor :cache_index
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@cache_index = 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def cache(obj)
|
39
|
+
self[obj] = @cache_index
|
40
|
+
@cache_index += 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class RxAMFSerializer
|
45
|
+
def initialize()
|
46
|
+
@stream = ""
|
47
|
+
@string_cache = SerializerCache.new
|
48
|
+
@object_cache = SerializerCache.new
|
49
|
+
end
|
50
|
+
|
51
|
+
def version
|
52
|
+
3
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
@stream
|
57
|
+
end
|
58
|
+
|
59
|
+
def write_vr(name)
|
60
|
+
if name == ''
|
61
|
+
@stream << AMF3_EMPTY_STRING
|
62
|
+
elsif @string_cache[name] != nil
|
63
|
+
write_reference(@string_cache[name])
|
64
|
+
else
|
65
|
+
# Cache string
|
66
|
+
@string_cache.cache(name)
|
67
|
+
|
68
|
+
# Build AMF string
|
69
|
+
header = name.length << 1 # make room for a low bit of 1
|
70
|
+
header = header | 1 # set the low bit to 1
|
71
|
+
@stream << pack_integer(header)
|
72
|
+
@stream << name
|
73
|
+
end
|
74
|
+
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
|
78
|
+
def serialize_property(prop)
|
79
|
+
if prop.is_a?(NilClass)
|
80
|
+
write_null
|
81
|
+
elsif prop.is_a?(TrueClass)
|
82
|
+
write_true
|
83
|
+
elsif prop.is_a?(FalseClass)
|
84
|
+
write_false
|
85
|
+
elsif prop.is_a?(Float) || prop.is_a?(Bignum) || prop.is_a?(BigDecimal)
|
86
|
+
write_float(prop)
|
87
|
+
elsif prop.is_a?(Integer)
|
88
|
+
write_integer(prop)
|
89
|
+
elsif prop.is_a?(Symbol) || prop.is_a?(String)
|
90
|
+
write_string(prop.to_s)
|
91
|
+
elsif prop.is_a?(Time) || prop.is_a?(DateTime)
|
92
|
+
write_time(prop)
|
93
|
+
elsif prop.is_a?(Date)
|
94
|
+
write_date(prop)
|
95
|
+
elsif prop.is_a?(Hash)
|
96
|
+
write_hash(prop)
|
97
|
+
end
|
98
|
+
|
99
|
+
self
|
100
|
+
end
|
101
|
+
|
102
|
+
def serialize_models_array(records, options = {})
|
103
|
+
@stream << AMF3_OBJECT_MARKER << AMF3_XML_DOC_MARKER
|
104
|
+
write_vr('org.restfulx.messaging.io.ModelsCollection')
|
105
|
+
@object_cache.cache_index += 2
|
106
|
+
|
107
|
+
serialize_records(records, options)
|
108
|
+
end
|
109
|
+
|
110
|
+
def serialize_typed_array(records, options = {})
|
111
|
+
@stream << AMF3_OBJECT_MARKER << AMF3_XML_DOC_MARKER
|
112
|
+
write_vr('org.restfulx.messaging.io.TypedArray')
|
113
|
+
@object_cache.cache_index += 1
|
114
|
+
serialize_property(options[:attributes])
|
115
|
+
@object_cache.cache_index += 1
|
116
|
+
|
117
|
+
serialize_records(records, options)
|
118
|
+
end
|
119
|
+
|
120
|
+
def serialize_errors(errors)
|
121
|
+
@stream << AMF3_OBJECT_MARKER << AMF3_XML_DOC_MARKER
|
122
|
+
write_vr('org.restfulx.messaging.io.ServiceErrors')
|
123
|
+
serialize_property(errors)
|
124
|
+
@stream << AMF3_CLOSE_DYNAMIC_OBJECT
|
125
|
+
self
|
126
|
+
end
|
127
|
+
|
128
|
+
def serialize_record(record, serializable_names = nil, options = {}, &block)
|
129
|
+
@stream << AMF3_OBJECT_MARKER
|
130
|
+
record_id = record.respond_to?(:unique_id) ? record.unique_id : record.object_id
|
131
|
+
|
132
|
+
partials = {}
|
133
|
+
|
134
|
+
if @object_cache[record_id] != nil
|
135
|
+
write_reference(@object_cache[record_id])
|
136
|
+
else
|
137
|
+
# Cache object
|
138
|
+
@object_cache.cache(record_id)
|
139
|
+
|
140
|
+
# Always serialize things as dynamic objects
|
141
|
+
@stream << AMF3_DYNAMIC_OBJECT
|
142
|
+
|
143
|
+
# Write class name/anonymous
|
144
|
+
class_name = RestfulX::AMF::ClassMapper.get_as_class_name(record)
|
145
|
+
if class_name
|
146
|
+
write_vr(class_name)
|
147
|
+
else
|
148
|
+
@stream << AMF3_ANONYMOUS_OBJECT
|
149
|
+
end
|
150
|
+
|
151
|
+
serializable_names.each do |prop|
|
152
|
+
if prop.is_a?(Hash)
|
153
|
+
record_name = prop[:name]
|
154
|
+
record_value = record[record_name]
|
155
|
+
ref_name = prop[:ref_name]
|
156
|
+
ref_class = prop[:ref_class]
|
157
|
+
|
158
|
+
if ref_class == "polymorphic"
|
159
|
+
ref_class_name = record["#{prop[:orig_ref_name]}_type"]
|
160
|
+
ref_class = ref_class_name.constantize
|
161
|
+
else
|
162
|
+
ref_class_name = ref_class.class_name
|
163
|
+
end
|
164
|
+
|
165
|
+
result_id = "#{ref_class_name}_#{record_value}" if record_value
|
166
|
+
|
167
|
+
write_vr(ref_name)
|
168
|
+
if result_id
|
169
|
+
if @object_cache[result_id]
|
170
|
+
@stream << AMF3_OBJECT_MARKER
|
171
|
+
write_reference(@object_cache[result_id])
|
172
|
+
else
|
173
|
+
partials[ref_name.to_s] = ref_class_name
|
174
|
+
unless partial = options[:cached_instances][ref_class_name]
|
175
|
+
options[:cached_instances][ref_class_name] = ref_class.new
|
176
|
+
partial = options[:cached_instances][ref_class_name]
|
177
|
+
end
|
178
|
+
partial.id = record_value
|
179
|
+
serialize_record(partial, ['id'])
|
180
|
+
end
|
181
|
+
else
|
182
|
+
write_null
|
183
|
+
end
|
184
|
+
else
|
185
|
+
write_vr(prop.to_s.camelize(:lower))
|
186
|
+
serialize_property(record[prop])
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
write_vr("partials")
|
191
|
+
serialize_property(partials)
|
192
|
+
|
193
|
+
block.call(self) if block_given?
|
194
|
+
|
195
|
+
# Write close
|
196
|
+
@stream << AMF3_CLOSE_DYNAMIC_OBJECT
|
197
|
+
end
|
198
|
+
self
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
def serialize_records(records, options = {})
|
203
|
+
@stream << AMF3_ARRAY_MARKER
|
204
|
+
|
205
|
+
header = records.length << 1 # make room for a low bit of 1
|
206
|
+
header = header | 1 # set the low bit to 1
|
207
|
+
@stream << pack_integer(header)
|
208
|
+
|
209
|
+
@stream << AMF3_CLOSE_DYNAMIC_ARRAY
|
210
|
+
records.each do |elem|
|
211
|
+
if elem.respond_to?(:to_amf)
|
212
|
+
elem.to_amf(options)
|
213
|
+
else
|
214
|
+
serialize_property(elem)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
self
|
219
|
+
end
|
220
|
+
|
221
|
+
def write_reference(index)
|
222
|
+
header = index << 1 # shift value left to leave a low bit of 0
|
223
|
+
@stream << pack_integer(header)
|
224
|
+
end
|
225
|
+
|
226
|
+
def write_null
|
227
|
+
@stream << AMF3_NULL_MARKER
|
228
|
+
end
|
229
|
+
|
230
|
+
def write_true
|
231
|
+
@stream << AMF3_TRUE_MARKER
|
232
|
+
end
|
233
|
+
|
234
|
+
def write_false
|
235
|
+
@stream << AMF3_FALSE_MARKER
|
236
|
+
end
|
237
|
+
|
238
|
+
def write_integer(int)
|
239
|
+
if int < MIN_INTEGER || int > MAX_INTEGER # Check valid range for 29 bits
|
240
|
+
write_float(int.to_f)
|
241
|
+
else
|
242
|
+
@stream << AMF3_INTEGER_MARKER
|
243
|
+
@stream << pack_integer(int)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def write_float(float)
|
248
|
+
@stream << AMF3_DOUBLE_MARKER
|
249
|
+
@stream << pack_double(float)
|
250
|
+
end
|
251
|
+
|
252
|
+
def write_string(str)
|
253
|
+
@stream << AMF3_STRING_MARKER
|
254
|
+
write_vr(str)
|
255
|
+
end
|
256
|
+
|
257
|
+
def write_time(time)
|
258
|
+
@stream << AMF3_DATE_MARKER
|
259
|
+
|
260
|
+
@object_cache.cache_index += 1
|
261
|
+
|
262
|
+
# Build AMF string
|
263
|
+
time.utc unless time.utc?
|
264
|
+
seconds = (time.to_f * 1000).to_i
|
265
|
+
@stream << pack_integer(AMF3_NULL_MARKER)
|
266
|
+
@stream << pack_double(seconds)
|
267
|
+
end
|
268
|
+
|
269
|
+
def write_date(date)
|
270
|
+
@stream << AMF3_DATE_MARKER
|
271
|
+
|
272
|
+
@object_cache.cache_index += 1
|
273
|
+
|
274
|
+
# Build AMF string
|
275
|
+
seconds = ((date.strftime("%s").to_i) * 1000).to_i
|
276
|
+
@stream << pack_integer(AMF3_NULL_MARKER)
|
277
|
+
@stream << pack_double(seconds)
|
278
|
+
end
|
279
|
+
|
280
|
+
def write_hash(hash)
|
281
|
+
@stream << AMF3_OBJECT_MARKER
|
282
|
+
if @object_cache[hash] != nil
|
283
|
+
write_reference(@object_cache[hash])
|
284
|
+
else
|
285
|
+
# Cache object
|
286
|
+
@object_cache.cache(hash)
|
287
|
+
|
288
|
+
# Always serialize things as dynamic objects
|
289
|
+
@stream << AMF3_DYNAMIC_OBJECT << AMF3_ANONYMOUS_OBJECT
|
290
|
+
|
291
|
+
hash.each do |key, value|
|
292
|
+
write_vr(key.to_s.camelize(:lower))
|
293
|
+
serialize_property(value)
|
294
|
+
end
|
295
|
+
|
296
|
+
# Write close
|
297
|
+
@stream << AMF3_CLOSE_DYNAMIC_OBJECT
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
include RestfulX::AMF::Pure::WriteIOHelpers
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
@@ -1,15 +1,19 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
require "yaml"
|
5
|
-
require "erb"
|
6
|
-
require File.dirname(__FILE__) + "/rails/schema_to_yaml/settings/config"
|
7
|
-
require File.dirname(__FILE__) + "/rails/schema_to_yaml/settings/core"
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
2
|
+
require 'erb'
|
3
|
+
require 'schema_to_rx_yaml' if !defined?(SchemaToRxYaml)
|
8
4
|
|
9
|
-
|
10
|
-
|
5
|
+
# Enumerable extensions
|
6
|
+
module Enumerable
|
7
|
+
# Helps you find duplicates
|
8
|
+
# used in schema_to_yaml.rb for cleanup
|
9
|
+
def dups
|
10
|
+
inject({}) { |h,v| h[v] = h[v].to_i + 1; h }.reject{ |k,v| v == 1 }.keys
|
11
|
+
end
|
11
12
|
end
|
12
13
|
|
14
|
+
# Interestingly enough there's no way to *just* upper-case or down-case first letter of a given
|
15
|
+
# string. Ruby's own +capitalize+ actually downcases all the rest of the characters in the string
|
16
|
+
# We patch the class to add our own implementation.
|
13
17
|
class String
|
14
18
|
# Upper-case first character of a string leave the rest of the string intact
|
15
19
|
def ucfirst
|
@@ -24,13 +28,13 @@ end
|
|
24
28
|
|
25
29
|
# Primary RestfulX configuration options
|
26
30
|
module RestfulX
|
27
|
-
# Computes necessary configuration options from the environment. This can be used in Rails
|
31
|
+
# Computes necessary configuration options from the environment. This can be used in Rails
|
28
32
|
# or standalone from the command line.
|
29
33
|
module Configuration
|
30
34
|
# We try to figure out the application root using a number of possible options
|
31
|
-
APP_ROOT = defined?(RAILS_ROOT) ? RAILS_ROOT :
|
35
|
+
APP_ROOT = defined?(RAILS_ROOT) ? RAILS_ROOT : File.expand_path(".")
|
32
36
|
|
33
|
-
RxSettings =
|
37
|
+
RxSettings = SchemaToRxYaml::Settings::Core
|
34
38
|
|
35
39
|
# Extract project, package, controller name, etc from the environment. This will respect
|
36
40
|
# config/restfulx.yml if it exists, you can override all of the defaults there.
|
@@ -2,7 +2,7 @@
|
|
2
2
|
#
|
3
3
|
# To use these recipes, add the following to your Capfile:
|
4
4
|
#
|
5
|
-
# require 'restfulx/
|
5
|
+
# require 'restfulx/recipes'
|
6
6
|
require 'find'
|
7
7
|
|
8
8
|
Capistrano::Configuration.instance(:must_exist).load do
|
@@ -17,7 +17,6 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
17
17
|
|
18
18
|
namespace :deploy do
|
19
19
|
namespace :flex do
|
20
|
-
|
21
20
|
desc "Creates the flex_files directory in the shared directory"
|
22
21
|
task :setup do
|
23
22
|
flex_dir = File.join(shared_path, 'flex_files')
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module RestfulX::ActionController
|
2
|
+
def self.included(base)
|
3
|
+
base.class_eval do
|
4
|
+
alias_method_chain :render, :amf
|
5
|
+
alias_method_chain :render, :fxml
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
# Defines support for rendering :amf blocks from within Rails constrollers
|
10
|
+
def render_with_amf(options=nil, extra_options={}, &block)
|
11
|
+
if Hash === options and options.key?(:amf)
|
12
|
+
object = options.delete(:amf)
|
13
|
+
unless String === object
|
14
|
+
object = object.to_amf(options, &block) if object.respond_to?(:to_amf)
|
15
|
+
end
|
16
|
+
render_without_amf(:text => object, :content_type => RestfulX::Types::APPLICATION_AMF)
|
17
|
+
else
|
18
|
+
render_without_amf(options, extra_options, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Defines support for rendering :fxml blocks from within Rails controllers
|
23
|
+
def render_with_fxml(options=nil, extra_options={}, &block)
|
24
|
+
if Hash === options and options.key?(:fxml)
|
25
|
+
object = options.delete(:fxml)
|
26
|
+
unless String === object
|
27
|
+
object = object.to_fxml(options, &block) if object.respond_to?(:to_fxml)
|
28
|
+
end
|
29
|
+
render_without_fxml(:text => object, :content_type => RestfulX::Types::APPLICATION_FXML)
|
30
|
+
else
|
31
|
+
render_without_fxml(options, extra_options, &block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,360 @@
|
|
1
|
+
module RestfulX
|
2
|
+
module Serialization #:nodoc:
|
3
|
+
class FXMLSerializer < ::ActiveRecord::Serialization::Serializer #:nodoc:
|
4
|
+
def builder
|
5
|
+
@builder ||= begin
|
6
|
+
options[:indent] ||= 2
|
7
|
+
builder = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
|
8
|
+
|
9
|
+
unless options[:skip_instruct]
|
10
|
+
builder.instruct!
|
11
|
+
options[:skip_instruct] = true
|
12
|
+
end
|
13
|
+
|
14
|
+
builder
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def root
|
19
|
+
root = (options[:root] || @record.class.to_s.underscore).to_s
|
20
|
+
reformat_name(root)
|
21
|
+
end
|
22
|
+
|
23
|
+
def dasherize?
|
24
|
+
!options.has_key?(:dasherize) || options[:dasherize]
|
25
|
+
end
|
26
|
+
|
27
|
+
def camelize?
|
28
|
+
options.has_key?(:camelize) && options[:camelize]
|
29
|
+
end
|
30
|
+
|
31
|
+
def reformat_name(name)
|
32
|
+
name = name.camelize if camelize?
|
33
|
+
dasherize? ? name.dasherize : name
|
34
|
+
end
|
35
|
+
|
36
|
+
def serializable_attributes
|
37
|
+
serializable_attribute_names.collect { |name| Attribute.new(name, @record) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def serializable_method_attributes
|
41
|
+
Array(options[:methods]).inject([]) do |method_attributes, name|
|
42
|
+
method_attributes << MethodAttribute.new(name.to_s, @record) if @record.respond_to?(name.to_s)
|
43
|
+
method_attributes
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_attributes
|
48
|
+
(serializable_attributes + serializable_method_attributes).each do |attribute|
|
49
|
+
add_tag(attribute)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_procs
|
54
|
+
if procs = options.delete(:procs)
|
55
|
+
[ *procs ].each do |proc|
|
56
|
+
proc.call(options)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_tag(attribute)
|
62
|
+
builder.tag!(
|
63
|
+
reformat_name(attribute.name),
|
64
|
+
attribute.value.to_s,
|
65
|
+
attribute.decorations(!options[:skip_types])
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_associations(association, records, opts)
|
70
|
+
if records.is_a?(Enumerable)
|
71
|
+
tag = reformat_name(association.to_s)
|
72
|
+
type = options[:skip_types] ? {} : {:type => "array"}
|
73
|
+
|
74
|
+
if records.empty?
|
75
|
+
builder.tag!(tag, type)
|
76
|
+
else
|
77
|
+
builder.tag!(tag, type) do
|
78
|
+
association_name = association.to_s.singularize
|
79
|
+
records.each do |record|
|
80
|
+
if options[:skip_types]
|
81
|
+
record_type = {}
|
82
|
+
else
|
83
|
+
record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
|
84
|
+
record_type = {:type => record_class}
|
85
|
+
end
|
86
|
+
|
87
|
+
record.to_fxml opts.merge(:root => association_name).merge(record_type)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
else
|
92
|
+
if record = @record.send(association)
|
93
|
+
record.to_fxml(opts.merge(:root => association))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def serialize
|
99
|
+
args = [root]
|
100
|
+
if options[:namespace]
|
101
|
+
args << {:xmlns=>options[:namespace]}
|
102
|
+
end
|
103
|
+
|
104
|
+
if options[:type]
|
105
|
+
args << {:type=>options[:type]}
|
106
|
+
end
|
107
|
+
|
108
|
+
builder.tag!(*args) do
|
109
|
+
add_attributes
|
110
|
+
procs = options.delete(:procs)
|
111
|
+
add_includes { |association, records, opts| add_associations(association, records, opts) }
|
112
|
+
options[:procs] = procs
|
113
|
+
add_procs
|
114
|
+
yield builder if block_given?
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class Attribute #:nodoc:
|
119
|
+
attr_reader :name, :value, :type
|
120
|
+
|
121
|
+
def initialize(name, record)
|
122
|
+
@name, @record = name, record
|
123
|
+
|
124
|
+
@type = compute_type
|
125
|
+
@value = compute_value
|
126
|
+
end
|
127
|
+
|
128
|
+
# There is a significant speed improvement if the value
|
129
|
+
# does not need to be escaped, as <tt>tag!</tt> escapes all values
|
130
|
+
# to ensure that valid XML is generated. For known binary
|
131
|
+
# values, it is at least an order of magnitude faster to
|
132
|
+
# Base64 encode binary values and directly put them in the
|
133
|
+
# output XML than to pass the original value or the Base64
|
134
|
+
# encoded value to the <tt>tag!</tt> method. It definitely makes
|
135
|
+
# no sense to Base64 encode the value and then give it to
|
136
|
+
# <tt>tag!</tt>, since that just adds additional overhead.
|
137
|
+
def needs_encoding?
|
138
|
+
![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type)
|
139
|
+
end
|
140
|
+
|
141
|
+
def decorations(include_types = true)
|
142
|
+
decorations = {}
|
143
|
+
|
144
|
+
if type == :binary
|
145
|
+
decorations[:encoding] = 'base64'
|
146
|
+
end
|
147
|
+
|
148
|
+
if include_types && type != :string
|
149
|
+
decorations[:type] = type
|
150
|
+
end
|
151
|
+
|
152
|
+
if value.nil?
|
153
|
+
decorations[:nil] = true
|
154
|
+
end
|
155
|
+
|
156
|
+
decorations
|
157
|
+
end
|
158
|
+
|
159
|
+
protected
|
160
|
+
def compute_type
|
161
|
+
type = @record.class.serialized_attributes.has_key?(name) ? :yaml : @record.class.columns_hash[name].type
|
162
|
+
|
163
|
+
case type
|
164
|
+
when :text
|
165
|
+
:string
|
166
|
+
when :time
|
167
|
+
:datetime
|
168
|
+
else
|
169
|
+
type
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def compute_value
|
174
|
+
value = @record.send(name)
|
175
|
+
|
176
|
+
if formatter = Hash::XML_FORMATTING[type.to_s]
|
177
|
+
value ? formatter.call(value) : nil
|
178
|
+
else
|
179
|
+
value
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
class MethodAttribute < Attribute #:nodoc:
|
185
|
+
protected
|
186
|
+
def compute_type
|
187
|
+
Hash::XML_TYPE_NAMES[@record.send(name).class.name] || :string
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
class AMFSerializer < ::ActiveRecord::Serialization::Serializer #:nodoc:
|
193
|
+
def initialize(record, options = {})
|
194
|
+
super(record, options)
|
195
|
+
@options[:methods] ||= []
|
196
|
+
@options[:amf_version] = 3
|
197
|
+
@options[:serializer] ||= RestfulX::AMF::RxAMFSerializer.new
|
198
|
+
|
199
|
+
# options are duplicated by default so we need a copy for caching attributes
|
200
|
+
@original_options = options
|
201
|
+
@original_options[:cached_attributes] ||= {}
|
202
|
+
@options[:cached_instances] = @original_options[:cached_instances] ||= {}
|
203
|
+
end
|
204
|
+
|
205
|
+
def serialize
|
206
|
+
@options[:serializer].serialize_record(@record, serializable_attributes, @options) do |serializer|
|
207
|
+
([].concat(@options[:methods])).each do |method|
|
208
|
+
if @record.respond_to?(method)
|
209
|
+
serializer.write_vr(method.to_s.camelcase(:lower))
|
210
|
+
serializer.serialize_property(@record.send(method))
|
211
|
+
end
|
212
|
+
end
|
213
|
+
add_includes do |association, records, opts|
|
214
|
+
add_associations(association, records, opts, serializer)
|
215
|
+
end
|
216
|
+
end.to_s
|
217
|
+
end
|
218
|
+
|
219
|
+
def serializable_attributes
|
220
|
+
includes = @options[:include] ||= {}
|
221
|
+
|
222
|
+
# if we are serializing an array we only need to compute serializable_attributes for the
|
223
|
+
# objects of the same type at the same level once
|
224
|
+
if @original_options[:cached_attributes].has_key?(@record.class.name)
|
225
|
+
@original_options[:cached_attributes][@record.class.name]
|
226
|
+
else
|
227
|
+
associations = Hash[*@record.class.reflect_on_all_associations(:belongs_to).collect do |assoc|
|
228
|
+
if assoc.options.has_key?(:polymorphic) && assoc.options[:polymorphic]
|
229
|
+
@options[:except] = ([] << @options[:except] << "#{assoc.name}_type".to_sym).flatten
|
230
|
+
# class_name = @record[assoc.options[:foreign_type]].constantize
|
231
|
+
class_name = "polymorphic"
|
232
|
+
else
|
233
|
+
class_name = assoc.klass
|
234
|
+
end
|
235
|
+
[assoc.primary_key_name, {:name => assoc.name, :klass => class_name}]
|
236
|
+
end.flatten]
|
237
|
+
|
238
|
+
attributes = serializable_names.select do |name|
|
239
|
+
!includes.include?(associations[name][:name]) rescue true
|
240
|
+
end.map do |name|
|
241
|
+
associations.has_key?(name) ? {:name => name, :ref_name => associations[name][:name].to_s.camelize(:lower),
|
242
|
+
:ref_class => associations[name][:klass], :orig_ref_name => associations[name][:name].to_s } : name.to_sym
|
243
|
+
end
|
244
|
+
@original_options[:cached_attributes][@record.class.name] = attributes
|
245
|
+
attributes
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def add_associations(association, records, opts, serializer)
|
250
|
+
serializer.write_vr(association.to_s.camelcase(:lower))
|
251
|
+
if records.is_a?(Enumerable)
|
252
|
+
serializer.serialize_models_array(records, opts)
|
253
|
+
else
|
254
|
+
if record = @record.send(association)
|
255
|
+
record.to_amf(opts)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
module ActiveRecord
|
263
|
+
def self.included(base)
|
264
|
+
base.send :include, InstanceMethods
|
265
|
+
end
|
266
|
+
|
267
|
+
module InstanceMethods
|
268
|
+
def unique_id
|
269
|
+
"#{self.class.to_s}_#{self.attributes()['id']}"
|
270
|
+
end
|
271
|
+
|
272
|
+
# serialize this model as AMF
|
273
|
+
def to_amf(options = {})
|
274
|
+
default_except = [:crypted_password, :salt, :remember_token, :remember_token_expires_at, :created_at, :updated_at]
|
275
|
+
options[:except] = (options[:except] ? options[:except] + default_except : default_except)
|
276
|
+
|
277
|
+
RestfulX::Serialization::AMFSerializer.new(self, options).to_s
|
278
|
+
end
|
279
|
+
|
280
|
+
# serialize this model as fXML
|
281
|
+
def to_fxml(options = {})
|
282
|
+
options.merge!(:dasherize => false)
|
283
|
+
default_except = [:crypted_password, :salt, :remember_token, :remember_token_expires_at, :created_at, :updated_at]
|
284
|
+
options[:except] = (options[:except] ? options[:except] + default_except : default_except)
|
285
|
+
|
286
|
+
RestfulX::Serialization::FXMLSerializer.new(self, options).to_s
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
module ActiveRecord
|
293
|
+
# ActiveRecord named scopes are computed *before* restfulx gem gets loaded
|
294
|
+
# this patch addresses that and makes sure +to_fxml/to_amf+ calls are properly
|
295
|
+
# delegated
|
296
|
+
module NamedScope
|
297
|
+
# make sure we properly delegate +to_fxml+ calls to the proxy
|
298
|
+
class Scope
|
299
|
+
delegate :to_fxml, :to => :proxy_found
|
300
|
+
delegate :to_amf, :to => :proxy_found
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# Change the xml serializer so that '?'s are stripped from attribute names.
|
305
|
+
# This makes it possible to serialize methods that end in a question mark, like 'valid?' or 'is_true?'
|
306
|
+
class XmlSerializer
|
307
|
+
# Strips '?' from serialized method names
|
308
|
+
def add_tag(attribute)
|
309
|
+
builder.tag!(
|
310
|
+
dasherize? ? attribute.display_name.dasherize : attribute.display_name,
|
311
|
+
attribute.value.to_s,
|
312
|
+
attribute.decorations(!options[:skip_types])
|
313
|
+
)
|
314
|
+
end
|
315
|
+
# Strips '?' from serialized method names
|
316
|
+
class Attribute
|
317
|
+
# Strips '?' from serialized method names
|
318
|
+
def display_name
|
319
|
+
@name.gsub('?','')
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# Add more extensive reporting on errors including field name along with a message
|
325
|
+
# when errors are serialized to XML and JSON
|
326
|
+
class Errors
|
327
|
+
alias_method :to_json_original, :to_json
|
328
|
+
|
329
|
+
# serializer errors to fXML
|
330
|
+
def to_fxml(options = {})
|
331
|
+
options[:root] ||= "errors"
|
332
|
+
options[:indent] ||= 2
|
333
|
+
options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
|
334
|
+
options[:builder].instruct! unless options.delete(:skip_instruct)
|
335
|
+
options[:builder].errors do |e|
|
336
|
+
# The @errors instance variable is a Hash inside the Errors class
|
337
|
+
@errors.each do |attr, msg|
|
338
|
+
next if msg.nil?
|
339
|
+
if attr == "base"
|
340
|
+
options[:builder].error("message", msg.to_s)
|
341
|
+
else
|
342
|
+
options[:builder].error("field" => attr.camelcase(:lower), "message" => msg.to_s)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
# serialize errors to JSON
|
349
|
+
def to_json(options = {})
|
350
|
+
"{#{'errors'.inspect}:#{to_json_original(options)}}"
|
351
|
+
end
|
352
|
+
|
353
|
+
# serialize errors to AMF
|
354
|
+
def to_amf(options = {})
|
355
|
+
options[:amf_version] = 3
|
356
|
+
options[:serializer] ||= RestfulX::AMF::RxAMFSerializer.new
|
357
|
+
options[:serializer].serialize_errors(Hash[*@errors.to_a.flatten]).to_s
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|