diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index d9574a8..533d1f3 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -7,6 +7,10 @@ jobs:
runs-on: ubuntu-latest
name: Example Rails app
steps:
+ - name: Install libvips
+ run: |
+ sudo apt-get update
+ sudo apt-get install --no-install-recommends libvips
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
@@ -29,7 +33,6 @@ jobs:
- '7.2'
- '8.0'
ruby:
- - '2.7'
- '3.0'
- '3.1'
- '3.2'
@@ -41,14 +44,10 @@ jobs:
ruby: '3.1'
- rails: '8.0'
ruby: '3.0'
- - rails: '8.0'
- ruby: '2.7'
# Rails 7.2 requires Ruby 3.1.
- rails: '7.2'
ruby: '3.0'
- - rails: '7.2'
- ruby: '2.7'
# Rails 7.0 doesn't work out of the box with Ruby 3.4.
- rails: '7.0'
@@ -57,6 +56,10 @@ jobs:
BUNDLE_GEMFILE: gemfiles/rails_${{ matrix.rails }}.gemfile
name: RSpec (Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }})
steps:
+ - name: Install libvips
+ run: |
+ sudo apt-get update
+ sudo apt-get install --no-install-recommends libvips
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
diff --git a/Appraisals b/Appraisals
index 8db81f2..4f5a968 100644
--- a/Appraisals
+++ b/Appraisals
@@ -3,6 +3,7 @@
appraise 'rails_7.0' do
gem 'concurrent-ruby', '< 1.3.5'
gem 'rails', '~> 7.0.0'
+ gem 'sqlite3', '<2'
end
appraise 'rails_7.1' do
diff --git a/Gemfile b/Gemfile
index cb1c5a1..3403baf 100644
--- a/Gemfile
+++ b/Gemfile
@@ -6,5 +6,18 @@ gemspec
gem 'appraisal'
gem 'rake'
-gem 'rspec'
gem 'rubocop'
+
+# For the example app tests.
+gem 'image_processing'
+gem 'sprockets-rails'
+gem 'sqlite3'
+
+group :development, :test do
+ gem 'rspec-rails'
+end
+
+group :test do
+ gem 'capybara'
+ gem 'factory_bot_rails'
+end
diff --git a/example-app/.gitignore b/example-app/.gitignore
index 7321a84..8245144 100644
--- a/example-app/.gitignore
+++ b/example-app/.gitignore
@@ -15,6 +15,8 @@
/log/*
/tmp/*
+# Ignore storage (uploaded files in development and any SQLite databases).
+/storage/*
/public/assets
.byebug_history
diff --git a/example-app/Gemfile b/example-app/Gemfile
index a1a2542..4b69492 100644
--- a/example-app/Gemfile
+++ b/example-app/Gemfile
@@ -1,9 +1,10 @@
source 'https://rubygems.org'
-gem 'rails', '~> 8.0.1'
+gem 'parklife-rails', path: '../'
+
+gem 'image_processing'
gem 'sprockets-rails'
gem 'sqlite3'
-gem 'parklife-rails', path: '../'
group :development do
gem 'webrick'
diff --git a/example-app/Parkfile b/example-app/Parkfile
index e657224..4a80ebc 100644
--- a/example-app/Parkfile
+++ b/example-app/Parkfile
@@ -1,4 +1,3 @@
-require 'parklife-rails'
require_relative 'config/environment'
Parklife.application.configure do |config|
@@ -17,4 +16,9 @@ Parklife.application.routes do
# Services typically allow a custom 404 page.
# get '/404.html'
+
+ if ENV['RAILS_ENV'] == 'test'
+ get test_middleware_path
+ get test_url_path
+ end
end
diff --git a/example-app/app/controllers/test_controller.rb b/example-app/app/controllers/test_controller.rb
new file mode 100644
index 0000000..6124555
--- /dev/null
+++ b/example-app/app/controllers/test_controller.rb
@@ -0,0 +1,11 @@
+class TestController < ApplicationController
+ layout false
+
+ def middleware
+ render plain: Rails.application.config.middleware.map(&:name).join("\n")
+ end
+
+ def url
+ render plain: test_url_url
+ end
+end
diff --git a/example-app/app/models/post.rb b/example-app/app/models/post.rb
index 27a6795..b706315 100644
--- a/example-app/app/models/post.rb
+++ b/example-app/app/models/post.rb
@@ -1,4 +1,6 @@
class Post < ApplicationRecord
+ has_one_attached :hero
+
def to_param
slug
end
diff --git a/example-app/app/views/posts/show.html.erb b/example-app/app/views/posts/show.html.erb
index 826e7b9..cfee805 100644
--- a/example-app/app/views/posts/show.html.erb
+++ b/example-app/app/views/posts/show.html.erb
@@ -1,3 +1,11 @@
+<% if @post.hero.attached? %>
+
+ <%= image_tag(
+ @post.hero.variant(resize_to_limit: [100, 100]).processed.url
+ ) %>
+
+<% end %>
+
<%= @post.title %>
<%= simple_format @post.body %>
diff --git a/example-app/config/application.rb b/example-app/config/application.rb
index 3384f33..3d64430 100644
--- a/example-app/config/application.rb
+++ b/example-app/config/application.rb
@@ -5,7 +5,7 @@
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
-# require "active_storage/engine"
+require "active_storage/engine"
require "action_controller/railtie"
# require "action_mailer/railtie"
# require "action_mailbox/engine"
@@ -18,15 +18,19 @@
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
+require 'parklife-rails/activestorage'
+
module Example
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
- config.load_defaults 8.0
+ config.load_defaults Rails::VERSION::STRING.to_f
- # Please, add to the `ignore` list any other `lib` subdirectories that do
- # not contain `.rb` files, or that should not be reloaded or eager loaded.
- # Common ones are `templates`, `generators`, or `middleware`, for example.
- config.autoload_lib(ignore: %w[assets tasks])
+ if config.respond_to?(:autoload_lib)
+ # Please, add to the `ignore` list any other `lib` subdirectories that do
+ # not contain `.rb` files, or that should not be reloaded or eager loaded.
+ # Common ones are `templates`, `generators`, or `middleware`, for example.
+ config.autoload_lib(ignore: %w[assets tasks])
+ end
# Configuration for the application, engines, and railties goes here.
#
diff --git a/example-app/config/environments/development.rb b/example-app/config/environments/development.rb
index 0165c73..bdbe732 100644
--- a/example-app/config/environments/development.rb
+++ b/example-app/config/environments/development.rb
@@ -28,6 +28,9 @@
# Change to :null_store to avoid any caching.
config.cache_store = :memory_store
+ # Store uploaded files on the local file system (see config/storage.yml for options).
+ config.active_storage.service = :local
+
# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
@@ -48,7 +51,4 @@
# Annotate rendered view with file names.
config.action_view.annotate_rendered_view_with_filenames = true
-
- # Raise error when a before_action's only/except options reference missing actions.
- config.action_controller.raise_on_missing_callback_actions = true
end
diff --git a/example-app/config/environments/production.rb b/example-app/config/environments/production.rb
index c6e93c6..ff425d5 100644
--- a/example-app/config/environments/production.rb
+++ b/example-app/config/environments/production.rb
@@ -21,6 +21,9 @@
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.asset_host = "http://assets.example.com"
+ # Store uploaded files on the local file system (see config/storage.yml for options).
+ config.active_storage.service = :local
+
# Assume all access to the app is happening through a SSL-terminating reverse proxy.
config.assume_ssl = true
diff --git a/example-app/config/environments/test.rb b/example-app/config/environments/test.rb
index 14bc29e..efdc7f7 100644
--- a/example-app/config/environments/test.rb
+++ b/example-app/config/environments/test.rb
@@ -28,6 +28,9 @@
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false
+ # Store uploaded files on the local file system in a temporary directory.
+ config.active_storage.service = :test
+
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
@@ -36,7 +39,4 @@
# Annotate rendered view with file names.
# config.action_view.annotate_rendered_view_with_filenames = true
-
- # Raise error when a before_action's only/except options reference missing actions.
- config.action_controller.raise_on_missing_callback_actions = true
end
diff --git a/example-app/config/initializers/parklife_test.rb b/example-app/config/initializers/parklife_test.rb
new file mode 100644
index 0000000..0884c4c
--- /dev/null
+++ b/example-app/config/initializers/parklife_test.rb
@@ -0,0 +1,11 @@
+case ENV['PARKLIFE_SET_RAILS_URL']
+when 'force_ssl'
+ Rails.application.config.force_ssl = true
+when 'yes'
+ ActionController::Base.relative_url_root = '/foo'
+
+ Rails.application.routes.default_url_options.merge!(
+ host: 'rails.example.org',
+ protocol: 'https',
+ )
+end
diff --git a/example-app/config/routes.rb b/example-app/config/routes.rb
index 7684cd1..0dbba7c 100644
--- a/example-app/config/routes.rb
+++ b/example-app/config/routes.rb
@@ -2,4 +2,7 @@
root to: 'posts#index'
resources :posts
+
+ get 'test/middleware', to: 'test#middleware'
+ get 'test/url', to: 'test#url'
end
diff --git a/example-app/config/storage.yml b/example-app/config/storage.yml
new file mode 100644
index 0000000..f713bf3
--- /dev/null
+++ b/example-app/config/storage.yml
@@ -0,0 +1,34 @@
+test:
+ service: Parklife
+ root: <%= Rails.root.join("tmp/storage") %>
+
+local:
+ service: Parklife
+ root: <%= Rails.root.join("storage") %>
+
+# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
+# amazon:
+# service: S3
+# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
+# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
+# region: us-east-1
+# bucket: your_own_bucket-<%= Rails.env %>
+
+# Remember not to checkin your GCS keyfile to a repository
+# google:
+# service: GCS
+# project: your_project
+# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
+# bucket: your_own_bucket-<%= Rails.env %>
+
+# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
+# microsoft:
+# service: AzureStorage
+# storage_account_name: your_account_name
+# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
+# container: your_container_name-<%= Rails.env %>
+
+# mirror:
+# service: Mirror
+# primary: local
+# mirrors: [ amazon, google, microsoft ]
diff --git a/example-app/db/migrate/20250831102030_create_active_storage_tables.active_storage.rb b/example-app/db/migrate/20250831102030_create_active_storage_tables.active_storage.rb
new file mode 100644
index 0000000..6bd8bd0
--- /dev/null
+++ b/example-app/db/migrate/20250831102030_create_active_storage_tables.active_storage.rb
@@ -0,0 +1,57 @@
+# This migration comes from active_storage (originally 20170806125915)
+class CreateActiveStorageTables < ActiveRecord::Migration[7.0]
+ def change
+ # Use Active Record's configured type for primary and foreign keys
+ primary_key_type, foreign_key_type = primary_and_foreign_key_types
+
+ create_table :active_storage_blobs, id: primary_key_type do |t|
+ t.string :key, null: false
+ t.string :filename, null: false
+ t.string :content_type
+ t.text :metadata
+ t.string :service_name, null: false
+ t.bigint :byte_size, null: false
+ t.string :checksum
+
+ if connection.supports_datetime_with_precision?
+ t.datetime :created_at, precision: 6, null: false
+ else
+ t.datetime :created_at, null: false
+ end
+
+ t.index [ :key ], unique: true
+ end
+
+ create_table :active_storage_attachments, id: primary_key_type do |t|
+ t.string :name, null: false
+ t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
+ t.references :blob, null: false, type: foreign_key_type
+
+ if connection.supports_datetime_with_precision?
+ t.datetime :created_at, precision: 6, null: false
+ else
+ t.datetime :created_at, null: false
+ end
+
+ t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true
+ t.foreign_key :active_storage_blobs, column: :blob_id
+ end
+
+ create_table :active_storage_variant_records, id: primary_key_type do |t|
+ t.belongs_to :blob, null: false, index: false, type: foreign_key_type
+ t.string :variation_digest, null: false
+
+ t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true
+ t.foreign_key :active_storage_blobs, column: :blob_id
+ end
+ end
+
+ private
+ def primary_and_foreign_key_types
+ config = Rails.configuration.generators
+ setting = config.options[config.orm][:primary_key_type]
+ primary_key_type = setting || :primary_key
+ foreign_key_type = setting || :bigint
+ [ primary_key_type, foreign_key_type ]
+ end
+end
diff --git a/example-app/db/schema.rb b/example-app/db/schema.rb
index a8f0e44..26d4602 100644
--- a/example-app/db/schema.rb
+++ b/example-app/db/schema.rb
@@ -10,7 +10,35 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.0].define(version: 2019_05_07_172823) do
+ActiveRecord::Schema[7.0].define(version: 2025_08_31_102030) do
+ create_table "active_storage_attachments", force: :cascade do |t|
+ t.string "name", null: false
+ t.string "record_type", null: false
+ t.bigint "record_id", null: false
+ t.bigint "blob_id", null: false
+ t.datetime "created_at", null: false
+ t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
+ t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
+ end
+
+ create_table "active_storage_blobs", force: :cascade do |t|
+ t.string "key", null: false
+ t.string "filename", null: false
+ t.string "content_type"
+ t.text "metadata"
+ t.string "service_name", null: false
+ t.bigint "byte_size", null: false
+ t.string "checksum"
+ t.datetime "created_at", null: false
+ t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
+ end
+
+ create_table "active_storage_variant_records", force: :cascade do |t|
+ t.bigint "blob_id", null: false
+ t.string "variation_digest", null: false
+ t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
+ end
+
create_table "posts", force: :cascade do |t|
t.string "slug", null: false
t.string "title", null: false
@@ -20,4 +48,6 @@
t.index ["slug"], name: "index_posts_on_slug", unique: true
end
+ add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
+ add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
end
diff --git a/example-app/db/seeds.rb b/example-app/db/seeds.rb
index 6889bd9..538328f 100644
--- a/example-app/db/seeds.rb
+++ b/example-app/db/seeds.rb
@@ -1,4 +1,6 @@
-Rails.root.join('data').children.each do |path|
+plasma_path = File.expand_path('../../spec/fixtures/files/plasma.jpg', __dir__)
+
+Rails.root.join('data').children.each_with_index do |path, i|
id, slug = path.basename('.*').to_s.split('-', 2)
title, rest = path.read.split("\n", 2)
@@ -7,4 +9,9 @@
post.slug = slug
post.title = title
post.save!
+
+ post.hero.attach(
+ filename: 'plasma.jpg',
+ io: File.open(plasma_path),
+ ) if i.odd?
end
diff --git a/gemfiles/rails_7.0.gemfile b/gemfiles/rails_7.0.gemfile
index 2bd2332..b52755b 100644
--- a/gemfiles/rails_7.0.gemfile
+++ b/gemfiles/rails_7.0.gemfile
@@ -4,9 +4,20 @@ source "https://rubygems.org"
gem "appraisal"
gem "rake"
-gem "rspec"
gem "rubocop"
+gem "image_processing"
+gem "sprockets-rails"
+gem "sqlite3", "<2"
gem "concurrent-ruby", "< 1.3.5"
gem "rails", "~> 7.0.0"
+group :development, :test do
+ gem "rspec-rails"
+end
+
+group :test do
+ gem "capybara"
+ gem "factory_bot_rails"
+end
+
gemspec path: "../"
diff --git a/gemfiles/rails_7.1.gemfile b/gemfiles/rails_7.1.gemfile
index 2fb8d4b..be07848 100644
--- a/gemfiles/rails_7.1.gemfile
+++ b/gemfiles/rails_7.1.gemfile
@@ -4,8 +4,19 @@ source "https://rubygems.org"
gem "appraisal"
gem "rake"
-gem "rspec"
gem "rubocop"
+gem "image_processing"
+gem "sprockets-rails"
+gem "sqlite3"
gem "rails", "~> 7.1.0"
+group :development, :test do
+ gem "rspec-rails"
+end
+
+group :test do
+ gem "capybara"
+ gem "factory_bot_rails"
+end
+
gemspec path: "../"
diff --git a/gemfiles/rails_7.2.gemfile b/gemfiles/rails_7.2.gemfile
index eb605c2..8d781ab 100644
--- a/gemfiles/rails_7.2.gemfile
+++ b/gemfiles/rails_7.2.gemfile
@@ -4,8 +4,19 @@ source "https://rubygems.org"
gem "appraisal"
gem "rake"
-gem "rspec"
gem "rubocop"
+gem "image_processing"
+gem "sprockets-rails"
+gem "sqlite3"
gem "rails", "~> 7.2.0"
+group :development, :test do
+ gem "rspec-rails"
+end
+
+group :test do
+ gem "capybara"
+ gem "factory_bot_rails"
+end
+
gemspec path: "../"
diff --git a/gemfiles/rails_8.0.gemfile b/gemfiles/rails_8.0.gemfile
index 34d56ef..b1c712c 100644
--- a/gemfiles/rails_8.0.gemfile
+++ b/gemfiles/rails_8.0.gemfile
@@ -4,8 +4,19 @@ source "https://rubygems.org"
gem "appraisal"
gem "rake"
-gem "rspec"
gem "rubocop"
+gem "image_processing"
+gem "sprockets-rails"
+gem "sqlite3"
gem "rails", "~> 8.0.0"
+group :development, :test do
+ gem "rspec-rails"
+end
+
+group :test do
+ gem "capybara"
+ gem "factory_bot_rails"
+end
+
gemspec path: "../"
diff --git a/spec/factories.rb b/spec/factories.rb
new file mode 100644
index 0000000..46bf428
--- /dev/null
+++ b/spec/factories.rb
@@ -0,0 +1,7 @@
+FactoryBot.define do
+ factory :post do
+ sequence(:slug) { |n| "slug-#{n}" }
+ sequence(:title) { |n| "Title #{n}" }
+ body { 'foo' }
+ end
+end
diff --git a/spec/features/active_storage_spec.rb b/spec/features/active_storage_spec.rb
new file mode 100644
index 0000000..5afe791
--- /dev/null
+++ b/spec/features/active_storage_spec.rb
@@ -0,0 +1,30 @@
+require 'rails_helper'
+
+RSpec.describe 'ActiveStorage integration' do
+ context 'with the Parklife service' do
+ let!(:post1) { FactoryBot.create(:post) }
+ let!(:post2) { FactoryBot.create(:post) }
+
+ before do
+ post1.hero.attach(
+ filename: 'plasma.jpg',
+ io: file_fixture('plasma.jpg').open,
+ )
+ end
+
+ it "works as normal and uses Parklife's blob controller" do
+ visit root_path
+ click_link post2.title
+
+ expect(page).not_to have_css('img')
+
+ click_link 'Home'
+ click_link post1.title
+
+ expect(page).to have_css('img')
+
+ img = find('img')
+ expect(img[:src]).to start_with('/parklife/')
+ end
+ end
+end
diff --git a/spec/fixtures/files/plasma.jpg b/spec/fixtures/files/plasma.jpg
new file mode 100644
index 0000000..8d31ae9
Binary files /dev/null and b/spec/fixtures/files/plasma.jpg differ
diff --git a/spec/parklife/integration_spec.rb b/spec/parklife/integration_spec.rb
new file mode 100644
index 0000000..8156304
--- /dev/null
+++ b/spec/parklife/integration_spec.rb
@@ -0,0 +1,90 @@
+require 'open3'
+
+RSpec.describe 'Integration tests' do
+ def command(cmd, env = {})
+ env = { 'RAILS_ENV' => 'test' }.merge(env)
+
+ Dir.chdir('example-app') do
+ Open3.capture2(env, cmd)
+ end
+ end
+
+ def parklife(cmd, env = {})
+ command("bundle exec parklife #{cmd}", env)
+ end
+
+ it 'gives access to Rails URL helpers when defining routes' do
+ stdout, status = parklife('routes')
+ expect(status).to be_success
+ expect(stdout).to include('/test/middleware', '/test/url')
+ end
+
+ it 'configures Rails default_url_options/relative_url_root when setting a Parklife base' do
+ stdout, status = parklife('get /test/url')
+ expect(status).to be_success
+ expect(stdout.chomp).to eql('http://example.com/test/url')
+
+ stdout, status = parklife('get /test/url --base /foo')
+ expect(status).to be_success
+ expect(stdout.chomp).to eql('http://example.com/foo/test/url')
+
+ stdout, status = parklife('get /test/url --base https://foo.example.org/foo/bar')
+ expect(status).to be_success
+ expect(stdout.chomp).to eql('https://foo.example.org/foo/bar/test/url')
+ end
+
+ it "sets Parklife's base from Rails default_url_options/relative_url_root when they're defined" do
+ stdout, status = parklife('get /test/url', 'PARKLIFE_SET_RAILS_URL' => 'yes')
+ expect(status).to be_success
+ expect(stdout.chomp).to eql('https://rails.example.org/foo/test/url')
+ end
+
+ it "updates Parklife's base with Rails force_ssl setting" do
+ stdout, status = parklife('get /test/url', 'PARKLIFE_SET_RAILS_URL' => 'force_ssl')
+ expect(status).to be_success
+ expect(stdout.chomp).to eql('https://example.com/test/url')
+ end
+
+ it 'host authorization middleware is removed in development - but only for a Parklife request' do
+ env = { 'RAILS_ENV' => 'development' }
+
+ stdout, status = command('bundle exec rails middleware', env)
+ expect(status).to be_success
+ expect(stdout).to include('ActionDispatch::HostAuthorization')
+
+ stdout, status = parklife('get /test/middleware', env)
+ expect(status).to be_success
+ expect(stdout).not_to include('ActionDispatch::HostAuthorization')
+ end
+
+ it 'builds successfully and includes encountered ActiveStorage blobs' do
+ skip('FIXME: get me working on CI') if ENV['CI']
+
+ env = { 'RAILS_ENV' => 'development' }
+
+ stdout, status = parklife('build', env)
+ expect(status).to be_success
+ expect(stdout).not_to be_empty
+
+ Dir.chdir('example-app/build') do
+ build_files = Dir
+ .glob('**/*')
+ .select { |it| File.file?(it) }
+
+ # HTML pages.
+ expect(build_files).to include(
+ 'index.html',
+ 'posts/hello-again/index.html',
+ 'posts/hello-from-parklife/index.html',
+ 'posts/magic-number/index.html',
+ )
+
+ # ActiveStorage blobs.
+ expect(
+ build_files
+ .select { |it| it.end_with?('/plasma.jpg') }
+ .size
+ ).to be(2)
+ end
+ end
+end
diff --git a/spec/parklife/rails_spec.rb b/spec/parklife/rails_spec.rb
deleted file mode 100644
index c91a0b8..0000000
--- a/spec/parklife/rails_spec.rb
+++ /dev/null
@@ -1,135 +0,0 @@
-require 'action_controller'
-require 'parklife/application'
-require 'parklife/rails/build_integration'
-
-RSpec.describe 'Parklife Rails integration' do
- let(:parklife_app) { Parklife::Application.new }
- let(:rails_app) {
- Class.new(Rails::Application) do
- # Silence warning in Rails 7.1.
- config.active_support.cache_format_version = 7.0
-
- # Silence warning in Rails 8.0.
- config.active_support.to_time_preserves_timezone = :zone
-
- config.eager_load = false
- config.logger = Logger.new('/dev/null')
- end
- }
-
- def initialize!
- rails_app.initialize!
- end
-
- around do |example|
- # Initialising each test Rails app in their own current working directory
- # stops Zeitwerk complaining about the same directory being managed by
- # different loaders.
- Dir.mktmpdir do |dir|
- Dir.chdir dir do
- example.run
- end
- end
- end
-
- before do
- allow(Parklife).to receive(:application).and_return(parklife_app)
- Rails.application = rails_app
- end
-
- after do
- ActionController::Base.relative_url_root = nil
- ActiveSupport::Dependencies.autoload_paths = []
- ActiveSupport::Dependencies.autoload_once_paths = []
- end
-
- it 'gives access to Rails URL helpers when defining routes' do
- initialize!
-
- rails_app.routes.draw do
- get :foo, to: proc { [200, {}, 'foo'] }
- end
-
- parklife_app.routes do
- get foo_path
- end
-
- route = Parklife::Route.new('/foo', crawl: false)
- expect(parklife_app.routes).to include(route)
-
- another_parklife_app = Parklife::Application.new
-
- expect {
- another_parklife_app.routes do
- get foo_path
- end
- }.to raise_error(NameError, /foo_path/)
- end
-
- it 'configures Rails default_url_options and relative_url_root when setting Parklife base' do
- initialize!
-
- parklife_app.config.base = 'https://localhost:3000/foo'
-
- expect(rails_app.default_url_options).to eql({
- host: 'localhost:3000',
- protocol: 'https',
- })
- expect(ActionController::Base.relative_url_root).to eql('/foo')
-
- parklife_app.config.base = 'http://foo.example.com/'
-
- expect(rails_app.default_url_options).to eql({
- host: 'foo.example.com',
- protocol: 'http',
- })
- expect(ActionController::Base.relative_url_root).to be_nil
-
- expect {
- another_parklife_app = Parklife::Application.new
- another_parklife_app.config.base = 'https://example.com/foo'
- }.not_to change {
- [rails_app.default_url_options, ActionController::Base.relative_url_root]
- }
- end
-
- it 'removes host authorization middleware' do
- initialize!
-
- expect(Rails.application.middleware).not_to include(ActionDispatch::HostAuthorization)
- end
-
- context 'setting the Parklife base from Rails config on initialize' do
- it 'uses Parklife defaults when nothing is set' do
- initialize!
-
- expect(parklife_app.config.base).to have_attributes(
- host: 'example.com',
- path: '',
- scheme: 'http',
- )
- end
-
- it 'copies the values from default_url_options and relative_url_root' do
- rails_app.default_url_options = { host: 'foo', protocol: 'bar' }
- ActionController::Base.relative_url_root = '/baz'
-
- initialize!
-
- expect(parklife_app.config.base).to have_attributes(
- host: 'foo',
- path: '/baz',
- scheme: 'bar',
- )
- end
-
- it 'always uses https if force_ssl=true' do
- rails_app.default_url_options = { protocol: 'foo' }
- rails_app.config.force_ssl = true
-
- initialize!
-
- expect(parklife_app.config.base.scheme).to eql('https')
- end
- end
-end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
new file mode 100644
index 0000000..36437a8
--- /dev/null
+++ b/spec/rails_helper.rb
@@ -0,0 +1,72 @@
+# This file is copied to spec/ when you run 'rails generate rspec:install'
+require 'spec_helper'
+ENV['RAILS_ENV'] ||= 'test'
+require_relative '../example-app/config/environment'
+# Prevent database truncation if the environment is production
+abort("The Rails environment is running in production mode!") if Rails.env.production?
+# Uncomment the line below in case you have `--require rails_helper` in the `.rspec` file
+# that will avoid rails generators crashing because migrations haven't been run yet
+# return unless Rails.env.test?
+require 'rspec/rails'
+# Add additional requires below this line. Rails is not loaded until this point!
+
+require 'factories'
+
+# Requires supporting ruby files with custom matchers and macros, etc, in
+# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
+# run as spec files by default. This means that files in spec/support that end
+# in _spec.rb will both be required and run as specs, causing the specs to be
+# run twice. It is recommended that you do not name files matching this glob to
+# end with _spec.rb. You can configure this pattern with the --pattern
+# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
+#
+# The following line is provided for convenience purposes. It has the downside
+# of increasing the boot-up time by auto-requiring all files in the support
+# directory. Alternatively, in the individual `*_spec.rb` files, manually
+# require only the support files necessary.
+#
+# Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f }
+
+# Ensures that the test database schema matches the current schema file.
+# If there are pending migrations it will invoke `db:test:prepare` to
+# recreate the test database by loading the schema.
+# If you are not using ActiveRecord, you can remove these lines.
+begin
+ ActiveRecord::Migration.maintain_test_schema!
+rescue ActiveRecord::PendingMigrationError => e
+ abort e.to_s.strip
+end
+
+RSpec.configure do |config|
+ # If you're not using ActiveRecord, or you'd prefer not to run each of your
+ # examples within a transaction, remove the following line or assign false
+ # instead of true.
+ config.use_transactional_fixtures = true
+
+ # You can uncomment this line to turn off ActiveRecord support entirely.
+ # config.use_active_record = false
+
+ # RSpec Rails uses metadata to mix in different behaviours to your tests,
+ # for example enabling you to call `get` and `post` in request specs. e.g.:
+ #
+ # RSpec.describe UsersController, type: :request do
+ # # ...
+ # end
+ #
+ # The different available types are documented in the features, such as in
+ # https://rspec.info/features/8-0/rspec-rails
+ #
+ # You can also this infer these behaviours automatically by location, e.g.
+ # /spec/models would pull in the same behaviour as `type: :model` but this
+ # behaviour is considered legacy and will be removed in a future version.
+ #
+ # To enable this behaviour uncomment the line below.
+ config.infer_spec_type_from_file_location!
+
+ # Filter lines from Rails gems in backtraces.
+ config.filter_rails_from_backtrace!
+ # arbitrary gems may also be filtered via:
+ # config.filter_gems_from_backtrace("gem name")
+
+ config.include FactoryBot::Syntax::Methods
+end