offroad 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/LICENSE +674 -0
  2. data/README.rdoc +29 -0
  3. data/Rakefile +75 -0
  4. data/TODO +42 -0
  5. data/lib/app/models/offroad/group_state.rb +85 -0
  6. data/lib/app/models/offroad/mirror_info.rb +53 -0
  7. data/lib/app/models/offroad/model_state.rb +36 -0
  8. data/lib/app/models/offroad/received_record_state.rb +109 -0
  9. data/lib/app/models/offroad/sendable_record_state.rb +91 -0
  10. data/lib/app/models/offroad/system_state.rb +33 -0
  11. data/lib/cargo_streamer.rb +222 -0
  12. data/lib/controller_extensions.rb +74 -0
  13. data/lib/exceptions.rb +16 -0
  14. data/lib/migrate/20100512164608_create_offroad_tables.rb +72 -0
  15. data/lib/mirror_data.rb +354 -0
  16. data/lib/model_extensions.rb +377 -0
  17. data/lib/module_funcs.rb +94 -0
  18. data/lib/offroad.rb +30 -0
  19. data/lib/version.rb +3 -0
  20. data/lib/view_helper.rb +7 -0
  21. data/templates/offline.rb +36 -0
  22. data/templates/offline_database.yml +7 -0
  23. data/templates/offroad.yml +6 -0
  24. data/test/app_root/app/controllers/application_controller.rb +2 -0
  25. data/test/app_root/app/controllers/group_controller.rb +28 -0
  26. data/test/app_root/app/models/global_record.rb +10 -0
  27. data/test/app_root/app/models/group.rb +12 -0
  28. data/test/app_root/app/models/group_owned_record.rb +68 -0
  29. data/test/app_root/app/models/guest.rb +7 -0
  30. data/test/app_root/app/models/subrecord.rb +12 -0
  31. data/test/app_root/app/models/unmirrored_record.rb +4 -0
  32. data/test/app_root/app/views/group/download_down_mirror.html.erb +4 -0
  33. data/test/app_root/app/views/group/download_initial_down_mirror.html.erb +4 -0
  34. data/test/app_root/app/views/group/download_up_mirror.html.erb +6 -0
  35. data/test/app_root/app/views/group/upload_down_mirror.html.erb +1 -0
  36. data/test/app_root/app/views/group/upload_up_mirror.html.erb +1 -0
  37. data/test/app_root/app/views/layouts/mirror.html.erb +9 -0
  38. data/test/app_root/config/boot.rb +115 -0
  39. data/test/app_root/config/database.yml +6 -0
  40. data/test/app_root/config/environment.rb +15 -0
  41. data/test/app_root/config/environments/test.rb +17 -0
  42. data/test/app_root/config/offroad.yml +6 -0
  43. data/test/app_root/config/routes.rb +4 -0
  44. data/test/app_root/db/migrate/20100529235049_create_tables.rb +64 -0
  45. data/test/app_root/lib/common_hobo.rb +15 -0
  46. data/test/app_root/vendor/plugins/offroad/init.rb +2 -0
  47. data/test/functional/mirror_operations_test.rb +148 -0
  48. data/test/test_helper.rb +405 -0
  49. data/test/unit/app_state_tracking_test.rb +275 -0
  50. data/test/unit/cargo_streamer_test.rb +332 -0
  51. data/test/unit/global_data_test.rb +102 -0
  52. data/test/unit/group_controller_test.rb +152 -0
  53. data/test/unit/group_data_test.rb +435 -0
  54. data/test/unit/group_single_test.rb +136 -0
  55. data/test/unit/hobo_permissions_test.rb +57 -0
  56. data/test/unit/mirror_data_test.rb +1271 -0
  57. data/test/unit/mirror_info_test.rb +31 -0
  58. data/test/unit/module_funcs_test.rb +37 -0
  59. data/test/unit/pathological_model_test.rb +62 -0
  60. data/test/unit/test_framework_test.rb +86 -0
  61. data/test/unit/unmirrored_data_test.rb +14 -0
  62. metadata +140 -0
