seed_gimmick 0.0.2 → 0.0.3
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 +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
|