staples 0.1.0 → 1.0.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -1
  3. data/README.md +225 -5
  4. data/lib/staples/cli.rb +25 -2
  5. data/lib/staples/version.rb +3 -1
  6. data/lib/staples.rb +6 -1
  7. data/lib/templates/Procfile +3 -0
  8. data/lib/templates/README.md +127 -0
  9. data/lib/templates/app/controllers/pages_controller.rb +3 -0
  10. data/lib/templates/app/models/membership.rb +4 -0
  11. data/lib/templates/app/models/organization.rb +3 -0
  12. data/lib/templates/app/models/user/account.rb +24 -0
  13. data/lib/templates/app/models/user.rb +3 -0
  14. data/lib/templates/app/views/application/_card.html.erb +5 -0
  15. data/lib/templates/app/views/application/_error_messages.html.erb +11 -0
  16. data/lib/templates/app/views/application/_flashes.html.erb +10 -0
  17. data/lib/templates/app/views/application/_nav.html.erb +27 -0
  18. data/lib/templates/app/views/devise/confirmations/new.html.erb +22 -0
  19. data/lib/templates/app/views/devise/passwords/edit.html.erb +31 -0
  20. data/lib/templates/app/views/devise/passwords/new.html.erb +22 -0
  21. data/lib/templates/app/views/devise/registrations/edit.html.erb +50 -0
  22. data/lib/templates/app/views/devise/registrations/new.html.erb +35 -0
  23. data/lib/templates/app/views/devise/sessions/new.html.erb +32 -0
  24. data/lib/templates/app/views/devise/shared/_links.html.erb +27 -0
  25. data/lib/templates/app/views/pages/home.html.erb +21 -0
  26. data/lib/templates/base.rb +246 -0
  27. data/lib/templates/config/initializers/high_voltage.rb +3 -0
  28. data/lib/templates/config/initializers/sidekiq.rb +12 -0
  29. data/lib/templates/db/migrate/20251121192825_devise_create_users.rb +37 -0
  30. data/lib/templates/db/migrate/20251122141432_create_organizations.rb +7 -0
  31. data/lib/templates/db/migrate/20251122141520_create_memberships.rb +11 -0
  32. data/lib/templates/lib/development/seeder.rb +10 -0
  33. data/lib/templates/lib/tasks/development.rake +15 -0
  34. data/lib/templates/test/controllers/devise/registrations_controller_test.rb +40 -0
  35. data/lib/templates/test/factories/memberships.rb +6 -0
  36. data/lib/templates/test/factories/organizations.rb +4 -0
  37. data/lib/templates/test/factories/users.rb +12 -0
  38. data/lib/templates/test/models/membership_test.rb +13 -0
  39. data/lib/templates/test/models/organization_test.rb +19 -0
  40. data/lib/templates/test/models/user_test.rb +36 -0
  41. data/lib/templates/test/system/authentication_stories_test.rb +72 -0
  42. metadata +36 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e71f9de435ab26acdd3fb0b8f0a3c7959a9f24e70c4eac86f5c93ff6b35938d
4
- data.tar.gz: d823d8bbdf8a72af9730f05522c55dfeafb5f7b07b6cdebe987d7eb8b4024e8e
3
+ metadata.gz: dfe4fb58be2f6637bc0d7fe688af62488a5ac8e28e4bd8a9cf85285d9d164890
4
+ data.tar.gz: e92a8ca1af2d5f8a20793abc37671b8312d4bb59d99ae1b8b3e84ef7402e74ce
5
5
  SHA512:
6
- metadata.gz: 4167ff970e9d52419d9900bc15f354000f86de287f37463813ad7baf0ec0ef5beb53497dbf659d82176ea37b77921319ba94eb9443f7b00cad131b1e0fd919c2
7
- data.tar.gz: 577e9181e26f66b5186ac377707d903f93598155a8c0cc23117e66813b9eec62d282952c6e93209975a8defb404e191fb61f26ef4bb1015ad5bd966e1a7cc7fe
6
+ metadata.gz: 7b1f44b2c626fa2690c1ed5c82938be6eb5f9de471286d5e31b5296c1be79bbdbf037f5e3de71390c11aaa6569319ed626533bbf359ecc5ee1092d6941b7f9c4
7
+ data.tar.gz: 6a459a6d9c411eb28c42c83f9312925996e34b4dbb56fdfe4bef60474a030bb724817dfb06d838c4493627d1c53dd81087eb6d8cc8a1b709a0be416840f78362
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.1.0] - 2025-11-23
3
+ ## [1.0.0] - 2025-11-26
4
+
5
+ ### Added
6
+
7
+ - YARD documentation comments for all public APIs
8
+ - Application template that sets up a new Rails application with sensible defaults
9
+
10
+ ## [0.1.0] - 2025-11-25
4
11
 
