├── .browserslistrc ├── config ├── locales │ ├── defaults │ │ ├── en.yml │ │ └── fr.yml │ ├── views │ │ ├── en.yml │ │ └── fr.yml │ └── models │ │ ├── en.yml │ │ └── fr.yml ├── initializers │ ├── sidekiq.rb │ ├── i18n.rb │ ├── generators.rb │ └── redis.rb ├── sidekiq.yml ├── routes.rb └── template.rb ├── .rubocop ├── .stylelintrc ├── app ├── controllers │ ├── pages_controller.rb │ └── application_controller.rb ├── views │ ├── pages │ │ └── home.html.erb │ └── layouts │ │ └── application.html.erb ├── template.rb ├── assets │ └── stylesheets │ │ └── application.scss └── javascript │ └── css │ └── application.css ├── Procfile ├── Procfile.dev ├── .overcommit.yml ├── db └── migrate │ ├── 20180208061510_enable_pg_crypto_extension.rb │ └── 20180208061509_create_friendly_id_slugs.rb ├── .eslintrc ├── .rubocop.yml ├── README.md.tt ├── LICENSE ├── Gemfile.tt ├── README.md └── template.rb /.browserslistrc: -------------------------------------------------------------------------------- 1 | >1% -------------------------------------------------------------------------------- /config/locales/defaults/en.yml: -------------------------------------------------------------------------------- 1 | en: -------------------------------------------------------------------------------- /config/locales/defaults/fr.yml: -------------------------------------------------------------------------------- 1 | fr: -------------------------------------------------------------------------------- /config/locales/views/en.yml: -------------------------------------------------------------------------------- 1 | en: -------------------------------------------------------------------------------- /config/locales/views/fr.yml: -------------------------------------------------------------------------------- 1 | fr: -------------------------------------------------------------------------------- /.rubocop: -------------------------------------------------------------------------------- 1 | --rails 2 | --extra-details 3 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard" 3 | } -------------------------------------------------------------------------------- /config/locales/models/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | activerecord: 3 | models: 4 | attributes: -------------------------------------------------------------------------------- /config/locales/models/fr.yml: -------------------------------------------------------------------------------- 1 | fr: 2 | activerecord: 3 | models: 4 | attributes: -------------------------------------------------------------------------------- /config/initializers/sidekiq.rb: -------------------------------------------------------------------------------- 1 | Rails.application.config.active_job.queue_adapter = :sidekiq -------------------------------------------------------------------------------- /app/controllers/pages_controller.rb: -------------------------------------------------------------------------------- 1 | class PagesController < ApplicationController 2 | def home 3 | end 4 | end -------------------------------------------------------------------------------- /config/sidekiq.yml: -------------------------------------------------------------------------------- 1 | :concurrency: 3 2 | :timeout: 60 3 | :verbose: true 4 | :queues: 5 | - default 6 | - mailers -------------------------------------------------------------------------------- /app/views/pages/home.html.erb: -------------------------------------------------------------------------------- 1 |

Hello from <%= Rails.application.class.parent_name %>!

