restfulx 1.2.5 → 1.3.0
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 +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
|