|
| 1 | +# frozen_string_literal: true |
| 2 | +# |
| 3 | +# Improvements over previous version: |
| 4 | +# |
| 5 | +# 1. `frozen_string_literal: true` — avoids unnecessary String allocations for every literal. |
| 6 | +# 2. `ApplicationRecord.transaction` — atomicity: a failure anywhere rolls the whole seed back. |
| 7 | +# 3. `delete_all` over `destroy_all` — skips ActiveRecord callbacks and validations; orders of |
| 8 | +# magnitude faster for bulk teardown where those side-effects are irrelevant. |
| 9 | +# 4. Ordered `delete_all` respects FK constraints without disabling them. |
| 10 | +# 5. `tag_map` as a local variable — constants defined in seed.rb leak into the global namespace |
| 11 | +# and raise "already initialised" warnings on every subsequent `db:seed` run. |
| 12 | +# 6. `days_ago.days.ago.to_date` — idiomatic Rails; reads like English and handles DST correctly. |
| 13 | +# 7. `PostTag.insert_all!` — collapses N individual INSERT statements into one, which is the |
| 14 | +# single biggest query-count win available in this file. |
| 15 | + |
| 16 | +ApplicationRecord.transaction do |
| 17 | + # 1. Clean up — order matters: children before parents to satisfy FK constraints. |
| 18 | + puts "Cleaning database..." |
| 19 | + [PostTag, Tag, Post, Profile, Author].each(&:delete_all) |
| 20 | + |
| 21 | + # 2. Tags |
| 22 | + puts "Creating tags..." |
| 23 | + tag_map = { |
| 24 | + tech: Tag.create!(name: "Technology"), |
| 25 | + travel: Tag.create!(name: "Travel"), |
| 26 | + food: Tag.create!(name: "Food") |
| 27 | + } |
| 28 | + |
| 29 | + # 3. Authors + Profiles |
| 30 | + puts "Creating authors and profiles..." |
| 31 | + [ |
| 32 | + { name: "Alice Smith", age: 30, email: "alice@example.com", bio: "Tech enthusiast and coder." }, |
| 33 | + { name: "Bob Jones", age: 45, email: "bob@example.com", bio: "Global traveler and food critic." } |
| 34 | + ].each do |data| |
| 35 | + author = Author.create!(name: data[:name], age: data[:age], email: data[:email]) |
| 36 | + Profile.create!(author: author, description: data[:bio]) |
| 37 | + end |
| 38 | + |
| 39 | + # 4. Posts |
| 40 | + puts "Creating posts..." |
| 41 | + alice = Author.find_by!(email: "alice@example.com") |
| 42 | + bob = Author.find_by!(email: "bob@example.com") |
| 43 | + |
| 44 | + posts_data = [ |
| 45 | + { |
| 46 | + title: "The Future of Rails 7", |
| 47 | + description: "Exploring the new features in the latest Rails update.", |
| 48 | + category: "Software", |
| 49 | + author: alice, |
| 50 | + tags: %i[tech], |
| 51 | + published: true, |
| 52 | + days_ago: 0 |
| 53 | + }, |
| 54 | + { |
| 55 | + title: "Best Pasta in Rome", |
| 56 | + description: "A deep dive into the culinary wonders of Italy.", |
| 57 | + category: "Lifestyle", |
| 58 | + author: bob, |
| 59 | + tags: %i[travel food], |
| 60 | + published: true, |
| 61 | + days_ago: 1 |
| 62 | + }, |
| 63 | + { |
| 64 | + title: "Intro to Hotwire and Turbo", |
| 65 | + description: "How Hotwire is changing the way we think about front-end in Rails.", |
| 66 | + category: "Software", |
| 67 | + author: alice, |
| 68 | + tags: %i[tech], |
| 69 | + published: true, |
| 70 | + days_ago: 2 |
| 71 | + }, |
| 72 | + { |
| 73 | + title: "Hidden Gems of Southeast Asia", |
| 74 | + description: "Off-the-beaten-path destinations you need to visit.", |
| 75 | + category: "Lifestyle", |
| 76 | + author: bob, |
| 77 | + tags: %i[travel], |
| 78 | + published: true, |
| 79 | + days_ago: 3 |
| 80 | + }, |
| 81 | + { |
| 82 | + title: "Ruby Performance Tips", |
| 83 | + description: "Practical techniques to squeeze more speed out of your Ruby code.", |
| 84 | + category: "Software", |
| 85 | + author: alice, |
| 86 | + tags: %i[tech], |
| 87 | + published: true, |
| 88 | + days_ago: 4 |
| 89 | + }, |
| 90 | + { |
| 91 | + title: "Street Food of Bangkok", |
| 92 | + description: "Navigating the incredible street food scene of Thailand's capital.", |
| 93 | + category: "Lifestyle", |
| 94 | + author: bob, |
| 95 | + tags: %i[travel food], |
| 96 | + published: true, |
| 97 | + days_ago: 5 |
| 98 | + }, |
| 99 | + { |
| 100 | + title: "Understanding ActiveRecord Callbacks", |
| 101 | + description: "A guide to using—and avoiding—callbacks in Rails models.", |
| 102 | + category: "Software", |
| 103 | + author: alice, |
| 104 | + tags: %i[tech], |
| 105 | + published: true, |
| 106 | + days_ago: 6 |
| 107 | + }, |
| 108 | + { |
| 109 | + title: "A Week in Kyoto", |
| 110 | + description: "Temples, matcha, and slow mornings in Japan's ancient capital.", |
| 111 | + category: "Lifestyle", |
| 112 | + author: bob, |
| 113 | + tags: %i[travel food], |
| 114 | + published: true, |
| 115 | + days_ago: 7 |
| 116 | + }, |
| 117 | + { |
| 118 | + title: "Designing RESTful APIs with Rails", |
| 119 | + description: "Best practices for building clean and maintainable JSON APIs.", |
| 120 | + category: "Software", |
| 121 | + author: alice, |
| 122 | + tags: %i[tech], |
| 123 | + published: false, |
| 124 | + days_ago: 8 |
| 125 | + }, |
| 126 | + { |
| 127 | + title: "The Art of Sourdough", |
| 128 | + description: "Everything you need to start baking your own sourdough bread.", |
| 129 | + category: "Lifestyle", |
| 130 | + author: bob, |
| 131 | + tags: %i[food], |
| 132 | + published: true, |
| 133 | + days_ago: 9 |
| 134 | + }, |
| 135 | + { |
| 136 | + title: "Background Jobs with Sidekiq", |
| 137 | + description: "How to offload work and keep your Rails app responsive.", |
| 138 | + category: "Software", |
| 139 | + author: alice, |
| 140 | + tags: %i[tech], |
| 141 | + published: true, |
| 142 | + days_ago: 10 |
| 143 | + }, |
| 144 | + { |
| 145 | + title: "Road Trip Through Patagonia", |
| 146 | + description: "Wind, glaciers, and endless roads at the end of the world.", |
| 147 | + category: "Lifestyle", |
| 148 | + author: bob, |
| 149 | + tags: %i[travel], |
| 150 | + published: true, |
| 151 | + days_ago: 11 |
| 152 | + }, |
| 153 | + { |
| 154 | + title: "Securing Your Rails Application", |
| 155 | + description: "A practical checklist for locking down common Rails vulnerabilities.", |
| 156 | + category: "Software", |
| 157 | + author: alice, |
| 158 | + tags: %i[tech], |
| 159 | + published: true, |
| 160 | + days_ago: 12 |
| 161 | + }, |
| 162 | + { |
| 163 | + title: "Ramen Beyond the Packet", |
| 164 | + description: "Crafting a rich tonkotsu broth from scratch at home.", |
| 165 | + category: "Lifestyle", |
| 166 | + author: bob, |
| 167 | + tags: %i[food], |
| 168 | + published: true, |
| 169 | + days_ago: 13 |
| 170 | + }, |
| 171 | + { |
| 172 | + title: "Caching Strategies in Rails", |
| 173 | + description: "Fragment, action, and low-level caching explained with examples.", |
| 174 | + category: "Software", |
| 175 | + author: alice, |
| 176 | + tags: %i[tech], |
| 177 | + published: false, |
| 178 | + days_ago: 14 |
| 179 | + }, |
| 180 | + { |
| 181 | + title: "Sailing the Greek Islands", |
| 182 | + description: "What a two-week sailing trip through the Cyclades taught me.", |
| 183 | + category: "Lifestyle", |
| 184 | + author: bob, |
| 185 | + tags: %i[travel], |
| 186 | + published: true, |
| 187 | + days_ago: 15 |
| 188 | + }, |
| 189 | + { |
| 190 | + title: "Testing with RSpec: Beyond the Basics", |
| 191 | + description: "Shared examples, custom matchers, and keeping your test suite fast.", |
| 192 | + category: "Software", |
| 193 | + author: alice, |
| 194 | + tags: %i[tech], |
| 195 | + published: true, |
| 196 | + days_ago: 16 |
| 197 | + }, |
| 198 | + { |
| 199 | + title: "Fermentation at Home", |
| 200 | + description: "Kimchi, kombucha, and kefir—why fermented foods are worth the effort.", |
| 201 | + category: "Lifestyle", |
| 202 | + author: bob, |
| 203 | + tags: %i[food], |
| 204 | + published: true, |
| 205 | + days_ago: 17 |
| 206 | + }, |
| 207 | + { |
| 208 | + title: "Multi-tenancy in Rails with Apartment", |
| 209 | + description: "Schema-based multi-tenancy patterns for SaaS applications.", |
| 210 | + category: "Software", |
| 211 | + author: alice, |
| 212 | + tags: %i[tech], |
| 213 | + published: true, |
| 214 | + days_ago: 18 |
| 215 | + }, |
| 216 | + { |
| 217 | + title: "Morocco on Two Wheels", |
| 218 | + description: "Cycling from Marrakech to the Sahara and back.", |
| 219 | + category: "Lifestyle", |
| 220 | + author: bob, |
| 221 | + tags: %i[travel food], |
| 222 | + published: true, |
| 223 | + days_ago: 19 |
| 224 | + }, |
| 225 | + { |
| 226 | + title: "Deploying Rails with Kamal", |
| 227 | + description: "Zero-downtime deploys to your own server without the Heroku price tag.", |
| 228 | + category: "Software", |
| 229 | + author: alice, |
| 230 | + tags: %i[tech], |
| 231 | + published: true, |
| 232 | + days_ago: 20 |
| 233 | + }, |
| 234 | + { |
| 235 | + title: "The Perfect Neapolitan Pizza", |
| 236 | + description: "Flour, water, salt, yeast—and a very hot oven.", |
| 237 | + category: "Lifestyle", |
| 238 | + author: bob, |
| 239 | + tags: %i[food], |
| 240 | + published: true, |
| 241 | + days_ago: 21 |
| 242 | + } |
| 243 | + ] |
| 244 | + |
| 245 | + # Accumulate PostTag rows while creating posts, then flush in one INSERT. |
| 246 | + post_tag_rows = [] |
| 247 | + |
| 248 | + posts_data.each_with_index do |data, index| |
| 249 | + post = Post.create!( |
| 250 | + title: data[:title], |
| 251 | + description: data[:description], |
| 252 | + state: index % 3, |
| 253 | + category: data[:category], |
| 254 | + dt: data[:days_ago].days.ago.to_date, |
| 255 | + position: (index + 1).to_f, |
| 256 | + published: data[:published], |
| 257 | + author: data[:author] |
| 258 | + ) |
| 259 | + |
| 260 | + data[:tags].each do |tag_key| |
| 261 | + post_tag_rows << { post_id: post.id, tag_id: tag_map[tag_key].id } |
| 262 | + end |
| 263 | + end |
| 264 | + |
| 265 | + # 5. Single-query bulk insert for all join-table rows. |
| 266 | + puts "Linking posts to tags..." |
| 267 | + PostTag.insert_all!(post_tag_rows) |
| 268 | + |
| 269 | + puts "Successfully seeded #{Author.count} authors, #{Post.count} posts, and #{Tag.count} tags!" |
| 270 | +end |
0 commit comments