Last active
October 30, 2021 16:16
-
-
Save jdickey/5f06dd10252ddd148b9660f4dfe0abe9 to your computer and use it in GitHub Desktop.
Attempting Automation of Rails App Generation with .railsrc and App Generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ## Intro | |
| (NB: Forgive the many content-free revisions; it has been many moons since I have created a Gist with attachments, and getting the | |
| ordering currect is maddeningly non-trivial.) | |
| After too many years doing this, never mind how many, I'm attempting to automate the generation of Rails apps using a | |
| `.railsrc` file and an app-generation template. The `.railsrc` file, largely cribbed from numerous others, appears to work | |
| splendidly. | |
| The template file, which should automate setup of the newly-created app to current shop standards, is being developed incrementally; increments largely separated by *the next wall I hit*. The current "wall" has had me stopped cold for two days; my Stack Overflow skills and Google-fu are clearly not up to the task. | |
| ## Possibly Relevant Background Items | |
| 1. Ruby is installed by way of [`rbenv`](https://github.com/rbenv/rbenv#readme), which we have used for *years* without known issues. We are presently on Ruby 3.0.2p107. | |
| 2. I'm attempting to continue our long-time shop-standard usage of [`rbenv-gemsets`](https://github.com/jf/rbenv-gemset#readme), which has hitherto (before attempted use of an app generator) saved our proverbial bacon often enough that I still have *some* hair. It supports our experimentation and evolution of candidate Gems for use, without polluting the system Gem repository. | |
| ## Problem Statement | |
| When I generate a new app using the template, everything appears to work fine setting up the new app up to and including when `bundle install` is run. However, immediately after bundling reports success, the Gems are installed not in the Gemset, but in the `rbenv` Gem repository for the current Ruby version. Running the installation process manually stores the Gems in the expected place. | |
| When I started writing this Gist, I had several open questions that I was seeking help for; I found answers to most of them on my own, save for two: | |
| 1. Why are the bundled Gems being installed in the `rbenv` Gem repository rather than my gemset, as occurs when I proceed manually? | |
| 2. Why has `app/javascript/packs/application.js` changed after completion of the template, forcing `public/packs/manifest.json` to also change? | |
| Help? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # This is my ~/.railsrc file. There are many like it, but this one is mine. | |
| # known defaults | |
| --no-api | |
| --no-dev | |
| --no-edge | |
| --no-no-rc # obviously... | |
| --no-skip-action-cable | |
| --no-skip-active-record | |
| --no-skip-gemfile | |
| --no-skip-git | |
| --no-skip-javascript | |
| --no-skip-keeps | |
| --no-skip-listen | |
| --no-skip-puma | |
| --no-skip-sprockets | |
| --no-skip-turbolinks | |
| --no-skip-yarn | |
| --skip-coffee | |
| --skip-namespace | |
| --skip-system-test # ditto | |
| --skip-test # Don't use MiniTest; we'll add RSpec later | |
| # --ruby=/path/to/executable/ruby | |
| # --template=/path/to/app/template | |
| # Custom/non-default values | |
| --database=postgresql | |
| --skip-action-mailer | |
| --skip-active-storage | |
| --skip-bootsnap | |
| --skip-bundle | |
| --skip-spring | |
| --webpack=stimulus | |
| # --template=/path/to/template.rb |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # frozen_string_literal: true | |
| # DRAFT Rails Application Template | |
| # Copyright 2021, Jeff Dickey/Seven Sigma Agility | |
| # Licensed for public use under the MIT License. | |
| # | |
| # This template sets up a newly-created Rails app, in conjunction with the | |
| # associated `.railsrc` file. This (mostly; see below) automates processes that | |
| # have been in use for a decade or so, over dozens of apps. Better late than | |
| # never, yes? B-) | |
| # | |
| # Sample usage would look something like | |
| # ``` | |
| # rails new the_app -m ./template.rb | |
| # ``` | |
| # where `the_app` would be replaced by the actual name of your new application. | |
| # | |
| # First, we set up our Gemfile. This is somewhat abbreviated from our standard | |
| # since this whole script runs essentially as a hook within Rails' template | |
| # processing, which has already created a Gemfile by the time we get here. It | |
| # Would Be Very Nice if that was documented a bit more clearly in the Rails | |
| # Guides, but oh well. | |
| # | |
| # This setup assumes `zsh`, `rbenv`, and `rbenv-gemset`. It further asdumes that | |
| # it is being run from what will become the parent directory of the new app, in | |
| # which exists a `.ruby-version` file, and possibly an `.rbenv-gemset` directory | |
| # that will be **DELETED** if it exists. | |
| ################################################################################ | |
| # FIXME: Outdated, hardcoded Node packages are *evil*. | |
| gem 'redis', '~> 4.0' | |
| gem 'bcrypt', '~> 3.1.7' | |
| gem "bundler-audit", "~> 0.9.0" | |
| gem "dotenv-rails", "~> 2.7" | |
| gem "dry-monads", "~> 1.4" | |
| gem "ice_nine", "~> 0.11.2" | |
| gem "slim-rails", "~> 3.3" | |
| gem "foreman", "~> 0.87.2" | |
| gem "sassc-rails", "~> 2.1" | |
| gem "bundler", "~> 2.2" | |
| gem "database_cleaner", "~> 2.0" | |
| gem "pry-byebug", "~> 3.8" | |
| gem "pry-doc", "~> 1.2" | |
| gem "pry-rails", "~> 0.3.9" | |
| # gem "stimulus_reflex", "~> 3.4" | |
| gem "nokogiri", ">= 1.12.5" | |
| gem "view_component", "~> 2.40", require: 'view_component/engine' | |
| gem_group :development, :test do | |
| gem "flay", "~> 2.12" | |
| gem "flog", "~> 4.6" | |
| gem "inch", "~> 0.8.0" | |
| gem "reek", "~> 6.0" | |
| gem "rubocop-rails", "~> 2.12" | |
| gem "yard", "~> 0.9.26" | |
| gem "fabrication", "~> 2.22" | |
| gem "simplecov", "~> 0.21.2" | |
| gem "ffaker", "~> 2.19" | |
| gem "rspec-html-matchers", "~> 0.9.4" | |
| gem "html2slim", "~> 0.2.0" | |
| end | |
| gem_group :test do | |
| gem 'capybara', '>= 3.26' | |
| gem 'selenium-webdriver' | |
| # Easy installation and use of web drivers to run system tests with browsers | |
| gem 'webdrivers' | |
| gem "rspec-rails", "~> 5.0" | |
| end | |
| # | |
| # Gemfile created; on to | |
| # * initial setup of the `rbenv` gemset; and | |
| # * running (what as far as we care is the first) `bundle install`. | |
| # | |
| run "cp ../.ruby-version ." | |
| run 'rm -f .rbenv-gemset' | |
| run "mkdir -p bin tmp/gemset" | |
| run "rbenv gemset delete #{ENV['RBENV_VERSION']} ./tmp/gemset 2>&1 > /dev/null" | |
| run "rbenv gemset create #{ENV['RBENV_VERSION']} ./tmp/gemset" | |
| run 'rm -f .rbenv-gemset' | |
| run "rbenv gemset init ./tmp/gemset" | |
| # run "echo ./tmp/gemset > .rbenv-gemset" | |
| run 'cd #{@app_name}; rbenv rehash; echo "Active gemsets: " `rbenv gemset active`' | |
| run "bundle install" | |
| # | |
| # Make sure we've got all our binstubs set up so that Rails will simply use them | |
| # (instead of complaining that they're set up for BUndler.) | |
| # | |
| run "bundle binstubs --all --force" | |
| run "(yes | rails app:update:bin)", capture: true | |
| rails_command 'app:update:bin' | |
| # | |
| # Create the database role/user for the app. | |
| # | |
| run "createuser -dw #{@app_name}" | |
| # | |
| # Tell Rails to use the `fabrication` Gem fixture data. It's pretty cool that | |
| # Rails/Thor has the `inject_data_file` method; this used to be the first of | |
| # two steps performed manually by directly editing files. | |
| # | |
| inject_into_file "config/application.rb", | |
| after: " config.generators.system_tests = nil\n" do <<-EOS | |
| config.generators do |gen| | |
| gen.test_framework :rspec, fixture_replacement: :fabrication | |
| gen.fixture_replacement :fabrication, dir: "spec/fabricators" | |
| end | |
| EOS | |
| end | |
| # | |
| # Fix the generated `Rails.root.join` call in the development environment, else | |
| # Rubocop will kvetch about it whenever its Rake task is called, either directly | |
| # or as part of the default processing. (See the Rake task setup below.) | |
| # | |
| gsub_file('config/environments/development.rb', | |
| "Rails.root.join('tmp', 'caching-dev.txt')", | |
| "Rails.root.join('tmp/caching-dev.txt')") | |
| # | |
| # Create the initial (empty) development and test databases. | |
| # | |
| rails_command "db:create" | |
| rails_command "db:migrate" | |
| rails_command "db:seed" | |
| # | |
| # Now is a good time to get RSpec set up | |
| # | |
| rails_command 'generate rspec:install' | |
| # | |
| # Create a Procfile for `foreman` to use to run multiple processes, including | |
| # the web server running our app. | |
| # | |
| create_file "Procfile", <<-ENDIT | |
| webpack: bin/webpack-dev-server | |
| web: bin/rails server -p 3000 | |
| ENDIT | |
| # TBD: Find out why Foreman changes ports & we must explicitly specify 3000 | |
| run 'bin/foreman check' | |
| # | |
| # Set up Git and stage all existing files not specified in `.gitignore`. Note | |
| # that we aren't committing yet; the idea is that we'll have one single commit | |
| # after all steps in this generator script (and the surrounding Rails processing | |
| # code) will have been completed. | |
| # | |
| # TBD: Figure out how to define an after-all-generator-steps-completed hook. Not | |
| # just for this script; for the whole generator that this runs within. | |
| # | |
| run 'rm -rf .git' | |
| git init: '-b main' | |
| git add: '.' | |
| insert_into_file 'spec/spec_helper.rb', before: 'RSpec.configure do |config|' do <<-EOS | |
| require 'simplecov' | |
| SimpleCov.start do | |
| coverage_dir './tmp/coverage' | |
| add_filter '/lib/tasks' | |
| add_filter '/spec' | |
| add_filter '/tmp' | |
| end | |
| EOS | |
| end | |
| git add: 'spec/spec_helper.rb' | |
| # | |
| # Set up Webpacker. | |
| # TBD: In future, we'll likely add Stimulus JS, but not at present. | |
| # | |
| rails_command 'dev:cache' | |
| rails_command 'webpacker:install' | |
| rails_command 'webpacker:check_binstubs' | |
| git add: '.' | |
| # | |
| # We use Slim for view templates, not ERb as generated by default. Convert the | |
| # app-wide base view template. | |
| # | |
| run 'erb2slim -d app/views/layouts/application.html.erb' | |
| git add: 'app/views/layouts/' | |
| # | |
| # Add configuration files for Flay, Reek, and Rubocop | |
| # | |
| # Add .flayignore | |
| create_file '.flayignore', <<-ENDIT | |
| ./spec/**/*.rb | |
| ./tmp/**/* | |
| ENDIT | |
| git add: '.flayignore' | |
| # Add Reek config | |
| create_file 'config.reek', <<-ENDIT | |
| --- | |
| detectors: | |
| IrresponsibleModule: | |
| enabled: false | |
| MissingSafeMethod: | |
| enabled: false | |
| NestedIterators: | |
| max_allowed_nesting: 2 | |
| ignore_iterators: | |
| - lambda | |
| UncommunicativeVariableName: | |
| exclude: | |
| - Conversagence::Application | |
| UnusedPrivateMethod: | |
| enabled: true | |
| UtilityFunction: | |
| public_methods_only: true | |
| LongParameterList: | |
| max_params: 4 # If it's good enough for Sandi, it's good enough for us | |
| ENDIT | |
| git add: 'config.reek' | |
| # Add Rubocop config | |
| create_file '.rubocop.yml', <<ENDIT | |
| require: | |
| - rubocop-rails | |
| AllCops: | |
| TargetRubyVersion: 2.6 | |
| Bundler/OrderedGems: | |
| Exclude: | |
| - 'Gemfile' | |
| Metrics/BlockLength: | |
| Max: 300 | |
| Include: | |
| - 'test/**/*.rb' | |
| Metrics/ModuleLength: | |
| Max: 300 | |
| Include: | |
| - 'test/**/*.rb' | |
| # Style/AsciiComments: | |
| # Exclude: | |
| # - 'spec/web/views/home/index_spec.rb' | |
| Style/CommentedKeyword: | |
| Enabled: false | |
| # This silences 'Missing top-level class documentation comment.', which we | |
| # particularly want to do on (initially-)generated files. | |
| Style/Documentation: | |
| Enabled: false | |
| Include: | |
| - 'app/**/*.rb' | |
| - 'lib/**/*.rb' | |
| - 'test/**/*.rb' | |
| ### | |
| ### New cops added as of rubocop 0.85.1 and rubocop-rails 2.6.0 | |
| Layout/EmptyLinesAroundAttributeAccessor: | |
| Enabled: true | |
| Layout/SpaceAroundMethodCallOperator: | |
| Enabled: true | |
| Lint/DeprecatedOpenSSLConstant: | |
| Enabled: true | |
| Lint/MixedRegexpCaptureTypes: | |
| Enabled: true | |
| Lint/RaiseException: | |
| Enabled: true | |
| Lint/StructNewOverride: | |
| Enabled: true | |
| Style/ExponentialNotation: | |
| Enabled: true | |
| Style/HashEachMethods: | |
| Enabled: true | |
| Style/HashTransformKeys: | |
| Enabled: true | |
| Style/HashTransformValues: | |
| Enabled: true | |
| Style/RedundantRegexpCharacterClass: | |
| Enabled: true | |
| Style/RedundantRegexpEscape: | |
| Enabled: true | |
| Style/SlicingWithRange: | |
| Enabled: true | |
| ENDIT | |
| git add: '.rubocop.yml' | |
| # | |
| # Add Rake task definitions, and the default set and order of tasks to be run | |
| # by `rake` when invoked without a task name. | |
| # | |
| ## For Flay | |
| create_file "lib/tasks/flay.rake", <<-ENDIT | |
| # frozen_string_literal: true | |
| require 'flay' | |
| require 'flay_task' | |
| FlayTask.new do |t| | |
| t.verbose = true | |
| t.dirs = %w[app config lib] | |
| end | |
| ENDIT | |
| git add: 'lib/tasks/flay.rake' | |
| ## For Flog | |
| create_file 'lib/tasks/flog.rake', <<-ENDIT | |
| # frozen_string_literal: true | |
| require 'flog' | |
| require 'flog_task' | |
| class FlogTask < Rake::TaskLib | |
| attr_accessor :methods_only | |
| end | |
| FlogTask.new do |t| | |
| t.verbose = true | |
| t.threshold = 200 # default is 200 | |
| t.methods_only = true | |
| t.dirs = %w[app config lib] # Look, Ma; no specs! Run the tool manually every so often for those. | |
| end | |
| ENDIT | |
| git add: 'lib/tasks/flog.rake' | |
| ## For Inch | |
| create_file "lib/tasks/inch.rake", <<-ENDIT | |
| # frozen_string_literal: true | |
| require 'inch/rake' | |
| Inch::Rake::Suggest.new do |t| | |
| t.args = ['--no-undocumented', '--pedantic'] | |
| end | |
| ENDIT | |
| git add: 'lib/tasks/inch.rake' | |
| ## For Reek | |
| create_file "lib/tasks/reek.rake", <<-ENDIT | |
| # frozen_string_literal: true | |
| require 'reek/rake/task' | |
| Reek::Rake::Task.new do |t| | |
| t.config_file = 'config.reek' | |
| t.source_files = '{app,config,lib}/**/*.rb' | |
| t.reek_opts = '--sort-by smelliness --no-progress -s' | |
| # t.verbose = true | |
| end | |
| ENDIT | |
| git add: 'lib/tasks/reek.rake' | |
| ## For Rubocop | |
| create_file "lib/tasks/rubocop.rake", <<-ENDIT | |
| # frozen_string_literal: true | |
| require 'rubocop/rake_task' | |
| RuboCop::RakeTask.new(:rubocop) do |task| | |
| task.patterns = [ | |
| 'app/**/*.rb', | |
| 'config/**/*.rb', | |
| 'lib/**/*.rb', | |
| 'spec/**/*.rb' | |
| ] | |
| task.formatters = ['simple'] | |
| task.fail_on_error = true | |
| task.requires << 'rubocop-rails' | |
| task.options << '--config=.rubocop.yml' | |
| task.options << '--display-cop-names' | |
| end | |
| ENDIT | |
| git add: 'lib/tasks/rubocop.rake' | |
| ## For Bundler sub-tasks | |
| create_file "lib/tasks/bundler-audit.rake", <<-ENDIT | |
| # frozen_string_literal: true | |
| namespace :bundler do | |
| desc 'Run bundler-audit, updating its database and then verbosely checking' | |
| task :audit do | |
| system('bundler-audit check -uv') | |
| end | |
| end | |
| ENDIT | |
| git add: 'lib/tasks/bundler-audit.rake' | |
| ## Defaults | |
| # frozen_string_literal: true | |
| create_file "lib/tasks/defaults.rake", <<-ENDIT | |
| # frozen_string_literal: true | |
| default_tasks=%i[spec flog flay reek inch rubocop:auto_correct rubocop] | |
| bundler_tasks=[] | |
| if ENV['RAKE_BUNDLER'] || ENV['RAKE_BUNDLER_GRAPH'] | |
| bundler_tasks = %i[bundler:audit bundler:dependencies:count] | |
| if ENV['RAKE_BUNDLER_GRAPH'] | |
| bundler_tasks = %i[bundler:audit bundler:dependencies:graph bundler:dependencies:count] | |
| end | |
| default_tasks += bundler_tasks | |
| end | |
| # Rake::Task['default'].clear | |
| task default: default_tasks | |
| default_tasks = nil | |
| bundler_tasks = nil | |
| ENDIT | |
| git add: 'lib/tasks/defaults.rake' | |
| # | |
| # Install TailwindCSS et al | |
| # | |
| run 'yarn add tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9' | |
| git add: 'package.json yarn.lock' | |
| insert_into_file 'postcss.config.js', after: ' plugins: [' do <<-EOS | |
| require('tailwindcss'), | |
| EOS | |
| end | |
| git add: 'postcss.config.js' | |
| run 'node_modules/.bin/tailwindcss init' | |
| git add: 'tailwind.config.js' | |
| insert_into_file 'app/javascript/packs/application.js', after: 'Turbolinks.start()' do <<-EOS | |
| import "tailwindcss/tailwind.css" | |
| EOS | |
| end | |
| git add: 'app/javascript/packs/application.js' | |
| gsub_file('app/views/layouts/application.html.slim', 'stylesheet_link_tag', | |
| 'stylesheet_pack_tag') | |
| git add: 'app/views/layouts/application.html.slim' | |
| # | |
| # Let's just make everything Rubocop-clean before we're done. | |
| # | |
| rails_command 'rubocop:auto_correct' | |
| git add: 'app/ config/ spec/' | |
| run 'git add --force public/packs/manifest.json' | |
| # | |
| # NOTE: Prior to the initial commit, you'll need to run `git add .` from the | |
| # command line before manually making the initial commit. The only file seemingly | |
| # affected is `public/packs/manifest.json`. We need to figure out how to automate | |
| # this. Is there an "after-everything" hook supported by the Rails app generator? | |
| # | |
| # We'll run `hr` a couple of times so that it's visually apparent in the output | |
| # from generating the app where the end of our template is, and the start of more | |
| # commands executed by the base generator. | |
| # | |
| run "hr" | |
| run "hr" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment