seed_gimmick 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +9 -1
- data/lib/seed_gimmick.rb +10 -0
- data/lib/seed_gimmick/inflector.rb +31 -9
- data/lib/seed_gimmick/options.rb +5 -5
- data/lib/seed_gimmick/seed.rb +17 -10
- data/lib/seed_gimmick/seed/difference.rb +81 -0
- data/lib/seed_gimmick/seed_io.rb +28 -4
- data/lib/seed_gimmick/seed_io/base.rb +32 -1
- data/lib/seed_gimmick/seed_io/csv_file.rb +23 -0
- data/lib/seed_gimmick/seed_io/yaml_file.rb +18 -12
- data/lib/seed_gimmick/version.rb +1 -1
- data/lib/tasks/seed_gimmick.rake +5 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b3f2287df970cf60ef0a9c7db0cd8a11c6c14f0
|
4
|
+
data.tar.gz: 9d71742e3197bc0cb2e08705044760adf02863d4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 265d168da24914c99ed796489e780a5e5e2d3d096ac1c85928afe2eb52af4c1775a2351cf29b0ecfdde00f67f63e70a3be52332680b83c6f505b5fa3823ff209
|
7
|
+
data.tar.gz: 3abee3493f1e12cdbf8b5b4f3bbfad2fde5158863558d2a5880355be41bb7f2379f4dd3856fac49238208a01a3ed1f3a5515d4cd2b5b19047c0679ea22edbe4a
|
data/README.md
CHANGED
@@ -43,7 +43,15 @@ $ [TABLES=table_name,table_name] bundle exec rake db:seed_gimmick
|
|
43
43
|
Dump to fixture file from database in seed directory.
|
44
44
|
|
45
45
|
```
|
46
|
-
$ MODELS=ModelName,ModelName bundle exec rake db:seed_gimmick:dump
|
46
|
+
$ MODELS=ModelName,ModelName [FORMAT=csv] bundle exec rake db:seed_gimmick:dump
|
47
|
+
```
|
48
|
+
|
49
|
+
#### Diff
|
50
|
+
|
51
|
+
Display the database and seed difference.
|
52
|
+
|
53
|
+
```
|
54
|
+
$ bundle exec rake seed_gimmick:diff
|
47
55
|
```
|
48
56
|
|
49
57
|
## Contributing
|
data/lib/seed_gimmick.rb
CHANGED
@@ -26,6 +26,16 @@ module SeedGimmick
|
|
26
26
|
Seed.new(model_name, options).dump
|
27
27
|
end
|
28
28
|
end
|
29
|
+
|
30
|
+
def diff(options = nil)
|
31
|
+
SeedGimmick::Seed.find(options).each do |seed|
|
32
|
+
ActiveRecord::Migration.say_with_time(seed.table_name) do
|
33
|
+
seed.compare.each do |changed|
|
34
|
+
puts [changed.flag, changed.id, changed.change_values].join("\t")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
29
39
|
end
|
30
40
|
end
|
31
41
|
|
@@ -7,10 +7,15 @@ module SeedGimmick
|
|
7
7
|
new(options || Options.new)
|
8
8
|
end
|
9
9
|
|
10
|
+
# Convert String to model class.
|
11
|
+
# @param name [String]
|
10
12
|
def model_class(name)
|
11
13
|
name.try(:safe_constantize)
|
12
14
|
end
|
13
15
|
|
16
|
+
# Convert String to Pathname.
|
17
|
+
# @return [Pathname]
|
18
|
+
# @return [nil]
|
14
19
|
def pathname(path)
|
15
20
|
case path
|
16
21
|
when Pathname then path
|
@@ -18,27 +23,44 @@ module SeedGimmick
|
|
18
23
|
else nil
|
19
24
|
end
|
20
25
|
end
|
26
|
+
|
27
|
+
# Path without extension.
|
28
|
+
# @return [Pathname]
|
29
|
+
def without_ext(path)
|
30
|
+
pathname(path).dirname.join(pathname(path).basename(".*"))
|
31
|
+
end
|
32
|
+
|
33
|
+
# Extension type.
|
34
|
+
# @return [Symbol]
|
35
|
+
def ext_type(path)
|
36
|
+
pathname(path).extname.sub(/\A./, '').to_sym
|
37
|
+
end
|
21
38
|
end
|
22
39
|
|
23
40
|
def initialize(options)
|
24
|
-
@seed_dir = options.seed_dir
|
41
|
+
@seed_dir = pathname(options.seed_dir)
|
25
42
|
end
|
26
43
|
|
44
|
+
# Convert seed file path to model class.
|
45
|
+
# @param seed_file [Pathname]
|
46
|
+
# @return [ActiveRecord::Base]
|
27
47
|
def model_for(seed_file)
|
28
|
-
|
29
|
-
self.class.model_class(
|
48
|
+
name = from_seed_dir(self.class.without_ext(seed_file)).to_s.classify
|
49
|
+
self.class.model_class(name)
|
30
50
|
end
|
31
51
|
|
52
|
+
# Convert model class to seed file path.
|
53
|
+
# @param model [ActiveRecord::Base]
|
54
|
+
# @param format [Symbol] Extension type.
|
55
|
+
# @return [Pathname]
|
32
56
|
def seed_for(model, format = :yml)
|
33
|
-
seed_dir
|
57
|
+
seed_dir.join(pathname(model.model_name.collection).sub_ext(".#{format}"))
|
34
58
|
end
|
35
59
|
|
36
60
|
private
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
def relative_path(path)
|
61
|
+
# Convert lerative path from seed_dir.
|
62
|
+
def from_seed_dir(path)
|
63
|
+
path = pathname(path)
|
42
64
|
path.relative? ? path : path.relative_path_from(seed_dir)
|
43
65
|
end
|
44
66
|
|
data/lib/seed_gimmick/options.rb
CHANGED
data/lib/seed_gimmick/seed.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "seed_gimmick/seed/difference"
|
2
|
+
|
1
3
|
module SeedGimmick
|
2
4
|
class Seed
|
3
5
|
class << self
|
@@ -35,6 +37,10 @@ module SeedGimmick
|
|
35
37
|
@seed_file ||= @inflector.seed_for(@model, (ext || @options.default_ext))
|
36
38
|
end
|
37
39
|
|
40
|
+
def seed_io
|
41
|
+
@seed_io ||= SeedIO.factory(seed_file)
|
42
|
+
end
|
43
|
+
|
38
44
|
def table_name
|
39
45
|
model.model_name.plural
|
40
46
|
end
|
@@ -45,27 +51,28 @@ module SeedGimmick
|
|
45
51
|
model.column_names - exclude_columns
|
46
52
|
end
|
47
53
|
|
48
|
-
def load_file
|
49
|
-
SeedIO.get(seed_file).load_data
|
50
|
-
end
|
51
|
-
|
52
|
-
def write_file(array_of_hashes)
|
53
|
-
SeedIO.get(seed_file).dump_data(array_of_hashes)
|
54
|
-
end
|
55
|
-
|
56
54
|
def bootstrap
|
57
55
|
ActiveRecord::Migration.say_with_time(table_name) do
|
58
56
|
model.transaction do
|
59
57
|
model.delete_all
|
60
|
-
model.import(
|
58
|
+
model.import(seed_io.values.map {|hash| model.new(hash) })
|
61
59
|
end
|
60
|
+
seed_io.values.size
|
62
61
|
end
|
63
62
|
rescue LoadFailed => e
|
64
63
|
$stdout.print e.message
|
65
64
|
end
|
66
65
|
|
67
66
|
def dump(exclude_columns = [])
|
68
|
-
|
67
|
+
ActiveRecord::Migration.say_with_time(table_name) do
|
68
|
+
seed_io.dump_data(
|
69
|
+
model.select(*dump_columns(exclude_columns)).map(&:attributes)
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def compare
|
75
|
+
Difference.extraction(self)
|
69
76
|
end
|
70
77
|
|
71
78
|
private
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module SeedGimmick
|
2
|
+
class Seed
|
3
|
+
class Difference
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :changes
|
7
|
+
|
8
|
+
module Flag
|
9
|
+
DATABASE = "-"
|
10
|
+
SEED = "+"
|
11
|
+
CHANGE = "<>"
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def extraction(seed)
|
16
|
+
pk = seed.model.primary_key
|
17
|
+
database = seed.model.all.each_with_object({}) {|row, obj|
|
18
|
+
obj[row.public_send(pk)] = row
|
19
|
+
}
|
20
|
+
seed_data = seed.seed_io.values.each_with_object({}) {|row, obj|
|
21
|
+
obj[row[pk].to_i] = row
|
22
|
+
}
|
23
|
+
diff = []
|
24
|
+
(database.keys - seed_data.keys).each do |id|
|
25
|
+
diff << [Flag::DATABASE, id, database.delete(id), nil, nil]
|
26
|
+
end
|
27
|
+
(seed_data.keys - database.keys).each do |id|
|
28
|
+
diff << [Flag::SEED, id, nil, seed_data.delete(id), nil]
|
29
|
+
end
|
30
|
+
database.each do |id, row|
|
31
|
+
s_attrs = seed_data[id].dup
|
32
|
+
s_attrs.delete(pk)
|
33
|
+
row.assign_attributes(s_attrs)
|
34
|
+
if row.changed?
|
35
|
+
changes = row.changes
|
36
|
+
changes.each {|k,(old,_)| row.public_send("#{k}=", old) }
|
37
|
+
diff << [Flag::CHANGE, id, row, seed_data.delete(id), changes]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
new(diff)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize(diff)
|
45
|
+
@changes = diff.sort_by(&:second).map {|d| Change.new(*d) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def changed?
|
49
|
+
!@changes.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
def each
|
53
|
+
@changes.each do |change|
|
54
|
+
yield change
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Change < Struct.new(:flag, :id, :database, :seed_data, :changes)
|
59
|
+
EXCLUDES = [
|
60
|
+
"created_at",
|
61
|
+
"updated_at"
|
62
|
+
].freeze
|
63
|
+
|
64
|
+
def change_values
|
65
|
+
changes.presence || changes_from_data
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def changes_from_data
|
70
|
+
if database
|
71
|
+
attrs = database.attributes
|
72
|
+
attrs.reject! {|k,_| EXCLUDES.include?(k) } || attrs
|
73
|
+
else
|
74
|
+
seed_data
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
data/lib/seed_gimmick/seed_io.rb
CHANGED
@@ -1,18 +1,42 @@
|
|
1
1
|
require "seed_gimmick/seed_io/base"
|
2
2
|
require "seed_gimmick/seed_io/yaml_file"
|
3
|
+
require "seed_gimmick/seed_io/csv_file"
|
3
4
|
|
4
5
|
module SeedGimmick
|
5
6
|
module SeedIO
|
7
|
+
module ExtType
|
8
|
+
# Collection map
|
9
|
+
# @note Require Symbol keys and String values.
|
10
|
+
CORRECTION = {
|
11
|
+
yml: "yaml",
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
# Corrected the fluctuation of extension.
|
15
|
+
# @param seed_file [String]
|
16
|
+
# @param seed_file [Pathname]
|
17
|
+
# @return [String]
|
18
|
+
def self.decision(path)
|
19
|
+
ext = Inflector.ext_type(path)
|
20
|
+
CORRECTION[ext].presence || ext.to_s
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
6
24
|
class << self
|
7
|
-
|
25
|
+
# Generate of IO class from seed_file path.
|
26
|
+
# @param seed_file [String]
|
27
|
+
# @param seed_file [Pathname]
|
28
|
+
# @return [SeedIO] Target IO class.
|
29
|
+
def factory(seed_file)
|
30
|
+
seed_file = Inflector.pathname(seed_file) || (raise ArgumentError)
|
8
31
|
const_get(io_class_name_for(seed_file), false).new(seed_file)
|
9
32
|
end
|
10
33
|
|
11
34
|
private
|
35
|
+
# Convert seed_file path to IO class name.
|
36
|
+
# @param seed_file [Pathname]
|
37
|
+
# @return [String] Target IO class name.
|
12
38
|
def io_class_name_for(seed_file)
|
13
|
-
|
14
|
-
ext = "yaml" if ext == "yml"
|
15
|
-
"%sFile" % ext.capitalize
|
39
|
+
"%sFile" % ExtType.decision(seed_file).capitalize
|
16
40
|
end
|
17
41
|
end
|
18
42
|
end
|
@@ -1,21 +1,52 @@
|
|
1
1
|
module SeedGimmick
|
2
2
|
module SeedIO
|
3
3
|
class Base
|
4
|
-
|
4
|
+
BASE_DATA_KEYS = %i(values metadata).freeze
|
5
5
|
|
6
|
+
attr_reader *[:seed_file].concat(BASE_DATA_KEYS)
|
7
|
+
|
8
|
+
# Data access callbacks.
|
9
|
+
BASE_DATA_KEYS.each do |key|
|
10
|
+
define_method "#{key}_with_load_if_not_data" do
|
11
|
+
public_send("#{key}_without_load_if_not_data") || load_data[key]
|
12
|
+
end
|
13
|
+
alias_method_chain key, :load_if_not_data
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param seed_file [Pathname]
|
6
17
|
def initialize(seed_file)
|
7
18
|
@seed_file = seed_file
|
8
19
|
end
|
9
20
|
|
21
|
+
# Data load from seed file.
|
22
|
+
# @note Need to return self or load data with Hash.
|
10
23
|
def load_data
|
11
24
|
raise NotImplementedError
|
12
25
|
end
|
13
26
|
|
27
|
+
# Data dump to seed file.
|
28
|
+
# @param array_of_hashes [Array<Hash>]
|
29
|
+
# @return [Integer] number of dump data.
|
14
30
|
def dump_data(array_of_hashes)
|
15
31
|
raise NotImplementedError
|
16
32
|
end
|
17
33
|
|
34
|
+
# Data access with bracket.
|
35
|
+
def [](key)
|
36
|
+
raise ArgumentError unless BASE_DATA_KEYS.include?(key)
|
37
|
+
public_send(key.to_sym)
|
38
|
+
end
|
39
|
+
|
18
40
|
private
|
41
|
+
# Update accessible data from Array of Hashes.
|
42
|
+
# @param array_of_hashes [Array<Hash>] loaded data.
|
43
|
+
def set_data(array_of_hashes)
|
44
|
+
@values = array_of_hashes
|
45
|
+
@metadata = {
|
46
|
+
rows: @values.size,
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
19
50
|
def write_raw(data)
|
20
51
|
seed_file.dirname.mkpath
|
21
52
|
seed_file.open("w") {|f| f.write data }
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SeedGimmick
|
2
|
+
module SeedIO
|
3
|
+
class CsvFile < Base
|
4
|
+
def self.raw_data(array_of_hashes)
|
5
|
+
columns = array_of_hashes.first.keys
|
6
|
+
array_of_hashes.map(&:values).unshift(columns).map(&:to_csv).join
|
7
|
+
end
|
8
|
+
|
9
|
+
def load_data
|
10
|
+
data = CSV.read(seed_file, headers: :first_row).map(&:to_hash) ||
|
11
|
+
(raise LoadFailed.new(seed_file))
|
12
|
+
set_data(data)
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def dump_data(array_of_hashes)
|
17
|
+
write_raw(self.class.raw_data(array_of_hashes))
|
18
|
+
array_of_hashes.size
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -1,24 +1,30 @@
|
|
1
1
|
module SeedGimmick
|
2
2
|
module SeedIO
|
3
3
|
class YamlFile < Base
|
4
|
+
class << self
|
5
|
+
def raw_data(array_of_hashes)
|
6
|
+
data = {}
|
7
|
+
array_of_hashes.each.with_index(1) do |row, i|
|
8
|
+
data[data_key(i)] = row
|
9
|
+
end
|
10
|
+
data.to_yaml
|
11
|
+
end
|
12
|
+
|
13
|
+
def data_key(id)
|
14
|
+
"data%d" % id
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
4
18
|
def load_data
|
5
19
|
data = YAML.load_file(seed_file) || (raise LoadFailed.new(seed_file))
|
6
|
-
data.values
|
20
|
+
set_data(data.values)
|
21
|
+
self
|
7
22
|
end
|
8
23
|
|
9
24
|
def dump_data(array_of_hashes)
|
10
|
-
|
11
|
-
array_of_hashes.
|
12
|
-
data[data_key(i)] = row
|
13
|
-
end
|
14
|
-
|
15
|
-
write_raw(data.to_yaml)
|
25
|
+
write_raw(self.class.raw_data(array_of_hashes))
|
26
|
+
array_of_hashes.size
|
16
27
|
end
|
17
|
-
|
18
|
-
private
|
19
|
-
def data_key(id)
|
20
|
-
"data%d" % id
|
21
|
-
end
|
22
28
|
end
|
23
29
|
end
|
24
30
|
end
|
data/lib/seed_gimmick/version.rb
CHANGED
data/lib/tasks/seed_gimmick.rake
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: seed_gimmick
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- i2bskn
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-02-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -100,8 +100,10 @@ files:
|
|
100
100
|
- lib/seed_gimmick/options.rb
|
101
101
|
- lib/seed_gimmick/railtie.rb
|
102
102
|
- lib/seed_gimmick/seed.rb
|
103
|
+
- lib/seed_gimmick/seed/difference.rb
|
103
104
|
- lib/seed_gimmick/seed_io.rb
|
104
105
|
- lib/seed_gimmick/seed_io/base.rb
|
106
|
+
- lib/seed_gimmick/seed_io/csv_file.rb
|
105
107
|
- lib/seed_gimmick/seed_io/yaml_file.rb
|
106
108
|
- lib/seed_gimmick/version.rb
|
107
109
|
- lib/tasks/seed_gimmick.rake
|