dump 1.0.0

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 (89) hide show
  1. data/.autotest +13 -0
  2. data/.gitignore +12 -0
  3. data/LICENSE.txt +20 -0
  4. data/README.markdown +250 -0
  5. data/dump.gemspec +22 -0
  6. data/lib/dump.rb +3 -0
  7. data/lib/dump/capistrano.rb +1 -0
  8. data/lib/dump/railtie.rb +8 -0
  9. data/lib/dump_rake.rb +85 -0
  10. data/lib/dump_rake/archive_tar_minitar_fix.rb +8 -0
  11. data/lib/dump_rake/assets.rb +22 -0
  12. data/lib/dump_rake/continious_timeout.rb +38 -0
  13. data/lib/dump_rake/dump.rb +175 -0
  14. data/lib/dump_rake/dump_reader.rb +289 -0
  15. data/lib/dump_rake/dump_writer.rb +119 -0
  16. data/lib/dump_rake/env.rb +139 -0
  17. data/lib/dump_rake/env/filter.rb +26 -0
  18. data/lib/dump_rake/rails_root.rb +12 -0
  19. data/lib/dump_rake/table_manipulation.rb +131 -0
  20. data/lib/generators/assets_config/assets_config_generator.rb +16 -0
  21. data/lib/generators/assets_config/templates/assets +8 -0
  22. data/lib/tasks/assets.rake +17 -0
  23. data/lib/tasks/dump.rake +27 -0
  24. data/recipes/dump.rb +343 -0
  25. data/script/update_readme +21 -0
  26. data/spec/.gitignore +1 -0
  27. data/spec/.tmignore +1 -0
  28. data/spec/cycle_spec.rb +229 -0
  29. data/spec/db/database.example.yml +19 -0
  30. data/spec/db/schema.rb +7 -0
  31. data/spec/dummy-3.1.3/.gitignore +15 -0
  32. data/spec/dummy-3.1.3/.rspec +1 -0
  33. data/spec/dummy-3.1.3/Gemfile +23 -0
  34. data/spec/dummy-3.1.3/Gemfile.lock +159 -0
  35. data/spec/dummy-3.1.3/README +261 -0
  36. data/spec/dummy-3.1.3/Rakefile +7 -0
  37. data/spec/dummy-3.1.3/app/assets/images/rails.png +0 -0
  38. data/spec/dummy-3.1.3/app/assets/javascripts/application.js +9 -0
  39. data/spec/dummy-3.1.3/app/assets/stylesheets/application.css +7 -0
  40. data/spec/dummy-3.1.3/app/controllers/application_controller.rb +3 -0
  41. data/spec/dummy-3.1.3/app/helpers/application_helper.rb +2 -0
  42. data/spec/dummy-3.1.3/app/mailers/.gitkeep +0 -0
  43. data/spec/dummy-3.1.3/app/models/.gitkeep +0 -0
  44. data/spec/dummy-3.1.3/app/views/layouts/application.html.erb +14 -0
  45. data/spec/dummy-3.1.3/config.ru +4 -0
  46. data/spec/dummy-3.1.3/config/application.rb +54 -0
  47. data/spec/dummy-3.1.3/config/boot.rb +6 -0
  48. data/spec/dummy-3.1.3/config/database.yml +25 -0
  49. data/spec/dummy-3.1.3/config/environment.rb +5 -0
  50. data/spec/dummy-3.1.3/config/environments/development.rb +30 -0
  51. data/spec/dummy-3.1.3/config/environments/production.rb +60 -0
  52. data/spec/dummy-3.1.3/config/environments/test.rb +39 -0
  53. data/spec/dummy-3.1.3/config/initializers/backtrace_silencers.rb +7 -0
  54. data/spec/dummy-3.1.3/config/initializers/inflections.rb +10 -0
  55. data/spec/dummy-3.1.3/config/initializers/mime_types.rb +5 -0
  56. data/spec/dummy-3.1.3/config/initializers/secret_token.rb +7 -0
  57. data/spec/dummy-3.1.3/config/initializers/session_store.rb +8 -0
  58. data/spec/dummy-3.1.3/config/initializers/wrap_parameters.rb +14 -0
  59. data/spec/dummy-3.1.3/config/locales/en.yml +5 -0
  60. data/spec/dummy-3.1.3/config/routes.rb +58 -0
  61. data/spec/dummy-3.1.3/db/seeds.rb +7 -0
  62. data/spec/dummy-3.1.3/doc/README_FOR_APP +2 -0
  63. data/spec/dummy-3.1.3/lib/assets/.gitkeep +0 -0
  64. data/spec/dummy-3.1.3/lib/tasks/.gitkeep +0 -0
  65. data/spec/dummy-3.1.3/log/.gitkeep +0 -0
  66. data/spec/dummy-3.1.3/public/404.html +26 -0
  67. data/spec/dummy-3.1.3/public/422.html +26 -0
  68. data/spec/dummy-3.1.3/public/500.html +26 -0
  69. data/spec/dummy-3.1.3/public/favicon.ico +0 -0
  70. data/spec/dummy-3.1.3/public/index.html +241 -0
  71. data/spec/dummy-3.1.3/public/robots.txt +5 -0
  72. data/spec/dummy-3.1.3/script/rails +6 -0
  73. data/spec/dummy-3.1.3/spec/spec_helper.rb +32 -0
  74. data/spec/dummy-3.1.3/vendor/assets/stylesheets/.gitkeep +0 -0
  75. data/spec/dummy-3.1.3/vendor/plugins/.gitkeep +0 -0
  76. data/spec/lib/dump_rake/dump_reader_spec.rb +638 -0
  77. data/spec/lib/dump_rake/dump_spec.rb +291 -0
  78. data/spec/lib/dump_rake/dump_writer_spec.rb +328 -0
  79. data/spec/lib/dump_rake/env/filter_spec.rb +56 -0
  80. data/spec/lib/dump_rake/env_spec.rb +139 -0
  81. data/spec/lib/dump_rake/rails_root_spec.rb +45 -0
  82. data/spec/lib/dump_rake/table_manipulation_spec.rb +256 -0
  83. data/spec/lib/dump_rake_spec.rb +326 -0
  84. data/spec/recipes/dump_spec.rb +553 -0
  85. data/spec/spec.opts +4 -0
  86. data/spec/spec_helper.rb +34 -0
  87. data/spec/tasks/assets_spec.rb +92 -0
  88. data/spec/tasks/dump_spec.rb +107 -0
  89. metadata +272 -0
