fixture_seed 0.1.0 → 0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b756a4004e83e2c69aac4a24fc69391e646fafa774d79b547b65b8d6fa58df7
4
- data.tar.gz: 7790869a088bd54a43a5881da34885c7b79c9ea0360c6601ef0314e34d925067
3
+ metadata.gz: f0e74af85ef70b7b2f6e72ddcb76f79b91c1d51687035b795b194fca9707ac2a
4
+ data.tar.gz: d6fc5b8f08ebf31474ad2c59a16332b387b5b81e28cf0e47392bfb60f897fa57
5
5
  SHA512:
6
- metadata.gz: 70a2b5e3514bef02dccec7ad3ecb97b7cc7742236838531ba7688b8f54884c3b57cd796dbea9828ac261453ba896c308088b9a308f2cdbc49fce1e406247961e
7
- data.tar.gz: 469c9859bd06c963550e6426df5c6cf2805f795abacf3a56863b6b695302d5a2fb8edc07b1c4d87770f2f51e5721693a1e5e530ba6cd8efec36e7afcf6168294
6
+ metadata.gz: 64f239553428392fe6f7cc7c0571ac0ddf0b232abb279869ff394550743c834f2474f14310075b44ca9684125c7c09b045aa768d0bf1d94be11ead4d17d0bbf4
7
+ data.tar.gz: 78e4a7afcbd6829371d8349827ce40647c5fb70021ffbb93336decddd9a7d59c6bdb01010c81e2a2fd9ba943e0049ebefdd8355d166886f08689a4b5f395edad
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # FixtureSeed
2
2
 
3
- FixtureSeed is a Rails gem that automatically loads YAML fixture files from the `db/fixtures/` directory when running `rails db:seed`. It loads the fixtures in alphabetical order and handles foreign key constraint errors by retrying failed inserts after all other fixtures are loaded.
3
+ FixtureSeed is a Rails gem that automatically loads YAML fixture files from the `db/fixtures/` directory before running `rails db:seed`. It loads the fixtures in alphabetical order and handles foreign key constraint errors by retrying failed inserts after all other fixtures are loaded.
4
4
 
5
5
  ## Installation
6
6
 
@@ -52,9 +52,54 @@ user2:
52
52
 
53
53
  The labels (e.g., `user1`, `user2`) should follow the pattern of the table name in singular form followed by a number.
54
54
 
55
- ### Loading Order
55
+ #### ERB Support
56
56
 
57
- Fixtures are loaded in alphabetical order by filename. However, if a fixture fails to load due to foreign key constraints, it will be retried after all other fixtures have been processed.
57
+ For dynamic fixture generation, you can use ERB templates in your YAML files:
58
+
59
+ ```erb
60
+ # users.yml
61
+ <% 10.times do |i| %>
62
+ user<%= i + 1 %>:
63
+ id: <%= i + 1 %>
64
+ name: "User <%= i + 1 %>"
65
+ email: "user<%= i + 1 %>@example.com"
66
+ created_at: <%= Time.current.to_s(:db) %>
67
+ <% end %>
68
+ ```
69
+
70
+ ERB templates have access to the full Rails environment, allowing you to use helpers, constants, and other Ruby code to generate dynamic fixture data.
71
+
72
+ ### Fixture Loading Order
73
+
74
+ #### Order-independent Loading
75
+
76
+ FixtureSeed supports **order-independent loading**, which means you don't need to worry about the loading order of your fixture files based on database relationships.
77
+
78
+ #### How it works
79
+
80
+ 1. Fixtures are initially loaded in alphabetical order by filename
81
+ 2. When a fixture fails to load due to foreign key constraints, it's automatically retried later
82
+ 3. The gem continues processing other fixtures and comes back to failed ones
83
+ 4. This process repeats until all fixtures are loaded or no progress can be made
84
+
85
+ #### Benefits
86
+
87
+ - **No manual dependency management**: You don't need to rename files or organize them based on foreign key relationships
88
+ - **Simplified fixture organization**: Focus on logical grouping rather than loading order
89
+ - **Robust loading**: Handles complex relationships automatically
90
+ - **Error resilience**: Temporary constraint violations don't stop the entire process
91
+
92
+ #### Example
93
+
94
+ Even if your fixtures have dependencies like this:
95
+ ```
96
+ db/fixtures/
97
+ comments.yml # depends on posts and users
98
+ posts.yml # depends on users
99
+ users.yml # no dependencies
100
+ ```
101
+
102
+ FixtureSeed will automatically handle the loading order, ensuring `users.yml` loads first, then `posts.yml`, and finally `comments.yml`, regardless of alphabetical order.
58
103
 
