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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -1
- data/README.md +225 -5
- data/lib/staples/cli.rb +25 -2
- data/lib/staples/version.rb +3 -1
- data/lib/staples.rb +6 -1
- data/lib/templates/Procfile +3 -0
- data/lib/templates/README.md +127 -0
- data/lib/templates/app/controllers/pages_controller.rb +3 -0
- data/lib/templates/app/models/membership.rb +4 -0
- data/lib/templates/app/models/organization.rb +3 -0
- data/lib/templates/app/models/user/account.rb +24 -0
- data/lib/templates/app/models/user.rb +3 -0
- data/lib/templates/app/views/application/_card.html.erb +5 -0
- data/lib/templates/app/views/application/_error_messages.html.erb +11 -0
- data/lib/templates/app/views/application/_flashes.html.erb +10 -0
- data/lib/templates/app/views/application/_nav.html.erb +27 -0
- data/lib/templates/app/views/devise/confirmations/new.html.erb +22 -0
- data/lib/templates/app/views/devise/passwords/edit.html.erb +31 -0
- data/lib/templates/app/views/devise/passwords/new.html.erb +22 -0
- data/lib/templates/app/views/devise/registrations/edit.html.erb +50 -0
- data/lib/templates/app/views/devise/registrations/new.html.erb +35 -0
- data/lib/templates/app/views/devise/sessions/new.html.erb +32 -0
- data/lib/templates/app/views/devise/shared/_links.html.erb +27 -0
- data/lib/templates/app/views/pages/home.html.erb +21 -0
- data/lib/templates/base.rb +246 -0
- data/lib/templates/config/initializers/high_voltage.rb +3 -0
- data/lib/templates/config/initializers/sidekiq.rb +12 -0
- data/lib/templates/db/migrate/20251121192825_devise_create_users.rb +37 -0
- data/lib/templates/db/migrate/20251122141432_create_organizations.rb +7 -0
- data/lib/templates/db/migrate/20251122141520_create_memberships.rb +11 -0
- data/lib/templates/lib/development/seeder.rb +10 -0
- data/lib/templates/lib/tasks/development.rake +15 -0
- data/lib/templates/test/controllers/devise/registrations_controller_test.rb +40 -0
- data/lib/templates/test/factories/memberships.rb +6 -0
- data/lib/templates/test/factories/organizations.rb +4 -0
- data/lib/templates/test/factories/users.rb +12 -0
- data/lib/templates/test/models/membership_test.rb +13 -0
- data/lib/templates/test/models/organization_test.rb +19 -0
- data/lib/templates/test/models/user_test.rb +36 -0
- data/lib/templates/test/system/authentication_stories_test.rb +72 -0
- metadata +36 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dfe4fb58be2f6637bc0d7fe688af62488a5ac8e28e4bd8a9cf85285d9d164890
|
|
4
|
+
data.tar.gz: e92a8ca1af2d5f8a20793abc37671b8312d4bb59d99ae1b8b3e84ef7402e74ce
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b1f44b2c626fa2690c1ed5c82938be6eb5f9de471286d5e31b5296c1be79bbdbf037f5e3de71390c11aaa6569319ed626533bbf359ecc5ee1092d6941b7f9c4
|
|
7
|
+
data.tar.gz: 6a459a6d9c411eb28c42c83f9312925996e34b4dbb56fdfe4bef60474a030bb724817dfb06d838c4493627d1c53dd81087eb6d8cc8a1b709a0be416840f78362
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
-
## [
|
|
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
|
+
[](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
|
|
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`.
|
|
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]
|
|
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]
|
|
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]
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
data/lib/staples/version.rb
CHANGED
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,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,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,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>
|