@@ -0,0 +1,119 @@
1
+ class DumpRake
2
+ class DumpWriter < Dump
3
+ attr_reader :stream, :config
4
+
5
+ def self.create(path)
6
+ new(path).open do |dump|
7
+ ActiveRecord::Base.logger.silence do
8
+ dump.write_schema
9
+
10
+ dump.write_tables
11
+ dump.write_assets
12
+
13
+ dump.write_config
14
+ end
15
+ end
16
+ end
17
+
18
+ def open
19
+ Pathname.new(path).dirname.mkpath
20
+ Zlib::GzipWriter.open(path) do |gzip|
21
+ gzip.mtime = Time.utc(2000)
22
+ lock do
23
+ Archive::Tar::Minitar.open(gzip, 'w') do |stream|
24
+ @stream = stream
25
+ @config = {:tables => {}}
26
+ yield(self)
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ def create_file(name)
33
+ Tempfile.open('dump') do |temp|
34
+ yield(temp)
35
+ temp.open
36
+ stream.tar.add_file_simple(name, :mode => 0100444, :size => temp.length) do |f|
37
+ f.write(temp.read(4096)) until temp.eof?
38
+ end
39
+ end
40
+ end
41
+
42
+ def write_schema
43
+ create_file('schema.rb') do |f|
44
+ DumpRake::Env.with_env('SCHEMA' => f.path) do
45
+ Rake::Task['db:schema:dump'].invoke
46
+ end
47
+ end
48
+ end
49
+
50
+ def write_tables
51
+ verify_connection
52
+ tables_to_dump.with_progress('Tables') do |table|
53
+ write_table(table)
54
+ end
55
+ end
56
+
57
+ def write_table(table)
58
+ row_count = table_row_count(table)
59
+ config[:tables][table] = row_count
60
+ Progress.start(table, 1 + row_count) do
61
+ create_file("#{table}.dump") do |f|
62
+ columns = table_columns(table)
63
+ column_names = columns.map(&:name).sort
64
+ columns_by_name = columns.index_by(&:name)
65
+
66
+ Marshal.dump(column_names, f)
67
+ Progress.step
68
+
69
+ each_table_row(table, row_count) do |row|
70
+ values = column_names.map do |column|
71
+ columns_by_name[column].type_cast(row[column])
72
+ end
73
+ Marshal.dump(values, f)
74
+ Progress.step
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ def write_assets
81
+ assets = assets_to_dump
82
+ if assets.present?
83
+ config[:assets] = {}
84
+ Dir.chdir(DumpRake::RailsRoot) do
85
+ assets = Dir[*assets].uniq
86
+ assets.with_progress('Assets') do |asset|
87
+ paths = Dir[File.join(asset, '**/*')]
88
+ files = paths.select{ |path| File.file?(path) }
89
+ config[:assets][asset] = {:total => paths.length, :files => files.length}
90
+ assets_root_link do |tmpdir, prefix|
91
+ paths.with_progress(asset) do |entry|
92
+ begin
93
+ Archive::Tar::Minitar.pack_file(File.join(prefix, entry), stream)
94
+ rescue => e
95
+ $stderr.puts "Skipped asset due to error #{e}"
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ def write_config
105
+ create_file('config') do |f|
106
+ Marshal.dump(config, f)
107
+ end
108
+ end
109
+
110
+ def assets_to_dump
111
+ begin
112
+ Rake::Task['assets'].invoke
113
+ DumpRake::Env[:assets].split(DumpRake::Assets::SPLITTER)
114
+ rescue
115
+ []
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,139 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'dump_rake/env/filter'
4
+
5
+ class DumpRake
6
+ module Env
7
+ DICTIONARY = {
8
+ :desc => %w[DESC DESCRIPTION],
9
+ :like => %w[LIKE VER VERSION],
10
+ :tags => %w[TAGS TAG],
11
+ :leave => %w[LEAVE],
12
+ :summary => %w[SUMMARY],
13
+ :assets => %w[ASSETS],
14
+ :tables => %w[TABLES],
15
+ :backup => %w[BACKUP AUTOBACKUP AUTO_BACKUP],
16
+ :transfer_via => %w[TRANSFER_VIA],
17
+ :migrate_down => %w[MIGRATE_DOWN],
18
+ :restore_schema => %w[RESTORE_SCHEMA],
19
+ :restore_tables => %w[RESTORE_TABLES],
20
+ :restore_assets => %w[RESTORE_ASSETS],
21
+ :show_size => %w[SHOW_SIZE], # internal
22
+ }.freeze unless defined? DICTIONARY
23
+
24
+ EXPLANATIONS = {
25
+ :desc => 'free form description of dump',
26
+ :like => 'filter dumps by full dump name',
27
+ :tags => 'comma separated list of tags',
28
+ :leave => 'number of dumps to leave',
29
+ :summary => 'output info about dump: "1", "true" or "yes" for basic info, "2" or "schema" to display schema as well',
30
+ :assets => 'comma or colon separated list of paths or globs to dump',
31
+ :tables => 'comma separated list of tables to dump or if prefixed by "-" — to skip; by default only sessions table is skipped; schema_info and schema_migrations are always included if they are present',
32
+ :backup => 'no autobackup if you pass "0", "no" or "false"',
33
+ :transfer_via => 'transfer method (rsync, sftp or scp)',
34
+ :migrate_down => 'don\'t run down for migrations not present in dump if you pass "0", "no" or "false"; pass "reset" to recreate (drop and create) db',
35
+ :restore_schema => 'don\'t read/change schema if you pass "0", "no" or "false" (useful to just restore data for table; note that schema info tables are also not restored)',
36
+ :restore_tables => 'works as TABLES, but for restoring',
37
+ :restore_assets => 'works as ASSETS, but for restoring',
38
+ }.freeze unless defined? EXPLANATIONS
39
+
40
+ class << self
41
+ def with_env(hash)
42
+ old = {}
43
+ hash.each do |key, value|
44
+ key = DICTIONARY[key].first if DICTIONARY[key]
45
+ old[key] = ENV[key]
46
+ ENV[key] = value
47
+ end
48
+ begin
49
+ yield
50
+ ensure
51
+ old.each do |key, value|
52
+ ENV[key] = value
53
+ end
54
+ end
55
+ end
56
+
57
+ def with_clean_env(hash = {}, &block)
58
+ empty_env = {}
59
+ DICTIONARY.keys.each{ |key| empty_env[key] = nil }
60
+ with_env(empty_env.merge(hash), &block)
61
+ end
62
+
63
+ def [](key)
64
+ if DICTIONARY[key]
65
+ ENV.values_at(*DICTIONARY[key]).compact.first
66
+ else
67
+ ENV[key]
68
+ end
69
+ end
70
+
71
+ def filter(key, splitter = nil)
72
+ @filters ||= Hash.new{ |hash, key| hash[key] = Filter.new(*key) }
73
+ @filters[[self[key], splitter]]
74
+ end
75
+
76
+ def yes?(key)
77
+ %w[1 y t].include?(first_char(key))
78
+ end
79
+
80
+ def no?(key)
81
+ %w[0 n f].include?(first_char(key))
82
+ end
83
+
84
+ def downcase(key)
85
+ self[key].to_s.downcase.strip
86
+ end
87
+
88
+ def variable_names_for_command(command)
89
+ m = {
90
+ :select => [:like, :tags],
91
+ :assets => [:assets],
92
+ :restore_options => [:migrate_down, :restore_schema, :restore_tables, :restore_assets],
93
+ :transfer_options => [:transfer_via]
94
+ }
95
+
96
+ m[:versions] = m[:select] | [:summary]
97
+ m[:create] = [:desc, :tags, :tables] | m[:assets]
98
+ m[:restore] = m[:select] | m[:restore_options]
99
+ m[:cleanup] = m[:select] | [:leave]
100
+
101
+ m[:transfer] = m[:select] | m[:transfer_options]
102
+
103
+ m[:mirror] = [:backup] | m[:create] | m[:transfer_options] | m[:restore_options]
104
+ m[:backup] = m[:create] | [:transfer_via]
105
+ m[:backup_restore] = m[:transfer] | m[:restore_options]
106
+
107
+ m[command] || []
108
+ end
109
+
110
+ def for_command(command, strings = false)
111
+ variables = variable_names_for_command(command)
112
+ variables.inject({}) do |env, variable|
113
+ value = self[variable]
114
+ env[strings ? DICTIONARY[variable].first : variable] = value if value
115
+ env
116
+ end
117
+ end
118
+
119
+ def stringify!(hash)
120
+ hash.keys.each do |key|
121
+ hash[DICTIONARY[key] ? DICTIONARY[key].first : key.to_s] = hash.delete(key)
122
+ end
123
+ end
124
+
125
+ def explain_variables_for_command(command)
126
+ ".\n" <<
127
+ variable_names_for_command(command).map do |variable_name|
128
+ " #{DICTIONARY[variable_name].join(', ')} — #{EXPLANATIONS[variable_name]}\n"
129
+ end.join('')
130
+ end
131
+
132
+ private
133
+
134
+ def first_char(key)
135
+ downcase(key)[0, 1]
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: UTF-8
2
+
3
+ class DumpRake
4
+ module Env
5
+ class Filter
6
+ attr_reader :invert, :values, :transparent
7
+ def initialize(s, splitter = nil)
8
+ if s
9
+ s = s.dup
10
+ @invert = !!s.sub!(/^-/, '')
11
+ @values = s.split(splitter || ',').map(&:strip).map(&:downcase).uniq.select(&:present?)
12
+ else
13
+ @transparent = true
14
+ end
15
+ end
16
+
17
+ def pass?(value)
18
+ transparent || (invert ^ values.include?(value.to_s.downcase))
19
+ end
20
+
21
+ def custom_pass?(&block)
22
+ transparent || (invert ^ values.any?(&block))
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: UTF-8
2
+
3
+ class DumpRake
4
+ RailsRoot = case
5
+ when defined?(Rails)
6
+ Rails.root
7
+ when defined?(RAILS_ROOT)
8
+ RAILS_ROOT
9
+ else
10
+ Dir.pwd
11
+ end.to_s
12
+ end
@@ -0,0 +1,131 @@
1
+ class DumpRake
2
+ module TableManipulation
3
+ protected
4
+
5
+ def schema_tables
6
+ %w[schema_info schema_migrations]
7
+ end
8
+
9
+ def verify_connection
10
+ ActiveRecord::Base.connection.verify!(0)
11
+ end
12
+
13
+
14
+ def quote_table_name(table)
15
+ ActiveRecord::Base.connection.quote_table_name(table)
16
+ end
17
+
18
+ def quote_column_name(column)
19
+ ActiveRecord::Base.connection.quote_column_name(column)
20
+ end
21
+
22
+ def quote_value(value)
23
+ ActiveRecord::Base.connection.quote(value)
24
+ end
25
+
26
+
27
+ def clear_table(table_sql)
28
+ ActiveRecord::Base.connection.delete("DELETE FROM #{table_sql}", 'Clearing table')
29
+ end
30
+
31
+ def insert_into_table(table_sql, columns_sql, values_sql)
32
+ values_sql = values_sql.join(',') if values_sql.is_a?(Array)
33
+ ActiveRecord::Base.connection.insert("INSERT INTO #{table_sql} #{columns_sql} VALUES #{values_sql}", 'Loading dump')
34
+ end
35
+
36
+ def fix_sequence!(table)
37
+ if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
38
+ ActiveRecord::Base.connection.reset_pk_sequence!(table)
39
+ end
40
+ end
41
+
42
+ def join_for_sql(quoted)
43
+ "(#{quoted.join(',')})"
44
+ end
45
+
46
+ def columns_insert_sql(columns)
47
+ join_for_sql(columns.map{ |column| quote_column_name(column) })
48
+ end
49
+
50
+ def values_insert_sql(values)
51
+ join_for_sql(values.map{ |value| quote_value(value) })
52
+ end
53
+
54
+
55
+ def avaliable_tables
56
+ ActiveRecord::Base.connection.tables
57
+ end
58
+
59
+ def tables_to_dump
60
+ if DumpRake::Env[:tables]
61
+ avaliable_tables.select do |table|
62
+ schema_tables.include?(table) || DumpRake::Env.filter(:tables).pass?(table)
63
+ end
64
+ else
65
+ avaliable_tables - %w[sessions]
66
+ end
67
+ end
68
+
69
+ def table_row_count(table)
70
+ ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM #{quote_table_name(table)}").to_i
71
+ end
72
+
73
+ CHUNK_SIZE_MIN = 100 unless const_defined?(:CHUNK_SIZE_MIN)
74
+ CHUNK_SIZE_MAX = 3_000 unless const_defined?(:CHUNK_SIZE_MAX)
75
+ def table_chunk_size(table)
76
+ expected_row_size = table_columns(table).map do |column|
77
+ case column.type
78
+ when :text
79
+ Math.sqrt(column.limit || 2_147_483_647)
80
+ when :string
81
+ Math.sqrt(column.limit || 255)
82
+ else
83
+ column.limit || 10
84
+ end
85
+ end.sum
86
+ [[(10_000_000 / expected_row_size).round, CHUNK_SIZE_MIN].max, CHUNK_SIZE_MAX].min
87
+ end
88
+
89
+ def table_columns(table)
90
+ ActiveRecord::Base.connection.columns(table)
91
+ end
92
+
93
+ def table_has_primary_column?(table)
94
+ # bad test for primary column, but primary even for primary column is nil
95
+ table_columns(table).any?{ |column| column.name == table_primary_key(table) && column.type == :integer }
96
+ end
97
+
98
+ def table_primary_key(table)
99
+ 'id'
100
+ end
101
+
102
+ def each_table_row(table, row_count, &block)
103
+ if table_has_primary_column?(table) && row_count > (chunk_size = table_chunk_size(table))
104
+ # adapted from ActiveRecord::Batches
105
+ primary_key = table_primary_key(table)
106
+ quoted_primary_key = "#{quote_table_name(table)}.#{quote_column_name(primary_key)}"
107
+ select_where_primary_key =
108
+ "SELECT * FROM #{quote_table_name(table)}" +
109
+ " WHERE #{quoted_primary_key} %s" +
110
+ " ORDER BY #{quoted_primary_key} ASC" +
111
+ " LIMIT #{chunk_size}"
112
+ rows = select_all_by_sql(select_where_primary_key % '>= 0')
113
+ until rows.blank?
114
+ rows.each(&block)
115
+ break if rows.length < chunk_size
116
+ rows = select_all_by_sql(select_where_primary_key % "> #{rows.last[primary_key].to_i}")
117
+ end
118
+ else
119
+ table_rows(table).each(&block)
120
+ end
121
+ end
122
+
123
+ def table_rows(table)
124
+ select_all_by_sql("SELECT * FROM #{quote_table_name(table)}")
125
+ end
126
+
127
+ def select_all_by_sql(sql)
128
+ ActiveRecord::Base.connection.select_all(sql)
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,16 @@
1
+ case
2
+ when defined?(Rails::Generator::Base)
3
+ class AssetsConfigGenerator < Rails::Generator::Base
4
+ def manifest
5
+ record do |m|
6
+ m.file 'assets', 'config/assets'
7
+ end
8
+ end
9
+ end
10
+ when defined?(Rails::Generators::Base)
11
+ class AssetsConfigGenerator < Rails::Generators::Base
12
+ def create_assets_config
13
+ create_file 'config/assets', File.read(File.join(File.dirname(__FILE__), 'templates/assets'))
14
+ end
15
+ end
16
+ end