diff --git a/Gemfile b/Gemfile index ac07282..afcedb4 100644 --- a/Gemfile +++ b/Gemfile @@ -4,6 +4,7 @@ source "https://rubygems.org" gemspec +gem "activestorage" gem "rake" gem "rspec" gem "rubocop-katalyst" diff --git a/Gemfile.lock b/Gemfile.lock index e9347c5..bffe3bc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -33,6 +33,12 @@ GEM activemodel (= 8.1.3) activesupport (= 8.1.3) timeout (>= 0.4.0) + activestorage (8.1.3) + actionpack (= 8.1.3) + activejob (= 8.1.3) + activerecord (= 8.1.3) + activesupport (= 8.1.3) + marcel (~> 1.0) activesupport (8.1.3) base64 bigdecimal @@ -80,13 +86,26 @@ GEM loofah (2.25.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) + marcel (1.2.1) minitest (6.0.6) drb (~> 2.0) prism (~> 1.5) + nokogiri (1.19.4-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.4-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.4-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.4-arm-linux-musl) + racc (~> 1.4) nokogiri (1.19.4-arm64-darwin) racc (~> 1.4) + nokogiri (1.19.4-x86_64-darwin) + racc (~> 1.4) nokogiri (1.19.4-x86_64-linux-gnu) racc (~> 1.4) + nokogiri (1.19.4-x86_64-linux-musl) + racc (~> 1.4) parallel (2.1.0) parser (3.3.11.1) ast (~> 2.4.1) @@ -220,17 +239,114 @@ GEM zeitwerk (2.8.2) PLATFORMS - arm64-darwin-23 - arm64-darwin-24 - arm64-darwin-25 - x86_64-linux + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl DEPENDENCIES + activestorage katalyst-thermite! rake rspec rubocop-katalyst solid_queue +CHECKSUMS + actionpack (8.1.3) sha256=af998cae4d47c5d581a2cc363b5c77eb718b7c4b45748d81b1887b25621c29a3 + actionview (8.1.3) sha256=1347c88c7f3edb38100c5ce0e9fb5e62d7755f3edc1b61cce2eb0b2c6ea2fd5d + activejob (8.1.3) sha256=a149b1766aa8204c3c3da7309e4becd40fcd5529c348cffbf6c9b16b565fe8d3 + activemodel (8.1.3) sha256=90c05cbe4cef3649b8f79f13016191ea94c4525ce4a5c0fb7ef909c4b91c8219 + activerecord (8.1.3) sha256=8003be7b2466ba0a2a670e603eeb0a61dd66058fccecfc49901e775260ac70ab + activestorage (8.1.3) sha256=0564ce9309143951a67615e1bb4e090ee54b8befed417133cae614479b46384d + activesupport (8.1.3) sha256=21a5e0dfbd4c3ddd9e1317ec6a4d782fa226e7867dc70b0743acda81a1dca20e + ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383 + base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b + bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd + builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f + bundler (4.0.14) sha256=d09a0a965cf772266a7e49e83610be7c2f4e49e61134c42a56804bb383cc24b8 + concurrent-ruby (1.3.7) sha256=4412caec3a5ea2e5fdc52076724c071a81f2c0593d83b2ac8cbb8ca63b3151b0 + connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a + crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d + date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0 + diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962 + drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373 + erb (6.0.4) sha256=38e3803694be357fe2bfe312487c74beaf9fb4e5beb3e22498952fe1645b95d9 + erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9 + et-orbi (1.4.0) sha256=6c7e3c90779821f9e3b324c5e96fda9767f72995d6ae435b96678a4f3e2de8bc + fugit (1.12.2) sha256=643f2bf28db263bd400cbf8e0dd8b76b2c9b94bdb130e12d2394de04d9c20e5e + globalid (1.3.0) sha256=05c639ad6eb4594522a0b07983022f04aa7254626ab69445a0e493aa3786ff11 + i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5 + io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc + irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3 + json (2.19.9) sha256=9b9025b7cdddafa38d316eca0b2358488e42d417045c1b90d216a9fefe46b79a + katalyst-thermite (1.0.0) + language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc + lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87 + logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 + loofah (2.25.1) sha256=d436c73dbd0c1147b16c4a41db097942d217303e1f7728704b37e4df9f6d2e04 + marcel (1.2.1) sha256=1678e9360e32f9eafa917c80029e2f6d10b2715c66a4b87b6d0da9b9cd1f859f + minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1 + nokogiri (1.19.4-aarch64-linux-gnu) sha256=1269fb644a6de405057a53dd5c762b1209b43ca7424f839454d3dbc677c31a8f + nokogiri (1.19.4-aarch64-linux-musl) sha256=35c65b9ce72b3bb03207bdbe7067915019dc18c1b9b59139684bd6690fdd01af + nokogiri (1.19.4-arm-linux-gnu) sha256=a301313e38bb065d68239e79734bcd6f56fb6efaacebde29e9abf2a4735340ca + nokogiri (1.19.4-arm-linux-musl) sha256=588923c101bcfa78869734d247d25b598674323e7f22474fc468f6e5647311eb + nokogiri (1.19.4-arm64-darwin) sha256=a46db9853286e6597b36ebc6953817d15acf3a299583eb3f89fdc6f91dd63527 + nokogiri (1.19.4-x86_64-darwin) sha256=7fd17057d3e1f00e9954a74b3cd76595d3d4a5ef233b7ed9599047c204f70551 + nokogiri (1.19.4-x86_64-linux-gnu) sha256=379fae440b28915e3f19d752ce2dcf8465ed2b2fbefd2a7ca0dd497bc981a06a + nokogiri (1.19.4-x86_64-linux-musl) sha256=17dfb7c1fa194ae02fbf7c51a7afc8d278045ab3fdacfd86f91d02d7b274470b + parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356 + parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54 + pp (0.6.4) sha256=dfcb0fce700c41456265922884f9fe195d7fbb0674a3578e6c0f69588e82b570 + prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 + prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85 + psych (5.4.0) sha256=14f72d69a611af663d7d70e4a7b67d9eb1f3ae9f8d916b478961d5a0075ba5b7 + raabro (1.4.0) sha256=d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882 + racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f + rack (3.2.6) sha256=5ed78e1f73b2e25679bec7d45ee2d4483cc4146eb1be0264fc4d94cb5ef212c2 + rack-session (2.1.2) sha256=595434f8c0c3473ae7d7ac56ecda6cc6dfd9d37c0b2b5255330aa1576967ffe8 + rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463 + rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868 + rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d + rails-html-sanitizer (1.7.0) sha256=28b145cceaf9cc214a9874feaa183c3acba036c9592b19886e0e45efc62b1e89 + railties (8.1.3) sha256=913eb0e0cb520aac687ffd74916bd726d48fa21f47833c6292576ef6a286de22 + rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a + rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701 + rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192 + regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb + reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835 + rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587 + rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d + rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836 + rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47 + rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c + rubocop (1.88.0) sha256=e420ddf1662d0ef34bc8a2910ac4b396a7ddda0b51a708264405241734b08e0b + rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035 + rubocop-capybara (2.23.0) sha256=f9ea1ba3a7561ee8e88cf76fc378ce517ce5327155f305ee7b5c2500e5aee357 + rubocop-factory_bot (2.28.0) sha256=4b17fc02124444173317e131759d195b0d762844a71a29fe8139c1105d92f0cb + rubocop-katalyst (3.0.1) sha256=c4013ac6877596b67aef17de980dfda39ad1f5107d0ebd3356ec7a5d88bf438f + rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834 + rubocop-rails (2.35.5) sha256=f00b3c936002ba8e9ac62e8607c54bb24cda44b36e41b9c7e4f3872e1b0f3fe3 + rubocop-rake (0.7.1) sha256=3797f2b6810c3e9df7376c26d5f44f3475eda59eb1adc38e6f62ecf027cbae4d + rubocop-rspec (3.10.2) sha256=0b3e2ecc592cd10ecbf0095bb58d1e357905276e069643523cc19eb7495f65e2 + rubocop-rspec_rails (2.32.0) sha256=4a0d641c72f6ebb957534f539d9d0a62c47abd8ce0d0aeee1ef4701e892a9100 + ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 + securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 + solid_queue (1.4.0) sha256=e6a18d196f0b27cb6e3c77c5b31258b05fb634f8ed64fb1866ed164047216c2a + stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1 + thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 + timeout (0.6.1) sha256=78f57368a7e7bbadec56971f78a3f5ecbcfb59b7fcbb0a3ed6ddc08a5094accb + tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f + tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b + unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42 + unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f + uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6 + useragent (0.16.11) sha256=700e6413ad4bb954bb63547fa098dddf7b0ebe75b40cc6f93b8d54255b173844 + zeitwerk (2.8.2) sha256=7212a61311083c604184b1ea2574b9aa05cd14f855a0841c06985cabe9181d12 + BUNDLED WITH 4.0.14 diff --git a/lib/generators/thermite/install/active_storage/USAGE b/lib/generators/thermite/install/active_storage/USAGE new file mode 100644 index 0000000..42362bb --- /dev/null +++ b/lib/generators/thermite/install/active_storage/USAGE @@ -0,0 +1,15 @@ +Description: + Installs Active Storage following Katalyst conventions, on fresh or existing apps. + + Writes a Katalyst-flavoured config/storage.yml (Disk locally, S3 elsewhere) and + points config.active_storage.service at :s3 for every non dev/test environment. + + To use the S3 service in deployed environments, add `aws-sdk-s3` to your Gemfile. + +Example: + bin/rails thermite:install:active_storage + + This will: + Create config/storage.yml + Install the Active Storage migrations + Set the Active Storage service to :s3 (non dev/test environments) diff --git a/lib/generators/thermite/install/active_storage/active_storage_generator.rb b/lib/generators/thermite/install/active_storage/active_storage_generator.rb new file mode 100644 index 0000000..b6abe6c --- /dev/null +++ b/lib/generators/thermite/install/active_storage/active_storage_generator.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require "rails" +require "rails/generators" + +# Installs Active Storage following Katalyst conventions, on fresh or existing apps. +module Thermite + module Install + class ActiveStorageGenerator < Rails::Generators::Base + def self.source_root + File.expand_path("templates", __dir__) + end + + def copy_storage_config + template "config/storage.yml" + end + + # update tops up an existing install; install lays the base for a new one. + def install_migrations + rails_command active_storage_installed? ? "active_storage:update" : "active_storage:install" + end + + def configure_services + environments.each do |pathname| + next if local?(pathname) + + gsub_file pathname, /(# )?config\.active_storage\.service\s*=.*/, + "config.active_storage.service = :s3", verbose: false + end + end + + private + + def environments + Pathname(destination_root).join("config/environments").glob("*.rb") + end + + def active_storage_installed? + Pathname(destination_root).join("db/migrate").glob("*create_active_storage_tables*").any? + end + + def local?(pathname) + %w[development test].include?(pathname.basename(".rb").to_s) + end + + # Overridable at runtime via RAILS_ASSETS_BUCKET_ID (see storage.yml). + def asset_bucket_name + name = if defined?(Rails.application) && Rails.application + Rails.application.class.module_parent_name + else + File.basename(destination_root) + end + + "#{name.to_s.underscore.dasherize}-web-assets" + end + end + end +end diff --git a/lib/generators/thermite/install/active_storage/templates/config/storage.yml.tt b/lib/generators/thermite/install/active_storage/templates/config/storage.yml.tt new file mode 100644 index 0000000..6789e1c --- /dev/null +++ b/lib/generators/thermite/install/active_storage/templates/config/storage.yml.tt @@ -0,0 +1,12 @@ +test: + service: Disk + root: <%%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%%= Rails.root.join("storage") %> + +s3: + service: S3 + region: ap-southeast-2 + bucket: <%%= ENV.fetch("RAILS_ASSETS_BUCKET_ID", "<%= asset_bucket_name %>") %> diff --git a/lib/thermite/tasks.rb b/lib/thermite/tasks.rb index 9f7128e..31d484a 100644 --- a/lib/thermite/tasks.rb +++ b/lib/thermite/tasks.rb @@ -6,5 +6,10 @@ task :solid_queue do Rails::Command.invoke :generate, ["thermite:install:solid_queue"] end + + desc "Install Active Storage" + task :active_storage do + Rails::Command.invoke :generate, ["thermite:install:active_storage"] + end end end diff --git a/spec/fixtures/app/config/environments/production.rb b/spec/fixtures/app/config/environments/production.rb index 3eb8294..aec3589 100644 --- a/spec/fixtures/app/config/environments/production.rb +++ b/spec/fixtures/app/config/environments/production.rb @@ -5,4 +5,7 @@ # Active Job (for thermite:install:solid_queue) # config.active_job.queue_adapter = :async # config.solid_queue.connects_to = { database: { writing: :queue } } + + # Active Storage (for thermite:install:active_storage) — replaced with :s3 + config.active_storage.service = :local end diff --git a/spec/fixtures/app/config/environments/staging.rb b/spec/fixtures/app/config/environments/staging.rb index a7ebfe4..54e349a 100644 --- a/spec/fixtures/app/config/environments/staging.rb +++ b/spec/fixtures/app/config/environments/staging.rb @@ -5,4 +5,7 @@ # Active Job (for thermite:install:solid_queue) # config.active_job.queue_adapter = :async # config.solid_queue.connects_to = { database: { writing: :queue } } + + # Active Storage (for thermite:install:active_storage) — replaced with :s3 + config.active_storage.service = :local end diff --git a/spec/generators/thermite/install/active_storage_generator_spec.rb b/spec/generators/thermite/install/active_storage_generator_spec.rb new file mode 100644 index 0000000..0d94aac --- /dev/null +++ b/spec/generators/thermite/install/active_storage_generator_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require "rails_helper" + +require "generators/thermite/install/active_storage/active_storage_generator" + +RSpec.describe Thermite::Install::ActiveStorageGenerator do + # Migrations are delegated to Active Storage's own rake tasks; don't shell out under test. + before { allow(generator).to receive(:rails_command) } + + it "completes successfully" do + expect { generator.invoke_all }.not_to raise_error + end + + it "writes the storage config" do + generator.invoke_all + + assert_file "config/storage.yml" + end + + it "points non-standard environments like staging at the s3 service" do + generator.invoke_all + + assert_file "config/environments/staging.rb", /config\.active_storage\.service = :s3/ + end + + it "installs the base migration when Active Storage isn't installed yet" do + generator.install_migrations + + expect(generator).to have_received(:rails_command).with("active_storage:install") + end + + it "updates instead when a create_active_storage_tables migration already exists" do + migrate_dir = File.join(destination_root, "db/migrate") + mkdir_p migrate_dir + File.write(File.join(migrate_dir, "20200101000000_create_active_storage_tables.active_storage.rb"), "") + + generator.install_migrations + + expect(generator).to have_received(:rails_command).with("active_storage:update") + end +end