seed_builder 1.2.0 → 1.3.0
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/.github/workflows/_trunk_check.yml +1 -1
- data/.github/workflows/test.yml +77 -21
- data/.gitignore +1 -0
- data/.standard.yml +22 -0
- data/Appraisals +15 -0
- data/CHANGELOG.md +25 -4
- data/Gemfile.lock +58 -3
- data/README.md +82 -2
- data/bin/appraisals +99 -0
- data/bin/seed +36 -0
- data/lib/seed_builder/config.rb +12 -5
- data/lib/seed_builder/loader.rb +137 -26
- data/lib/seed_builder/railtie.rb +15 -0
- data/lib/seed_builder.rb +22 -1
- data/lib/tasks/seed.rake +22 -0
- data/seed_builder.gemspec +7 -4
- data/sig/seed_builder/config.rbs +22 -0
- data/sig/seed_builder/loader.rbs +8 -0
- data/sig/seed_builder/railtie.rbs +5 -0
- data/sig/seed_builder.rbs +7 -0
- data/spec/seed_builder/config_spec.rb +15 -0
- data/spec/seed_builder/loader_spec.rb +281 -16
- data/spec/seed_builder/railtie_spec.rb +118 -0
- data/spec/spec_helper.rb +3 -0
- metadata +68 -12
data/lib/seed_builder/loader.rb
CHANGED
|
@@ -1,47 +1,158 @@
|
|
|
1
1
|
module SeedBuilder
|
|
2
2
|
class Loader
|
|
3
3
|
def load_seed
|
|
4
|
-
|
|
4
|
+
prepare_active_record
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
Rails.logger.level = Logger::INFO
|
|
8
|
-
|
|
9
|
-
ActiveRecord::Base.connection.schema_cache.clear!
|
|
10
|
-
ActiveRecord::Base.descendants.each(&:reset_column_information)
|
|
11
|
-
|
|
12
|
-
default_seeds_rb = SeedBuilder.config.default_seeds_path
|
|
6
|
+
default_seeds_rb = SeedBuilder.config.default_seeds_full_path
|
|
13
7
|
if File.exist?(default_seeds_rb) && SeedBuilder.config.load_default_seeds?
|
|
14
|
-
|
|
8
|
+
started_at = Time.current
|
|
9
|
+
logger.tagged("seed") do
|
|
10
|
+
logger.info "== #{SeedBuilder.config.default_seeds_path}: seeding"
|
|
11
|
+
end
|
|
12
|
+
load default_seeds_rb
|
|
13
|
+
logger.tagged("seed") do
|
|
14
|
+
logger.info "== #{SeedBuilder.config.default_seeds_path}: seeded (#{(Time.current - started_at).round(4)}s)"
|
|
15
|
+
end
|
|
15
16
|
end
|
|
16
17
|
|
|
17
|
-
base_path = SeedBuilder.config.
|
|
18
|
+
base_path = SeedBuilder.config.seeds_full_path
|
|
18
19
|
if File.exist?(base_path) && SeedBuilder.config.load_seeds?
|
|
19
20
|
Dir[SeedBuilder.config.seeds_path_glob]
|
|
20
21
|
.map { |f| File.basename(f, ".*") }
|
|
21
22
|
.each do |seed_path|
|
|
22
|
-
|
|
23
|
+
load "#{base_path}/#{seed_path}.rb"
|
|
23
24
|
timestamp, klass_name = seed_path.scan(/^([0-9]+)_(.+)$/).first
|
|
24
25
|
next if klass_name.blank?
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
execute_seed_class(klass_name, timestamp)
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
logger.tagged("seed") do
|
|
31
|
+
logger.warn "Seed directory #{base_path} does not exist"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def load_seed_file(seed_name)
|
|
37
|
+
prepare_active_record
|
|
38
|
+
|
|
39
|
+
base_path = SeedBuilder.config.seeds_full_path
|
|
40
|
+
unless File.exist?(base_path)
|
|
41
|
+
logger.tagged("seed") do
|
|
42
|
+
logger.warn "Seed directory #{base_path} does not exist"
|
|
43
|
+
end
|
|
44
|
+
return
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Find the seed file by name
|
|
48
|
+
# Supports: "create_users", "20241206200111_create_users", "20241206200111"
|
|
49
|
+
result = find_seed_file(base_path, seed_name)
|
|
50
|
+
|
|
51
|
+
if result.nil?
|
|
52
|
+
logger.tagged("seed") do
|
|
53
|
+
logger.warn "Seed file '#{seed_name}' not found in #{base_path}"
|
|
54
|
+
end
|
|
55
|
+
return
|
|
56
|
+
elsif result == :ambiguous
|
|
57
|
+
matches = find_seed_file_matches(base_path, seed_name)
|
|
58
|
+
logger.tagged("seed") do
|
|
59
|
+
logger.warn "Multiple seed files match '#{seed_name}':"
|
|
60
|
+
matches.each do |file|
|
|
61
|
+
logger.warn " - #{File.basename(file, ".*")}"
|
|
39
62
|
end
|
|
63
|
+
logger.warn "Please be more specific using the full name with timestamp (e.g., 20241206200111_create_users)"
|
|
64
|
+
end
|
|
65
|
+
return
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
seed_file = result
|
|
69
|
+
|
|
70
|
+
seed_path = File.basename(seed_file, ".*")
|
|
71
|
+
timestamp, klass_name = seed_path.scan(/^([0-9]+)_(.+)$/).first
|
|
72
|
+
|
|
73
|
+
if klass_name.blank?
|
|
74
|
+
logger.tagged("seed") do
|
|
75
|
+
logger.warn "Invalid seed file format: #{seed_path}. Expected format: TIMESTAMP_CLASS_NAME.rb"
|
|
76
|
+
end
|
|
77
|
+
return
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
load seed_file
|
|
81
|
+
execute_seed_class(klass_name, timestamp, seed_name)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def logger
|
|
87
|
+
SeedBuilder.logger
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def prepare_active_record
|
|
91
|
+
ActiveRecord::Base.connection.schema_cache.clear!
|
|
92
|
+
ActiveRecord::Base.descendants.each(&:reset_column_information)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def execute_seed_class(klass_name, timestamp, seed_name = nil)
|
|
96
|
+
klass = klass_name.camelize.constantize
|
|
97
|
+
klass_name_display = klass.name
|
|
98
|
+
logger.tagged("seed") do
|
|
99
|
+
logger.info "== #{timestamp} #{klass_name_display}: seeding"
|
|
100
|
+
end
|
|
101
|
+
started_at = Time.current
|
|
102
|
+
seed_instance = klass.new
|
|
103
|
+
if seed_instance.respond_to?(:change)
|
|
104
|
+
seed_instance.change
|
|
105
|
+
else
|
|
106
|
+
raise "Seed #{klass_name_display} does not respond to :change"
|
|
107
|
+
end
|
|
108
|
+
logger.tagged("seed") do
|
|
109
|
+
logger.info "== #{timestamp} #{klass_name_display}: seeded (#{(Time.current - started_at).round(4)}s)"
|
|
110
|
+
end
|
|
111
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
112
|
+
klass_name_display = defined?(klass) ? klass.name : (klass_name&.camelize || seed_name || klass_name)
|
|
113
|
+
logger.tagged("seed") do
|
|
114
|
+
logger.error "Seeding #{klass_name_display} failed: #{e.record.errors.full_messages}"
|
|
115
|
+
end
|
|
116
|
+
raise e
|
|
117
|
+
rescue => e
|
|
118
|
+
logger.tagged("seed") do
|
|
119
|
+
logger.error "Error loading seed: #{e.message}"
|
|
120
|
+
end
|
|
121
|
+
raise e
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def find_seed_file(base_path, seed_name)
|
|
125
|
+
# Try exact match first
|
|
126
|
+
exact_match = File.join(base_path, "#{seed_name}.rb")
|
|
127
|
+
return exact_match if File.exist?(exact_match)
|
|
128
|
+
|
|
129
|
+
# Collect all matching files
|
|
130
|
+
matches = find_seed_file_matches(base_path, seed_name)
|
|
131
|
+
|
|
132
|
+
# Return appropriate result based on number of matches
|
|
133
|
+
case matches.length
|
|
134
|
+
when 0
|
|
135
|
+
nil
|
|
136
|
+
when 1
|
|
137
|
+
matches.first
|
|
40
138
|
else
|
|
41
|
-
|
|
139
|
+
:ambiguous
|
|
42
140
|
end
|
|
141
|
+
end
|
|
43
142
|
|
|
44
|
-
|
|
143
|
+
def find_seed_file_matches(base_path, seed_name)
|
|
144
|
+
matches = []
|
|
145
|
+
Dir[SeedBuilder.config.seeds_path_glob].each do |file|
|
|
146
|
+
basename = File.basename(file, ".*")
|
|
147
|
+
# Match by class name (e.g., "create_users")
|
|
148
|
+
if basename.end_with?("_#{seed_name}") || basename == seed_name
|
|
149
|
+
matches << file
|
|
150
|
+
# Match by timestamp (e.g., "20241206200111")
|
|
151
|
+
elsif basename.start_with?("#{seed_name}_")
|
|
152
|
+
matches << file
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
matches
|
|
45
156
|
end
|
|
46
157
|
end
|
|
47
158
|
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module SeedBuilder
|
|
2
|
+
class Railtie < ::Rails::Railtie
|
|
3
|
+
config.to_prepare do
|
|
4
|
+
if SeedBuilder.config.use_seed_loader?
|
|
5
|
+
# Patch Rails.application.load_seed to use our custom loader
|
|
6
|
+
Rails.application.class.class_eval do
|
|
7
|
+
define_method(:load_seed) do
|
|
8
|
+
loader = SeedBuilder::Loader.new
|
|
9
|
+
loader.load_seed
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/seed_builder.rb
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
require "active_support/tagged_logging"
|
|
1
2
|
require "seed_builder/config"
|
|
2
3
|
require "seed_builder/loader"
|
|
3
4
|
|
|
4
5
|
module SeedBuilder
|
|
5
|
-
VERSION = "1.
|
|
6
|
+
VERSION = "1.3.0".freeze
|
|
6
7
|
|
|
7
8
|
module_function
|
|
8
9
|
|
|
@@ -13,6 +14,26 @@ module SeedBuilder
|
|
|
13
14
|
def configure
|
|
14
15
|
yield config
|
|
15
16
|
end
|
|
17
|
+
|
|
18
|
+
def logger
|
|
19
|
+
# Use configured logger if set
|
|
20
|
+
base_logger = if config.logger
|
|
21
|
+
config.logger
|
|
22
|
+
elsif defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
23
|
+
Rails.logger
|
|
24
|
+
else
|
|
25
|
+
Logger.new($stdout)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# If the logger already supports tagging (e.g., Rails.logger is already TaggedLogging),
|
|
29
|
+
# use it directly to avoid double-wrapping and ensure tags propagate properly
|
|
30
|
+
if base_logger.respond_to?(:tagged)
|
|
31
|
+
base_logger
|
|
32
|
+
else
|
|
33
|
+
ActiveSupport::TaggedLogging.new(base_logger)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
16
36
|
end
|
|
17
37
|
|
|
18
38
|
require "rails_config"
|
|
39
|
+
require "seed_builder/railtie" if defined?(Rails::Railtie)
|
data/lib/tasks/seed.rake
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
namespace :seed do
|
|
2
|
+
desc "Run a specific seed file by name"
|
|
3
|
+
task :run, [:seed_name] => :environment do |_task, args|
|
|
4
|
+
if args[:seed_name].nil? || args[:seed_name].empty?
|
|
5
|
+
puts "Usage: bin/rails seed:run[SEED_NAME]"
|
|
6
|
+
puts ""
|
|
7
|
+
puts "Run a specific seed file by name."
|
|
8
|
+
puts ""
|
|
9
|
+
puts "Examples:"
|
|
10
|
+
puts " bin/rails seed:run[create_users]"
|
|
11
|
+
puts " bin/rails seed:run[20241206200111_create_users]"
|
|
12
|
+
puts " bin/rails seed:run[20241206200111]"
|
|
13
|
+
puts ""
|
|
14
|
+
puts "Note: If multiple seed files match the name, use the full name with"
|
|
15
|
+
puts " timestamp to avoid ambiguity (e.g., 20241206200111_create_users)."
|
|
16
|
+
exit 1
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
loader = SeedBuilder::Loader.new
|
|
20
|
+
loader.load_seed_file(args[:seed_name])
|
|
21
|
+
end
|
|
22
|
+
end
|
data/seed_builder.gemspec
CHANGED
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |gem|
|
|
|
11
11
|
gem.platform = Gem::Platform::RUBY
|
|
12
12
|
|
|
13
13
|
gem.authors = ["Andrei Makarov"]
|
|
14
|
-
gem.email = ["
|
|
14
|
+
gem.email = ["contact@kiskolabs.com"]
|
|
15
15
|
gem.homepage = repository_url
|
|
16
16
|
gem.summary = "Seed builder with loader and generator"
|
|
17
17
|
gem.description = "Extension for ActiveRecord to organize seeds in a directory and generate them as migrations"
|
|
@@ -23,15 +23,15 @@ Gem::Specification.new do |gem|
|
|
|
23
23
|
"rubygems_mfa_required" => "true"
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
gem.files = `git ls-files`.split("\n")
|
|
26
|
+
gem.files = `git ls-files`.split("\n").reject { |f| f.match?(%r{^(test|spec|features)/}) }
|
|
27
27
|
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
28
28
|
gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
|
29
29
|
|
|
30
30
|
gem.required_ruby_version = ">= 3"
|
|
31
31
|
gem.require_paths = ["lib"]
|
|
32
32
|
|
|
33
|
-
gem.add_dependency "rails", ">= 6", "< 8.
|
|
34
|
-
gem.add_dependency "activerecord", ">= 6", "< 8.
|
|
33
|
+
gem.add_dependency "rails", ">= 6.1", "< 8.2"
|
|
34
|
+
gem.add_dependency "activerecord", ">= 6.1", "< 8.2"
|
|
35
35
|
|
|
36
36
|
gem.add_development_dependency "bundler", "~> 2"
|
|
37
37
|
gem.add_development_dependency "rspec", "~> 3"
|
|
@@ -39,4 +39,7 @@ Gem::Specification.new do |gem|
|
|
|
39
39
|
gem.add_development_dependency "simplecov", "~> 0.21"
|
|
40
40
|
gem.add_development_dependency "simplecov-cobertura", "~> 2"
|
|
41
41
|
gem.add_development_dependency "sqlite3", "~> 2.4"
|
|
42
|
+
gem.add_development_dependency "standard", "~> 1.0"
|
|
43
|
+
gem.add_development_dependency "rbs", "~> 3.0"
|
|
44
|
+
gem.add_development_dependency "appraisal", "~> 2.4"
|
|
42
45
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module SeedBuilder
|
|
2
|
+
class Config
|
|
3
|
+
def default_seeds_path: () -> String
|
|
4
|
+
def default_seeds_path=: (String) -> String
|
|
5
|
+
def default_seeds_full_path: () -> Pathname
|
|
6
|
+
def default_seeds_full_path=: (Pathname) -> Pathname
|
|
7
|
+
def seeds_path: () -> String
|
|
8
|
+
def seeds_path=: (String) -> String
|
|
9
|
+
def seeds_full_path: () -> Pathname
|
|
10
|
+
def seeds_full_path=: (Pathname) -> Pathname
|
|
11
|
+
def seeds_path_glob: () -> String
|
|
12
|
+
def load_default_seeds?: () -> bool
|
|
13
|
+
def load_default_seeds=: (bool) -> bool
|
|
14
|
+
def load_seeds?: () -> bool
|
|
15
|
+
def load_seeds=: (bool) -> bool
|
|
16
|
+
def generate_spec?: () -> bool
|
|
17
|
+
def generate_spec=: (bool) -> bool
|
|
18
|
+
def use_seed_loader?: () -> bool
|
|
19
|
+
def use_seed_loader=: (bool) -> bool
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
require "spec_helper"
|
|
2
2
|
|
|
3
|
+
# Define Rails module if not already defined
|
|
4
|
+
unless defined?(Rails)
|
|
5
|
+
module Rails
|
|
6
|
+
class << self
|
|
7
|
+
attr_accessor :root, :env, :logger
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
3
12
|
describe SeedBuilder::Config do
|
|
4
13
|
subject(:config) { described_class.new }
|
|
5
14
|
|
|
@@ -30,4 +39,10 @@ describe SeedBuilder::Config do
|
|
|
30
39
|
it "has default seeds_path_glob" do
|
|
31
40
|
expect(config.seeds_path_glob).to eq Rails.root.join("db/seeds/*.rb").to_s
|
|
32
41
|
end
|
|
42
|
+
|
|
43
|
+
it "allows setting a custom logger" do
|
|
44
|
+
custom_logger = Logger.new($stdout)
|
|
45
|
+
config.logger = custom_logger
|
|
46
|
+
expect(config.logger).to eq custom_logger
|
|
47
|
+
end
|
|
33
48
|
end
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
require "spec_helper"
|
|
2
2
|
|
|
3
|
+
# Define Rails module if not already defined
|
|
4
|
+
unless defined?(Rails)
|
|
5
|
+
module Rails
|
|
6
|
+
class << self
|
|
7
|
+
attr_accessor :root, :env, :logger
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
3
12
|
class SeedBuilderUser < ActiveRecord::Base; end
|
|
4
13
|
|
|
5
14
|
class SeedUser < SeedBuilderUser; end
|
|
@@ -7,28 +16,284 @@ class SeedUser < SeedBuilderUser; end
|
|
|
7
16
|
describe SeedBuilder::Loader do
|
|
8
17
|
subject(:loader) { described_class.new }
|
|
9
18
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
let(:logger) { Logger.new($stdout) }
|
|
20
|
+
let(:seeds_path) { File.expand_path("../../fixtures/seeds", __FILE__) }
|
|
21
|
+
let(:default_seeds_path) { File.expand_path("../../fixtures/seeds.rb", __FILE__) }
|
|
22
|
+
|
|
23
|
+
before do
|
|
24
|
+
allow(Rails).to receive(:root).and_return(rails_root)
|
|
25
|
+
allow(Rails).to receive(:env).and_return(rails_env)
|
|
26
|
+
allow(Rails).to receive(:logger).and_return(logger)
|
|
27
|
+
allow(Rails).to receive(:logger=)
|
|
28
|
+
# Reset SeedBuilder logger to pick up the stubbed Rails.logger
|
|
29
|
+
SeedBuilder.instance_variable_set(:@logger, nil) if SeedBuilder.instance_variable_defined?(:@logger)
|
|
14
30
|
|
|
31
|
+
SeedBuilder.configure do |config|
|
|
32
|
+
config.seeds_full_path = seeds_path
|
|
33
|
+
config.default_seeds_full_path = default_seeds_path
|
|
34
|
+
config.load_default_seeds = true
|
|
35
|
+
config.load_seeds = true
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe "#load_seed" do
|
|
15
40
|
before do
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
41
|
+
SeedUser.delete_all
|
|
42
|
+
SeedBuilderUser.delete_all
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context "when loading default seeds" do
|
|
46
|
+
let(:load_default_seeds) { true }
|
|
47
|
+
let(:load_seeds) { true }
|
|
20
48
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
49
|
+
before do
|
|
50
|
+
SeedBuilder.configure do |config|
|
|
51
|
+
config.load_default_seeds = load_default_seeds
|
|
52
|
+
config.load_seeds = load_seeds
|
|
53
|
+
end
|
|
54
|
+
loader.load_seed
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "loads the seeds" do
|
|
58
|
+
expect(SeedUser.count).to eq 1
|
|
59
|
+
expect(SeedBuilderUser.count).to eq 1
|
|
26
60
|
end
|
|
27
|
-
loader.load_seed
|
|
28
61
|
end
|
|
29
62
|
|
|
30
|
-
|
|
31
|
-
|
|
63
|
+
context "when seeds directory does not exist" do
|
|
64
|
+
let(:log_output) { StringIO.new }
|
|
65
|
+
let(:test_logger) { Logger.new(log_output) }
|
|
66
|
+
|
|
67
|
+
before do
|
|
68
|
+
allow(Rails).to receive(:logger).and_return(test_logger)
|
|
69
|
+
# Reset SeedBuilder logger to pick up the stubbed Rails.logger
|
|
70
|
+
SeedBuilder.instance_variable_set(:@logger, nil) if SeedBuilder.instance_variable_defined?(:@logger)
|
|
71
|
+
SeedBuilder.configure do |config|
|
|
72
|
+
config.seeds_full_path = "/non/existent/path"
|
|
73
|
+
config.load_default_seeds = false
|
|
74
|
+
config.load_seeds = true
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "outputs a message" do
|
|
79
|
+
loader.load_seed
|
|
80
|
+
expect(log_output.string).to match(/Seed directory.*does not exist/)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
describe "#load_seed_file" do
|
|
86
|
+
context "when loading by class name" do
|
|
87
|
+
before do
|
|
88
|
+
SeedBuilderUser.delete_all
|
|
89
|
+
loader.load_seed_file("create_users")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it "loads the specific seed file" do
|
|
93
|
+
expect(SeedBuilderUser.count).to eq 1
|
|
94
|
+
expect(SeedBuilderUser.first.email).to eq "test@example.com"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
context "when loading by full name" do
|
|
99
|
+
before do
|
|
100
|
+
SeedBuilderUser.delete_all
|
|
101
|
+
loader.load_seed_file("20241206200111_create_users")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "loads the specific seed file" do
|
|
105
|
+
expect(SeedBuilderUser.count).to eq 1
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
context "when loading by timestamp" do
|
|
110
|
+
before do
|
|
111
|
+
SeedBuilderUser.delete_all
|
|
112
|
+
loader.load_seed_file("20241206200111")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "loads the specific seed file" do
|
|
116
|
+
expect(SeedBuilderUser.count).to eq 1
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
context "when seed file does not exist" do
|
|
121
|
+
let(:log_output) { StringIO.new }
|
|
122
|
+
let(:test_logger) { Logger.new(log_output) }
|
|
123
|
+
|
|
124
|
+
before do
|
|
125
|
+
allow(Rails).to receive(:logger).and_return(test_logger)
|
|
126
|
+
# Reset SeedBuilder logger to pick up the stubbed Rails.logger
|
|
127
|
+
SeedBuilder.instance_variable_set(:@logger, nil) if SeedBuilder.instance_variable_defined?(:@logger)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it "outputs an error message" do
|
|
131
|
+
loader.load_seed_file("nonexistent_seed")
|
|
132
|
+
expect(log_output.string).to match(/Seed file 'nonexistent_seed' not found/)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "does not raise an error" do
|
|
136
|
+
expect { loader.load_seed_file("nonexistent_seed") }.not_to raise_error
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
context "when multiple seed files match the name" do
|
|
141
|
+
let(:seed_path_1) { File.expand_path("../../tmp/rspec/db/seeds/20241206200111_create_users.rb", __FILE__) }
|
|
142
|
+
let(:seed_path_2) { File.expand_path("../../tmp/rspec/db/seeds/20241206200112_create_users.rb", __FILE__) }
|
|
143
|
+
let(:log_output) { StringIO.new }
|
|
144
|
+
let(:test_logger) { Logger.new(log_output) }
|
|
145
|
+
|
|
146
|
+
before do
|
|
147
|
+
allow(Rails).to receive(:logger).and_return(test_logger)
|
|
148
|
+
# Reset SeedBuilder logger to pick up the stubbed Rails.logger
|
|
149
|
+
SeedBuilder.instance_variable_set(:@logger, nil) if SeedBuilder.instance_variable_defined?(:@logger)
|
|
150
|
+
FileUtils.mkdir_p(File.dirname(seed_path_1))
|
|
151
|
+
File.write(seed_path_1, "class CreateUsers; def change; end; end")
|
|
152
|
+
File.write(seed_path_2, "class CreateUsers; def change; end; end")
|
|
153
|
+
SeedBuilder.configure do |config|
|
|
154
|
+
config.seeds_full_path = File.dirname(seed_path_1)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
after do
|
|
159
|
+
File.delete(seed_path_1) if File.exist?(seed_path_1)
|
|
160
|
+
File.delete(seed_path_2) if File.exist?(seed_path_2)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it "outputs an error message listing all matching files" do
|
|
164
|
+
loader.load_seed_file("create_users")
|
|
165
|
+
expect(log_output.string).to match(
|
|
166
|
+
/Multiple seed files match 'create_users':.*20241206200111_create_users.*20241206200112_create_users.*Please be more specific/m
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it "does not raise an error" do
|
|
171
|
+
expect { loader.load_seed_file("create_users") }.not_to raise_error
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it "works when using the full name with timestamp" do
|
|
175
|
+
SeedBuilderUser.delete_all
|
|
176
|
+
loader.load_seed_file("20241206200111_create_users")
|
|
177
|
+
expect(log_output.string).not_to match(/Multiple seed files/)
|
|
178
|
+
expect(SeedBuilderUser.count).to eq 0 # The seed doesn't actually create anything, just checks it runs
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
context "when seeds directory does not exist" do
|
|
183
|
+
let(:log_output) { StringIO.new }
|
|
184
|
+
let(:test_logger) { Logger.new(log_output) }
|
|
185
|
+
|
|
186
|
+
before do
|
|
187
|
+
allow(Rails).to receive(:logger).and_return(test_logger)
|
|
188
|
+
# Reset SeedBuilder logger to pick up the stubbed Rails.logger
|
|
189
|
+
SeedBuilder.instance_variable_set(:@logger, nil) if SeedBuilder.instance_variable_defined?(:@logger)
|
|
190
|
+
SeedBuilder.configure do |config|
|
|
191
|
+
config.seeds_full_path = "/non/existent/path"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it "outputs an error message" do
|
|
196
|
+
loader.load_seed_file("create_users")
|
|
197
|
+
expect(log_output.string).to match(/Seed directory.*does not exist/)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it "does not raise an error" do
|
|
201
|
+
expect { loader.load_seed_file("create_users") }.not_to raise_error
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
context "when seed file has invalid format" do
|
|
206
|
+
let(:invalid_seed_path) { File.expand_path("../../tmp/rspec/invalid_seed.rb", __FILE__) }
|
|
207
|
+
let(:log_output) { StringIO.new }
|
|
208
|
+
let(:test_logger) { Logger.new(log_output) }
|
|
209
|
+
|
|
210
|
+
before do
|
|
211
|
+
allow(Rails).to receive(:logger).and_return(test_logger)
|
|
212
|
+
# Reset SeedBuilder logger to pick up the stubbed Rails.logger
|
|
213
|
+
SeedBuilder.instance_variable_set(:@logger, nil) if SeedBuilder.instance_variable_defined?(:@logger)
|
|
214
|
+
FileUtils.mkdir_p(File.dirname(invalid_seed_path))
|
|
215
|
+
File.write(invalid_seed_path, "class InvalidSeed; end")
|
|
216
|
+
SeedBuilder.configure do |config|
|
|
217
|
+
config.seeds_full_path = File.dirname(invalid_seed_path)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
after do
|
|
222
|
+
File.delete(invalid_seed_path) if File.exist?(invalid_seed_path)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it "outputs an error message" do
|
|
226
|
+
loader.load_seed_file("invalid_seed")
|
|
227
|
+
expect(log_output.string).to match(/Invalid seed file format/)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
it "does not raise an error" do
|
|
231
|
+
expect { loader.load_seed_file("invalid_seed") }.not_to raise_error
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
context "when seed class does not respond to change" do
|
|
236
|
+
let(:invalid_seed_path) { File.expand_path("../../tmp/rspec/db/seeds/20241206200112_no_change.rb", __FILE__) }
|
|
237
|
+
|
|
238
|
+
before do
|
|
239
|
+
FileUtils.mkdir_p(File.dirname(invalid_seed_path))
|
|
240
|
+
File.write(invalid_seed_path, <<~RUBY)
|
|
241
|
+
class NoChange
|
|
242
|
+
# Does not have change method
|
|
243
|
+
end
|
|
244
|
+
RUBY
|
|
245
|
+
SeedBuilder.configure do |config|
|
|
246
|
+
config.seeds_full_path = File.dirname(invalid_seed_path)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
after do
|
|
251
|
+
File.delete(invalid_seed_path) if File.exist?(invalid_seed_path)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it "raises an error" do
|
|
255
|
+
expect { loader.load_seed_file("no_change") }.to raise_error(/does not respond to :change/)
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
context "when seed raises RecordInvalid" do
|
|
260
|
+
let(:invalid_seed_path) { File.expand_path("../../tmp/rspec/db/seeds/20241206200113_invalid_record.rb", __FILE__) }
|
|
261
|
+
let(:log_output) { StringIO.new }
|
|
262
|
+
let(:test_logger) { Logger.new(log_output) }
|
|
263
|
+
|
|
264
|
+
before do
|
|
265
|
+
allow(Rails).to receive(:logger).and_return(test_logger)
|
|
266
|
+
# Reset SeedBuilder logger to pick up the stubbed Rails.logger
|
|
267
|
+
SeedBuilder.instance_variable_set(:@logger, nil) if SeedBuilder.instance_variable_defined?(:@logger)
|
|
268
|
+
# Add a validation to SeedBuilderUser to make save! fail
|
|
269
|
+
SeedBuilderUser.class_eval do
|
|
270
|
+
validates :email, presence: true
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
FileUtils.mkdir_p(File.dirname(invalid_seed_path))
|
|
274
|
+
File.write(invalid_seed_path, <<~RUBY)
|
|
275
|
+
class InvalidRecord
|
|
276
|
+
def change
|
|
277
|
+
user = SeedBuilderUser.new # Missing required email
|
|
278
|
+
user.save! # This will fail validation
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
RUBY
|
|
282
|
+
SeedBuilder.configure do |config|
|
|
283
|
+
config.seeds_full_path = File.dirname(invalid_seed_path)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
after do
|
|
288
|
+
File.delete(invalid_seed_path) if File.exist?(invalid_seed_path)
|
|
289
|
+
# Remove the validation
|
|
290
|
+
SeedBuilderUser.reset_column_information
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
it "outputs error message and re-raises" do
|
|
294
|
+
expect { loader.load_seed_file("invalid_record") }.to raise_error(ActiveRecord::RecordInvalid)
|
|
295
|
+
expect(log_output.string).to match(/Seeding.*failed/)
|
|
296
|
+
end
|
|
32
297
|
end
|
|
33
298
|
end
|
|
34
299
|
end
|