Skip to content

Commit 4d88297

Browse files
committed
Use Rails.app.creds for combined ENV + credentials lookup
Backport Rails 8.2's unified credentials API (Rails.app.creds) that checks ENV first, then falls back to encrypted credentials. This makes .env and rails credentials interchangeable - developers can choose either approach without code changes. - Add config/initializers/app_credentials.rb polyfill (remove after Rails 8.2 upgrade) - Replace all Rails.application.credentials.dig calls with Rails.app.creds.option - Update .env.example to mirror all credential keys using double-underscore convention - Update Gemfile to Rails ~> 8.1.2 https://claude.ai/code/session_018vkGAjms65YpjwUidRYLkL
1 parent 7ee9069 commit 4d88297

15 files changed

Lines changed: 133 additions & 31 deletions

File tree

.env.example

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,57 @@
1-
# Solid Queue Configuration
1+
# =============================================================================
2+
# Environment Variables
3+
# =============================================================================
4+
# Copy this file to .env and fill in the values.
5+
# These ENV vars are interchangeable with Rails encrypted credentials.
6+
# Rails.app.creds checks ENV first, then falls back to encrypted credentials.
7+
# Nested credential keys use "__" (double underscore) as separator.
8+
#
9+
# For example, credentials.dig(:stripe, :private_key) maps to STRIPE__PRIVATE_KEY
10+
# =============================================================================
11+
12+
# --- Stripe Payments ---
13+
STRIPE__PRIVATE_KEY=sk_test_
14+
STRIPE__PUBLIC_KEY=pk_test_
15+
STRIPE__WEBHOOK_RECEIVE_TEST_EVENTS=true
16+
STRIPE__SIGNING_SECRET=whsec_
17+
18+
# --- OAuth: Google ---
19+
GOOGLE_OAUTH2__CLIENT_ID=
20+
GOOGLE_OAUTH2__CLIENT_SECRET=
21+
22+
# --- OAuth: GitHub ---
23+
GITHUB__KEY=
24+
GITHUB__SECRET=
25+
26+
# --- Telegram ---
27+
TELEGRAM__BOT_NICKNAME=
28+
TELEGRAM__BOT_SECRET=
29+
30+
# --- SMTP ---
31+
SMTP__USER_NAME=
32+
SMTP__PASSWORD=
33+
34+
# --- S3 Storage ---
35+
S3__ACCESS_KEY_ID=
36+
S3__SECRET_ACCESS_KEY=
37+
S3__REGION=
38+
S3__BUCKET=
39+
40+
# --- Active Record Encryption ---
41+
ACTIVE_RECORD_ENCRYPTION__PRIMARY_KEY=
42+
ACTIVE_RECORD_ENCRYPTION__DETERMINISTIC_KEY=
43+
ACTIVE_RECORD_ENCRYPTION__KEY_DERIVATION_SALT=
44+
45+
# --- Tigris S3-compatible Storage (used via direct ENV) ---
46+
AWS_ACCESS_KEY_ID=
47+
AWS_SECRET_ACCESS_KEY=
48+
AWS_ENDPOINT_URL_S3=
49+
BUCKET_NAME=
50+
51+
# --- Application ---
252
SOLID_QUEUE_IN_PUMA=true
53+
HOST=localhost:3000
354

4-
# Avo License Key (if using Avo Pro)
5-
AVO_LICENSE_KEY=''
6-
OPENAI_API_KEY=''
55+
# --- Third-party ---
56+
AVO_LICENSE_KEY=
57+
OPENAI_API_KEY=

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ source "https://rubygems.org"
55
ruby file: ".ruby-version"
66

77
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
8-
gem "rails", "~> 8.1.1"
8+
gem "rails", "~> 8.1.2"
99
# The modern asset pipeline for Rails [https://github.com/rails/propshaft]
1010
gem "propshaft"
1111
# Use postgresql as the database for Active Record

app/controllers/organizations/subscriptions_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@ def sync_subscriptions
5757
end
5858

5959
def require_billing_enabled
60-
redirect_to organization_url(@organization) if Rails.application.credentials.dig(:stripe, :private_key).blank?
60+
redirect_to organization_url(@organization) if Rails.app.creds.option(:stripe, :private_key).blank?
6161
end
6262
end

app/models/connected_account.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ class ConnectedAccount < ApplicationRecord
66
validates :provider, presence: true
77
validates :uid, presence: true, uniqueness: { scope: :provider }
88

9-
encrypts :access_token, :refresh_token if Rails.application.credentials.active_record_encryption.present?
9+
encrypts :access_token, :refresh_token if Rails.app.creds.option(:active_record_encryption, :primary_key).present?
1010

