restfulx 1.2.5 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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