@@ -0,0 +1,377 @@
1
+ module Offroad
2
+ module ModelExtensions
3
+ OFFROAD_VALID_MODES = [:group_base, :group_owned, :group_single, :global]
4
+ OFFROAD_GROUP_MODES = [:group_base, :group_owned, :group_single]
5
+
6
+ def acts_as_offroadable(mode, opts = {})
7
+ raise ModelError.new("You can only call acts_as_offroadable once per model") if acts_as_offroadable?
8
+ raise ModelError.new("You must specify a mode, one of " + OFFROAD_VALID_MODES.map(&:inspect).join("/")) unless OFFROAD_VALID_MODES.include?(mode)
9
+
10
+ set_internal_cattr :offroad_mode, mode
11
+
12
+ case mode
13
+ when :group_owned then
14
+ raise ModelError.new("For :group_owned models, need to specify :parent") unless opts[:parent]
15
+ assoc = reflect_on_association(opts.delete(:parent))
16
+ raise ModelError.new("No such parent associaton") unless assoc
17
+ raise ModelError.new("Parent association must be a belongs_to association") unless assoc.belongs_to?
18
+ raise ModelError.new("Parent association must be to a group data model") unless assoc.klass.offroad_group_data?
19
+
20
+ set_internal_cattr :offroad_parent_assoc, assoc
21
+ Offroad::note_group_owned_model(self)
22
+ when :group_single then
23
+ Offroad::note_group_single_model(self)
24
+ when :group_base then
25
+ Offroad::note_group_base_model(self)
26
+ when :global then
27
+ Offroad::note_global_data_model(self)
28
+ end
29
+
30
+ # We should have deleted all the options from the hash by this point
31
+ raise ModelError.new("Unknown or inapplicable option(s) specified") unless opts.size == 0
32
+
33
+ case mode
34
+ when :group_base then
35
+ named_scope :owned_by_offroad_group, lambda { |group| { :conditions => { :id => group.id } } }
36
+ named_scope :offline_groups, {
37
+ :joins =>
38
+ "INNER JOIN `#{Offroad::GroupState.table_name}` ON `#{Offroad::GroupState.table_name}`.app_group_id = `#{table_name}`.`#{primary_key}`"
39
+ }
40
+ named_scope :online_groups, {
41
+ :joins =>
42
+ "LEFT JOIN `#{Offroad::GroupState.table_name}` ON `#{Offroad::GroupState.table_name}`.app_group_id = `#{table_name}`.`#{primary_key}`",
43
+ :conditions =>
44
+ "`#{Offroad::GroupState.table_name}`.app_group_id IS NULL"
45
+ }
46
+ when :group_owned then
47
+ named_scope :owned_by_offroad_group, lambda { |group| args_for_ownership_scope(group) }
48
+ when :group_single then
49
+ named_scope :owned_by_offroad_group, lambda { |group| {
50
+ :conditions => (Offroad::GroupState.count > 0 && group == Offroad::GroupState.first.app_group) ? "1=1" : "1=0"
51
+ } }
52
+ end
53
+
54
+ if offroad_group_data?
55
+ include GroupDataInstanceMethods
56
+ else
57
+ include GlobalDataInstanceMethods
58
+ end
59
+ include CommonInstanceMethods
60
+
61
+ before_destroy :before_mirrored_data_destroy
62
+ after_destroy :after_mirrored_data_destroy
63
+ before_save :before_mirrored_data_save
64
+ after_save :after_mirrored_data_save
65
+
66
+ if Object.const_defined?(:Hobo) and included_modules.include?(Hobo::Model)
67
+ include HoboPermissionsInstanceMethods
68
+ [
69
+ [:create, :save],
70
+ [:update, :save],
71
+ [:destroy, :destroy]
72
+ ].each do |perm_name, check_name|
73
+ define_method "#{perm_name}_permitted_with_offroad_check?".to_sym do
74
+ pre_check_passed?("before_mirrored_data_#{check_name}".to_sym) && send("#{perm_name}_permitted_without_offroad_check?")
75
+ end
76
+ alias_method_chain "#{perm_name}_permitted?", "offroad_check"
77
+ end
78
+ end
79
+ end
80
+
81
+ def offroad_model_state
82
+ model_scope = Offroad::ModelState::for_model(self)
83
+ return model_scope.first || model_scope.create
84
+ end
85
+
86
+ def acts_as_offroadable?
87
+ respond_to? :offroad_mode
88
+ end
89
+
90
+ def safe_to_load_from_cargo_stream?
91
+ acts_as_offroadable?
92
+ end
93
+
94
+ def offroad_group_base?
95
+ acts_as_offroadable? && offroad_mode == :group_base
96
+ end
97
+
98
+ def offroad_group_data?
99
+ acts_as_offroadable? && OFFROAD_GROUP_MODES.include?(offroad_mode)
100
+ end
101
+
102
+ def offroad_global_data?
103
+ acts_as_offroadable? && offroad_mode == :global
104
+ end
105
+
106
+ private
107
+
108
+ def set_internal_cattr(name, value)
109
+ write_inheritable_attribute name, value
110
+ class_inheritable_reader name
111
+ end
112
+
113
+ def args_for_ownership_scope(group)
114
+ included_assocs = []
115
+ conditions = []
116
+ assoc_owner = self
117
+ assoc = offroad_parent_assoc
118
+ while true
119
+ if assoc.klass.offroad_group_base?
120
+ conditions << "`#{assoc_owner.table_name}`.`#{assoc.primary_key_name}` = #{group.id}"
121
+ break
122
+ else
123
+ conditions << "`#{assoc_owner.table_name}`.`#{assoc.primary_key_name}` = `#{assoc.klass.table_name}`.`#{assoc.klass.primary_key}`"
124
+ included_assocs << assoc
125
+ assoc_owner = assoc.klass
126
+ assoc = assoc.klass.offroad_parent_assoc
127
+ end
128
+ end
129
+
130
+ includes = {}
131
+ included_assocs.reverse.each do |assoc|
132
+ includes = {assoc.name => includes}
133
+ end
134
+
135
+ return {:include => includes, :conditions => conditions.join(" AND ")}
136
+ end
137
+
138
+ module CommonInstanceMethods
139
+ # Methods below this point are only to be used internally by Offroad
140
+ # However, making all of them private would make using them from elsewhere troublesome
141
+
142
+ # TODO Should put common save and destroy wrappers in here, with access to a method that checks if SRS needed
143
+ # TODO That method should also be used in import_model_cargo instead of explicitly trying to find the srs
144
+
145
+ #:nodoc:#
146
+ def validate_changed_id_columns
147
+ changes.each do |colname, arr|
148
+ orig_val = arr[0]
149
+ new_val = arr[1]
150
+
151
+ raise DataError.new("Cannot change id of offroad-tracked records (orig #{orig_val.inspect}, new #{new_val.inspect}") if colname == self.class.primary_key && orig_val != nil
152
+
153
+ # FIXME : Use association reflection instead
154
+ next unless colname.end_with? "_id"
155
+ accessor_name = colname[0, colname.size-3]
156
+ next unless respond_to? accessor_name
157
+ obj = send(accessor_name)
158
+
159
+ raise DataError.new("Mirrored data cannot hold a foreign key to unmirrored data") unless obj.class.acts_as_offroadable?
160
+
161
+ if !new_record? and offroad_mode == :group_owned and colname == offroad_parent_assoc.primary_key_name
162
+ # obj is our parent
163
+ # FIXME: What if we can't find orig_val?
164
+ if obj.owning_group != obj.class.find(orig_val).owning_group
165
+ raise DataError.new("Group-owned data cannot be transferred between groups")
166
+ end
167
+ end
168
+
169
+ if self.class.offroad_group_data?
170
+ if obj.class.offroad_group_data? && obj.owning_group && obj.owning_group.id != owning_group.id
171
+ raise DataError.new("Invalid #{colname}: Group data cannot hold a foreign key to data owned by another group")
172
+ end
173
+ elsif self.class.offroad_global_data?
174
+ unless obj.class.offroad_global_data?
175
+ raise DataError.new("Invalid #{colname}: Global mirrored data cannot hold a foreign key to group data")
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ end
182
+
183
+ module GlobalDataInstanceMethods
184
+ # Methods below this point are only to be used internally by Offroad
185
+ # However, marking all of them private would make using them from elsewhere troublesome
186
+
187
+ def locked_by_offroad?
188
+ Offroad::app_offline?
189
+ end
190
+
191
+ #:nodoc#
192
+ def before_mirrored_data_destroy
193
+ raise ActiveRecord::ReadOnlyRecord if locked_by_offroad?
194
+ return true
195
+ end
196
+
197
+ #:nodoc#
198
+ def after_mirrored_data_destroy
199
+ Offroad::SendableRecordState::note_record_destroyed(self) if Offroad::app_online?
200
+ return true
201
+ end
202
+
203
+ #:nodoc#
204
+ def before_mirrored_data_save
205
+ raise ActiveRecord::ReadOnlyRecord if locked_by_offroad?
206
+ validate_changed_id_columns
207
+ return true
208
+ end
209
+
210
+ #:nodoc#
211
+ def after_mirrored_data_save
212
+ Offroad::SendableRecordState::note_record_created_or_updated(self) if Offroad::app_online? && changed?
213
+ return true
214
+ end
215
+ end
216
+
217
+ module GroupDataInstanceMethods
218
+ def locked_by_offroad?
219
+ return true if Offroad::app_online? && group_offline?
220
+ return true if Offroad::app_offline? && (!group_state || group_state.group_locked?)
221
+ return false
222
+ end
223
+
224
+ # If called on a group_owned_model, methods below bubble up to the group_base_model
225
+
226
+ def offroad_group_lock!
227
+ raise DataError.new("Cannot lock groups from online app") if Offroad::app_online?
228
+ group_state.update_attribute(:group_locked, true)
229
+ end
230
+
231
+ # Returns a hash with the latest information about this group in the offline app
232
+ def last_known_status
233
+ raise DataError.new("This method is only for offline groups") if group_online?
234
+ s = group_state
235
+ fields_of_interest = [
236
+ :last_installer_downloaded_at,
237
+ :last_installation_at,
238
+ :last_down_mirror_created_at,
239
+ :last_down_mirror_loaded_at,
240
+ :last_up_mirror_created_at,
241
+ :last_up_mirror_loaded_at,
242
+ :launcher_version,
243
+ :app_version,
244
+ :operating_system
245
+ ]
246
+ return fields_of_interest.map {|field_name| s.send(field_name)}
247
+ end
248
+
249
+ def group_offline?
250
+ not group_online?
251
+ end
252
+
253
+ def group_online?
254
+ return group_state.nil?
255
+ end
256
+
257
+ def group_offline=(b)
258
+ raise DataError.new("Unable to change a group's offline status in offline app") if Offroad::app_offline?
259
+
260
+ if b and Offroad::group_single_models.size > 0 and Offroad::GroupState.count > 0
261
+ raise DataError.new("Unable to set more than one group offline if there are any group single models")
262
+ end
263
+
264
+ if b && !group_state
265
+ Offroad::GroupState.for_group(owning_group).create!
266
+ elsif group_state
267
+ group_state.destroy
268
+ end
269
+ end
270
+
271
+ def owning_group
272
+ return nil if unlocked_group_single_record?
273
+ return Offroad::GroupState.first.app_group if offroad_mode == :group_single
274
+
275
+ # Recurse upwards until we get to the group base
276
+ if self.class.offroad_group_base?
277
+ return self
278
+ else
279
+ parent = send(offroad_parent_assoc.name)
280
+ if parent
281
+ return parent.owning_group
282
+ else
283
+ return nil
284
+ end
285
+ end
286
+ end
287
+
288
+ # Methods below this point are only to be used internally by Offroad
289
+ # However, marking them private makes using them from elsewhere troublesome
290
+
291
+ #:nodoc#
292
+ def before_mirrored_data_destroy
293
+ if group_offline? && offroad_mode == :group_base
294
+ group_state.update_attribute(:group_being_destroyed, true)
295
+ end
296
+
297
+ return true if unlocked_group_single_record?
298
+
299
+ if locked_by_offroad?
300
+ # The only thing that can be deleted is the entire group (possibly with its dependent records), and only if we're online
301
+ raise ActiveRecord::ReadOnlyRecord unless Offroad::app_online? and (offroad_mode == :group_base or group_being_destroyed)
302
+ end
303
+
304
+ # If the app is offline, the only thing that CAN'T be deleted even if unlocked is the group
305
+ raise ActiveRecord::ReadOnlyRecord if Offroad::app_offline? and offroad_mode == :group_base
306
+
307
+ return true
308
+ end
309
+
310
+ #:nodoc#
311
+ def after_mirrored_data_destroy
312
+ Offroad::SendableRecordState::note_record_destroyed(self) if Offroad::app_offline?
313
+ Offroad::GroupState::note_group_destroyed(self) if group_offline? && offroad_mode == :group_base
314
+ return true
315
+ end
316
+
317
+ #:nodoc#
318
+ def before_mirrored_data_save
319
+ return true if unlocked_group_single_record?
320
+
321
+ raise DataError.new("Invalid owning group") unless owning_group
322
+
323
+ if Offroad::app_offline?
324
+ case offroad_mode
325
+ when :group_base
326
+ raise DataError.new("Cannot create groups in offline mode") if new_record?
327
+ when :group_owned
328
+ raise DataError.new("Owning group must be the offline group") if owning_group != Offroad::offline_group
329
+ end
330
+ end
331
+
332
+ validate_changed_id_columns
333
+
334
+ raise ActiveRecord::ReadOnlyRecord if locked_by_offroad?
335
+
336
+ return true
337
+ end
338
+
339
+ #:nodoc#
340
+ def after_mirrored_data_save
341
+ Offroad::SendableRecordState::note_record_created_or_updated(self) if Offroad::app_offline? && changed?
342
+ return true
343
+ end
344
+
345
+ #:nodoc#
346
+ def group_state
347
+ Offroad::GroupState.for_group(owning_group).first
348
+ end
349
+
350
+ #:nodoc:#
351
+ def group_being_destroyed
352
+ return true unless owning_group # If the group doesn't exist anymore, then it's pretty well destroyed
353
+ return group_state.group_being_destroyed
354
+ end
355
+
356
+ #:nodoc:#
357
+ def unlocked_group_single_record?
358
+ offroad_mode == :group_single && Offroad::GroupState.count == 0
359
+ end
360
+ end
361
+
362
+ module HoboPermissionsInstanceMethods
363
+ private
364
+
365
+ def pre_check_passed?(method_name)
366
+ begin
367
+ send(method_name)
368
+ rescue ActiveRecord::ReadOnlyRecord
369
+ return false
370
+ rescue Offroad::DataError
371
+ return false
372
+ end
373
+ return true
374
+ end
375
+ end
376
+ end
377
+ end
@@ -0,0 +1,94 @@
1
+ module Offroad
2
+ @@app_online_flag = nil
3
+ @@group_base_model = nil
4
+ @@global_data_models = {}
5
+ @@group_owned_models = {}
6
+ @@group_single_models = {}
7
+
8
+ # Used in the environment configuration file to set the app to online or offline mode.
9
+ # This should not be called from within the app.
10
+ def self.config_app_online(flag)
11
+ @@app_online_flag = flag
12
+ end
13
+
14
+ # Returns true if the app is in offline mode (running on a local system without access to the main server)
15
+ def self.app_offline?
16
+ not app_online?
17
+ end
18
+
19
+ # Returns true if the app is in online mode (or in other words, this is the main server)
20
+ def self.app_online?
21
+ case @@app_online_flag
22
+ when true then true
23
+ when false then false
24
+ else raise AppModeUnknownError.new
25
+ end
26
+ end
27
+
28
+ # Returns a Time that identifies the version of the app
29
+ # Specifically, the modification timestamp (on the online app) of the most recently modified source file in the app
30
+ def self.app_version
31
+ # TODO Implement
32
+ # Online - When app is launched, scan all application files, returns the Time of the most recently changed file
33
+ # Offline - Based on the app version noted in the last down-mirror file successfully loaded
34
+ return 1
35
+ end
36
+
37
+ #:nodoc#
38
+ def self.init
39
+ @@config = YAML.load_file(File.join(RAILS_ROOT, "config", "offroad.yml"))
40
+ end
41
+
42
+ # Returns the record of the group base model that this app is in charge of
43
+ # This is only applicable if the app is offline
44
+ def self.offline_group
45
+ raise PluginError.new("'Offline group' is only meaningful if the app is offline") unless app_offline?
46
+ group_base_model.first
47
+ end
48
+
49
+ private
50
+
51
+ def self.online_url
52
+ @@config[:online_url] or raise PluginError.new("No online url specified in offroad config")
53
+ end
54
+
55
+ def self.app_name
56
+ @@config[:app_name] or raise PluginError.new("No app name specified in offroad config")
57
+ end
58
+
59
+ def self.note_global_data_model(cls)
60
+ @@global_data_models[cls.name] = cls
61
+ end
62
+
63
+ def self.global_data_models
64
+ @@global_data_models
65
+ end
66
+
67
+ def self.note_group_base_model(cls)
68
+ if @@group_base_model and @@group_base_model.to_s != cls.to_s
69
+ raise ModelError.new("You can only define one group base model")
70
+ end
71
+ @@group_base_model = cls
72
+ end
73
+
74
+ def self.group_base_model
75
+ raise ModelError.new("No group base model was specified") unless @@group_base_model
76
+ @@group_base_model
77
+ end
78
+
79
+ def self.note_group_owned_model(cls)
80
+ @@group_owned_models[cls.name] = cls
81
+ end
82
+
83
+ def self.group_owned_models
84
+ @@group_owned_models
85
+ end
86
+
87
+ def self.note_group_single_model(cls)
88
+ @@group_single_models[cls.name] = cls
89
+ end
90
+
91
+ def self.group_single_models
92
+ @@group_single_models
93
+ end
94
+ end
data/lib/offroad.rb ADDED
@@ -0,0 +1,30 @@
1
+ # Offroad
2
+
3
+ require 'version'
4
+ require 'module_funcs'
5
+ require 'cargo_streamer'
6
+ require 'exceptions'
7
+ require 'mirror_data'
8
+
9
+ path = File.join(File.dirname(__FILE__), 'app', 'models')
10
+ $LOAD_PATH << path
11
+ ActiveSupport::Dependencies.autoload_paths << path
12
+
13
+ require 'ar-extensions' # External dependency
14
+
15
+ require 'controller_extensions'
16
+ class ActionController::Base
17
+ extend Offroad::ControllerExtensions
18
+ end
19
+
20
+ require 'model_extensions'
21
+ class ActiveRecord::Base
22
+ extend Offroad::ModelExtensions
23
+ end
24
+
25
+ require 'view_helper'
26
+ class ActionView::Base
27
+ include Offroad::ViewHelper
28
+ end
29
+
30
+ Offroad::init
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Offroad
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,7 @@
1
+ module Offroad
2
+ module ViewHelper
3
+ def link_to_online_app(name = nil)
4
+ link_to(name ? name : Offroad::online_url, Offroad::online_url)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,36 @@
1
+ # This file defines the offline envrionment
2
+ # "Offline" means that the application is running on a local network, with only a subset of the real online app's data
3
+ # It is implemented by the gem Offroad
4
+
5
+ # Settings specified here will take precedence over those in config/environment.rb
6
+
7
+ # Use the offline database configuration file instead of the regular one
8
+ config.database_configuration_file = "config/offline_database.yml"
9
+
10
+ # Code is not reloaded between requests
11
+ config.cache_classes = true
12
+
13
+ # Full error reports are disabled and caching is turned on
14
+ config.action_controller.consider_all_requests_local = false
15
+ config.action_controller.perform_caching = true
16
+ config.action_view.cache_template_loading = true
17
+
18
+ # See everything in the log (default is :info)
19
+ # config.log_level = :debug
20
+
21
+ # Use a different logger for distributed setups
22
+ # config.logger = SyslogLogger.new
23
+
24
+ # Use a different cache store in production
25
+ # config.cache_store = :mem_cache_store
26
+
27
+ # Enable serving of images, stylesheets, and javascripts from an asset server
28
+ # config.action_controller.asset_host = "http://assets.example.com"
29
+
30
+ # Disable delivery errors, bad email addresses will be ignored
31
+ # config.action_mailer.raise_delivery_errors = false
32
+
33
+ # Enable threaded mode
34
+ # config.threadsafe!
35
+
36
+ Offroad::config_app_online(false)
@@ -0,0 +1,7 @@
1
+ # This database config file is used by the 'offline' environment
2
+
3
+ offline:
4
+ adapter: sqlite3
5
+ database: db/offline.sqlite3
6
+ pool: 5
7
+ timeout: 5000
@@ -0,0 +1,6 @@
1
+ # Configuration for Offroad
2
+ # :online_url: The URL for the regular online version of the app
3
+ # :app_name: The name of the application, to be used in helpful messages in the generated mirror data files.
4
+
5
+ :online_url: "http://example.com"
6
+ :app_name: Example Application
@@ -0,0 +1,2 @@
1
+ class ApplicationController < ActionController::Base
2
+ end
@@ -0,0 +1,28 @@
1
+ class GroupController < ApplicationController
2
+ offroad_group_controller
3
+
4
+ def download_down_mirror
5
+ render_down_mirror_file Group.find(params[:id]), "down-mirror-file", :layout => "mirror"
6
+ end
7
+
8
+ def download_initial_down_mirror
9
+ render_down_mirror_file Group.find(params[:id]), "down-mirror-file", :layout => "mirror", :initial_mode => true
10
+ end
11
+
12
+ def download_up_mirror
13
+ render_up_mirror_file Group.find(params[:id]), "up-mirror-file", :layout => "mirror"
14
+ end
15
+
16
+ def upload_up_mirror
17
+ load_up_mirror_file Group.find(params[:id]), params[:mirror_data]
18
+ end
19
+
20
+ def upload_down_mirror
21
+ load_down_mirror_file Group.find(params[:id]), params[:mirror_data]
22
+ end
23
+
24
+ def upload_initial_down_mirror
25
+ load_down_mirror_file nil, params[:mirror_data], :initial_mode => true
26
+ render :upload_down_mirror
27
+ end
28
+ end
@@ -0,0 +1,10 @@
1
+ class GlobalRecord < ActiveRecord::Base
2
+ include CommonHobo if HOBO_TEST_MODE
3
+ acts_as_offroadable :global
4
+ validates_presence_of :title
5
+ belongs_to :unmirrored_record
6
+ belongs_to :some_group, :class_name => "Group"
7
+ belongs_to :friend, :class_name => "GlobalRecord"
8
+ validates_numericality_of :should_be_odd, :odd => true
9
+ attr_protected :protected_integer
10
+ end
@@ -0,0 +1,12 @@
1
+ class Group < ActiveRecord::Base
2
+ include CommonHobo if HOBO_TEST_MODE
3
+ acts_as_offroadable :group_base
4
+ validates_presence_of :name
5
+ has_many :group_owned_records, :dependent => :destroy
6
+ belongs_to :favorite, :class_name => "GroupOwnedRecord"
7
+ belongs_to :unmirrored_record
8
+ belongs_to :global_record
9
+ def to_s
10
+ name
11
+ end
12
+ end
@@ -0,0 +1,68 @@
1
+ class GroupOwnedRecord < ActiveRecord::Base
2
+ include CommonHobo if HOBO_TEST_MODE
3
+ belongs_to :group
4
+ acts_as_offroadable :group_owned, :parent => :group
5
+
6
+ belongs_to :parent, :class_name => "GroupOwnedRecord"
7
+ belongs_to :unmirrored_record
8
+ belongs_to :global_record
9
+ has_many :children, :foreign_key => "parent_id", :class_name => "GroupOwnedRecord"
10
+ has_many :subrecords
11
+ validates_presence_of :description, :group
12
+ validates_numericality_of :should_be_even, :even => true
13
+ attr_protected :protected_integer
14
+
15
+ def to_s
16
+ description
17
+ end
18
+
19
+ def before_save
20
+ @@callback_called = true
21
+ end
22
+
23
+ def after_save
24
+ @@callback_called = true
25
+ end
26
+
27
+ def before_destroy
28
+ @@callback_called = true
29
+ end
30
+
31
+ def after_destroy
32
+ @@callback_called = true
33
+ end
34
+
35
+ def self.reset_callback_called
36
+ @@callback_called = false
37
+ end
38
+
39
+ def self.callback_called
40
+ @@callback_called
41
+ end
42
+
43
+ def after_offroad_upload
44
+ @@after_upload_count ||= 0
45
+ @@after_upload_count += 1
46
+ end
47
+
48
+ def self.reset_after_upload_count
49
+ @@after_upload_count = 0
50
+ end
51
+
52
+ def self.after_upload_count
53
+ @@after_upload_count
54
+ end
55
+
56
+ def after_offroad_destroy
57
+ @@after_destroy_count ||= 0
58
+ @@after_destroy_count += 1
59
+ end
60
+
61
+ def self.reset_after_destroy_count
62
+ @@after_destroy_count = 0
63
+ end
64
+
65
+ def self.after_destroy_count
66
+ @@after_destroy_count
67
+ end
68
+ end