offroad 0.0.2 → 0.0.3

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 (61) hide show
  1. data/LICENSE +674 -674
  2. data/README.rdoc +29 -29
  3. data/Rakefile +75 -75
  4. data/TODO +42 -42
  5. data/lib/app/models/offroad/group_state.rb +85 -85
  6. data/lib/app/models/offroad/mirror_info.rb +53 -53
  7. data/lib/app/models/offroad/model_state.rb +36 -36
  8. data/lib/app/models/offroad/received_record_state.rb +115 -115
  9. data/lib/app/models/offroad/sendable_record_state.rb +91 -91
  10. data/lib/app/models/offroad/system_state.rb +33 -33
  11. data/lib/cargo_streamer.rb +222 -222
  12. data/lib/controller_extensions.rb +74 -74
  13. data/lib/exceptions.rb +16 -16
  14. data/lib/migrate/20100512164608_create_offroad_tables.rb +72 -72
  15. data/lib/mirror_data.rb +376 -376
  16. data/lib/model_extensions.rb +378 -377
  17. data/lib/module_funcs.rb +94 -94
  18. data/lib/offroad.rb +41 -41
  19. data/lib/version.rb +3 -3
  20. data/lib/view_helper.rb +7 -7
  21. data/templates/offline.rb +36 -36
  22. data/templates/offline_database.yml +7 -7
  23. data/templates/offroad.yml +6 -6
  24. data/test/app_root/app/controllers/application_controller.rb +2 -2
  25. data/test/app_root/app/controllers/group_controller.rb +28 -28
  26. data/test/app_root/app/models/global_record.rb +10 -10
  27. data/test/app_root/app/models/group.rb +12 -12
  28. data/test/app_root/app/models/group_owned_record.rb +68 -68
  29. data/test/app_root/app/models/guest.rb +7 -7
  30. data/test/app_root/app/models/subrecord.rb +12 -12
  31. data/test/app_root/app/models/unmirrored_record.rb +4 -4
  32. data/test/app_root/app/views/group/download_down_mirror.html.erb +3 -3
  33. data/test/app_root/app/views/group/download_initial_down_mirror.html.erb +3 -3
  34. data/test/app_root/app/views/group/download_up_mirror.html.erb +5 -5
  35. data/test/app_root/app/views/layouts/mirror.html.erb +8 -8
  36. data/test/app_root/config/boot.rb +115 -115
  37. data/test/app_root/config/database-pg.yml +8 -8
  38. data/test/app_root/config/database.yml +5 -5
  39. data/test/app_root/config/environment.rb +24 -24
  40. data/test/app_root/config/environments/test.rb +17 -17
  41. data/test/app_root/config/offroad.yml +6 -6
  42. data/test/app_root/config/routes.rb +4 -4
  43. data/test/app_root/db/migrate/20100529235049_create_tables.rb +64 -64
  44. data/test/app_root/lib/common_hobo.rb +15 -15
  45. data/test/app_root/vendor/plugins/offroad/init.rb +2 -2
  46. data/test/functional/mirror_operations_test.rb +148 -148
  47. data/test/test_helper.rb +453 -453
  48. data/test/unit/app_state_tracking_test.rb +275 -275
  49. data/test/unit/cargo_streamer_test.rb +332 -332
  50. data/test/unit/global_data_test.rb +102 -102
  51. data/test/unit/group_controller_test.rb +152 -152
  52. data/test/unit/group_data_test.rb +442 -435
  53. data/test/unit/group_single_test.rb +136 -136
  54. data/test/unit/hobo_permissions_test.rb +57 -57
  55. data/test/unit/mirror_data_test.rb +1283 -1283
  56. data/test/unit/mirror_info_test.rb +31 -31
  57. data/test/unit/module_funcs_test.rb +37 -37
  58. data/test/unit/pathological_model_test.rb +62 -62
  59. data/test/unit/test_framework_test.rb +86 -86
  60. data/test/unit/unmirrored_data_test.rb +14 -14
  61. metadata +6 -8
