offroad 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -1
- data/lib/app/models/offroad/received_record_state.rb +13 -7
- data/lib/mirror_data.rb +28 -6
- data/lib/model_extensions.rb +5 -5
- data/lib/offroad.rb +11 -0
- data/lib/version.rb +1 -1
- data/test/app_root/config/database-pg.yml +8 -0
- data/test/app_root/config/environment.rb +9 -0
- data/test/test_helper.rb +55 -7
- data/test/unit/mirror_data_test.rb +13 -1
- metadata +10 -7
data/Rakefile
CHANGED
@@ -81,12 +81,18 @@ module Offroad
|
|
81
81
|
|
82
82
|
return unless remaining.size > 0
|
83
83
|
|
84
|
-
# Reserve access to a block of local ids
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
90
96
|
|
91
97
|
# Create the corresponding RRSes
|
92
98
|
model_state_id = model.offroad_model_state.id
|
@@ -101,7 +107,7 @@ module Offroad
|
|
101
107
|
# Finally do the redirection to the new ids
|
102
108
|
remaining.each_key.each_with_index do |remote_id, i|
|
103
109
|
remaining[remote_id].each do |r|
|
104
|
-
r[column] = local_ids
|
110
|
+
r[column] = local_ids[i]
|
105
111
|
end
|
106
112
|
end
|
107
113
|
end
|
data/lib/mirror_data.rb
CHANGED
@@ -89,17 +89,28 @@ module Offroad
|
|
89
89
|
end
|
90
90
|
|
91
91
|
def delete_all_existing_database_records!
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
92
|
+
tables = ActiveRecord::Base.connection.tables
|
93
|
+
if ActiveRecord::Base.connection.adapter_name.downcase.include?("sqlite")
|
94
|
+
# Emptying sqlite_sequence resets SQLite's autoincrement counters.
|
95
|
+
# SQLite's autoincrement is nice in that it automatically picks largest ever id + 1.
|
96
|
+
# This means that after clearing sqlite_sequence and then populating database with manually-id'd rows,
|
97
|
+
# new records will be inserted with unique id's, no problem.
|
98
|
+
tables << "sqlite_sequence"
|
99
|
+
end
|
97
100
|
|
98
101
|
tables.each do |table|
|
99
|
-
next if table.start_with?("
|
102
|
+
next if table.start_with?("virtual_") # Used in testing # FIXME Should pick something less likely to collide with app name
|
100
103
|
next if table == "schema_migrations"
|
101
104
|
ActiveRecord::Base.connection.execute "DELETE FROM #{table}"
|
102
105
|
end
|
106
|
+
|
107
|
+
if ActiveRecord::Base.connection.adapter_name.downcase.include?("postgres")
|
108
|
+
# Reset all sequences so that autoincremented ids start from 1 again
|
109
|
+
seqnames = ActiveRecord::Base.connection.select_values "SELECT c.relname FROM pg_class c WHERE c.relkind = 'S'"
|
110
|
+
seqnames.each do |s|
|
111
|
+
ActiveRecord::Base.connection.execute "SELECT setval('#{s}', 1, false)"
|
112
|
+
end
|
113
|
+
end
|
103
114
|
end
|
104
115
|
|
105
116
|
def write_data(tgt)
|
@@ -292,6 +303,16 @@ module Offroad
|
|
292
303
|
batch.each { |rec| rec.after_offroad_upload }
|
293
304
|
end
|
294
305
|
end
|
306
|
+
if ActiveRecord::Base.connection.adapter_name.downcase.include?("postgres")
|
307
|
+
# Need to adjust the sequences so that records inserted from this point on don't collide with existing ids
|
308
|
+
cols = ActiveRecord::Base.connection.select_rows "select table_name, column_name, column_default from information_schema.columns WHERE column_default like 'nextval%'"
|
309
|
+
cols.each do |table_name, column_name, column_default|
|
310
|
+
if column_default =~ /nextval\('(.+)'(?:::.+)?\)/
|
311
|
+
seqname = $1
|
312
|
+
ActiveRecord::Base.connection.execute "SELECT setval('#{seqname}', (SELECT MAX(\"#{column_name}\") FROM \"#{table_name}\"))"
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
295
316
|
end
|
296
317
|
|
297
318
|
def import_non_initial_model_cargo(cs, model)
|
@@ -335,6 +356,7 @@ module Offroad
|
|
335
356
|
end
|
336
357
|
|
337
358
|
def validate_imported_models(cs)
|
359
|
+
Offroad::group_base_model.connection.clear_query_cache
|
338
360
|
while @imported_models_to_validate.size > 0
|
339
361
|
model = @imported_models_to_validate.pop
|
340
362
|
rrs_source = Offroad::ReceivedRecordState.for_model_and_group_if_apropos(model, @group)
|
data/lib/model_extensions.rb
CHANGED
@@ -35,13 +35,13 @@ module Offroad
|
|
35
35
|
named_scope :owned_by_offroad_group, lambda { |group| { :conditions => { :id => group.id } } }
|
36
36
|
named_scope :offline_groups, {
|
37
37
|
:joins =>
|
38
|
-
"INNER JOIN
|
38
|
+
"INNER JOIN \"#{Offroad::GroupState.table_name}\" ON \"#{Offroad::GroupState.table_name}\".app_group_id = \"#{table_name}\".\"#{primary_key}\""
|
39
39
|
}
|
40
40
|
named_scope :online_groups, {
|
41
41
|
:joins =>
|
42
|
-
"LEFT JOIN
|
42
|
+
"LEFT JOIN \"#{Offroad::GroupState.table_name}\" ON \"#{Offroad::GroupState.table_name}\".app_group_id = \"#{table_name}\".\"#{primary_key}\"",
|
43
43
|
:conditions =>
|
44
|
-
"
|
44
|
+
"\"#{Offroad::GroupState.table_name}\".app_group_id IS NULL"
|
45
45
|
}
|
46
46
|
when :group_owned then
|
47
47
|
named_scope :owned_by_offroad_group, lambda { |group| args_for_ownership_scope(group) }
|
@@ -117,10 +117,10 @@ module Offroad
|
|
117
117
|
assoc = offroad_parent_assoc
|
118
118
|
while true
|
119
119
|
if assoc.klass.offroad_group_base?
|
120
|
-
conditions << "
|
120
|
+
conditions << "\"#{assoc_owner.table_name}\".\"#{assoc.primary_key_name}\" = #{group.id}"
|
121
121
|
break
|
122
122
|
else
|
123
|
-
conditions << "
|
123
|
+
conditions << "\"#{assoc_owner.table_name}\".\"#{assoc.primary_key_name}\" = \"#{assoc.klass.table_name}\".\"#{assoc.klass.primary_key}\""
|
124
124
|
included_assocs << assoc
|
125
125
|
assoc_owner = assoc.klass
|
126
126
|
assoc = assoc.klass.offroad_parent_assoc
|
data/lib/offroad.rb
CHANGED
@@ -11,6 +11,17 @@ $LOAD_PATH << path
|
|
11
11
|
ActiveSupport::Dependencies.autoload_paths << path
|
12
12
|
|
13
13
|
require 'ar-extensions' # External dependency
|
14
|
+
# Monkey patch a bug in ar-extensions which breaks postgres compatibility
|
15
|
+
module ActiveRecord # :nodoc:
|
16
|
+
module ConnectionAdapters # :nodoc:
|
17
|
+
class AbstractAdapter # :nodoc:
|
18
|
+
def next_value_for_sequence(sequence_name)
|
19
|
+
%{nextval('#{sequence_name}')}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
14
25
|
|
15
26
|
require 'controller_extensions'
|
16
27
|
class ActionController::Base
|
data/lib/version.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), 'boot')
|
2
2
|
|
3
|
+
RAILS_GEM_VERSION = '2.3.11'
|
4
|
+
|
3
5
|
Rails::Initializer.run do |config|
|
4
6
|
config.cache_classes = false
|
5
7
|
config.whiny_nils = true
|
@@ -12,4 +14,11 @@ Rails::Initializer.run do |config|
|
|
12
14
|
else
|
13
15
|
HOBO_TEST_MODE = false
|
14
16
|
end
|
17
|
+
|
18
|
+
if ENV['PSQL_TEST_MODE']
|
19
|
+
puts "Using postgresql for a test database"
|
20
|
+
config.database_configuration_file = "#{RAILS_ROOT}/config/database-pg.yml"
|
21
|
+
else
|
22
|
+
puts "Using sqlite for a test database"
|
23
|
+
end
|
15
24
|
end
|
data/test/test_helper.rb
CHANGED
@@ -30,7 +30,11 @@ end
|
|
30
30
|
module Test::Unit::Util::BacktraceFilter
|
31
31
|
def filter_backtrace(backtrace, prefix = nil)
|
32
32
|
backtrace = backtrace.select do |e|
|
33
|
-
|
33
|
+
if ENV['FULL_BACKTRACE']
|
34
|
+
true
|
35
|
+
else
|
36
|
+
e.include?("offroad") || !(e.include?("/ruby/") || e.include?("/gems/"))
|
37
|
+
end
|
34
38
|
end
|
35
39
|
|
36
40
|
common_prefix = nil
|
@@ -101,12 +105,24 @@ class VirtualTestDatabase
|
|
101
105
|
end
|
102
106
|
|
103
107
|
def delete_all_rows
|
104
|
-
tables =
|
108
|
+
tables = ActiveRecord::Base.connection.tables
|
109
|
+
if ActiveRecord::Base.connection.adapter_name.downcase.include?("sqlite")
|
110
|
+
tables << "sqlite_sequence"
|
111
|
+
end
|
112
|
+
|
105
113
|
tables.each do |table|
|
106
|
-
next if table.start_with?("
|
114
|
+
next if table.downcase.start_with?("virtual_")
|
107
115
|
next if table == "schema_migrations"
|
108
116
|
ActiveRecord::Base.connection.execute "DELETE FROM #{table}"
|
109
117
|
end
|
118
|
+
|
119
|
+
if ActiveRecord::Base.connection.adapter_name.downcase.include?("postgres")
|
120
|
+
# Reset all sequences so that autoincremented ids start from 1 again
|
121
|
+
seqnames = ActiveRecord::Base.connection.select_values "SELECT c.relname FROM pg_class c WHERE c.relkind = 'S'"
|
122
|
+
seqnames.each do |s|
|
123
|
+
ActiveRecord::Base.connection.execute "SELECT setval('#{s}', 1, false)"
|
124
|
+
end
|
125
|
+
end
|
110
126
|
end
|
111
127
|
|
112
128
|
protected
|
@@ -118,11 +134,11 @@ class VirtualTestDatabase
|
|
118
134
|
private
|
119
135
|
|
120
136
|
def normal_prefix
|
121
|
-
"
|
137
|
+
"virtual_normal_#{@prefix}_"
|
122
138
|
end
|
123
139
|
|
124
140
|
def fresh_prefix
|
125
|
-
"
|
141
|
+
"virtual_fresh_#{@prefix}_"
|
126
142
|
end
|
127
143
|
|
128
144
|
def put_away
|
@@ -147,13 +163,16 @@ class VirtualTestDatabase
|
|
147
163
|
end
|
148
164
|
|
149
165
|
def copy_tables(src_prefix, dst_prefix)
|
150
|
-
tables =
|
166
|
+
tables = ActiveRecord::Base.connection.tables
|
167
|
+
if ActiveRecord::Base.connection.adapter_name.downcase.include?("sqlite")
|
168
|
+
tables << "sqlite_sequence"
|
169
|
+
end
|
151
170
|
tables.each do |src_table|
|
152
171
|
next if src_table.end_with?("schema_migrations")
|
153
172
|
next unless src_table.start_with?(src_prefix)
|
154
173
|
next if dst_prefix != "" && src_table.start_with?(dst_prefix)
|
155
174
|
dst_table = dst_prefix + src_table[(src_prefix.size)..(src_table.size)]
|
156
|
-
next if src_table.start_with?("
|
175
|
+
next if src_table.downcase.start_with?("virtual_") && dst_table.downcase.start_with?("virtual_")
|
157
176
|
if tables.include?(dst_table)
|
158
177
|
ActiveRecord::Base.connection.execute "DELETE FROM #{dst_table}"
|
159
178
|
ActiveRecord::Base.connection.execute "INSERT INTO #{dst_table} SELECT * FROM #{src_table}"
|
@@ -161,6 +180,24 @@ class VirtualTestDatabase
|
|
161
180
|
ActiveRecord::Base.connection.execute "CREATE TABLE #{dst_table} AS SELECT * FROM #{src_table}"
|
162
181
|
end
|
163
182
|
end
|
183
|
+
|
184
|
+
if ActiveRecord::Base.connection.adapter_name.downcase.include?("postgres")
|
185
|
+
# Manage postgresql sequences by keeping non-current sequence vals backed up in Ruby
|
186
|
+
@pg_seq_vals ||= {}
|
187
|
+
if src_prefix.blank?
|
188
|
+
# PG Sequences -> Ruby
|
189
|
+
@pg_seq_vals[dst_prefix] ||= {}
|
190
|
+
seqnames = ActiveRecord::Base.connection.select_values "SELECT c.relname FROM pg_class c WHERE c.relkind = 'S'"
|
191
|
+
seqnames.each do |s|
|
192
|
+
@pg_seq_vals[dst_prefix][s] = ActiveRecord::Base.connection.select_value "SELECT last_value FROM \"#{s}\""
|
193
|
+
end
|
194
|
+
else
|
195
|
+
# Ruby -> PG Sequences
|
196
|
+
@pg_seq_vals[src_prefix].each do |s, v|
|
197
|
+
ActiveRecord::Base.connection.execute "SELECT setval('#{s}', #{v}, true)"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
164
201
|
end
|
165
202
|
|
166
203
|
def backup_instance_vars(key)
|
@@ -280,11 +317,22 @@ end
|
|
280
317
|
class Test::Unit::TestCase
|
281
318
|
@@online_database = nil
|
282
319
|
@@offline_database = nil
|
320
|
+
@@initial_setup = false
|
283
321
|
|
284
322
|
include Test::Unit::Util::BacktraceFilter
|
285
323
|
|
286
324
|
def setup
|
287
325
|
begin
|
326
|
+
unless @@initial_setup
|
327
|
+
if ActiveRecord::Base.connection.adapter_name.downcase.include?("postgresql")
|
328
|
+
puts "Dropping all postgresql tables"
|
329
|
+
tables = ActiveRecord::Base.connection.tables.each do |table|
|
330
|
+
ActiveRecord::Base.connection.execute "DROP TABLE #{table}"
|
331
|
+
end
|
332
|
+
end
|
333
|
+
@@initial_setup = true
|
334
|
+
end
|
335
|
+
|
288
336
|
unless ActiveRecord::Base.connection.table_exists?("schema_migrations")
|
289
337
|
# FIXME : Figure out why ActionController::TestCase keeps on deleting all the tables before each method
|
290
338
|
|
@@ -49,11 +49,23 @@ class MirrorDataTest < Test::Unit::TestCase
|
|
49
49
|
assert_equal @offline_group.id, group_state.app_group_id
|
50
50
|
end
|
51
51
|
|
52
|
+
def strip_msecs_in_values(hash)
|
53
|
+
new_hash = {}
|
54
|
+
hash.each do |k,v|
|
55
|
+
if v.is_a?(DateTime) || v.is_a?(Time)
|
56
|
+
new_hash[k] = v.to_i
|
57
|
+
else
|
58
|
+
new_hash[k] = v
|
59
|
+
end
|
60
|
+
end
|
61
|
+
new_hash
|
62
|
+
end
|
63
|
+
|
52
64
|
def assert_single_model_cargo_entry_matches(cs, record)
|
53
65
|
record.reload
|
54
66
|
data_name = Offroad::MirrorData.send(:data_cargo_name_for_model, record.class)
|
55
67
|
assert_single_cargo_section_named cs, data_name
|
56
|
-
assert_equal record.attributes, cs.first_cargo_element(data_name).attributes
|
68
|
+
assert_equal strip_msecs_in_values(record.attributes), strip_msecs_in_values(cs.first_cargo_element(data_name).attributes)
|
57
69
|
end
|
58
70
|
|
59
71
|
def assert_record_not_present(cs, record)
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: offroad
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- David Mike Simon
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-04-
|
18
|
+
date: 2011-04-15 00:00:00 -05:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -24,12 +24,14 @@ dependencies:
|
|
24
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
26
26
|
requirements:
|
27
|
-
- - "
|
27
|
+
- - "="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
hash:
|
29
|
+
hash: 51
|
30
30
|
segments:
|
31
31
|
- 0
|
32
|
-
|
32
|
+
- 9
|
33
|
+
- 4
|
34
|
+
version: 0.9.4
|
33
35
|
type: :runtime
|
34
36
|
version_requirements: *id001
|
35
37
|
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.
|
@@ -79,6 +81,7 @@ files:
|
|
79
81
|
- test/app_root/app/views/group/upload_up_mirror.html.erb
|
80
82
|
- test/app_root/app/views/layouts/mirror.html.erb
|
81
83
|
- test/app_root/config/boot.rb
|
84
|
+
- test/app_root/config/database-pg.yml
|
82
85
|
- test/app_root/config/database.yml
|
83
86
|
- test/app_root/config/environment.rb
|
84
87
|
- test/app_root/config/environments/test.rb
|