5
12
  - Initial release
data/README.md CHANGED
@@ -1,35 +1,255 @@
1
1
  # Staples
2
2
 
3
+ [![Ruby](https://github.com/stevepolitodesign/staples/actions/workflows/main.yml/badge.svg)](https://github.com/stevepolitodesign/staples/actions/workflows/main.yml)
4
+
3
5
  The basic ingredients for your next Rails project.
4
6
 
7
+ ## Why Staples?
8
+
9
+ Templates like [JumpStart][jumpstart] or [Bullet Train][bullettrain] are intended to be used for rapidly building SaaS applications.
10
+
11
+ Staples makes no assumptions about your project. Instead, it's intended to be a starting off point for any and all new projects.
12
+
13
+ [jumpstart]: https://jumpstartrails.com
14
+ [bullettrain]: https://bullettrain.co
15
+
5
16
  ## Installation
6
17
 
7
18
  Install the gem by executing:
8
19
 
9
20
  ```bash
10
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
21
+ gem install staples
11
22
  ```
12
23
 
24
+ ## Requirements
25
+
26
+ Staples requires the latest version of [Rails][rails] and its dependencies.
27
+
28
+ Additionally, Staples requires [yarn][yarn], [PostgreSQL][postgresql] and
29
+ [Redis][redis].
30
+
31
+ [rails]: https://guides.rubyonrails.org/install_ruby_on_rails.html
32
+ [yarn]: https://yarnpkg.com/getting-started/install
33
+ [postgresql]: https://formulae.brew.sh/formula/postgresql@17
34
+ [redis]: https://formulae.brew.sh/formula/redis
35
+
13
36
  ## Usage
14
37
 
15
38
  ```
16
39
  staples <app_name>
17
40
  ```
18
41
 
42
+ ## Environment Variables
43
+
44
+ Staples configures your application to use the following environment variables in production:
45
+
46
+ - `DATABASE_URL` - PostgreSQL database connection string (required)
47
+ - `APPLICATION_HOST` - The domain where your application is hosted (required, used for mailer URL generation)
48
+ - `ASSET_HOST` - CDN or asset host URL (optional, for serving static assets)
49
+ - `MAILER_SENDER` - Default email address for outgoing emails (defaults to `contact@example.com`)
50
+ - `RAILS_MASTER_KEY` - Required for decrypting credentials (automatically set in CI)
51
+
52
+ ## Deploying to Heroku
53
+
54
+ Staples is optimized for Heroku. As such, you'll want to be sure to add the required buildpacks, addons and environment variables.
55
+
56
+ ```
57
+ heroku apps:create
58
+
59
+ heroku buildpacks:set heroku/nodejs
60
+ heroku buildpacks:set heroku/ruby
61
+
62
+ heroku addons:create heroku-postgresql:essential-0
63
+ heroku addons:create heroku-redis:mini
64
+ heroku config:set \
65
+ APPLICATION_HOST=value-from-heroku
66
+ RAILS_MASTER_KEY=value-from-config/master.key
67
+ ```
68
+
69
+ ## GitHub Actions
70
+
71
+ Because Staples sets `config.require_master_key = true`, you'll need to set this value in GitHub in order for GitHub Actions to work.
72
+
73
+ ```
74
+ gh variable set RAILS_MASTER_KEY < config/master.key
75
+ ```
76
+
77
+ ## Features
78
+
79
+ ### Authentication
80
+
81
+ Staples ships with a `user` model via [Devise][devise]. We prefer Devise over the [authentication generator][auth-generator] because...
82
+
83
+ - It receives frequent security updates, whereas you're on your own with a generator.
84
+ - It's widely adopted in the Rails community.
85
+ - It has a [rich ecosystem][ecosystem].
86
+
87
+ Additionally, the following modules are enabled:
88
+
89
+ - [Database Authenticatable][database-authenticatable]
90
+ - [Registerable][registerable]
91
+ - [Recoverable][recoverable]
92
+ - [Rememberable][rememberable]
93
+ - [Validatable][validatable]
94
+ - [Trackable][trackable]
95
+
96
+ [database-authenticatable]: https://www.rubydoc.info/gems/devise/Devise/Models/DatabaseAuthenticatable
97
+ [registerable]: https://www.rubydoc.info/gems/devise/Devise/Models/Registerable
98
+ [recoverable]: https://www.rubydoc.info/gems/devise/Devise/Models/Recoverable
99
+ [rememberable]: https://www.rubydoc.info/gems/devise/Devise/Models/Rememberable
100
+ [validatable]: https://www.rubydoc.info/gems/devise/Devise/Models/Validatable
101
+ [trackable]: https://www.rubydoc.info/gems/devise/Devise/Models/Trackable
102
+ [devise]: https://github.com/heartcombo/devise
103
+ [auth-generator]: https://guides.rubyonrails.org/security.html#authentication
104
+ [ecosystem]: https://github.com/heartcombo/devise?tab=readme-ov-file#extensions
105
+
106
+ ### Organizations
107
+
108
+ Staples draws inspiration from [Laravel][laravel], [JumpStart][jumpstart], and [Bullet Train][bullettrain] by introducing the concept of "Teams" in an effort to make your application more resilient from day 0.
109
+
110
+ When a `user` is created, we automatically create an `organization`, and associate the two via a `membership`.
111
+
112
+ [laravel]: https://jetstream.laravel.com/features/teams.html
113
+ [jumpstart]: https://jumpstartrails.com/docs/accounts
114
+ [bullettrain]: https://blog.bullettrain.co/teams-should-be-an-mvp-feature/
115
+
116
+ ### Frontend
117
+
118
+ Staples proudly ships with [Bootstrap][bootstrap] as its frontend toolkit.
119
+
120
+ Bootstrap is mature, battle tested, and well documented. It's basically the Rails of frontend toolkits for server-rendered applications. It gives you everything you need, (including a rich set of [JavaScript plugins][bootstrap-js]), and is [meant to be customized][bootstrap-customize].
121
+
122
+ [bootstrap]: https://getbootstrap.com
123
+ [bootstrap-js]: https://getbootstrap.com/docs/5.3/getting-started/javascript/
124
+ [bootstrap-customize]: https://getbootstrap.com/docs/5.3/customize/overview/
125
+
126
+ ### Background Jobs
127
+
128
+ Staples ships with [Sidekiq][sidekiq] instead of [Solid Queue][solid-queue] simply because there's an [open issue][solid-queue-issue] with Solid Queue and Heroku.
129
+
130
+ [sidekiq]: https://github.com/sidekiq/sidekiq
131
+ [solid-queue]: https://github.com/rails/solid_queue/
132
+ [solid-queue-issue]: https://github.com/rails/solid_queue/issues/330
133
+
134
+ ### Configuration
135
+
136
+ #### Application
137
+
138
+ The following configurations are applied to all environments:
139
+
140
+ - `config.active_job.queue_adapter = :sidekiq` - Uses Sidekiq for background job processing
141
+ - `config.active_record.strict_loading_by_default = true` - Enables strict loading to prevent N+1 queries
142
+ - `config.active_record.strict_loading_mode = :n_plus_one_only` - Strict loading only raises errors for N+1 queries
143
+ - `config.require_master_key = true` - Requires the master key to be present for encrypted credentials
144
+
145
+ #### Production
146
+
147
+ - `config.sandbox_by_default = true` - Database sessions are sandboxed by default in console
148
+ - `config.active_record.action_on_strict_loading_violation = :log` - Logs strict loading violations instead of raising errors
149
+ - `config.asset_host = ENV["ASSET_HOST"]` - Configures asset host from environment variable
150
+ - `config.action_mailer.default_url_options = { host: ENV.fetch("APPLICATION_HOST") }` - Sets mailer host from environment variable
151
+
152
+ #### Development
153
+
154
+ - `config.active_model.i18n_customize_full_message = true` - Customizes full error messages for internationalization
155
+ - `config.i18n.raise_on_missing_translations = true` - Raises errors when translations are missing
156
+ - `config.generators.apply_rubocop_autocorrect_after_generate! = true` - Automatically runs RuboCop autocorrect after generating files
157
+
158
+ #### Test
159
+
160
+ - `config.action_dispatch.show_exceptions = :none` - Disables exception pages to allow errors to propagate in tests
161
+ - `config.action_mailer.default_url_options = { host: "localhost", port: 3001 }` - Sets mailer host for test environment
162
+ - `config.i18n.raise_on_missing_translations = true` - Raises errors when translations are missing
163
+ - `config.active_job.queue_adapter = :inline` - Executes background jobs synchronously in tests
164
+
165
+ ### Views
166
+
167
+ Staples includes several view enhancements.
168
+
169
+ #### Application Layout
170
+
171
+ The HTML tag includes a `lang` attribute set to the current locale (`<html lang="<%= I18n.locale %>">`), which improves accessibility and SEO.
172
+
173
+ #### Partials
174
+
175
+ Staples provides reusable partials to handle common UI patterns:
176
+
177
+ - **Flash messages** (`app/views/application/_flashes.html.erb`) - Displays Bootstrap-styled flash messages for notices, alerts, and other feedback
178
+ - **Form error messages** (`app/views/application/_error_messages.html.erb`) - Consistently displays validation errors across forms
179
+ - **Navigation** (`app/views/application/_nav.html.erb`) - A Bootstrap navbar with authentication-aware links
180
+ - **Card component** (`app/views/application/_card.html.erb`) - A reusable card component for consistent layout
181
+
182
+ #### Devise Views
183
+
184
+ Custom Devise views are included for all authentication flows (sign up, sign in, password recovery, account editing) with Bootstrap styling applied.
185
+
186
+ ### Test Suite
187
+
188
+ Staples includes a comprehensive test suite built on Rails' default testing framework with additional tools for better test coverage and maintainability.
189
+
190
+ #### Testing Gems
191
+
192
+ The following gems are included to enhance the testing experience:
193
+
194
+ - [Factory Bot][factory-bot] - Provides factories for test data instead of fixtures
195
+ - [Capybara Email][capybara-email] - Adds email testing capabilities to system tests
196
+ - [Capybara Accessibility Audit][capybara-accessibility] - Automatically audits accessibility in system tests
197
+
198
+ [factory-bot]: https://github.com/thoughtbot/factory_bot_rails
199
+ [capybara-email]: https://github.com/dockyard/capybara-email
200
+ [capybara-accessibility]: https://github.com/thoughtbot/capybara_accessibility_audit
201
+
202
+ #### Test Helpers
203
+
204
+ Staples configures the test suite with helpful integrations:
205
+
206
+ - Devise test helpers are included in all tests via `Devise::Test::IntegrationHelpers`
207
+ - Factory Bot syntax methods are available throughout the test suite
208
+ - Capybara is configured to run on port 3001 in order to work with Capybara Email.
209
+ - A `sign_in_as(user)` helper method is added to `ApplicationSystemTestCase` for easy authentication in system tests
210
+
211
+ #### Included Tests
212
+
213
+ The following test files are generated to provide examples and coverage for core functionality:
214
+
215
+ - Model tests for `User`, `Organization`, and `Membership`
216
+ - Controller tests for Devise registrations
217
+ - System tests for authentication flows
218
+ - Factory definitions for all core models
219
+
220
+ ### Strong Migrations
221
+
222
+ Staples ships with [Strong Migrations][strong-migrations] in order to catch unsafe migrations in development.
223
+
224
+ [strong-migrations]: https://github.com/ankane/strong_migrations
225
+
226
+ ### Static Pages
227
+
228
+ Staples ships with [High Voltage][high-voltage] to easily include static pages.
229
+
230
+ [high-voltage]: https://github.com/thoughtbot/high_voltage
231
+
19
232
  ## Development
20
233
 
21
234
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
22
235
 
23
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
236
+ To install this gem onto your local machine, run `bundle exec rake install`. From there, you can run `staples <app_name>` to test the current code.
237
+
238
+ To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org][rubygems].
239
+
240
+ [rubygems]: https://rubygems.org
24
241
 
25
242
  ## Contributing
26
243
 
27
- Bug reports and pull requests are welcome on GitHub at https://github.com/stevepolitodesign/staples. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/stevepolitodesign/staples/blob/main/CODE_OF_CONDUCT.md).
244
+ Bug reports and pull requests are welcome on GitHub at https://github.com/stevepolitodesign/staples. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct][coc].
28
245
 
29
246
  ## License
30
247
 
31
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
248
+ The gem is available as open source under the terms of the [MIT License][mit].
32
249
 
33
250
  ## Code of Conduct
34
251
 
35
- Everyone interacting in the Staples project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/stevepolitodesign/staples/blob/main/CODE_OF_CONDUCT.md).
252
+ Everyone interacting in the Staples project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct][coc].
253
+
254
+ [coc]: https://github.com/stevepolitodesign/staples/blob/main/CODE_OF_CONDUCT.md
255
+ [mit]: https://opensource.org/licenses/MIT
data/lib/staples/cli.rb CHANGED
@@ -1,21 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Staples
4
+ # Command-line interface for generating Rails applications with opinionated defaults.
5
+ #
6
+ # This class handles the creation of new Rails applications with predefined
7
+ # configuration options including PostgreSQL database, Bootstrap CSS, and
8
+ # skipping Solid Queue.
4
9
  class CLI
