offroad 0.0.1 → 0.0.2

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/Rakefile CHANGED
@@ -68,7 +68,7 @@ gemspec = Gem::Specification.new do |s|
68
68
  s.require_path = 'lib'
69
69
  s.rubyforge_project = '[none]'
70
70
 
71
- s.add_dependency('ar-extensions')
71
+ s.add_dependency('ar-extensions', '0.9.4')
72
72
  end
73
73
 
74
74
  Rake::GemPackageTask.new(gemspec) do |pkg|
@@ -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 by creating temporary records to advance the autoincrement counter
85
- # TODO I'm pretty sure this is safe because it'll always be used in a transaction, but I should check
86
- model.import([model.primary_key.to_sym], [[nil]]*remaining.size, :validate => false, :timestamps => false)
87
- last_id = model.last(:select => model.primary_key, :order => model.primary_key).id
88
- local_ids = (last_id+1-remaining.size)..last_id
89
- model.delete(local_ids)
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.first+i
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
- # Emptying sqlite_sequence resets SQLite's autoincrement counters.
93
- # SQLite's autoincrement is nice in that automatically picks largest ever id + 1.
94
- # This means that after clearing sqlite_sequence and then populating database with manually-id'd rows,
95
- # new records will be inserted with unique id's, no problem.
96
- tables = ["sqlite_sequence"] + ActiveRecord::Base.connection.tables
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?("VIRTUAL_") # Used in testing
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)
@@ -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 `#{Offroad::GroupState.table_name}` ON `#{Offroad::GroupState.table_name}`.app_group_id = `#{table_name}`.`#{primary_key}`"
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 `#{Offroad::GroupState.table_name}` ON `#{Offroad::GroupState.table_name}`.app_group_id = `#{table_name}`.`#{primary_key}`",
42
+ "LEFT JOIN \"#{Offroad::GroupState.table_name}\" ON \"#{Offroad::GroupState.table_name}\".app_group_id = \"#{table_name}\".\"#{primary_key}\"",
43
43
  :conditions =>
44
- "`#{Offroad::GroupState.table_name}`.app_group_id IS NULL"
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 << "`#{assoc_owner.table_name}`.`#{assoc.primary_key_name}` = #{group.id}"
120
+ conditions << "\"#{assoc_owner.table_name}\".\"#{assoc.primary_key_name}\" = #{group.id}"
121
121
  break
122
122
  else
123
- conditions << "`#{assoc_owner.table_name}`.`#{assoc.primary_key_name}` = `#{assoc.klass.table_name}`.`#{assoc.klass.primary_key}`"
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,3 +1,3 @@
1
1
  module Offroad
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -0,0 +1,8 @@
1
+ test:
2
+ adapter: postgresql
3
+ encoding: unicode
4
+ database: offroad_test
5
+ pool: 5
6
+ username: david
7
+ password: foobar
8
+ min_messages: WARNING
@@ -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
- e.include?("offroad") || !(e.include?("/ruby/") || e.include?("/gems/"))
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 = ["sqlite_sequence"] + ActiveRecord::Base.connection.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?("VIRTUAL_")
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
- "VIRTUAL_normal_#{@prefix}_"
137
+ "virtual_normal_#{@prefix}_"
122
138
  end
123
139
 
124
140
  def fresh_prefix
125
- "VIRTUAL_fresh_#{@prefix}_"
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 = ["sqlite_sequence"] + ActiveRecord::Base.connection.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?("VIRTUAL") && dst_table.start_with?("VIRTUAL")
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: 29
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 1
10
- version: 0.0.1
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-11 00:00:00 -05:00
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: 3
29
+ hash: 51
30
30
  segments:
31
31
  - 0
32
- version: "0"
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