offroad 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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