seed_builder 1.2.1 → 1.3.1

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
  SHA256:
3
- metadata.gz: a607759220852d1d00fcfe17bce2c9d419446a3bfa066c446e85121a11e70f17
4
- data.tar.gz: d02f1a778d953c5a48a6862a64b9bf4dd2dec73118c066865aec91399ff6b2e6
3
+ metadata.gz: 6e17c7224d3dede956d465e3d7c81d45fadfae50481fad4f68fc0d5aee8d2936
4
+ data.tar.gz: a0bea568dc1e39af05473f5614ecd7f7337328ab191ce86de7f7ae1632e51615
5
5
  SHA512:
6
- metadata.gz: 9c731f5ed5c7644f27a475122aedc0f48c32d17c27f3034473fc6b6a127808b25c5d016275ccdf4d36743414d7928706fad046e9d1704a6dc08de5a2dd8475c0
7
- data.tar.gz: 90f6e27dd1bd7022f940261181a1308fe3ff8a2efd42b932914dd44419d438f2abafaf33650a68cd9b6ab8386235041add92ecc1126b6b74d39294139304adf3
6
+ metadata.gz: 7cd7abeb732f00b1a226caa1cc2793c9bb403e416ca85886ee72232b70eac6d33c81f3b1e7df88a3dc8eeecc798c0e0497254e3a5e9ab85eb3aa2d97199dc3b1
7
+ data.tar.gz: 4ea91653aae9ab4216311fbf9da32a649595ced5e128632ed095e7dcd8c8ff1d0b19a79ab0e7fe23354abb2740f9864886fc90e3bc92d970f0fead5216e5ceb3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.3.1 (2026-11-03)
4
+
5
+ - Use updated seed paths after configuration has already been read.
6
+ - Load the application's Rails environment when running the seed command.
7
+ - Exclude development tools and audit records from the installed gem.
8
+ - Cancel release preparation when confirmation input is closed.
9
+ - Run timestamped seed files in numeric timestamp order.
10
+ - Fail `bin/seed` and `bin/rails seed:run` when the seed is missing, ambiguous, invalid, or outside the seeds directory.
11
+ - Reject an invalid or missing seed name before opening a database connection.
12
+ - Warn that the seed directory is missing only when directory loading is enabled.
13
+ - Keep `bin/rails db:seed` on the Rails seed file when `SeedBuilder.config.use_seed_loader` is false.
14
+
15
+ ## 1.3.0 (2025-11-04)
16
+
17
+ - Replace `puts` statements with `Rails.logger` for proper logging integration
18
+ - Add tagged logging with `seed` tag for all seed-related log messages
19
+ - Add `logger` configuration option to allow custom logger setup via `SeedBuilder.config.logger = custom_logger`
20
+ - Fix seed file loading to use `load` instead of `require` to ensure files are re-executed on each seed run
21
+ - Logger automatically detects if `Rails.logger` already supports tagging to avoid double-wrapping
22
+ - Add ability to run specific seed files on demand using `bin/rails seed:run[SEED_NAME]`
23
+ - Add `load_seed_file` method to `SeedBuilder::Loader` for loading individual seeds
24
+ - Add ambiguous match detection: when multiple seed files match the same name, the loader lists the matches and the command needs the full name with the timestamp
25
+ - Patch `Rails.application.load_seed` to use SeedBuilder loader via Railtie integration
26
+
3
27
  ## 1.2.1
4
28
 
5
29
  - Fix seed files to be loaded as `.rb` files