1111
PROVIDER_CONFIG = {
1212
google_oauth2: {

app/models/stripe_price.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def configured_price_ids
8484
end
8585

8686
def stripe_configured?
87-
Rails.application.credentials.dig(:stripe, :private_key).present?
87+
Rails.app.creds.option(:stripe, :private_key).present?
8888
end
8989
end
9090
end

app/views/shared/_navbar.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
<% if Current.membership.admin? %>
2626
<%= nav_link t("organizations.edit.title"), edit_organization_path(Current.organization), icon: "svg/cog-6-tooth.svg", active: [["organizations"], ["edit"]] %>
27-
<%= nav_link t("organizations.subscriptions.index.title"), organization_subscriptions_path(Current.organization), icon: subscription_status_label(Current.organization) if Rails.application.credentials.dig(:stripe, :private_key).present? %>
27+
<%= nav_link t("organizations.subscriptions.index.title"), organization_subscriptions_path(Current.organization), icon: subscription_status_label(Current.organization) if Rails.app.creds.option(:stripe, :private_key).present? %>
2828
<% end %>
2929
</ul>
3030
</div>

app/views/shared/_sidebar.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
<% if Current.membership.admin? %>
2727
<%= nav_link t("organizations.edit.title"), edit_organization_path(Current.organization), icon: "svg/cog-6-tooth.svg", active: [["organizations"], ["edit"]] %>
28-
<%= nav_link t("organizations.subscriptions.index.title"), organization_subscriptions_path(Current.organization), icon: subscription_status_label(Current.organization) if Rails.application.credentials.dig(:stripe, :private_key).present? %>
28+
<%= nav_link t("organizations.subscriptions.index.title"), organization_subscriptions_path(Current.organization), icon: subscription_status_label(Current.organization) if Rails.app.creds.option(:stripe, :private_key).present? %>
2929
<% end %>
3030
</ul>
3131
</details>

app/views/shared/_sidebar_links.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
<%= nav_link t("organizations.dashboard.index.title"), organization_dashboard_path(Current.organization), icon: "svg/chart-bar.svg" %>
44

5-
<%= nav_link t("organizations.dashboard.paywalled_page.title"), organization_paywalled_page_path(Current.organization), icon: "🔐" if Rails.application.credentials.dig(:stripe, :private_key).present? %>
5+
<%= nav_link t("organizations.dashboard.paywalled_page.title"), organization_paywalled_page_path(Current.organization), icon: "🔐" if Rails.app.creds.option(:stripe, :private_key).present? %>
66

77
<%= nav_link "Projects", organization_projects_path(Current.organization), icon: "svg/question-mark-circle.svg" if policy(Project).index? %>

config/environments/production.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@
5959
# Set host to be used by links generated in mailer templates.
6060
config.action_mailer.default_url_options = { host: ENV.fetch("HOST", "example.com") }
6161

62-
# Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit.
62+
# Specify outgoing SMTP server. Set via credentials or ENV (SMTP__USER_NAME, SMTP__PASSWORD).
6363
# config.action_mailer.smtp_settings = {
64-
# user_name: Rails.application.credentials.dig(:smtp, :user_name),
65-
# password: Rails.application.credentials.dig(:smtp, :password),
64+
# user_name: Rails.app.creds.option(:smtp, :user_name),
65+
# password: Rails.app.creds.option(:smtp, :password),
6666
# address: "smtp.example.com",
6767
# port: 587,
6868
# authentication: :plain
@@ -95,8 +95,8 @@
9595
# config.action_mailer.smtp_settings = {
9696
# port: 587,
9797
# address: "email-smtp.eu-central-1.amazonaws.com",
98-
# user_name: Rails.application.credentials.dig(:smtp, :user_name),
99-
# password: Rails.application.credentials.dig(:smtp, :password),
98+
# user_name: Rails.app.creds.option(:smtp, :user_name),
99+
# password: Rails.app.creds.option(:smtp, :password),
100100
# authentication: :plain,
101101
# enable_starttls_auto: true
102102
# }
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# frozen_string_literal: true
2+
3+
# Backport of Rails 8.2 combined credentials (Rails.app.creds).
4+
# Checks ENV first, then falls back to encrypted credentials.
5+
# ENV keys are derived by uppercasing and joining nested keys with "__" (double underscore).
6+
#
7+
# Examples:
8+
# Rails.app.creds.option(:stripe, :private_key)
9+
# # => ENV["STRIPE__PRIVATE_KEY"] || credentials.dig(:stripe, :private_key)
10+
#
11+
# Rails.app.creds.require(:stripe, :private_key)
12+
# # => same, but raises KeyError if missing from both
13+
#
14+
# TODO: Remove this file after upgrading to Rails 8.2+
15+
16+
unless Rails.respond_to?(:app)
17+
class AppCreds
18+
def option(*keys, default: nil)
19+
env_value(*keys) || Rails.application.credentials.dig(*keys) || default
20+
end
21+
22+
def require(*keys)
23+
value = option(*keys)
24+
raise KeyError, "Missing credential: #{keys.map(&:to_s).join('.')}" if value.blank?
25+
value
26+
end
27+
28+
private
29+
30+
def env_value(*keys)
31+
env_key = keys.map { |k| k.to_s.upcase }.join("__")
32+
ENV[env_key].presence
33+
end
34+
end
35+
36+
class AppContainer
37+
def creds
38+
@creds ||= AppCreds.new
39+
end
40+
41+
def credentials
42+
Rails.application.credentials
43+
end
44+
end
45+
46+
Rails.define_singleton_method(:app) { @_app ||= AppContainer.new }
47+
end

0 commit comments

Comments
 (0)