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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: aceba72060bab98a41a0d8427ed09c9a1bf83338
4
- data.tar.gz: 85ff18ec13553c2c3fd5fc6c36c3b3cafe0a6f66
3
+ metadata.gz: 9b3f2287df970cf60ef0a9c7db0cd8a11c6c14f0
4
+ data.tar.gz: 9d71742e3197bc0cb2e08705044760adf02863d4
5
5
  SHA512:
6
- metadata.gz: 48768e2ace6be4b85f2558ef2726e7ef867b9f956f4bfd78cacc665be3376ef1ac517f08cf01ee19768daf71def30a73fc9c6205a79f7c5658893e33662f5a2f
7
- data.tar.gz: 84d28b1cb0489fc9dd8aa2fea8ce52ba7141bca70be3cca103f38983263d1a9d454b6c46e5ad7a8c2f40b86d2163ac8d18b04781c0ac09cddab3b4d95b77c730
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
@@ -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
- class_name = relative_path(without_ext(seed_file)).to_s.classify
29
- self.class.model_class(class_name)
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 + pathname(model.model_name.collection).sub_ext(".#{format}")
57
+ seed_dir.join(pathname(model.model_name.collection).sub_ext(".#{format}"))
34
58
  end
35
59
 
36
60
  private
37
- def without_ext(path)
38
- path.dirname + path.basename(".*")
39
- end
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
 
@@ -1,11 +1,11 @@
1
1
  module SeedGimmick
2
2
  class Options
3
3
  VALID_OPTIONS_KEYS = %i(
4
- seed_dir
5
- tables
6
- models
7
- default_ext
8
- exclude_columns
4
+ seed_dir
5
+ tables
6
+ models
7
+ default_ext
8
+ exclude_columns
9
9
  ).freeze
10
10
 
11
11
  VALID_OPTIONS_KEYS.each do |key|
@@ -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(load_file.map {|hash| model.new(hash) })
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
- write_file(model.select(*dump_columns(exclude_columns)).map(&:attributes))
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
+
@@ -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
- def get(seed_file)
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
- ext = Inflector.pathname(seed_file).extname.sub(/\A\./, "")
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
- attr_reader :seed_file
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
- data = {}
11
- array_of_hashes.each.with_index(1) do |row, i|
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
@@ -1,3 +1,3 @@
1
1
  module SeedGimmick
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -20,4 +20,9 @@ namespace :seed_gimmick do
20
20
  task :seed_files do
21
21
  pp SeedGimmick::Seed.find.map {|seed| seed.seed_file.to_s }
22
22
  end
23
+
24
+ task :diff => :environment do
25
+ SeedGimmick.diff
26
+ end
23
27
  end
28
+
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.2
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-01-24 00:00:00.000000000 Z
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