├── CHANGELOG ├── LICENSE ├── README.rdoc ├── app ├── actions.rb ├── amf.rb ├── configuration.rb ├── fault_object.rb ├── filters.rb ├── mime_type.rb ├── rails_gateway.rb └── request_store.rb ├── exception ├── exception_handler.rb └── rubyamf_exception.rb ├── generators ├── rubyamf_mappings │ └── rubyamf_mappings_generator.rb └── rubyamf_scaffold │ ├── USAGE │ ├── rubyamf_scaffold_generator.rb │ └── templates │ ├── controller.rb │ └── helper.rb ├── init.rb ├── install.rb ├── io ├── amf_deserializer.rb ├── amf_serializer.rb └── read_write.rb ├── rails_installer_files ├── crossdomain.xml ├── rubyamf_config.rb ├── rubyamf_controller.rb └── rubyamf_helper.rb └── util ├── action_controller.rb ├── active_record.rb ├── string.rb └── vo_helper.rb /CHANGELOG: -------------------------------------------------------------------------------- 1 | --changes 1.6.6 2 | * Applied patch from fosrias fixing issue 125, which: 3 | - Fixed ActiveRecord deserialization with partial_updates enabled so that existing ActiveRecord attributes are flagged to update. 4 | - Added ability to use custom primary keys on ActiveRecords, including composite primary keys. 5 | * Ruby 1.9 compatibility 6 | 7 | --changes 1.6.5 8 | * Fixed issue #107, method mapping for associations 9 | * fixed issue where associations of type :has_and_belongs_to_many where not being detected 10 | * fixed issue where associations in cyclic object graphs were deserialized to Hashes instead of ActiveRecords. 11 | * fixed issue when specifying actionscript object properties/values to map to active record methods/parameters using "ClassMappings.register(:methods =>". Previously this worked when sending active record objects from rails to flex, but not when sending actionscript objects from flex to rails. 12 | --changes 1.6.4 13 | * FaultObject now takes an optional second argument, which is any object you want to send back with the fault. This object is accessible on the Flex side in the returned FaultEvent - faultEvent.message['extendedData'] as a typed object. 14 | * changed serializer to store and look up object references with a hash instead of an array. Nice speed improvement to serialization 15 | * fixed issue #63 - rendering in filter chain would not halt the chain. [mihai.tarnovan] 16 | * added a filter to capture amf input. experemental [fm. Aaron Smith] 17 | 18 | --change 1.6.3 19 | * applied patch from trisweb fixing issue #58 - ArrayCollection not serialized correctly in AMF3 20 | 21 | --change 1.6.2 22 | * fixed backwards compatibility issue with mime type fixes 23 | 24 | --change 1.6.1 25 | * fixed problems running with Rails 2.1 26 | * fixed issue 62 - false is rendered as null 27 | 28 | --changes 1.6 29 | Changes 30 | * documented hash_key_access for ClassMappings. 31 | * new install process - now updates routes.rb with the rubyamf controller route. Also puts the mime type registration in the config/initializers directory if there is one, to conform better to Rails 2 convention. One day rubyamf_config.rb may live there too. 32 | * added ability to specify method results from active records to be set on actionscript object properties, just like attributes. 33 | * added a generator, rubyamf_scaffold, to generate a RubyAMF scaffold - a model, fixtures, tests, and a RubyAMF specific controller 34 | * added a "generator", rubyamf_mappings, to assist creating class mappings. Writes mappings for all models to the command line; does not modify any files. 35 | 36 | Many Bug fixes, e.g.: 37 | * fixed annoying warning where rubyamf was sending attributesCache or attributes_cache as an attribute of all objects 38 | * fixed issue saving new active records with timestamps where timestamp variables are ignored 39 | * fixed a bug where rendering a FaultEvent would throw another exception since result has no clientId in that case 40 | * reimplemented object references in the serializer - incoming object graphs work now 41 | * fixed issue 48, module error when passing hash to controller with scaffolding on. Also added related scaffolding improvement. 42 | * fixed issue with Rails 2 43 | * fixed issue 36, error stopping migrations from working 44 | * fixed issues with authenticaiton credentials and the ClassMappings.hash_key_access. Athentication was always :symbols, where it should be taking hash_key_access into account and give you what you want.. 45 | * fixed some small but big bugs in the serializer. in write_amf3. was testing (if !value). but that is wrong, as it should specifically be if value == nil. otherwise when false comes through the serializer, it will be null in flex 46 | 47 | 48 | --changes 1.5 49 | #re-factored 80% of the codebase 50 | #Added ClassMappings.ignore_fields for globally ignoring incoming properties on objects 51 | #Added ClassMappings.translate_case 52 | #Added ClassMappings.force_active_record_ids for forcing id's to be included in incoming hash's, even if you don't put it in the :attributes of a class mapping. 53 | #Added ClassMappings.assume_types. For assuming typed object transfers. This enables typed transfers without defining ClassMappings 54 | #Added ClassMappings.use_ruby_date_time. 55 | #Added ClassMappings.use_array_collection 56 | #Added ClassMappings.check_for_associations 57 | #Added ParameterMappings.always_add_to_params 58 | #Added ParameterMappings.scaffolding 59 | #Added :attributes,:associations,:ignore_fields,:scope into ClassMappings.register options 60 | #Fixed referencing bug with deserializer 61 | #renamed ValueOBjects to ClassMappings 62 | 63 | --changes 1.3.4 64 | #Killed RubyAMF Lite. The last version of RubyAMF Lite can be found in svn at /branches/rubyamflite_9**. 65 | #single mysql records are ALWAYS returned as an object, not wrapped in an array anymore. 66 | #single active records are ALWAYS returned as an object. No use for as_single! anymore. Note there is a deprecation warning if you use it. 67 | #fixed small bug with incoming VO names. EI this wouldn't work: ValueObjects.regier({:incoming=>'vo.SectionVO',:map_to=>'Section',:outgoing=>'vo.SectionVO'}). Note that the package always get's truncated in situation like this. So this would be in the params hash as params[:sectionvo]. 68 | #added case translations for value objects. You can translate incoming lowerCamelCase to snake_case, and outgoing snake_case to lowerCamelCase. See configuration for value objects. (ValueObjects.translate_case) 69 | #added setRemoteCredentials support. 70 | #added "credentials" method to ActionController::Base. Anytime you set credentials with "setRemoteCredentials" or "setCredentials" in Flex, this method will return a hash with {username:'username',password:'mypassword'}. 71 | #fixed issue with returned value objects that had :belongs_to association, it now correctly returns an object, not wrapped in any array anymore. 72 | #changed read_write so it doesn't crash in production when setCredentials is set. When in production mode the exception message was different in different modes. 73 | #fixed debug player issues. It correlates to the rails modes. An error that was happening while reading integers was raising errors, but the message was different per mode, so a regex I was using to sniff it was not catching it in production mode. 74 | #added :through support, note that in order to get your data back to Flex, you must use eager loading (:include) 75 | #added magic_field ignorance. Incoming VO's magic field members are ignored. Currently magic fields are "created_at","created_on","updated_at","updated_on" 76 | #Rails no longer complains about missing views for RubyAMF requests. 77 | #added ParamMapping for mapping incoming remoting parameters into rails' params hash. 78 | #Rewrote active record adapter. Everything is done with the usual recursion, but also with reflections. Allows a 1:1 VO to AR. 79 | #Squeezed every possible millisecond out of the active record adapter. It's slightly faster, were talking 10's - 100's of milleseconds. The speed increase varies depending on the complexity of your result. 80 | #Fixed problem with VO's that aren't active records. 81 | #Added ValueObjects.vo_path to vo_config. See the documentation in that config file about what it is. 82 | #Changed how render AMF decorates ActionController::Base. This new way is right for rails. It has suppressed any errors with rendering / layouts 83 | 84 | --changes 1.3.3b 85 | #updated rails parameter mapping for sending generic objects from flash/flex 86 | 87 | --changes 1.3.3a 88 | #gook out references to amf_id in serializer 89 | #reverted a condition in the util/action_controller that was breaking new/create/edit/update action controller methods, after a remoting call had been made 90 | 91 | --changes 1.3.3 92 | #updated the ActiveRecord::Base.update_nil_associations method. Added the 3rd open struct param. Which takes the original incoming ValueObject, and set's any similar associated model properties to the same values that are being assigned to the hash association properties. This was causing an issue with some models, not all. Not sure what the qualifications were for this being an issue, but it's fixed. 93 | #fixed incoming AMF stream object references when reading Objects. 94 | #Fixed outgoing AMF stream object references. 95 | 96 | --changes 97 | 1.3.2b 98 | #Updated VoUtil again for incoming VO's. If any properties are nil, they're now deleted out of the open struct so that the resulting update hash for active record doesn't put in NULL into the database. 99 | 100 | --changes 1.3.2a 101 | #put a fix in VoUtil to handle incoming "update VO", if the created_at or updated_at is nil it's not updated. 102 | 103 | --changes 1.3.2 104 | #added AMF0 / AMF3 NaN support ("NaN" constant in ruby) 105 | #added AMF3 Infinity support ("Infinity" constant in ruby) 106 | #added AMF3 -Infinity support ("NInfinity" constant in ruby) 107 | #added BigDecimal support 108 | #added isNaN Top Level function (has alias of isNaN?) 109 | #added isFinite Top Level function (has alias of isFinite?) 110 | #added some better logic for incoming VO -> params[:] mapping. Now the first incoming VO properties get merged into the params hash. Thus params[:id] will work. (enabling update,edit,destroy scaffolding methods) 111 | #put a rescue_action in the rubyamf_controller. This needs to be piped back to flash yet. This is the only case where exceptions can't get returned to the player. 112 | #took out ValueObjects.rails_parameter_mapping_type. It is always 'update_hash', not really needed anymore. 113 | #changed recordset_format default to be FL9 114 | #Handled bug with Date/Time/DateTime not writing as correct reference to other Date's in the AMF stream, (if the same date was being returned, it would write a reference, but writing references to dates does not work correctly in the player) 115 | #fixed final friggin 'id' property problems. All id attributes come through correctly now, (both ways). 116 | 117 | 118 | --changes 1.3.1 119 | #added Adapters.deep_adaptations See an adapters_config file for more information. 120 | 121 | --changes 1.3 FINAL 122 | #added more logic for incoming nil/undefined/empty associated active_record model data. Was a few cases when it wouldn't work correctly (see util/active_record#update_nil_associations) 123 | #add util/active_record#update_nans which updates any hash values that are NaN, turns them into nil 124 | #updated AS3FaultObject, no regex was needed in it anymore 125 | 126 | --changes 1.3 revision 756 127 | #Fixed bug when taking incoming VO's and mapping them to AR. I wasn't dealing with an :incoming class path (such as vo.Task). Now I strip off the class path and just use the class. (Task.downcase). This was causing the VO to be put in params['vo.Task']. Dah. 128 | #Fixed slight problem with incoming VO's and nil associated model data. If an incoming associated model was nil it was automatically becoming an Array. It's handled a little more graceful with ActiveRecord reflections to find out what an incoming nil members real value should be (Array / nil). If :has_many, val == []. If :belongs_to, val == nil, if :has_and_belongs_to_many, val = [] 129 | #created_at and updated_at now work correctly according to ActiveRecord, because of the above bug fix. 130 | #took out puts in mysql_adapter 131 | #updated rails installer. It won't overwrite vo_config / adapter_config if it exists. 132 | 133 | --changes 1.3 RC2 revision 748 134 | #Added in FaultObject handling when sending back FaultObjects from Controller's, it had been stripped out when rewriting the rails plugin. 135 | #split vo / adapter configs into two files, now the configs live in rails/config/rubyamf. For LITE, the configs are now in rubyamf/services/config/ 136 | #added ValueObjects.rails_parameter_mapping_type, to specify what get's put into params[:[voclass]]. Read the vo_config file fore more information 137 | 138 | --changes 1.3 RC2 revision 742 139 | #changed service.is_amf / service.is_rubyamf to 'false' after the service call to rails controller 140 | #gzip works now, strange as I didn't change anything specifically for it 141 | 142 | --changes 1.3 RC2 143 | #fixed Rails production environment problem 144 | #full controller support 145 | #added crossdomain.xml file to rails installer, now copies crossdomain to rails/public. 146 | #fixed RemoteObject fault handler executions for AsyncTokens. 147 | #took out two return statements in BatchFilter, as it should continue through the batch process. 148 | #re-wrote the rails plugin again. Now has full controller support, and does not harm with production enviornment. 149 | #util/action_controller_run_target no longer valid, see util/active_record, and util/action_controller. 150 | #changed the "inner" ActionController requests HTTP_ACCEPT header to accept application/x-amf, to enable the respond_to method to work properly 151 | 152 | --changes 1.3 RC1 153 | #rewrote rubyamf_core/adapters/active_record_adapter.rb to be recursive. Now handles VO mapping, and associated models when using :include 154 | #rewrote rubyamf_core/app/rails_gateway.rb. It now extends the normal gateway and just changes out the actions 155 | #completed VO mappings 156 | #added a fix in core/io/read_write.rb for a case when Rails would truncate the last \000 byte from AMF data. That was in Rails' ActionPack gem ActionController -> raw_post_data_fix.rb 157 | #Added @is_rubyamf to controllers. 158 | #added VoUtil class 159 | #added "as_single!", "single!" and "single?" helper to active record. See wiki.rubyamf.org/wiki/show/ActiveRecordSingles for more information about this 160 | #added util/active_record.rb. This implementes the single! method and single? method (single? is used internally to RubyAMF) See wiki.rubyamf.org/wiki/show/ActiveRecordSingles for more information about this 161 | #added util/openstruct.rb. #This is a fix for Object#id problems 162 | #added util/object.rb #this is a fix for Object#id problems 163 | #updated the VoUtil class for ActiveRecord VO instantiation 164 | #added ApplicationInitializationAction. This is used for RubyAMF lite when using AR VO's. (AR needs to be connected before incoming VO's can be turned into AR's) 165 | #added capability to change the rubyamf configuration file path 166 | #added ApplictionInstanceInitAction for configuration processing, and pre-emptive ActiveRecord connecting for ActiveRecord Value Object handling 167 | #added getActiveRecordFromOpenStruct, for incoming VO usage. 168 | #changed all mappings variables to use the new ValueObjects class in configuration.rb 169 | #added makeHashFromOpenStruct, used in combination with getActiveRecordFromOpenStruct. 170 | #changed deserializer ValueObject mapping to include the new ActiveRecord Value Object mapping functionality 171 | #updated the active_record_connector helper. updated so I can use it in ApplicationInitInstanceAction. 172 | #rails installer now copies rubyamf_core/app/default_rails_config into rails/config/rubyamf_config.rb. 173 | --#added active record error handling, return an active record with errors and it sends back the first error in the errors hash as a string (EX: "login already taken") 174 | --#thread safety 175 | --#re-wrote rails plugin as to not break everything when in production environment 176 | 177 | --changes 1.2 178 | ####RAILS 179 | #rails installer (ruby script/plugin install svn://rubyforge.org/var/svn/rubyamf/tags/current/rubyamf) 180 | #support for respond_to/format.amf { render :amf => content }. You must add a mime type into railsapp/config/environment.rb (Mime::Type.register "application/x-amf", :amf) 181 | See the page http://wiki.rubyamf.org/wiki/show/AmfRest for more info 182 | #took out some puts statements 183 | 184 | --changes 1.1 185 | #FaultObjects cannot be "raised" from service methods, they must be returned. If raised, a custom error message tells you not to. 186 | #cleaned up some authentication code, less logic 187 | #the recordset_format header values are now 'fl8' and 'fl9'. fl.data.DataProvider and mx.remoting.RecordSet are still in there. But fl8 and fl9 make more sense. 188 | #added the -r commandline switch. If this is on services are reloaded when executed. 189 | #Fixed an issue with recordsets. Was another slight logic issue that decided to use flash 8 / 9 or flex 2 FDS recordsets. 190 | 191 | ####Rails 192 | #fixed sessions variable in controller classes 193 | #fixed cookies variable in controller classes 194 | 195 | --changes 1.0 final 196 | #####Rails Plugin 197 | #initial rails plugin 198 | #added rubyamf_controller.rb intro services. This is intended to be put in app/controllers as the RubyAMF gateway for Rails. 199 | #@is_amf available in any controller method 200 | #get_credentials() for authentication 201 | #parameter[] hash mapping, remoting params get into params[] 202 | #allow_after_filters 203 | #most filters work, all but 'around' filters 204 | ######RubyAMF App server 205 | #Fixed daemonizing bug for Mongrel 206 | #added authentication back in 207 | #added predefined_variables in rubyamf/services as documentation for available top level variables. 208 | 209 | 210 | --changes 0.9.8 211 | #Fix RecordSet with Flash 9 issue. Results sets were being returned to Flash 9 as mx.remoting.RecordSet. Now it's returned as fl.data.DataProvider. 212 | #Added custom recordset_format header support. You can change the recordset format by adding the appropriate header to the netconnection. EX: service.addHeader('recordset_format:fl.data.DataProvider',false,false); OR service.addHeader('recordset_format:mx.remoting.RecordSet',false,false); 213 | #Made sure the _explicitType member variable isn't being written in the actionscript object when serializing AF3 or AMF0 214 | #added db_error_logger helper class. Use for easily logging errors in services 215 | #added error_notification class. Use for sending emails when errors happen, Note this is just stub code for now 216 | 217 | 218 | --changes 0.9.7 219 | #Fixed more object/array issues with AMF0 and AMF3 220 | #Fixed AMF0 custom VO class mappings. VO class path: /rubyamf/services/vo (use Object.registerClass from Flash) 221 | #Changed AMF0 objects into OpenStructs to match AMF3 objects 222 | #Got rid of ASObject class, not needed anymore 223 | #Took out heavy delegation to Binary Reader/Writer mix-ins. Was generally about 3-5 method calls before any actual write/read operation was happening. Performance Performance Performance! Now there is one read_write mixin in the de/serializers. One method call to perform the operation. 224 | 225 | --changes 0.9.6 226 | #Fixed an issue where AMF3 was being sent, but it was being treated as Flex Messaging, should have been straight AMF3. 227 | #Fixed an issue with reading / writing mixed AMF3 arrays and objects. Sending arrays with mixed keys was breaking it. Sending "Objects" from Flash was breaking. 228 | 229 | --changes 0.9.5 230 | #mongrel http servlet now available 231 | #active_record_adapter (confirmed) 232 | #firebird_fireruby_adapter (untested) 233 | #hypersonc_adater (untested) 234 | #lafacadio adapter (untested but logic was from lafcadio developer) 235 | #lore_adapter (untested but logic is from lore developer) 236 | #mysql_adapter (confirmed) 237 | #object_graph_adapter (untested but logic was from OG developer) 238 | #oracle_oci8_adapter (untested) 239 | #postgres_adapter (untested) 240 | #ruby_dbi_adapter (untested) 241 | #sequel_adapter (untested but logic is from sequel developer) 242 | #sqlite_adapter (untested) 243 | #Refactored app/request_store 244 | #updated order of server meta info when starting lighttpd 245 | #updated some variable names in optparser to match flag names 246 | #took out all internal RubyAMF logging. 247 | #added custom faultObject helper (services/rubyamf/helpers/fault_object) 248 | #added active_record_connector helper module (services/rubyamf/helpers/active_record_connector) 249 | #Changed adapter implementation. Now each adapter you have in the settings is loaded and you can use the "use_adapter?" hook method - place logic in there that decides if the result qualifies to be "adapted" into whatever adapter type. You can copy the mysql_adapter or any other file in the adapters file. 250 | #Updated de/serializers for unit testing capabliities. Now you can call either read_raw or write_raw on AMFDeserializer / AMFSerializer respectivly. And this allows you to read/write "pieces" of data. This is only for unit testing the AMF IO capabilities. Two new methods are used internal for RubyAMF; rubyamf_read, and rubyamf_write. These are specifically for doing complete AMF request operations 251 | #took out sessions 252 | #took out authentication 253 | #code optimizing, lots of comments and fluff taken out 254 | 255 | --changes 0.9.1 256 | #Fixed bug when reading byte array's with AMF3. 257 | 258 | --changes 0.9.0a 259 | #changed services location outside of docroot for security reasons. Now public is for accessing gateways only. 260 | #Added a case for DateTime in serialization. 261 | #Fixed up AMF3 Date handling 262 | #AMF3 Dates are now passed to your service as a DateTime object. This is to keep the time as well 263 | #Date / Time / DateTime are now serializing correctly to AMF3 Date. Date truncates H:M:S, Time and DateTime keep a hold of the time as well 264 | #Updated AMF3 deserializing, now DateTime instead 265 | #Updated some startup logic to get rid of redundant STDOUT statements 266 | #Updated some commandline option comments 267 | #READEME - updated email 268 | #Changed license from MIT to GNU GPLv2 269 | 270 | ---changes 0.8.9 271 | #Updated AS3 recordset serialization to handle dates (from db) 272 | #Updated AS3 recordset serialization to handle nil (from db) 273 | #Updated ResultAdapterAction to handle single activeRecords, they were working in some cases. sometimes not.. Not sure how they were working as they shouldn't have been 274 | 275 | ---changes - 0.8.8 276 | #added s simple check for amf content vs a browser request.. (navigate to localhost:8024/gateway.rb) and see a message 277 | #Added some some html messages if browsed to the gateway location 278 | #updated LightTPD config to deny the services directory 279 | #updated the WEBrickServlet Document Root to point to /public/. 280 | #updated how each gateway checks for amf content VS a browser request. 281 | 282 | ---changes - 0.8.7 283 | #sessions are now off by defult, turn them on with -r commandline switch 284 | #@session instance variable is now injected into service methods 285 | #Added @session.inspect method. intended use is inside a service method, if you want to NetDebug.Trace(@session.inspect). It will return only the session data not the entire session object 286 | #@sessions_enabled is injected into the service class. Intended use is so you can avoid exceptions when sessions aren't enabled (if(@sessions_enabled)) etc. 287 | #fixed issue with session toggling, (was not initializing correctly) causing every request to make a new session 288 | #changed session persistence to be after any invocation. In case you set session vars in your service method, they need to be persistent. (only if sessions are enabled) 289 | #added silencer to startup, add test if lighttpd exists 290 | #NetDebug.Trace(msg) is gracefuly shut off when in amf3 mode, as it causes a 291 | #Sessions are gracefully shut off in amf3 mode. (if -r was used at startup) 292 | #took out any reference to 'DB' when talking about adapters, as any result can be adapted 293 | #shifted in the RequestStore.SERVICE_PATH to $:. When using require in a service, start from where the top of the declared service directory 294 | #minor benchmarking and profiling, this things pretty fast. 295 | #Added RUBYAMF_SERVICES global var. This is for services when using the require statement. If you need to require anything of your own, use this is the prefix, then build from there. As usually you'll need to require rubygems. But rubygems screws with the load path. So chances are everything will error out if you don't use this new variable. 296 | 297 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007 Aaron Smith (aaron@rubyamf.org) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | There is one exception to the above MIT license. WebORB may not use this code 11 | base in any of their releases of WebORB for RoR. 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = RubyAMF: Flash and Flex Remoting for Ruby on Rails 2 | 3 | RubyAMF is an open source flash remoting gateway for Ruby on Rails. 4 | 5 | == Installation 6 | 7 | $ ruby script/plugin install git://github.com/victorcoder/rubyamf_plugin.git 8 | 9 | Rails 3: 10 | 11 | $ rails plugin install git://github.com/victorcoder/rubyamf_plugin.git 12 | 13 | Confirm correct installation and routing by browsing to: 14 | 15 | http://localhost:[port]/rubyamf/gateway 16 | 17 | You will see the rubyamf logo on a black background. 18 | 19 | == Configuration 20 | 21 | Please read the documentation in rubyamf_config.rb. That file gets installed to config/rubyamf_config.rb when you use the rails installer. 22 | 23 | == Parameter mappings 24 | 25 | Here's how parameter mappings behave by default: 26 | 27 | - Every remoting parameter is available in the order it came - in the "params" hash. So if you sent over 2 parameters in your remoting call. You could access them like: params[0], and params[1]. 28 | 29 | - You can toggle this behavior, RubyAMF has a property called "rubyamf_params". This is useful if you don't want every parameter coming into the "params" hash. Set "ParameterMappings.always_add_to_params" to false. Once set to false - parameters by index will only be put into "rubyamf_params" and not "params" 30 | 31 | Here's what happens when you go beyond that and setup some custom ParameterMappings: 32 | 33 | - When you setup ParameterMappings. They get put into the "params" hash, as well as the "rubyamf_params" hash for consistency. 34 | 35 | How do I get data deeper in the object than just referencing the index it came in? 36 | 37 | Because RubyAMF turns dynamic objects into hashes, you can use hash accessor like logic to get deeper into a parameter. Here's a quick EX: 38 | 39 | ParameterMappings.register({:controller => :MyController, :action => :myaction, :params => {:myproperty => "[0]['firstname']" }}) 40 | 41 | == Supported types 42 | 43 | RubyAMF Supports AMF0, AMF3, and RemoteObject 44 | 45 | == AMF Type conversions 46 | 47 | Flash 2 RubyAMF: 48 | 49 | undefined -> nil 50 | null -> nil 51 | false -> false 52 | true -> true 53 | Number -> Fixnum 54 | int -> Integer 55 | String -> String 56 | XML -> String (cast in your service) 57 | Array -> Array 58 | MixexArray? -> Hash 59 | Object -> Hash 60 | Custom Class -> Ruby Class 61 | RubyAMF 2 Flash: 62 | 63 | nil -> null 64 | false -> false 65 | true -> true 66 | Numeric -> Number 67 | String -> String 68 | BeautifulSoup? -> XML 69 | REXML::Doc -> xml 70 | Array -> Array 71 | Hash -> Object 72 | Ruby Class -> Custom Class 73 | 74 | == Generators 75 | 76 | RubyAMF Code Generators and Helper Scripts 77 | 78 | === Introduction 79 | 80 | RubyAMF currently has two generators 81 | 82 | RubyAMF only scaffold generator 83 | Console printing ClassMapping? helper generator 84 | 85 | Note: Currently the generators have not been upgraded to Rails 3 generators. 86 | 87 | === Details 88 | 89 | $ script/generate rubyamf_scaffold 90 | 91 | will produce a model, fixtures, tests, and a RubyAMF specific controller with actions that work only with incoming :amf formats. 92 | 93 | $ script/generate rubyamf_mappings 94 | 95 | will print class mapping entries for each model in app/models and below to the command line. This is designed to help easily add new class mappings to the rubyamf_config.rb configuration file. 96 | 97 | 98 | 99 | == Credits 100 | 101 | (c) aaron smith 102 | http://www.rubyamf.org -------------------------------------------------------------------------------- /app/actions.rb: -------------------------------------------------------------------------------- 1 | RUBY_19 = "1.9.0" 2 | RUBY_18 = "1.8.4" 3 | 4 | require 'app/configuration' 5 | module RubyAMF 6 | module Actions 7 | module Utils 8 | include RubyAMF::VoHelper 9 | 10 | def generate_acknowledge_object(message_id = nil, client_id = nil) 11 | res = VoHash.new 12 | res._explicitType = "flex.messaging.messages.AcknowledgeMessage" 13 | res["messageId"] = rand_uuid 14 | res["clientId"] = client_id||rand_uuid 15 | res["destination"] = nil 16 | res["body"] = nil 17 | res["timeToLive"] = 0 18 | res["timestamp"] = (String(Time.new) + '00') 19 | res["headers"] = {} 20 | res["correlationId"] = message_id 21 | res 22 | end 23 | 24 | #going for speed with these UUID's not neccessarily unique in space and time continue - um, word 25 | def rand_uuid 26 | [8,4,4,4,12].map {|n| rand_hex_3(n)}.join('-').to_s 27 | end 28 | 29 | def rand_hex_3(l) 30 | "%0#{l}x" % rand(1 << l*4) 31 | end 32 | end 33 | #This sets up each body for processing 34 | class PrepareAction 35 | include RubyAMF::App 36 | include RubyAMF::Actions::Utils 37 | include RubyAMF::Configuration 38 | 39 | def run(amfbody) 40 | if RequestStore.amf_encoding == 'amf3' && #AMF3 41 | (raw_body = amfbody.value[0]).is_a?(VoHash) && 42 | ['flex.messaging.messages.RemotingMessage','flex.messaging.messages.CommandMessage'].include?(raw_body._explicitType) 43 | case raw_body._explicitType 44 | when 'flex.messaging.messages.RemotingMessage' #Flex Messaging setup 45 | RequestStore.flex_messaging = true # only set RequestStore and ClassMappings when its a remoting message, not command message 46 | ClassMappings.use_array_collection = !(ClassMappings.use_array_collection==false) # it will only set it to false if the user specifically sets use_array_collection to false 47 | amfbody.special_handling = 'RemotingMessage' 48 | amfbody.value = raw_body['body'] 49 | amfbody.set_meta('clientId', raw_body['clientId']) 50 | amfbody.set_meta('messageId', raw_body['messageId']) 51 | amfbody.target_uri = raw_body['source'] 52 | amfbody.service_method_name = raw_body['operation'] 53 | amfbody._explicitType = raw_body._explicitType 54 | when 'flex.messaging.messages.CommandMessage' #it's a ping, don't process this body, and hence, dont set service uri information 55 | if raw_body['operation'] == 5 56 | amfbody.exec = false 57 | amfbody.special_handling = 'Ping' 58 | amfbody.set_meta('clientId', raw_body['clientId']) 59 | amfbody.set_meta('messageId', raw_body['messageId']) 60 | end 61 | return # we don't want it to run set_service_uri_information 62 | end 63 | else 64 | RequestStore.flex_messaging = false # ensure that array_collection is disabled 65 | ClassMappings.use_array_collection = false 66 | end 67 | 68 | amfbody.set_service_uri_information! 69 | end 70 | end 71 | 72 | #Invoke ActionController's process on the target controller action 73 | class RailsInvokeAction 74 | include RubyAMF::App 75 | include RubyAMF::Exceptions 76 | include RubyAMF::Configuration 77 | include RubyAMF::Actions::Utils 78 | include RubyAMF::VoHelper 79 | 80 | def run(amfbody) 81 | if amfbody.exec == false 82 | if amfbody.special_handling == 'Ping' 83 | amfbody.results = generate_acknowledge_object(amfbody.get_meta('messageId'), amfbody.get_meta('clientId')) #generate an empty acknowledge message here, no body needed for a ping 84 | amfbody.success! #flag the success response 85 | end 86 | return 87 | end 88 | @amfbody = amfbody #store amfbody in member var 89 | invoke 90 | end 91 | 92 | #invoke the service call 93 | def invoke 94 | begin 95 | # RequestStore.available_services[@amfbody.service_class_name] ||= 96 | @service = @amfbody.service_class_name.constantize.new #handle on service 97 | rescue Exception => e 98 | puts e.message 99 | puts e.backtrace 100 | raise RUBYAMFException.new(RUBYAMFException.UNDEFINED_OBJECT_REFERENCE_ERROR, "There was an error loading the service class #{@amfbody.service_class_name}") 101 | end 102 | 103 | #call one or the other method depending in the ruby version we are using 104 | if RUBY_VERSION > RUBY_19 105 | caller = "to_sym" 106 | else 107 | caller = "to_s" 108 | end 109 | 110 | if @service.private_methods.include?(@amfbody.service_method_name.send(caller)) 111 | raise RUBYAMFException.new(RUBYAMFException.METHOD_ACCESS_ERROR, "The method {#{@amfbody.service_method_name}} in class {#{@amfbody.service_class_file_path}} is declared as private, it must be defined as public to access it.") 112 | elsif !@service.public_methods.include?(@amfbody.service_method_name.send(caller)) 113 | raise RUBYAMFException.new(RUBYAMFException.METHOD_UNDEFINED_METHOD_ERROR, "The method {#{@amfbody.service_method_name}} in class {#{@amfbody.service_class_file_path}} is not declared.") 114 | end 115 | 116 | #clone the request and response and alter it for the target controller/method 117 | req = RequestStore.rails_request.clone 118 | res = RequestStore.rails_response.clone 119 | 120 | #change the request controller/action targets and tell the service to process. THIS IS THE VOODOO. SWEET! 121 | controller = @amfbody.service_class_name.gsub("Controller","").underscore 122 | action = @amfbody.service_method_name 123 | req.parameters['controller'] = req.request_parameters['controller'] = req.path_parameters['controller'] = controller 124 | req.parameters['action'] = req.request_parameters['action'] = req.path_parameters['action'] = action 125 | req.env['PATH_INFO'] = req.env['REQUEST_PATH'] = req.env['REQUEST_URI'] = "#{controller}/#{action}" 126 | req.env['HTTP_ACCEPT'] = 'application/x-amf,' + req.env['HTTP_ACCEPT'].to_s 127 | 128 | #set conditional helper 129 | @service.is_amf = true 130 | @service.is_rubyamf = true 131 | 132 | #process the request 133 | rubyamf_params = @service.rubyamf_params = {} 134 | if @amfbody.value && !@amfbody.value.empty? 135 | @amfbody.value.each_with_index do |item,i| 136 | rubyamf_params[i] = item 137 | end 138 | end 139 | 140 | # put them by default into the parameter hash if they opt for it 141 | rubyamf_params.each{|k,v| req.parameters[k] = v} if ParameterMappings.always_add_to_params 142 | 143 | begin 144 | #One last update of the parameters hash, this will map custom mappings to the hash, and will override any conflicting from above 145 | ParameterMappings.update_request_parameters(@amfbody.service_class_name, @amfbody.service_method_name, req.parameters, rubyamf_params, @amfbody.value) 146 | rescue Exception => e 147 | raise RUBYAMFException.new(RUBYAMFException.PARAMETER_MAPPING_ERROR, "There was an error with your parameter mappings: {#{e.message}}") 148 | end 149 | 150 | #fosrias 151 | #@service.process(req, res) 152 | 153 | # call the controller action differently depending on Rails version 154 | if Rails::VERSION::MAJOR < 3 155 | @service.process(req, res) 156 | else 157 | @service.request = req 158 | @service.response = res 159 | @service.process(action.to_sym) 160 | end 161 | #fosrias 162 | 163 | #unset conditional helper 164 | @service.is_amf = false 165 | @service.is_rubyamf = false 166 | @service.rubyamf_params = rubyamf_params # add the rubyamf_args into the controller to be accessed 167 | 168 | result = RequestStore.render_amf_results 169 | 170 | #handle FaultObjects 171 | if result.class.to_s == 'FaultObject' #catch returned FaultObjects - use this check so we don't have to include the fault object module 172 | e = RUBYAMFException.new(result['code'], result['message']) 173 | e.payload = result['payload'] 174 | raise e 175 | end 176 | 177 | #amf3 178 | @amfbody.results = result 179 | if @amfbody.special_handling == 'RemotingMessage' 180 | @wrapper = generate_acknowledge_object(@amfbody.get_meta('messageId'), @amfbody.get_meta('clientId')) 181 | @wrapper["body"] = result 182 | @amfbody.results = @wrapper 183 | end 184 | @amfbody.success! #set the success response uri flag (/onResult) 185 | end 186 | end 187 | end 188 | end 189 | -------------------------------------------------------------------------------- /app/amf.rb: -------------------------------------------------------------------------------- 1 | require 'exception/rubyamf_exception' 2 | module RubyAMF 3 | module AMF 4 | include RubyAMF::VoHelper 5 | #A High level amf message wrapper with methods for easy header and body manipulation 6 | class AMFObject 7 | 8 | #raw input stream 9 | attr_accessor :input_stream 10 | 11 | #serialized output stream 12 | attr_accessor :output_stream 13 | 14 | attr_accessor :bodys 15 | 16 | #create a new AMFObject, pass the raw request data 17 | def initialize(rw = nil) 18 | @input_stream = rw 19 | @output_stream = "" #BinaryString.new("") 20 | @inheaders = Array.new 21 | @outheaders = Array.new 22 | @bodys = Array.new 23 | @header_table = Hash.new 24 | end 25 | 26 | #add a raw header to this amf_object 27 | def add_header(amf_header) 28 | @inheaders << amf_header 29 | @header_table[amf_header.name] = amf_header 30 | end 31 | 32 | #get a header by it's key 33 | def get_header_by_key(key) 34 | @header_table[key]||false 35 | end 36 | 37 | #get a header at a specific index 38 | def get_header_at(i=0) 39 | @inheaders[i]||false 40 | end 41 | 42 | #get the number of in headers 43 | def num_headers 44 | @inheaders.length 45 | end 46 | 47 | #add a parse header to the outgoing pool of headers 48 | def add_outheader(amf_header) 49 | @outheaders << amf_header 50 | end 51 | 52 | #get a header at a specific index 53 | def get_outheader_at(i=0) 54 | @outheaders[i]||false 55 | end 56 | 57 | #get all the in headers 58 | def get_outheaders 59 | @outheaders 60 | end 61 | 62 | #Get the number of out headers 63 | def num_outheaders 64 | @outheaders.length 65 | end 66 | 67 | #add a body 68 | def add_body(amf_body) 69 | @bodys << amf_body 70 | end 71 | 72 | #get a body obj at index 73 | def get_body_at(i=0) 74 | @bodys[i]||false 75 | end 76 | 77 | #get the number of bodies 78 | def num_body 79 | @bodys.length 80 | end 81 | 82 | #add a body to the body pool at index 83 | def add_body_at(index,body) 84 | @bodys.insert(index,body) 85 | end 86 | 87 | #add a body to the top of the array 88 | def add_body_top(body) 89 | @bodys.unshift(body) 90 | end 91 | 92 | #Remove a body from the body pool at index 93 | def remove_body_at(index) 94 | @bodys.delete_at(index) 95 | end 96 | 97 | #remove the AUTH header, (it is always at the top) 98 | def remove_auth_body 99 | @bodys.shift 100 | end 101 | 102 | #remove all bodies except the auth body 103 | def only_auth_fail_body! 104 | auth_body = nil 105 | @bodys.each do |b| 106 | if b.inspect.to_s.match(/Authentication Failed/) != 107 | auth_body = b 108 | end 109 | end 110 | @bodys = [auth_body] if auth_body 111 | end 112 | end 113 | # Wraps an amfbody with methods and params for easter manipulation 114 | class AMFBody 115 | 116 | include RubyAMF::Exceptions 117 | include RubyAMF::App 118 | 119 | attr_accessor :id #the amfbody id 120 | attr_accessor :response_index #the response unique index that the player understands, knows which result / fault methods to call. 121 | attr_accessor :response_uri #the complete response uri (EX: /12/onStatus) 122 | attr_accessor :target_uri #the target uri (service name) 123 | attr_accessor :service_class_file_path #the service file path 124 | attr_accessor :service_class_name #the service name 125 | attr_accessor :service_method_name #the service method name 126 | attr_accessor :value #the parameters to use in the service call 127 | attr_accessor :results #the results from a service call 128 | attr_accessor :special_handling #special handling 129 | attr_accessor :exec #executeable body 130 | attr_accessor :_explicitType #set the explicit type 131 | 132 | #create a new amfbody object 133 | def initialize(target = "", response_index = "", value = "") 134 | @id = response_index.clone.split('/').to_s 135 | @target_uri = target 136 | @response_index = response_index 137 | @response_uri = @response_index + '/onStatus' #default to status call 138 | @value = value 139 | @exec = true 140 | @_explicitType = "" 141 | @meta = {} 142 | end 143 | 144 | #append string data the the response uri 145 | def append_to_response_uri(str) 146 | @response_uri = @response_uri + str 147 | end 148 | 149 | #set some meta data for this amfbody 150 | def set_meta(key,val) 151 | @meta[key] = val 152 | end 153 | 154 | #get the meta data by key 155 | def get_meta(key) 156 | @meta[key] 157 | end 158 | 159 | #trigger an update to the response_uri to be a successfull response (/1/onResult) 160 | def success! 161 | @response_uri = "#{@response_index}/onResult" 162 | end 163 | 164 | #force the call to fail in the flash player 165 | def fail! 166 | @response_uri = "#{@response_index}/onStatus" 167 | end 168 | 169 | # allows a target_uri of "services.[bB]ooks", "services.[bB]ooksController to become service_class_name "Services::BooksController" and the class file path to be "services/books_controller.rb" 170 | def set_service_uri_information! 171 | if @target_uri 172 | uri_elements = @target_uri.split(".") 173 | @service_method_name ||= uri_elements.pop # this was already set, probably amf3, that means the target_uri doesn't include it 174 | if !uri_elements.empty? 175 | uri_elements.last << "Controller" unless uri_elements.last.include?("Controller") 176 | @service_class_name = uri_elements.collect(&:to_title).join("::") 177 | @service_class_file_path = "#{RequestStore.service_path}/#{uri_elements[0..-2].collect{|x| x+'/'}}#{uri_elements.last.underscore}.rb" 178 | else 179 | raise RUBYAMFException.new(RUBYAMFException.SERVICE_TRANSLATION_ERROR, "The correct service information was not provided to complete the service call. The service and method name were not provided") 180 | end 181 | else 182 | if RequestStore.flex_messaging 183 | raise RUBYAMFException.new(RUBYAMFException.USER_ERROR, "There is no \"source\" property defined on your RemoteObject, please see RemoteObject documentation for more information.") 184 | else 185 | raise RUBYAMFException.new(RUBYAMFException.SERVICE_TRANSLATION_ERROR, "The correct service information was not provided to complete the service call. The service and method name were not provided") 186 | end 187 | end 188 | end 189 | 190 | end 191 | 192 | #a simple wrapper class that wraps an amfheader 193 | class AMFHeader 194 | attr_accessor :name, :value, :required 195 | def initialize(name,required,value) 196 | @name, @value, @required = name, value, required 197 | end 198 | end 199 | 200 | #this cass takes a RUBYAMFException and inspects the details of the exception, returning this object back to flash as a Fault object 201 | class ASFault < VoHash 202 | 203 | #pass a RUBYAMFException, create new keys based on exception for the fault object 204 | def initialize(e) 205 | 206 | backtrace = e.backtrace || e.ebacktrace #grab the correct backtrace 207 | 208 | begin 209 | linerx = /:(\d*):/ 210 | line = linerx.match(backtrace[0])[1] #get the numbers 211 | rescue Exception => e 212 | line = 'No backtrace was found in this exception' 213 | end 214 | 215 | begin 216 | methodrx = /`(\S*)\'/ 217 | method = methodrx.match(backtrace[0])[1] #just method name 218 | rescue Exception => e 219 | method = "No method was found in this exception" 220 | end 221 | 222 | begin 223 | classrx = /([a-zA-Z0-9_]*)\.rb/ 224 | classm = classrx.match(backtrace[0]) #class name 225 | rescue Exception => e 226 | classm = "No class was found in this exception" 227 | end 228 | 229 | self["code"] = e.etype.to_s #e.type.to_s 230 | self["description"] = e.message 231 | self["details"] = backtrace[0] 232 | self["level"] = 'UserError' 233 | self["class_file"] = classm.to_s 234 | self["line"] = line 235 | self["function"] = method 236 | self["faultString"] = e.message 237 | self["faultCode"] = e.etype.to_s 238 | self["backtrace"] = backtrace 239 | end 240 | end 241 | 242 | #ActionScript 3 Exeption, this class bubbles to the player after an Exception in Ruby 243 | class AS3Fault < VoHash 244 | 245 | # attr_accessor :faultCode, :faultString, :faultDetail, :rootCause, :extendedData 246 | #pass a RUBYAMFException, create new keys based on exception for the fault object 247 | def initialize(e) 248 | backtrace = e.backtrace || e.ebacktrace #grab the correct backtrace 249 | self._explicitType = 'flex.messaging.messages.ErrorMessage' 250 | self["faultCode"] = e.etype.to_s #e.type.to_s 251 | self["faultString"] = e.message 252 | self["faultDetail"] = backtrace 253 | self["rootCause"] = backtrace[0] 254 | self["extendedData"] = e.payload || backtrace 255 | end 256 | end 257 | 258 | # Simple wrapper for serizlization time. All adapters adapt the db result into an ASRecordset, 259 | class ASRecordset 260 | 261 | #accessible attributes for this asrecordset 262 | attr_accessor :total_count 263 | 264 | #the number of rows in the recordset 265 | attr_accessor :row_count 266 | 267 | #columns returned 268 | attr_accessor :column_names 269 | 270 | #the payload for a recordset 271 | attr_accessor :initial_data 272 | 273 | #cursor position 274 | attr_accessor :cursor 275 | 276 | #id of the recoredset 277 | attr_accessor :id 278 | 279 | #version of the recordset 280 | attr_accessor :version 281 | 282 | #the service name that was originally called 283 | attr_accessor :service_class_name 284 | 285 | #this is an optional argument., a database adapter could optionally serialize the results, instead of the AMFSerializer serializing the results 286 | attr_accessor :serialized_data 287 | 288 | #mark this recordset as pageable 289 | attr_accessor :is_pageable 290 | 291 | #new ASRecordset 292 | def initialize(row_count,column_names,initial_data) 293 | self.row_count = row_count 294 | self.column_names = column_names 295 | self.initial_data = initial_data 296 | cursor = 1 297 | version = 1 298 | end 299 | end 300 | 301 | end 302 | end -------------------------------------------------------------------------------- /app/configuration.rb: -------------------------------------------------------------------------------- 1 | #This stores supporting configuration classes used in the config file to register class mappings and parameter mappings etc. 2 | require 'app/request_store' 3 | require 'exception/rubyamf_exception' 4 | module RubyAMF 5 | module Configuration 6 | #ClassMappings configuration support class 7 | class ClassMappings 8 | 9 | # these NEED to be outside the class << self to work 10 | @ignore_fields = ['created_at','created_on','updated_at','updated_on'] 11 | @translate_case = false 12 | @class_mappings_by_ruby_class = {} 13 | @class_mappings_by_actionscript_class = {} 14 | @scoped_class_mappings_by_ruby_class = {} 15 | @attribute_names = {} 16 | @hash_key_access = :symbol 17 | @translate_case = false 18 | @force_active_record_ids = true 19 | @assume_types = false 20 | @use_ruby_date_time = false 21 | @use_array_collection = false 22 | @check_for_associations = true 23 | @capture_incoming_amf = false 24 | 25 | # Aryk: I cleaned up how the class variables are called here. It doesnt matter if you use class variables or instance variables on the class level. Check out this simple tutorial 26 | # - http://sporkmonger.com/2007/2/19/instance-variables-class-variables-and-inheritance-in-ruby 27 | 28 | class << self 29 | include RubyAMF::App 30 | include RubyAMF::Exceptions 31 | 32 | attr_accessor :ignore_fields, :use_array_collection, :default_mapping_scope, :force_active_record_ids, :attribute_names, :capture_incoming_amf, 33 | :use_ruby_date_time, :current_mapping_scope, :check_for_associations, :translate_case, :assume_types, :hash_key_access #the rails parameter mapping type 34 | 35 | def register(mapping) #register a value object map 36 | #build out ignore field logic 37 | hashed_ignores = {} 38 | ClassMappings.ignore_fields.to_a.each{|k| hashed_ignores[k] = true} # strings and nils will be put into an array with to_a 39 | mapping[:ignore_fields].to_a.each{|k| hashed_ignores[k] = true} 40 | mapping[:ignore_fields] = hashed_ignores # overwrite the original ignore fields 41 | 42 | # if they specify custom attributes, ensure that AR ids are being passed as well if they opt for it. 43 | if force_active_record_ids && mapping[:attributes] && mapping[:type]=="active_record" && !mapping[:attributes].include?("id") 44 | mapping[:attributes] << "id" 45 | end 46 | 47 | # created caching hashes for mapping 48 | @class_mappings_by_ruby_class[mapping[:ruby]] = mapping # for quick referencing purposes 49 | @class_mappings_by_actionscript_class[mapping[:actionscript]] = mapping # for quick referencing purposes 50 | @scoped_class_mappings_by_ruby_class[mapping[:ruby]] = {} # used later for caching based on scope (will get cached after the first run) 51 | # for deserialization - looking up in a hash is faster than looking up in an array. 52 | begin 53 | if mapping[:type] == "active_record" 54 | primary_key = eval(mapping[:ruby]).primary_key # fosrias : allows for custom primary keys 55 | @attribute_names[mapping[:ruby]] = (mapping[:ruby].constantize.new.attribute_names + [primary_key]).inject({}){|hash, attr| hash[attr]=true ; hash} # fosrias: include the primary key attribute 56 | end 57 | rescue StandardError => e 58 | # This error occurs during migrations, since the AR constructed above will check its columns, but the table won't exist yet. 59 | # We'll ignore the error if we're migrating. 60 | raise unless ARGV.include?("migrate") or ARGV.include?("db:migrate") or ARGV.include?("rollback") or ARGV.include?("db:rollback") 61 | end 62 | end 63 | 64 | def get_vo_mapping_for_ruby_class(ruby_class) 65 | return unless scoped_class_mapping = @scoped_class_mappings_by_ruby_class[ruby_class] # just in case they didnt specify a ClassMapping for this Ruby Class 66 | scoped_class_mapping[@current_mapping_scope] ||= (if vo_mapping = @class_mappings_by_ruby_class[ruby_class] 67 | vo_mapping = vo_mapping.dup # need to duplicate it or else we will overwrite the keys from the original mappings 68 | vo_mapping[:attributes] = vo_mapping[:attributes][@current_mapping_scope]||[] if vo_mapping[:attributes].is_a?(Hash) # don't include any of these attributes if there is no scope 69 | vo_mapping[:associations] = vo_mapping[:associations][@current_mapping_scope]||[] if vo_mapping[:associations].is_a?(Hash) # don't include any of these attributes 70 | vo_mapping 71 | end 72 | ) 73 | end 74 | 75 | def get_vo_mapping_for_actionscript_class(actionscript_class) 76 | @class_mappings_by_actionscript_class[actionscript_class] 77 | end 78 | end 79 | end 80 | 81 | class ParameterMappings 82 | @parameter_mappings = {} 83 | @always_add_to_params = true 84 | @scaffolding = false 85 | 86 | class << self 87 | 88 | attr_accessor :scaffolding, :always_add_to_params 89 | 90 | def register(mapping) 91 | raise RUBYAMFException.new(RUBYAMFException.USER_ERROR, "You must atleast specify the :controller for a parameter mapping") unless mapping[:controller] 92 | set_parameter_mapping(mapping[:controller], mapping[:action], mapping) 93 | end 94 | 95 | def update_request_parameters(controller_class_name, controller_action_name, request_params, rubyamf_params, remoting_params) 96 | if map = get_parameter_mapping(controller_class_name, controller_action_name) 97 | map[:params].each do |k,v| 98 | val = eval("remoting_params#{v}") 99 | if scaffolding && val.is_a?(ActiveRecord::Base) 100 | request_params[k.to_sym] = val.attributes.dup 101 | val.instance_variables.each do |assoc| 102 | next if "@new_record" == assoc 103 | request_params[k.to_sym][assoc[1..-1]] = val.instance_variable_get(assoc) 104 | end 105 | else 106 | request_params[k.to_sym] = val 107 | end 108 | rubyamf_params[k.to_sym] = request_params[k.to_sym] # assign it to rubyamf_params for consistency 109 | end 110 | else #do some default mappings for the first element in the parameters 111 | if remoting_params.is_a?(Array) 112 | if scaffolding 113 | if (first = remoting_params[0]) 114 | if first.is_a?(ActiveRecord::Base) 115 | key = first.class.to_s.to_snake!.downcase.to_sym # a generated scaffold expects params in snake_case, rubyamf_params gets them for consistency in scaffolding 116 | rubyamf_params[key] = first.attributes.dup 117 | first.instance_variables.each do |assoc| 118 | next if "@new_record" == assoc 119 | rubyamf_params[key][assoc[1..-1]] = first.instance_variable_get(assoc) 120 | end 121 | if always_add_to_params #if wanted in params, put it in 122 | request_params[key] = rubyamf_params[key] #put it into rubyamf_params 123 | end 124 | else 125 | if first.is_a?(RubyAMF::VoHelper::VoHash) 126 | if (key = first.explicitType.split('::').last.to_snake!.downcase.to_sym) 127 | rubyamf_params[key] = first 128 | if always_add_to_params 129 | request_params[key] = first 130 | end 131 | end 132 | elsif first.is_a?(Hash) # a simple hash should become named params in params 133 | rubyamf_params.merge!(first) 134 | if always_add_to_params 135 | request_params.merge!(first) 136 | end 137 | end 138 | end 139 | request_params[:id] = rubyamf_params[:id] = first['id'] if (first['id'] && !(first['id']==0)) 140 | end 141 | end 142 | end 143 | end 144 | end 145 | 146 | private 147 | def get_parameter_mapping(controller_class_name, controller_action_name) 148 | @parameter_mappings[end_point_string(controller_class_name, controller_action_name)]|| # first check to see if there is a paramter mapping for the controller/action combo, 149 | @parameter_mappings[end_point_string(controller_class_name)] # then just check if there is one for the controller 150 | end 151 | 152 | def set_parameter_mapping(controller_class_name, controller_action_name, mapping) 153 | @parameter_mappings[end_point_string(controller_class_name, controller_action_name)] = mapping 154 | end 155 | 156 | def end_point_string(controller_class_name, controller_action_name=nil) # if the controller_action_name is nil, than we the parameter mapping is for the entire controller 157 | eps = controller_class_name.to_s.dup # could be a symbol from the class mapping ; need to dup because it will recursively append the action_name 158 | eps << ".#{controller_action_name}" if controller_action_name 159 | eps 160 | end 161 | 162 | end 163 | end 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /app/fault_object.rb: -------------------------------------------------------------------------------- 1 | #This is a helper to return FaultObjects. Often times there are sitiuations with database logic that requires an "error state" 2 | #to be set in Flash / Flex, but returning false isn't the best because it still get's mapped to the onResult handler, even returning a 3 | #generic object with some specific keys set, such as ({error:'someError', code:3}). That is still a pain because it gets mapped to 4 | #the onResult function still. So return one of these objects to RubyAMF and it will auto generate a faultObject to return to flash 5 | #so that it maps correctly to the onFault handler. 6 | class FaultObject < Hash 7 | def initialize(message = '', payload=nil) 8 | self['faultCode'] = 1 9 | self['code'] = 1 10 | self['message'] = message 11 | self['faultString'] = message 12 | self['payload'] = payload 13 | end 14 | end -------------------------------------------------------------------------------- /app/filters.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'io/amf_deserializer' 3 | require 'io/amf_serializer' 4 | require 'exception/exception_handler' 5 | module RubyAMF 6 | module Filter 7 | 8 | class FilterChain 9 | include RubyAMF::App 10 | def run(amfobj) 11 | RequestStore.filters.each do |filter| #grab the filters to run through 12 | filter.run(amfobj) 13 | # puts "#{filter}: " +Benchmark.realtime{}.to_s 14 | end 15 | end 16 | end 17 | 18 | class AMFDeserializerFilter 19 | include RubyAMF::IO 20 | def run(amfobj) 21 | AMFDeserializer.new.rubyamf_read(amfobj) 22 | end 23 | end 24 | 25 | class AMFCaptureFilter 26 | include RubyAMF::Configuration 27 | def run(amfobj) 28 | return unless ClassMappings.capture_incoming_amf 29 | message_body = amfobj.input_stream 30 | body = amfobj.get_body_at(0) 31 | path = "#{File.expand_path(Rails::VERSION::MAJOR < 3 ? RAILS_ROOT : ::Rails.root.to_s)}/vendor/plugins/rubyamf/amfcaptures" #fosrias: RAILS_ROOT deprectated in Rails 3 32 | FileUtils.mkdir_p path 33 | filename = "#{path}/#{body.target_uri}_#{body.service_method_name}" 34 | File.open(filename,"w").write(message_body) unless body.target_uri == "null" 35 | end 36 | end 37 | 38 | class AuthenticationFilter 39 | include RubyAMF::App 40 | include RubyAMF::Configuration 41 | def run(amfobj) 42 | RequestStore.auth_header = nil # Aryk: why do we need to rescue this? 43 | if (auth_header = amfobj.get_header_by_key('Credentials')) 44 | RequestStore.auth_header = auth_header #store the auth header for later 45 | case ClassMappings.hash_key_access 46 | when :string then 47 | auth = {'username' => auth_header.value['userid'], 'password' => auth_header.value['password']} 48 | when :symbol then 49 | auth = {:username => auth_header.value['userid'], :password => auth_header.value['password']} 50 | when :indifferent then 51 | auth = HashWithIndifferentAccess.new({:username => auth_header.value['userid'], :password => auth_header.value['password']}) 52 | end 53 | RequestStore.rails_authentication = auth 54 | end 55 | end 56 | end 57 | 58 | class BatchFilter 59 | include RubyAMF::App 60 | include RubyAMF::Exceptions 61 | def run(amfobj) 62 | body_count = amfobj.num_body 63 | 0.upto(body_count - 1) do |i| #loop through all bodies, do each action on the body 64 | body = amfobj.get_body_at(i) 65 | RequestStore.actions.each do |action| 66 | begin #this is where any exception throughout the RubyAMF Process gets transformed into a relevant AMF0/AMF3 faultObject 67 | # action.run(body) 68 | seconds = Benchmark.realtime{ action.run(body) } 69 | puts ">>>>>>>> RubyAMF >>>>>>>>> #{action} took: #{'%.5f' % seconds} secs" 70 | rescue RUBYAMFException => ramfe 71 | puts ramfe.message 72 | puts ramfe.backtrace 73 | ramfe.ebacktrace = ramfe.backtrace.to_s 74 | ExceptionHandler::HandleException(ramfe,body) 75 | rescue Exception => e 76 | puts e.message 77 | puts e.backtrace 78 | ramfe = RUBYAMFException.new(e.class.to_s, e.message.to_s) #translate the exception into a rubyamf exception 79 | ramfe.ebacktrace = e.backtrace.to_s 80 | ExceptionHandler::HandleException(ramfe, body) 81 | end 82 | end 83 | end 84 | end 85 | end 86 | 87 | class AMFSerializeFilter 88 | include RubyAMF::IO 89 | def run(amfobj) 90 | # AMFSerializer.new(amfobj).run 91 | seconds = Benchmark.realtime{ AMFSerializer.new(amfobj).run } 92 | puts ">>>>>>>> RubyAMF >>>>>>>>> Serialization took: #{'%.5f' % seconds} secs" 93 | end 94 | end 95 | end 96 | end -------------------------------------------------------------------------------- /app/mime_type.rb: -------------------------------------------------------------------------------- 1 | # Adding amf to the list of mime type formats that are not checked for forgery protection 2 | # at the time of writing (rails 2.1) the other types were [:text, :json, :csv, :xml, :rss, :atom, :yaml] 3 | Mime::Type.unverifiable_types << :amf if defined? Mime::Type.unverifiable_types -------------------------------------------------------------------------------- /app/rails_gateway.rb: -------------------------------------------------------------------------------- 1 | # require 'app/gateway' 2 | require 'app/request_store' 3 | require 'app/amf' 4 | require 'app/actions' 5 | require 'app/filters' 6 | require 'app/configuration' 7 | require 'zlib' 8 | module RubyAMF 9 | module App 10 | #Rails Gateway, extends regular gateway and changes the actions 11 | class RailsGateway 12 | 13 | include RubyAMF::Actions 14 | include RubyAMF::AMF 15 | include RubyAMF::Configuration 16 | include RubyAMF::Filter 17 | include RubyAMF::App # for RequestStore 18 | include RubyAMF::Exceptions 19 | 20 | def initialize 21 | RequestStore.filters = Array[AMFDeserializerFilter.new, AuthenticationFilter.new, BatchFilter.new, AMFCaptureFilter.new, AMFSerializeFilter.new] #create the filter 22 | RequestStore.actions = Array[PrepareAction.new, RailsInvokeAction.new] #override the actions 23 | end 24 | 25 | #all get and post requests circulate throught his method 26 | def service(raw) 27 | amfobj = AMFObject.new(raw) 28 | FilterChain.new.run(amfobj) 29 | RequestStore.gzip ? Zlib::Deflate.deflate(amfobj.output_stream) : amfobj.output_stream 30 | end 31 | end 32 | end 33 | end -------------------------------------------------------------------------------- /app/request_store.rb: -------------------------------------------------------------------------------- 1 | module RubyAMF 2 | module App 3 | 4 | #store information on a per request basis 5 | class RequestStore 6 | @render_amf_results = false 7 | @flex_messaging = false 8 | @auth_header = nil 9 | @rails_authentication 10 | @reload_services = false 11 | @gzip = false 12 | @service_path = File.expand_path(Rails::VERSION::MAJOR < 3 ? RAILS_ROOT : ::Rails.root.to_s) + '/app/controllers' #fosrias: RAILS_ROOT deprectated in Rails 3 13 | 14 | class << self 15 | attr_accessor :amf_encoding 16 | attr_accessor :service_path 17 | attr_accessor :filters 18 | attr_accessor :actions 19 | attr_accessor :rails_request 20 | attr_accessor :rails_response 21 | attr_accessor :render_amf_results 22 | attr_accessor :flex_messaging 23 | attr_accessor :auth_header 24 | attr_accessor :rails_authentication 25 | attr_accessor :reload_services 26 | attr_accessor :gzip 27 | end 28 | end 29 | end 30 | end -------------------------------------------------------------------------------- /exception/exception_handler.rb: -------------------------------------------------------------------------------- 1 | require 'app/request_store' 2 | require 'app/amf' 3 | module RubyAMF 4 | module Exceptions 5 | 6 | #This class is used to take an RUBYAMFException and translate it into something that is useful when returned back to flash. 7 | class ExceptionHandler 8 | include RubyAMF::App 9 | include RubyAMF::AMF 10 | 11 | def ExceptionHandler.HandleException(e, body) 12 | if RequestStore.amf_encoding == 'amf3' 13 | body.results = AS3Fault.new(e) 14 | #trigger RemoteObject failure for AsyncTokens 15 | if body.special_handling == "RemotingMessage" 16 | body.results["correlationId"] = body.get_meta('messageId') 17 | body.results["clientId"] = body.get_meta('clientId') || body.results["correlationId"] 18 | end 19 | else 20 | body.fail! #force the fail trigger for F8, this causes it to map to the onFault handler 21 | body.results = ASFault.new(e) 22 | end 23 | end 24 | end 25 | end 26 | end -------------------------------------------------------------------------------- /exception/rubyamf_exception.rb: -------------------------------------------------------------------------------- 1 | module RubyAMF 2 | module Exceptions 3 | 4 | #Encompasses all rubyamf specific exceptions that occur 5 | class RUBYAMFException < Exception 6 | 7 | #when version is not 0 or 3 8 | @VERSION_ERROR = 'RUBYAMF_AMF_VERSION_ERROR' 9 | 10 | #when translating the target_uri of a body, there isn't a .(period) to map the service / method name 11 | @SERVICE_TRANSLATION_ERROR = 'RUBYAMF_SERVICE_TRANSLATION_ERROR' 12 | 13 | #when an authentication error occurs 14 | @AUTHENTICATION_ERROR = 'RUBYAMF_ATUHENTICATION_ERROR' 15 | 16 | #when a method is called, but the method is either private or doesn't exist 17 | @METHOD_ACCESS_ERROR = 'RUBYAMF_METHOD_ACCESS_ERROR' 18 | 19 | #when a mehod is undefined 20 | @METHOD_UNDEFINED_METHOD_ERROR = 'RUBYAMF_UNDECLARED_METHOD_ERROR' 21 | 22 | #when there is an error with session implementation 23 | @SESSION_ERROR = 'RUBYAMF_SESSION_ERROR' 24 | 25 | #when a general user error has occured 26 | @USER_ERROR = 'RUBYAMF_USER_ERROR' 27 | 28 | #when parsing AMF3, an undefined object reference 29 | @UNDEFINED_OBJECT_REFERENCE_ERROR = 'RUBYAMF_UNDEFINED_OBJECT_REFERENCE_ERROR' 30 | 31 | #when parsing AMF3, an undefined class definition 32 | @UNDEFINED_DEFINITION_REFERENCE_ERROR = 'RUBYAMF_UNDEFINED_DEFINIITON_REFERENCE_ERROR' 33 | 34 | #when parsing amf3, an undefined string reference 35 | @UNDEFINED_STRING_REFERENCE_ERROR = 'RUBYAMF_UNDEFINED_STRING_REFERENCE_ERROR' 36 | 37 | #unsupported AMF0 type 38 | @UNSUPPORTED_AMF0_TYPE = 'UNSUPPORTED_AMF0_TYPE' 39 | 40 | #when the Rails ActionController Filter chain haults 41 | @FILTER_CHAIN_HAULTED = 'RAILS_ACTION_CONTROLLER_FILTER_CHAIN_HAULTED' 42 | 43 | #when active record errors 44 | @ACTIVE_RECORD_ERRORS = 'ACTIVE_RECORD_ERRORS' 45 | 46 | #whan amf data is incomplete or incorrect 47 | @AMF_ERROR = 'AMF_ERROR' 48 | 49 | #vo errors 50 | @VO_ERROR = 'VO_ERROR' 51 | 52 | #when a parameter mapping error occurs 53 | @PARAMETER_MAPPING_ERROR = "PARAMETER_MAPPING_ERROR" 54 | 55 | attr_accessor :message 56 | attr_accessor :etype 57 | attr_accessor :ebacktrace 58 | attr_accessor :payload 59 | 60 | #static accessors 61 | class << self 62 | attr_accessor :VERSION_ERROR 63 | attr_accessor :SERVICE_TRANSLATION_ERROR 64 | attr_accessor :AUTHENTICATION_ERROR 65 | attr_accessor :METHOD_ACCESS_ERROR 66 | attr_accessor :METHOD_UNDEFINED_METHOD_ERROR 67 | attr_accessor :SESSION_ERROR 68 | attr_accessor :USER_ERROR 69 | attr_accessor :UNDEFINED_OBJECT_REFERENCE_ERROR 70 | attr_accessor :UNDEFINED_DEFINITION_REFERENCE_ERROR 71 | attr_accessor :UNDEFINED_STRING_REFERENCE_ERROR 72 | attr_accessor :UNSUPPORTED_TYPE 73 | attr_accessor :ADAPTER_ERROR 74 | attr_accessor :INTERNAL_ERROR 75 | attr_accessor :UNSUPPORTED_AMF0_TYPE 76 | attr_accessor :FILTER_CHAIN_HAULTED 77 | attr_accessor :ACTIVE_RECORD_ERRORS 78 | attr_accessor :VO_ERROR 79 | attr_accessor :AMF_ERROR 80 | attr_accessor :PARAMETER_MAPPING_ERROR 81 | end 82 | 83 | def initialize(type,msg) 84 | super(msg) 85 | @message = msg 86 | @etype = type 87 | end 88 | 89 | # stringify the message 90 | def to_s 91 | @msg 92 | end 93 | 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /generators/rubyamf_mappings/rubyamf_mappings_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rbconfig' 2 | 3 | class RubyamfMappingsGenerator < Rails::Generator::Base 4 | 5 | MODEL_DIR = File.join(Rails::VERSION::MAJOR < 3 ? RAILS_ROOT : ::Rails.root.to_s, "app/models") #fosrias: RAILS_ROOT deprectated in Rails 3 6 | 7 | def initialize(runtime_args, runtime_options = {}) 8 | end 9 | 10 | def manifest 11 | record do |m| 12 | mappings = map_models_with_all_attributes_and_associations 13 | puts 'Copy this block of text into config/rubyamf_config.rb:' 14 | puts mappings 15 | end 16 | end 17 | 18 | private 19 | 20 | def create_full_map(klass) 21 | mapping = <<-MAPPING 22 | 23 | ClassMappings.register( 24 | :actionscript => '#{klass.class_name}', 25 | :ruby => '#{klass.class_name}', 26 | :type => 'active_record' 27 | MAPPING 28 | 29 | if associations = associations_for(klass) 30 | mapping.chop! << ",\n" 31 | mapping << <<-ASSOC 32 | :associations => #{associations} 33 | ASSOC 34 | end 35 | 36 | if attributes = attributes_for(klass) 37 | mapping.chop! << ",\n" 38 | mapping << <<-ASSOC 39 | :attributes => #{attributes} 40 | ASSOC 41 | end 42 | 43 | mapping.chop! << ")\n" 44 | end 45 | 46 | def create_simple_map(klass) 47 | " ClassMappings.register(:actionscript => '#{klass.class_name}', :ruby => '#{klass.class_name}', :type => 'active_record')\n" 48 | end 49 | 50 | def associations_for(klass) 51 | klass.reflections.keys.empty? ? nil : klass.reflections.stringify_keys.keys.inspect 52 | end 53 | 54 | def attributes_for(klass) 55 | klass.column_names.empty? ? nil : klass.column_names.inspect 56 | end 57 | 58 | def get_model_names 59 | models = [] 60 | Dir.chdir(MODEL_DIR) do 61 | models = Dir["**/*.rb"] 62 | end 63 | models 64 | end 65 | 66 | def map_models(full=false) 67 | mappings = '' 68 | get_model_names.each do |m| 69 | class_name = m.sub(/\.rb$/,'').camelize 70 | begin 71 | klass = class_name.split('::').inject(Object){ |klass,part| klass.const_get(part) } 72 | if klass < ActiveRecord::Base && !klass.abstract_class? 73 | mappings << (full ? create_full_map(klass) : create_simple_map(klass)) 74 | else 75 | puts "Skipping #{class_name}: either not active record or abstract" 76 | end 77 | rescue Exception => e 78 | puts "Unable to map #{class_name}: #{e.message}" 79 | end 80 | end 81 | mappings 82 | end 83 | 84 | def map_models_with_all_attributes_and_associations 85 | map_models(true) 86 | end 87 | 88 | end -------------------------------------------------------------------------------- /generators/rubyamf_scaffold/USAGE: -------------------------------------------------------------------------------- 1 | Description: 2 | Scaffolds a RubyAMF resource: model, migration, and controller. The 3 | resource is ready to use as a RubyAMF service. 4 | 5 | Pass the name of the model, either CamelCased or under_scored, as the first 6 | argument, and an optional list of attribute pairs. 7 | 8 | Attribute pairs are column_name:sql_type arguments specifying the 9 | model's attributes. Timestamps are added by default, so you don't have to 10 | specify them by hand as 'created_at:datetime updated_at:datetime'. 11 | 12 | You don't have to think up every attribute up front, but it helps to 13 | sketch out a few so you can start working with the resource immediately. 14 | 15 | For example, `rubyamf_scaffold post title:string body:text published:boolean` 16 | gives you a model with those three attributes, a controller that handles 17 | the find/save/destroy. 18 | 19 | Examples: 20 | `./script/generate rubyamf_scaffold post` # no attributes 21 | `./script/generate rubyamf_scaffold post title:string body:text published:boolean` 22 | `./script/generate rubyamf_scaffold purchase order_id:integer amount:decimal` 23 | -------------------------------------------------------------------------------- /generators/rubyamf_scaffold/rubyamf_scaffold_generator.rb: -------------------------------------------------------------------------------- 1 | #copied from rails tag rel_2-0-0_RC1 scaffold generator 2 | class RubyamfScaffoldGenerator < Rails::Generator::NamedBase 3 | default_options :skip_timestamps => false, :skip_migration => false 4 | 5 | attr_reader :controller_name, 6 | :controller_class_path, 7 | :controller_file_path, 8 | :controller_class_nesting, 9 | :controller_class_nesting_depth, 10 | :controller_class_name, 11 | :controller_underscore_name, 12 | :controller_singular_name, 13 | :controller_plural_name 14 | alias_method :controller_file_name, :controller_underscore_name 15 | alias_method :controller_table_name, :controller_plural_name 16 | 17 | def initialize(runtime_args, runtime_options = {}) 18 | super 19 | 20 | @controller_name = @name.pluralize 21 | 22 | base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@controller_name) 23 | @controller_class_name_without_nesting, @controller_underscore_name, @controller_plural_name = inflect_names(base_name) 24 | @controller_singular_name=base_name.singularize 25 | if @controller_class_nesting.empty? 26 | @controller_class_name = @controller_class_name_without_nesting 27 | else 28 | @controller_class_name = "#{@controller_class_nesting}::#{@controller_class_name_without_nesting}" 29 | end 30 | end 31 | 32 | def manifest 33 | record do |m| 34 | # Check for class naming collisions. 35 | m.class_collisions(controller_class_path, "#{controller_class_name}Controller", "#{controller_class_name}Helper") 36 | m.class_collisions(class_path, "#{class_name}") 37 | 38 | # Controller, helper, views, and test directories. 39 | m.directory(File.join('app/models', class_path)) 40 | m.directory(File.join('app/controllers', controller_class_path)) 41 | m.directory(File.join('app/helpers', controller_class_path)) 42 | m.directory(File.join('test/unit', class_path)) 43 | 44 | m.dependency 'model', [singular_name] + @args, :collision => :skip 45 | 46 | m.template( 47 | 'controller.rb', File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb") 48 | ) 49 | 50 | m.template('helper.rb',File.join('app/helpers', controller_class_path, "#{controller_file_name}_helper.rb")) 51 | 52 | end 53 | end 54 | 55 | protected 56 | # Override with your own usage banner. 57 | def banner 58 | "Usage: #{$0} scaffold ModelName [field:type, field:type]" 59 | end 60 | 61 | def add_options!(opt) 62 | opt.separator '' 63 | opt.separator 'Options:' 64 | opt.on("--skip-timestamps", 65 | "Don't add timestamps to the migration file for this model") { |v| options[:skip_timestamps] = v } 66 | opt.on("--skip-migration", 67 | "Don't generate a migration file for this model") { |v| options[:skip_migration] = v } 68 | end 69 | 70 | def model_name 71 | class_name.demodulize 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /generators/rubyamf_scaffold/templates/controller.rb: -------------------------------------------------------------------------------- 1 | class <%= controller_class_name %>Controller < ApplicationController 2 | 3 | # return all <%= class_name.pluralize %> 4 | def find_all 5 | respond_to do |format| 6 | format.amf { render :amf => <%= class_name %>.find(:all) } 7 | end 8 | end 9 | 10 | # return a single <%= class_name %> by id 11 | # expects id in params[0] 12 | def find_by_id 13 | respond_to do |format| 14 | format.amf { render :amf => <%= class_name %>.find(params[0]) } 15 | end 16 | end 17 | 18 | # saves new or updates existing <%= class_name %> 19 | # expect params[0] to be incoming <%= class_name %> 20 | def save 21 | respond_to do |format| 22 | format.amf do 23 | @<%= table_name.singularize %> = params[0] 24 | 25 | if @<%= table_name.singularize %>.save 26 | render :amf => @<%= table_name.singularize %> 27 | else 28 | render :amf => FaultObject.new(@<%= table_name.singularize %>.errors.full_messages.join('\n')) 29 | end 30 | end 31 | end 32 | end 33 | 34 | # destroy a <%= class_name %> 35 | # expects id in params[0] 36 | def destroy 37 | respond_to do |format| 38 | format.amf do 39 | @<%= table_name.singularize %> = <%= class_name %>.find(params[0]) 40 | @<%= table_name.singularize %>.destroy 41 | 42 | render :amf => true 43 | end 44 | end 45 | end 46 | 47 | end 48 | -------------------------------------------------------------------------------- /generators/rubyamf_scaffold/templates/helper.rb: -------------------------------------------------------------------------------- 1 | module <%= controller_class_name %>Helper 2 | end 3 | -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | $:.unshift(File.expand_path(Rails::VERSION::MAJOR < 3 ? RAILS_ROOT : ::Rails.root.to_s) + '/vendor/plugins/rubyamf_plugin/') #fosrias: RAILS_ROOT deprectated in Rails 3 2 | 3 | #utils must be first 4 | require 'util/string' 5 | require 'util/vo_helper' 6 | require 'util/active_record' 7 | require 'util/action_controller' 8 | require 'app/mime_type' 9 | require 'app/fault_object' 10 | require 'app/rails_gateway' 11 | require File.expand_path(Rails::VERSION::MAJOR < 3 ? RAILS_ROOT : ::Rails.root.to_s) + '/config/rubyamf_config' #run the configuration, fosrias: RAILS_ROOT deprectated in Rails 3 12 | 13 | 14 | -------------------------------------------------------------------------------- /install.rb: -------------------------------------------------------------------------------- 1 | #This Install script is for Rails Plugin Installation. If using the RubyAMF Lite this is not needed. 2 | begin 3 | require 'fileutils' 4 | overwrite = true 5 | 6 | if !File.exist?('./config/rubyamf_config.rb') 7 | FileUtils.copy_file("./vendor/plugins/rubyamf_plugin/rails_installer_files/rubyamf_config.rb", "./config/rubyamf_config.rb", false) 8 | end 9 | 10 | FileUtils.copy_file("./vendor/plugins/rubyamf_plugin/rails_installer_files/rubyamf_controller.rb","./app/controllers/rubyamf_controller.rb",false) 11 | FileUtils.copy_file("./vendor/plugins/rubyamf_plugin/rails_installer_files/rubyamf_helper.rb","./app/helpers/rubyamf_helper.rb",false) 12 | FileUtils.copy_file("./vendor/plugins/rubyamf_plugin/rails_installer_files/crossdomain.xml","./public/crossdomain.xml", false) 13 | 14 | mime = true 15 | mime_types_file_exists = File.exists?('./config/initializers/mime_types.rb') 16 | mime_config_file = mime_types_file_exists ? './config/initializers/mime_types.rb' : './config/environment.rb' 17 | 18 | File.open(mime_config_file, "r") do |f| 19 | while line = f.gets 20 | if line.match(/application\/x-amf/) 21 | mime = false 22 | break 23 | end 24 | end 25 | end 26 | 27 | if mime 28 | File.open(mime_config_file,"a") do |f| 29 | f.puts "\nMime::Type.register \"application/x-amf\", :amf" 30 | end 31 | end 32 | 33 | route_amf_controller = true 34 | #fosrias 35 | #File.open('./config/routes.rb', 'r') do |f| 36 | # while line = f.gets 37 | # if line.match("map.rubyamf_gateway 'rubyamf_gateway', :controller => 'rubyamf', :action => 'gateway") 38 | # route_amf_controller = false 39 | # break 40 | # end 41 | # end 42 | #end 43 | 44 | #if route_amf_controller 45 | # routes = File.read('./config/routes.rb') 46 | # updated_routes = routes.gsub(/(ActionController::Routing::Routes.draw do \|map\|)/) do |s| 47 | # "#{$1}\n map.rubyamf_gateway 'rubyamf_gateway', :controller => 'rubyamf', :action => 'gateway'\n" 48 | # end 49 | # File.open('./config/routes.rb', 'w') do |file| 50 | # file.write updated_routes 51 | # end 52 | #end 53 | 54 | #fosrias: Add version correct route 55 | File.open('./config/routes.rb', 'r') do |f| 56 | while line = f.gets 57 | if line.match("map.rubyamf_gateway 'rubyamf_gateway', :controller => 'rubyamf', :action => 'gateway") && 58 | Rails::VERSION::MAJOR < 3 || line.match("match 'rubyamf/gateway', :to => 'rubyamf#gateway'") #Rails 3 route 59 | route_amf_controller = false 60 | break 61 | end 62 | end 63 | end 64 | 65 | if route_amf_controller 66 | routes = File.read('./config/routes.rb') 67 | routes_regexp = Rails::VERSION::MAJOR < 3 ? /(ActionController::Routing::Routes.draw do \|map\|)/ : /(Application.routes.draw do)/ 68 | updated_routes = routes.gsub(routes_regexp) do |s| 69 | if Rails::VERSION::MAJOR < 3 70 | "#{$1}\n map.rubyamf_gateway 'rubyamf_gateway', :controller => 'rubyamf', :action => 'gateway'\n" 71 | else 72 | "#{$1}\n match 'rubyamf/gateway', :to => 'rubyamf#gateway'\n" #Rails 3 route 73 | end 74 | end 75 | File.open('./config/routes.rb', 'w') do |file| 76 | file.write updated_routes 77 | end 78 | end 79 | #fosrias 80 | 81 | rescue Exception => e 82 | puts "ERROR INSTALLING RUBYAMF: " + e.message 83 | end -------------------------------------------------------------------------------- /io/amf_deserializer.rb: -------------------------------------------------------------------------------- 1 | module RubyAMF 2 | module IO 3 | class AMFDeserializer 4 | 5 | require 'io/read_write' 6 | 7 | include RubyAMF::AMF 8 | include RubyAMF::App 9 | include RubyAMF::Configuration 10 | include RubyAMF::Exceptions 11 | include RubyAMF::IO::BinaryReader 12 | include RubyAMF::IO::Constants 13 | include RubyAMF::VoHelper 14 | attr_accessor :stream 15 | attr_accessor :stream_position 16 | attr_accessor :amf0_object_default_members_ignore 17 | 18 | def initialize 19 | @raw = true 20 | @rawkickoff = true 21 | @stream_position = 0 22 | reset_referencables 23 | end 24 | 25 | #do an entire read operation on a complete amf request 26 | def rubyamf_read(amfobj) 27 | RequestStore.amf_encoding = 'amf0' 28 | @amfobj = amfobj 29 | @stream = @amfobj.input_stream 30 | preamble 31 | headers 32 | bodys 33 | end 34 | 35 | def reset_referencables 36 | @amf0_stored_objects = [] 37 | @amf0_object_default_members_ignore = {} 38 | @class_member_defs = {} 39 | @stored_strings = [] 40 | @stored_objects = [] 41 | @stored_defs = [] 42 | end 43 | 44 | def preamble 45 | version = read_int8 #first byte, not anything important 46 | if version != 0 && version != 3 47 | raise RUBYAMFException.new(RUBYAMFException.VERSION_ERROR, "The amf version is incorrect") 48 | end 49 | 50 | #read the client. (0x00 - Flash Player, 0x01 - FlashComm) 51 | client = read_int8 52 | end 53 | 54 | def headers 55 | @amf0_object_default_members_ignore = { 56 | 'Credentials' => true, 57 | 'coldfusion' => true, 58 | 'amfheaders' => true, 59 | 'amf' => true, 60 | 'httpheaders' => true, 61 | 'recordset' => true, 62 | 'error' => true, 63 | 'trace' => true, 64 | 'm_debug' => true} 65 | 66 | #Find total number of header elements 67 | header_count = read_word16_network 68 | 69 | 0.upto(header_count - 1) do 70 | 71 | #find the key of the header 72 | name = read_utf 73 | 74 | #Find the must understand flag 75 | required = read_booleanr 76 | 77 | #Grab the length of the header element 78 | length = read_word32_network 79 | 80 | #Grab the type of the element 81 | type = read_byte 82 | 83 | #Turn the element into real data 84 | value = read(type) 85 | 86 | #create new header 87 | header = AMFHeader.new(name,required,value) 88 | 89 | #add header to the amfbody object 90 | @amfobj.add_header(header) 91 | end 92 | end 93 | 94 | def bodys 95 | @amf0_object_default_members_ignore = {} 96 | 97 | #find the total number of body elements 98 | body_count = read_int16_network 99 | 100 | #Loop over all the body elements 101 | 0.upto(body_count - 1) do 102 | 103 | reset_referencables 104 | 105 | #The target method 106 | target = read_utf 107 | 108 | #The unique id that the client understands 109 | response = read_utf 110 | 111 | #Get the length of the body element 112 | length = read_word32_network 113 | 114 | #Grab the type of the element 115 | type = read_byte 116 | 117 | #Turn the argument elements into real data 118 | value = read(type) 119 | 120 | #new body 121 | body = AMFBody.new(target,response,value) 122 | 123 | #add the body to the amfobj 124 | @amfobj.add_body(body) 125 | end 126 | end 127 | 128 | #Reads object data by type from @input_stream 129 | def read(type) 130 | case type 131 | when AMF3_TYPE 132 | RequestStore.amf_encoding = 'amf3' 133 | read_amf3 134 | when AMF_NUMBER 135 | read_number 136 | when AMF_BOOLEAN 137 | read_booleanr 138 | when AMF_STRING 139 | read_string 140 | when AMF_OBJECT 141 | read_object 142 | when AMF_MOVIE_CLIP 143 | raise RUBYAMFException.new(RUBYAMFException.UNSUPPORTED_AMF0_TYPE, 'You cannot send a movie clip') 144 | when AMF_NULL 145 | return nil 146 | when AMF_UNDEFINED 147 | return nil 148 | when AMF_REFERENCE 149 | return nil #TODO Implement this 150 | when AMF_MIXED_ARRAY 151 | length = read_int32_network #long, don't do anything with it 152 | read_mixed_array 153 | when AMF_EOO 154 | return nil 155 | when AMF_ARRAY 156 | read_array 157 | when AMF_DATE 158 | read_date 159 | when AMF_LONG_STRING 160 | utflen = read_int32_network #don't touch the length 161 | read_utf 162 | when AMF_UNSUPPORTED 163 | raise RUBYAMFException.new(RUBYAMFException.UNSUPPORTED_AMF0_TYPE, 'Unsupported type') 164 | when AMF_RECORDSET 165 | raise RUBYAMFException.new(RUBYAMFException.UNSUPPORTED_AMF0_TYPE, 'You cannot send a RecordSet to RubyAMF, although you can receive them from RubyAMF.') 166 | when AMF_XML 167 | read_xml 168 | when AMF_TYPED_OBJECT 169 | read_custom_class 170 | end 171 | end 172 | 173 | #AMF3 174 | def read_amf3 175 | type = read_word8 176 | case type 177 | when AMF3_UNDEFINED 178 | nil 179 | when AMF3_NULL 180 | nil 181 | when AMF3_FALSE 182 | false 183 | when AMF3_TRUE 184 | true 185 | when AMF3_INTEGER 186 | read_amf3_integer 187 | when AMF3_NUMBER 188 | read_number #read standard AMF0 number, a double 189 | when AMF3_STRING 190 | read_amf3_string 191 | when AMF3_XML 192 | read_amf3_xml_string 193 | when AMF3_DATE 194 | read_amf3_date 195 | when AMF3_ARRAY 196 | read_amf3_array 197 | when AMF3_OBJECT 198 | read_amf3_object 199 | when AMF3_XML_STRING 200 | read_amf3_xml 201 | when AMF3_BYTE_ARRAY 202 | read_amf3_byte_array 203 | end 204 | end 205 | 206 | def read_amf3_integer 207 | n = 0 208 | b = read_word8||0 209 | result = 0 210 | 211 | while ((b & 0x80) != 0 && n < 3) 212 | result = result << 7 213 | result = result | (b & 0x7f) 214 | b = read_word8||0 215 | n = n + 1 216 | end 217 | 218 | if (n < 3) 219 | result = result << 7 220 | result = result | b 221 | else 222 | #Use all 8 bits from the 4th byte 223 | result = result << 8 224 | result = result | b 225 | 226 | #Check if the integer should be negative 227 | if (result > AMF3_INTEGER_MAX) 228 | result -= (1 << 29) 229 | end 230 | end 231 | return result 232 | end 233 | 234 | def read_amf3_string 235 | type = read_amf3_integer 236 | isReference = (type & 0x01) == 0 237 | if isReference 238 | reference = type >> 1 239 | if reference < @stored_strings.length 240 | if @stored_strings[reference] == nil 241 | raise( RUBYAMFException.new(RUBYAMFException.UNDEFINED_OBJECT_REFERENCE_ERROR, "Reference to non existant string at index #{reference}, please tell aaron@rubyamf.org")) 242 | end 243 | return @stored_strings[reference] 244 | else 245 | raise( RUBYAMFException.new(RUBYAMFException.UNDEFINED_STRING_REFERENCE_ERROR, "Reference to non existant string at index #{reference}, please tell aaron@rubyamf.org") ) 246 | end 247 | else 248 | 249 | length = type >> 1 250 | 251 | #Note that we have to read the string into a byte buffer and then 252 | #convert to a UTF-8 string, because for standard readUTF() it 253 | #reads an unsigned short to get the string length. 254 | #A string isn't stored as a reference if it is the empty string 255 | #thanks Karl von Randow for this 256 | if length > 0 257 | str = String.new(readn(length)) #specifically cast as string, as we're reading verbatim from the stream 258 | if RUBY_VERSION < RUBY_19 259 | str.toutf8 #convert to utf8 260 | end 261 | @stored_strings << str 262 | end 263 | return str 264 | end 265 | end 266 | 267 | def read_amf3_xml 268 | type = read_amf3_integer 269 | length = type >> 1 270 | readn(length) 271 | end 272 | 273 | def read_amf3_date 274 | type = read_amf3_integer 275 | isReference = (type & 0x01) == 0 276 | if isReference 277 | reference = type >> 1 278 | if reference < @stored_objects.length 279 | if @stored_objects[reference] == nil 280 | 281 | raise( RUBYAMFException.new(RUBYAMFException.UNDEFINED_OBJECT_REFERENCE_ERROR, "Reference to non existant date at index #{reference}, please tell aaron@rubyamf.org")) 282 | end 283 | return @stored_objects[reference] 284 | else 285 | raise( RUBYAMFException.new(RUBYAMFException.UNDEFINED_OBJECT_REFERENCE_ERROR, "Undefined date object reference when deserialing AMF3: #{reference}") ) 286 | end 287 | else 288 | seconds = read_double.to_f/1000 289 | time = if (seconds < 0) || ClassMappings.use_ruby_date_time # we can't use Time if its a negative second value 290 | seconds += (DateTime.now.offset * 60 * 60 * 24).to_i 291 | DateTime.strptime(seconds.to_s, "%s") 292 | else 293 | Time.at(seconds) 294 | end 295 | @stored_objects << time 296 | time 297 | end 298 | end 299 | 300 | def read_amf3_array 301 | type = read_amf3_integer 302 | isReference = (type & 0x01) == 0 303 | 304 | if isReference 305 | reference = type >> 1 306 | if reference < @stored_objects.length 307 | if @stored_objects[reference] == nil 308 | raise(RUBYAMFException.new(RUBYAMFException.UNDEFINED_OBJECT_REFERENCE_ERROR, "Reference to non existant array at index #{reference}, please tell aaron@rubyamf.org")) 309 | end 310 | return @stored_objects[reference] 311 | else 312 | raise Exception.new("Reference to non-existent array at index #{reference}, please tell aaron@rubyamf.org") 313 | end 314 | else 315 | length = type >> 1 316 | propertyName = read_amf3_string 317 | if propertyName != nil 318 | array = {} 319 | @stored_objects << array 320 | begin 321 | while(propertyName.length) 322 | value = read_amf3 323 | array[propertyName] = value 324 | propertyName = read_amf3_string 325 | end 326 | rescue Exception => e #end of object exception, because propertyName.length will be non existent 327 | end 328 | 0.upto(length - 1) do |i| 329 | array["" + i.to_s] = read_amf3 330 | end 331 | else 332 | array = [] 333 | @stored_objects << array 334 | 0.upto(length - 1) do 335 | array << read_amf3 336 | end 337 | end 338 | array 339 | end 340 | end 341 | 342 | def read_amf3_object 343 | type = read_amf3_integer 344 | isReference = (type & 0x01) == 0 345 | 346 | if isReference 347 | reference = type >> 1 348 | if reference < @stored_objects.length 349 | if @stored_objects[reference] == nil 350 | raise( RUBYAMFException.new(RUBYAMFException.UNDEFINED_OBJECT_REFERENCE_ERROR, "Reference to non existant object at index #{reference}, please tell aaron@rubyamf.org.")) 351 | end 352 | return @stored_objects[reference] 353 | else 354 | raise( RUBYAMFException.new(RUBYAMFException.UNDEFINED_OBJECT_REFERENCE_ERROR, "Reference to non existant object #{reference}")) 355 | end 356 | else 357 | 358 | class_type = type >> 1 359 | class_is_reference = (class_type & 0x01) == 0 360 | 361 | if class_is_reference 362 | class_reference = class_type >> 1 363 | if class_reference < @stored_defs.length 364 | class_definition = @stored_defs[class_reference] 365 | else 366 | raise RUBYAMFException.new(RUBYAMFException.UNDEFINED_DEFINITION_REFERENCE_ERROR, "Reference to non existant class definition #{class_reference}") 367 | end 368 | else 369 | actionscript_class_name = read_amf3_string 370 | externalizable = (class_type & 0x02) != 0 371 | dynamic = (class_type & 0x04) != 0 372 | attribute_count = class_type >> 3 373 | 374 | class_attributes = [] 375 | attribute_count.times{class_attributes << read_amf3_string} # Read class members 376 | 377 | class_definition = {"as_class_name" => actionscript_class_name, "members" => class_attributes, "externalizable" => externalizable, "dynamic" => dynamic} 378 | @stored_defs << class_definition 379 | end 380 | action_class_name = class_definition['as_class_name'] #get the className according to type 381 | 382 | # check to see if its the first main amf object or a flex message obj, because then we need a _explicitType field type and skip some things 383 | skip_mapping = if action_class_name && action_class_name.include?("flex.messaging") 384 | obj = VoHash.new # initialize an empty VoHash value holder 385 | obj._explicitType = action_class_name 386 | true 387 | else 388 | obj = VoUtil.get_ruby_class(action_class_name).new 389 | false 390 | end 391 | 392 | obj_position = @stored_objects.size # need to replace the object later for referencing (MUST be before inserting the object into stored_objs) 393 | @stored_objects << obj 394 | 395 | 396 | if class_definition['externalizable'] 397 | if ['flex.messaging.io.ObjectProxy','flex.messaging.io.ArrayCollection'].include?(action_class_name) 398 | obj = read_amf3 399 | else 400 | raise( RUBYAMFException.new(RUBYAMFException.USER_ERROR, "Unable to read externalizable data type #{type}")) 401 | end 402 | else 403 | translate_case = !skip_mapping&&ClassMappings.translate_case # remove the need for a method call / also, don't want to convert on main remoting object 404 | class_definition['members'].each do |key| 405 | value = read_amf3 406 | #if (value)&& value != 'NaN'# have to read key to move the reader ahead in the stream 407 | key.to_snake! if translate_case 408 | VoUtil.set_value(obj, key, value) 409 | #end 410 | end 411 | 412 | if class_definition['dynamic'] 413 | while (key = read_amf3_string) && key.length != 0 do # read next key 414 | value = read_amf3 415 | #if (value) && value != 'NaN' 416 | key.to_snake! if translate_case 417 | VoUtil.set_value(obj, key, value) 418 | #end 419 | end 420 | end 421 | VoUtil.finalize_object(obj) #Handle id, type, lock_version, timestamps, new_record, etc. (only for ActiveRecords) 422 | end 423 | obj 424 | end 425 | end 426 | 427 | def read_amf3_byte_array # according to the charles amf3 deserializer, they store byte array 428 | type = read_amf3_integer 429 | isReference = (type & 0x01) == 0 430 | if isReference 431 | reference = type >> 1 432 | if reference < @stored_objects.length 433 | if @stored_objects[reference] == nil 434 | raise( RUBYAMFException.new(RUBYAMFException.UNDEFINED_OBJECT_REFERENCE_ERROR, "Reference to non existant byteArray at index #{reference}, please tell aaron@rubyamf.org")) 435 | end 436 | return @stored_objects[reference] 437 | else 438 | raise( RUBYAMFException.new(RUBYAMFException.UNDEFINED_OBJECT_REFERENCE_ERROR, "Reference to non existant byteArray #{reference}")) 439 | end 440 | else 441 | length = type >> 1 442 | begin # first assume its gzipped and rescue an exception if its not 443 | inflated_stream = Zlib::Inflate.inflate( self.stream[self.stream_position,length] ) 444 | arr = inflated_stream.unpack('c'*inflated_stream.length) 445 | rescue Exception => e 446 | arr = self.stream[self.stream_position,length].unpack('c'*length) 447 | end 448 | self.stream_position += length 449 | @stored_objects << arr 450 | arr 451 | end 452 | end 453 | 454 | #AMF0 455 | def read_number 456 | res = read_double 457 | res.is_a?(Float)&&res.nan? ? nil : res # check for NaN and convert them to nil 458 | end 459 | 460 | def read_booleanr 461 | read_boolean 462 | end 463 | 464 | def read_string 465 | read_utf 466 | end 467 | 468 | def read_array 469 | ret = [] #create new array 470 | length = read_word32_network # Grab the length of the array 471 | 472 | #catch empty arguments 473 | if !length 474 | return [] 475 | end 476 | 477 | #Loop over all the elements in the data 478 | 0.upto(length - 1) do 479 | type = read_byte #Grab the type for each element 480 | data = read(type) #Grab the element 481 | ret << data 482 | end 483 | ret 484 | end 485 | 486 | def read_date 487 | #epoch time comes in millis, convert to seconds. 488 | seconds = read_double.to_f / 1000 489 | 490 | #flash client timezone offset (which comes in minutes, 491 | #but incorrectly signed), convert to seconds, and fix the sign. 492 | client_zone_offset = (read_int16_network) * 60 * -1 # now we have seconds 493 | 494 | #get server timezone offset 495 | server_zone_offset = Time.zone_offset(Time.now.zone) 496 | 497 | # adjust the timezone with the offsets 498 | seconds += (client_zone_offset - server_zone_offset) 499 | 500 | #TODO: handle daylight savings 501 | #sent_time_zone = sent.get_time_zone 502 | #we have to handle daylight savings ms as well 503 | #if (sent_time_zone.in_daylight_time(sent.get_time)) 504 | #sent.set_time_in_millis(sent.get_time_in_millis - sent_time_zone.get_dst_savings) 505 | #end 506 | 507 | #diff the timezone offset's and subtract from seconds 508 | #create a time object from the result 509 | if (seconds < 0) || ClassMappings.use_ruby_date_time # we can't use Time if its a negative second value 510 | DateTime.strptime(seconds.to_s, "%s") 511 | else 512 | Time.at(seconds) 513 | end 514 | end 515 | 516 | #Reads and instantiates a custom incoming ValueObject 517 | def read_custom_class 518 | type = read_utf 519 | value = read_object 520 | #if type not nil and it is an VoHash, check VO Mapping 521 | if type && value.is_a?(VoHash) 522 | vo = VoUtil.get_vo_for_incoming(value,type) 523 | value = vo 524 | end 525 | value #return value if no VO was created 526 | end 527 | 528 | #reads a mixed array 529 | def read_mixed_array 530 | mix_array = Hash.new 531 | key = read_utf 532 | type = read_byte 533 | while(type != 9) 534 | value = read(type) 535 | mix_array[key] = value 536 | key = read_utf 537 | type = read_byte 538 | end 539 | mix_array 540 | end 541 | 542 | def read_object 543 | obj = VoHash.new 544 | key = read_utf #read the value's key 545 | type = read_byte #read the value's type 546 | while (type != 9) do 547 | value = read(type) 548 | key.to_snake! if ClassMappings.translate_case 549 | obj[key] = value 550 | key = read_utf # Read the next key 551 | type = read_byte # Read the next type 552 | end 553 | obj 554 | end 555 | 556 | def read_xml 557 | read_long_utf 558 | end 559 | end 560 | end 561 | end 562 | -------------------------------------------------------------------------------- /io/amf_serializer.rb: -------------------------------------------------------------------------------- 1 | module RubyAMF 2 | module IO 3 | class AMFSerializer 4 | 5 | require 'io/read_write' 6 | 7 | include RubyAMF::AMF 8 | include RubyAMF::Configuration 9 | include RubyAMF::App 10 | include RubyAMF::IO::BinaryWriter 11 | include RubyAMF::IO::Constants 12 | include RubyAMF::VoHelper 13 | attr_accessor :stream 14 | 15 | def initialize(amfobj) 16 | @amfobj = amfobj 17 | @stream = @amfobj.output_stream #grab the output stream for the amfobj 18 | end 19 | 20 | def reset_referencables 21 | @amf0_stored_objects = [] 22 | @stored_objects = {} 23 | @stored_objects_count = 0; 24 | @stored_strings = {} # hash is way faster than array 25 | @stored_strings[""] = true # add this in automatically 26 | @floats_cache = {} 27 | @write_amf3_integer_results = {} # cache the integers 28 | @current_strings_index = 0 29 | end 30 | 31 | def run 32 | #write the amf version 33 | write_int16_network(0) 34 | @header_count = @amfobj.num_outheaders 35 | write_int16_network(@header_count) 36 | 37 | 0.upto(@header_count - 1) do |i| 38 | #get the header obj at index 39 | @header = @amfobj.get_outheader_at(i) 40 | 41 | #write the header name 42 | write_utf(@header.name) 43 | 44 | #write the version 45 | write_byte(@header.required) 46 | write_word32_network(-1) #the usual four bytes of FF 47 | 48 | #write the header data 49 | write(@header.value) 50 | end 51 | 52 | #num bodies 53 | @body_count = @amfobj.num_body 54 | write_int16_network(@body_count) 55 | 56 | 0.upto(@body_count - 1) do |i| 57 | reset_referencables #reset any stored references in this scope 58 | 59 | #get the body obj at index 60 | @body = @amfobj.get_body_at(i) 61 | 62 | #write the response uri 63 | write_utf(@body.response_uri) 64 | 65 | #write null (usually target, no use for though) 66 | write_utf("null") 67 | write_word32_network(-1) #the usual four bytes of FF 68 | 69 | #write the results of the service call 70 | write(@body.results) 71 | end 72 | end 73 | 74 | #write Ruby data as AMF to output stream 75 | def write(value) 76 | if RequestStore.amf_encoding == 'amf3' 77 | write_byte(AMF3_TYPE) 78 | write_amf3(value) 79 | 80 | elsif value == nil 81 | @stream << "\005" #write_null 82 | 83 | elsif (value.is_a?(Numeric)) 84 | write_number(value) 85 | 86 | elsif (value.is_a?(String)) 87 | write_string(value) 88 | 89 | elsif (value.is_a?(TrueClass) || value.is_a?(FalseClass)) 90 | (value) ? @stream << "\001" : @stream << "\000" 91 | 92 | elsif value.is_a?(ActiveRecord::Base) # Aryk: this way, we can bypass the next four checks most of the time 93 | write_object(VoUtil.get_vo_hash_for_outgoing(value)) 94 | 95 | elsif(value.is_a?(VoHash)) 96 | write_object(value) 97 | 98 | elsif (value.is_a?(Array)) 99 | write_array(value) 100 | 101 | elsif (value.is_a?(Hash)) 102 | write_hash(value) 103 | 104 | elsif (value.is_a?(Date)) 105 | write_date(value.strftime("%s").to_i) # Convert a Date into a time object 106 | 107 | elsif (value.is_a?(Time)) 108 | write_date(value.to_f) 109 | 110 | elsif value.class.to_s == 'REXML::Document' 111 | write_xml(value.write.to_s) 112 | 113 | elsif (value.class.to_s == 'BeautifulSoup') 114 | write_xml(value.to_s) 115 | else 116 | write_object(VoUtil.get_vo_hash_for_outgoing(value)) 117 | end 118 | end 119 | 120 | #AMF3 121 | def write_amf3(value) 122 | if value == nil 123 | @stream << "\001" # represents an amf3 null 124 | 125 | elsif (value.is_a?(TrueClass) || value.is_a?(FalseClass)) 126 | value ? (@stream << "\003") : (@stream << "\002") # represents an amf3 true and false 127 | 128 | elsif value.is_a?(Numeric) 129 | if value.is_a?(Integer) # Aryk: This was also incorrect before because you has Bignum check AFTER the Integer check, which means the Bignum's were getting picked up by Integers 130 | if value.is_a?(Bignum) 131 | @stream << "\005" # represents an amf3 complex number 132 | write_double(value) 133 | else 134 | write_amf3_number(value) 135 | end 136 | elsif(value.is_a?(Float)) 137 | @stream << "\005" # represents an amf3 complex number 138 | write_double(value) 139 | elsif value.is_a?(BigDecimal) # Aryk: BigDecimal does not relate to Float, so keep it as a seperate check. 140 | # TODO: Aryk: Not quite sure why you do value.to_s.to_f? can't you just do value.to_f? 141 | value = value.to_s('F').to_f #this is turning a string into a Ruby Float, but because there are no further operations on it it is safe 142 | @stream << "\005" # represents an amf3 complex number 143 | write_double(value) 144 | end 145 | 146 | elsif(value.is_a?(String)) 147 | @stream << "\006" # represents an amf3 string 148 | write_amf3_string(value) 149 | 150 | elsif(value.is_a?(Array)) 151 | write_amf3_array(value) 152 | 153 | elsif(value.is_a?(Hash)) 154 | write_amf3_object(value) 155 | 156 | elsif (value.is_a?(Time)||value.is_a?(Date)) 157 | @stream << "\b" # represents an amf3 date 158 | write_amf3_date(value) 159 | 160 | # I know we can combine this with the last condition, but don't ; the Rexml and Beautiful Soup test is expensive, and for large record sets with many AR its better to be able to skip the next step 161 | elsif value.is_a?(ActiveRecord::Base) # Aryk: this way, we can bypass the "['REXML::Document', 'BeautifulSoup'].include?(value.class.to_s) " operation 162 | write_amf3_object(value) 163 | 164 | elsif ['REXML::Document', 'BeautifulSoup'].include?(value.class.to_s) 165 | write_byte(AMF3_XML) 166 | write_amf3_xml(value) 167 | 168 | elsif value.is_a?(Object) 169 | write_amf3_object(value) 170 | end 171 | end 172 | 173 | def write_amf3_integer(int) 174 | @stream << (@write_amf3_integer_results[int] ||= ( 175 | int = int & 0x1fffffff 176 | if(int < 0x80) 177 | [int].pack('c') 178 | elsif(int < 0x4000) 179 | [int >> 7 & 0x7f | 0x80].pack('c')+ 180 | [int & 0x7f].pack('c') 181 | elsif(int < 0x200000) 182 | [int >> 14 & 0x7f | 0x80].pack('c')+ 183 | [int >> 7 & 0x7f | 0x80].pack('c')+ 184 | [int & 0x7f].pack('c') 185 | else 186 | [int >> 22 & 0x7f | 0x80].pack('c')+ 187 | [int >> 15 & 0x7f | 0x80].pack('c')+ 188 | [int >> 8 & 0x7f | 0x80].pack('c')+ 189 | [int & 0xff].pack('c') 190 | end 191 | )) 192 | end 193 | 194 | def write_amf3_number(number) 195 | if(number >= AMF3_INTEGER_MIN && number <= AMF3_INTEGER_MAX) #check valid range for 29bits 196 | @stream << "\004" # represents an amf3 integer 197 | write_amf3_integer(number) 198 | else #overflow condition otherwise 199 | @stream << "\005" # represents an amf3 complex number 200 | write_double(number) 201 | end 202 | end 203 | 204 | def write_amf3_string(string) 205 | if index = @stored_strings[string] 206 | if string == "" # store this initially so it gets caught by the stored_strings check 207 | @stream << "\001" # represents an amf3 empty string 208 | else 209 | reference = index << 1 210 | write_amf3_integer(reference) 211 | end 212 | else 213 | @stored_strings[string] = @current_strings_index 214 | @current_strings_index += 1 # increment the index 215 | reference = string.length 216 | reference = reference << 1 217 | reference = reference | 1 218 | write_amf3_integer(reference) 219 | writen(string) 220 | end 221 | end 222 | 223 | def write_amf3_object(value) 224 | # Check if this object has already been written (for circular references) 225 | i = @stored_objects[value] 226 | if i != nil 227 | @stream << "\n" 228 | reference = i << 1 229 | write_amf3_integer(reference) 230 | else 231 | hash = value.is_a?(Hash) ? value : VoUtil.get_vo_hash_for_outgoing(value) 232 | not_vo_hash = !hash.is_a?(VoHash) # is this not a vohash - then doesnt have an _explicitType parameter 233 | @stream << "\n\v" # represents an amf3 object and dynamic object 234 | store_object value # add object here for circular references 235 | not_vo_hash || !hash._explicitType ? (@stream << "\001") : write_amf3_string(hash._explicitType) 236 | hash.each do |attr, attvalue| # Aryk: no need to remove any "_explicitType" or "rmember" key since they werent added as keys 237 | if not_vo_hash # then that means that the attr might not be symbols and it hasn't gone through camelizing if thats needed 238 | attr = attr.to_s.dup # need this just in case its frozen 239 | attr.to_camel! if ClassMappings.translate_case 240 | end 241 | write_amf3_string(attr) # write property name 242 | attvalue.nil? ? (@stream << "\001") : write_amf3(attvalue) # if value is nil, write an amf null, otherwise, write value 243 | end 244 | @stream << "\001" # represents an amf3 empty string to close open object 245 | end 246 | end 247 | 248 | def write_amf3_array(array) 249 | i = @stored_objects[array.object_id] 250 | if i != nil 251 | ClassMappings.use_array_collection ? @stream << "\n" : @stream << "\t" 252 | reference = i << 1 253 | write_amf3_integer(reference) 254 | else 255 | if ClassMappings.use_array_collection 256 | @stream << "\n\a" # AMF3_OBJECT and AMF3_XML 257 | write_amf3_string("flex.messaging.io.ArrayCollection") 258 | @stored_objects_count += 1 259 | end 260 | store_object array 261 | @stream << "\t" # represents an amf3 array 262 | write_amf3_integer(array.length << 1 | 1) 263 | @stream << "\001" # represents an amf3 empty string #write empty for string keyed elements here, as it's never allowed from ruby 264 | array.each { |v| write_amf3(v) } 265 | end 266 | end 267 | 268 | def write_amf3_date(datetime) # Aryk: Dates will almost never be the same, so turn off the storing_objects 269 | store_object datetime # we may not do lookups, but dates are still end up in the object table, so we need to add them here to keep the right counts 270 | write_amf3_integer(1) 271 | seconds = if datetime.is_a?(Time) 272 | datetime.utc unless datetime.utc? 273 | datetime.to_f 274 | elsif datetime.is_a?(Date) # this also handles the case for DateTime 275 | datetime.strftime("%s").to_i 276 | # datetime = Time.gm( datetime.year, datetime.month, datetime.day ) 277 | # datetime = Time.gm( datetime.year, datetime.month, datetime.day, datetime.hour, datetime.min, datetime.sec ) 278 | end 279 | write_double( (seconds*1000).to_i ) # used to be total_milliseconds = datetime.to_i * 1000 + ( datetime.usec/1000 ) 280 | end 281 | 282 | def write_amf3_xml(value) 283 | xml = value.to_s 284 | a = xml.strip 285 | if(a != nil) 286 | b = a.gsub!(/\>(\n|\r|\r\n| |\t)*\<') #clean whitespace 287 | else 288 | b = xml.gsub!(/\>(\n|\r|\r\n| |\t)*\<') #clean whitespace 289 | end 290 | write_amf3_string(b) 291 | end 292 | 293 | #AMF0 294 | def write_null 295 | @stream << "\005" #write_byte(5) 296 | end 297 | 298 | def write_number(numeric) 299 | @stream << "\000" #write_byte(0) 300 | write_double(numeric) 301 | end 302 | 303 | def write_string(string) 304 | @stream << "\002" #write_byte(2) 305 | write_utf(string.to_s) 306 | end 307 | 308 | def write_booleanr(bool) 309 | @stream << "\001" #write_byte(1) 310 | (bool) ? @stream << "\001" : @stream << "\000" #write_boolean(bool) 311 | end 312 | 313 | def write_date(seconds) 314 | @stream << "\v" #write_byte(11) 315 | write_double(seconds * 1000) 316 | offset = Time.zone_offset(Time.now.zone) 317 | write_int16_network(offset / 60 * -1) 318 | end 319 | 320 | def write_array(array) 321 | @stream << "\n" #write_byte(10) 322 | write_word32_network(array.length) 323 | array.each do |el| 324 | write(el) 325 | end 326 | end 327 | 328 | def write_hash(hash) 329 | @stream << "\003" #write_byte(3) 330 | hash.each do |key, value| 331 | key.to_s.dup.to_camel! if ClassMappings.translate_case 332 | write_utf(key) 333 | write(value) 334 | end 335 | #write the end object flag 0x00, 0x00, 0x09 336 | write_int16_network(0) 337 | @stream << "\t" #write_byte(9) 338 | end 339 | 340 | def write_object(vohash) 341 | if vohash.is_a?(VoHash) && vohash._explicitType 342 | @stream << "\020" #write_byte(16) #custom class 343 | write_utf(vohash._explicitType) 344 | else 345 | @stream << "\003" #write_byte(3) 346 | end 347 | 348 | vohash.each do |key,val| 349 | key.to_s.dup.to_camel! if ClassMappings.translate_case 350 | write_utf(key) 351 | write(val) 352 | end 353 | 354 | #write the end object flag 0x00, 0x00, 0x09 355 | write_int16_network(0) 356 | @stream << "\t" #write_byte(9) 357 | end 358 | 359 | def write_xml(xml_string) 360 | write_byte(AMF_XML) 361 | write_long_utf(xml_string.to_s) 362 | end 363 | 364 | protected 365 | def store_object(obj) 366 | @stored_objects[obj] = @stored_objects_count 367 | @stored_objects_count += 1 368 | end 369 | end 370 | end 371 | end 372 | -------------------------------------------------------------------------------- /io/read_write.rb: -------------------------------------------------------------------------------- 1 | require 'app/request_store' 2 | begin 3 | module RubyAMF 4 | module IO 5 | module Constants 6 | #AMF0 7 | AMF_NUMBER = 0x00 8 | AMF_BOOLEAN = 0x01 9 | AMF_STRING = 0x02 10 | AMF_OBJECT = 0x03 11 | AMF_MOVIE_CLIP = 0x04 12 | AMF_NULL = 0x05 13 | AMF_UNDEFINED = 0x06 14 | AMF_REFERENCE = 0x07 15 | AMF_MIXED_ARRAY = 0x08 16 | AMF_EOO = 0x09 17 | AMF_ARRAY = 0x0A 18 | AMF_DATE = 0x0B 19 | AMF_LONG_STRING = 0x0C 20 | AMF_UNSUPPORTED = 0x0D 21 | AMF_RECORDSET = 0x0E 22 | AMF_XML = 0x0F 23 | AMF_TYPED_OBJECT = 0x10 24 | 25 | #AMF3 26 | AMF3_TYPE = 0x11 27 | AMF3_UNDEFINED = 0x00 28 | AMF3_NULL = 0x01 29 | AMF3_FALSE = 0x02 30 | AMF3_TRUE = 0x03 31 | AMF3_INTEGER = 0x04 32 | AMF3_NUMBER = 0x05 33 | AMF3_STRING = 0x06 34 | AMF3_XML = 0x07 35 | AMF3_DATE = 0x08 36 | AMF3_ARRAY = 0x09 37 | AMF3_OBJECT = 0x0A 38 | AMF3_XML_STRING = 0x0B 39 | AMF3_BYTE_ARRAY = 0x0C 40 | AMF3_INTEGER_MAX = 268435455 41 | AMF3_INTEGER_MIN = -268435456 42 | end 43 | module BinaryReader 44 | include RubyAMF::App 45 | 46 | Native = :Native 47 | Big = BigEndian = Network = :BigEndian 48 | Little = LittleEndian = :LittleEndian 49 | 50 | #examines the locale byte order on the running machine 51 | def byte_order 52 | if [0x12345678].pack("L") == "\x12\x34\x56\x78" 53 | :BigEndian 54 | else 55 | :LittleEndian 56 | end 57 | end 58 | 59 | def byte_order_little? 60 | (byte_order == :LittleEndian) ? true : false; 61 | end 62 | 63 | def byte_order_big? 64 | (byte_order == :BigEndian) ? true : false; 65 | end 66 | alias :byte_order_network? :byte_order_big? 67 | 68 | #read N length from stream starting at position 69 | def readn(length) 70 | self.stream_position ||= 0 71 | str = self.stream[self.stream_position, length] 72 | self.stream_position += length 73 | str 74 | end 75 | 76 | #reada a boolean 77 | def read_boolean 78 | d = self.stream[self.stream_position,1].unpack('c').first 79 | self.stream_position += 1 80 | (d == 1) ? true : false; 81 | end 82 | 83 | #8bits no byte order 84 | def read_int8 85 | d = self.stream[self.stream_position,1].unpack('c').first 86 | self.stream_position += 1 87 | d 88 | end 89 | alias :read_byte :read_int8 90 | 91 | # Aryk: TODO: This needs to be written more cleanly. Using rescue and then regex checks on top of that slows things down 92 | def read_word8 93 | begin 94 | d = self.stream[self.stream_position,1].unpack('C').first 95 | self.stream_position += 1 96 | d 97 | rescue Exception => e 98 | #this handles an exception condition when Rails' 99 | #ActionPack strips off the last "\000" of the AMF stream 100 | self.stream_position += 1 101 | return 0 102 | end 103 | end 104 | 105 | #16 bits Unsigned 106 | def read_word16_native 107 | d = self.stream[self.stream_position,2].unpack('S').first 108 | self.stream_position += 2 109 | d 110 | end 111 | 112 | def read_word16_little 113 | d = self.stream[self.stream_position,2].unpack('v').first 114 | self.stream_position += 2 115 | d 116 | end 117 | 118 | def read_word16_network 119 | d = self.stream[self.stream_position,2].unpack('n').first 120 | self.stream_position += 2 121 | d 122 | end 123 | 124 | #16 bits Signed 125 | def read_int16_native 126 | str = self.readn(2).unpack('s').first 127 | end 128 | 129 | def read_int16_little 130 | str = self.readn(2) 131 | str.reverse! if byte_order_network? # swap bytes as native=network (and we want little) 132 | str.unpack('s').first 133 | end 134 | 135 | def read_int16_network 136 | str = self.readn(2) 137 | str.reverse! if byte_order_little? # swap bytes as native=little (and we want network) 138 | str.unpack('s').first 139 | end 140 | 141 | #32 bits unsigned 142 | def read_word32_native 143 | d = self.stream[self.stream_position,4].unpack('L').first 144 | self.stream_position += 4 145 | d 146 | end 147 | 148 | def read_word32_little 149 | d = self.stream[self.stream_position,4].unpack('V').first 150 | self.stream_position += 4 151 | d 152 | end 153 | 154 | def read_word32_network 155 | d = self.stream[self.stream_position,4].unpack('N').first 156 | self.stream_position += 4 157 | d 158 | end 159 | 160 | #32 bits signed 161 | def read_int32_native 162 | d = self.stream[self.stream_position,4].unpack('l').first 163 | self.stream_position += 4 164 | d 165 | end 166 | 167 | def read_int32_little 168 | str = readn(4) 169 | str.reverse! if byte_order_network? # swap bytes as native=network (and we want little) 170 | str.unpack('l').first 171 | end 172 | 173 | def read_int32_network 174 | str = readn(4) 175 | str.reverse! if byte_order_little? # swap bytes as native=little (and we want network) 176 | str.unpack('l').first 177 | end 178 | 179 | 180 | #UTF string 181 | def read_utf 182 | length = self.read_word16_network 183 | readn(length) 184 | end 185 | 186 | def read_int32_network 187 | str = self.readn(4) 188 | str.reverse! if byte_order_little? # swap bytes as native=little (and we want network) 189 | str.unpack('l').first 190 | end 191 | 192 | def read_double 193 | d = self.stream[self.stream_position,8].unpack('G').first 194 | self.stream_position += 8 195 | d 196 | end 197 | 198 | def read_long_utf(length) 199 | length = read_word32_network #get the length of the string (1st 4 bytes) 200 | self.readn(length) #read length number of bytes 201 | end 202 | end 203 | 204 | 205 | module BinaryWriter 206 | 207 | #examines the locale byte order on the running machine 208 | def byte_order 209 | if [0x12345678].pack("L") == "\x12\x34\x56\x78" 210 | :BigEndian 211 | else 212 | :LittleEndian 213 | end 214 | end 215 | 216 | def byte_order_little? 217 | (byte_order == :LittleEndian) ? true : false; 218 | end 219 | 220 | def byte_order_big? 221 | (byte_order == :BigEndian) ? true : false; 222 | end 223 | alias :byte_order_network? :byte_order_big? 224 | 225 | def writen(val) 226 | @stream << val 227 | end 228 | 229 | #8 bit no byteorder 230 | def write_word8(val) 231 | self.stream << [val].pack('C') 232 | end 233 | 234 | def write_int8(val) 235 | self.stream << [val].pack('c') 236 | end 237 | 238 | #16 bit unsigned 239 | def write_word16_native(val) 240 | self.stream << [val].pack('S') 241 | end 242 | 243 | def write_word16_little(val) 244 | str = [val].pack('S') 245 | str.reverse! if byte_order_network? # swap bytes as native=network (and we want little) 246 | self.stream << str 247 | end 248 | 249 | def write_word16_network(val) 250 | str = [val].pack('S') 251 | str.reverse! if byte_order_little? # swap bytes as native=little (and we want network) 252 | self.stream << str 253 | end 254 | 255 | #16 bits signed 256 | def write_int16_native(val) 257 | self.stream << [val].pack('s') 258 | end 259 | 260 | def write_int16_little(val) 261 | self.stream << [val].pack('v') 262 | end 263 | 264 | def write_int16_network(val) 265 | self.stream << [val].pack('n') 266 | end 267 | 268 | #32 bit unsigned 269 | def write_word32_native(val) 270 | self.stream << [val].pack('L') 271 | end 272 | 273 | def write_word32_little(val) 274 | str = [val].pack('L') 275 | str.reverse! if byte_order_network? # swap bytes as native=network (and we want little) 276 | self.stream << str 277 | end 278 | 279 | def write_word32_network(val) 280 | str = [val].pack('L') 281 | str.reverse! if byte_order_little? # swap bytes as native=little (and we want network) 282 | self.stream << str 283 | end 284 | 285 | #32 signed 286 | def write_int32_native(val) 287 | self.stream << [val].pack('l') 288 | end 289 | 290 | def write_int32_little(val) 291 | self.stream << [val].pack('V') 292 | end 293 | 294 | def write_int32_network(val) 295 | self.stream << [val].pack('N') 296 | end 297 | 298 | # write utility methods 299 | def write_byte(val) 300 | #self.write_int8(val) 301 | @stream << [val].pack('c') 302 | end 303 | 304 | def write_boolean(val) 305 | if val then self.write_byte(1) else self.write_byte(0) end 306 | end 307 | 308 | def write_utf(str) 309 | self.write_int16_network(str.length) 310 | self.stream << str 311 | end 312 | 313 | def write_long_utf(str) 314 | self.write_int32_network(str.length) 315 | self.stream << str 316 | end 317 | 318 | def write_double(val) 319 | self.stream << ( @floats_cache[val] ||= 320 | [val].pack('G') 321 | ) 322 | #puts "WRITE DOUBLE" 323 | #puts @floats_cache 324 | end 325 | end 326 | end 327 | end 328 | rescue Exception => e 329 | raise RUBYAMFException.new(RUBYAMFException.AMF_ERROR, "The AMF data is incorrect or incomplete.") 330 | end 331 | -------------------------------------------------------------------------------- /rails_installer_files/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /rails_installer_files/rubyamf_config.rb: -------------------------------------------------------------------------------- 1 | require 'app/configuration' 2 | module RubyAMF 3 | module Configuration 4 | #set the service path used in all requests 5 | # RubyAMF::App::RequestStore.service_path = File.expand_path(Rails::VERSION::MAJOR < 3 ? RAILS_ROOT : ::Rails.root.to_s) + '/app/controllers' RAILS_ROOT deprectated in Rails 3 6 | 7 | # => CLASS MAPPING CONFIGURATION 8 | 9 | # => Global Property Ignoring 10 | # By putting attribute names into this array, you opt in to globally ignore these properties on incoming objects. 11 | # If you want to ignore specific properties on certain objects, use the :ignore_fields property in a 12 | # Class Mapping definition (see CLASS MAPPING DEFINITIONS) 13 | # ClassMappings.ignore_fields = ['created_at','created_on','updated_at','updated_on'] 14 | 15 | # => Case Translations 16 | # Most actionscript uses camelCase instead of snake_case. Set ClassMappings.translate_case to true if want translations to occur. 17 | # The translations only occur on object properties 18 | # An incoming property like: myProperty gets turned into my_property 19 | # An outgoing property like my_property gets turned into myProperty 20 | #ClassMappings.translate_case = false 21 | 22 | # => Force Active Record Ids 23 | # includes the id field for activerecord objects even if you don't specify it when using custom attributes. This is important for deserialization 24 | # where ids are needed to keep active record association integrity. 25 | # ClassMappings.force_active_record_ids = true 26 | 27 | # => Hash key access 28 | # You can choose how keys in hashes are created. As :string, :symbol, or :indifferent. 29 | # :string creates keys as hash['key'] 30 | # :symbol creates keys as hash[:key] 31 | # :indifferent uses rails HashWithIndifferentAccess so you can use hash[:key] of hash['key'] 32 | # There are performance issues with HashWithIndifferentAccess. Use :symbol or :string for best performance. 33 | # The default is :symbol 34 | # ClassMappings.hash_key_access = :symbol 35 | 36 | # => Assume Class Types 37 | # This tells RubyAMF to assume class type transfers. So when you register a class Alias from Flash or Flex like this: 38 | # Flash:: fl.net.registerClassAlias('User',User) 39 | # Flex:: [RemoteClass(alias='User')] 40 | # RubyAMF will automagically convert it to a User active record without you having to create a class mapping. 41 | # This also works with non active record class mappings. See the wiki on the google code page for a downloadable example. 42 | # ClassMappings.assume_types = false 43 | 44 | # => Class Mapping Definitions 45 | # A Class Mapping definition conists of at least these two properties: 46 | # :actionscript # The incoming action script class to watch for 47 | # :ruby # The ruby class to turn it into 48 | # 49 | # => Optional value object properties: 50 | # :type # Used to spectify the type of VO, valid options are 'active_record', 'active_resource', 'custom', (or don't specify at all) 51 | # :associations # Specify which associations to read on the active record (only applies to active records) 52 | # :attributes # Specifically which attributes to include in the serialization 53 | # :methods # An array of methods to call and place values in a similarly named attribute on the Actionscript Object (outgoing, or Rails => Actionscript only) 54 | # :ignore_fields # An array of field names you want to ignore on incoming classes 55 | # 56 | # If you are using ActiveRecord VO's you do not need to specify a fully qualified class path to the model, you can just define the class name, 57 | # EX: ClassMappings.register(:actionscript => 'vo.Person', :ruby => 'Person', :type => 'active_record') 58 | # 59 | # If you are using custom VO's you would need to specify the fully qualified class path to the file 60 | # EX: ClassMappings.register(:actionscript => 'vo.Person', :ruby => 'org.mypackage.Person') 61 | # 62 | # ClassMappings.register(:actionscript => 'Person', :ruby => 'Person', :type => 'active_record', :attributes => ["id", "address_id"]) 63 | # ClassMappings.register(:actionscript => 'User', :ruby => 'User', :type => 'active_record', :associations=> ["addresses", "credit_cards"]) 64 | # ClassMappings.register(:actionscript => 'Address', :ruby => 'Address', :type => 'active_record') 65 | # ClassMappings.register(:actionscript => 'User', :ruby => 'User', :type => 'active_record', :associations=> ["addresses", "credit_cards"], :methods => ["friends"]) 66 | # 67 | # => Class Mapping Scope (Advanced Usage) 68 | # You can also specify a class mapping scope if you want. For example, lets say you need certain attributes for a book when you are viewing a book 69 | # in flex as opposed to editing a book (where you would need more parameters). You can define a scope mapping parameter for ":attributes" 70 | # or for ":associations." You're mapping would look something like this. 71 | # ClassMappings.register( 72 | # :actionscript => 'com.mixbook.vo.books.BookVO', 73 | # :ruby => 'Book', 74 | # :type => 'active_record', 75 | # :associations => ["access_info", "pages", "page_ratio"], 76 | # :attributes => {:viewing => ["description", "title"], :editing => ["id","published_at","theme_id"] } <=== notice the hash instead of an array 77 | # 78 | # Now, to call the class mapping scope of editing (you are sending objects to the editing application), your controller call would look like this: 79 | # EX: render :amf => book, :class_mapping_scope => :editing 80 | # 81 | # You can also specify a default scope to use. If you don't set this and you don't specify a class mapping scope on an attribute or association, then 82 | # it will not have a scope to use and will not add any attributes or associations (whichever it cant match) to that association. 83 | # ClassMappings.default_mapping_scope = :viewing 84 | 85 | # => Date Conversion 86 | # Incoming dates from Flash by default are Time objects, this can conver to DateTime if needed 87 | # ClassMappings.use_ruby_date_time = false 88 | 89 | # => Use Array Collection 90 | # By setting this to true, you opt in to using array collections for all the arrays generated by the body of your request. 91 | # Note: This only works for amf3 with Remote Object, NOT with Net Connection. 92 | # ClassMappings.use_array_collection = false 93 | 94 | # => Check for Associations 95 | # Enabling this will automagically pick up eager loaded association data on objects returned through RubyAMF. 96 | # If this is disabled, you will need to specify any associations you DO want picked up in the ClassMapping 97 | # ClassMappings.check_for_associations = true 98 | 99 | # => NAMED PARAMETER MAPPING CONFIGURATION 100 | 101 | #=> Always Put Remoting Parameters into the "params" hash 102 | # If set to true, arguments from Flash/Flex will come in to the controllers as params[0], params[1], etc.. This is especally useful if you are sending huge objects 103 | # from Flex into Ruby so it doesnt eat up all your output window with outputting the params in the controller/action header information while in dev mode. 104 | # Even if its set to false, if you specify specific ParameterMappings, those will still get entered as the param keys you specify. Likewise, you 105 | # always have access to the parameters from rubyamf in your controller by calling rubyamf_params[0], rubyamf_params[1], etc regardless of 106 | # if it this is set or not. 107 | # ParameterMappings.always_add_to_params = true 108 | 109 | # => Return Top Level Hash 110 | # For those scaffolding users out there, who want the top-level object to come as a hash so scaffolding works out of the box. 111 | # ParameterMappings.scaffolding = false 112 | 113 | # => Incoming Remoting Parameter Mappings 114 | # Incoming Remoting Parameter mappings allow you to map an incoming requests parameters into rails' params hash 115 | # 116 | # Here's an example: 117 | # ParameterMappings.register(:controller => :UserController, :action => :find_friend, :params => { :friend => "[0]['friend']" }) 118 | end 119 | end -------------------------------------------------------------------------------- /rails_installer_files/rubyamf_controller.rb: -------------------------------------------------------------------------------- 1 | class RubyamfController < ActionController::Base 2 | 3 | include RubyAMF::App 4 | 5 | def gateway 6 | RequestStore.rails_authentication = nil #clear auth hash 7 | RequestStore.rails_request = request 8 | RequestStore.rails_response = response 9 | 10 | #Compress the amf output for smaller data transfer over the wire 11 | RequestStore.gzip = request.env['ACCEPT_ENCODING'].to_s.match(/gzip,[\s]{0,1}deflate/) 12 | 13 | #if not flash user agent, send some html content 14 | amf_response = if request.env['CONTENT_TYPE'].to_s.match(/x-amf/) 15 | headers['Content-Type'] = "application/x-amf" 16 | RailsGateway.new.service(request.raw_post) #send the raw data throught the rubyamf gateway and create the response 17 | else 18 | headers['Content-Type'] = "text/html" 19 | welcome_screen_html # load in some stub html 20 | end 21 | 22 | # this is a fugly patch, but I'm not sure what the real problem is. Content-Type is set for gateway requests 23 | # and type is set for other requests. 24 | headers['Content-Type'] ||= headers['type'] 25 | 26 | #render the AMF 27 | send_data(amf_response, {:type => headers['Content-Type'], :disposition => 'inline'}) 28 | rescue Exception => e #only errors in this scope will ever be rescued here, see BatchFiler 29 | STDOUT.puts e.to_s 30 | STDOUT.puts e.backtrace 31 | end 32 | 33 | 34 | def rescue_action(e) 35 | #There are a couple things that will trigger this rescue_action. Which aren't 36 | #ever returned to the flash player. be ware. but I will put a fix for this in. 37 | puts "/rubyamf/gateway/render_action" 38 | puts e.message 39 | puts e.backtrace 40 | end 41 | 42 | private 43 | def welcome_screen_html 44 | " 45 | 46 | RubyAMF Gateway 47 | 48 | 49 | 50 |
51 | " 52 | end 53 | 54 | end -------------------------------------------------------------------------------- /rails_installer_files/rubyamf_helper.rb: -------------------------------------------------------------------------------- 1 | #This is just a stub file so Rails doesn't complain about me not being in the helpers folder 2 | module RubyamfHelper 3 | end -------------------------------------------------------------------------------- /util/action_controller.rb: -------------------------------------------------------------------------------- 1 | require 'app/request_store' 2 | require 'app/configuration' 3 | ActionController::Base.class_eval do 4 | def render_with_amf(options = nil, extra_options ={}, &block) 5 | begin 6 | if options && options.is_a?(Hash) && options.keys.include?(:amf) 7 | #set the @performed_render flag to avoid double renders 8 | @performed_render = true 9 | #store results on RequestStore, can't prematurely return or send_data. 10 | RubyAMF::App::RequestStore.render_amf_results = options[:amf] 11 | RubyAMF::Configuration::ClassMappings.current_mapping_scope = options[:class_mapping_scope]||RubyAMF::Configuration::ClassMappings.default_mapping_scope 12 | else 13 | render_without_amf(options,extra_options,&block) 14 | end 15 | rescue Exception => e 16 | #suppress missing template warnings 17 | raise e if !e.message.match(/^Missing template/) 18 | end 19 | end 20 | alias_method_chain :render, :amf 21 | end if Rails::VERSION::MAJOR < 3 22 | 23 | # fosrias: Update for Rails 3+ rendering 24 | module ActionController 25 | module Renderers 26 | add :amf do |amf, options| 27 | #set the @performed_render flag to avoid double renders 28 | @performed_render = true 29 | #store results on RequestStore, can't prematurely return or send_data. 30 | RubyAMF::App::RequestStore.render_amf_results = amf 31 | RubyAMF::Configuration::ClassMappings.current_mapping_scope = options[:class_mapping_scope]||RubyAMF::Configuration::ClassMappings.default_mapping_scope 32 | self.content_type ||= Mime::AMF 33 | self.response_body = " " 34 | end unless Rails::VERSION::MAJOR < 3 35 | end 36 | end 37 | 38 | #This class extends ActionController::Base 39 | class ActionController::Base 40 | attr_accessor :is_amf 41 | attr_accessor :is_rubyamf #-> for simeon :)- 42 | attr_accessor :rubyamf_params # this way they can always access the rubyamf_params 43 | 44 | #Higher level "credentials" method that returns credentials wether or not 45 | #it was from setRemoteCredentials, or setCredentials 46 | def credentials 47 | empty_auth = {:username => nil, :password => nil} 48 | amf_credentials||html_credentials||empty_auth #return an empty auth, this watches out for being the cause of an exception, (nil[]) 49 | end 50 | 51 | private 52 | #setCredentials access 53 | def amf_credentials 54 | RubyAMF::App::RequestStore.rails_authentication 55 | end 56 | 57 | #remoteObject setRemoteCredentials retrieval 58 | def html_credentials 59 | auth_data = request.env['RAW_POST_DATA'] 60 | auth_data = auth_data.scan(/DSRemoteCredentials\006.([A-Za-z0-9\+\/=]*).*?\006/)[0][0] 61 | auth_data.gsub!("DSRemoteCredentialsCharset", "") 62 | if auth_data.size > 0 63 | 64 | remote_auth = Base64.decode64(auth_data).split(':')[0..1] 65 | else 66 | return nil 67 | end 68 | case RubyAMF::Configuration::ClassMappings.hash_key_access 69 | when :string then 70 | return {'username' => remote_auth[0], 'password' => remote_auth[1]} 71 | when :symbol then 72 | return {:username => remote_auth[0], :password => remote_auth[1]} 73 | when :indifferent then 74 | return HashWithIndifferentAccess.new({:username => remote_auth[0], :password => remote_auth[1]}) 75 | end 76 | end 77 | end 78 | 79 | 80 | -------------------------------------------------------------------------------- /util/active_record.rb: -------------------------------------------------------------------------------- 1 | class ActiveRecord::Base 2 | 3 | #This holds the original incoming Value Object from deserialization time, as when an incoming VO with an 'id' property 4 | #on it is found, it is 'found' (Model.find(id)) in the DB (instead of Model.new(hash)). So right before the params hash 5 | #is updated for the rails request, I slip in this original object so you can do an "update_attributes(params[:model])" 6 | #and the correct 'update' values will be used. 7 | attr_accessor :original_vo_from_deserialization 8 | 9 | def as_single! 10 | SDTOUT.puts "ActiveRecord::Base#as_single! is no longer needed, all single active records return as an object. This warning will be taken out in 1.4, please update your controller" 11 | self 12 | end 13 | def single! 14 | SDTOUT.puts "ActiveRecord::Base#as_single! is no longer needed, all single active records return as an object. This warning will be taken out in 1.4, please update your controller" 15 | self 16 | end 17 | 18 | end -------------------------------------------------------------------------------- /util/string.rb: -------------------------------------------------------------------------------- 1 | class String 2 | 3 | def to_snake! # no one should change these unless they can benchmark and prove their way is faster. =) 4 | @cached_snake_strings ||= {} 5 | @cached_snake_strings[self] ||= ( 6 | while x = index(/([a-z\d])([A-Z\d])/) # unfortunately have to use regex for this one 7 | y=x+1 8 | self[x..y] = self[x..x]+"_"+self[y..y].downcase 9 | end 10 | self 11 | ) 12 | end 13 | 14 | # Aryk: This might be a better way of writing it. I made a lightweight version to use in my modifications. Feel free to adapt this to the rest. 15 | def to_camel! # no one should change these unless they can benchmark and prove their way is faster. =) 16 | @cached_camel_strings ||= {} 17 | @cached_camel_strings[self] ||= ( 18 | # new_string = self.dup # need to do this since sometimes the string is frozen 19 | while x = index("_") 20 | y=x+1 21 | self[x..y] = self[y..y].capitalize # in my tests, it was faster than upcase 22 | end 23 | self 24 | ) 25 | end 26 | 27 | def to_title 28 | title = self.dup 29 | title[0..0] = title[0..0].upcase 30 | title 31 | end 32 | 33 | end -------------------------------------------------------------------------------- /util/vo_helper.rb: -------------------------------------------------------------------------------- 1 | module RubyAMF 2 | module VoHelper 3 | class VoHash < Hash 4 | attr_accessor :_explicitType 5 | 6 | def ==(other) 7 | other.is_a?(VoHash) && !_explicitType.nil? && _explicitType == other._explicitType && super 8 | end 9 | 10 | end 11 | 12 | require 'app/configuration' # cant put this at the top because VoHash has to be instantiated for app/configuration to work 13 | class VoUtil 14 | 15 | include RubyAMF::Configuration 16 | include RubyAMF::Exceptions 17 | 18 | def self.get_ruby_class(action_class_name) 19 | mapping = ClassMappings.get_vo_mapping_for_actionscript_class(action_class_name) 20 | if mapping 21 | return mapping[:ruby].constantize 22 | else 23 | begin 24 | assumed_class = action_class_name.constantize 25 | assumed_class.new 26 | assumable=true 27 | rescue 28 | assumable=false 29 | ensure 30 | if ClassMappings.assume_types && assumable && assumed_class!=nil 31 | return assumed_class 32 | else 33 | case ClassMappings.hash_key_access 34 | when :symbol then return Hash 35 | when :string then return Hash 36 | when :indifferent then return HashWithIndifferentAccess 37 | end 38 | end 39 | end 40 | end 41 | end 42 | 43 | def self.set_value(obj, key, value) 44 | if obj.kind_of?(ActiveRecord::Base) 45 | #ATTRIBUTE 46 | attributes = obj.instance_variable_get('@attributes') 47 | attribs = (obj.attribute_names + [obj.class.primary_key]).inject({}){|hash, attr| hash[attr]=true ; hash} # fosrias: includes custom primary keys 48 | mapping = ClassMappings.get_vo_mapping_for_ruby_class(obj.class.name) 49 | if (!mapping && attribs[key]) || 50 | (mapping && !mapping[:ignore_fields].include?(key) && ClassMappings.attribute_names[mapping[:ruby]][key]) 51 | attributes[key] = value 52 | obj.send("#{key}_will_change!") #fosrias: flags the attribute to change with partial_updates 53 | #ASSOCIATION 54 | elsif reflection = obj.class.reflections[key.to_sym] # is it an association 55 | case reflection.macro 56 | when :has_one 57 | obj.send("set_#{key}_target", value) if value 58 | when :belongs_to 59 | obj.send("#{key}=", value) if value 60 | when :has_many, :has_and_belongs_to_many 61 | obj.send("#{key}").target = value if value 62 | when :composed_of 63 | obj.send("#{key}=", value) if value # this sets the attributes to the corresponding values 64 | end 65 | elsif 66 | # build @methods hash 67 | if mapping[:methods] 68 | @methods = Hash.new if !@methods 69 | @methods[obj.class.name]=Hash.new if !@methods[obj.class.name] 70 | mapping[:methods].each do |method| 71 | if method == key 72 | @methods[obj.class.name]["#{key}"] = value 73 | end 74 | end 75 | end 76 | else 77 | obj.instance_variable_set("@#{key}", value) 78 | end 79 | elsif obj.kind_of? ActiveResource::Base 80 | # fosrias: if assume_types = true and there is no mapping, all attributes will be mapped since there is no way to tell the related model's attributes. 81 | # Use [Transient] tag in flex/flash classes to prevent mapping in this case for local properties. 82 | attributes = obj.instance_variable_get('@attributes') 83 | mapping = ClassMappings.get_vo_mapping_for_ruby_class(obj.class.name) 84 | if (!mapping || mapping && !mapping[:ignore_fields].include?(key)) 85 | attributes[key] = value 86 | end 87 | elsif obj.kind_of? Hash 88 | obj[key] = value 89 | else 90 | raise RUBYAMFException.new(RUBYAMFException.VO_ERROR, "Argument, #{obj}, is not an ActiveRecord::Base or a Hash.") 91 | end 92 | end 93 | 94 | def self.finalize_object(obj) 95 | if obj.kind_of? ActiveRecord::Base 96 | attributes = obj.instance_variable_get('@attributes') 97 | primary_key = obj.class.primary_key # fosrias: allows for custom primary keys 98 | attributes.delete(primary_key) if attributes[primary_key]==0 || attributes[primary_key]==nil # fosrias: primary_key attribute cannot be zero or nil 99 | attributes['type']=obj.class.name if attributes['type']==nil && obj.class.superclass!=ActiveRecord::Base #STI: Always need 'type' on subclasses. 100 | attributes[obj.class.locking_column]=0 if obj.class.locking_column && attributes[obj.class.locking_column]==nil #Missing lock_version is equivalent to 0. 101 | attributes.delete('lock_version') if attributes['lock_version']==nil || attributes['lock_version']==0 #Always need lock_version on ActiveRecords that use it, even if it's not defined on ModelObject or mapped correctly. 102 | if primary_key != 'id' 103 | if obj.id && obj.class.exists?(obj.id.to_s) # fosrias: no other way to tell with custom primary keys 104 | if Rails::VERSION::MAJOR < 3 # fosrias: new record tracking changed 105 | obj.instance_variable_set("@new_record", false) 106 | else 107 | obj.instance_variable_set("@persisted", true) 108 | end 109 | end 110 | else 111 | if Rails::VERSION::MAJOR < 3 # fosrias: new record tracking changed 112 | obj.instance_variable_set("@new_record", false) if attributes['id'] # the record already exists in the database 113 | else 114 | obj.instance_variable_set("@persisted", true) if attributes['id'] # the record already exists in the database 115 | end 116 | end 117 | #superstition 118 | if (obj.new_record?) 119 | obj.created_at = nil if obj.respond_to? "created_at" 120 | obj.created_on = nil if obj.respond_to? "created_on" 121 | obj.updated_at = nil if obj.respond_to? "updated_at" 122 | obj.updated_on = nil if obj.respond_to? "updated_on" 123 | obj.instance_variable_set("@changed_attributes", {}) # fosrias: only set changed attributes for existing records 124 | end 125 | 126 | end 127 | 128 | if obj.kind_of?(ActiveRecord::Base) || obj.kind_of?(ActiveResource::Base) 129 | # process @methods hash 130 | if @methods 131 | @methods.delete(obj.class.name).each do |key, value| 132 | obj.send("#{key}=", value) 133 | end if @methods[obj.class.name] 134 | 135 | # fosrias: Process method related attributes passed back if they have a setter defined 136 | # Allows setting attr_accessors as methods on ActiveRecord or ActiveResource objects. 137 | @methods.each do |key, value| 138 | obj.send("#{key}=", value) if obj.respond_to?("#{key}=") 139 | end 140 | end 141 | end 142 | end 143 | 144 | #moved logic here so AMF3 and AMF0 can use it 145 | def self.get_vo_for_incoming(obj,action_class_name) 146 | ruby_obj = VoUtil.get_ruby_class(action_class_name).new 147 | if ruby_obj.kind_of?(ActiveRecord::Base) 148 | obj.each_pair{|key, value| VoUtil.set_value(ruby_obj, key, value)} 149 | VoUtil.finalize_object(ruby_obj) 150 | return ruby_obj 151 | else 152 | case ClassMappings.hash_key_access 153 | when :symbol then return obj.symbolize_keys! 154 | when :string then return obj # by default the keys are a string type, so just return the obj 155 | when :indifferent then return HashWithIndifferentAccess.new(obj) 156 | # else # TODO: maybe add a raise FlexError since they somehow put the wrong value for this feature 157 | end 158 | end 159 | end 160 | 161 | # Aryk: I tried to make this more efficent and clean. 162 | def self.get_vo_hash_for_outgoing(obj) 163 | new_object = VoHash.new #use VoHash because one day, we might do away with the class Object patching 164 | instance_vars = obj.instance_variables 165 | methods = [] 166 | if map = ClassMappings.get_vo_mapping_for_ruby_class(obj.class.to_s) 167 | if map[:type]=="active_record" 168 | attributes_hash = obj.attributes 169 | (map[:attributes]||attributes_hash.keys).each do |attr| # need to use dup because sometimes the attr is frozen from the AR attributes hash 170 | attr_name = attr 171 | attr_name = attr_name.dup.to_camel! if ClassMappings.translate_case # need to do it this way because the string might be frozen if it came from the attributes_hash.keys 172 | new_object[attr_name] = attributes_hash[attr] 173 | end 174 | instance_vars = [] # reset the instance_vars for the associations, this way no unwanted instance variables (ie @new_record, @read_only) can get through 175 | # Note: if you did not specify associations, it will not show up even if you eager loaded them. 176 | if map[:associations] # Aryk: if they opted for assocations, make sure that they are loaded in. This is great for composed_of, since it cannot be included on a find 177 | map[:associations].each do |assoc| 178 | instance_vars << ("@"+assoc) if obj.send(assoc) # this will make sure they are instantiated and only load it if they have a value. 179 | end 180 | elsif ClassMappings.check_for_associations 181 | instance_vars = obj.instance_variables.reject{|assoc| ["@attributes","@new_record","@read_only","@attributes_cache"].include?(assoc)} 182 | end 183 | 184 | # if there are AR methods they want in the AS object as an attribute, see about them here. 185 | if map[:methods] 186 | map[:methods].each do |method| 187 | methods << method if obj.respond_to?(method) 188 | end 189 | end 190 | elsif map[:type]=="active_resource" # fosrias: without this, attributes is one of the two instance variables and it will not map the individual attributes. Elseif for speed so only checked if not active record. 191 | instance_vars = obj.instance_variable_get("@attributes").collect do |attribute_pair| 192 | attr_name = "@#{attribute_pair[0]}" 193 | obj.instance_variable_set(attr_name, attribute_pair[1]) 194 | attr_name 195 | end 196 | if map[:associations] # Aryk: if they opted for assocations, make sure that they are loaded in. This is great for composed_of, since it cannot be included on a find 197 | # fosrias: not implemented 198 | elsif ClassMappings.check_for_associations 199 | # fosrias: not implemented 200 | end 201 | end 202 | new_object._explicitType = map[:actionscript] # Aryk: This only works on the Hash because rubyAMF extended class Object to have this accessor, probably not the best idea, but its already there. 203 | # Tony: There's some duplication in here. Had trouble consolidating the logic though. Ruby skills failed. 204 | elsif ClassMappings.assume_types 205 | new_object._explicitType = obj.class.to_s 206 | if obj.is_a?(ActiveRecord::Base) 207 | obj.attributes.keys.each do |key| 208 | attr_name = key 209 | attr_name = attr_name.dup.to_camel! if ClassMappings.translate_case # need to do it this way because the string might be frozen if it came from the attributes_hash.keys 210 | new_object[attr_name] = obj.attributes[key] 211 | end 212 | instance_vars = [] 213 | if ClassMappings.check_for_associations 214 | instance_vars = obj.instance_variables.reject{|assoc| ["@attributes","@new_record","@read_only","@attributes_cache"].include?(assoc)} 215 | end 216 | elsif obj.is_a?(ActiveResource::Base) # fosrias: without this, attributes is one of the two instance variables and it will not map the individual attributes. Elseif for speed so only checked if not active record. 217 | instance_vars = obj.instance_variable_get("@attributes").collect do |attribute_pair| 218 | attr_name = "@#{attribute_pair[0]}" 219 | obj.instance_variable_set(attr_name, attribute_pair[1]) 220 | attr_name 221 | end 222 | if ClassMappings.check_for_associations 223 | # fosrias: not implemented 224 | end 225 | end 226 | elsif obj.is_a?(ActiveResource::Base) # fosrias: Need this for case of no mapping and assumed_types = false. 227 | instance_vars = obj.instance_variable_get("@attributes").collect do |attribute_pair| 228 | attr_name = "@#{attribute_pair[0]}" 229 | obj.instance_variable_set(attr_name, attribute_pair[1]) 230 | attr_name 231 | end 232 | if ClassMappings.check_for_associations 233 | # fosrias: not implemented 234 | end 235 | end 236 | instance_vars.each do |var| # this also picks up the eager loaded associations, because association called "has_many_assoc" has an instance variable called "@has_many_assoc" 237 | attr_name = var[1..-1] 238 | attr_name.to_camel! if ClassMappings.translate_case 239 | new_object[attr_name] = obj.instance_variable_get(var) 240 | end 241 | methods.each do |method| 242 | attr_name = method.dup 243 | attr_name.to_camel! if ClassMappings.translate_case 244 | new_object[attr_name] = obj.send(method) 245 | end 246 | new_object 247 | rescue Exception => e 248 | puts e.message 249 | # puts e.backtrace 250 | raise RUBYAMFException.new(RUBYAMFException.VO_ERROR, e.message) 251 | end 252 | end 253 | end 254 | end 255 | --------------------------------------------------------------------------------