data/README.md CHANGED
@@ -1,10 +1,8 @@
1
1
  # seed_builder.rb
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/seed_builder.svg)](https://badge.fury.io/rb/seed_builder) [![Test Status](https://github.com/amkisko/seed_builder.rb/actions/workflows/test.yml/badge.svg)](https://github.com/amkisko/seed_builder.rb/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/amkisko/seed_builder.rb/graph/badge.svg?token=57R6OHOJDQ)](https://codecov.io/gh/amkisko/seed_builder.rb)
3
+ [![Gem Version](https://badge.fury.io/rb/seed_builder.svg)](https://badge.fury.io/rb/seed_builder) [![Test Status](https://github.com/amkisko/seed_builder.rb/actions/workflows/test.yml/badge.svg)](https://github.com/amkisko/seed_builder.rb/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/amkisko/seed_builder.rb/graph/badge.svg?token=57R6OHOJDQ)](https://app.codecov.io/github/amkisko/seed_builder.rb)
4
4
 
5
- Seed builder for ActiveRecord. Includes seeds loader and generator.
6
-
7
- Sponsored by [Kisko Labs](https://www.kiskolabs.com).
5
+ Seed loader and generator for ActiveRecord.
8
6
 
9
7
  ## Installation
10
8
 
@@ -30,10 +28,31 @@ gem "seed_builder"
30
28
 
31
29
  ### Run seeds
32
30
 
31
+ Run all seeds:
32
+
33
33
  ```sh
34
34
  bin/rails db:seed
35
35
  ```
36
36
 
37
+ Or in code:
38
+
39
+ ```ruby
40
+ Rails.application.load_seed
41
+ ```
42
+
43
+ Run a specific seed file:
44
+
45
+ ```sh
46
+ bin/rails seed:run[create_users]
47
+ ```
48
+
49
+ You can specify the seed name in different formats:
50
+ - Class name: `create_users` (matches `20241206200111_create_users.rb`)
51
+ - Full name: `20241206200111_create_users`
52
+ - Timestamp: `20241206200111`
53
+
54
+ If several seed files match the same name, the command writes the matching names to the log and exits with status 1. Run it again with the timestamped name, for example `20241206200111_create_users`. `bin/seed` uses the same names and the same failed status.
55
+
37
56
  ### Generate seed file
38
57
 
39
58
  ```sh
@@ -46,7 +65,7 @@ bin/rails g seed create_users
46
65
 
47
66
  By default seed file `db/seeds.rb` is loaded.
48
67
 
49
- To turn off loading default seeds.rb file:
68
+ To stop loading `db/seeds.rb`:
50
69
 
51
70
  ```ruby
52
71
  SeedBuilder.config.load_default_seeds = false
@@ -54,7 +73,7 @@ SeedBuilder.config.load_default_seeds = false
54
73
 
55
74
  ### Set seed directory
56
75
 
57
- Absolute path will be resolved by using `Rails.root`.
76
+ A relative `seeds_path` is joined to `Rails.root`. An absolute `seeds_path` is used as given. Set `seeds_full_path` to select the directory directly.
58
77
 
59
78
  ```ruby
60
79
  SeedBuilder.config.seeds_path = "db/seeds"
@@ -72,45 +91,123 @@ SeedBuilder.config.generate_spec = false
72
91
  SeedBuilder.config.use_seed_loader = false
73
92
  ```
74
93
 
94
+ ### Set custom logger
95
+
96
+ By default, SeedBuilder uses `Rails.logger` (or falls back to `Logger.new($stdout)` if Rails is not available). All seed-related log messages are tagged with `[seed]`.
97
+
98
+ To use a custom logger:
99
+
100
+ ```ruby
101
+ SeedBuilder.config.logger = MyCustomLogger.new
102
+ ```
103
+
104
+ SeedBuilder wraps that logger with `ActiveSupport::TaggedLogging` when the logger has no `tagged` method, so the `[seed]` tag is present.
105
+
75
106
  ## Specification checklist
76
107
 
77
108
  - [x] User can generate seed file under `db/seeds` directory with common format
78
109
  - [x] User can generate seed file with test script included
79
110
  - [x] User can run all seeds
80
- - [ ] User can run specific seed file
111
+ - [x] User can run specific seed file
112
+
113
+ ## Limitations
114
+
115
+ - `bin/rails db:seed` calls `ActiveRecord::Tasks::DatabaseTasks.load_seed`, which calls `Rails.application.load_seed`. The railtie replaces `load_seed` while `use_seed_loader` is on. Set `use_seed_loader = false` in an initializer, before the application finishes booting, to keep the Rails seed file.
116
+ - The ActiveRecord migration generator writes the seed files.
117
+ - A seed run is not reversed. Write seed files so they can run more than once, for example by checking that a record is already present.
118
+ - A seed file is not a migration.
119
+
120
+ ## Development
121
+
122
+ ```bash
123
+ # Install dependencies
124
+ bundle install
125
+ bundle exec appraisal generate
81
126
 
82
- ## Limitations & explanations
127
+ # Run tests for current Rails version
128
+ bundle exec polyrun parallel-rspec --workers 5 --merge-failures
83
129
 
84
- - Gem patches `ActiveRecord::Tasks::DatabaseTasks.seed_loader` to have custom loader
85
- - ActiveRecord migrations generator is used to generate seed files
86
- - Seeded data is not reversible, there is no point to implement it as logic can be complex and operations on data might lead to irreversible changes
87
- - Seed file is not a migration, although as good practice is to keep it idempotent, e.g. by checking uniqueness of seeded records
130
+ # Run tests for the configured Rails versions (7.2 and 8.1)
131
+ bin/appraisals
132
+
133
+ # Run tests for Rails 7.2
134
+ bin/appraisals rails72
135
+
136
+ # Run tests for Rails 7.2 and Rails 8.1 on Ruby 3.4
137
+ bin/appraisals rails72 rails8ruby34
138
+
139
+ # Or use appraisal directly
140
+ bundle exec appraisal rails72 rspec
141
+
142
+ # Run linter
143
+ bundle exec standardrb --fix
144
+
145
+ # Check type signatures
146
+ bundle exec rbs validate
147
+ ```
148
+
149
+ ### Development: Using from Local Repository
150
+
151
+ When developing the gem or testing changes in your application, you can point your Gemfile to a local path:
152
+
153
+ ```ruby
154
+ # In your application's Gemfile
155
+ gem "seed_builder", path: "../seed_builder.rb"
156
+ ```
157
+
158
+ Then run:
159
+
160
+ ```bash
161
+ bundle install
162
+ ```
163
+
164
+ With `path:` in the Gemfile, Bundler loads this checkout. Edits in the checkout are what the application runs, without a rebuild or reinstall.
88
165
 
89
166
  ## Contributing
90
167
 
91
168
  Bug reports and pull requests are welcome on GitHub at <https://github.com/amkisko/seed_builder.rb>
92
169
 
93
170
  Contribution policy:
94
-
95
171
  - New features are not necessarily added to the gem
96
172
  - Pull request should have test coverage for affected parts
97
173
  - Pull request should have changelog entry
174
+
175
+ Review policy:
98
176
  - It might take up to 2 calendar weeks to review and merge critical fixes
99
177
  - It might take up to 6 calendar months to review and merge pull request
100
178
  - It might take up to 1 calendar year to review an issue
101
179
 
102
180
  ## Publishing
103
181
 
104
- Prefer using script `usr/bin/release.sh`, it will ensure that repository is synced and after publishing gem will create a tag.
182
+ From a clean checkout, run:
105
183
 
106
184
  ```sh
107
- GEM_VERSION=$(grep -Eo "VERSION\s*=\s*\".+\"" lib/seed_builder.rb | grep -Eo "[0-9.]{5,}")
108
- rm seed_builder-*.gem
109
- gem build seed_builder.gemspec
110
- gem push seed_builder-$GEM_VERSION.gem
111
- git tag $GEM_VERSION && git push --tags
185
+ ruby usr/bin/release.rb
112
186
  ```
113
187
 
188
+ The script installs dependencies, runs the test suite, requires a clean worktree, checks the changelog heading, and asks before publishing. On confirmation it pushes the gem, tags that version, and opens a GitHub release.
189
+
190
+ ## Links
191
+
192
+ - [GitHub](https://github.com/amkisko/seed_builder.rb)
193
+ - [GitLab](https://gitlab.com/amkisko/seed_builder.rb)
194
+ - [RubyGems](https://rubygems.org/gems/seed_builder)
195
+ - [Versions Atom](https://rubygems.org/gems/seed_builder/versions.atom) (feed id `8140233409492`)
196
+ - [libraries.io](https://libraries.io/rubygems/seed_builder)
197
+ - [Deps.dev](https://deps.dev/rubygems/seed_builder)
198
+ - [SonarCloud](https://sonarcloud.io/project/overview?id=amkisko_seed_builder.rb)
199
+ - [Snyk](https://snyk.io/test/github/amkisko/seed_builder.rb)
200
+ - [Codecov](https://app.codecov.io/github/amkisko/seed_builder.rb)
201
+ - [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/amkisko/seed_builder.rb)
202
+
114
203
  ## License
115
204
 
116
205
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
206
+
207
+ ## Sponsors
208
+
209
+ Sponsored by [Kisko Labs](https://www.kiskolabs.com).
210
+
211
+ <a href="https://www.kiskolabs.com">
212
+ <img src="kisko.svg" width="200" alt="Sponsored by Kisko Labs" />
213
+ </a>
data/bin/seed ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ environment = File.expand_path("config/environment.rb", Dir.pwd)
4
+ require environment if File.file?(environment)
5
+
6
+ unless defined?(Rails)
7
+ puts "Error: Rails environment not loaded. Please run this from a Rails application root."
8
+ puts "Alternatively, use: bin/rails seed:run[SEED_NAME]"
9
+ exit 1
10
+ end
11
+
12
+ require "seed_builder"
13
+
14
+ if ARGV.empty?
15
+ puts "Usage: bin/seed SEED_NAME"
16
+ puts ""
17
+ puts "Run a specific seed file by name."
18
+ puts ""
19
+ puts "Examples:"
20
+ puts " bin/seed create_users"
21
+ puts " bin/seed 20241206200111_create_users"
22
+ puts " bin/seed 20241206200111"
23
+ puts ""
24
+ puts "Note: This script must be run from a Rails application root."
25
+ puts " If multiple seed files match the name, use the full name with"
26
+ puts " timestamp to avoid ambiguity (e.g., 20241206200111_create_users)."
27
+ puts ""
28
+ puts "Alternatively, use: bin/rails seed:run[SEED_NAME]"
29
+ exit 1
30
+ end
31
+
32
+ seed_name = ARGV[0]
33
+
34
+ loader = SeedBuilder::Loader.new
35
+ exit 1 unless loader.load_seed_file(seed_name)
36
+
@@ -7,7 +7,7 @@ module SeedBuilder
7
7
 
8
8
  attr_writer :default_seeds_full_path
9
9
  def default_seeds_full_path
10
- @default_seeds_full_path ||= Rails.root.join(default_seeds_path)
10
+ @default_seeds_full_path || Rails.root.join(default_seeds_path)
11
11
  end
12
12
 
13
13
  attr_writer :seeds_path
@@ -17,7 +17,7 @@ module SeedBuilder
17
17
 
18
18
  attr_writer :seeds_full_path
19
19
  def seeds_full_path
20
- @seeds_full_path ||= Rails.root.join(seeds_path)
20
+ @seeds_full_path || Rails.root.join(seeds_path)
21
21
  end
22
22
 
23
23
  def seeds_path_glob
@@ -26,22 +26,24 @@ module SeedBuilder
26
26
 
27
27
  attr_writer :load_default_seeds
28
28
  def load_default_seeds?
29
- @load_default_seeds.nil? ? true : @load_default_seeds
29
+ @load_default_seeds.nil? || @load_default_seeds
30
30
  end
31
31
 
32
32
  attr_writer :load_seeds
33
33
  def load_seeds?
34
- @load_seeds.nil? ? true : @load_seeds
34
+ @load_seeds.nil? || @load_seeds
35
35
  end
36
36
 
37
37
  attr_writer :generate_spec
38
38
  def generate_spec?
39
- @generate_spec.nil? ? true : @generate_spec
39
+ @generate_spec.nil? || @generate_spec
40
40
  end
41
41
 
42
42
  attr_writer :use_seed_loader
43
43
  def use_seed_loader?
44
- @use_seed_loader.nil? ? true : @use_seed_loader
44
+ @use_seed_loader.nil? || @use_seed_loader
45
45
  end
46
+
47
+ attr_accessor :logger
46
48
  end
47
49
  end
@@ -1,50 +1,188 @@
1
1
  module SeedBuilder
2
2
  class Loader
3
+ SEED_NAME = /\A[A-Za-z0-9_]+\z/
4
+ private_constant :SEED_NAME
5
+
3
6
  def load_seed
4
- initial_logger = Rails.logger
7
+ default_path = SeedBuilder.config.default_seeds_full_path
8
+ base_path = SeedBuilder.config.seeds_full_path
9
+ load_default = SeedBuilder.config.load_default_seeds? && File.file?(default_path.to_s)
10
+ if SeedBuilder.config.load_seeds? && !File.directory?(base_path.to_s)
11
+ warn_seed("Seed directory #{base_path} does not exist")
12
+ end
13
+ load_directory = SeedBuilder.config.load_seeds? && File.directory?(base_path.to_s)
14
+ return unless load_default || load_directory
15
+
16
+ prepare_active_record
17
+ load_default_seed(default_path) if load_default
18
+ load_directory_seeds(base_path) if load_directory
19
+ end
20
+
21
+ def load_seed_file(seed_name)
22
+ base_path = SeedBuilder.config.seeds_full_path
23
+ unless seed_name.is_a?(String) && SEED_NAME.match?(seed_name)
24
+ warn_seed("Seed name '#{seed_name}' is invalid")
25
+ return false
26
+ end
27
+
28
+ unless File.directory?(base_path.to_s)
29
+ warn_seed("Seed directory #{base_path} does not exist")
30
+ return false
31
+ end
32
+
33
+ result = locate_seed_file(base_path, seed_name)
34
+ if result.nil?
35
+ warn_seed("Seed file '#{seed_name}' not found in #{base_path}")
36
+ return false
37
+ elsif result.is_a?(Array) && result.first == :ambiguous
38
+ matches = result.last
39
+ logger.tagged("seed") do
40
+ logger.warn "Multiple seed files match '#{seed_name}':"
41
+ matches.each do |file|
42
+ logger.warn " - #{File.basename(file, ".*")}"
43
+ end
44
+ logger.warn "Please be more specific using the full name with timestamp (e.g., 20241206200111_create_users)"
45
+ end
46
+ return false
47
+ end
48
+
49
+ seed_file = result
50
+ seed_path = File.basename(seed_file, ".*")
51
+ timestamp, klass_name = split_seed_basename(seed_path)
52
+ if klass_name.blank?
53
+ warn_seed("Invalid seed file format: #{seed_path}. Expected format: TIMESTAMP_CLASS_NAME.rb")
54
+ return false
55
+ end
56
+
57
+ prepare_active_record
58
+ load seed_file
59
+ execute_seed_class(klass_name, timestamp, seed_name)
60
+ true
61
+ end
62
+
63
+ private
64
+
65
+ def logger
66
+ SeedBuilder.logger
67
+ end
68
+
69
+ def warn_seed(message)
70
+ logger.tagged("seed") do
71
+ logger.warn(message)
72
+ end
73
+ end
74
+
75
+ def load_default_seed(default_path)
76
+ started_at = Time.current
77
+ logger.tagged("seed") do
78
+ logger.info "== #{SeedBuilder.config.default_seeds_path}: seeding"
79
+ end
80
+ load default_path
81
+ logger.tagged("seed") do
82
+ logger.info "== #{SeedBuilder.config.default_seeds_path}: seeded (#{(Time.current - started_at).round(4)}s)"
83
+ end
84
+ end
5
85
 
6
- Rails.logger = Logger.new($stdout)
7
- Rails.logger.level = Logger::INFO
86
+ def load_directory_seeds(base_path)
87
+ seed_basenames(base_path).each do |seed_path|
88
+ full_path = File.join(base_path, "#{seed_path}.rb")
89
+ unless inside_directory?(base_path, full_path)
90
+ warn_seed("Seed file #{seed_path} was not loaded from #{base_path}")
91
+ next
92
+ end
8
93
 
94
+ load full_path
95
+ timestamp, klass_name = split_seed_basename(seed_path)
96
+ next if klass_name.blank?
97
+
98
+ execute_seed_class(klass_name, timestamp)
99
+ end
100
+ end
101
+
102
+ def prepare_active_record
9
103
  ActiveRecord::Base.connection.schema_cache.clear!
10
104
  ActiveRecord::Base.descendants.each(&:reset_column_information)
105
+ end
11
106
 
12
- default_seeds_rb = SeedBuilder.config.default_seeds_full_path
13
- if File.exist?(default_seeds_rb) && SeedBuilder.config.load_default_seeds?
14
- started_at = Time.current
15
- puts "== #{SeedBuilder.config.default_seeds_path}: seeding"
16
- require default_seeds_rb
17
- puts "== #{SeedBuilder.config.default_seeds_path}: seeded (#{(Time.current - started_at).round(4)}s)"
107
+ def execute_seed_class(klass_name, timestamp, seed_name = nil)
108
+ klass = klass_name.camelize.constantize
109
+ klass_name_display = klass.name
110
+ logger.tagged("seed") do
111
+ logger.info "== #{timestamp} #{klass_name_display}: seeding"
112
+ end
113
+ started_at = Time.current
114
+ seed_instance = klass.new
115
+ if seed_instance.respond_to?(:change)
116
+ seed_instance.change
117
+ else
118
+ raise "Seed #{klass_name_display} does not respond to :change"
18
119
  end
120
+ logger.tagged("seed") do
121
+ logger.info "== #{timestamp} #{klass_name_display}: seeded (#{(Time.current - started_at).round(4)}s)"
122
+ end
123
+ rescue ActiveRecord::RecordInvalid => e
124
+ klass_name_display = defined?(klass) ? klass.name : (klass_name&.camelize || seed_name || klass_name)
125
+ logger.tagged("seed") do
126
+ logger.error "Seeding #{klass_name_display} failed: #{e.record.errors.full_messages}"
127
+ end
128
+ raise e
129
+ rescue => e
130
+ logger.tagged("seed") do
131
+ logger.error "Error loading seed: #{e.message}"
132
+ end
133
+ raise e
134
+ end
19
135
 
20
- base_path = SeedBuilder.config.seeds_full_path
21
- if File.exist?(base_path) && SeedBuilder.config.load_seeds?
22
- Dir[SeedBuilder.config.seeds_path_glob]
23
- .map { |f| File.basename(f, ".*") }
24
- .each do |seed_path|
25
- require "#{base_path}/#{seed_path}.rb"
26
- timestamp, klass_name = seed_path.scan(/^([0-9]+)_(.+)$/).first
27
- next if klass_name.blank?
28
-
29
- klass = klass_name.camelize.constantize
30
- puts "== #{timestamp} #{klass.name}: seeding"
31
- started_at = Time.current
32
- seed_instance = klass.new
33
- if seed_instance.respond_to?(:change)
34
- seed_instance.change
35
- else
36
- raise "Seed #{klass.name} does not respond to :change"
37
- end
38
- puts "== #{timestamp} #{klass.name}: seeded (#{(Time.current - started_at).round(4)}s)"
39
- rescue ActiveRecord::RecordInvalid => e
40
- puts "Seeding #{klass.name} failed: #{e.record.errors.full_messages}"
41
- raise e
42
- end
136
+ def locate_seed_file(base_path, seed_name)
137
+ exact_path = File.join(base_path, "#{seed_name}.rb")
138
+ if File.file?(exact_path)
139
+ return inside_directory?(base_path, exact_path) ? exact_path : nil
140
+ end
141
+
142
+ matches = seed_basenames(base_path).filter_map { |basename|
143
+ next unless basename == seed_name || basename.end_with?("_#{seed_name}") || basename.start_with?("#{seed_name}_")
144
+
145
+ path = File.join(base_path, "#{basename}.rb")
146
+ path if inside_directory?(base_path, path)
147
+ }
148
+
149
+ case matches.length
150
+ when 0
151
+ nil
152
+ when 1
153
+ matches.first
43
154
  else
44
- puts "Seed directory #{base_path} does not exist"
155
+ [:ambiguous, matches]
45
156
  end
157
+ end
158
+
159
+ def seed_basenames(base_path)
160
+ Dir.children(base_path.to_s).filter_map { |entry|
161
+ next if entry.start_with?(".")
162
+ next unless entry.end_with?(".rb")
163
+
164
+ File.basename(entry, ".rb")
165
+ }.sort_by { |name| seed_sort_key(name) }
166
+ end
167
+
168
+ def seed_sort_key(name)
169
+ match = name.match(/\A([0-9]+)_/)
170
+ match ? [0, match[1].to_i, name] : [1, 0, name]
171
+ end
172
+
173
+ def split_seed_basename(seed_path)
174
+ match = seed_path.match(/\A([0-9]+)_(.+)\z/)
175
+ return [nil, nil] unless match
176
+
177
+ [match[1], match[2]]
178
+ end
46
179
 
47
- Rails.logger = initial_logger
180
+ def inside_directory?(base_path, file_path)
181
+ root = File.realpath(base_path.to_s)
182
+ resolved = File.realpath(file_path)
183
+ resolved.start_with?(root + File::SEPARATOR)
184
+ rescue Errno::ENOENT
185
+ false
48
186
  end
49
187
  end
50
188
  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.2.1".freeze
6
+ VERSION = "1.3.1".freeze
6
7
 
7
8
  module_function
8
9
 
@@ -13,6 +14,25 @@ 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
- require "rails_config"
38
+ require "seed_builder/railtie" if defined?(Rails::Railtie)
@@ -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].blank?
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
+ exit 1 unless loader.load_seed_file(args[:seed_name])
21
+ end
22
+ end