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.
- data/LICENSE +674 -0
- data/README.rdoc +29 -0
- data/Rakefile +75 -0
- data/TODO +42 -0
- data/lib/app/models/offroad/group_state.rb +85 -0
- data/lib/app/models/offroad/mirror_info.rb +53 -0
- data/lib/app/models/offroad/model_state.rb +36 -0
- data/lib/app/models/offroad/received_record_state.rb +109 -0
- data/lib/app/models/offroad/sendable_record_state.rb +91 -0
- data/lib/app/models/offroad/system_state.rb +33 -0
- data/lib/cargo_streamer.rb +222 -0
- data/lib/controller_extensions.rb +74 -0
- data/lib/exceptions.rb +16 -0
- data/lib/migrate/20100512164608_create_offroad_tables.rb +72 -0
- data/lib/mirror_data.rb +354 -0
- data/lib/model_extensions.rb +377 -0
- data/lib/module_funcs.rb +94 -0
- data/lib/offroad.rb +30 -0
- data/lib/version.rb +3 -0
- data/lib/view_helper.rb +7 -0
- data/templates/offline.rb +36 -0
- data/templates/offline_database.yml +7 -0
- data/templates/offroad.yml +6 -0
- data/test/app_root/app/controllers/application_controller.rb +2 -0
- data/test/app_root/app/controllers/group_controller.rb +28 -0
- data/test/app_root/app/models/global_record.rb +10 -0
- data/test/app_root/app/models/group.rb +12 -0
- data/test/app_root/app/models/group_owned_record.rb +68 -0
- data/test/app_root/app/models/guest.rb +7 -0
- data/test/app_root/app/models/subrecord.rb +12 -0
- data/test/app_root/app/models/unmirrored_record.rb +4 -0
- data/test/app_root/app/views/group/download_down_mirror.html.erb +4 -0
- data/test/app_root/app/views/group/download_initial_down_mirror.html.erb +4 -0
- data/test/app_root/app/views/group/download_up_mirror.html.erb +6 -0
- data/test/app_root/app/views/group/upload_down_mirror.html.erb +1 -0
- data/test/app_root/app/views/group/upload_up_mirror.html.erb +1 -0
- data/test/app_root/app/views/layouts/mirror.html.erb +9 -0
- data/test/app_root/config/boot.rb +115 -0
- data/test/app_root/config/database.yml +6 -0
- data/test/app_root/config/environment.rb +15 -0
- data/test/app_root/config/environments/test.rb +17 -0
- data/test/app_root/config/offroad.yml +6 -0
- data/test/app_root/config/routes.rb +4 -0
- data/test/app_root/db/migrate/20100529235049_create_tables.rb +64 -0
- data/test/app_root/lib/common_hobo.rb +15 -0
- data/test/app_root/vendor/plugins/offroad/init.rb +2 -0
- data/test/functional/mirror_operations_test.rb +148 -0
- data/test/test_helper.rb +405 -0
- data/test/unit/app_state_tracking_test.rb +275 -0
- data/test/unit/cargo_streamer_test.rb +332 -0
- data/test/unit/global_data_test.rb +102 -0
- data/test/unit/group_controller_test.rb +152 -0
- data/test/unit/group_data_test.rb +435 -0
- data/test/unit/group_single_test.rb +136 -0
- data/test/unit/hobo_permissions_test.rb +57 -0
- data/test/unit/mirror_data_test.rb +1271 -0
- data/test/unit/mirror_info_test.rb +31 -0
- data/test/unit/module_funcs_test.rb +37 -0
- data/test/unit/pathological_model_test.rb +62 -0
- data/test/unit/test_framework_test.rb +86 -0
- data/test/unit/unmirrored_data_test.rb +14 -0
- 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
|
data/lib/module_funcs.rb
ADDED
@@ -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
data/lib/view_helper.rb
ADDED
@@ -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,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,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
|