-------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | 3 | scope '(:locale)', locale: /fr/ do 4 | root to: 'pages#home' 5 | end 6 | end -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | release: bundle exec rails db:migrate 2 | web: bundle exec puma -C config/puma.rb 3 | worker: bundle exec sidekiq -C config/sidekiq.yml 4 | -------------------------------------------------------------------------------- /Procfile.dev: -------------------------------------------------------------------------------- 1 | web: bin/rails server 2 | assets: bin/webpack-dev-server 3 | worker: bundle exec sidekiq -C config/sidekiq.yml 4 | guard: bundle exec guard 5 | -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | PreCommit: 2 | RuboCop: 3 | enabled: true 4 | command: ['bundle', 'exec', 'rubocop'] 5 | 6 | PrePush: 7 | Brakeman: 8 | enabled: true 9 | -------------------------------------------------------------------------------- /config/initializers/i18n.rb: -------------------------------------------------------------------------------- 1 | I18n.default_locale = :en 2 | I18n.available_locales = [:en, :fr] 3 | I18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')] -------------------------------------------------------------------------------- /db/migrate/20180208061510_enable_pg_crypto_extension.rb: -------------------------------------------------------------------------------- 1 | class EnablePgCryptoExtension < ActiveRecord::Migration[5.2] 2 | def change 3 | enable_extension 'pgcrypto' 4 | end 5 | end -------------------------------------------------------------------------------- /app/template.rb: -------------------------------------------------------------------------------- 1 | copy_file 'app/controllers/application_controller.rb', force: true 2 | copy_file 'app/controllers/pages_controller.rb' 3 | copy_file 'app/views/layouts/application.html.erb', force: true 4 | copy_file 'app/views/pages/home.html.erb' 5 | -------------------------------------------------------------------------------- /config/initializers/generators.rb: -------------------------------------------------------------------------------- 1 | Rails.application.config.generators do |g| 2 | # Disable generators we don't need. 3 | g.javascripts false 4 | g.stylesheets false 5 | g.assets false 6 | g.helper false 7 | g.test_framework false 8 | g.channel assets: false 9 | end -------------------------------------------------------------------------------- /config/initializers/redis.rb: -------------------------------------------------------------------------------- 1 | $redis = Redis.new 2 | 3 | url = ENV["REDISCLOUD_URL"] 4 | 5 | if url 6 | Sidekiq.configure_server do |config| 7 | config.redis = { url: url } 8 | end 9 | 10 | Sidekiq.configure_client do |config| 11 | config.redis = { url: url } 12 | end 13 | $redis = Redis.new(:url => url) 14 | end -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery with: :exception 3 | 4 | before_action :set_locale 5 | 6 | def set_locale 7 | I18n.locale = params.fetch(:locale, I18n.default_locale).to_sym 8 | end 9 | 10 | def default_url_options 11 | { locale: I18n.locale == I18n.default_locale ? nil : I18n.locale } 12 | end 13 | end -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | // Graphical variables 2 | // @import "config/fonts"; 3 | // @import "config/colors"; 4 | // @import "config/bootstrap_variables"; 5 | 6 | // External libraries 7 | // @import "bootstrap-sprockets"; 8 | // @import "bootstrap"; 9 | // @import "font-awesome-sprockets"; 10 | // @import "font-awesome"; 11 | 12 | // Your CSS partials 13 | // @import "layouts/index"; 14 | // @import "components/index"; 15 | // @import "pages/index"; 16 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint-config-airbnb-base", "prettier"], 3 | 4 | "plugins": ["prettier"], 5 | 6 | "env": { 7 | "browser": true 8 | }, 9 | 10 | "rules": { 11 | "prettier/prettier": "error" 12 | }, 13 | 14 | "parser": "babel-eslint", 15 | 16 | "settings": { 17 | "import/resolver": { 18 | "webpack": { 19 | "config": { 20 | "resolve": { 21 | "modules": ["frontend", "node_modules"] 22 | } 23 | } 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= Rails.application.class.parent_name %> 7 | <%= csrf_meta_tags %> 8 | <%= action_cable_meta_tag %> 9 | <%= stylesheet_pack_tag 'application', media: 'all' %> 10 | 11 | 12 | <%= yield %> 13 | <%= javascript_include_tag 'application' %> 14 | <%= javascript_pack_tag 'application' %> 15 | 16 | -------------------------------------------------------------------------------- /db/migrate/20180208061509_create_friendly_id_slugs.rb: -------------------------------------------------------------------------------- 1 | class CreateFriendlyIdSlugs < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :friendly_id_slugs do |t| 4 | t.string :slug, :null => false 5 | t.integer :sluggable_id, :null => false 6 | t.string :sluggable_type, :limit => 50 7 | t.string :scope 8 | t.datetime :created_at 9 | end 10 | add_index :friendly_id_slugs, :sluggable_id 11 | add_index :friendly_id_slugs, [:slug, :sluggable_type], length: { slug: 140, sluggable_type: 50 } 12 | add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], length: { slug: 70, sluggable_type: 50, scope: 70 }, unique: true 13 | add_index :friendly_id_slugs, :sluggable_type 14 | end 15 | end -------------------------------------------------------------------------------- /config/template.rb: -------------------------------------------------------------------------------- 1 | copy_file 'config/initializers/generators.rb' 2 | copy_file 'config/initializers/i18n.rb' 3 | copy_file 'config/initializers/sidekiq.rb' 4 | copy_file 'config/initializers/redis.rb' 5 | 6 | copy_file 'config/routes.rb', force: true 7 | 8 | insert_into_file 'config/environments/development.rb', after: /config\.action_mailer\.raise_delivery_errors = false\n/ do 9 | <<-RUBY 10 | config.action_mailer.default_url_options = { :host => "localhost:3000" } 11 | config.action_mailer.asset_host = "http://localhost:3000" 12 | RUBY 13 | end 14 | 15 | copy_file 'config/sidekiq.yml' 16 | 17 | remove_file 'config/locales/en.yml' 18 | copy_file 'config/locales/defaults/en.yml' 19 | copy_file 'config/locales/models/en.yml' 20 | copy_file 'config/locales/views/en.yml' 21 | copy_file 'config/locales/defaults/fr.yml' 22 | copy_file 'config/locales/models/fr.yml' 23 | copy_file 'config/locales/views/fr.yml' 24 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Rails: 2 | Enabled: true 3 | 4 | Metrics/LineLength: 5 | Max: 100 6 | 7 | AllCops: 8 | Exclude: 9 | - db/migrate/* 10 | - db/schema.rb 11 | - bin/* 12 | - node_modules/**/* 13 | 14 | Style/Documentation: 15 | Enabled: false 16 | 17 | Style/DoubleNegation: 18 | Enabled: false 19 | 20 | Style/ClassAndModuleChildren: 21 | Enabled: false 22 | 23 | Style/StructInheritance: 24 | Exclude: 25 | - app/policies/* 26 | 27 | Rails/InverseOf: 28 | Enabled: false 29 | 30 | Metrics/BlockLength: 31 | Exclude: 32 | - config/environments/development.rb 33 | - Guardfile 34 | - lib/tasks/auto_annotate_models.rake 35 | - config/initializers/simple_form_bootstrap.rb 36 | - lib/tasks/* 37 | - app/admin/* 38 | 39 | Layout/AlignHash: 40 | Enabled: False 41 | 42 | GlobalVars: 43 | AllowedVariables: 44 | - $redis 45 | 46 | Rails/Output: 47 | Exclude: 48 | - db/seeds.rb 49 | -------------------------------------------------------------------------------- /README.md.tt: -------------------------------------------------------------------------------- 1 | # <%= app_name %> 2 | 3 | This is a Rails <%= Rails::VERSION::MAJOR %> app created using [modern-rails-template][]. Please check README of the template repository to see the features available out of the box. To have a look to the new features introduced by Rails 5.2 (credentials for example), check this [article][]. 4 | 5 | ## How to use this project 6 | 7 | * We advise you to use [overmind][] to launch your processes in development. You just need to run `overmind s -f Procfile.dev`. Of course, another process manager, like [foreman][], would work too. 8 | * We advise you to deploy to [Heroku][]. Do not forget to add the [Redis] add-on. 9 | 10 | [article]: https://evilmartians.com/chronicles/rails-5-2-active-storage-and-beyond 11 | [modern-rails-template]: https://github.com/damienlethiec/modern-rails-template 12 | [here]: http://nvie.com/posts/a-successful-git-branching-model/ 13 | [overmind]: https://github.com/DarthSim/overmind 14 | [foreman]: https://github.com/ddollar/foreman 15 | [heroku]: https://www.heroku.com/ 16 | [redis]: https://devcenter.heroku.com/articles/heroku-redis 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Matt Brictson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Gemfile.tt: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | ruby '<%= RUBY_VERSION %>' 5 | 6 | gem 'pg'<%= gemfile_requirement('pg') %> 7 | gem 'puma'<%= gemfile_requirement('puma') %> 8 | gem 'rails', '<%= Rails.version %>' 9 | gem 'sass-rails'<%= gemfile_requirement('sass-rails') %> 10 | 11 | gem 'uglifier'<%= gemfile_requirement('uglifier') %> 12 | gem 'webpacker' 13 | 14 | gem 'jbuilder'<%= gemfile_requirement('jbuilder') %> 15 | gem 'redis'<%= gemfile_requirement('redis') %> 16 | gem 'turbolinks'<%= gemfile_requirement('turbolinks') %> 17 | 18 | gem 'bootsnap', '>= 1.1.0', require: false 19 | 20 | gem 'rails-i18n' 21 | 22 | gem 'sidekiq' 23 | gem 'sidekiq-failures' 24 | 25 | gem 'friendly_id' 26 | 27 | group :development, :test do 28 | gem 'pry-byebug' 29 | gem 'pry-rails' 30 | end 31 | 32 | group :development do 33 | gem 'listen'<%= gemfile_requirement('listen') %> 34 | gem 'spring' 35 | gem 'spring-watcher-listen'<%= gemfile_requirement('spring-watcher-listen') %> 36 | 37 | gem 'annotate' 38 | gem 'awesome_print' 39 | gem 'bullet' 40 | gem 'rails-erd' 41 | 42 | gem 'brakeman', require: false 43 | gem 'overcommit' 44 | gem 'rubocop', require: false 45 | 46 | gem 'guard' 47 | gem 'guard-bundler', require: false 48 | gem 'guard-livereload', require: false 49 | gem 'rack-livereload' 50 | 51 | gem 'better_errors' 52 | gem 'binding_of_caller' 53 | gem 'web-console'<%= gemfile_requirement('web-console') %> 54 | end 55 | -------------------------------------------------------------------------------- /app/javascript/css/application.css: -------------------------------------------------------------------------------- 1 | /** 2 | * This injects Tailwind's base styles, which is a combination of 3 | * Normalize.css and some additional base styles. 4 | * 5 | * You can see the styles here: 6 | * https://github.com/tailwindcss/tailwindcss/blob/master/css/preflight.css 7 | * 8 | * If using `postcss-import`, you should import this line from it's own file: 9 | * 10 | * @import "./tailwind-preflight.css"; 11 | * 12 | * See: https://github.com/tailwindcss/tailwindcss/issues/53#issuecomment-341413622 13 | */ 14 | @tailwind preflight; 15 | 16 | /** 17 | * Here you would add any of your custom component classes; stuff that you'd 18 | * want loaded *before* the utilities so that the utilities could still 19 | * override them. 20 | * 21 | * Example: 22 | * 23 | * .btn { ... } 24 | * .form-input { ... } 25 | * 26 | * Or if using a preprocessor or `postcss-import`: 27 | * 28 | * @import "components/buttons"; 29 | * @import "components/forms"; 30 | */ 31 | 32 | /** 33 | * This injects all of Tailwind's utility classes, generated based on your 34 | * config file. 35 | * 36 | * If using `postcss-import`, you should import this line from it's own file: 37 | * 38 | * @import "./tailwind-utilities.css"; 39 | * 40 | * See: https://github.com/tailwindcss/tailwindcss/issues/53#issuecomment-341413622 41 | */ 42 | @tailwind utilities; 43 | 44 | /** 45 | * Here you would add any custom utilities you need that don't come out of the 46 | * box with Tailwind. 47 | * 48 | * Example : 49 | * 50 | * .bg-pattern-graph-paper { ... } 51 | * .skew-45 { ... } 52 | * 53 | * Or if using a preprocessor or `postcss-import`: 54 | * 55 | * @import "utilities/background-patterns"; 56 | * @import "utilities/skew-transforms"; 57 | */ 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Modern Rails Template 2 | 3 | ## Description 4 | 5 | This is the rails template I used for my Rails 5.2 projects as a freelance developer. Its goal is to allow to begin new rails application easily, with a modern and efficient configuration and with good set of defaults. The project is still very much a work in progress. So do not expect it to be 100% bug free. [Contributions][], ideas and help are really welcome. 6 | 7 | This project is inspired by the template developed by Matt Brictson. Have a look [here][] to compare both. 8 | 9 | ## Requirements 10 | 11 | This template currently works with: 12 | 13 | * Rails 5.2.x 14 | * PostgreSQL 15 | 16 | ## Installation 17 | 18 | _Optional._ 19 | 20 | To make this the default Rails application template on your system, create a `~/.railsrc` file with these contents: 21 | 22 | ``` 23 | --skip-coffee 24 | --webpack 25 | -d postgresql 26 | -T 27 | -m https://raw.githubusercontent.com/damienlethiec/modern-rails-template/master/template.rb 28 | ``` 29 | 30 | ## Usage 31 | 32 | To generate a Rails application using this template, pass the options below to `rails new`, like this: 33 | 34 | ``` 35 | rails new blog \ 36 | --skip-coffee \ 37 | --webpack \ 38 | -d postgresql \ 39 | -T \ 40 | -m https://raw.githubusercontent.com/damienlethiec/modern-rails-template/master/template.rb 41 | ``` 42 | 43 | _Remember that options must go after the name of the application._ The only database supported by this template is `postgresql`. 44 | 45 | If you’ve installed this template as your default (using `~/.railsrc` as described above), then all you have to do is run: 46 | 47 | ``` 48 | rails new blog 49 | ``` 50 | 51 | ## What does it do? 52 | 53 | The template will perform the following steps: 54 | 55 | 1. Ask for which option you want in this project 56 | 1. Generate your application files and directories 57 | 1. Add useful gems and good configs 58 | 1. Add the optional config specified 59 | 1. Commit everything to git 60 | 61 | ## What is included? 62 | 63 | Below is an extract of what this generator does. You can check all the features by following the code, especially in `template.rb` and in the `Gemfile`. 64 | 65 | ### Standard configuration 66 | 67 | * Change the default generators config (cf `config/initializers/generators.rb`) 68 | * Setup [I18n][] for English and French 69 | * Improve the main layout (cf `app/views/layouts/application.haml.erb`) to include webpack in the asset pipeline 70 | * Create a basic PagesController to have something to show when the app launch 71 | * Add a [Procfile][] for dev and a Procfile for production to manage the different processes you need to manage in a modern web application. 72 | * Add Javascript ([ESLint][]) and CSS ([Stylelint][]) linters with webpack 73 | * Add and configure the [friendly_id][] gem for slugging 74 | * Add and configure the [annotate][] gem to add useful comments in our models 75 | * Add and configure the [bullet][] gem to track N+1 queries 76 | * Add and configure the [rails_erd][] gem to generate automatically a schema of our database relationships 77 | * Add and configure the [sidekiq][] gem for background jobs. You can access the sidekiq dashboard in the app at `/sidekiq` (don't forget to limit its access in the routes if needed) 78 | * Add and configure [rubocop][] for style and [brakeman][] for security and add them to [overcommit][] git hooks 79 | * Add livereload for view and automatic bundle installs using [guard][], [guard-livereload][] and [guard-bundler][] 80 | * Add [better-errors][] for easier debugging 81 | * Add [awesome-print][] for easier exploration in the terminal 82 | 83 | ### Additional options 84 | 85 | When you launch a new rails app with the template, a few questions will be asked. Answer 'y' or 'yes' to unable the given option. 86 | 87 | * If you need authentication, the [devise][] gem can be added and configured directly 88 | * If you also need authorization, [pundit][] can be added and configured too. 89 | * You can choose to use [Haml][] instead of `erb` 90 | * You can decide to use [UUID][] as the primary key for Active Record 91 | * If you feel adventurous, you can choose to use the [komponent][] gem and build your front-end following the workflow describe in this great [article][] 92 | * You can include [tailwindcss][] in your project. The configure is inspired by the following Gorails [episode][] 93 | * Finally, you can choose to create a Github repository for you project and push it directly. 94 | 95 | ## How does it work? 96 | 97 | This project works by hooking into the standard Rails application templates system, with some caveats. The entry point is the `template.rb` file in the root of this repository. 98 | 99 | Normally, Rails only allows a single file to be specified as an application template (i.e. using the `-m ` option). To work around this limitation, the first step this template performs is a `git clone` of the `damienlethiec/modern-rails-template` repository to a local temporary directory. 100 | 101 | This temporary directory is then added to the `source_paths` of the Rails generator system, allowing all of its ERb templates and files to be referenced when the application template script is evaluated. 102 | 103 | Rails generators are very lightly documented; what you’ll find is that most of the heavy lifting is done by [Thor][]. The most common methods used by this template are Thor’s `copy_file`, `template`, and `gsub_file`. 104 | 105 | ## Contributing 106 | 107 | If you want to contribute, please have a look to the issues in this repository and pick one you are interested in. You can then clone the project and submit a pull request. We also happily welcome new idea and, of course, bug reports. 108 | 109 | [thor]: https://github.com/erikhuda/thor 110 | [here]: https://github.com/mattbrictson/rails-template 111 | [contributions]: https://github.com/damienlethiec/modern-rails-template#contributing 112 | [procfile]: https://devcenter.heroku.com/articles/procfile 113 | [i18n]: http://guides.rubyonrails.org/i18n.html 114 | [uuid]: https://lab.io/articles/2017/04/13/uuids-rails-5-1/ 115 | [eslint]: https://eslint.org/ 116 | [stylelint]: https://stylelint.io/ 117 | [friendly_id]: https://github.com/norman/friendly_id 118 | [annotate]: https://github.com/ctran/annotate_models 119 | [bullet]: https://github.com/flyerhzm/bullet 120 | [rails_erd]: https://github.com/voormedia/rails-erd 121 | [sidekiq]: https://github.com/mperham/sidekiq 122 | [rubocop]: http://rubocop.readthedocs.io/en/latest/ 123 | [brakeman]: https://brakemanscanner.org/ 124 | [overcommit]: https://github.com/brigade/overcommit 125 | [guard]: https://github.com/guard/guard 126 | [guard-livereload]: https://github.com/guard/guard-livereload 127 | [guard-bundler]: https://github.com/guard/guard-bundler 128 | [better-errors]: https://github.com/charliesome/better_errors 129 | [xray-rails]: https://github.com/brentd/xray-rails 130 | [awesome-print]: https://github.com/michaeldv/awesome_print 131 | [table-print]: https://github.com/arches/table_print 132 | [devise]: https://github.com/plataformatec/devise 133 | [pundit]: https://github.com/varvet/pundit 134 | [haml]: http://haml.info/ 135 | [komponent]: https://github.com/komposable/komponent 136 | [article]: https://evilmartians.com/chronicles/evil-front-part-1 137 | [tailwindcss]: tailwindcss.com 138 | [episode]: https://gorails.com/episodes/tailwind-css-framework-with-rails 139 | -------------------------------------------------------------------------------- /template.rb: -------------------------------------------------------------------------------- 1 | require "fileutils" 2 | require "shellwords" 3 | require "tmpdir" 4 | 5 | RAILS_REQUIREMENT = ">= 5.2.2" 6 | 7 | def apply_template! 8 | assert_minimum_rails_version 9 | add_template_repository_to_source_path 10 | 11 | # temporary fix bootsnap bug 12 | # comment_lines 'config/boot.rb', /bootsnap/ 13 | 14 | template "Gemfile.tt", force: true 15 | template 'README.md.tt', force: true 16 | apply 'config/template.rb' 17 | apply 'app/template.rb' 18 | copy_file 'Procfile' 19 | copy_file 'Procfile.dev' 20 | 21 | ask_optional_options 22 | 23 | install_optional_gems 24 | 25 | after_bundle do 26 | setup_uuid if @uuid 27 | 28 | setup_front_end 29 | setup_npm_packages 30 | optional_options_front_end 31 | 32 | setup_gems 33 | 34 | run 'bundle binstubs bundler --force' 35 | 36 | run 'rails db:create db:migrate' 37 | 38 | setup_git 39 | push_github if @github 40 | setup_overcommit 41 | end 42 | end 43 | 44 | def assert_minimum_rails_version 45 | requirement = Gem::Requirement.new(RAILS_REQUIREMENT) 46 | rails_version = Gem::Version.new(Rails::VERSION::STRING) 47 | return if requirement.satisfied_by?(rails_version) 48 | 49 | prompt = "This template requires Rails #{RAILS_REQUIREMENT}. "\ 50 | "You are using #{rails_version}. Continue anyway?" 51 | exit 1 if no?(prompt) 52 | end 53 | 54 | # Add this template directory to source_paths so that Thor actions like 55 | # copy_file and template resolve against our source files. If this file was 56 | # invoked remotely via HTTP, that means the files are not present locally. 57 | # In that case, use `git clone` to download them to a local temporary dir. 58 | def add_template_repository_to_source_path 59 | if __FILE__ =~ %r{\Ahttps?://} 60 | source_paths.unshift(tempdir = Dir.mktmpdir("rails-template-")) 61 | at_exit { FileUtils.remove_entry(tempdir) } 62 | git :clone => [ 63 | "--quiet", 64 | "https://github.com/damienlethiec/modern-rails-template", 65 | tempdir 66 | ].map(&:shellescape).join(" ") 67 | else 68 | source_paths.unshift(File.dirname(__FILE__)) 69 | end 70 | end 71 | 72 | def gemfile_requirement(name) 73 | @original_gemfile ||= IO.read("Gemfile") 74 | req = @original_gemfile[/gem\s+['"]#{name}['"]\s*(,[><~= \t\d\.\w'"]*)?.*$/, 1] 75 | req && req.gsub("'", %(")).strip.sub(/^,\s*"/, ', "') 76 | end 77 | 78 | 79 | 80 | def ask_optional_options 81 | @devise = yes?('Do you want to implement authentication in your app with the Devise gem?') 82 | @pundit = yes?('Do you want to manage authorizations with Pundit?') if @devise 83 | @uuid = yes?('Do you want to use UUID for active record primary?') 84 | @haml = yes?('Do you want to use Haml instead of EBR?') 85 | @komponent = yes?('Do you want to adopt a component based design for your front-end?') 86 | @tailwind = yes?('Do you want to use Tailwind as a CSS framework?') 87 | @github = yes?('Do you want to push your project to Github?') 88 | end 89 | 90 | def install_optional_gems 91 | add_devise if @devise 92 | add_pundit if @pundit 93 | add_komponent if @komponent 94 | add_haml if @haml 95 | end 96 | 97 | def add_devise 98 | insert_into_file 'Gemfile', "gem 'devise'\n", after: /'friendly_id'\n/ 99 | insert_into_file 'Gemfile', "gem 'devise-i18n'\n", after: /'friendly_id'\n/ 100 | end 101 | 102 | def add_pundit 103 | insert_into_file 'Gemfile', "gem 'pundit'\n", after: /'friendly_id'\n/ 104 | end 105 | 106 | def add_haml 107 | insert_into_file 'Gemfile', "gem 'haml'\n", after: /'friendly_id'\n/ 108 | insert_into_file 'Gemfile', "gem 'haml-rails', git: 'git://github.com/indirect/haml-rails.git'\n", after: /'friendly_id'\n/ 109 | end 110 | 111 | def add_komponent 112 | insert_into_file 'Gemfile', "gem 'komponent'\n", after: /'friendly_id'\n/ 113 | end 114 | 115 | 116 | 117 | def setup_uuid 118 | copy_file 'db/migrate/20180208061510_enable_pg_crypto_extension.rb' 119 | insert_into_file 'config/initializers/generators.rb', " g.orm :active_record, primary_key_type: :uuid\n", after: /assets: false\n/ 120 | end 121 | 122 | 123 | 124 | def setup_front_end 125 | copy_file '.browserslistrc' 126 | copy_file 'app/assets/stylesheets/application.scss' 127 | remove_file 'app/assets/stylesheets/application.css' 128 | append_to_file 'Procfile', "assets: bin/webpack-dev-server\n" 129 | end 130 | 131 | def setup_npm_packages 132 | add_linters 133 | end 134 | 135 | def add_linters 136 | run 'yarn add eslint babel-eslint eslint-config-airbnb-base eslint-config-prettier eslint-import-resolver-webpack eslint-plugin-import eslint-plugin-prettier lint-staged prettier stylelint stylelint-config-standard --dev' 137 | copy_file '.eslintrc' 138 | copy_file '.stylelintrc' 139 | run 'yarn add normalize.css' 140 | end 141 | 142 | def optional_options_front_end 143 | add_css_framework if @tailwind 144 | end 145 | 146 | def add_css_framework 147 | run 'yarn add tailwindcss --dev' 148 | run './node_modules/.bin/tailwind init app/javascript/css/tailwind.js' 149 | copy_file 'app/javascript/css/application.css' 150 | append_to_file 'app/javascript/packs/application.js', "import '../css/application.css';\n" 151 | if @komponent 152 | append_to_file '.postcssrc.yml', " tailwindcss: './frontend/css/tailwind.js'" 153 | else 154 | append_to_file '.postcssrc.yml', " tailwindcss: './app/javascript/css/tailwind.js'" 155 | end 156 | end 157 | 158 | 159 | 160 | def setup_gems 161 | setup_friendly_id 162 | setup_annotate 163 | setup_bullet 164 | setup_erd 165 | setup_sidekiq 166 | setup_rubocop 167 | setup_brakeman 168 | setup_guard 169 | setup_komponent if @komponent 170 | setup_devise if @devise 171 | setup_pundit if @pundit 172 | setup_haml if @haml 173 | end 174 | 175 | def setup_friendly_id 176 | # temporal fix bug friendly_id generator 177 | copy_file 'db/migrate/20180208061509_create_friendly_id_slugs.rb' 178 | end 179 | 180 | def setup_annotate 181 | run 'rails g annotate:install' 182 | run 'bundle binstubs annotate' 183 | end 184 | 185 | def setup_bullet 186 | insert_into_file 'config/environments/development.rb', before: /^end/ do 187 | <<-RUBY 188 | Bullet.enable = true 189 | Bullet.alert = true 190 | RUBY 191 | end 192 | end 193 | 194 | def setup_erd 195 | run 'rails g erd:install' 196 | append_to_file '.gitignore', 'erd.pdf' 197 | end 198 | 199 | def setup_sidekiq 200 | run 'bundle binstubs sidekiq' 201 | append_to_file 'Procfile.dev', "worker: bundle exec sidekiq -C config/sidekiq.yml\n" 202 | append_to_file 'Procfile', "worker: bundle exec sidekiq -C config/sidekiq.yml\n" 203 | end 204 | 205 | def setup_rubocop 206 | run 'bundle binstubs rubocop' 207 | copy_file '.rubocop' 208 | copy_file '.rubocop.yml' 209 | run 'rubocop' 210 | end 211 | 212 | def setup_brakeman 213 | run 'bundle binstubs brakeman' 214 | end 215 | 216 | def setup_guard 217 | run 'bundle binstubs guard' 218 | run 'guard init livereload bundler' 219 | append_to_file 'Procfile.dev', "guard: bundle exec guard\n" 220 | insert_into_file 'config/environments/development.rb', " config.middleware.insert_after ActionDispatch::Static, Rack::LiveReload\n", before: /^end/ 221 | if @komponent 222 | insert_into_file 'Guardfile', %q( watch(%r{frontend/.+\.(#{rails_view_exts * '|'})$})) + "\n", after: /extensions.values.uniq\n/ 223 | end 224 | end 225 | 226 | def setup_komponent 227 | install_komponent 228 | add_basic_components 229 | end 230 | 231 | def install_komponent 232 | run 'rails g komponent:install --stimulus' 233 | insert_into_file 'config/initializers/generators.rb', " g.komponent stimulus: true, locale: true\n", after: /assets: false\n/ 234 | FileUtils.rm_rf 'app/javascript' 235 | insert_into_file 'app/controllers/application_controller.rb', " prepend_view_path Rails.root.join('frontend')\n", after: /exception\n/ 236 | end 237 | 238 | def add_basic_components 239 | run 'rails g component flash' 240 | insert_into_file 'app/views/layouts/application.html.erb', " <%= component 'flash' %>\n", after: /\n/ 241 | run 'rails g component button' 242 | run 'rails g component card' 243 | run 'rails g component form' 244 | end 245 | 246 | def setup_devise 247 | run 'rails generate devise:install' 248 | run 'rails g devise:i18n:views' 249 | insert_into_file 'config/routes.rb', after: /draw do\n/ do 250 | <<-RUBY 251 | require "sidekiq/web" 252 | mount Sidekiq::Web => '/sidekiq' 253 | RUBY 254 | end 255 | insert_into_file 'config/initializers/devise.rb', " config.secret_key = Rails.application.credentials.secret_key_base\n", before: /^end/ 256 | run 'rails g devise User' 257 | insert_into_file 'app/controllers/application_controller.rb', " before_action :authenticate_user!\n", after: /exception\n/ 258 | insert_into_file 'app/controllers/pages_controller.rb', " skip_before_action :authenticate_user!, only: :home\n", after: /ApplicationController\n/ 259 | end 260 | 261 | def setup_pundit 262 | insert_into_file 'app/controllers/application_controller.rb', before: /^end/ do 263 | <<-RUBY 264 | def user_not_authorized 265 | flash[:alert] = "You are not authorized to perform this action." 266 | redirect_to(root_path) 267 | end 268 | 269 | private 270 | 271 | def skip_pundit? 272 | devise_controller? || params[:controller] =~ /(^(rails_)?admin)|(^pages$)/ 273 | end 274 | RUBY 275 | end 276 | insert_into_file 'app/controllers/application_controller.rb', after: /exception\n/ do 277 | <<-RUBY 278 | include Pundit 279 | 280 | after_action :verify_authorized, except: :index, unless: :skip_pundit? 281 | after_action :verify_policy_scoped, only: :index, unless: :skip_pundit? 282 | 283 | rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized 284 | RUBY 285 | end 286 | run 'spring stop' 287 | run 'rails g pundit:install' 288 | end 289 | 290 | def setup_haml 291 | run 'HAML_RAILS_DELETE_ERB=true rake haml:erb2haml' 292 | end 293 | 294 | 295 | 296 | def setup_git 297 | git add: '.' 298 | git commit: '-m "End of the template generation"' 299 | end 300 | 301 | def push_github 302 | @hub = run 'brew ls --versions hub' 303 | if @hub 304 | run 'hub create' 305 | run 'git push origin master' 306 | run 'git push origin develop' 307 | run 'hub browse' 308 | else 309 | puts 'You first need to install the hub command line tool' 310 | end 311 | end 312 | 313 | def setup_overcommit 314 | run 'overcommit --install' 315 | copy_file '.overcommit.yml', force: true 316 | run 'overcommit --sign' 317 | end 318 | 319 | run 'pgrep spring | xargs kill -9' 320 | 321 | # launch the main template creation method 322 | apply_template! 323 | --------------------------------------------------------------------------------