data/README.rdoc CHANGED
@@ -1,29 +1,29 @@
1
- *NOTE*: This gem is a work in progress. It does work, but it's not especially convenient, yet.
2
- Better documentation and scripts will follow in time.
3
-
4
- *NOTE*: Currently the gem is only compatible with Rails 2.
5
-
6
- == Overview
7
-
8
- This gem allows users without Internet access to still use your app. It requires that your application can
9
- be thought of in the following way:
10
-
11
- Your site is broken down into a set of "groups" or "organizations". Each group has their own distinct set of
12
- records, which are editable by only a distinct set of a users. Besides that, there are global records that can
13
- only be edited by overall site administrators, though they can possibly be viewed by regular users.
14
-
15
- In order to make offline use of your app possible, Offroad will generate an installer bundle, which is an
16
- OS-specific executable that can install Ruby, Rails, and also a launcher which controls a local instance of
17
- WEBrick and manages imports and exports. The installer and launcher will be visually labelled everywhere with
18
- the name of the Rails app itself, making its function obvious to users. This launcher can generate up-mirror
19
- files, which are transported on a USB thumbdrive or some other medium to an online system, where they can be
20
- uploaded to the main app site. After having done an upload, the user is prompted to download a down-mirror
21
- file which contains any changes to the global records, and bring this back to the offline system, which
22
- completes the sneakernet round-trip.
23
-
24
- Rather than sending all data, only globally accessible records and updates to the app itself are transferred
25
- in the down-mirror (except for the first down-mirror, which also contains the initial set of records for the
26
- group). Furthermore, the offline version of the app is considered be the absolutely canonical source of that
27
- group's records; really, this is not a "sync", it's a "mirror". This means that no-one on the online site can
28
- make changes to an offline group's records, not even the administrator; this is enforced by Offroad at a
29
- low level.
1
+ *NOTE*: This gem is a work in progress. It does work, but it's not especially convenient, yet.
2
+ Better documentation and scripts will follow in time.
3
+
4
+ *NOTE*: Currently the gem is only compatible with Rails 2.
5
+
6
+ == Overview
7
+
8
+ This gem allows users without Internet access to still use your app. It requires that your application can
9
+ be thought of in the following way:
10
+
11
+ Your site is broken down into a set of "groups" or "organizations". Each group has their own distinct set of
12
+ records, which are editable by only a distinct set of a users. Besides that, there are global records that can
13
+ only be edited by overall site administrators, though they can possibly be viewed by regular users.
14
+
15
+ In order to make offline use of your app possible, Offroad will generate an installer bundle, which is an
16
+ OS-specific executable that can install Ruby, Rails, and also a launcher which controls a local instance of
17
+ WEBrick and manages imports and exports. The installer and launcher will be visually labelled everywhere with
18
+ the name of the Rails app itself, making its function obvious to users. This launcher can generate up-mirror
19
+ files, which are transported on a USB thumbdrive or some other medium to an online system, where they can be
20
+ uploaded to the main app site. After having done an upload, the user is prompted to download a down-mirror
21
+ file which contains any changes to the global records, and bring this back to the offline system, which
22
+ completes the sneakernet round-trip.
23
+
24
+ Rather than sending all data, only globally accessible records and updates to the app itself are transferred
25
+ in the down-mirror (except for the first down-mirror, which also contains the initial set of records for the
26
+ group). Furthermore, the offline version of the app is considered be the absolutely canonical source of that
27
+ group's records; really, this is not a "sync", it's a "mirror". This means that no-one on the online site can
28
+ make changes to an offline group's records, not even the administrator; this is enforced by Offroad at a
29
+ low level.
data/Rakefile CHANGED
@@ -1,75 +1,75 @@
1
- require 'rubygems'
2
- require 'rake'
3
- require 'rake/testtask'
4
- require 'rake/rdoctask'
5
- require 'rake/gempackagetask'
6
-
7
- def common_test_settings(t)
8
- t.libs << 'lib'
9
- t.libs << 'test'
10
- t.pattern = 'test/**/*_test.rb'
11
- t.verbose = true
12
- end
13
-
14
- desc 'Default: run unit and functional tests.'
15
- task :default => :test
16
-
17
- desc 'Test Offroad'
18
- Rake::TestTask.new(:test) do |t|
19
- common_test_settings(t)
20
- end
21
-
22
- desc 'Generate documentation for Offroad.'
23
- Rake::RDocTask.new(:rdoc) do |rdoc|
24
- rdoc.rdoc_dir = 'rdoc'
25
- rdoc.title = 'Offroad'
26
- rdoc.options << '--line-numbers' << '--inline-source'
27
- rdoc.rdoc_files.include('README')
28
- rdoc.rdoc_files.include('lib/**/*.rb')
29
- end
30
-
31
- begin
32
- require 'rcov/rcovtask'
33
-
34
- Rcov::RcovTask.new(:rcov) do |t|
35
- common_test_settings(t)
36
- t.pattern = 'test/unit/*_test.rb' # Don't care about coverage added by functional tests
37
- t.rcov_opts << '-o coverage -x "/ruby/,/gems/,/test/,/migrate/"'
38
- end
39
- rescue LoadError
40
- # Rcov wasn't available
41
- end
42
-
43
- begin
44
- require 'ruby-prof/task'
45
-
46
- RubyProf::ProfileTask.new(:profile) do |t|
47
- common_test_settings(t)
48
- t.output_dir = "#{File.dirname(__FILE__)}/profile"
49
- t.printer = :call_tree
50
- t.min_percent = 10
51
- end
52
- rescue LoadError
53
- # Ruby-prof wasn't available
54
- end
55
-
56
- require 'lib/version'
57
- gemspec = Gem::Specification.new do |s|
58
- s.name = "offroad"
59
- s.version = Offroad::VERSION
60
- s.authors = ["David Mike Simon"]
61
- s.email = "david.mike.simon@gmail.com"
62
- s.homepage = "http://github.com/DavidMikeSimon/offroad"
63
- s.summary = "Manages off-Internet instances of a Rails app"
64
- s.description = "Offroad manages offline instances of a Rails app on computers without Internet access. The online and offline instances can communicate via mirror files, transported by the user via thumbdrive, burned CD, etc."
65
-
66
- s.files = `git ls-files .`.split("\n") - [".gitignore"]
67
- s.platform = Gem::Platform::RUBY
68
- s.require_path = 'lib'
69
- s.rubyforge_project = '[none]'
70
-
71
- s.add_dependency('ar-extensions', '0.9.4')
72
- end
73
-
74
- Rake::GemPackageTask.new(gemspec) do |pkg|
75
- end
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+
7
+ def common_test_settings(t)
8
+ t.libs << 'lib'
9
+ t.libs << 'test'
10
+ t.pattern = 'test/**/*_test.rb'
11
+ t.verbose = true
12
+ end
13
+
14
+ desc 'Default: run unit and functional tests.'
15
+ task :default => :test
16
+
17
+ desc 'Test Offroad'
18
+ Rake::TestTask.new(:test) do |t|
19
+ common_test_settings(t)
20
+ end
21
+
22
+ desc 'Generate documentation for Offroad.'
23
+ Rake::RDocTask.new(:rdoc) do |rdoc|
24
+ rdoc.rdoc_dir = 'rdoc'
25
+ rdoc.title = 'Offroad'
26
+ rdoc.options << '--line-numbers' << '--inline-source'
27
+ rdoc.rdoc_files.include('README')
28
+ rdoc.rdoc_files.include('lib/**/*.rb')
29
+ end
30
+
31
+ begin
32
+ require 'rcov/rcovtask'
33
+
34
+ Rcov::RcovTask.new(:rcov) do |t|
35
+ common_test_settings(t)
36
+ t.pattern = 'test/unit/*_test.rb' # Don't care about coverage added by functional tests
37
+ t.rcov_opts << '-o coverage -x "/ruby/,/gems/,/test/,/migrate/"'
38
+ end
39
+ rescue LoadError
40
+ # Rcov wasn't available
41
+ end
42
+
43
+ begin
44
+ require 'ruby-prof/task'
45
+
46
+ RubyProf::ProfileTask.new(:profile) do |t|
47
+ common_test_settings(t)
48
+ t.output_dir = "#{File.dirname(__FILE__)}/profile"
49
+ t.printer = :call_tree
50
+ t.min_percent = 10
51
+ end
52
+ rescue LoadError
53
+ # Ruby-prof wasn't available
54
+ end
55
+
56
+ require 'lib/version'
57
+ gemspec = Gem::Specification.new do |s|
58
+ s.name = "offroad"
59
+ s.version = Offroad::VERSION
60
+ s.authors = ["David Mike Simon"]
61
+ s.email = "david.mike.simon@gmail.com"
62
+ s.homepage = "http://github.com/DavidMikeSimon/offroad"
63
+ s.summary = "Manages off-Internet instances of a Rails app"
64
+ s.description = "Offroad manages offline instances of a Rails app on computers without Internet access. The online and offline instances can communicate via mirror files, transported by the user via thumbdrive, burned CD, etc."
65
+
66
+ s.files = `git ls-files .`.split("\n") - [".gitignore"]
67
+ s.platform = Gem::Platform::RUBY
68
+ s.require_path = 'lib'
69
+ s.rubyforge_project = '[none]'
70
+
71
+ s.add_dependency('ar-extensions', '0.9.4')
72
+ end
73
+
74
+ Rake::GemPackageTask.new(gemspec) do |pkg|
75
+ end
data/TODO CHANGED
@@ -1,42 +1,42 @@
1
- High priority:
2
- - Creating/updating the installer (ideally, git pull on target platform, one rake task, then git push)
3
- - Add a generate task to initialize config files in a Rails app
4
- - Documentation
5
- - Resolve issue of too-long index names
6
-
7
- Low priority:
8
- - Make unit tests against bugs fixed by 7f8415 and 31455d
9
- - Try to come up with some more convenient way of building unit-test-specific model classes
10
- - Make sure ModelStates don't continue to stick around and cause trouble even after a class no longer acts_as_offroadable
11
- - Get README into the auto-generated gem rdocs
12
- - Get the test suite to stop generating a log file, it's annoying
13
- - Use finer-grained exceptions to allow for better error testing
14
- - Don't allow group_single records to reference non-single group records, since group_single "ownership" can change
15
- - Don't assume "id" as primary key name, always look it up with the appropriate ActiveRecord method
16
- - Allow offline app to relinquish ownership by setting a belongs_to field to 0 or NULL
17
- - Figure out why Hobo assumes all fields non-editable (something I'm doing in the patched-in updatable check...)
18
- - Assign a random number to group state when it is created, only accept mirror files with matching random number
19
- - This is to prevent confusion if a group is made offline, then online, then offline again
20
- - Also assign a random number to the online app, and only accept mirror files which match this number as well
21
- - Prevents multiple installations of an app from reading each others' mirror files
22
- - Use read transactions for mirror write operations (is it already doing this?)
23
- - The launcher should keep a log file
24
- - Include recent log lines (for both Rails and the launcher) in generated up-mirror files, for debugging purposes
25
- - Use rails logger to note activity
26
- - When mirror data version is confirmed, delete all sendable record state entries for deletions older than min(version) over all group_states
27
- - Try to gather some kind of machine identifier to put in a offline-owned group_state column
28
- - Try streaming out with large data sets and make sure it is sufficiently speedy, and is actually streaming (i.e. no Content-Length header)
29
- - Change mime-type of mirror files being downloaded to something that doesn't allow it to be accidentally viewed in-browser
30
- - Use md5sum on the entire cargo file instead of just each individual part, to protect against corruption outside the actual data segments
31
- - Allow app to optionally specify batch sizes on a model-by-model basis
32
- - Mirror file imports in the down mirror app should happen through GUI, and GUI should delete files once succesfully imported
33
- - If offline app attempts to import a down mirror file and it doesn't work, it should:
34
- - Delete the down mirror file as usual, but...
35
- - Explain that it wasn't imported and that another down mirror file should be downloaded
36
- - Figure out which activerecord methods skip the save callbacks, and wrap them to also update the mirror version
37
- - Maybe methods for which this isn't practical should raise an error when called...
38
- - Load all the other informational values in GroupState
39
- - App creates a record then deletes it then sends mirror; does a deletion srs still get sent?
40
- - For any fix to this: make sure that if app updates record then deletes it then sends mirror that deletion gets sent
41
- - Maybe that means we need both create_version and update_version on srs?
42
- - Put the CargoStreamer into its own gem, it might be handy for other stuff
1
+ High priority:
2
+ - Creating/updating the installer (ideally, git pull on target platform, one rake task, then git push)
3
+ - Add a generate task to initialize config files in a Rails app
4
+ - Documentation
5
+ - Resolve issue of too-long index names
6
+
7
+ Low priority:
8
+ - Make unit tests against bugs fixed by 7f8415 and 31455d
9
+ - Try to come up with some more convenient way of building unit-test-specific model classes
10
+ - Make sure ModelStates don't continue to stick around and cause trouble even after a class no longer acts_as_offroadable
11
+ - Get README into the auto-generated gem rdocs
12
+ - Get the test suite to stop generating a log file, it's annoying
13
+ - Use finer-grained exceptions to allow for better error testing
14
+ - Don't allow group_single records to reference non-single group records, since group_single "ownership" can change
15
+ - Don't assume "id" as primary key name, always look it up with the appropriate ActiveRecord method
16
+ - Allow offline app to relinquish ownership by setting a belongs_to field to 0 or NULL
17
+ - Figure out why Hobo assumes all fields non-editable (something I'm doing in the patched-in updatable check...)
18
+ - Assign a random number to group state when it is created, only accept mirror files with matching random number
19
+ - This is to prevent confusion if a group is made offline, then online, then offline again
20
+ - Also assign a random number to the online app, and only accept mirror files which match this number as well
21
+ - Prevents multiple installations of an app from reading each others' mirror files
22
+ - Use read transactions for mirror write operations (is it already doing this?)
23
+ - The launcher should keep a log file
24
+ - Include recent log lines (for both Rails and the launcher) in generated up-mirror files, for debugging purposes
25
+ - Use rails logger to note activity
26
+ - When mirror data version is confirmed, delete all sendable record state entries for deletions older than min(version) over all group_states
27
+ - Try to gather some kind of machine identifier to put in a offline-owned group_state column
28
+ - Try streaming out with large data sets and make sure it is sufficiently speedy, and is actually streaming (i.e. no Content-Length header)
29
+ - Change mime-type of mirror files being downloaded to something that doesn't allow it to be accidentally viewed in-browser
30
+ - Use md5sum on the entire cargo file instead of just each individual part, to protect against corruption outside the actual data segments
31
+ - Allow app to optionally specify batch sizes on a model-by-model basis
32
+ - Mirror file imports in the down mirror app should happen through GUI, and GUI should delete files once succesfully imported
33
+ - If offline app attempts to import a down mirror file and it doesn't work, it should:
34
+ - Delete the down mirror file as usual, but...
35
+ - Explain that it wasn't imported and that another down mirror file should be downloaded
36
+ - Figure out which activerecord methods skip the save callbacks, and wrap them to also update the mirror version
37
+ - Maybe methods for which this isn't practical should raise an error when called...
38
+ - Load all the other informational values in GroupState
39
+ - App creates a record then deletes it then sends mirror; does a deletion srs still get sent?
40
+ - For any fix to this: make sure that if app updates record then deletes it then sends mirror that deletion gets sent
41
+ - Maybe that means we need both create_version and update_version on srs?
42
+ - Put the CargoStreamer into its own gem, it might be handy for other stuff
@@ -1,85 +1,85 @@
1
- module Offroad
2
- private
3
-
4
- class GroupState < ActiveRecord::Base
5
- set_table_name "offroad_group_states"
6
-
7
- validates_presence_of :app_group_id
8
-
9
- def validate
10
- errors.add_to_base "Cannot find associated app group record" unless app_group
11
- end
12
-
13
- def app_group
14
- Offroad::group_base_model.find_by_id(app_group_id)
15
- end
16
-
17
- has_many :received_record_states, :class_name => "::Offroad::ReceivedRecordState", :dependent => :delete_all
18
-
19
- named_scope :for_group, lambda { |group| { :conditions => {
20
- :app_group_id => valid_group_record?(group) ? group.id : 0
21
- } } }
22
-
23
- def before_create
24
- if Offroad::app_offline?
25
- # FIXME : Fill in last_installation_at, launcher_version, app_version, etc
26
- self.operating_system ||= RUBY_PLATFORM
27
- end
28
-
29
- self.confirmed_group_data_version ||= 1
30
-
31
- # When first setting a group offline at online app, assume it will start out with at least current global data.
32
- # It should, since that's the earliest version that could be loaded into the initial down mirror file.
33
- self.confirmed_global_data_version ||= Offroad::app_online? ? SystemState::current_mirror_version : 1
34
- end
35
-
36
- def update_from_remote_group_state!(remote_gs)
37
- versioning_columns = [
38
- 'confirmed_global_data_version',
39
- 'confirmed_group_data_version'
40
- ]
41
-
42
- online_owned_columns = [
43
- 'last_installer_downloaded_at',
44
- 'last_down_mirror_created_at',
45
- 'last_up_mirror_loaded_at'
46
- ]
47
-
48
- offline_owned_columns = [
49
- 'last_installation_at',
50
- 'last_down_mirror_loaded_at',
51
- 'last_up_mirror_created_at',
52
- 'launcher_version',
53
- 'app_version',
54
- 'operating_system'
55
- ]
56
-
57
- # Copy in values from columns owned by the remote environment that created remote_gs
58
- (Offroad::app_offline? ? online_owned_columns : offline_owned_columns).each do |col|
59
- self.send("#{col}=", remote_gs.send(col))
60
- end
61
-
62
- # If the remote side says they have a newer version of something than we currently think they have, update
63
- versioning_columns.each do |col|
64
- self.send("#{col}=", [self.send(col), remote_gs.send(col)].max)
65
- end
66
-
67
- save!
68
- end
69
-
70
- def self.safe_to_load_from_cargo_stream?
71
- true
72
- end
73
-
74
- def self.note_group_destroyed(group)
75
- rec = find_by_app_group_id(group.id)
76
- rec.destroy
77
- end
78
-
79
- private
80
-
81
- def self.valid_group_record?(rec)
82
- rec.class.respond_to?(:offroad_group_base?) && rec.class.offroad_group_base?
83
- end
84
- end
85
- end
1
+ module Offroad
2
+ private
3
+
4
+ class GroupState < ActiveRecord::Base
5
+ set_table_name "offroad_group_states"
6
+
7
+ validates_presence_of :app_group_id
8
+
9
+ def validate
10
+ errors.add_to_base "Cannot find associated app group record" unless app_group
11
+ end
12
+
13
+ def app_group
14
+ Offroad::group_base_model.find_by_id(app_group_id)
15
+ end
16
+
17
+ has_many :received_record_states, :class_name => "::Offroad::ReceivedRecordState", :dependent => :delete_all
18
+
19
+ named_scope :for_group, lambda { |group| { :conditions => {
20
+ :app_group_id => valid_group_record?(group) ? group.id : 0
21
+ } } }
22
+
23
+ def before_create
24
+ if Offroad::app_offline?
25
+ # FIXME : Fill in last_installation_at, launcher_version, app_version, etc
26
+ self.operating_system ||= RUBY_PLATFORM
27
+ end
28
+
29
+ self.confirmed_group_data_version ||= 1
30
+
31
+ # When first setting a group offline at online app, assume it will start out with at least current global data.
32
+ # It should, since that's the earliest version that could be loaded into the initial down mirror file.
33
+ self.confirmed_global_data_version ||= Offroad::app_online? ? SystemState::current_mirror_version : 1
34
+ end
35
+
36
+ def update_from_remote_group_state!(remote_gs)
37
+ versioning_columns = [
38
+ 'confirmed_global_data_version',
39
+ 'confirmed_group_data_version'
40
+ ]
41
+
42
+ online_owned_columns = [
43
+ 'last_installer_downloaded_at',
44
+ 'last_down_mirror_created_at',
45
+ 'last_up_mirror_loaded_at'
46
+ ]
47
+
48
+ offline_owned_columns = [
49
+ 'last_installation_at',
50
+ 'last_down_mirror_loaded_at',
51
+ 'last_up_mirror_created_at',
52
+ 'launcher_version',
53
+ 'app_version',
54
+ 'operating_system'
55
+ ]
56
+
57
+ # Copy in values from columns owned by the remote environment that created remote_gs
58
+ (Offroad::app_offline? ? online_owned_columns : offline_owned_columns).each do |col|
59
+ self.send("#{col}=", remote_gs.send(col))
60
+ end
61
+
62
+ # If the remote side says they have a newer version of something than we currently think they have, update
63
+ versioning_columns.each do |col|
64
+ self.send("#{col}=", [self.send(col), remote_gs.send(col)].max)
65
+ end
66
+
67
+ save!
68
+ end
69
+
70
+ def self.safe_to_load_from_cargo_stream?
71
+ true
72
+ end
73
+
74
+ def self.note_group_destroyed(group)
75
+ rec = find_by_app_group_id(group.id)
76
+ rec.destroy
77
+ end
78
+
79
+ private
80
+
81
+ def self.valid_group_record?(rec)
82
+ rec.class.respond_to?(:offroad_group_base?) && rec.class.offroad_group_base?
83
+ end
84
+ end
85
+ end