parklife-rails 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab76eceaed9401e81a67c48ee2ca9b55e7e90ce62b7bf31549e358d838c0106b
4
- data.tar.gz: a230681dfc800e4700dc6218dde011dca3c93e6bebc2af500932d74814e3b249
3
+ metadata.gz: 61520daa959509f52822c34d3b946dca45f285fbdc1389a628389440f7d32c1e
4
+ data.tar.gz: 75df6c7770beb136684d4c312faf97b78c0aaf67705c4fa67785c600a4b35122
5
5
  SHA512:
6
- metadata.gz: 1f9c7f1613d0888bc1e1114c0bb4a2d366e337982c990920c290adc09bd452855297cf2d24dcb8c00f702394d206a69078e152441a413934fe68579e9dcb8c70
7
- data.tar.gz: bcf023342973cb13d307160bf106f9e2b1e66c55b0a1e3cb35f5d8946238da9677b4d1944cf44d0d693313771421ba86e101d5a32ecbd744cc87ba9fc957a4b3
6
+ metadata.gz: 2ead1d16e2162277d7d1d6900af837fbf4db4ac692d5be1515bb4e43f59566b8006bc0dd9be4710fdf5784399d10925e58c88fe79a8de308541b5ee6ffbd3dee
7
+ data.tar.gz: f56206e1a280ff942ad0014e24fca6c6434ca4dc73d4fdf4ec3e36061475e63ffed7ed16aa3e06a34a0bdacca905d71e1b304e8d84120dc36f620cebc802c686
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## Unreleased
2
2
 
3
+ ## 0.2.0 - 2025-08-21
4
+
5
+ - ActiveStorage integration.
6
+
3
7
  ## 0.1.0 - 2025-08-10
4
8
 
5
9
  - Initial release extracted from Parklife 0.7.0.
data/README.md CHANGED
@@ -8,6 +8,35 @@ Add the gem to your application's `Gemfile`:
8
8
  gem 'parklife-rails'
