offroad 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -1,53 +1,53 @@
1
- module Offroad
2
- private
3
-
4
- # Non-database model representing general information attached to any mirror file
5
- # Based on the pattern found here: http://stackoverflow.com/questions/315850/rails-model-without-database
6
- class MirrorInfo < ActiveRecord::Base
7
- self.abstract_class = true
8
-
9
- def self.columns
10
- @columns ||= []
11
- end
12
-
13
- [
14
- [:created_at, :datetime],
15
- [:online_site, :string],
16
- [:app, :string],
17
- [:app_mode, :string],
18
- [:app_version, :string],
19
- [:operating_system, :string],
20
- [:generator, :string],
21
- [:schema_migrations, :string],
22
- [:initial_file, :boolean]
23
- ].each do |attr_name, attr_type|
24
- columns << ActiveRecord::ConnectionAdapters::Column.new(attr_name.to_s, nil, attr_type.to_s, true)
25
- validates_presence_of attr_name unless attr_type == :boolean
26
- end
27
-
28
- def self.safe_to_load_from_cargo_stream?
29
- true
30
- end
31
-
32
- def self.new_from_group(group, initial_file = false)
33
- mode = Offroad::app_online? ? "online" : "offline"
34
- migration_query = "SELECT version FROM schema_migrations ORDER BY version"
35
- migrations = Offroad::group_base_model.connection.select_all(migration_query).map{ |r| r["version"] }
36
- return MirrorInfo.new(
37
- :created_at => Time.now.to_s,
38
- :online_site => Offroad::online_url,
39
- :app => Offroad::app_name,
40
- :app_mode => mode.titleize,
41
- :app_version => Offroad::app_version,
42
- :operating_system => RUBY_PLATFORM,
43
- :generator => "Offroad " + Offroad::VERSION,
44
- :schema_migrations => migrations.join(","),
45
- :initial_file => initial_file
46
- )
47
- end
48
-
49
- def save
50
- raise DataError.new("Cannot save MirrorInfo records")
51
- end
52
- end
53
- end
1
+ module Offroad
2
+ private
3
+
4
+ # Non-database model representing general information attached to any mirror file
5
+ # Based on the pattern found here: http://stackoverflow.com/questions/315850/rails-model-without-database
6
+ class MirrorInfo < ActiveRecord::Base
7
+ self.abstract_class = true
8
+
9
+ def self.columns
10
+ @columns ||= []
11
+ end
12
+
13
+ [
14
+ [:created_at, :datetime],
15
+ [:online_site, :string],
16
+ [:app, :string],
17
+ [:app_mode, :string],
18
+ [:app_version, :string],
19
+ [:operating_system, :string],
20
+ [:generator, :string],
21
+ [:schema_migrations, :string],
22
+ [:initial_file, :boolean]
23
+ ].each do |attr_name, attr_type|
24
+ columns << ActiveRecord::ConnectionAdapters::Column.new(attr_name.to_s, nil, attr_type.to_s, true)
25
+ validates_presence_of attr_name unless attr_type == :boolean
26
+ end
27
+
28
+ def self.safe_to_load_from_cargo_stream?
29
+ true
30
+ end
31
+
32
+ def self.new_from_group(group, initial_file = false)
33
+ mode = Offroad::app_online? ? "online" : "offline"
34
+ migration_query = "SELECT version FROM schema_migrations ORDER BY version"
35
+ migrations = Offroad::group_base_model.connection.select_all(migration_query).map{ |r| r["version"] }
36
+ return MirrorInfo.new(
37
+ :created_at => Time.now.to_s,
38
+ :online_site => Offroad::online_url,
39
+ :app => Offroad::app_name,
40
+ :app_mode => mode.titleize,
41
+ :app_version => Offroad::app_version,
42
+ :operating_system => RUBY_PLATFORM,
43
+ :generator => "Offroad " + Offroad::VERSION,
44
+ :schema_migrations => migrations.join(","),
45
+ :initial_file => initial_file
46
+ )
47
+ end
48
+
49
+ def save
50
+ raise DataError.new("Cannot save MirrorInfo records")
51
+ end
52
+ end
53
+ end
@@ -1,36 +1,36 @@
1
- module Offroad
2
- private
3
-
4
- class ModelState < ActiveRecord::Base
5
- set_table_name "offroad_model_states"
6
-
7
- validates_presence_of :app_model_name
8
-
9
- def validate
10
- model = nil
11
- begin
12
- model = app_model
13
- rescue NameError
14
- errors.add_to_base "Given model name does not correspond to a constant"
15
- end
16
-
17
- if model
18
- errors.add_to_base "Constant is not a mirrored model" unless self.class.valid_model?(model)
19
- end
20
- end
21
-
22
- named_scope :for_model, lambda { |model| { :conditions => {
23
- :app_model_name => valid_model?(model) ? model.name : nil
24
- } } }
25
-
26
- def app_model
27
- app_model_name.constantize
28
- end
29
-
30
- private
31
-
32
- def self.valid_model?(model)
33
- model.respond_to?(:acts_as_offroadable?) && model.acts_as_offroadable?
34
- end
35
- end
36
- end
1
+ module Offroad
2
+ private
3
+
4
+ class ModelState < ActiveRecord::Base
5
+ set_table_name "offroad_model_states"
6
+
7
+ validates_presence_of :app_model_name
8
+
9
+ def validate
10
+ model = nil
11
+ begin
12
+ model = app_model
13
+ rescue NameError
14
+ errors.add_to_base "Given model name does not correspond to a constant"
15
+ end
16
+
17
+ if model
18
+ errors.add_to_base "Constant is not a mirrored model" unless self.class.valid_model?(model)
19
+ end
20
+ end
21
+
22
+ named_scope :for_model, lambda { |model| { :conditions => {
23
+ :app_model_name => valid_model?(model) ? model.name : nil
24
+ } } }
25
+
26
+ def app_model
27
+ app_model_name.constantize
28
+ end
29
+
30
+ private
31
+
32
+ def self.valid_model?(model)
33
+ model.respond_to?(:acts_as_offroadable?) && model.acts_as_offroadable?
34
+ end
35
+ end
36
+ end
@@ -1,115 +1,115 @@
1
- module Offroad
2
- private
3
-
4
- class ReceivedRecordState < ActiveRecord::Base
5
- set_table_name "offroad_received_record_states"
6
-
7
- belongs_to :model_state, :class_name => "::Offroad::ModelState"
8
-
9
- belongs_to :group_state, :class_name => "::Offroad::GroupState"
10
-
11
- def validate
12
- unless model_state
13
- errors.add_to_base "Cannot find associated model state"
14
- return
15
- end
16
- model = model_state.app_model
17
-
18
- if Offroad::app_offline?
19
- if model.offroad_group_data?
20
- errors.add_to_base "Cannot allow received record state for group data in offline app"
21
- end
22
- elsif Offroad::app_online?
23
- if model.offroad_global_data?
24
- errors.add_to_base "Cannot allow received record state for global records in online app"
25
- elsif group_state.nil?
26
- errors.add_to_base "Cannot allow received record state for online group records in online app"
27
- end
28
- end
29
-
30
- if model.offroad_global_data? && group_state
31
- errors.add_to_base "Cannot allow received record state for global records to also be assoc with a group"
32
- end
33
-
34
- begin
35
- app_record
36
- rescue ActiveRecord::RecordNotFound
37
- errors.add_to_base "Cannot find associated app record"
38
- end
39
- end
40
-
41
- named_scope :for_model, lambda { |model| { :conditions => {
42
- :model_state_id => model.offroad_model_state.id
43
- } } }
44
-
45
- named_scope :for_model_and_group_if_apropos, lambda { |model, group| { :conditions => {
46
- :model_state_id => model.offroad_model_state.id,
47
- :group_state_id => (group && model.offroad_group_data? && group.group_state) ? group.group_state.id : 0
48
- } } }
49
-
50
- named_scope :for_record, lambda { |rec| { :conditions => {
51
- :model_state_id => rec.class.offroad_model_state.id,
52
- :group_state_id => (rec.class.offroad_group_data? && rec.group_state) ? rec.group_state.id : 0,
53
- :local_record_id => rec.id
54
- } } }
55
-
56
- def app_record
57
- model_state.app_model.find(local_record_id)
58
- end
59
-
60
- def self.redirect_to_local_ids(records, column, model, group)
61
- column = column.to_sym
62
- source = self.for_model_and_group_if_apropos(model, group)
63
- already_allocated = source.all(:conditions => { :remote_record_id => records.map{|r| r[column]} }).index_by(&:remote_record_id)
64
-
65
- remaining = {} # Maps newly discovered remote id to list of records in batch that reference that id
66
- records.each do |r|
67
- remote_id = r[column]
68
- next unless remote_id && remote_id > 0
69
- # TODO Check for illegal references here (i.e. group model referencing global model)
70
- if already_allocated.has_key?(remote_id)
71
- r[column] = already_allocated[remote_id].local_record_id
72
- else
73
- # Target doesn't exist yet, we'll figure out what its local id will be later
74
- if remaining.has_key?(remote_id)
75
- remaining[remote_id] << r
76
- else
77
- remaining[remote_id] = [r]
78
- end
79
- end
80
- end
81
-
82
- return unless remaining.size > 0
83
-
84
- # Reserve access to a block of local ids
85
- if model.connection.adapter_name.downcase.include?("postgres")
86
- nextval_cmd = model.connection.select_value("SELECT column_default FROM information_schema.columns WHERE table_name = '#{model.table_name}' AND column_name = '#{model.primary_key}'")
87
- local_ids = model.connection.select_values("SELECT #{nextval_cmd} FROM generate_series(1,#{remaining.size})").map(&:to_i)
88
- else
89
- # Reserve access to a block of local ids by creating temporary records to advance the autoincrement counter
90
- # TODO I'm pretty sure this is safe because it'll always be used in a transaction, but I should check
91
- model.import([model.primary_key.to_sym], [[nil]]*remaining.size, :validate => false, :timestamps => false)
92
- last_id = model.last(:select => model.primary_key, :order => model.primary_key).id
93
- local_ids = ((last_id+1-remaining.size)..last_id).to_a
94
- model.delete(local_ids)
95
- end
96
-
97
- # Create the corresponding RRSes
98
- model_state_id = model.offroad_model_state.id
99
- group_state = model.offroad_group_data? && group ? group.group_state : nil
100
- group_state_id = group_state ? group_state.id : 0
101
- self.import(
102
- [:model_state_id, :group_state_id, :local_record_id, :remote_record_id],
103
- local_ids.zip(remaining.keys).map{|here, there| [model_state_id, group_state_id, here, there]},
104
- :validate => false, :timestamps => false
105
- )
106
-
107
- # Finally do the redirection to the new ids
108
- remaining.each_key.each_with_index do |remote_id, i|
109
- remaining[remote_id].each do |r|
110
- r[column] = local_ids[i]
111
- end
112
- end
113
- end
114
- end
115
- end
1
+ module Offroad
2
+ private
3
+
4
+ class ReceivedRecordState < ActiveRecord::Base
5
+ set_table_name "offroad_received_record_states"
6
+
7
+ belongs_to :model_state, :class_name => "::Offroad::ModelState"
8
+
9
+ belongs_to :group_state, :class_name => "::Offroad::GroupState"
10
+
11
+ def validate
12
+ unless model_state
13
+ errors.add_to_base "Cannot find associated model state"
14
+ return
15
+ end
16
+ model = model_state.app_model
17
+
18
+ if Offroad::app_offline?
19
+ if model.offroad_group_data?
20
+ errors.add_to_base "Cannot allow received record state for group data in offline app"
21
+ end
22
+ elsif Offroad::app_online?
23
+ if model.offroad_global_data?
24
+ errors.add_to_base "Cannot allow received record state for global records in online app"
25
+ elsif group_state.nil?
26
+ errors.add_to_base "Cannot allow received record state for online group records in online app"
27
+ end
28
+ end
29
+
30
+ if model.offroad_global_data? && group_state
31
+ errors.add_to_base "Cannot allow received record state for global records to also be assoc with a group"
32
+ end
33
+
34
+ begin
35
+ app_record
36
+ rescue ActiveRecord::RecordNotFound
37
+ errors.add_to_base "Cannot find associated app record"
38
+ end
39
+ end
40
+
41
+ named_scope :for_model, lambda { |model| { :conditions => {
42
+ :model_state_id => model.offroad_model_state.id
43
+ } } }
44
+
45
+ named_scope :for_model_and_group_if_apropos, lambda { |model, group| { :conditions => {
46
+ :model_state_id => model.offroad_model_state.id,
47
+ :group_state_id => (group && model.offroad_group_data? && group.group_state) ? group.group_state.id : 0
48
+ } } }
49
+
50
+ named_scope :for_record, lambda { |rec| { :conditions => {
51
+ :model_state_id => rec.class.offroad_model_state.id,
52
+ :group_state_id => (rec.class.offroad_group_data? && rec.group_state) ? rec.group_state.id : 0,
53
+ :local_record_id => rec.id
54
+ } } }
55
+
56
+ def app_record
57
+ model_state.app_model.find(local_record_id)
58
+ end
59
+
60
+ def self.redirect_to_local_ids(records, column, model, group)
61
+ column = column.to_sym
62
+ source = self.for_model_and_group_if_apropos(model, group)
63
+ already_allocated = source.all(:conditions => { :remote_record_id => records.map{|r| r[column]} }).index_by(&:remote_record_id)
64
+
65
+ remaining = {} # Maps newly discovered remote id to list of records in batch that reference that id
66
+ records.each do |r|
67
+ remote_id = r[column]
68
+ next unless remote_id && remote_id > 0
69
+ # TODO Check for illegal references here (i.e. group model referencing global model)
70
+ if already_allocated.has_key?(remote_id)
71
+ r[column] = already_allocated[remote_id].local_record_id
72
+ else
73
+ # Target doesn't exist yet, we'll figure out what its local id will be later
74
+ if remaining.has_key?(remote_id)
75
+ remaining[remote_id] << r
76
+ else
77
+ remaining[remote_id] = [r]
78
+ end
79
+ end
80
+ end
81
+
82
+ return unless remaining.size > 0
83
+
84
+ # Reserve access to a block of local ids
85
+ if model.connection.adapter_name.downcase.include?("postgres")
86
+ nextval_cmd = model.connection.select_value("SELECT column_default FROM information_schema.columns WHERE table_name = '#{model.table_name}' AND column_name = '#{model.primary_key}'")
87
+ local_ids = model.connection.select_values("SELECT #{nextval_cmd} FROM generate_series(1,#{remaining.size})").map(&:to_i)
88
+ else
89
+ # Reserve access to a block of local ids by creating temporary records to advance the autoincrement counter
90
+ # TODO I'm pretty sure this is safe because it'll always be used in a transaction, but I should check
91
+ model.import([model.primary_key.to_sym], [[nil]]*remaining.size, :validate => false, :timestamps => false)
92
+ last_id = model.last(:select => model.primary_key, :order => model.primary_key).id
93
+ local_ids = ((last_id+1-remaining.size)..last_id).to_a
94
+ model.delete(local_ids)
95
+ end
96
+
97
+ # Create the corresponding RRSes
98
+ model_state_id = model.offroad_model_state.id
99
+ group_state = model.offroad_group_data? && group ? group.group_state : nil
100
+ group_state_id = group_state ? group_state.id : 0
101
+ self.import(
102
+ [:model_state_id, :group_state_id, :local_record_id, :remote_record_id],
103
+ local_ids.zip(remaining.keys).map{|here, there| [model_state_id, group_state_id, here, there]},
104
+ :validate => false, :timestamps => false
105
+ )
106
+
107
+ # Finally do the redirection to the new ids
108
+ remaining.each_key.each_with_index do |remote_id, i|
109
+ remaining[remote_id].each do |r|
110
+ r[column] = local_ids[i]
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -1,91 +1,91 @@
1
- module Offroad
2
- private
3
-
4
- class SendableRecordState < ActiveRecord::Base
5
- set_table_name "offroad_sendable_record_states"
6
-
7
- belongs_to :model_state, :class_name => "::Offroad::ModelState"
8
-
9
- def validate
10
- unless model_state
11
- errors.add_to_base "Cannot find associated model state"
12
- return
13
- end
14
-
15
- rec = nil
16
- unless deleted
17
- begin
18
- rec = app_record
19
- rescue ActiveRecord::RecordNotFound
20
- errors.add_to_base "Cannot find associated app record"
21
- end
22
- end
23
-
24
- if rec
25
- if Offroad::app_offline? && app_record.class.offroad_global_data?
26
- errors.add_to_base "Cannot create sendable record state for global data in offline app"
27
- elsif Offroad::app_online? && app_record.class.offroad_group_data?
28
- errors.add_to_base "Cannot create sendable record state for group data in online app"
29
- end
30
- end
31
- end
32
-
33
- named_scope :for_model, lambda { |model| { :conditions => {
34
- :model_state_id => model.offroad_model_state.id
35
- } } }
36
-
37
- named_scope :for_deleted_records, :conditions => { :deleted => true }
38
- named_scope :for_non_deleted_records, :conditions => { :deleted => false }
39
-
40
- named_scope :with_version_greater_than, lambda { |v| { :conditions => ["mirror_version > ?", v] } }
41
-
42
- named_scope :for_record, lambda { |rec| { :conditions => {
43
- :model_state_id => rec.class.offroad_model_state.id,
44
- :local_record_id => rec.id
45
- } } }
46
-
47
- def app_record
48
- model_state.app_model.find(local_record_id)
49
- end
50
-
51
- # We put SRS records in mirror files to represent deleted records
52
- def self.safe_to_load_from_cargo_stream?
53
- true
54
- end
55
-
56
- def self.note_record_destroyed(record)
57
- mark_record_changes(record) do |rec|
58
- rec.deleted = true
59
- end
60
- end
61
-
62
- def self.note_record_created_or_updated(record)
63
- mark_record_changes(record)
64
- end
65
-
66
- def self.setup_imported(model, batch)
67
- model_state_id = model.offroad_model_state.id
68
- mirror_version = SystemState::current_mirror_version
69
- self.import(
70
- [:model_state_id, :local_record_id, :mirror_version],
71
- batch.map{|r| [model_state_id, r.id, mirror_version]},
72
- :validate => false,
73
- :timestamps => false
74
- )
75
- end
76
-
77
- private
78
-
79
- def self.mark_record_changes(record)
80
- transaction do
81
- scope = for_record(record)
82
- rec_state = scope.first || scope.create
83
- rec_state.lock!
84
- rec_state.mirror_version = SystemState::current_mirror_version
85
- yield(rec_state) if block_given?
86
- rec_state.save!
87
- end
88
- end
89
-
90
- end
91
- end
1
+ module Offroad
2
+ private
3
+
4
+ class SendableRecordState < ActiveRecord::Base
5
+ set_table_name "offroad_sendable_record_states"
6
+
7
+ belongs_to :model_state, :class_name => "::Offroad::ModelState"
8
+
9
+ def validate
10
+ unless model_state
11
+ errors.add_to_base "Cannot find associated model state"
12
+ return
13
+ end
14
+
15
+ rec = nil
16
+ unless deleted
17
+ begin
18
+ rec = app_record
19
+ rescue ActiveRecord::RecordNotFound
20
+ errors.add_to_base "Cannot find associated app record"
21
+ end
22
+ end
23
+
24
+ if rec
25
+ if Offroad::app_offline? && app_record.class.offroad_global_data?
26
+ errors.add_to_base "Cannot create sendable record state for global data in offline app"
27
+ elsif Offroad::app_online? && app_record.class.offroad_group_data?
28
+ errors.add_to_base "Cannot create sendable record state for group data in online app"
29
+ end
30
+ end
31
+ end
32
+
33
+ named_scope :for_model, lambda { |model| { :conditions => {
34
+ :model_state_id => model.offroad_model_state.id
35
+ } } }
36
+
37
+ named_scope :for_deleted_records, :conditions => { :deleted => true }
38
+ named_scope :for_non_deleted_records, :conditions => { :deleted => false }
39
+
40
+ named_scope :with_version_greater_than, lambda { |v| { :conditions => ["mirror_version > ?", v] } }
41
+
42
+ named_scope :for_record, lambda { |rec| { :conditions => {
43
+ :model_state_id => rec.class.offroad_model_state.id,
44
+ :local_record_id => rec.id
45
+ } } }
46
+
47
+ def app_record
48
+ model_state.app_model.find(local_record_id)
49
+ end
50
+
51
+ # We put SRS records in mirror files to represent deleted records
52
+ def self.safe_to_load_from_cargo_stream?
53
+ true
54
+ end
55
+
56
+ def self.note_record_destroyed(record)
57
+ mark_record_changes(record) do |rec|
58
+ rec.deleted = true
59
+ end
60
+ end
61
+
62
+ def self.note_record_created_or_updated(record)
63
+ mark_record_changes(record)
64
+ end
65
+
66
+ def self.setup_imported(model, batch)
67
+ model_state_id = model.offroad_model_state.id
68
+ mirror_version = SystemState::current_mirror_version
69
+ self.import(
70
+ [:model_state_id, :local_record_id, :mirror_version],
71
+ batch.map{|r| [model_state_id, r.id, mirror_version]},
72
+ :validate => false,
73
+ :timestamps => false
74
+ )
75
+ end
76
+
77
+ private
78
+
79
+ def self.mark_record_changes(record)
80
+ transaction do
81
+ scope = for_record(record)
82
+ rec_state = scope.first || scope.create
83
+ rec_state.lock!
84
+ rec_state.mirror_version = SystemState::current_mirror_version
85
+ yield(rec_state) if block_given?
86
+ rec_state.save!
87
+ end
88
+ end
89
+
90
+ end
91
+ end