yaml_db 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,35 @@
1
+ = YamlDb
2
+
3
+ YamlDb is a database-independent format for dumping and restoring data. It complements the the database-independent schema format found in db/schema.rb. The data is saved into db/data.yml.
4
+
5
+ This can be used as a replacement for mysqldump or pg_dump, but only for the databases typically used by Rails apps. Users, permissions, schemas, triggers, and other advanced database features are not supported - by design.
6
+
7
+ Any database that has an ActiveRecord adapter should work.
8
+
9
+ == Usage
10
+
11
+ rake db:data:dump -> Dump contents of Rails database to db/data.yml
12
+ rake db:data:load -> Load contents of db/data.yml into the database
13
+
14
+ Further, there are tasks db:dump and db:load which do the entire database (the equivalent of running db:schema:dump followed by db:data:load).
15
+
16
+ == Examples
17
+
18
+ One common use would be to switch your data from one database backend to another. For example, let's say you wanted to switch from SQLite to MySQL. You might execute the following steps:
19
+
20
+ 1. rake db:dump
21
+
22
+ 2. Edit config/database.yml and change your adapter to mysql, set up database params
23
+
24
+ 3. mysqladmin create [database name]
25
+
26
+ 4. rake db:load
27
+
28
+ == Credits
29
+
30
+ Created by Orion Henry and Adam Wiggins. Major updates by Ricardo Chimal, Jr.
31
+
32
+ Patches contributed by Michael Irwin, Tom Locke, and Tim Galeckas.
33
+
34
+ Send questions, feedback, or patches to the Heroku mailing list: http://groups.google.com/group/heroku
35
+
@@ -0,0 +1,29 @@
1
+ require 'rake'
2
+ require 'spec/rake/spectask'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "yaml_db"
8
+ gem.summary = %Q{yaml_db allows export/import of database into/from yaml files}
9
+ gem.description = %Q{
10
+ YamlDb is a database-independent format for dumping and restoring data. It complements the the database-independent schema format found in db/schema.rb. The data is saved into db/data.yml.
11
+ This can be used as a replacement for mysqldump or pg_dump, but only for the databases typically used by Rails apps. Users, permissions, schemas, triggers, and other advanced database features are not supported - by design.
12
+ Any database that has an ActiveRecord adapter should work
13
+ }
14
+ gem.email = "nate@ludicast.com"
15
+ gem.homepage = "http://github.com/ludicast/yaml_db"
16
+ gem.authors = ["Adam Wiggins","Orion Henry"]
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ desc "Run all specs"
24
+ Spec::Rake::SpecTask.new('spec') do |t|
25
+ t.spec_files = FileList['spec/*_spec.rb']
26
+ end
27
+
28
+ task :default => :spec
29
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,5 @@
1
+ author: Orion Henry and Adam Wiggins of Heroku
2
+ summary: Dumps and loads a database-independent data dump format in db/data.yml.
3
+ homepage: http://opensource.heroku.com/
4
+ license: MIT
5
+ rails_version: 1.2+
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'yaml_db'
@@ -0,0 +1,78 @@
1
+ #require 'FasterCSV'
2
+ module CsvDb
3
+ module Helper
4
+ def self.loader
5
+ Load
6
+ end
7
+
8
+ def self.dumper
9
+ Dump
10
+ end
11
+
12
+ def self.extension
13
+ "csv"
14
+ end
15
+ end
16
+
17
+ class Load < SerializationHelper::Load
18
+ def self.load_documents(io, truncate = true)
19
+ tables = {}
20
+ curr_table = nil
21
+ io.each do |line|
22
+ if /BEGIN_CSV_TABLE_DECLARATION(.+)END_CSV_TABLE_DECLARATION/ =~ line
23
+ curr_table = $1
24
+ tables[curr_table] = {}
25
+ else
26
+ if tables[curr_table]["columns"]
27
+ tables[curr_table]["records"] << FasterCSV.parse(line)[0]
28
+ else
29
+ tables[curr_table]["columns"] = FasterCSV.parse(line)[0]
30
+ tables[curr_table]["records"] = []
31
+ end
32
+ end
33
+ end
34
+
35
+ tables.each_pair do |table_name, contents|
36
+ load_table(table_name, contents, truncate)
37
+ end
38
+ end
39
+ end
40
+
41
+ class Dump < SerializationHelper::Dump
42
+
43
+ def self.before_table(io,table)
44
+ io.write "BEGIN_CSV_TABLE_DECLARATION#{table}END_CSV_TABLE_DECLARATION\n"
45
+ end
46
+
47
+ def self.dump(io)
48
+ tables.each do |table|
49
+ before_table(io, table)
50
+ dump_table(io, table)
51
+ after_table(io, table)
52
+ end
53
+ end
54
+
55
+ def self.after_table(io,table)
56
+ io.write ""
57
+ end
58
+
59
+ def self.dump_table_columns(io, table)
60
+ io.write(table_column_names(table).to_csv)
61
+ end
62
+
63
+ def self.dump_table_records(io, table)
64
+
65
+ column_names = table_column_names(table)
66
+
67
+ each_table_page(table) do |records|
68
+ rows = SerializationHelper::Utils.unhash_records(records, column_names)
69
+ records.each do |record|
70
+ io.write(record.to_csv)
71
+ end
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+
78
+ end
@@ -0,0 +1,193 @@
1
+ module SerializationHelper
2
+
3
+ class Base
4
+ attr_reader :extension
5
+
6
+ def initialize(helper)
7
+ @dumper = helper.dumper
8
+ @loader = helper.loader
9
+ @extension = helper.extension
10
+ end
11
+
12
+ def dump(filename)
13
+ disable_logger
14
+ @dumper.dump(File.new(filename, "w"))
15
+ reenable_logger
16
+ end
17
+
18
+ def dump_to_dir(dirname)
19
+ Dir.mkdir(dirname)
20
+ tables = @dumper.tables
21
+ tables.each do |table|
22
+ io = File.new "#{dirname}/#{table}.#{@extension}", "w"
23
+ @dumper.before_table(io, table)
24
+ @dumper.dump_table io, table
25
+ @dumper.after_table(io, table)
26
+ end
27
+ end
28
+
29
+ def load(filename, truncate = true)
30
+ disable_logger
31
+ @loader.load(File.new(filename, "r"), truncate)
32
+ reenable_logger
33
+ end
34
+
35
+ def load_from_dir(dirname, truncate = true)
36
+ Dir.entries(dirname).each do |filename|
37
+ if filename =~ /^[.]/
38
+ next
39
+ end
40
+ @loader.load(File.new("#{dirname}/#{filename}", "r"), truncate)
41
+ end
42
+ end
43
+
44
+ def disable_logger
45
+ @@old_logger = ActiveRecord::Base.logger
46
+ ActiveRecord::Base.logger = nil
47
+ end
48
+
49
+ def reenable_logger
50
+ ActiveRecord::Base.logger = @@old_logger
51
+ end
52
+ end
53
+
54
+ class Load
55
+ def self.load(io, truncate = true)
56
+ ActiveRecord::Base.connection.transaction do
57
+ load_documents(io, truncate)
58
+ end
59
+ end
60
+
61
+ def self.truncate_table(table)
62
+ begin
63
+ ActiveRecord::Base.connection.execute("TRUNCATE #{SerializationHelper::Utils.quote_table(table)}")
64
+ rescue Exception
65
+ ActiveRecord::Base.connection.execute("DELETE FROM #{SerializationHelper::Utils.quote_table(table)}")
66
+ end
67
+ end
68
+
69
+ def self.load_table(table, data, truncate = true)
70
+ column_names = data['columns']
71
+ if truncate
72
+ truncate_table(table)
73
+ end
74
+ load_records(table, column_names, data['records'])
75
+ reset_pk_sequence!(table)
76
+ end
77
+
78
+ def self.load_records(table, column_names, records)
79
+ if column_names.nil?
80
+ return
81
+ end
82
+ quoted_column_names = column_names.map { |column| ActiveRecord::Base.connection.quote_column_name(column) }.join(',')
83
+ quoted_table_name = SerializationHelper::Utils.quote_table(table)
84
+ records.each do |record|
85
+ ActiveRecord::Base.connection.execute("INSERT INTO #{quoted_table_name} (#{quoted_column_names}) VALUES (#{record.map { |r| ActiveRecord::Base.connection.quote(r) }.join(',')})")
86
+ end
87
+ end
88
+
89
+ def self.reset_pk_sequence!(table_name)
90
+ if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
91
+ ActiveRecord::Base.connection.reset_pk_sequence!(table_name)
92
+ end
93
+ end
94
+
95
+
96
+ end
97
+
98
+ module Utils
99
+
100
+ def self.unhash(hash, keys)
101
+ keys.map { |key| hash[key] }
102
+ end
103
+
104
+ def self.unhash_records(records, keys)
105
+ records.each_with_index do |record, index|
106
+ records[index] = unhash(record, keys)
107
+ end
108
+
109
+ records
110
+ end
111
+
112
+ def self.convert_booleans(records, columns)
113
+ records.each do |record|
114
+ columns.each do |column|
115
+ next if is_boolean(record[column])
116
+ record[column] = (record[column] == 't' or record[column] == '1')
117
+ end
118
+ end
119
+ records
120
+ end
121
+
122
+ def self.boolean_columns(table)
123
+ columns = ActiveRecord::Base.connection.columns(table).reject { |c| silence_warnings { c.type != :boolean } }
124
+ columns.map { |c| c.name }
125
+ end
126
+
127
+ def self.is_boolean(value)
128
+ value.kind_of?(TrueClass) or value.kind_of?(FalseClass)
129
+ end
130
+
131
+ def self.quote_table(table)
132
+ ActiveRecord::Base.connection.quote_table_name(table)
133
+ end
134
+
135
+ end
136
+
137
+ class Dump
138
+ def self.before_table(io, table)
139
+
140
+ end
141
+
142
+ def self.dump(io)
143
+ tables.each do |table|
144
+ before_table(io, table)
145
+ dump_table(io, table)
146
+ after_table(io, table)
147
+ end
148
+ end
149
+
150
+ def self.after_table(io, table)
151
+
152
+ end
153
+
154
+ def self.tables
155
+ ActiveRecord::Base.connection.tables.reject { |table| ['schema_info', 'schema_migrations'].include?(table) }
156
+ end
157
+
158
+ def self.dump_table(io, table)
159
+ return if table_record_count(table).zero?
160
+
161
+ dump_table_columns(io, table)
162
+ dump_table_records(io, table)
163
+ end
164
+
165
+ def self.table_column_names(table)
166
+ ActiveRecord::Base.connection.columns(table).map { |c| c.name }
167
+ end
168
+
169
+
170
+ def self.each_table_page(table, records_per_page=1000)
171
+ total_count = table_record_count(table)
172
+ pages = (total_count.to_f / records_per_page).ceil - 1
173
+ id = table_column_names(table).first
174
+ boolean_columns = SerializationHelper::Utils.boolean_columns(table)
175
+ quoted_table_name = SerializationHelper::Utils.quote_table(table)
176
+
177
+ (0..pages).to_a.each do |page|
178
+ sql = ActiveRecord::Base.connection.add_limit_offset!("SELECT * FROM #{quoted_table_name} ORDER BY #{id}",
179
+ :limit => records_per_page, :offset => records_per_page * page
180
+ )
181
+ records = ActiveRecord::Base.connection.select_all(sql)
182
+ records = SerializationHelper::Utils.convert_booleans(records, boolean_columns)
183
+ yield records
184
+ end
185
+ end
186
+
187
+ def self.table_record_count(table)
188
+ ActiveRecord::Base.connection.select_one("SELECT COUNT(*) FROM #{SerializationHelper::Utils.quote_table(table)}").values.first.to_i
189
+ end
190
+
191
+ end
192
+
193
+ end
@@ -0,0 +1,67 @@
1
+ require 'rubygems'
2
+ require 'yaml'
3
+ require 'active_record'
4
+ require 'serialization_helper'
5
+
6
+ module YamlDb
7
+ module Helper
8
+ def self.loader
9
+ YamlDb::Load
10
+ end
11
+
12
+ def self.dumper
13
+ YamlDb::Dump
14
+ end
15
+
16
+ def self.extension
17
+ "yml"
18
+ end
19
+ end
20
+
21
+
22
+ module Utils
23
+ def self.chunk_records(records)
24
+ yaml = [ records ].to_yaml
25
+ yaml.sub!("--- \n", "")
26
+ yaml.sub!('- - -', ' - -')
27
+ yaml
28
+ end
29
+
30
+ end
31
+
32
+ class Dump < SerializationHelper::Dump
33
+
34
+ def self.dump_table_columns(io, table)
35
+ io.write("\n")
36
+ io.write({ table => { 'columns' => table_column_names(table) } }.to_yaml)
37
+ end
38
+
39
+ def self.dump_table_records(io, table)
40
+ table_record_header(io)
41
+
42
+ column_names = table_column_names(table)
43
+
44
+ each_table_page(table) do |records|
45
+ rows = SerializationHelper::Utils.unhash_records(records, column_names)
46
+ io.write(YamlDb::Utils.chunk_records(records))
47
+ end
48
+ end
49
+
50
+ def self.table_record_header(io)
51
+ io.write(" records: \n")
52
+ end
53
+
54
+ end
55
+
56
+ class Load < SerializationHelper::Load
57
+ def self.load_documents(io, truncate = true)
58
+ YAML.load_documents(io) do |ydoc|
59
+ ydoc.keys.each do |table_name|
60
+ next if ydoc[table_name].nil?
61
+ load_table(table_name, ydoc[table_name], truncate)
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
5
+ require 'yaml_db'
6
+ require 'serialization_helper'
@@ -0,0 +1,53 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ describe SerializationHelper::Base do
4
+
5
+ before do
6
+ @io = StringIO.new
7
+ silence_warnings { ActiveRecord::Base = mock('ActiveRecord::Base', :null_object => true) }
8
+ ActiveRecord::Base.connection = mock('connection')
9
+ ActiveRecord::Base.connection.stub!(:tables).and_return([ 'mytable', 'schema_info', 'schema_migrations' ])
10
+ end
11
+
12
+ def stub_helper!
13
+ @helper = mock("MyHelper")
14
+ @dumper = mock("MyDumper");
15
+ @loader = mock("MyLoader");
16
+ @helper.stub!(:dumper).and_return(@dumper)
17
+ @helper.stub!(:loader).and_return(@loader)
18
+ @helper.stub!(:extension).and_return("yml")
19
+ @dumper.stub!(:tables).and_return([ActiveRecord::Base.connection.tables[0]])
20
+ @dumper.stub!(:before_table).and_return(nil)
21
+ @dumper.stub!(:after_table).and_return(nil)
22
+ end
23
+
24
+ context "for multi-file dumps" do
25
+ before do
26
+ File.should_receive(:new).once.with("dir_name/mytable.yml", "w").and_return(@io)
27
+ Dir.should_receive(:mkdir).once.with("dir_name")
28
+ stub_helper!
29
+ @dumper.should_receive(:dump_table).once.with(@io, "mytable")
30
+ end
31
+
32
+ it "should create the number of files that there are tables" do
33
+ SerializationHelper::Base.new(@helper).dump_to_dir "dir_name"
34
+ end
35
+
36
+ end
37
+
38
+ context "for multi-file loads" do
39
+
40
+ before do
41
+ stub_helper!
42
+ @loader.should_receive(:load).once.with(@io, true)
43
+ File.should_receive(:new).once.with("dir_name/mytable.yml", "r").and_return(@io)
44
+ Dir.stub!(:entries).and_return(["mytable.yml"])
45
+ end
46
+
47
+ it "should insert into then umber of tables that there are files" do
48
+ SerializationHelper::Base.new(@helper).load_from_dir "dir_name"
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,65 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ describe SerializationHelper::Dump do
4
+
5
+ before do
6
+ silence_warnings { ActiveRecord::Base = mock('ActiveRecord::Base', :null_object => true) }
7
+ ActiveRecord::Base.connection = mock('connection')
8
+ ActiveRecord::Base.connection.stub!(:tables).and_return([ 'mytable', 'schema_info', 'schema_migrations' ])
9
+ ActiveRecord::Base.connection.stub!(:columns).with('mytable').and_return([ mock('a',:name => 'a'), mock('b', :name => 'b') ])
10
+ ActiveRecord::Base.connection.stub!(:select_one).and_return({"count"=>"2"})
11
+ ActiveRecord::Base.connection.stub!(:select_all).and_return([ { 'a' => 1, 'b' => 2 }, { 'a' => 3, 'b' => 4 } ])
12
+ SerializationHelper::Utils.stub!(:quote_table).with('mytable').and_return('mytable')
13
+ end
14
+
15
+ before(:each) do
16
+ File.stub!(:new).with('dump.yml', 'w').and_return(StringIO.new)
17
+ @io = StringIO.new
18
+ end
19
+
20
+ it "should return a list of column names" do
21
+ SerializationHelper::Dump.table_column_names('mytable').should == [ 'a', 'b' ]
22
+ end
23
+
24
+ it "should return a list of tables without the rails schema table" do
25
+ SerializationHelper::Dump.tables.should == ['mytable']
26
+ end
27
+
28
+ it "should return the total number of records in a table" do
29
+ SerializationHelper::Dump.table_record_count('mytable').should == 2
30
+ end
31
+
32
+ it "should return all records from the database and return them when there is only 1 page" do
33
+ SerializationHelper::Dump.each_table_page('mytable') do |records|
34
+ records.should == [ { 'a' => 1, 'b' => 2 }, { 'a' => 3, 'b' => 4 } ]
35
+ end
36
+ end
37
+
38
+ it "should paginate records from the database and return them" do
39
+ ActiveRecord::Base.connection.stub!(:select_all).and_return([ { 'a' => 1, 'b' => 2 } ], [ { 'a' => 3, 'b' => 4 } ])
40
+
41
+ records = [ ]
42
+ SerializationHelper::Dump.each_table_page('mytable', 1) do |page|
43
+ page.size.should == 1
44
+ records.concat(page)
45
+ end
46
+
47
+ records.should == [ { 'a' => 1, 'b' => 2 }, { 'a' => 3, 'b' => 4 } ]
48
+ end
49
+
50
+ it "should dump a table's contents to yaml" do
51
+ SerializationHelper::Dump.should_receive(:dump_table_columns)
52
+ SerializationHelper::Dump.should_receive(:dump_table_records)
53
+ SerializationHelper::Dump.dump_table(@io, 'mytable')
54
+ end
55
+
56
+ it "should not dump a table's contents when the record count is zero" do
57
+ SerializationHelper::Dump.stub!(:table_record_count).with('mytable').and_return(0)
58
+ SerializationHelper::Dump.should_not_receive(:dump_table_columns)
59
+ SerializationHelper::Dump.should_not_receive(:dump_table_records)
60
+ SerializationHelper::Dump.dump_table(@io, 'mytable')
61
+ end
62
+
63
+
64
+
65
+ end
@@ -0,0 +1,71 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ describe SerializationHelper::Load do
4
+ before do
5
+ SerializationHelper::Utils.stub!(:quote_table).with('mytable').and_return('mytable')
6
+
7
+ silence_warnings { ActiveRecord::Base = mock('ActiveRecord::Base', :null_object => true) }
8
+ ActiveRecord::Base.connection = mock('connection')
9
+ ActiveRecord::Base.connection.stub!(:transaction).and_yield
10
+ @io = StringIO.new
11
+ end
12
+
13
+ it "should truncate the table" do
14
+ ActiveRecord::Base.connection.stub!(:execute).with("TRUNCATE mytable").and_return(true)
15
+ ActiveRecord::Base.connection.should_not_receive(:execute).with("DELETE FROM mytable")
16
+ SerializationHelper::Load.truncate_table('mytable')
17
+ end
18
+
19
+ it "should delete the table if truncate throws an exception" do
20
+ ActiveRecord::Base.connection.should_receive(:execute).with("TRUNCATE mytable").and_raise()
21
+ ActiveRecord::Base.connection.should_receive(:execute).with("DELETE FROM mytable").and_return(true)
22
+ SerializationHelper::Load.truncate_table('mytable')
23
+ end
24
+
25
+
26
+ it "should call reset pk sequence if the connection adapter is postgres" do
27
+ ActiveRecord::Base.connection.should_receive(:respond_to?).with(:reset_pk_sequence!).and_return(true)
28
+ ActiveRecord::Base.connection.should_receive(:reset_pk_sequence!).with('mytable')
29
+ SerializationHelper::Load.reset_pk_sequence!('mytable')
30
+ end
31
+
32
+ it "should not call reset pk sequence for other adapters" do
33
+ ActiveRecord::Base.connection.should_receive(:respond_to?).with(:reset_pk_sequence!).and_return(false)
34
+ ActiveRecord::Base.connection.should_not_receive(:reset_pk_sequence!)
35
+ SerializationHelper::Load.reset_pk_sequence!('mytable')
36
+ end
37
+
38
+ it "should insert records into a table" do
39
+ ActiveRecord::Base.connection.stub!(:quote_column_name).with('a').and_return('a')
40
+ ActiveRecord::Base.connection.stub!(:quote_column_name).with('b').and_return('b')
41
+ ActiveRecord::Base.connection.stub!(:quote).with(1).and_return("'1'")
42
+ ActiveRecord::Base.connection.stub!(:quote).with(2).and_return("'2'")
43
+ ActiveRecord::Base.connection.stub!(:quote).with(3).and_return("'3'")
44
+ ActiveRecord::Base.connection.stub!(:quote).with(4).and_return("'4'")
45
+ ActiveRecord::Base.connection.should_receive(:execute).with("INSERT INTO mytable (a,b) VALUES ('1','2')")
46
+ ActiveRecord::Base.connection.should_receive(:execute).with("INSERT INTO mytable (a,b) VALUES ('3','4')")
47
+
48
+ SerializationHelper::Load.load_records('mytable', ['a', 'b'], [[1, 2], [3, 4]])
49
+ end
50
+
51
+ it "should quote column names that correspond to sql keywords" do
52
+ ActiveRecord::Base.connection.stub!(:quote_column_name).with('a').and_return('a')
53
+ ActiveRecord::Base.connection.stub!(:quote_column_name).with('count').and_return('"count"')
54
+ ActiveRecord::Base.connection.stub!(:quote).with(1).and_return("'1'")
55
+ ActiveRecord::Base.connection.stub!(:quote).with(2).and_return("'2'")
56
+ ActiveRecord::Base.connection.stub!(:quote).with(3).and_return("'3'")
57
+ ActiveRecord::Base.connection.stub!(:quote).with(4).and_return("'4'")
58
+ ActiveRecord::Base.connection.should_receive(:execute).with("INSERT INTO mytable (a,\"count\") VALUES ('1','2')")
59
+ ActiveRecord::Base.connection.should_receive(:execute).with("INSERT INTO mytable (a,\"count\") VALUES ('3','4')")
60
+
61
+ SerializationHelper::Load.load_records('mytable', ['a', 'count'], [[1, 2], [3, 4]])
62
+ end
63
+
64
+ it "should truncate the table and then load the records into the table" do
65
+ SerializationHelper::Load.should_receive(:truncate_table).with('mytable')
66
+ SerializationHelper::Load.should_receive(:load_records).with('mytable', ['a', 'b'], [[1, 2], [3, 4]])
67
+ SerializationHelper::Load.should_receive(:reset_pk_sequence!).with('mytable')
68
+
69
+ SerializationHelper::Load.load_table('mytable', { 'columns' => [ 'a', 'b' ], 'records' => [[1, 2], [3, 4]] })
70
+ end
71
+ end
@@ -0,0 +1,31 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ describe SerializationHelper::Utils, " convert records utility method" do
4
+ before do
5
+ silence_warnings { ActiveRecord::Base = mock('ActiveRecord::Base', :null_object => true) }
6
+ ActiveRecord::Base.connection = mock('connection')
7
+ end
8
+
9
+ it "returns an array of hash values using an array of ordered keys" do
10
+ SerializationHelper::Utils.unhash({ 'a' => 1, 'b' => 2 }, [ 'b', 'a' ]).should == [ 2, 1 ]
11
+ end
12
+
13
+ it "should unhash each hash an array using an array of ordered keys" do
14
+ SerializationHelper::Utils.unhash_records([ { 'a' => 1, 'b' => 2 }, { 'a' => 3, 'b' => 4 } ], [ 'b', 'a' ]).should == [ [ 2, 1 ], [ 4, 3 ] ]
15
+ end
16
+
17
+ it "should return true if it is a boolean type" do
18
+ SerializationHelper::Utils.is_boolean(true).should == true
19
+ SerializationHelper::Utils.is_boolean('true').should_not == true
20
+ end
21
+
22
+ it "should return an array of boolean columns" do
23
+ ActiveRecord::Base.connection.stub!(:columns).with('mytable').and_return([ mock('a',:name => 'a',:type => :string), mock('b', :name => 'b',:type => :boolean) ])
24
+ SerializationHelper::Utils.boolean_columns('mytable').should == ['b']
25
+ end
26
+
27
+ it "should quote the table name" do
28
+ ActiveRecord::Base.connection.should_receive(:quote_table_name).with('values').and_return('`values`')
29
+ SerializationHelper::Utils.quote_table('values').should == '`values`'
30
+ end
31
+ end
@@ -0,0 +1,55 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ describe YamlDb::Dump do
4
+
5
+ before do
6
+ silence_warnings { ActiveRecord::Base = mock('ActiveRecord::Base', :null_object => true) }
7
+ ActiveRecord::Base.connection = mock('connection')
8
+ ActiveRecord::Base.connection.stub!(:tables).and_return([ 'mytable', 'schema_info', 'schema_migrations' ])
9
+ ActiveRecord::Base.connection.stub!(:columns).with('mytable').and_return([ mock('a',:name => 'a'), mock('b', :name => 'b') ])
10
+ ActiveRecord::Base.connection.stub!(:select_one).and_return({"count"=>"2"})
11
+ ActiveRecord::Base.connection.stub!(:select_all).and_return([ { 'a' => 1, 'b' => 2 }, { 'a' => 3, 'b' => 4 } ])
12
+ YamlDb::Utils.stub!(:quote_table).with('mytable').and_return('mytable')
13
+ end
14
+
15
+ before(:each) do
16
+ File.stub!(:new).with('dump.yml', 'w').and_return(StringIO.new)
17
+ @io = StringIO.new
18
+ end
19
+
20
+ it "should return a formatted string" do
21
+ YamlDb::Dump.table_record_header(@io)
22
+ @io.rewind
23
+ @io.read.should == " records: \n"
24
+ end
25
+
26
+
27
+ it "should return a yaml string that contains a table header and column names" do
28
+ YamlDb::Dump.stub!(:table_column_names).with('mytable').and_return([ 'a', 'b' ])
29
+ YamlDb::Dump.dump_table_columns(@io, 'mytable')
30
+ @io.rewind
31
+ @io.read.should == <<EOYAML
32
+
33
+ ---
34
+ mytable:
35
+ columns:
36
+ - a
37
+ - b
38
+ EOYAML
39
+ end
40
+
41
+ it "should return dump the records for a table in yaml to a given io stream" do
42
+ YamlDb::Dump.dump_table_records(@io, 'mytable')
43
+ @io.rewind
44
+ @io.read.should == <<EOYAML
45
+ records:
46
+ - - 1
47
+ - 2
48
+ - - 3
49
+ - 4
50
+ EOYAML
51
+ end
52
+
53
+
54
+
55
+ end
@@ -0,0 +1,32 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ describe YamlDb::Load do
4
+ before do
5
+ SerializationHelper::Utils.stub!(:quote_table).with('mytable').and_return('mytable')
6
+
7
+ silence_warnings { ActiveRecord::Base = mock('ActiveRecord::Base', :null_object => true) }
8
+ ActiveRecord::Base.connection = mock('connection')
9
+ ActiveRecord::Base.connection.stub!(:transaction).and_yield
10
+ end
11
+
12
+ before(:each) do
13
+ @io = StringIO.new
14
+ end
15
+
16
+
17
+ it "should call load structure for each document in the file" do
18
+ YAML.should_receive(:load_documents).with(@io).and_yield({ 'mytable' => {
19
+ 'columns' => [ 'a', 'b' ],
20
+ 'records' => [[1, 2], [3, 4]]
21
+ } } )
22
+ YamlDb::Load.should_receive(:load_table).with('mytable', { 'columns' => [ 'a', 'b' ], 'records' => [[1, 2], [3, 4]] },true)
23
+ YamlDb::Load.load(@io)
24
+ end
25
+
26
+ it "should not call load structure when the document in the file contains no records" do
27
+ YAML.should_receive(:load_documents).with(@io).and_yield({ 'mytable' => nil })
28
+ YamlDb::Load.should_not_receive(:load_table)
29
+ YamlDb::Load.load(@io)
30
+ end
31
+
32
+ end
@@ -0,0 +1,21 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ describe YamlDb::Utils, " convert records utility method" do
4
+
5
+ it "turns an array with one record into a yaml chunk" do
6
+ YamlDb::Utils.chunk_records([ %w(a b) ]).should == <<EOYAML
7
+ - - a
8
+ - b
9
+ EOYAML
10
+ end
11
+
12
+ it "turns an array with two records into a yaml chunk" do
13
+ YamlDb::Utils.chunk_records([ %w(a b), %w(x y) ]).should == <<EOYAML
14
+ - - a
15
+ - b
16
+ - - x
17
+ - y
18
+ EOYAML
19
+ end
20
+
21
+ end
@@ -0,0 +1,45 @@
1
+ namespace :db do
2
+ desc "Dump schema and data to db/schema.rb and db/data.yml"
3
+ task(:dump => [ "db:schema:dump", "db:data:dump" ])
4
+
5
+ desc "Load schema and data from db/schema.rb and db/data.yml"
6
+ task(:load => [ "db:schema:load", "db:data:load" ])
7
+
8
+ namespace :data do
9
+ def db_dump_data_file (extension = "yml")
10
+ "#{dump_dir}/data.#{extension}"
11
+ end
12
+
13
+ def dump_dir(dir = "")
14
+ "#{RAILS_ROOT}/db#{dir}"
15
+ end
16
+
17
+ desc "Dump contents of database to db/data.extension (defaults to yaml)"
18
+ task :dump => :environment do
19
+ format_class = ENV['class'] || "YamlDb::Helper"
20
+ helper = format_class.constantize
21
+ SerializationHelper::Base.new(helper).dump db_dump_data_file helper.extension
22
+ end
23
+
24
+ desc "Dump contents of database to curr_dir_name/tablename.extension (defaults to yaml)"
25
+ task :dump_dir => :environment do
26
+ format_class = ENV['class'] || "YamlDb::Helper"
27
+ dir = ENV['dir'] || "#{Time.now.to_s.gsub(/ /, '_')}"
28
+ SerializationHelper::Base.new(format_class.constantize).dump_to_dir dump_dir("/#{dir}")
29
+ end
30
+
31
+ desc "Load contents of db/data.extension (defaults to yaml) into database"
32
+ task :load => :environment do
33
+ format_class = ENV['class'] || "YamlDb::Helper"
34
+ helper = format_class.constantize
35
+ SerializationHelper::Base.new(helper).load (db_dump_data_file helper.extension)
36
+ end
37
+
38
+ desc "Load contents of db/data_dir into database"
39
+ task :load_dir => :environment do
40
+ dir = ENV['dir'] || "base"
41
+ format_class = ENV['class'] || "YamlDb::Helper"
42
+ SerializationHelper::Base.new(format_class.constantize).load_from_dir dump_dir("/#{dir}")
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,68 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{yaml_db}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Adam Wiggins", "Orion Henry"]
12
+ s.date = %q{2009-12-27}
13
+ s.description = %q{
14
+ YamlDb is a database-independent format for dumping and restoring data. It complements the the database-independent schema format found in db/schema.rb. The data is saved into db/data.yml.
15
+ This can be used as a replacement for mysqldump or pg_dump, but only for the databases typically used by Rails apps. Users, permissions, schemas, triggers, and other advanced database features are not supported - by design.
16
+ Any database that has an ActiveRecord adapter should work
17
+ }
18
+ s.email = %q{nate@ludicast.com}
19
+ s.extra_rdoc_files = [
20
+ "README"
21
+ ]
22
+ s.files = [
23
+ "README",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "about.yml",
27
+ "init.rb",
28
+ "lib/csv_db.rb",
29
+ "lib/serialization_helper.rb",
30
+ "lib/yaml_db.rb",
31
+ "spec/base.rb",
32
+ "spec/serialization_helper_base_spec.rb",
33
+ "spec/serialization_helper_dump_spec.rb",
34
+ "spec/serialization_helper_load_spec.rb",
35
+ "spec/serialization_utils_spec.rb",
36
+ "spec/yaml_dump_spec.rb",
37
+ "spec/yaml_load_spec.rb",
38
+ "spec/yaml_utils_spec.rb",
39
+ "tasks/yaml_db_tasks.rake",
40
+ "yaml_db.gemspec"
41
+ ]
42
+ s.homepage = %q{http://github.com/ludicast/yaml_db}
43
+ s.rdoc_options = ["--charset=UTF-8"]
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = %q{1.3.5}
46
+ s.summary = %q{yaml_db allows export/import of database into/from yaml files}
47
+ s.test_files = [
48
+ "spec/base.rb",
49
+ "spec/serialization_helper_base_spec.rb",
50
+ "spec/serialization_helper_dump_spec.rb",
51
+ "spec/serialization_helper_load_spec.rb",
52
+ "spec/serialization_utils_spec.rb",
53
+ "spec/yaml_dump_spec.rb",
54
+ "spec/yaml_load_spec.rb",
55
+ "spec/yaml_utils_spec.rb"
56
+ ]
57
+
58
+ if s.respond_to? :specification_version then
59
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
60
+ s.specification_version = 3
61
+
62
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
63
+ else
64
+ end
65
+ else
66
+ end
67
+ end
68
+
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yaml_db
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Adam Wiggins
8
+ - Orion Henry
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-12-27 00:00:00 -05:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: |
18
+
19
+ YamlDb is a database-independent format for dumping and restoring data. It complements the the database-independent schema format found in db/schema.rb. The data is saved into db/data.yml.
20
+ This can be used as a replacement for mysqldump or pg_dump, but only for the databases typically used by Rails apps. Users, permissions, schemas, triggers, and other advanced database features are not supported - by design.
21
+ Any database that has an ActiveRecord adapter should work
22
+
23
+ email: nate@ludicast.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - README
30
+ files:
31
+ - README
32
+ - Rakefile
33
+ - VERSION
34
+ - about.yml
35
+ - init.rb
36
+ - lib/csv_db.rb
37
+ - lib/serialization_helper.rb
38
+ - lib/yaml_db.rb
39
+ - spec/base.rb
40
+ - spec/serialization_helper_base_spec.rb
41
+ - spec/serialization_helper_dump_spec.rb
42
+ - spec/serialization_helper_load_spec.rb
43
+ - spec/serialization_utils_spec.rb
44
+ - spec/yaml_dump_spec.rb
45
+ - spec/yaml_load_spec.rb
46
+ - spec/yaml_utils_spec.rb
47
+ - tasks/yaml_db_tasks.rake
48
+ - yaml_db.gemspec
49
+ has_rdoc: true
50
+ homepage: http://github.com/ludicast/yaml_db
51
+ licenses: []
52
+
53
+ post_install_message:
54
+ rdoc_options:
55
+ - --charset=UTF-8
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ requirements: []
71
+
72
+ rubyforge_project:
73
+ rubygems_version: 1.3.5
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: yaml_db allows export/import of database into/from yaml files
77
+ test_files:
78
+ - spec/base.rb
79
+ - spec/serialization_helper_base_spec.rb
80
+ - spec/serialization_helper_dump_spec.rb
81
+ - spec/serialization_helper_load_spec.rb
82
+ - spec/serialization_utils_spec.rb
83
+ - spec/yaml_dump_spec.rb
84
+ - spec/yaml_load_spec.rb
85
+ - spec/yaml_utils_spec.rb