9
9
  ```
10
10
 
11
+ ## ActiveStorage integration
12
+
13
+ Parklife's ActiveStorage integration allows you to use ActiveStorage as normal in development, then during a Parklife build any encountered attachments are collected and copied to the build directory so they can be served alongside the rest of your static files. This is achieved via a Rails Engine and custom ActiveStorage DiskService which work together to tweak ActiveStorage URLs so they're suitable for a static web server.
14
+
15
+ Enable the engine at the bottom of `config/application.rb`:
16
+
17
+ > [!NOTE]
18
+ > This must be done before the app boots so can't be in an initializer.
19
+
20
+ ```ruby
21
+ require 'parklife-rails/activestorage'
22
+ ```
23
+
24
+ Then switch to Parklife's ActiveStorage service in `config/storage.yml`:
25
+
26
+ ```yml
27
+ local:
28
+ service: Parklife
29
+ root: <%= Rails.root.join("storage") %>
30
+ ```
31
+
32
+ Finally anywhere an attachment is referenced make sure to use the `processed` URL:
33
+
34
+ ```ruby
35
+ image_tag(
36
+ blog_post.hero_image.representation(:medium).processed.url
37
+ )
38
+ ```
39
+
11
40
  ## Contributing
12
41
 
13
42
  Bug reports and pull requests are welcome on GitHub at <https://github.com/benpickles/parklife-rails>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/benpickles/parklife-rails/blob/main/CODE_OF_CONDUCT.md).
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ module Parklife
3
+ module Rails
4
+ module ActiveStorage
5
+ class BlobsController < ActionController::Base
6
+ include ::ActiveStorage::FileServer
7
+
8
+ def show
9
+ blob = ::ActiveStorage::Blob.find_by!(key: params[:key])
10
+
11
+ serve_file(
12
+ named_disk_service(blob.service_name).path_for(blob.key),
13
+ content_type: blob.content_type,
14
+ disposition: :inline,
15
+ )
16
+ rescue Errno::ENOENT
17
+ head :not_found
18
+ end
19
+
20
+ private
21
+ def named_disk_service(name)
22
+ ::ActiveStorage::Blob.services.fetch(name)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ Rails.application.routes.draw do
3
+ scope Parklife::Rails::ActiveStorage.routes_prefix do
4
+ get 'blobs/:key/*filename',
5
+ to: 'parklife/rails/active_storage/blobs#show',
6
+ as: :parklife_blob_service
7
+ end
8
+
9
+ direct :parklife_blob do |blob, options|
10
+ route_for(
11
+ :parklife_blob_service,
12
+ blob.key,
13
+ blob.filename,
14
+ { only_path: true }.merge(options),
15
+ )
16
+ end
17
+
18
+ resolve('ActiveStorage::Attachment') { |attachment, options| route_for(:parklife_blob, attachment.blob, options) }
19
+ resolve('ActiveStorage::Blob') { |blob, options| route_for(:parklife_blob, blob, options) }
20
+ resolve('ActiveStorage::Preview') { |preview, options| route_for(:parklife_blob, preview.blob, options) }
21
+ resolve('ActiveStorage::VariantWithRecord') { |variant, options| route_for(:parklife_blob, variant.blob, options) }
22
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ require 'active_storage/service/disk_service'
3
+
4
+ module ActiveStorage
5
+ class Service::ParklifeService < Service::DiskService
6
+ def url(key, **options)
7
+ super.tap do |url|
8
+ if Parklife::Rails::ActiveStorage.collect_assets
9
+ Parklife::Rails::ActiveStorage.collect_asset(self, key, url)
10
+ end
11
+ end
12
+ end
13
+
14
+ private
15
+ def generate_url(key, expires_in:, filename:, content_type:, disposition:) # rubocop:disable Lint/UnusedMethodArgument
16
+ url_helpers.parklife_blob_service_path(key: key, filename: filename)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ module Parklife
3
+ module Rails
4
+ module ActiveStorage
5
+ Asset = Struct.new(:service, :key, :url) do
6
+ def blob_path
7
+ service.path_for(key)
8
+ end
9
+ end
10
+
11
+ class Engine < ::Rails::Engine
12
+ isolate_namespace Parklife::Rails::ActiveStorage
13
+
14
+ initializer 'parklife.app_integration' do |app|
15
+ # Disable the standard ActiveStorage routes that will otherwise
16
+ # prevent a blob being served by Parklife's controller.
17
+ app.config.active_storage.draw_routes = false
18
+ end
19
+ end
20
+
21
+ mattr_accessor :collect_assets, default: false
22
+ mattr_accessor :collected_assets, default: {}
23
+ mattr_accessor :routes_prefix, default: 'parklife'
24
+
25
+ def self.collect_asset(service, key, url)
26
+ collected_assets[key] ||= Asset.new(service, key, url)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+ require 'fileutils'
3
+ require 'parklife'
4
+ require 'rails'
5
+ require_relative 'config_refinements'
6
+ require_relative 'route_set_refinements'
7
+
8
+ module Parklife
9
+ module Rails
10
+ class BuildIntegration < ::Rails::Railtie
11
+ initializer 'parklife.build_integration' do |app|
12
+ # The offending middleware is included in Rails (6+) development mode and
13
+ # rejects a request with a 403 response if its host isn't present in the
14
+ # allowlist (a security feature). This prevents Parklife from working in
15
+ # a Rails app out of the box unless you manually add the expected
16
+ # Parklife base to the hosts allowlist or set it to nil to disable it -
17
+ # both of which aren't great because they disable the security feature
18
+ # whenever the development server is booted.
19
+ #
20
+ # https://guides.rubyonrails.org/configuring.html#actiondispatch-hostauthorization
21
+ #
22
+ # However it's safe to remove the middleware at this point because it
23
+ # won't be executed in the normal Rails development flow, only via a
24
+ # Parkfile when parklife/rails is required.
25
+ if defined?(ActionDispatch::HostAuthorization)
26
+ app.middleware.delete(ActionDispatch::HostAuthorization)
27
+ end
28
+
29
+ Parklife.application.config.app = app
30
+
31
+ # This ensures next tweak is compatibile with Rails 8+ lazy routes.
32
+ Parklife.application.routes.extend(RouteSetRefinements)
33
+
34
+ # Allow use of the Rails application's route helpers when defining
35
+ # Parklife routes in the block form.
36
+ Parklife.application.routes.singleton_class.include(app.routes.url_helpers)
37
+
38
+ Parklife.application.config.extend(ConfigRefinements)
39
+ end
40
+
41
+ config.after_initialize do |app|
42
+ # Read the Rails app's URL config and apply it to Parklife's so that the
43
+ # Rails config can be used as the single source of truth.
44
+ host, port, protocol = app.default_url_options.values_at(:host, :port, :protocol)
45
+ protocol = 'https' if app.config.force_ssl
46
+ path = ActionController::Base.relative_url_root
47
+
48
+ Parklife.application.config.base.scheme = protocol if protocol
49
+ Parklife.application.config.base.host = host if host
50
+ Parklife.application.config.base.port = port if port
51
+ Parklife.application.config.base.path = path if path
52
+
53
+ # If the host Rails app includes Parklife's ActiveStorage integration
54
+ # then automatically collect attachments encountered during a build and
55
+ # copy them to the build directory.
56
+ if defined?(Parklife::Rails::ActiveStorage)
57
+ Parklife.application.before_build do
58
+ ActiveStorage.collected_assets.clear
59
+ ActiveStorage.collect_assets = true
60
+ end
61
+
62
+ Parklife.application.after_build do
63
+ ActiveStorage.collected_assets.each_value do |asset|
64
+ build_path = File.join(Parklife.application.config.build_dir, asset.url)
65
+ FileUtils.mkdir_p(File.dirname(build_path))
66
+ FileUtils.cp(asset.blob_path, build_path)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ module Parklife
3
+ module Rails
4
+ module ConfigRefinements
5
+ # When setting Parklife's base also configure the Rails app's
6
+ # default_url_options and relative_url_root to match.
7
+ def base=(value)
8
+ super.tap { |uri|
9
+ app.default_url_options = {
10
+ host: Utils.host_with_port(uri),
11
+ protocol: uri.scheme,
12
+ }
13
+
14
+ base_path = !uri.path.empty? && uri.path != '/' ? uri.path : nil
15
+ ActionController::Base.relative_url_root = base_path
16
+ }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ module Parklife
3
+ module Rails
4
+ module RouteSetRefinements
5
+ def default_url_options
6
+ ::Rails.application.default_url_options
7
+ end
8
+ end
9
+ end
10
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Parklife
3
3
  module Rails
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
6
6
  end
@@ -1,70 +1,9 @@
1
1
  # frozen_string_literal: true
2
- require 'parklife'
3
- require 'rails'
4
2
 
5
- module Parklife
6
- module RailsConfigRefinements
7
- # When setting Parklife's base also configure the Rails app's
8
- # default_url_options and relative_url_root to match.
9
- def base=(value)
10
- super.tap { |uri|
11
- app.default_url_options = {
12
- host: Utils.host_with_port(uri),
13
- protocol: uri.scheme,
14
- }
15
-
16
- base_path = !uri.path.empty? && uri.path != '/' ? uri.path : nil
17
- ActionController::Base.relative_url_root = base_path
18
- }
19
- end
20
- end
21
-
22
- module RailsRouteSetRefinements
23
- def default_url_options
24
- ::Rails.application.default_url_options
25
- end
26
- end
27
-
28
- class Railtie < ::Rails::Railtie
29
- initializer 'parklife.disable_host_authorization' do |app|
30
- # The offending middleware is included in Rails (6+) development mode and
31
- # rejects a request with a 403 response if its host isn't present in the
32
- # allowlist (a security feature). This prevents Parklife from working in
33
- # a Rails app out of the box unless you manually add the expected
34
- # Parklife base to the hosts allowlist or set it to nil to disable it -
35
- # both of which aren't great because they disable the security feature
36
- # whenever the development server is booted.
37
- #
38
- # https://guides.rubyonrails.org/configuring.html#actiondispatch-hostauthorization
39
- #
40
- # However it's safe to remove the middleware at this point because it
41
- # won't be executed in the normal Rails development flow, only via a
42
- # Parkfile when parklife/rails is required.
43
- if defined?(ActionDispatch::HostAuthorization)
44
- app.middleware.delete(ActionDispatch::HostAuthorization)
45
- end
46
-
47
- Parklife.application.config.app = app
48
-
49
- # Allow use of the Rails application's route helpers when defining
50
- # Parklife routes in the block form.
51
- Parklife.application.routes.singleton_class.include(RailsRouteSetRefinements)
52
- Parklife.application.routes.singleton_class.include(app.routes.url_helpers)
53
-
54
- Parklife.application.config.extend(RailsConfigRefinements)
55
- end
56
-
57
- config.after_initialize do |app|
58
- # Read the Rails app's URL config and apply it to Parklife's so that the
59
- # Rails config can be used as the single source of truth.
60
- host, port, protocol = app.default_url_options.values_at(:host, :port, :protocol)
61
- protocol = 'https' if app.config.force_ssl
62
- path = ActionController::Base.relative_url_root
63
-
64
- Parklife.application.config.base.scheme = protocol if protocol
65
- Parklife.application.config.base.host = host if host
66
- Parklife.application.config.base.port = port if port
67
- Parklife.application.config.base.path = path if path
68
- end
69
- end
3
+ # Only require the build integration when running from a Parklife CLI command.
4
+ #
5
+ # This means that the gem can safely be included in the app's Gemfile without
6
+ # applying any of its build-time tweaks.
7
+ if defined?(Parklife::CLI)
8
+ require_relative 'rails/build_integration'
70
9
  end
@@ -0,0 +1,2 @@
1
+ # frozen_string_literal: true
2
+ require_relative '../parklife/rails/activestorage'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parklife-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Pickles
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-08-10 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: parklife
@@ -38,7 +37,6 @@ dependencies:
38
37
  - - ">="
39
38
  - !ruby/object:Gem::Version
40
39
  version: '0'
41
- description:
42
40
  email:
43
41
  - spideryoung@gmail.com
44
42
  executables: []
@@ -51,12 +49,20 @@ files:
51
49
  - LICENSE.txt
52
50
  - README.md
53
51
  - Rakefile
52
+ - app/controllers/parklife/rails/active_storage/blobs_controller.rb
53
+ - config/routes.rb
54
54
  - gemfiles/rails_7.0.gemfile
55
55
  - gemfiles/rails_7.1.gemfile
56
56
  - gemfiles/rails_7.2.gemfile
57
57
  - gemfiles/rails_8.0.gemfile
58
+ - lib/active_storage/service/parklife_service.rb
58
59
  - lib/parklife-rails.rb
60
+ - lib/parklife-rails/activestorage.rb
59
61
  - lib/parklife/rails.rb
62
+ - lib/parklife/rails/activestorage.rb
63
+ - lib/parklife/rails/build_integration.rb
64
+ - lib/parklife/rails/config_refinements.rb
65
+ - lib/parklife/rails/route_set_refinements.rb
60
66
  - lib/parklife/rails/version.rb
61
67
  homepage: https://parklife.dev/rails
62
68
  licenses:
@@ -66,7 +72,6 @@ metadata:
66
72
  homepage_uri: https://parklife.dev/rails
67
73
  rubygems_mfa_required: 'true'
68
74
  source_code_uri: https://github.com/benpickles/parklife-rails
69
- post_install_message:
70
75
  rdoc_options: []
71
76
  require_paths:
72
77
  - lib
@@ -81,8 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
86
  - !ruby/object:Gem::Version
82
87
  version: '0'
83
88
  requirements: []
84
- rubygems_version: 3.1.6
85
- signing_key:
89
+ rubygems_version: 3.7.1
86
90
  specification_version: 4
87
91
  summary: Rails integration for Parklife
88
92
  test_files: []