bullet_train 1.0.34 → 1.0.37
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/views/layouts/docs.html.erb +46 -18
- data/app/views/public/home/docs.html.erb +18 -1
- data/config/routes.rb +1 -1
- data/docs/api.md +3 -0
- data/docs/authentication.md +13 -0
- data/docs/billing/stripe.md +90 -0
- data/docs/desktop.md +13 -0
- data/docs/field-partials/buttons.md +42 -0
- data/docs/field-partials/super-select.md +58 -0
- data/docs/field-partials.md +132 -0
- data/docs/font-awesome-pro.md +50 -0
- data/docs/getting-started.md +55 -0
- data/docs/heroku.md +91 -0
- data/docs/i18n.md +3 -0
- data/docs/index.md +52 -0
- data/docs/indirection.md +53 -0
- data/docs/modeling.md +93 -0
- data/docs/namespacing.md +11 -0
- data/docs/oauth.md +27 -0
- data/docs/onboarding.md +41 -0
- data/docs/permissions.md +18 -0
- data/docs/seeds.md +48 -0
- data/docs/super-scaffolding/delegated-types.md +328 -0
- data/docs/super-scaffolding.md +246 -0
- data/docs/teams.md +8 -0
- data/docs/testing.md +34 -0
- data/docs/themes.md +101 -0
- data/docs/tunneling.md +29 -0
- data/docs/upgrades.md +69 -0
- data/docs/webhooks/incoming.md +3 -0
- data/docs/webhooks/outgoing.md +3 -0
- data/lib/bullet_train/version.rb +1 -1
- metadata +30 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 48bdf85d340015182f0265ee63fbd647e731dbba5ac5ee776b0f723fd866a9ec
|
4
|
+
data.tar.gz: 405bd407266bfc304ebf33f37924419717f155aa7f899edf472f298597209e54
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a10cb7ca6fc7f8c547aeaef879437267c46d9bbb6230790939246d6e56b3539dd6f55e36d9964e9ac6aa01151ad3a45d695d74624c999f91a25b83ee4bc6d178
|
7
|
+
data.tar.gz: 47a8b0c7efaefb3df5744a484b9dd9b5cc86f57f5e11e2c0585232d97039f6b035b85ada3bd8550b27b200b37005880ae045e06912e679ac0b96e4ee0740d552
|
@@ -5,7 +5,7 @@
|
|
5
5
|
<%
|
6
6
|
# we're going to use the
|
7
7
|
body = Nokogiri::HTML(@body)
|
8
|
-
title = body.css('h1').first
|
8
|
+
title = body.css('h1').first&.text.presence || t('bullet_train.tagline')
|
9
9
|
first_paragraph = body.css('p').first
|
10
10
|
preceding_heading = first_paragraph&.xpath("preceding-sibling::h2")
|
11
11
|
description = [preceding_heading&.text, first_paragraph&.text].select(&:present?).join(" — ") || t('bullet_train.description')
|
@@ -73,41 +73,43 @@
|
|
73
73
|
<% end %>
|
74
74
|
<% end %>
|
75
75
|
|
76
|
-
<%= render 'account/shared/menu/item', url: '/docs/
|
76
|
+
<%= render 'account/shared/menu/item', url: '/docs/upgrades', label: 'Upgrades' do |p| %>
|
77
77
|
<% p.content_for :icon do %>
|
78
|
-
<i class="fal fa-
|
78
|
+
<i class="fal fa-sparkles ti ti-arrow-up"></i>
|
79
79
|
<% end %>
|
80
80
|
<% end %>
|
81
|
+
<% end %>
|
81
82
|
|
82
|
-
|
83
|
+
<%= render 'account/shared/menu/section', title: 'General Topics' do %>
|
84
|
+
<%= render 'account/shared/menu/item', url: '/docs/modeling', label: 'Domain Modeling' do |p| %>
|
83
85
|
<% p.content_for :icon do %>
|
84
|
-
<i class="fal fa-
|
86
|
+
<i class="fal fa-bolt ti ti-bolt"></i>
|
85
87
|
<% end %>
|
86
88
|
<% end %>
|
87
89
|
|
88
|
-
<%= render 'account/shared/menu/item', url: '/docs/
|
90
|
+
<%= render 'account/shared/menu/item', url: '/docs/indirection', label: 'Indirection' do |p| %>
|
89
91
|
<% p.content_for :icon do %>
|
90
|
-
<i class="fal fa-bolt ti ti-
|
92
|
+
<i class="fal fa-bolt ti ti-direction"></i>
|
91
93
|
<% end %>
|
92
94
|
<% end %>
|
93
|
-
<% end %>
|
94
95
|
|
95
|
-
|
96
|
-
<%= render 'account/shared/menu/item', url: '/docs/super-scaffolding', label: 'Super Scaffolding' do |p| %>
|
96
|
+
<%= render 'account/shared/menu/item', url: '/docs/overriding', label: 'Overriding' do |p| %>
|
97
97
|
<% p.content_for :icon do %>
|
98
|
-
<i class="fal fa-
|
98
|
+
<i class="fal fa-bolt ti ti-spray"></i>
|
99
99
|
<% end %>
|
100
100
|
<% end %>
|
101
101
|
|
102
|
-
<%= render 'account/shared/menu/item', url: '/docs/
|
102
|
+
<%= render 'account/shared/menu/item', url: '/docs/tunneling', label: 'Tunneling' do |p| %>
|
103
103
|
<% p.content_for :icon do %>
|
104
|
-
<i class="fal fa-
|
104
|
+
<i class="fal fa-bolt ti ti-bolt"></i>
|
105
105
|
<% end %>
|
106
106
|
<% end %>
|
107
|
+
<% end %>
|
107
108
|
|
108
|
-
|
109
|
+
<%= render 'account/shared/menu/section', title: 'Developer Tools' do %>
|
110
|
+
<%= render 'account/shared/menu/item', url: '/docs/super-scaffolding', label: 'Super Scaffolding' do |p| %>
|
109
111
|
<% p.content_for :icon do %>
|
110
|
-
<i class="fal fa-
|
112
|
+
<i class="fal fa-magic ti ti-wand"></i>
|
111
113
|
<% end %>
|
112
114
|
<% end %>
|
113
115
|
|
@@ -122,6 +124,12 @@
|
|
122
124
|
<i class="fal fa-check ti ti-check"></i>
|
123
125
|
<% end %>
|
124
126
|
<% end %>
|
127
|
+
|
128
|
+
<%= render 'account/shared/menu/item', url: 'https://github.com/bullet-train-co/magic_test', label: 'Magic Test' do |p| %>
|
129
|
+
<% p.content_for :icon do %>
|
130
|
+
<i class="fal fa-check ti ti-video-camera"></i>
|
131
|
+
<% end %>
|
132
|
+
<% end %>
|
125
133
|
<% end %>
|
126
134
|
|
127
135
|
<%= render 'account/shared/menu/section', title: 'Accounts & Teams' do %>
|
@@ -156,6 +164,26 @@
|
|
156
164
|
<% end %>
|
157
165
|
<% end %>
|
158
166
|
|
167
|
+
<%= render 'account/shared/menu/section', title: 'User Interface' do %>
|
168
|
+
<%= render 'account/shared/menu/item', url: '/docs/field-partials', label: 'Field Partials' do |p| %>
|
169
|
+
<% p.content_for :icon do %>
|
170
|
+
<i class="fal fa-i-cursor ti ti-text"></i>
|
171
|
+
<% end %>
|
172
|
+
<% end %>
|
173
|
+
|
174
|
+
<%= render 'account/shared/menu/item', url: '/docs/themes', label: 'Themes' do |p| %>
|
175
|
+
<% p.content_for :icon do %>
|
176
|
+
<i class="fal fa-swatchbook ti ti-palette"></i>
|
177
|
+
<% end %>
|
178
|
+
<% end %>
|
179
|
+
|
180
|
+
<%= render 'account/shared/menu/item', url: 'https://github.com/bullet-train-co/nice_partials', label: 'Nice Partials' do |p| %>
|
181
|
+
<% p.content_for :icon do %>
|
182
|
+
<i class="fal fa-swatchbook ti ti-widget"></i>
|
183
|
+
<% end %>
|
184
|
+
<% end %>
|
185
|
+
<% end %>
|
186
|
+
|
159
187
|
<%= render 'account/shared/menu/section', title: 'Billing' do %>
|
160
188
|
<%= render 'account/shared/menu/item', url: '/docs/billing/stripe', label: 'Stripe' do |p| %>
|
161
189
|
<% p.content_for :icon do %>
|
@@ -175,19 +203,19 @@
|
|
175
203
|
<% p.content_for :icon do %>
|
176
204
|
<i class="fal fa-brackets-curly ti ti-settings"></i>
|
177
205
|
<% end %>
|
178
|
-
<% end
|
206
|
+
<% end %>
|
179
207
|
|
180
208
|
<%= render 'account/shared/menu/item', url: '/docs/webhooks/outgoing', label: 'Outgoing Webhooks' do |p| %>
|
181
209
|
<% p.content_for :icon do %>
|
182
210
|
<i class="fal fa-outlet ti ti-pulse"></i>
|
183
211
|
<% end %>
|
184
|
-
<% end
|
212
|
+
<% end %>
|
185
213
|
|
186
214
|
<%= render 'account/shared/menu/item', url: '/docs/webhooks/incoming', label: 'Incoming Webhooks' do |p| %>
|
187
215
|
<% p.content_for :icon do %>
|
188
216
|
<i class="fal fa-plug ti ti-plug"></i>
|
189
217
|
<% end %>
|
190
|
-
<% end
|
218
|
+
<% end %>
|
191
219
|
<% end %>
|
192
220
|
|
193
221
|
<%= render 'account/shared/menu/section', title: 'Internationalization' do %>
|
@@ -1 +1,18 @@
|
|
1
|
-
|
1
|
+
<% @body = markdown(File.read(Rails.root.to_s + "/#{@file}").gsub('.md)', ')')) %>
|
2
|
+
|
3
|
+
<% if @file == "tmp/gems/bullet_train/docs/index.md" %>
|
4
|
+
<% header, groups = @body.split("<h2>", 2) %>
|
5
|
+
<%= header.html_safe %>
|
6
|
+
|
7
|
+
<% # Restore the leading <h2> to the groups of links. %>
|
8
|
+
<% groups = "<h2>#{groups}" %>
|
9
|
+
<div class="xl:grid grid-cols-3 gap-20">
|
10
|
+
<% groups.split("<h2>").select(&:present?).map { |s| "<h2>#{s}" }.in_groups(3).each do |group| %>
|
11
|
+
<div>
|
12
|
+
<%= group.join.html_safe %>
|
13
|
+
</div>
|
14
|
+
<% end %>
|
15
|
+
</div>
|
16
|
+
<% else %>
|
17
|
+
<%= @body %>
|
18
|
+
<% end %>
|
data/config/routes.rb
CHANGED
@@ -3,7 +3,7 @@ Rails.application.routes.draw do
|
|
3
3
|
root to: "home#index"
|
4
4
|
get "invitation" => "home#invitation", :as => "invitation"
|
5
5
|
|
6
|
-
if Rails.env.
|
6
|
+
if Rails.env.development?
|
7
7
|
get "docs", to: "home#docs"
|
8
8
|
get "docs/*page", to: "home#docs"
|
9
9
|
end
|
data/docs/api.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Authentication
|
2
|
+
Bullet Train uses [Devise](https://github.com/heartcombo/devise) for authentication and we've done the work of making the related views look pretty and well-integrated with the look-and-feel of the application template.
|
3
|
+
|
4
|
+
## Customizing Controllers
|
5
|
+
Bullet Train registers its own slightly customized registration and session controllers for Devise. If you want to customize them further, you can simply eject those controllers from the framework and override them locally, like so:
|
6
|
+
|
7
|
+
```
|
8
|
+
$ bin/resolve RegistrationsController --eject --open
|
9
|
+
$ bin/resolve SessionsController --eject --open
|
10
|
+
```
|
11
|
+
|
12
|
+
## Customizing Views
|
13
|
+
You can customize Devise views using the same workflow you would use to customize any other Bullet Train views.
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# Enabling Stripe Subscriptions
|
2
|
+
|
3
|
+
Bullet Train provides a base billing package and a Stripe-specific package with support for Stripe Checkout, Stripe Billing's customer portal, and incoming Stripe webhooks.
|
4
|
+
|
5
|
+
## 1. Add the `bullet_train-billing-stripe` Ruby gem.
|
6
|
+
|
7
|
+
> TODO Add instructions about subscribing to Bullet Train Pro.
|
8
|
+
|
9
|
+
## 2. Migrate the Database
|
10
|
+
|
11
|
+
```
|
12
|
+
rake db:migrate
|
13
|
+
```
|
14
|
+
|
15
|
+
## 3. Create API Keys with Stripe
|
16
|
+
|
17
|
+
- Create a Stripe account if you don't already have one.
|
18
|
+
- Visit https://dashboard.stripe.com/apikeys.
|
19
|
+
- For your development environment, make sure you toggle the "test mode" flag to "on" in the top-right corner.
|
20
|
+
- Create a new secret key.
|
21
|
+
|
22
|
+
## 4. Configure Stripe API Keys Locally
|
23
|
+
|
24
|
+
Edit `config/application.yml` and add your Stripe publishable key and new secret key to the file, and also tell the system to use Stripe subscriptions by default:
|
25
|
+
|
26
|
+
```
|
27
|
+
BILLING_DEFAULT_SUBSCRIPTION: "Billing::Stripe::Subscription"
|
28
|
+
STRIPE_PUBLISHABLE_KEY: pk_0CJwz5wHlKBXxDA4VO1uEoipxQob0
|
29
|
+
STRIPE_SECRET_KEY: sk_0CJw2Iu5wwIKXUDdqphrt2zFZyOCH
|
30
|
+
```
|
31
|
+
|
32
|
+
## 5. Populate Stripe with Locally Configured Products
|
33
|
+
|
34
|
+
Bullet Train defines subscription plans and other purchasable add-ons in `config/models/billing/products.yml` and comes preconfigured with some example plans. We recommend just getting started with these plans to ensure your setup is working before customizing the attributes of these plans.
|
35
|
+
|
36
|
+
Before you can use Stripe Checkout or Stripe Billing's customer portal, these products will have to be defined on Stripe as well. You can have all locally defined products automatically created on Stripe by running the following:
|
37
|
+
|
38
|
+
```
|
39
|
+
rake billing:stripe:populate_products_in_stripe
|
40
|
+
```
|
41
|
+
|
42
|
+
## 6. Import Additional Environment Variables
|
43
|
+
|
44
|
+
The script in the previous step will output some additional environment variables you need to copy into `config/application.yml`.
|
45
|
+
|
46
|
+
## 7. Restart Rails
|
47
|
+
|
48
|
+
We've modified a bunch of environment variables, so you'll have to have to restart your Rails server before you see the results in your browser.
|
49
|
+
|
50
|
+
```
|
51
|
+
rails restart
|
52
|
+
```
|
53
|
+
|
54
|
+
## 8. Test Creating a Subscription
|
55
|
+
|
56
|
+
Bullet Train comes preconfigured with a "freemium" plan, so new and existing accounts will continue to work as normal. A new "billing" menu item will appear and you can test subscription creation by clicking "upgrade" and selecting one of the two plans presented.
|
57
|
+
|
58
|
+
You should be in "test mode" on Stripe, so when prompted for a credit card number, you can enter `4242 4242 4242 4242`.
|
59
|
+
|
60
|
+
## 9. Configuring Webhooks
|
61
|
+
|
62
|
+
Basic subscription creation will work without receiving and processing Stripe's webhooks. However, advanced payment workflows like SCA payments and customer portal cancelations and plan changes require receiving webhooks and processing them.
|
63
|
+
|
64
|
+
- Stripe can't deliver webhooks to `http://localhost:3000`, so you'll need to [get an HTTP tunnel up and running](/docs/tunneling.md). For this example, we'll assume you're using ngrok.
|
65
|
+
- Visit https://dashboard.stripe.com/test/webhooks/create.
|
66
|
+
- Configure the "endpoint URL" to be `https://your-tunnel.ngrok.io/webhooks/incoming/stripe_webhooks`, replacing `your-tunnel` with whatever the subdomain of your tunnel is.
|
67
|
+
- When configuring which events to receive, just "select all events" for simplicity. This ensures that any webhooks Bullet Train might add support for in the future will be properly handled when you upgrade.
|
68
|
+
- Add the endpoint.
|
69
|
+
- On the page for the webhook endpoint you've just configured with Stripe, click on "reveal" under the heading "signing secret". This is a secret token that is required to authenticate that webhooks your application is receiving are actually coming from Stripe. Copy this into your `config/application.yml` like so:
|
70
|
+
|
71
|
+
```
|
72
|
+
STRIPE_WEBHOOKS_ENDPOINT_SECRET: whsec_VsM3c2zeZyqAddkaPaXzf1wJsYp2fRKR
|
73
|
+
```
|
74
|
+
|
75
|
+
- Restart your Rails server with `rails restart`.
|
76
|
+
- Trigger a test webhook just to ensure it's resulting in an HTTP status code of 201.
|
77
|
+
|
78
|
+
## 10. Configure Stripe Billing's Customer Portal
|
79
|
+
|
80
|
+
- Visit https://dashboard.stripe.com/test/settings/billing/portal.
|
81
|
+
- Complete all required fields.
|
82
|
+
- Be sure to add all of your actively available plans under "products".
|
83
|
+
|
84
|
+
This "products" list is what Stripe will display to users as upgrade and downgrade options in the customer portal. You shouldn't list any products here that aren't properly configured in your Rails app, otherwise the resulting webhook will fail to process. If you want to stop offering a plan, you should remove it from this list as well.
|
85
|
+
|
86
|
+
## 11. Test Webhooks by Managing a Subscription
|
87
|
+
|
88
|
+
In the same account where you created your first test subscription, go into the "billing" menu and click "manage" on that subscription. This will take you to the Stripe Billing customer portal.
|
89
|
+
|
90
|
+
Once you're in the customer portal, you should test upgrading, downgrading, and canceling your subscription and clicking "⬅ Return to {Your Application Name}" in between each step to ensure that each change you're making is properly reflected in your Bullet Train application. This will let you know that webhooks are being properly delivered and processed and all the products in both systems are properly mapped in both directions.
|
data/docs/desktop.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Distributing as a Desktop Application
|
2
|
+
|
3
|
+
Bullet Train is fine-tuned to run well within an Electron container and the easiest way to generate code-signed, Electron-powered application bundles for Windows, macOS, and Linux is the commercial tool provided by our friends at [ToDesktop](https://www.todesktop.com).
|
4
|
+
|
5
|
+
## Developing Desktop Experiences Locally
|
6
|
+
|
7
|
+
To test your application's desktop experience during development, you can simply [download and install Bullet Train's own prebuilt desktop application](https://dl.todesktop.com/210204wqi0hp3xe). This example application (built by ToDesktop) points to `http://localhost:3000`, so after spinning up `rails s`, you can launch this application to tweak and tune your desktop experience in real time.
|
8
|
+
|
9
|
+
This same bundle is available for the following platforms:
|
10
|
+
|
11
|
+
- [Windows](https://dl.todesktop.com/210204wqi0hp3xe/windows/nsis/x64)
|
12
|
+
- [macOS](https://dl.todesktop.com/210204wqi0hp3xe/mac/dmg/x64)
|
13
|
+
- [Linux](https://dl.todesktop.com/210204wqi0hp3xe/linux/appImage/x64)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Examples for the `buttons` Field Partial
|
2
|
+
|
3
|
+
## Define Available Buttons via Localization Yaml
|
4
|
+
|
5
|
+
If you invoke the field partial in `app/views/account/some_class_name/_form.html.erb` like so:
|
6
|
+
|
7
|
+
```
|
8
|
+
<%= render 'shared/fields/buttons', form: form, method: :enabled %>
|
9
|
+
```
|
10
|
+
|
11
|
+
You can define the available buttons in `config/locales/en/some_class_name.en.yml` like so:
|
12
|
+
|
13
|
+
```
|
14
|
+
en:
|
15
|
+
some_class_name:
|
16
|
+
fields:
|
17
|
+
enabled:
|
18
|
+
name: &enabled Enabled
|
19
|
+
label: Should this item be enabled?
|
20
|
+
heading: Enabled?
|
21
|
+
options:
|
22
|
+
yes: "Yes, this item should be enabled."
|
23
|
+
no: "No, this item should be disabled."
|
24
|
+
```
|
25
|
+
|
26
|
+
## Generate Buttons Programmatically
|
27
|
+
|
28
|
+
You can generate the available buttons using a collection of database objects by passing the `options` option like so:
|
29
|
+
|
30
|
+
```
|
31
|
+
<%= render 'shared/fields/buttons', form: form, method: :category_id,
|
32
|
+
options: Category.all.map { |category| [category.id, category.label_string] } %>
|
33
|
+
```
|
34
|
+
|
35
|
+
## Allow Multiple Button Selections
|
36
|
+
|
37
|
+
You can allow multiple buttons to be selected using the `multiple` option, like so:
|
38
|
+
|
39
|
+
```
|
40
|
+
<%= render 'shared/fields/buttons', form: form, method: :category_ids,
|
41
|
+
options: Category.all.map { |category| [category.id, category.label_string] }, multiple: true %>
|
42
|
+
```
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Examples for the `super_select` Field Partial
|
2
|
+
|
3
|
+
The `super_select` partial provides a wonderful default UI (in contrast to the vanilla browser experience for this, which is horrible) with optional search and multi-select functionality out-of-the-box. It invokes the [Select2](https://select2.org) library to provide you these features.
|
4
|
+
|
5
|
+
## Define Available Buttons via Localization Yaml
|
6
|
+
|
7
|
+
If you invoke the field partial in `app/views/account/some_class_name/_form.html.erb` like so:
|
8
|
+
|
9
|
+
<pre><code><%= render 'shared/fields/super_select', form: form, method: :response_behavior %></code></pre>
|
10
|
+
|
11
|
+
You can define the available options in `config/locales/en/some_class_name.en.yml` like so:
|
12
|
+
|
13
|
+
<pre><code>en:
|
14
|
+
some_class_name:
|
15
|
+
fields:
|
16
|
+
response_behavior:
|
17
|
+
name: &response_behavior Response Behavior
|
18
|
+
label: When should this object respond to new submissions?
|
19
|
+
heading: Responds
|
20
|
+
choices:
|
21
|
+
immediate: Immediately
|
22
|
+
after_10_minutes: After a 10 minute delay
|
23
|
+
disabled: Doesn't respond
|
24
|
+
</code></pre>
|
25
|
+
|
26
|
+
## Specify Available Choices Inline
|
27
|
+
|
28
|
+
Although it's recommended to define any static list of choices in the localization Yaml file (so your application remains easy to translate into other languages), you can also specify these choices using the `choices` option from the underlying select form field helper:
|
29
|
+
|
30
|
+
<pre><code><%= render 'shared/fields/super_select', form: form, method: :response_behavior,
|
31
|
+
choices: [['Immediately', 'immediate'], ['After a 10 minute delay', 'after_10_minutes'] ["Doesn't respond", 'disabled']] %></code></pre>
|
32
|
+
|
33
|
+
## Generate Choices Programmatically
|
34
|
+
|
35
|
+
You can generate the available buttons using a collection of database objects by passing the `options` option like so:
|
36
|
+
|
37
|
+
<pre><code><%= render 'shared/fields/super_select', form: form, method: :category_id,
|
38
|
+
choices: Category.all.map { |category| [category.label_string, category.id] } %></code></pre>
|
39
|
+
|
40
|
+
## Allowing Multiple Option Selections
|
41
|
+
|
42
|
+
Here is an example allowing multiple team members to be assigned to a (hypothetical) `Project` model:
|
43
|
+
|
44
|
+
<pre><code><%= render 'shared/fields/super_select', form: form, method: :membership_ids,
|
45
|
+
choices: @project.valid_memberships.map { |membership| [membership.name, membership.id] },
|
46
|
+
html_options: {multiple: true} %>
|
47
|
+
</code></pre>
|
48
|
+
|
49
|
+
The `html_options` key is just inherited from the underlying Rails select form field helper.
|
50
|
+
|
51
|
+
## Allowing Search
|
52
|
+
|
53
|
+
Here is the same example, with search enabled:
|
54
|
+
|
55
|
+
<pre><code><%= render 'shared/fields/super_select', form: form, method: :membership_ids,
|
56
|
+
choices: @project.valid_memberships.map { |membership| [membership.name, membership.id] },
|
57
|
+
html_options: {multiple: true}, other_options: {search: true} %>
|
58
|
+
</code></pre>
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# Field Partials
|
2
|
+
Bullet Train includes a collection of view partials that are intended to [DRY-up](https://en.wikipedia.org/wiki/Don't_repeat_yourself) as much redundant presentation logic as possible for different types of form fields without taking on a third-party dependency like Formtastic.
|
3
|
+
|
4
|
+
## Responsibilities
|
5
|
+
These form field partials standardize and centralize the following behavior across all form fields that use them:
|
6
|
+
|
7
|
+
- Apply theme styling and classes.
|
8
|
+
- Display any error messages for a specific field inline under the field itself.
|
9
|
+
- Display a stylized asterisk next to the label of fields that are known to be required.
|
10
|
+
- Any labels, placeholder values, and help text are defined in a standardized way in the model's localization Yaml file.
|
11
|
+
- For fields presenting a static list of options (e.g. a list of buttons or a select field) the options can be defined in the localization Yaml file.
|
12
|
+
|
13
|
+
It's a simple set of responsibilities, but putting them all together in one place cleans up a lot of form view code. One of the most compelling features of this "field partials" approach is that they're just HTML in ERB templates using standard Rails form field helpers within the standard Rails `form_with` method. That means there are no "last mile" issues if you need to customize the markup being generated. There's no library to fork or classes to override.
|
14
|
+
|
15
|
+
## The Complete Package
|
16
|
+
Each field partial can optionally include whichever of the following are required to fully support it:
|
17
|
+
|
18
|
+
- **Controller assignment helper** to be used alongside Strong Parameters to convert whatever is submitted in the form to the appropriate ActiveRecord attribute value.
|
19
|
+
- **Turbo-compatible JavaScript invocation** of any third-party library that helps support the field partial.
|
20
|
+
- **Theme-compatible styling** to ensure any third-party libraries "fit in".
|
21
|
+
- **Capybara testing helper** to ensure it's easy to inject values into a field partial in headless browser tests.
|
22
|
+
|
23
|
+
## Basic Usage
|
24
|
+
The form field partials are designed to be a 1:1 match for [the native Rails form field helpers](https://guides.rubyonrails.org/form_helpers.html) developers are already used to using. For example, consider the following basic Rails form field helper invocation:
|
25
|
+
|
26
|
+
```
|
27
|
+
<%= form.text_field :text_field_value, autofocus: true %>
|
28
|
+
```
|
29
|
+
|
30
|
+
Using the field partials, the same field would be implemented as follows:
|
31
|
+
|
32
|
+
```
|
33
|
+
<%= render 'shared/fields/text_field', form: form, method: :text_field_value, options: {autofocus: true} %>
|
34
|
+
```
|
35
|
+
|
36
|
+
At first blush it might look like a more verbose invocation, but that doesn't take into account that the first vanilla Rails example doesn't handle the field label or any other related functionality.
|
37
|
+
|
38
|
+
Breaking down the invocation:
|
39
|
+
|
40
|
+
- `text_field` matches the name of the native Rails form field helper we want to invoke.
|
41
|
+
- The `form` option passes a reference to the form object the field will exist within.
|
42
|
+
- The `method` option specifies which attribute of the model the field represents, in the same way as the first parameter of the basic Rails `text_field` helper.
|
43
|
+
- The `options` option is basically a passthrough, allowing you to specify options which will passed directly to the underlying Rails form field helper.
|
44
|
+
|
45
|
+
The 1:1 relationship between these field partials and their underlying Rails form helpers is an important design decision. For example, the way `options` is passed through to native Rails form field helpers means that experienced Rails developers will still be able to leverage what they remember about using Rails, while those of us who don't readily remember all the various options of those helpers can make use of [the standard Rails documentation](https://guides.rubyonrails.org/form_helpers.html) and the great wealth of Rails code examples available online and still take advantage of these field partials. That means the amount of documentation we need to maintain for these field partials is strictly for those features that are in addition to what Rails provides by default.
|
46
|
+
|
47
|
+
Individual field partials might have additional options available based on the underlying Rails form field helper. Links to the documentation for individual form field partials are listed at the end of this page.
|
48
|
+
|
49
|
+
## `options` vs. `other_options`
|
50
|
+
|
51
|
+
Because Bullet Train field partials have more responsibilities than the underlying Rails form field helpers, there are also additional options for things like hiding labels, displaying specific error messages, etc. For these options, we pass them separately as `other_options`. This keeps them separate from the options in `options` that will be passed directly to the underlying Rails form field helper.
|
52
|
+
|
53
|
+
For example, to suppress a label on any field, we can use the `hide_label` option like so:
|
54
|
+
|
55
|
+
```
|
56
|
+
<%= render 'shared/fields/text_field', form: form, method: :text_field_value, other_options: {hide_label: true} %>
|
57
|
+
```
|
58
|
+
|
59
|
+
### Globally-Available `other_options` Options
|
60
|
+
|
61
|
+
| Key | Value Type | Description |
|
62
|
+
| --- | --- | --- |
|
63
|
+
| `help` | string | Display a specific help string. |
|
64
|
+
| `error` | string | Display a specific error string. |
|
65
|
+
| `hide_label` | boolean | Hide the field label. |
|
66
|
+
|
67
|
+
## Reducing Repetition
|
68
|
+
When you're including multiple fields, you can DRY up redundant settings (e.g. `form: form`) like so:
|
69
|
+
|
70
|
+
```
|
71
|
+
<% with_field_settings form: form do %>
|
72
|
+
<%= render 'shared/fields/text_field', method: :text_field_value, options: {autofocus: true} %>
|
73
|
+
<%= render 'shared/fields/buttons', method: :button_value %>
|
74
|
+
<%= render 'shared/fields/cloudinary_image', method: :cloudinary_image_value %>
|
75
|
+
<% end %>
|
76
|
+
```
|
77
|
+
|
78
|
+
## Field partials that integrate with third-party service providers
|
79
|
+
- `cloudinary` makes it trivial to upload photos and images to [Cloudinary](https://cloudinary.com) and store their resulting Cloudinary ID as an attribute of the model backing the form.
|
80
|
+
|
81
|
+
## Yaml Configuration
|
82
|
+
The localization Yaml file (where you configure label and option values for a field) is automatically generated when you run Super Scaffolding for a model. If you haven't done this yet, the localization Yaml file for `Scaffolding::CompletelyConcrete::TangibleThing` serves as a good example. Under `en.scaffolding/completely_concrete/tangible_things.fields` you'll see definitions like this:
|
83
|
+
|
84
|
+
<pre><code>text_field_value:
|
85
|
+
name: &text_field_value Text Field Value
|
86
|
+
label: *text_field_value
|
87
|
+
heading: *text_field_value
|
88
|
+
</code></pre>
|
89
|
+
|
90
|
+
This might look redundant at first glance, as you can see that by default the same label ("Text Field Value") is being used for both the form field label (`label`) and the heading (`heading`) of the show view and table view. It's also used when the field is referred to in a validation error message. However, having these three values defined separately gives us the flexibility of defining much more user-friendly labels in the context of a form field. In my own applications, I'll frequently configure these form field labels to be much more verbose questions (in an attempt to improve the UX), but still use the shorter label as a column header on the table view and the show view:
|
91
|
+
|
92
|
+
<pre><code>text_field_value:
|
93
|
+
name: &text_field_value Text Field Value
|
94
|
+
label: "What should the value of this text field be?"
|
95
|
+
heading: *text_field_value
|
96
|
+
</code></pre>
|
97
|
+
|
98
|
+
You can also configure some placeholder text (displayed in the field when in an empty state) or some inline help text (to be presented to users under the form field) like so:
|
99
|
+
|
100
|
+
<pre><code>text_field_value:
|
101
|
+
name: &text_field_value Text Field Value
|
102
|
+
label: "What should the value of this text field be?"
|
103
|
+
heading: *text_field_value
|
104
|
+
placeholder: "Type your response here"
|
105
|
+
help: "The value can be anything you want it to be!"
|
106
|
+
</code></pre>
|
107
|
+
|
108
|
+
Certain form field partials like `buttons` and `super_select` can also have their selectable options configured in this Yaml file. See their respective documentation for details, as usage varies slightly.
|
109
|
+
|
110
|
+
## Available Field Partials
|
111
|
+
|
112
|
+
| Field Partial | Multiple Values? | Assignment Helpers | JavaScript Library | Description | Commercial License Required |
|
113
|
+
| --- | --- | --- | --- | --- | --- |
|
114
|
+
| `boolean` | | `assign_boolean` | | | |
|
115
|
+
| [`buttons`](/docs/field-partials/buttons.md) | Optionally | `assign_checkboxes` | | | |
|
116
|
+
| `cloudinary_image` | | | | | |
|
117
|
+
| `color_picker` | | | [pickr](https://simonwep.github.io/pickr/) | | |
|
118
|
+
| `date_and_time_field` | | `assign_date_and_time` | [Date Range Picker](https://www.daterangepicker.com) | | |
|
119
|
+
| `date_field` | | `assign_date` | [Date Range Picker](https://www.daterangepicker.com) | | |
|
120
|
+
| `email_field` | | | | | |
|
121
|
+
| `file_field` | | | [Active Storage](https://edgeguides.rubyonrails.org/active_storage_overview.html) | | |
|
122
|
+
| `options` | Optionally | `assign_checkboxes` | | | |
|
123
|
+
| `password_field` | | | | | |
|
124
|
+
| `phone_field` | | | [International Telephone Input](https://intl-tel-input.com) | Ensures telephone numbers are in a format that can be used by providers like Twilio. | |
|
125
|
+
| [`super_select`](/docs/field-partials/super-select.md) | Optionally | `assign_select_options` | [Select2](https://select2.org) | Provides powerful option search, AJAX search, and multi-select functionality. | |
|
126
|
+
| `text_area` | | | | | |
|
127
|
+
| `text_field` | | | | | |
|
128
|
+
| `trix_editor` | | | [Trix](https://github.com/basecamp/trix) | Basic HTML-powered formatting features and support for at-mentions amongst team members. | |
|
129
|
+
|
130
|
+
## Additional Field Partials Documentation
|
131
|
+
- [`buttons`](/docs/field-partials/buttons.md)
|
132
|
+
- [`super_select`](/docs/field-partials/super-select.md)
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Font Awesome Pro
|
2
|
+
|
3
|
+
By default, Bullet Train ships with both [Themify Icons](https://themify.me/themify-icons) and [Font Awesome Pro's Light icons](https://fontawesome.com/icons?d=gallery&s=light) preconfigured for each menu item. However, Font Awesome Pro is a [paid product](https://fontawesome.com/plans), so by default Bullet Train falls back to showing the Themify icons.
|
4
|
+
|
5
|
+
In our experience, there is no better resource than Font Awesome Pro for finding the perfect icon for every model when you're using Super Scaffolding, so we encourage you to make the investment. Once you configure Font Awesome Pro in your environment, its icons will take precedence over the Themify Icons that were provided as a fallback.
|
6
|
+
|
7
|
+
## Configuring Font Awesome Pro
|
8
|
+
|
9
|
+
### 1. Set Authentication Token Environment Variable
|
10
|
+
|
11
|
+
Once you buy a license for Font Awesome Pro, set `FONTAWESOME_NPM_AUTH_TOKEN` in your shell environment to be equal to your key as presented [on their instructions page](https://fontawesome.com/how-to-use/on-the-web/setup/using-package-managers). Unfortunately, it's not enough to simply set `FONTAWESOME_NPM_AUTH_TOKEN` in `config/application.yml` like you might be thinking, because that value won't be picked up when you run `yarn install`.
|
12
|
+
|
13
|
+
#### If you're using **Bash**:
|
14
|
+
- Add `export FONTAWESOME_NPM_AUTH_TOKEN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX` in `~/.bashrc`.
|
15
|
+
- Restart your terminal.
|
16
|
+
|
17
|
+
#### If you're using **zsh**:
|
18
|
+
- Add `export FONTAWESOME_NPM_AUTH_TOKEN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX` in `~/.zshrc`.
|
19
|
+
- Restart your terminal.
|
20
|
+
|
21
|
+
If you're configuring this in another type of shell, please let us know what the steps are [in a new GitHub issue](http://github.com/bullet-train-co/bullet-train-tailwind-css/issues/new) and we'll add them here for others.
|
22
|
+
|
23
|
+
### 2. Add `.npmrc` Configuration
|
24
|
+
|
25
|
+
Create a `.npmrc` file in the root of your project if you don't already have one, and add the following to it:
|
26
|
+
|
27
|
+
```
|
28
|
+
@fortawesome:registry=https://npm.fontawesome.com/
|
29
|
+
//npm.fontawesome.com/:_authToken=${FONTAWESOME_NPM_AUTH_TOKEN}
|
30
|
+
```
|
31
|
+
|
32
|
+
This will pull the environment variable in, but also be compatible with the way we need to supply this value when deploying to Heroku.
|
33
|
+
|
34
|
+
### 3. Add Font Awesome Pro npm Package
|
35
|
+
|
36
|
+
Once you've got your Font Awesome Pro authentication token configured, you can run:
|
37
|
+
|
38
|
+
```
|
39
|
+
yarn add @fortawesome/fontawesome-pro
|
40
|
+
```
|
41
|
+
|
42
|
+
No, that's not a typo. [That's the name of their company.](https://fortawesome.com) If you receive an error at this point, be sure you restarted your terminal, and reach out for help!
|
43
|
+
|
44
|
+
### 4. Add Font Awesome Pro to the Asset Pipeline
|
45
|
+
|
46
|
+
In `app/javascript/application.js`, below `require("@icon/themify-icons/themify-icons.css")`, add:
|
47
|
+
|
48
|
+
```
|
49
|
+
require("@fortawesome/fontawesome-pro/css/all.css")
|
50
|
+
```
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Getting Started
|
2
|
+
|
3
|
+
## Starting a New Project
|
4
|
+
|
5
|
+
Whether you want to build a new application with Bullet Train or contribute to Bullet Train itself, you should start by following the instructions on [the starter repository](https://github.com/bullet-train-co/bullet_train).
|
6
|
+
|
7
|
+
## Basic Techniques
|
8
|
+
|
9
|
+
If you're using Bullet Train for the first time, begin by learning these five important techniques:
|
10
|
+
|
11
|
+
1. Use `rails g model` to create and `bin/super-scaffold` to scaffold a new model:
|
12
|
+
|
13
|
+
```
|
14
|
+
$ rails g model Project team:references name:string
|
15
|
+
$ bin/super-scaffold crud Project Team name:text_field
|
16
|
+
```
|
17
|
+
|
18
|
+
In this example, `Team` refers to the immediate parent of the `Project` resource. For more details, just run `bin/super-scaffold` or [read the documentation](https://github.com/bullet-train-co/bullet_train-base/blob/main/docs/super-scaffolding.md).
|
19
|
+
|
20
|
+
2. Use `rails g migration` and `bin/super-scaffold` to add a new field to a model you've already scaffolded:
|
21
|
+
|
22
|
+
```
|
23
|
+
$ rails g migration add_description_to_projects description:text
|
24
|
+
$ bin/super-scaffold crud-field Project description:trix_editor
|
25
|
+
```
|
26
|
+
|
27
|
+
These first two points about Super Scaffolding are just the tip of the iceberg, so be sure to circle around and [read the full documentation](https://github.com/bullet-train-co/bullet_train-base/blob/main/docs/super-scaffolding.md).
|
28
|
+
|
29
|
+
3. Figure out which ERB views are powering something you see in the UI by:
|
30
|
+
|
31
|
+
- Right clicking the element.
|
32
|
+
- Selecting "Inspect Element".
|
33
|
+
- Looking for the `<!--XRAY START ...-->` comment above the element you've selected.
|
34
|
+
|
35
|
+
4. Figure out the full I18N translation key of any string on the page by adding `?show_locales=true` to the URL.
|
36
|
+
|
37
|
+
5. Use `bin/resolve` to figure out where framework or theme things are coming from and eject them if you need to customize something locally:
|
38
|
+
|
39
|
+
```
|
40
|
+
$ bin/resolve Users::Base
|
41
|
+
$ bin/resolve en.account.teams.show.header --open
|
42
|
+
$ bin/resolve shared/box --open --eject
|
43
|
+
```
|
44
|
+
|
45
|
+
Also, for inputs that can't be provided on the shell, there's an interactive mode where you can paste them:
|
46
|
+
|
47
|
+
```
|
48
|
+
$ bin/resolve --interactive --eject --open
|
49
|
+
```
|
50
|
+
|
51
|
+
And then paste any input, e.g.:
|
52
|
+
|
53
|
+
```
|
54
|
+
<!--XRAY START 73 /Users/andrewculver/.rbenv/versions/3.1.1/lib/ruby/gems/3.1.0/gems/bullet_train-themes-light-1.0.10/app/views/themes/light/commentary/_box.html.erb-->
|
55
|
+
```
|