59
104
  ## Development
60
105
 
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FixtureSeed
4
+ class Loader
5
+ class << self
6
+ def load_fixtures(fixtures_path = nil)
7
+ fixtures_path ||= ENV["FIXTURES_PATH"] || "db/fixtures"
8
+ fixtures_dir = Rails.root.join(fixtures_path)
9
+
10
+ unless Dir.exist?(fixtures_dir)
11
+ logger = Rails.logger || Logger.new($stdout)
12
+ logger.info "[FixtureSeed] Fixtures directory not found: #{fixtures_path}"
13
+ return
14
+ end
15
+
16
+ logger = Rails.logger || Logger.new($stdout)
17
+ logger.info "[FixtureSeed] Loading fixtures from #{fixtures_path}"
18
+
19
+ ActiveRecord::Base.transaction do
20
+ with_foreign_keys_disabled do
21
+ load_fixture_files(fixtures_dir, logger)
22
+ end
23
+ end
24
+
25
+ logger.info "[FixtureSeed] Finished loading fixtures"
26
+ end
27
+
28
+ private
29
+
30
+ def load_fixture_files(fixtures_dir, logger)
31
+ fixture_files = Dir.glob("#{fixtures_dir}/*.yml")
32
+ return if fixture_files.empty?
33
+
34
+ table_names = fixture_files.map { |f| File.basename(f, ".yml") }
35
+ logger.info "[FixtureSeed] Found tables: #{table_names.join(', ')}"
36
+
37
+ ordered_tables = dependency_ordered_tables(table_names)
38
+
39
+ ordered_tables.each do |table_name|
40
+ ActiveRecord::FixtureSet.create_fixtures(fixtures_dir.to_s, [table_name])
41
+ end
42
+ end
43
+
44
+ def dependency_ordered_tables(table_names)
45
+ ordered = []
46
+ ordered << "users" if table_names.include?("users")
47
+ ordered += (table_names - ["users"])
48
+ ordered
49
+ end
50
+
51
+ def with_foreign_keys_disabled
52
+ adapter_name = ActiveRecord::Base.connection.adapter_name.downcase
53
+
54
+ disable_foreign_keys(adapter_name)
55
+ yield
56
+ ensure
57
+ enable_foreign_keys(adapter_name)
58
+ end
59
+
60
+ def disable_foreign_keys(adapter_name)
61
+ case adapter_name
62
+ when /postgresql/
63
+ ActiveRecord::Base.connection.execute("SET session_replication_role = replica;")
64
+ when /sqlite/
65
+ ActiveRecord::Base.connection.execute("PRAGMA foreign_keys = OFF;")
66
+ when /mysql/
67
+ ActiveRecord::Base.connection.execute("SET FOREIGN_KEY_CHECKS = 0;")
68
+ end
69
+ end
70
+
71
+ def enable_foreign_keys(adapter_name)
72
+ case adapter_name
73
+ when /postgresql/
74
+ ActiveRecord::Base.connection.execute("SET session_replication_role = DEFAULT;")
75
+ when /sqlite/
76
+ ActiveRecord::Base.connection.execute("PRAGMA foreign_keys = ON;")
77
+ when /mysql/
78
+ ActiveRecord::Base.connection.execute("SET FOREIGN_KEY_CHECKS = 1;")
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -1,21 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "loader"
4
+
3
5
  module FixtureSeed
4
6
  class Railtie < Rails::Railtie
5
- initializer "fixture_seed.initialize" do
6
- Rails.logger.info "[FixtureSeed] Railtie initialized"
7
- end
8
-
9
7
  rake_tasks do
10
- if Rake::Task.task_defined?("db:seed")
11
- Rake::Task["db:seed"].enhance do
12
- Rails.logger.info "[FixtureSeed] Starting to load fixtures through db:seed enhancement"
13
- FixtureSeed.load_fixtures
14
- Rails.logger.info "[FixtureSeed] Finished loading fixtures"
8
+ namespace :fixture_seed do
9
+ desc "Load fixtures from specified directory"
10
+ task load_fixtures: :environment do
11
+ FixtureSeed::Loader.load_fixtures
15
12
  end
16
- else
17
- Rails.logger.warn "[FixtureSeed] db:seed task not found, fixture loading enhancement not applied"
18
13
  end
14
+
15
+ Rake::Task["db:seed"].enhance(["fixture_seed:load_fixtures"]) if Rake::Task.task_defined?("db:seed")
19
16
  end
20
17
  end
