rubyamf-ouvrages 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,215 @@
1
+ module RubyAMF
2
+ # Simply include in your ruby object to enable advanced serialization features
3
+ # like an in-model mapping API, customizable initialization after
4
+ # deserialization, scoped property configuration for serialization, and several
5
+ # other things. See RubyAMF::Model::ClassMethods for details of in-model mapping
6
+ # API.
7
+ #
8
+ # Example:
9
+ #
10
+ # class SerializableObject
11
+ # include RubyAMF::Model
12
+ #
13
+ # as_class "com.rubyamf.ASObject"
14
+ # map_amf :only => "prop_a"
15
+ #
16
+ # attr_accessor :prop_a, :prop_b
17
+ # end
18
+ #
19
+ # == Integration
20
+ #
21
+ # If the object you include RubyAMF::Model into implements <tt>attributes</tt>
22
+ # and <tt>attributes=</tt>, those two methods will be automatically used to
23
+ # determine serializable properties and to set them after deserialization. If
24
+ # you do not implement those methods, attributes will be guessed by going through
25
+ # all methods that don't take arguments, and attribute setters will be used
26
+ # rather than <tt>attributes=</tt>.
27
+ #
28
+ # For most ORMs, the provided <tt>rubyamf_init</tt>, <tt>rubyamf_hash</tt>, and
29
+ # <tt>rubyamf_retrieve_association</tt> should work correctly. However, they
30
+ # can be overridden to provide custom behavior if the default has issues with
31
+ # the ORM you are using. See RubyAMF::Rails::Model for an example of ORM-specific
32
+ # customization.
33
+ module Model
34
+ def self.included base #:nodoc:
35
+ base.send :extend, ClassMethods
36
+ end
37
+
38
+ # In-model mapping configuration methods
39
+ module ClassMethods
40
+ # Specify the actionscript class name that this class maps to.
41
+ #
42
+ # Example:
43
+ #
44
+ # class SerializableObject
45
+ # include RubyAMF::Model
46
+ # as_class "com.rubyamf.ASObject"
47
+ # end
48
+ def as_class class_name
49
+ @as_class = class_name.to_s
50
+ RubyAMF::ClassMapper.mappings.map :as => @as_class, :ruby => self.name
51
+ end
52
+ alias :actionscript_class :as_class
53
+ alias :flash_class :as_class
54
+ alias :amf_class :as_class
55
+
56
+ # Define a parameter mapping for the default scope or a given scope. If the
57
+ # first parameter is a hash, it looks for a <tt>:default_scope</tt> key to
58
+ # set the default scope and scope the given configuration, and parses the
59
+ # other keys like <tt>serializable_hash</tt> does. If the first argument
60
+ # is a symbol, that symbol is assumed to be the scope for the given
61
+ # configuration. Just like <tt>serializable_hash</tt>, it supports
62
+ # <tt>:except</tt>, <tt>:only</tt>, <tt>:methods</tt>, and <tt>:include</tt>
63
+ # for relations. It also has an <tt>:ignore_fields</tt> configuration for
64
+ # skipping certain fields during deserialization if the actionscript object
65
+ # contains extra fields or to protect yourself from modification of
66
+ # protected properties. <tt>:ignore_fields</tt> must be defined on the
67
+ # default scope, or it will be ignored.
68
+ #
69
+ # Example:
70
+ #
71
+ # class SerializableObject
72
+ # include RubyAMF::Model
73
+ # as_class "com.rubyamf.ASObject"
74
+ # map_amf :only => "prop_a"
75
+ # map_amf :testing, :only => "prop_b"
76
+ # map_amf :default_scope => :asdf, :only => "prop_c", :ignore_fields => ["password", "password_confirm"]
77
+ # end
78
+ def map_amf scope_or_options=nil, options=nil
79
+ # Make sure they've already called as_class first
80
+ raise "Must define as_class first" unless @as_class
81
+
82
+ # Format parameters to pass to RubyAMF::MappingSet#map
83
+ if options
84
+ options[:scope] = scope_or_options
85
+ else
86
+ options = scope_or_options
87
+ end
88
+ options[:as] = @as_class
89
+ options[:ruby] = self.name
90
+ RubyAMF::ClassMapper.mappings.map options
91
+ end
92
+ end
93
+
94
+ # Populates the object after deserialization. By default it calls initialize,
95
+ # calls setters for keys not in attributes, and calls <tt>attributes=</tt> for
96
+ # the remaining properties if it's implemented. Override if necessary to
97
+ # support your ORM.
98
+ def rubyamf_init props, dynamic_props = nil
99
+ initialize # warhammerkid: Call initialize by default - good decision?
100
+
101
+ props.merge!(dynamic_props) if dynamic_props
102
+ if respond_to?(:attributes=)
103
+ attrs = self.attributes
104
+ rubyamf_set_non_attributes props, attrs
105
+ self.attributes = props # Populate using attributes setter
106
+ else
107
+ rubyamf_set_non_attributes props, {} # Calls setters for all props it finds setters for
108
+ end
109
+ end
110
+
111
+ # Calls setters for all keys in the given hash not found in the base attributes
112
+ # hash and deletes those keys from the hash. Performs some simple checks on
113
+ # the keys to hopefully prevent more private setters from being called.
114
+ def rubyamf_set_non_attributes attrs, base_attrs
115
+ not_attributes = attrs.keys.select {|k| !base_attrs.include?(k)}
116
+ not_attributes.each do |k|
117
+ setter = "#{k}="
118
+ next if setter !~ /^[a-z][A-Za-z0-9_]+=/ # Make sure setter doesn't start with capital, dollar, or underscore to make this safer
119
+ if respond_to?(setter)
120
+ send(setter, attrs.delete(k))
121
+ else
122
+ RubyAMF.logger.warn("RubyAMF: Cannot call setter for non-attribute on #{self.class.name}: #{k}")
123
+ end
124
+ end
125
+ end
126
+
127
+ # Like serializable_hash, rubyamf_hash returns a hash for serialization
128
+ # calculated from the given options. Supported options are <tt>:only</tt>,
129
+ # <tt>:except</tt>, <tt>:methods</tt>, and <tt>:include</tt>. This method
130
+ # is automatically called by RubyAMF::ClassMapping on serialization with
131
+ # the pre-configured options for whatever the current scope is.
132
+ def rubyamf_hash options=nil
133
+ # Process options
134
+ options ||= {}
135
+ only = Array.wrap(options[:only]).map(&:to_s)
136
+ except = Array.wrap(options[:except]).map(&:to_s)
137
+ method_names = []
138
+ Array.wrap(options[:methods]).each do |name|
139
+ method_names << name.to_s if respond_to?(name)
140
+ end
141
+
142
+ # Get list of attributes
143
+ if respond_to?(:attributes)
144
+ attrs = send(:attributes)
145
+ else
146
+ attrs = {}
147
+ ignored_props = Object.new.public_methods
148
+ (self.public_methods - ignored_props).each do |method_name|
149
+ # Add them to the attrs hash if they take no arguments
150
+ method_def = self.method(method_name)
151
+ attrs[method_name.to_s] = send(method_name) if method_def.arity == 0
152
+ end
153
+ end
154
+ attribute_names = attrs.keys.sort
155
+ if only.any?
156
+ attribute_names &= only
157
+ elsif except.any?
158
+ attribute_names -= except
159
+ end
160
+
161
+ # Build hash from attributes and methods
162
+ hash = {}
163
+ attribute_names.each {|name| hash[name] = attrs[name]}
164
+ method_names.each {|name| hash[name] = send(name)}
165
+
166
+ # Add associations using ActiveRecord::Serialization style options
167
+ # processing
168
+ if include_associations = options.delete(:include)
169
+ # Process options
170
+ base_only_or_except = {:except => options[:except], :only => options[:only]}
171
+ include_has_options = include_associations.is_a?(Hash)
172
+ associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
173
+
174
+ # Call to_amf on each object in the association, passing processed options
175
+ associations.each do |association|
176
+ records = rubyamf_retrieve_association(association)
177
+ if records
178
+ opts = include_has_options ? include_associations[association] : nil
179
+ if records.is_a?(Enumerable)
180
+ hash[association.to_s] = records.map {|r| opts.nil? ? r : r.to_amf(opts)}
181
+ else
182
+ hash[association.to_s] = opts.nil? ? records : records.to_amf(opts)
183
+ end
184
+ end
185
+ end
186
+
187
+ options[:include] = include_associations
188
+ end
189
+
190
+ hash
191
+ end
192
+
193
+ # Override if necessary to support your ORM's system of retrieving objects
194
+ # in an association.
195
+ def rubyamf_retrieve_association association
196
+ # Naive implementation that should work for most cases without
197
+ # need for overriding
198
+ send(association)
199
+ end
200
+
201
+ # Stores the given options and object in an IntermediateObject so that the
202
+ # default serialization mapping options can be overriden if necessary.
203
+ def to_amf options=nil
204
+ RubyAMF::IntermediateObject.new(self, options)
205
+ end
206
+ end
207
+ end
208
+
209
+ class Array
210
+ # Returns an array of RubyAMF::IntermediateObject objects created from calling
211
+ # <tt>to_amf</tt> on each object in the array with the given options.
212
+ def to_amf options=nil
213
+ self.map {|o| o.to_amf(options)}
214
+ end
215
+ end
@@ -0,0 +1,23 @@
1
+ module RubyAMF::Rails
2
+ # Rails controller extensions to access AMF information.
3
+ module Controller
4
+ protected
5
+ # Contains the parameters hash with named properties if mapped
6
+ attr_reader :rubyamf_params
7
+
8
+ # Contains the credentials hash from RubyAMF::Envelope#credentials
9
+ attr_reader :credentials
10
+
11
+ # Used internally by RequestProcessor
12
+ attr_reader :amf_response
13
+ # Used internally by RequestProcessor
14
+ attr_reader :mapping_scope
15
+
16
+ # Returns whether or not the request is an AMF request
17
+ def is_amf?
18
+ @is_amf == true
19
+ end
20
+ alias_method :is_amf, :is_amf?
21
+ alias_method :is_rubyamf, :is_amf?
22
+ end
23
+ end
@@ -0,0 +1,151 @@
1
+ module RubyAMF::Rails
2
+ # Rails-specific implementation of <tt>RubyAMF::Model</tt> APIs
3
+ module Model
4
+ include RubyAMF::Model
5
+
6
+ def self.included base #:nodoc:
7
+ base.send :extend, ClassMethods
8
+ end
9
+
10
+ def rubyamf_init props, dynamic_props = nil
11
+ # Convert props and dynamic props to hash with string keys for attributes
12
+ attrs = {}
13
+ props.each {|k,v| attrs[k.to_s] = v}
14
+ dynamic_props.each {|k,v| attrs[k.to_s] = v} unless dynamic_props.nil?
15
+
16
+ # Is it a new record or existing? - support composite primary keys just in case
17
+ is_new = true
18
+ pk = Array.wrap(self.class.primary_key).map &:to_s
19
+ if pk.length > 1 || pk[0] != 'id'
20
+ unless pk.any? {|k| empty_key?(attrs, k)}
21
+ search = pk.map {|k| attrs[k]}
22
+ search = search.first if search.length == 1
23
+ is_new = !self.class.exists?(search) # Look it up in the database to make sure because it may be a string PK (or composite PK)
24
+ end
25
+ else
26
+ is_new = false unless empty_key?(attrs, 'id')
27
+ end
28
+
29
+ # Get base attributes hash for later use
30
+ base_attrs = self.send(:attributes_from_column_definition)
31
+
32
+ if is_new
33
+ # Call initialize to populate everything for a new object
34
+ self.send(:initialize)
35
+
36
+ # Populate part of given primary key
37
+ pk.each do |k|
38
+ self.send("#{k}=", attrs[k]) unless empty_key?(attrs, k)
39
+ end
40
+ else
41
+ # Initialize with defaults so that changed properties will be marked dirty
42
+ pk_attrs = pk.inject({}) {|h, k| h[k] = attrs[k]; h}
43
+ base_attrs.merge!(pk_attrs)
44
+
45
+ if ::ActiveRecord::VERSION::MAJOR == 2
46
+ # if rails 2, use code from ActiveRecord::Base#instantiate (just copied it over)
47
+ object = self
48
+ object.instance_variable_set("@attributes", base_attrs)
49
+ object.instance_variable_set("@attributes_cache", Hash.new)
50
+
51
+ if object.respond_to_without_attributes?(:after_find)
52
+ object.send(:callback, :after_find)
53
+ end
54
+
55
+ if object.respond_to_without_attributes?(:after_initialize)
56
+ object.send(:callback, :after_initialize)
57
+ end
58
+ else
59
+ # if rails 3, use init_with('attributes' => attributes_hash)
60
+ self.init_with('attributes' => base_attrs)
61
+ end
62
+ end
63
+
64
+ # Delete pk from attrs as they have already been set
65
+ pk.each {|k| attrs.delete(k)}
66
+
67
+ # Set associations
68
+ (reflections = self.class.reflections).keys.each do |k|
69
+ if value = attrs.delete(k.to_s)
70
+ reflection_macro = reflections[k].macro
71
+ if ::ActiveRecord::VERSION::STRING < "3.1"
72
+ case reflection_macro
73
+ when :has_one
74
+ self.send("set_#{k}_target", value)
75
+ when :belongs_to
76
+ self.send("#{k}=", value)
77
+ when :has_many, :has_and_belongs_to_many
78
+ self.send("#{k}").target = value
79
+ when :composed_of
80
+ self.send("#{k}=", value) # this sets the attributes to the corresponding values
81
+ end
82
+ else
83
+ case reflection_macro
84
+ when :has_many, :has_and_belongs_to_many, :has_one
85
+ self.association(k).target = value
86
+ when :belongs_to, :composed_of
87
+ self.send("#{k}=", value) # this sets the attributes to the corresponding values
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ # Set attributes
94
+ rubyamf_set_non_attributes attrs, base_attrs
95
+ self.send(:attributes=, attrs)
96
+
97
+ self
98
+ end
99
+
100
+ def rubyamf_hash options=nil
101
+ return super(options) unless RubyAMF.configuration.check_for_associations
102
+
103
+ options ||= {}
104
+
105
+ # Iterate through assocations and check to see if they are loaded
106
+ auto_include = []
107
+ self.class.reflect_on_all_associations.each do |reflection|
108
+ next if reflection.macro == :belongs_to # Skip belongs_to to prevent recursion
109
+ is_loaded = if self.respond_to?(:association)
110
+ # Rails 3.1
111
+ self.association(reflection.name).loaded?
112
+ elsif self.respond_to?("loaded_#{reflection.name}?")
113
+ # Rails 2.3 and 3.0 for some types
114
+ self.send("loaded_#{reflection.name}?")
115
+ else
116
+ # Rails 2.3 and 3.0 for some types
117
+ self.send(reflection.name).loaded?
118
+ end
119
+ auto_include << reflection.name if is_loaded
120
+ end
121
+
122
+ # Add these assocations to the :include if they are not already there
123
+ if include_associations = options.delete(:include)
124
+ if include_associations.is_a?(Hash)
125
+ auto_include.each {|assoc| include_associations[assoc] ||= {}}
126
+ else
127
+ include_associations = Array.wrap(include_associations) | auto_include
128
+ end
129
+ options[:include] = include_associations
130
+ else
131
+ options[:include] = auto_include if auto_include.length > 0
132
+ end
133
+
134
+ super(options)
135
+ end
136
+
137
+ def rubyamf_retrieve_association association
138
+ case self.class.reflect_on_association(association).macro
139
+ when :has_many, :has_and_belongs_to_many
140
+ send(association).to_a
141
+ when :has_one, :belongs_to
142
+ send(association)
143
+ end
144
+ end
145
+
146
+ def empty_key? attrs, key
147
+ return true unless attrs.key?(key)
148
+ attrs[key] == 0 || attrs[key].nil?
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,73 @@
1
+ require 'rubyamf/rails/controller'
2
+ require 'rubyamf/rails/model'
3
+ require 'rubyamf/rails/request_processor'
4
+ require 'rubyamf/rails/routing'
5
+ require 'rubyamf/rails/time'
6
+ require 'action_controller'
7
+
8
+ # Hook up MIME type
9
+ Mime::Type.register RubyAMF::MIME_TYPE, :amf
10
+
11
+ # Hook routing into routes
12
+ ActionController::Routing::RouteSet::Mapper.send(:include, RubyAMF::Rails::Routing)
13
+
14
+ # Add some utility methods to ActionController
15
+ ActionController::Base.send(:include, RubyAMF::Rails::Controller)
16
+
17
+ # Hook up ActiveRecord Model extensions
18
+ if defined?(ActiveRecord)
19
+ ActiveRecord::Base.send(:include, RubyAMF::Rails::Model)
20
+ end
21
+
22
+ # Hook up rendering and hack in our custom error handling
23
+ module ActionController #:nodoc:
24
+ class Base #:nodoc:
25
+ def render_with_amf(options = nil, extra_options ={}, &block)
26
+ if options && options.is_a?(Hash) && options.has_key?(:amf)
27
+ @performed_render = true
28
+ @amf_response = options[:amf]
29
+ @mapping_scope = options[:class_mapping_scope] || options[:mapping_scope] || nil
30
+ else
31
+ render_without_amf(options, extra_options, &block)
32
+ end
33
+ end
34
+ alias_method_chain :render, :amf
35
+ end
36
+
37
+ module Rescue #:nodoc:
38
+ protected
39
+ # Re-raise the exception so that RubyAMF gets it if it's an AMF call. Otherwise
40
+ # RubyAMF doesn't know that the call failed and a "success" response is sent.
41
+ def rescue_action_with_amf(exception)
42
+ raise exception if respond_to?(:is_amf?) && is_amf?
43
+ rescue_action_without_amf exception
44
+ end
45
+ alias_method_chain :rescue_action, :amf
46
+ end
47
+ end
48
+
49
+ # Add middleware
50
+ add_middleware = Proc.new {
51
+ m = Rails.configuration.middleware
52
+ m.use RubyAMF::RequestParser
53
+ m.use RubyAMF::Rails::RequestProcessor
54
+ }
55
+ if Rails.initialized?
56
+ add_middleware.call
57
+ else
58
+ Rails.configuration.after_initialize &add_middleware
59
+ end
60
+
61
+ module RubyAMF
62
+ def self.configure
63
+ # Load legacy config if they have one
64
+ begin
65
+ RubyAMF.configuration.load_legacy
66
+ rescue
67
+ RubyAMF.logger.info "RubyAMF: Could not find legacy config file to load"
68
+ end
69
+
70
+ yield configuration
71
+ bootstrap
72
+ end
73
+ end