5
- OPTIONS = [
10
+ # Default options passed to the Rails generator.
11
+ #
12
+ # @return [Array<String>] the list of Rails CLI options
13
+ BASE_OPTIONS = [
6
14
  "-d=postgresql",
7
15
  "--css=bootstrap",
8
16
  "--skip-solid"
9
17
  ]
10
18
 
19
+ # Initializes a new CLI instance.
20
+ #
21
+ # @param app_name [String] the name of the Rails application to create
11
22
  def initialize(app_name)
12
23
  @app_name = app_name
13
24
  end
14
25
 
26
+ # Creates and runs a new CLI instance.
27
+ #
28
+ # @param app_name [String] the name of the Rails application to create
29
+ # @return [Boolean] true if the Rails app was created successfully
30
+ # @raise [Error] if Rails is not installed or app creation fails
15
31
  def self.run(app_name)
16
32
  new(app_name).run
17
33
  end
18
34
 
35
+ # Executes the Rails application generation process.
36
+ #
37
+ # @return [Boolean] true if the Rails app was created successfully
38
+ # @raise [Error] if Rails is not installed or app creation fails
19
39
  def run
20
40
  verify_rails_exists!
21
41
  generate_new_rails_app
@@ -32,7 +52,10 @@ module Staples
32
52
  end
33
53
 
34
54
  def generate_new_rails_app
35
- if system("rails", "new", app_name, *OPTIONS)
55
+ template_path = File.expand_path("../templates/base.rb", __dir__)
56
+ options = BASE_OPTIONS + ["-m=#{template_path}"]
57
+
58
+ if system("rails", "new", app_name, *options)
36
59
  true
37
60
  else
38
61
  raise Error, "Failed to create Rails app"
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # The Staples module provides a CLI tool for generating opinionated Rails applications.
3
4
  module Staples
4
- VERSION = "0.1.0"
5
+ # The current version of the Staples gem.
6
+ VERSION = "1.0.0"
5
7
  end
data/lib/staples.rb CHANGED
@@ -3,7 +3,12 @@
3
3
  require_relative "staples/version"
4
4
  require_relative "staples/cli"
5
5
 
6
+ # The Staples module provides a CLI tool for generating opinionated Rails applications.
7
+ #
8
+ # This gem wraps the Rails application generator with a predefined set of options
9
+ # to quickly scaffold new Rails projects with PostgreSQL, Bootstrap CSS, and other
10
+ # opinionated defaults.
6
11
  module Staples
12
+ # Base error class for all Staples-specific errors.
7
13
  class Error < StandardError; end
8
- # Your code goes here...
9
14
  end
@@ -0,0 +1,3 @@
1
+ release: bundle exec rails db:migrate; bundle exec rails db:seed
2
+ web: bundle exec puma -C config/puma.rb
3
+ worker: bundle exec sidekiq -c 10
@@ -0,0 +1,127 @@
1
+ # README
2
+
3
+ ## Environment Variables
4
+
5
+ - `DATABASE_URL` - PostgreSQL database connection string (required)
6
+ - `APPLICATION_HOST` - The domain where your application is hosted (required, used for mailer URL generation)
7
+ - `ASSET_HOST` - CDN or asset host URL (optional, for serving static assets)
8
+ - `MAILER_SENDER` - Default email address for outgoing emails (defaults to `contact@example.com`)
9
+ - `RAILS_MASTER_KEY` - Required for decrypting credentials (automatically set in CI)
10
+
11
+ ## Features
12
+
13
+ ### Authentication
14
+
15
+ This application ships with a `user` model via [Devise][devise]. We prefer Devise over the [authentication generator][auth-generator] because...
16
+
17
+ - It receives frequent security updates, whereas you're on your own with a generator.
18
+ - It's widely adopted in the Rails community.
19
+ - It has a [rich ecosystem][ecosystem].
20
+
21
+ Additionally, the following modules are enabled:
22
+
23
+ - [Database Authenticatable][database-authenticatable]
24
+ - [Registerable][registerable]
25
+ - [Recoverable][recoverable]
26
+ - [Rememberable][rememberable]
27
+ - [Validatable][validatable]
28
+ - [Trackable][trackable]
29
+
30
+ [database-authenticatable]: https://www.rubydoc.info/gems/devise/Devise/Models/DatabaseAuthenticatable
31
+ [registerable]: https://www.rubydoc.info/gems/devise/Devise/Models/Registerable
32
+ [recoverable]: https://www.rubydoc.info/gems/devise/Devise/Models/Recoverable
33
+ [rememberable]: https://www.rubydoc.info/gems/devise/Devise/Models/Rememberable
34
+ [validatable]: https://www.rubydoc.info/gems/devise/Devise/Models/Validatable
35
+ [trackable]: https://www.rubydoc.info/gems/devise/Devise/Models/Trackable
36
+ [devise]: https://github.com/heartcombo/devise
37
+ [auth-generator]: https://guides.rubyonrails.org/security.html#authentication
38
+ [ecosystem]: https://github.com/heartcombo/devise?tab=readme-ov-file#extensions
39
+
40
+ ### Organizations
41
+
42
+ This application draws inspiration from [Laravel][laravel], [JumpStart][jumpstart], and [Bullet Train][bullettrain] by introducing the concept of "Teams" in an effort to make your application more resilient from day 0.
43
+
44
+ When a `user` is created, we automatically create an `organization`, and associate the two via a `membership`.
45
+
46
+ [laravel]: https://jetstream.laravel.com/features/teams.html
47
+ [jumpstart]: https://jumpstartrails.com/docs/accounts
48
+ [bullettrain]: https://blog.bullettrain.co/teams-should-be-an-mvp-feature/
49
+
50
+ ### Frontend
51
+
52
+ This application proudly ships with [Bootstrap][bootstrap] as its frontend toolkit.
53
+
54
+ Bootstrap is mature, battle tested, and well documented. It's basically the Rails of frontend toolkits for server-rendered applications. It gives you everything you need, (including a rich set of [JavaScript plugins][bootstrap-js]), and is [meant to be customized][bootstrap-customize].
55
+
56
+ [bootstrap]: https://getbootstrap.com
57
+ [bootstrap-js]: https://getbootstrap.com/docs/5.3/getting-started/javascript/
58
+ [bootstrap-customize]: https://getbootstrap.com/docs/5.3/customize/overview/
59
+
60
+ ### Background Jobs
61
+
62
+ This application ships with [Sidekiq][sidekiq] instead of [Solid Queue][solid-queue] simply because there's an [open issue][solid-queue-issue] with Solid Queue and Heroku.
63
+
64
+ [sidekiq]: https://github.com/sidekiq/sidekiq
65
+ [solid-queue]: https://github.com/rails/solid_queue/
66
+ [solid-queue-issue]: https://github.com/rails/solid_queue/issues/330
67
+
68
+ ### Configuration
69
+
70
+ #### Application
71
+
72
+ The following configurations are applied to all environments:
73
+
74
+ - `config.active_job.queue_adapter = :sidekiq` - Uses Sidekiq for background job processing
75
+ - `config.active_record.strict_loading_by_default = true` - Enables strict loading to prevent N+1 queries
76
+ - `config.active_record.strict_loading_mode = :n_plus_one_only` - Strict loading only raises errors for N+1 queries
77
+ - `config.require_master_key = true` - Requires the master key to be present for encrypted credentials
78
+
79
+ #### Production
80
+
81
+ - `config.sandbox_by_default = true` - Database sessions are sandboxed by default in console
82
+ - `config.active_record.action_on_strict_loading_violation = :log` - Logs strict loading violations instead of raising errors
83
+ - `config.asset_host = ENV["ASSET_HOST"]` - Configures asset host from environment variable
84
+ - `config.action_mailer.default_url_options = { host: ENV.fetch("APPLICATION_HOST") }` - Sets mailer host from environment variable
85
+
86
+ #### Development
87
+
88
+ - `config.active_model.i18n_customize_full_message = true` - Customizes full error messages for internationalization
89
+ - `config.i18n.raise_on_missing_translations = true` - Raises errors when translations are missing
90
+ - `config.generators.apply_rubocop_autocorrect_after_generate! = true` - Automatically runs RuboCop autocorrect after generating files
91
+
92
+ #### Test
93
+
94
+ - `config.action_dispatch.show_exceptions = :none` - Disables exception pages to allow errors to propagate in tests
95
+ - `config.action_mailer.default_url_options = { host: "localhost", port: 3001 }` - Sets mailer host for test environment
96
+ - `config.i18n.raise_on_missing_translations = true` - Raises errors when translations are missing
97
+ - `config.active_job.queue_adapter = :inline` - Executes background jobs synchronously in tests
98
+
99
+ ### Strong Migrations
100
+
101
+ This application ships with [Strong Migrations][strong-migrations] in order to catch unsafe migrations in development.
102
+
103
+ [strong-migrations]: https://github.com/ankane/strong_migrations
104
+
105
+ ### Static Pages
106
+
107
+ This application ships with [High Voltage][high-voltage] to easily include static pages.
108
+
109
+ [high-voltage]: https://github.com/thoughtbot/high_voltage
110
+
111
+ ### Development Seeds
112
+
113
+ This application provides a custom seeder task specifically for development and staging environments. Unlike `db/seeds.rb` which runs in all environments, the development seeder is isolated to local development.
114
+
115
+ The seeder is implemented in `lib/development/seeder.rb` and can be run with:
116
+
117
+ ```bash
118
+ rake development:db:seed
119
+ ```
120
+
121
+ For a complete reset, use the replant task which truncates all tables before seeding:
122
+
123
+ ```bash
124
+ rake development:db:seed:replant
125
+ ```
126
+
127
+ **Important**: The development seeder should be idempotent, meaning it can be run multiple times without creating duplicate data or errors.
@@ -0,0 +1,3 @@
1
+ class PagesController < ApplicationController
2
+ include HighVoltage::StaticPage
3
+ end
@@ -0,0 +1,4 @@
1
+ class Membership < ApplicationRecord
2
+ belongs_to :user
3
+ belongs_to :organization
4
+ end
@@ -0,0 +1,3 @@
1
+ class Organization < ApplicationRecord
2
+ has_many :memberships, dependent: :restrict_with_exception
3
+ end
@@ -0,0 +1,24 @@
1
+ module User::Account
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ devise :database_authenticatable, :registerable,
6
+ :recoverable, :rememberable, :validatable,
7
+ :confirmable, :trackable
8
+
9
+ after_create_commit :create_initial_organization!
10
+
11
+ has_many :memberships, dependent: :destroy
12
+ has_many :organizations, through: :memberships
13
+ end
14
+
15
+ def organization
16
+ organizations.sole
17
+ end
18
+
19
+ private
20
+
21
+ def create_initial_organization!
22
+ organizations.create!
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ class User < ApplicationRecord
2
+ include User::Account
3
+ end
@@ -0,0 +1,5 @@
1
+ <div class="card">
2
+ <div class="card-body">
3
+ <%= yield %>
4
+ </div>
5
+ </div>
@@ -0,0 +1,11 @@
1
+ <%# locals: (resource: ) %>
2
+
3
+ <% if resource.errors.any? %>
4
+ <div id="error_explanation" class="alert alert-danger" role="alert" data-turbo-cache="false">
5
+ <ul class="mb-0">
6
+ <% resource.errors.full_messages.each do |message| %>
7
+ <li><%= message %></li>
8
+ <% end %>
9
+ </ul>
10
+ </div>
11
+ <% end %>
@@ -0,0 +1,10 @@
1
+ <% if flash.any? %>
2
+ <div class="flashes">
3
+ <% flash.each do |type, message| -%>
4
+ <div class="<%= class_names("alert alert-dismissible fade show", "alert-success": type == "notice", "alert-danger": type == "alert") %>" role="alert">
5
+ <%= message %>
6
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
7
+ </div>
8
+ <% end -%>
9
+ </div>
10
+ <% end %>
@@ -0,0 +1,27 @@
1
+ <nav class="navbar navbar-expand-lg bg-body-tertiary mb-4" aria-label="Account">
2
+ <div class="container-fluid">
3
+ <%= link_to "Rails MVP", root_path, class: "navbar-brand" %>
4
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarAccount" aria-controls="navbarAccount" aria-expanded="false" aria-label="Toggle navigation">
5
+ <span class="navbar-toggler-icon"></span>
6
+ </button>
7
+ <div class="collapse navbar-collapse" id="navbarAccount">
8
+ <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
9
+ <% if user_signed_in? %>
10
+ <li class="nav-item">
11
+ <%= active_link_to current_user.email, edit_user_registration_path, class: "nav-link" %>
12
+ </li>
13
+ <li class="nav-item">
14
+ <%= button_to "Log out", destroy_user_session_path, method: :delete, class: "nav-link" %>
15
+ </li>
16
+ <% else %>
17
+ <li class="nav-item">
18
+ <%= active_link_to "Log in", new_user_session_path, class: "nav-link" %>
19
+ </li>
20
+ <li class="nav-item">
21
+ <%= link_to "Register", new_user_registration_path, class: "nav-link" %>
22
+ </li>
23
+ <% end %>
24
+ </ul>
25
+ </div>
26
+ </div>
27
+ </nav>
@@ -0,0 +1,22 @@
1
+ <% content_for :title, "Resend confirmation instructions" %>
2
+
3
+ <h1 id="main_label" class="text-center"><%= content_for :title %></h1>
4
+
5
+ <div class="mx-auto w-100" style="max-width: 400px;">
6
+ <%= render "card" do %>
7
+ <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
8
+ <%= render "error_messages", resource: resource %>
9
+
10
+ <div class="mb-3">
11
+ <%= f.label :email, class: "form-label" %>
12
+ <%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email), class: "form-control" %>
13
+ </div>
14
+
15
+ <div class="mb-3">
16
+ <%= f.submit "Resend confirmation instructions", class: "btn btn-primary" %>
17
+ </div>
18
+ <% end %>
19
+
20
+ <%= render "devise/shared/links" %>
21
+ <% end %>
22
+ </div>
@@ -0,0 +1,31 @@
1
+ <% content_for :title, "Change your password" %>
2
+
3
+ <h1 id="main_label" class="text-center"><%= content_for :title %></h1>
4
+
5
+ <div class="mx-auto w-100" style="max-width: 400px;">
6
+ <%= render "card" do %>
7
+ <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
8
+ <%= render "error_messages", resource: resource %>
9
+ <%= f.hidden_field :reset_password_token %>
10
+
11
+ <div class="mb-3">
12
+ <%= f.label :password, "New password", class: "form-label" %>
13
+ <% if @minimum_password_length %>
14
+ <div class="form-text"><%= @minimum_password_length %> characters minimum</div>
15
+ <% end %>
16
+ <%= f.password_field :password, autofocus: true, autocomplete: "new-password", class: "form-control" %>
17
+ </div>
18
+
19
+ <div class="mb-3">
20
+ <%= f.label :password_confirmation, "Confirm new password", class: "form-label" %>
21
+ <%= f.password_field :password_confirmation, autocomplete: "new-password", class: "form-control" %>
22
+ </div>
23
+
24
+ <div class="mb-3">
25
+ <%= f.submit "Change my password", class: "btn btn-primary" %>
26
+ </div>
27
+ <% end %>
28
+
29
+ <%= render "devise/shared/links" %>
30
+ <% end %>
31
+ </div>