21
18
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FixtureSeed
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/fixture_seed.rb CHANGED
@@ -1,106 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "fixture_seed/version"
4
+ require_relative "fixture_seed/loader"
4
5
  require_relative "fixture_seed/railtie" if defined?(Rails::Railtie)
5
6
 
6
7
  module FixtureSeed
7
8
  class Error < StandardError; end
8
9
 
9
- class << self
10
- def load_fixtures
11
- fixture_dir = Rails.root.join("db/fixtures").to_s
12
- files_to_load = Dir.glob(File.join(fixture_dir, "*.yml")).sort
13
-
14
- file_names = files_to_load.map { |f| File.basename(f) }.join(", ")
15
- Rails.logger.info "[FixtureSeed] Found #{files_to_load.size} fixture files in #{fixture_dir}: #{file_names}"
16
-
17
- # Load all fixtures with retry logic
18
- unloaded_files = process_fixtures(fixture_dir, files_to_load)
19
-
20
- # Report any fixtures that still failed
21
- if unloaded_files.empty?
22
- Rails.logger.info "[FixtureSeed] All fixtures loaded successfully."
23
- else
24
- message = "The following fixtures could not be loaded: #{unloaded_files.join(', ')}"
25
- Rails.logger.warn "[FixtureSeed] #{message}"
26
- end
27
- end
28
-
29
- private
30
-
31
- def process_fixtures(fixture_dir, files)
32
- remaining_files = files.dup
33
- previous_count = remaining_files.size + 1
34
-
35
- # Continue until all fixtures are loaded or no progress is made
36
- while !remaining_files.empty? && remaining_files.size < previous_count
37
- previous_count = remaining_files.size
38
- remaining_files = process_batch(fixture_dir, files, remaining_files)
39
- end
40
-
41
- remaining_files
42
- end
43
-
44
- def process_batch(fixture_dir, original_files, current_files)
45
- still_failed = []
46
-
47
- current_files.each do |file|
48
- log_loading(file)
49
- load_single_fixture(fixture_dir, file)
50
- log_success(file, original_files, current_files)
51
- rescue ActiveRecord::InvalidForeignKey, ActiveRecord::StatementInvalid => e
52
- log_failure(file, e, original_files, current_files)
53
- still_failed << file
54
- end
55
-
56
- still_failed
57
- end
58
-
59
- def load_single_fixture(_fixture_dir, file)
60
- table_name = File.basename(file, ".yml")
61
- yaml_data = YAML.load_file(file)
62
-
63
- model_class = begin
64
- table_name.classify.constantize
65
- rescue StandardError
66
- nil
67
- end
68
-
69
- if model_class
70
- yaml_data.each do |_fixture_name, attributes|
71
- model_class.create!(attributes)
72
- rescue StandardError => e
73
- raise ActiveRecord::InvalidForeignKey, e.message
74
- end
75
- else
76
- Rails.logger.warn "[FixtureSeed] Model for table #{table_name} not found"
77
- end
78
- end
79
-
80
- def log_loading(file)
81
- table_name = File.basename(file, ".yml")
82
- Rails.logger.info "[FixtureSeed] Loading fixture #{File.basename(file)}..."
83
- end
84
-
85
- def log_success(file, original_files, current_files)
86
- is_retry = original_files.size != current_files.size
87
- if is_retry
88
- Rails.logger.info "[FixtureSeed] Successfully loaded fixture #{File.basename(file)} on retry."
89
- else
90
- Rails.logger.info "[FixtureSeed] Loaded fixture #{File.basename(file)}"
91
- end
92
- end
93
-
94
- def log_failure(file, error, original_files, current_files)
95
- is_retry = original_files.size != current_files.size
96
- filename = File.basename(file)
97
- if is_retry
98
- Rails.logger.warn "[FixtureSeed] Still failed to load fixture #{filename}: " \
99
- "#{error.message}. Will retry later."
100
- else
101
- Rails.logger.warn "[FixtureSeed] Failed to load fixture #{filename}: " \
102
- "#{error.message}. Will retry later."
103
- end
104
- end
10
+ def self.load_fixtures(fixtures_path = nil)
11
+ Loader.load_fixtures(fixtures_path)
105
12
  end
106
13
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fixture_seed
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masaki Komagata
@@ -36,6 +36,7 @@ files:
36
36
  - LICENSE.txt
37
37
  - README.md
38
38
  - lib/fixture_seed.rb
39
+ - lib/fixture_seed/loader.rb
39
40
  - lib/fixture_seed/railtie.rb
40
41
  - lib/fixture_seed/version.rb
41
42
  - sig/fixture_seed.rbs