activestorage-memory 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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +28 -7
  3. data/app/controllers/activestorage/memory/memory_controller.rb +60 -0
  4. data/config/routes.rb +6 -0
  5. data/lib/active_storage/service/memory_service.rb +102 -12
  6. data/lib/activestorage/memory/engine.rb +7 -0
  7. data/lib/{active_storage → activestorage}/memory/version.rb +2 -2
  8. data/lib/{active_storage → activestorage}/memory.rb +2 -1
  9. data/spec/active_storage/service/memory_service_spec.rb +99 -0
  10. data/spec/activestorage/memory_spec.rb +7 -0
  11. data/spec/controllers/activestorage/memory/memory_controller_spec.rb +136 -0
  12. data/spec/dummy/Rakefile +6 -0
  13. data/spec/dummy/app/assets/config/manifest.js +3 -0
  14. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  15. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  16. data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
  17. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  18. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  19. data/spec/dummy/app/jobs/application_job.rb +7 -0
  20. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  21. data/spec/dummy/app/models/application_record.rb +3 -0
  22. data/spec/dummy/app/views/layouts/application.html.erb +15 -0
  23. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  24. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  25. data/spec/dummy/bin/rails +4 -0
  26. data/spec/dummy/bin/rake +4 -0
  27. data/spec/dummy/bin/setup +33 -0
  28. data/spec/dummy/config/application.rb +44 -0
  29. data/spec/dummy/config/boot.rb +5 -0
  30. data/spec/dummy/config/cable.yml +10 -0
  31. data/spec/dummy/config/database.yml +25 -0
  32. data/spec/dummy/config/environment.rb +5 -0
  33. data/spec/dummy/config/environments/development.rb +76 -0
  34. data/spec/dummy/config/environments/production.rb +97 -0
  35. data/spec/dummy/config/environments/test.rb +66 -0
  36. data/spec/dummy/config/initializers/assets.rb +12 -0
  37. data/spec/dummy/config/initializers/content_security_policy.rb +25 -0
  38. data/spec/dummy/config/initializers/filter_parameter_logging.rb +8 -0
  39. data/spec/dummy/config/initializers/inflections.rb +16 -0
  40. data/spec/dummy/config/initializers/permissions_policy.rb +13 -0
  41. data/spec/dummy/config/locales/en.yml +31 -0
  42. data/spec/dummy/config/puma.rb +35 -0
  43. data/spec/dummy/config/routes.rb +3 -0
  44. data/spec/dummy/config/storage.yml +37 -0
  45. data/spec/dummy/config.ru +6 -0
  46. data/spec/dummy/db/migrate/20240413101449_create_active_storage_tables.active_storage.rb +57 -0
  47. data/spec/dummy/db/schema.rb +44 -0
  48. data/spec/dummy/log/development.log +53 -0
  49. data/spec/dummy/log/test.log +4820 -0
  50. data/spec/dummy/public/404.html +67 -0
  51. data/spec/dummy/public/422.html +67 -0
  52. data/spec/dummy/public/500.html +66 -0
  53. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  54. data/spec/dummy/public/apple-touch-icon.png +0 -0
  55. data/spec/dummy/public/favicon.ico +0 -0
  56. data/spec/dummy/storage/development.sqlite3 +0 -0
  57. data/spec/dummy/storage/test.sqlite3 +0 -0
  58. data/spec/dummy/tmp/local_secret.txt +1 -0
  59. data/spec/dummy/tmp/restart.txt +0 -0
  60. data/spec/rails_helper.rb +65 -0
  61. data/spec/spec_helper.rb +15 -0
  62. metadata +119 -19
  63. data/.github/workflows/main.yml +0 -18
  64. data/.gitignore +0 -11
  65. data/.rspec +0 -3
  66. data/Gemfile +0 -10
  67. data/Gemfile.lock +0 -204
  68. data/LICENSE.txt +0 -21
  69. data/activestorage-memory.gemspec +0 -36
  70. data/bin/console +0 -15
  71. data/bin/setup +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 225cd38418cfa2b7318059599b306cecb07a0a14f16673ab13468a261ca7c87d
