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.
Files changed (52) hide show
  1. data/README.rdoc +3 -3
  2. data/Rakefile +17 -2
  3. data/VERSION.yml +2 -3
  4. data/bin/rx-gen +1 -1
  5. data/ext/restfulx/ext/amf/serializer/extconf.rb +3 -0
  6. data/lib/restfulx.rb +43 -81
  7. data/lib/restfulx/active_record_tasks.rb +8 -6
  8. data/lib/restfulx/active_record_uuid_helper.rb +9 -10
  9. data/lib/restfulx/amf.rb +14 -0
  10. data/lib/restfulx/amf/class_mapping.rb +106 -0
  11. data/lib/restfulx/amf/ext.rb +11 -0
  12. data/lib/restfulx/amf/ext/serializer.bundle +0 -0
  13. data/lib/restfulx/amf/pure.rb +11 -0
  14. data/lib/restfulx/amf/pure/io_helpers.rb +52 -0
  15. data/lib/restfulx/amf/pure/serializer.rb +304 -0
  16. data/lib/restfulx/configuration.rb +16 -12
  17. data/lib/restfulx/{rails/recipes.rb → recipes.rb} +1 -2
  18. data/lib/restfulx/rx_action_controller.rb +34 -0
  19. data/lib/restfulx/rx_active_record.rb +360 -0
  20. data/lib/restfulx/rx_active_support.rb +139 -0
  21. data/lib/restfulx/{datamapper_foo.rb → rx_datamapper.rb} +0 -2
  22. data/lib/restfulx/{rails/schema_to_yaml.rb → schema_to_rx_yaml.rb} +88 -5
  23. data/lib/restfulx/swf_helper.rb +61 -0
  24. data/lib/restfulx/tasks.rb +5 -3
  25. data/rails_generators/rx_config/rx_config_generator.rb +2 -2
  26. data/rails_generators/rx_config/templates/mainapp.mxml +1 -1
  27. data/rails_generators/rx_config/templates/restfulx.erb +5 -3
  28. data/rails_generators/rx_main_app/rx_main_app_generator.rb +2 -2
  29. data/rails_generators/rx_scaffold/rx_scaffold_generator.rb +7 -7
  30. data/rails_generators/rx_yaml_scaffold/rx_yaml_scaffold_generator.rb +5 -3
  31. data/rxgen_generators/rx_config/rx_config_generator.rb +1 -1
  32. data/test/rails/fixtures/locations.yml +5 -6
  33. data/test/rails/fixtures/notes.yml +5 -15
  34. data/test/rails/fixtures/projects.yml +7 -23
  35. data/test/rails/fixtures/tasks.yml +17 -43
  36. data/test/rails/fixtures/users.yml +7 -11
  37. data/test/rails/helpers/functional_test_helper.rb +5 -4
  38. data/test/rails/helpers/performance_test_helper.rb +5 -0
  39. data/test/rails/helpers/test_helper.rb +27 -0
  40. data/test/rails/helpers/unit_test_helper.rb +3 -15
  41. data/test/rails/test_active_foo.rb +21 -25
  42. data/test/rails/{test_rails_integration_functional.rb → test_notes_controller_functional.rb} +5 -5
  43. data/test/rails/test_serialiazation_performance.rb +32 -0
  44. data/test/rails/test_to_amf.rb +30 -0
  45. data/test/rails/test_to_fxml.rb +18 -15
  46. data/test/rails/test_to_json.rb +2 -14
  47. metadata +30 -19
  48. data/lib/restfulx/active_foo.rb +0 -178
  49. data/lib/restfulx/rails/schema_to_yaml/extensions/enumerable.rb +0 -8
  50. data/lib/restfulx/rails/schema_to_yaml/settings/config.rb +0 -17
  51. data/lib/restfulx/rails/schema_to_yaml/settings/core.rb +0 -73
  52. 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
- # Interestingly enough there's no way to *just* upper-case or down-case first letter of a given
2
- # string. Ruby's own +capitalize+ actually downcases all the rest of the characters in the string
3
- # We patch the class to add our own implementation.
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
- Dir[File.dirname(__FILE__) + "/rails/schema_to_yaml/extensions/*.rb"].each do |f|
10
- require f
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, Merb
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 : defined?(Merb) ? Merb.root : File.expand_path(".")
35
+ APP_ROOT = defined?(RAILS_ROOT) ? RAILS_ROOT : File.expand_path(".")
32
36
 
33
- RxSettings = SchemaToYaml::Settings::Core
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/rails/recipes'
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