db_to_file 1.2.4
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +11 -0
- data/db/database.yml +2 -0
- data/db/test.sqlite3 +0 -0
- data/db_to_file.gemspec +33 -0
- data/lib/db_to_file.rb +21 -0
- data/lib/db_to_file/config.rb +56 -0
- data/lib/db_to_file/railtie.rb +12 -0
- data/lib/db_to_file/system_executer.rb +16 -0
- data/lib/db_to_file/unloader.rb +151 -0
- data/lib/db_to_file/uploader.rb +128 -0
- data/lib/db_to_file/values_normalizer/object_to_hash.rb +46 -0
- data/lib/db_to_file/values_normalizer/value_into_object.rb +40 -0
- data/lib/db_to_file/version.rb +3 -0
- data/lib/db_to_file/version_controller.rb +78 -0
- data/lib/tasks/unloader.rake +18 -0
- data/lib/tasks/uploader.rake +37 -0
- data/test/fixtures/config.yml +6 -0
- data/test/lib/db_to_file/unloader_test.rb +137 -0
- data/test/lib/db_to_file/uploader_test.rb +145 -0
- data/test/lib/db_to_file/values_normalizer/object_to_hash_test.rb +68 -0
- data/test/lib/db_to_file/values_normalizer/value_into_object_test.rb +63 -0
- data/test/lib/db_to_file/version_controller_test.rb +132 -0
- data/test/lib/db_to_file/version_test.rb +7 -0
- data/test/lib/setting.rb +3 -0
- data/test/lib/user.rb +2 -0
- data/test/test_helper.rb +45 -0
- metadata +252 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d5be7f37b637cad13b6c873c22fd7513697f39c9
|
4
|
+
data.tar.gz: bb24640e221dfd4bb0bc126e312181279b11f91b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ac169b0f083355209e9f2d4281fc5b9b57e3ab4d34dd50d8caafa1a3f5152937416e153ecf6ee635f8f51e2e9372eb099ec1d6fa2a031b2ac244b0725a08cb27
|
7
|
+
data.tar.gz: 8d56efd2cad35ed1a07f8457845274719c11353bd7608baaa0100fd79c0af337e252e0db44250a17b4da04aaf7a48394f8cbec5ffef1decc46379f55d0312ee1
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Ewout Quax
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# DbToFile
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'db_to_file'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install db_to_file
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it ( http://github.com/<my-github-username>/db_to_file/fork )
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/db/database.yml
ADDED
data/db/test.sqlite3
ADDED
Binary file
|
data/db_to_file.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'db_to_file/version'
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "db_to_file"
|
7
|
+
spec.version = DbToFile::VERSION
|
8
|
+
spec.authors = ["Ewout Quax"]
|
9
|
+
spec.email = ["ewout.quax@quicknet.nl"]
|
10
|
+
spec.summary = %q{Unload and upload database-tables to a file-system}
|
11
|
+
spec.description = %q{Unload and upload database-tables to a file-system}
|
12
|
+
spec.homepage = ""
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency 'activerecord', '> 3.2.0'
|
21
|
+
spec.add_dependency 'activesupport', '> 3.2.0'
|
22
|
+
spec.add_dependency 'git', '> 1.2.6'
|
23
|
+
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.5'
|
25
|
+
spec.add_development_dependency 'rake'
|
26
|
+
spec.add_development_dependency 'pry'
|
27
|
+
spec.add_development_dependency "minitest", "~> 4.7.3"
|
28
|
+
spec.add_development_dependency 'minitest-reporters', '>= 0.5.0'
|
29
|
+
spec.add_development_dependency "mocha"
|
30
|
+
spec.add_development_dependency 'sqlite3'
|
31
|
+
spec.add_development_dependency 'turn'
|
32
|
+
spec.add_development_dependency 'simplecov'
|
33
|
+
end
|
data/lib/db_to_file.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
require 'git'
|
4
|
+
require 'db_to_file/config'
|
5
|
+
require 'db_to_file/version'
|
6
|
+
require 'db_to_file/version_controller'
|
7
|
+
require 'db_to_file/unloader'
|
8
|
+
require 'db_to_file/uploader'
|
9
|
+
require 'db_to_file/values_normalizer/object_to_hash'
|
10
|
+
require 'db_to_file/values_normalizer/value_into_object'
|
11
|
+
require 'db_to_file/system_executer'
|
12
|
+
|
13
|
+
module DbToFile
|
14
|
+
if defined?(Rails)
|
15
|
+
require 'db_to_file/railtie'
|
16
|
+
else
|
17
|
+
require 'yaml'
|
18
|
+
dbconfig = YAML::load(File.open('db/database.yml'))
|
19
|
+
ActiveRecord::Base.establish_connection(dbconfig)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module DbToFile
|
4
|
+
class Config
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
def self.instance
|
8
|
+
@@instance ||= new
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@data = load_config
|
13
|
+
end
|
14
|
+
|
15
|
+
def tables
|
16
|
+
data['tables'].keys
|
17
|
+
end
|
18
|
+
|
19
|
+
def field_extension(table_name, field_name)
|
20
|
+
begin
|
21
|
+
data['tables'][table_name]['field_extensions'][field_name]
|
22
|
+
rescue NoMethodError
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def ignore_columns(table_name)
|
28
|
+
begin
|
29
|
+
data['tables'][table_name]['ignore_columns']
|
30
|
+
rescue NoMethodError
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def directory_prefix(table_name)
|
36
|
+
begin
|
37
|
+
data['tables'][table_name]['directory_prefix']
|
38
|
+
rescue NoMethodError
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def data
|
44
|
+
@data
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def load_config
|
49
|
+
YAML::load(File.read(config_file))
|
50
|
+
end
|
51
|
+
|
52
|
+
def config_file
|
53
|
+
'config/db_to_file.yml'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
module DbToFile
|
2
|
+
class Unloader
|
3
|
+
def initialize
|
4
|
+
# Load config and build database connection, before stashing possible changes
|
5
|
+
@config ||= config
|
6
|
+
ActiveRecord::Base.connection.tables
|
7
|
+
end
|
8
|
+
|
9
|
+
def unload
|
10
|
+
prepare_code_version
|
11
|
+
unload_tables
|
12
|
+
update_code_version
|
13
|
+
restore_local_stash
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def prepare_code_version
|
18
|
+
version_controller.prepare_code_version
|
19
|
+
end
|
20
|
+
|
21
|
+
def unload_tables
|
22
|
+
puts 'Start downloading tables'
|
23
|
+
tables.each do |table_name|
|
24
|
+
puts "Downloading table '#{table_name}'"
|
25
|
+
Table.new(table_name, self).unload
|
26
|
+
end
|
27
|
+
puts 'Done downloading tables'
|
28
|
+
end
|
29
|
+
|
30
|
+
def update_code_version
|
31
|
+
puts 'Start updating code version'
|
32
|
+
version_controller.update_code_version
|
33
|
+
puts 'Done updating code version'
|
34
|
+
end
|
35
|
+
|
36
|
+
def restore_local_stash
|
37
|
+
version_controller.restore_local_stash
|
38
|
+
end
|
39
|
+
|
40
|
+
def version_controller
|
41
|
+
@version_controller ||= VersionController.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def tables
|
45
|
+
config.tables
|
46
|
+
end
|
47
|
+
|
48
|
+
def config
|
49
|
+
Config.instance
|
50
|
+
end
|
51
|
+
|
52
|
+
class Table
|
53
|
+
def initialize(table_name, unloader)
|
54
|
+
@table = table_name.singularize.classify.constantize
|
55
|
+
@unloader = unloader
|
56
|
+
end
|
57
|
+
|
58
|
+
def unload
|
59
|
+
@table.all.each do |row|
|
60
|
+
Record.new(row, self).fields_to_files
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
class Record
|
66
|
+
def initialize(row, table)
|
67
|
+
@row = row
|
68
|
+
@table = table
|
69
|
+
end
|
70
|
+
|
71
|
+
def fields_to_files
|
72
|
+
build_directory
|
73
|
+
|
74
|
+
normalized_hash = DbToFile::ValuesNormalizer::ObjectToHash.new(@row).normalize
|
75
|
+
normalized_hash.except(*ignore_columns).each_pair do |field_name, value|
|
76
|
+
Field.new(field_name, self).write_value(value)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
def build_directory
|
82
|
+
FileUtils.mkdir_p(base_dir)
|
83
|
+
end
|
84
|
+
|
85
|
+
def ignore_columns
|
86
|
+
config.ignore_columns(table_name)
|
87
|
+
end
|
88
|
+
|
89
|
+
def base_dir
|
90
|
+
"db/db_to_file/#{table_name}/#{row_name}"
|
91
|
+
end
|
92
|
+
|
93
|
+
def row_name
|
94
|
+
[directory_prefix, @row.id.to_s].compact.reject(&:empty?).join('_')
|
95
|
+
end
|
96
|
+
|
97
|
+
def directory_prefix
|
98
|
+
if config_directory_prefix.present?
|
99
|
+
(@row.send(config_directory_prefix) || '').parameterize
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def config_directory_prefix
|
104
|
+
config.directory_prefix(table_name)
|
105
|
+
end
|
106
|
+
|
107
|
+
def table_name
|
108
|
+
@row.class.table_name
|
109
|
+
end
|
110
|
+
|
111
|
+
def config
|
112
|
+
Config.instance
|
113
|
+
end
|
114
|
+
|
115
|
+
class Field
|
116
|
+
def initialize(field_name, record)
|
117
|
+
@field_name = field_name
|
118
|
+
@record = record
|
119
|
+
end
|
120
|
+
|
121
|
+
def write_value(value)
|
122
|
+
handle = File.open(full_file_path, 'w')
|
123
|
+
handle.write(value)
|
124
|
+
handle.close
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
def full_file_path
|
129
|
+
File.join(@record.send(:base_dir), file_with_extension)
|
130
|
+
end
|
131
|
+
|
132
|
+
def file_with_extension
|
133
|
+
if (extension = config_field_extension).present?
|
134
|
+
"#{@field_name}.#{extension}"
|
135
|
+
else
|
136
|
+
@field_name
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def config_field_extension
|
141
|
+
config.field_extension(@record.send(:table_name), @field_name)
|
142
|
+
end
|
143
|
+
|
144
|
+
def config
|
145
|
+
Config.instance
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module DbToFile
|
2
|
+
class Uploader
|
3
|
+
def upload(commit_message)
|
4
|
+
if can_continue?
|
5
|
+
invoke_unloader
|
6
|
+
end
|
7
|
+
if can_continue?
|
8
|
+
write_objects_to_db
|
9
|
+
update_code_version(commit_message)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def force_upload
|
14
|
+
write_objects_to_db
|
15
|
+
end
|
16
|
+
|
17
|
+
def force_changed
|
18
|
+
write_changed_objects_to_db
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def invoke_unloader
|
23
|
+
Unloader.new.unload
|
24
|
+
end
|
25
|
+
|
26
|
+
def can_continue?
|
27
|
+
!merge_conflicts_present?
|
28
|
+
end
|
29
|
+
|
30
|
+
def merge_conflicts_present?
|
31
|
+
version_controller.merge_conflicts_present?
|
32
|
+
end
|
33
|
+
|
34
|
+
def write_objects_to_db
|
35
|
+
objects.each(&:save!)
|
36
|
+
end
|
37
|
+
|
38
|
+
def write_changed_objects_to_db
|
39
|
+
changed_objects.each(&:save!)
|
40
|
+
end
|
41
|
+
|
42
|
+
def update_code_version(commit_message)
|
43
|
+
version_controller.update_code_version(commit_message)
|
44
|
+
end
|
45
|
+
|
46
|
+
def objects
|
47
|
+
@objects ||= build_objects
|
48
|
+
end
|
49
|
+
|
50
|
+
def changed_objects
|
51
|
+
@changed_objects ||= build_objects(true)
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_objects(changed_only = false)
|
55
|
+
objects = []
|
56
|
+
files = changed_only ? (version_controller.get_modified_file_list & read_files) : read_files
|
57
|
+
files.each do |model_field_file|
|
58
|
+
data_segments = extract_data_segments(model_field_file)
|
59
|
+
model = data_segments[:model]
|
60
|
+
# find existing object
|
61
|
+
object = objects.detect do |existing_object|
|
62
|
+
existing_object.class == model && existing_object.id == data_segments[:id]
|
63
|
+
end
|
64
|
+
# build new object
|
65
|
+
unless object
|
66
|
+
object = model.find_by_id(data_segments[:id]) || model.new(id: data_segments[:id])
|
67
|
+
objects << object
|
68
|
+
end
|
69
|
+
# set field-value to model
|
70
|
+
update_object_with_field_value(object, data_segments[:field], model_field_file)
|
71
|
+
end
|
72
|
+
|
73
|
+
objects
|
74
|
+
end
|
75
|
+
|
76
|
+
def update_object_with_field_value(object, field, model_field_file)
|
77
|
+
value = file_value(model_field_file)
|
78
|
+
DbToFile::ValuesNormalizer::ValueIntoObject.new(object).normalize(field, value)
|
79
|
+
end
|
80
|
+
|
81
|
+
def file_value(model_field_file)
|
82
|
+
File.read(model_field_file)
|
83
|
+
end
|
84
|
+
|
85
|
+
def extract_data_segments(model_field_file)
|
86
|
+
matches = model_field_file.split('/').last(3)
|
87
|
+
|
88
|
+
{
|
89
|
+
model: matches[0].singularize.classify.constantize,
|
90
|
+
id: matches[1].split('_').last.to_i,
|
91
|
+
field: strip_extension(matches[2])
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def read_files
|
96
|
+
files_in_dir(File.join('db', 'db_to_file'))
|
97
|
+
end
|
98
|
+
|
99
|
+
def files_in_dir(folder)
|
100
|
+
files = Dir.entries(folder)
|
101
|
+
|
102
|
+
found_files = []
|
103
|
+
files.each do |file|
|
104
|
+
full_file = File.join([folder, file])
|
105
|
+
if File.directory?(full_file) && file[0] != '.'
|
106
|
+
subdir = File.join(folder, file)
|
107
|
+
files_in_dir(subdir).each do |subdirfile|
|
108
|
+
found_files << subdirfile
|
109
|
+
end
|
110
|
+
end
|
111
|
+
if File.file?(full_file)
|
112
|
+
found_files << full_file
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
found_files
|
117
|
+
end
|
118
|
+
|
119
|
+
def version_controller
|
120
|
+
@version_controller ||= VersionController.new
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
def strip_extension(file_with_extension)
|
125
|
+
file_with_extension.split('.')[0]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|