4
- data.tar.gz: c96879c4425e6df7c435b52be5718187f8b5bf6cfba92131da1f6d5632cb3a01
3
+ metadata.gz: 1b5979d939220916998f7502ffce56be3fbf14328601cdcfae7d3aeaaa181ce2
4
+ data.tar.gz: 615c9f1c2472e74058a353f1c17c79a80eee114a6fb97a770973f68dd0c1fde9
5
5
  SHA512:
6
- metadata.gz: 0a69efcb428a4a2b9fead9ee4d1c78427fcd6823e4314cb4a550c98ab05bf8fd88ebb476f9c90761f09b3b581cd5f7ed91d294112a897a8f94a81c4bf96cbeee
7
- data.tar.gz: bd0a3463ec6ad8d08b93c318a9f299610be8452d09f5b0ed862cd5417c0ffe33ee1210f4124997d7a049853b50d4821b2f66f007469a15b7bb5cb9481f5c7245
6
+ metadata.gz: 0161b44194c712db207d028801bb57174ed2ba05322ae056b6989a690e870df5d4769316313bae0f0c1df4e9bb9ccf0d434f6e0cffdc6f02147f41b95534bc5b
7
+ data.tar.gz: 2698dd5ca0974dcdc6443dbcde2065822e2b32f91a13da1d22c6f7b07f5f423b2d0c9e0ee50b70401cd38e904500c58de7206a4cac41582a9504af4f0a77eb0e
data/README.md CHANGED
@@ -1,8 +1,7 @@
1
- # ActiveStorage::Memory
1
+ # ActiveStorage-Memory
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/active_storage/memory`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Provides an in-memory ActiveStorage service.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
6
5
 
7
6
  ## Installation
8
7
 
@@ -22,13 +21,35 @@ Or install it yourself as:
22
21
 
23
22
  ## Usage
24
23
 
25
- TODO: Write usage instructions here
24
+ Declare a Memory service in config/storage.yml
25
+
26
+ ```
27
+ memory:
28
+ service: Memory
29
+ ```
30
+
31
+ To use the Memory service in test, you add the following to config/environments/test.rb:
32
+
33
+ ```
34
+ config.active_storage.service = :memory
35
+ ```
36
+
37
+ In Active Storage's analyzer feature, asynchronous jobs are executed. So you should set the queue adapter to inline at config/environments/test.
38
+ ```
39
+ config.active_job.queue_adapter = :inline
40
+ ```
41
+
42
+ If you are conducting file downloads and uploads during system testing or integration testing, please add the following to the routing.
43
+
44
+ ``` config/routes.rb
45
+
46
+ mount ActiveStorage::Memory::Engine => "/" if Rails.env.test?
47
+
48
+ ```
26
49
 
27
- ## Development
28
50
 
29
- 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.
51
+ You can read more about Active Storage in the Active Storage Overview guide.
30
52
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. 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](https://rubygems.org).
32
53
 
33
54
  ## Contributing
34
55
 
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Activestorage::Memory
4
+ class MemoryController < ActiveStorage::BaseController
5
+ skip_forgery_protection
6
+
7
+ def show
8
+ key = decode_verified_key
9
+
10
+ unless key
11
+ head :not_found
12
+ return
13
+ end
14
+
15
+ service = named_memory_service(key[:service_name])
16
+ if service.exist?(key[:key])
17
+ send_data(service.store[key[:key]], content_type: key[:content_type], disposition: key[:disposition])
18
+ else
19
+ head :not_found
20
+ end
21
+ end
22
+
23
+ def update
24
+ token = decode_verified_token
25
+
26
+ unless token
27
+ head :not_found
28
+ return
29
+ end
30
+
31
+ unless acceptable_content?(token)
32
+ head :unprocessable_entity
33
+ return
34
+ end
35
+
36
+ named_memory_service(token[:service_name]).upload token[:key], request.body, checksum: token[:checksum]
37
+ head :no_content
38
+ end
39
+
40
+ private
41
+
42
+ def named_memory_service(name)
43
+ ActiveStorage::Blob.services.fetch(name) do
44
+ ActiveStorage::Blob.service
45
+ end
46
+ end
47
+
48
+ def decode_verified_key
49
+ ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key)&.deep_symbolize_keys
50
+ end
51
+
52
+ def decode_verified_token
53
+ ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token)&.deep_symbolize_keys
54
+ end
55
+
56
+ def acceptable_content?(token)
57
+ token[:content_type] == request.content_mime_type.to_s && token[:content_length] == request.content_length
58
+ end
59
+ end
60
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,6 @@
1
+ Activestorage::Memory::Engine.routes.draw do
2
+ scope ActiveStorage.routes_prefix do
3
+ get '/memory/:encoded_key/*filename' => 'memory#show', as: :rails_memory_service
4
+ put '/memory/:encoded_token' => 'memory#update', as: :update_rails_memory_service
5
+ end
6
+ end
@@ -11,19 +11,26 @@ module ActiveStorage
11
11
  @config = config
12
12
  end
13
13
 
14
- def upload(key, io, **)
14
+ def upload(key, io, checksum: nil, **)
15
15
  instrument(:upload, key: key) do
16
16
  store[key] = io.read
17
+ ensure_integrity_of(key, checksum) if checksum
17
18
  end
18
19
  end
19
20
 
20
- def download(key)
21
- instrument(:streaming_download, key: key) do
22
- io = StringIO.new(store.fetch(key))
23
- io.set_encoding(io.string.encoding)
24
- io
25
- rescue KeyError
26
- raise ActiveStorage::FileNotFoundError
21
+ def download(key, &block)
22
+ if block_given?
23
+ instrument(:streaming_download, key: key) do
24
+ stream key, &block
25
+ end
26
+ else
27
+ instrument(:download, key: key) do
28
+ io = StringIO.new(store.fetch(key))
29
+ io.set_encoding(io.string.encoding)
30
+ io
31
+ rescue KeyError
32
+ raise ActiveStorage::FileNotFoundError
33
+ end
27
34
  end
28
35
  end
29
36
 
@@ -43,11 +50,94 @@ module ActiveStorage
43
50
  end
44
51
  end
45
52
 
46
- def url(key)
47
- instrument(:url, key: key) do
48
- # FIXME: - this should be a URL that can be used to directly download the file
49
- "memory://#{key}"
53
+ def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:, **)
54
+ instrument :url, key: key do |payload|
55
+ verified_token_with_expiration = generate_verified_token(
56
+ key,
57
+ expires_in: expires_in,
58
+ content_type: content_type,
59
+ content_length: content_length,
60
+ checksum: checksum
61
+ )
62
+ url_helpers.update_rails_memory_service_url(
63
+ verified_token_with_expiration,
64
+ url_options
65
+ ).tap do |generated_url|
66
+ payload[:url] = generated_url
67
+ end
68
+ end
69
+ end
70
+
71
+ def headers_for_direct_upload(_key, content_type:, **)
72
+ { 'Content-Type' => content_type }
73
+ end
74
+
75
+ private
76
+
77
+ def stream(key)
78
+ io = StringIO.new(store.fetch(key))
79
+ while data = io.read(5.megabytes)
80
+ yield data
50
81
  end
82
+ rescue KeyError
83
+ raise ActiveStorage::FileNotFoundError
84
+ end
85
+
86
+ def url_helpers
87
+ @url_helpers ||= Activestorage::Memory::Engine.routes.url_helpers
88
+ end
89
+
90
+ def generate_verified_token(key, expires_in:, content_type:, content_length:, checksum:)
91
+ ActiveStorage.verifier.generate(
92
+ {
93
+ key: key,
94
+ content_type: content_type,
95
+ content_length: content_length,
96
+ checksum: checksum,
97
+ service_name: name
98
+ },
99
+ expires_in: expires_in,
100
+ purpose: :blob_token
101
+ )
102
+ end
103
+
104
+ def private_url(key, expires_in:, filename:, content_type:, disposition: :inline, **)
105
+ generate_url(key, expires_in: expires_in, filename: filename, content_type: content_type, disposition: disposition)
106
+ end
107
+
108
+ def public_url(key, filename:, content_type: nil, disposition: :attachment, **)
109
+ generate_url(key, expires_in: nil, filename: filename, content_type: content_type, disposition: disposition)
110
+ end
111
+
112
+ def generate_url(key, expires_in:, filename:, content_type:, disposition:)
113
+ content_disposition = content_disposition_with(type: disposition, filename: ActiveStorage::Filename.wrap(filename))
114
+ verified_key_with_expiration = ActiveStorage.verifier.generate(
115
+ {
116
+ key: key,
117
+ disposition: content_disposition,
118
+ content_type: content_type,
119
+ service_name: name
120
+ },
121
+ expires_in: expires_in,
122
+ purpose: :blob_key
123
+ )
124
+
125
+ if url_options.blank?
126
+ raise ArgumentError, "Cannot generate URL for #{filename} using Memory service, please set ActiveStorage::Current.url_options."
127
+ end
128
+
129
+ url_helpers.rails_memory_service_url(verified_key_with_expiration, filename: filename, **url_options)
130
+ end
131
+
132
+ def url_options
133
+ ActiveStorage::Current.url_options || Rails.application.default_url_options
134
+ end
135
+
136
+ def ensure_integrity_of(key, checksum)
137
+ return if OpenSSL::Digest.new('md5', store[key]).base64digest == checksum
138
+
139
+ delete key
140
+ raise ActiveStorage::IntegrityError
51
141
  end
52
142
  end
53
143
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Activestorage::Memory
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace Activestorage::Memory
6
+ end
7
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActiveStorage
3
+ module Activestorage
4
4
  module Memory
5
- VERSION = "0.1.0"
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "memory/version"
3
+ require_relative 'memory/version'
4
+ require_relative 'memory/engine'
4
5
 
5
6
  module Activestorage
6
7
  module Memory
@@ -0,0 +1,99 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe ActiveStorage::Service::MemoryService do
4
+ let(:service) { ActiveStorage::Service::MemoryService.new }
5
+ let(:content) { 'content' }
6
+ let(:io) { StringIO.new(content) }
7
+ let(:key) { 'key' }
8
+ let(:host) { 'example.com' }
9
+
10
+ before do
11
+ ActiveStorage::Current.url_options = { host: host }
12
+ end
13
+
14
+ describe '#upload' do
15
+ it 'stores by key' do
16
+ service.upload(key, io)
17
+ expect(service.store[key]).to eq(content)
18
+ end
19
+ end
20
+
21
+ describe '#download' do
22
+ context 'when key does not exist' do
23
+ it 'raises ActiveStorage::FileNotFoundError' do
24
+ expect { service.download(key) }.to raise_error(ActiveStorage::FileNotFoundError)
25
+ end
26
+ end
27
+
28
+ context 'when key exists' do
29
+ before do
30
+ service.upload(key, io)
31
+ end
32
+ it 'retrieves by key' do
33
+ expect(service.download(key).read).to eq(content)
34
+ end
35
+ end
36
+ end
37
+
38
+ describe '#delete' do
39
+ context 'when key does not exist' do
40
+ it 'ignores key errors' do
41
+ expect { service.delete(key) }.not_to raise_error
42
+ end
43
+ end
44
+
45
+ context 'when key exists' do
46
+ before do
47
+ service.upload(key, io)
48
+ end
49
+
50
+ it 'deletes by key' do
51
+ service.delete(key)
52
+ expect(service.store).not_to have_key(key)
53
+ end
54
+ end
55
+ end
56
+
57
+ describe '#exist?' do
58
+ context 'when key does not exist' do
59
+ it 'returns false' do
60
+ expect(service.exist?(key)).to be false
61
+ end
62
+ end
63
+
64
+ context 'when key exists' do
65
+ before do
66
+ service.upload(key, io)
67
+ end
68
+
69
+ it 'checks by key' do
70
+ expect(service.exist?(key)).to be true
71
+ end
72
+ end
73
+ end
74
+
75
+ describe '#url_for_direct_upload' do
76
+ let(:filename) { 'filename' }
77
+ let(:content_type) { 'image/jpeg' }
78
+ let(:content_length) { content.size }
79
+ let(:checksum) { OpenSSL::Digest.new('md5', content).base64digest }
80
+ let(:service_name) { 'memory' }
81
+ let(:expires_in) { 5.minutes }
82
+
83
+ before do
84
+ service.upload(key, io)
85
+ end
86
+
87
+ it 'returns a memory url' do
88
+ expect(service.url(key, expires_in: expires_in, filename: filename, content_type: content_type)).to start_with("http://#{host}/")
89
+ end
90
+ end
91
+
92
+ describe '#headers_for_direct_upload' do
93
+ let(:content_type) { 'image/jpeg' }
94
+
95
+ it 'returns content type' do
96
+ expect(service.headers_for_direct_upload(key, content_type: content_type)).to eq('Content-Type' => content_type)
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Activestorage::Memory do
4
+ it "has a version number" do
5
+ expect(Activestorage::Memory::VERSION).not_to be nil
6
+ end
7
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ RSpec.describe Activestorage::Memory::MemoryController, type: :request do
6
+ let(:key) { 'file_key' }
7
+ let(:content) { 'content' }
8
+ let(:filename) { 'file.jpg' }
9
+ let(:content_type) { 'image/jpeg' }
10
+ let(:service_name) { :memory }
11
+ let(:disposition) { "inline; filename=\"file.jpg\"; filename*=UTF-8''file.jpg" }
12
+ let(:expires_in) { ActiveStorage.service_urls_expire_in }
13
+ let(:checksum) { OpenSSL::Digest.new('md5', content).base64digest }
14
+ let!(:blob) do
15
+ ActiveStorage::Blob.create_and_upload!(
16
+ key: key,
17
+ io: StringIO.new(content),
18
+ filename: filename,
19
+ content_type: content_type,
20
+ service_name: service_name
21
+ )
22
+ end
23
+
24
+ before do
25
+ ActiveStorage::Current.url_options = { only_path: true }
26
+ end
27
+
28
+ describe "GET #show" do
29
+ let(:valid_key) do
30
+ ActiveStorage.verifier.generate(
31
+ {
32
+ key: key,
33
+ service_name: service_name,
34
+ content_type: content_type,
35
+ disposition: disposition
36
+ },
37
+ expires_in: expires_in,
38
+ purpose: :blob_key
39
+ )
40
+ end
41
+ let(:invalid_key) { 'invalid_key' }
42
+
43
+ subject { get "/rails/active_storage/memory/#{valid_key}/#{filename}" }
44
+
45
+ context "when key is valid" do
46
+ it "returns http success if key is valid and file exists" do
47
+ subject
48
+ expect(response).to have_http_status(:success)
49
+ expect(response.body).to eq(content)
50
+ end
51
+
52
+ context "when file does not exist" do
53
+ before do
54
+ ActiveStorage::Blob.services.fetch(service_name).delete(key)
55
+ end
56
+
57
+ it "returns http not_found" do
58
+ subject
59
+ expect(response).to have_http_status(:not_found)
60
+ end
61
+ end
62
+ end
63
+
64
+ context "when key is invalid" do
65
+ subject { get "/rails/active_storage/memory/#{invalid_key}/#{filename}" }
66
+
67
+ it "returns http not_found" do
68
+ subject
69
+ expect(response).to have_http_status(:not_found)
70
+ end
71
+ end
72
+ end
73
+
74
+ describe "PATCH #update" do
75
+ let(:valid_token) do
76
+ ActiveStorage.verifier.generate(
77
+ {
78
+ key: key,
79
+ service_name: service_name,
80
+ checksum: checksum,
81
+ content_type: content_type,
82
+ content_length: content.length
83
+ },
84
+ purpose: :blob_token
85
+ )
86
+ end
87
+ let(:invalid_token) { 'invalid_token' }
88
+
89
+ context "when token is valid" do
90
+ context "when content is acceptable" do
91
+ subject do
92
+ put(
93
+ "/rails/active_storage/memory/#{valid_token}",
94
+ params: content,
95
+ headers: { 'Content-Type' => content_type, 'Content-Length' => content.size }
96
+ )
97
+ end
98
+
99
+ it "returns http no_content if token is valid and content is acceptable" do
100
+ subject
101
+ expect(response).to have_http_status(:no_content)
102
+ end
103
+ end
104
+
105
+ context "when content is not acceptable" do
106
+ subject do
107
+ put(
108
+ "/rails/active_storage/memory/#{valid_token}",
109
+ params: content,
110
+ headers: { 'Content-Type' => "image/png", 'Content-Length' => content.size }
111
+ )
112
+ end
113
+
114
+ it "returns http unprocessable_entity" do
115
+ subject
116
+ expect(response).to have_http_status(:unprocessable_entity)
117
+ end
118
+ end
119
+ end
120
+
121
+ context "when token is invalid" do
122
+ subject do
123
+ put(
124
+ "/rails/active_storage/memory/#{invalid_token}",
125
+ params: content,
126
+ headers: { 'Content-Type' => content_type, 'Content-Length' => content.size }
127
+ )
128
+ end
129
+
130
+ it "returns http not_found" do
131
+ subject
132
+ expect(response).to have_http_status(:not_found)
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require_relative "config/application"
5
+
6
+ Rails.application.load_tasks
@@ -0,0 +1,3 @@
1
+ //= link_tree ../images
2
+ //= link_directory ../stylesheets .css
3
+ //= link my_engine_manifest.js
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ module ApplicationCable
2
+ class Channel < ActionCable::Channel::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module ApplicationCable
2
+ class Connection < ActionCable::Connection::Base
3
+ end
4
+ end
@@ -0,0 +1,2 @@
1
+ class ApplicationController < ActionController::Base
2
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
@@ -0,0 +1,7 @@
1
+ class ApplicationJob < ActiveJob::Base
2
+ # Automatically retry jobs that encountered a deadlock
3
+ # retry_on ActiveRecord::Deadlocked
4
+
5
+ # Most jobs are safe to ignore if the underlying records are no longer available
6
+ # discard_on ActiveJob::DeserializationError
7
+ end
@@ -0,0 +1,4 @@
1
+ class ApplicationMailer < ActionMailer::Base
2
+ default from: "from@example.com"
3
+ layout "mailer"
4
+ end
@@ -0,0 +1,3 @@
1
+ class ApplicationRecord < ActiveRecord::Base
2
+ primary_abstract_class
3
+ end
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Dummy</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <%= csrf_meta_tags %>
7
+ <%= csp_meta_tag %>
8
+
9
+ <%= stylesheet_link_tag "application" %>
10
+ </head>
11
+
12
+ <body>
13
+ <%= yield %>
14
+ </body>
15
+ </html>
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5
+ <style>
6
+ /* Email styles need to be inline */
7
+ </style>
8
+ </head>
9
+
10
+ <body>
11
+ <%= yield %>
12
+ </body>
13
+ </html>
@@ -0,0 +1 @@
1
+ <%= yield %>
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ APP_PATH = File.expand_path("../config/application", __dir__)
3
+ require_relative "../config/boot"
4
+ require "rails/commands"
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative "../config/boot"
3
+ require "rake"
4
+ Rake.application.run