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 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