├── .gitignore
├── Gemfile
├── Gemfile.lock
├── Procfile
├── README.md
├── Rakefile
├── app
├── controllers
│ ├── albums_controller.rb
│ ├── application_controller.rb
│ └── concerns
│ │ └── .keep
├── helpers
│ └── application_helper.rb
├── javascript
│ ├── application.css
│ ├── application.js
│ └── src
│ │ ├── application.js
│ │ ├── controllers
│ │ ├── multiple_upload_controller.js
│ │ └── single_upload_controller.js
│ │ └── uppy.js
├── jobs
│ ├── application_job.rb
│ └── attachment
│ │ ├── destroy_job.rb
│ │ └── promote_job.rb
├── mailers
│ └── application_mailer.rb
├── models
│ ├── album.rb
│ ├── application_record.rb
│ ├── concerns
│ │ └── .keep
│ └── photo.rb
├── uploaders
│ └── image_uploader.rb
└── views
│ ├── albums
│ ├── _form.html.erb
│ ├── _photo.html.erb
│ ├── index.html.erb
│ ├── new.html.erb
│ └── show.html.erb
│ └── layouts
│ ├── application.html.erb
│ ├── mailer.html.erb
│ └── mailer.text.erb
├── bin
├── bundle
├── rails
├── rake
├── setup
├── webpack
├── webpack-dev-server
└── yarn
├── config.ru
├── config
├── application.rb
├── boot.rb
├── credentials.yml.enc
├── database.yml
├── environment.rb
├── environments
│ ├── development.rb
│ ├── production.rb
│ └── test.rb
├── initializers
│ ├── application_controller_renderer.rb
│ ├── backtrace_silencers.rb
│ ├── content_security_policy.rb
│ ├── cookies_serializer.rb
│ ├── filter_parameter_logging.rb
│ ├── inflections.rb
│ ├── mime_types.rb
│ ├── shrine.rb
│ └── wrap_parameters.rb
├── locales
│ └── en.yml
├── puma.rb
├── routes.rb
├── webpack
│ ├── base.js
│ ├── development.js
│ ├── production.js
│ └── test.js
└── webpacker.yml
├── db
├── migrate
│ ├── 20180107214918_create_albums.rb
│ └── 20180107214928_create_photos.rb
├── schema.rb
└── seeds.rb
├── lib
├── assets
│ └── .keep
├── generate_thumbnail.rb
└── tasks
│ └── .keep
├── log
└── .keep
├── package.json
├── test
├── application_system_test_case.rb
├── controllers
│ └── .keep
├── fixtures
│ ├── .keep
│ └── files
│ │ └── .keep
├── helpers
│ └── .keep
├── integration
│ └── .keep
├── mailers
│ └── .keep
├── models
│ └── .keep
├── system
│ └── .keep
└── test_helper.rb
├── tmp
└── .keep
├── vendor
└── .keep
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 |
10 | # Ignore the default SQLite database.
11 | /db/*.sqlite3
12 | /db/*.sqlite3-journal
13 | /db/*.sqlite3-*
14 |
15 | # Ignore all logfiles and tempfiles.
16 | /log/*
17 | /tmp/*
18 | !/log/.keep
19 | !/tmp/.keep
20 |
21 |
22 | /public/assets
23 | .byebug_history
24 |
25 | # Ignore master key for decrypting credentials and more.
26 | /config/master.key
27 |
28 | /public/packs
29 | /public/packs-test
30 | /node_modules
31 | /yarn-error.log
32 | yarn-debug.log*
33 | .yarn-integrity
34 |
35 | /public/uploads
36 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'rails', '~> 6.1.4'
4 | gem 'sqlite3', '~> 1.4'
5 | gem 'puma', '~> 5.4'
6 | gem 'webpacker', '6.0.0.rc.5'
7 |
8 | gem 'bootsnap', '>= 1.4.2', require: false
9 |
10 | group :development do
11 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
12 | gem 'web-console', '~> 4.1'
13 | gem 'listen', '~> 3.7'
14 | end
15 |
16 | group :test do
17 | # Adds support for Capybara system testing and selenium driver
18 | gem 'capybara', '~> 3.35'
19 | gem 'selenium-webdriver'
20 | # Easy installation and use of web drivers to run system tests with browsers
21 | gem 'webdrivers'
22 | end
23 |
24 | gem 'shrine', '~> 3.3'
25 | gem 'aws-sdk-s3', '~> 1.14'
26 | gem 'image_processing', '~> 1.10'
27 | gem 'uppy-s3_multipart', '~> 1.1'
28 | gem 'marcel'
29 | gem 'fastimage'
30 |
31 | gem 'sucker_punch'
32 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | actioncable (6.1.4.1)
5 | actionpack (= 6.1.4.1)
6 | activesupport (= 6.1.4.1)
7 | nio4r (~> 2.0)
8 | websocket-driver (>= 0.6.1)
9 | actionmailbox (6.1.4.1)
10 | actionpack (= 6.1.4.1)
11 | activejob (= 6.1.4.1)
12 | activerecord (= 6.1.4.1)
13 | activestorage (= 6.1.4.1)
14 | activesupport (= 6.1.4.1)
15 | mail (>= 2.7.1)
16 | actionmailer (6.1.4.1)
17 | actionpack (= 6.1.4.1)
18 | actionview (= 6.1.4.1)
19 | activejob (= 6.1.4.1)
20 | activesupport (= 6.1.4.1)
21 | mail (~> 2.5, >= 2.5.4)
22 | rails-dom-testing (~> 2.0)
23 | actionpack (6.1.4.1)
24 | actionview (= 6.1.4.1)
25 | activesupport (= 6.1.4.1)
26 | rack (~> 2.0, >= 2.0.9)
27 | rack-test (>= 0.6.3)
28 | rails-dom-testing (~> 2.0)
29 | rails-html-sanitizer (~> 1.0, >= 1.2.0)
30 | actiontext (6.1.4.1)
31 | actionpack (= 6.1.4.1)
32 | activerecord (= 6.1.4.1)
33 | activestorage (= 6.1.4.1)
34 | activesupport (= 6.1.4.1)
35 | nokogiri (>= 1.8.5)
36 | actionview (6.1.4.1)
37 | activesupport (= 6.1.4.1)
38 | builder (~> 3.1)
39 | erubi (~> 1.4)
40 | rails-dom-testing (~> 2.0)
41 | rails-html-sanitizer (~> 1.1, >= 1.2.0)
42 | activejob (6.1.4.1)
43 | activesupport (= 6.1.4.1)
44 | globalid (>= 0.3.6)
45 | activemodel (6.1.4.1)
46 | activesupport (= 6.1.4.1)
47 | activerecord (6.1.4.1)
48 | activemodel (= 6.1.4.1)
49 | activesupport (= 6.1.4.1)
50 | activestorage (6.1.4.1)
51 | actionpack (= 6.1.4.1)
52 | activejob (= 6.1.4.1)
53 | activerecord (= 6.1.4.1)
54 | activesupport (= 6.1.4.1)
55 | marcel (~> 1.0.0)
56 | mini_mime (>= 1.1.0)
57 | activesupport (6.1.4.1)
58 | concurrent-ruby (~> 1.0, >= 1.0.2)
59 | i18n (>= 1.6, < 2)
60 | minitest (>= 5.1)
61 | tzinfo (~> 2.0)
62 | zeitwerk (~> 2.3)
63 | addressable (2.7.0)
64 | public_suffix (>= 2.0.2, < 5.0)
65 | aws-eventstream (1.2.0)
66 | aws-partitions (1.503.0)
67 | aws-sdk-core (3.121.0)
68 | aws-eventstream (~> 1, >= 1.0.2)
69 | aws-partitions (~> 1, >= 1.239.0)
70 | aws-sigv4 (~> 1.1)
71 | jmespath (~> 1.0)
72 | aws-sdk-kms (1.48.0)
73 | aws-sdk-core (~> 3, >= 3.120.0)
74 | aws-sigv4 (~> 1.1)
75 | aws-sdk-s3 (1.103.0)
76 | aws-sdk-core (~> 3, >= 3.120.0)
77 | aws-sdk-kms (~> 1)
78 | aws-sigv4 (~> 1.4)
79 | aws-sigv4 (1.4.0)
80 | aws-eventstream (~> 1, >= 1.0.2)
81 | bindex (0.8.1)
82 | bootsnap (1.4.5)
83 | msgpack (~> 1.0)
84 | builder (3.2.4)
85 | capybara (3.35.3)
86 | addressable
87 | mini_mime (>= 0.1.3)
88 | nokogiri (~> 1.8)
89 | rack (>= 1.6.0)
90 | rack-test (>= 0.6.3)
91 | regexp_parser (>= 1.5, < 3.0)
92 | xpath (~> 3.2)
93 | childprocess (3.0.0)
94 | concurrent-ruby (1.1.9)
95 | content_disposition (1.0.0)
96 | crass (1.0.6)
97 | down (5.2.0)
98 | addressable (~> 2.5)
99 | erubi (1.10.0)
100 | fastimage (2.1.7)
101 | ffi (1.15.4)
102 | globalid (0.5.2)
103 | activesupport (>= 5.0)
104 | i18n (1.8.10)
105 | concurrent-ruby (~> 1.0)
106 | image_processing (1.10.3)
107 | mini_magick (>= 4.9.5, < 5)
108 | ruby-vips (>= 2.0.17, < 3)
109 | jmespath (1.4.0)
110 | listen (3.7.0)
111 | rb-fsevent (~> 0.10, >= 0.10.3)
112 | rb-inotify (~> 0.9, >= 0.9.10)
113 | loofah (2.12.0)
114 | crass (~> 1.0.2)
115 | nokogiri (>= 1.5.9)
116 | mail (2.7.1)
117 | mini_mime (>= 0.1.1)
118 | marcel (1.0.1)
119 | method_source (1.0.0)
120 | mini_magick (4.10.1)
121 | mini_mime (1.1.1)
122 | mini_portile2 (2.6.1)
123 | minitest (5.14.4)
124 | msgpack (1.3.1)
125 | nio4r (2.5.8)
126 | nokogiri (1.12.4)
127 | mini_portile2 (~> 2.6.1)
128 | racc (~> 1.4)
129 | public_suffix (4.0.6)
130 | puma (5.4.0)
131 | nio4r (~> 2.0)
132 | racc (1.5.2)
133 | rack (2.2.3)
134 | rack-proxy (0.7.0)
135 | rack
136 | rack-test (1.1.0)
137 | rack (>= 1.0, < 3)
138 | rails (6.1.4.1)
139 | actioncable (= 6.1.4.1)
140 | actionmailbox (= 6.1.4.1)
141 | actionmailer (= 6.1.4.1)
142 | actionpack (= 6.1.4.1)
143 | actiontext (= 6.1.4.1)
144 | actionview (= 6.1.4.1)
145 | activejob (= 6.1.4.1)
146 | activemodel (= 6.1.4.1)
147 | activerecord (= 6.1.4.1)
148 | activestorage (= 6.1.4.1)
149 | activesupport (= 6.1.4.1)
150 | bundler (>= 1.15.0)
151 | railties (= 6.1.4.1)
152 | sprockets-rails (>= 2.0.0)
153 | rails-dom-testing (2.0.3)
154 | activesupport (>= 4.2.0)
155 | nokogiri (>= 1.6)
156 | rails-html-sanitizer (1.4.2)
157 | loofah (~> 2.3)
158 | railties (6.1.4.1)
159 | actionpack (= 6.1.4.1)
160 | activesupport (= 6.1.4.1)
161 | method_source
162 | rake (>= 0.13)
163 | thor (~> 1.0)
164 | rake (13.0.6)
165 | rb-fsevent (0.11.0)
166 | rb-inotify (0.10.1)
167 | ffi (~> 1.0)
168 | regexp_parser (2.1.1)
169 | roda (3.48.0)
170 | rack
171 | ruby-vips (2.0.17)
172 | ffi (~> 1.9)
173 | rubyzip (2.0.0)
174 | selenium-webdriver (3.142.7)
175 | childprocess (>= 0.5, < 4.0)
176 | rubyzip (>= 1.2.2)
177 | semantic_range (3.0.0)
178 | shrine (3.3.0)
179 | content_disposition (~> 1.0)
180 | down (~> 5.1)
181 | sprockets (4.0.2)
182 | concurrent-ruby (~> 1.0)
183 | rack (> 1, < 3)
184 | sprockets-rails (3.2.2)
185 | actionpack (>= 4.0)
186 | activesupport (>= 4.0)
187 | sprockets (>= 3.0.0)
188 | sqlite3 (1.4.2)
189 | sucker_punch (2.1.2)
190 | concurrent-ruby (~> 1.0)
191 | thor (1.1.0)
192 | tzinfo (2.0.4)
193 | concurrent-ruby (~> 1.0)
194 | uppy-s3_multipart (1.2.0)
195 | aws-sdk-s3 (~> 1.0)
196 | content_disposition (~> 1.0)
197 | roda (>= 2.27, < 4)
198 | web-console (4.1.0)
199 | actionview (>= 6.0.0)
200 | activemodel (>= 6.0.0)
201 | bindex (>= 0.4.0)
202 | railties (>= 6.0.0)
203 | webdrivers (4.2.0)
204 | nokogiri (~> 1.6)
205 | rubyzip (>= 1.3.0)
206 | selenium-webdriver (>= 3.0, < 4.0)
207 | webpacker (6.0.0.rc.5)
208 | activesupport (>= 5.2)
209 | rack-proxy (>= 0.6.1)
210 | railties (>= 5.2)
211 | semantic_range (>= 2.3.0)
212 | websocket-driver (0.7.5)
213 | websocket-extensions (>= 0.1.0)
214 | websocket-extensions (0.1.5)
215 | xpath (3.2.0)
216 | nokogiri (~> 1.8)
217 | zeitwerk (2.4.2)
218 |
219 | PLATFORMS
220 | ruby
221 |
222 | DEPENDENCIES
223 | aws-sdk-s3 (~> 1.14)
224 | bootsnap (>= 1.4.2)
225 | capybara (~> 3.35)
226 | fastimage
227 | image_processing (~> 1.10)
228 | listen (~> 3.7)
229 | marcel
230 | puma (~> 5.4)
231 | rails (~> 6.1.4)
232 | selenium-webdriver
233 | shrine (~> 3.3)
234 | sqlite3 (~> 1.4)
235 | sucker_punch
236 | uppy-s3_multipart (~> 1.1)
237 | web-console (~> 4.1)
238 | webdrivers
239 | webpacker (= 6.0.0.rc.5)
240 |
241 | BUNDLED WITH
242 | 2.3.7
243 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: rails server
2 | worker: bundle exec sidekiq
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Shrine Rails demo
2 |
3 | This is a Rails demo for [Shrine]. It allows the user to create albums and
4 | attach images. The demo shows an advanced workflow:
5 |
6 | Uploading:
7 |
8 | 1. User selects one or more files
9 | 2. The files get asynchronously uploaded directly to S3 and a progress bar is displayed
10 | 3. The cached file data gets written to the hidden fields
11 | 4. Once the form is submitted, background jobs are kicked off to process the images
12 | 5. The records are saved with cached files, which are shown as fallback
13 | 6. Once background jobs are finished, records are updated with processed attachment data
14 |
15 | Deleting:
16 |
17 | 1. User marks photos for deletion and submits
18 | 2. Deletion starts in background, and form submits instantly
19 | 3. Background job finishes deleting
20 |
21 | This asynchronicity generally provides an ideal user experience, because the
22 | user doesn't have to wait for processing or deleting, and due to fallbacks
23 | they can be unaware of background jobs.
24 |
25 | Direct uploads and backgrounding also have performance advantages, since your
26 | app doesn't have to receive file uploads (as files are uploaded directly to S3),
27 | and the web workers aren't blocked by processing, storing or deleting.
28 |
29 | ## Implementation
30 |
31 | The demo can upload files directly to S3 (default in production), or they can be
32 | uploaded to the app on stored on disk (default in development and test environment).
33 | See "Upload server modes" below for more info.
34 |
35 | The demo features both single and multiple uploads.
36 |
37 | On the client side [Uppy] is used for handling file uploads. The complete
38 | JavaScript implementation for the demo can be found in [application.js].
39 |
40 | ## Requirements
41 |
42 | To run the app you need to setup the following things:
43 |
44 | * Install ImageMagick:
45 |
46 | ```rb
47 | $ brew install imagemagick
48 | ```
49 |
50 | * Install the gems:
51 |
52 | ```rb
53 | $ bundle install
54 | ```
55 |
56 | * Have SQLite installed and run the migrations:
57 |
58 | ```sh
59 | $ rake db:migrate
60 | ```
61 |
62 | * If you'll be using Amazon S3, run `rails credentials:edit` and put your S3
63 | credentials, and [setup CORS]:
64 |
65 | ```yaml
66 | s3:
67 | access_key_id: "..."
68 | secret_access_key: "..."
69 | region: "..."
70 | bucket: "..."
71 | ```
72 |
73 | Once you have all of these things set up, you can run the app:
74 |
75 | ```sh
76 | $ rails server
77 | ```
78 |
79 | ## Upload server modes
80 |
81 | This demo app is capable of uploading files directly to S3 (using straight
82 | upload or S3 multipart upload), or of uploading to an application action and
83 | storing on local disk.
84 |
85 | In all three modes, the file selected in the browser is immediately uploaded by
86 | Javascript to some storage location ("JS direct upload"), and then on form
87 | submit a shrine-compatible hash describing the already-stored file is sent to
88 | the Rails app. Using shrine [cached_attachment_data] and [restore_cached_data]
89 | plugins. The difference is in where the Javascript sends the file, and how.
90 |
91 | You can choose which upload server mode to by setting the `UPLOAD_SERVER` env
92 | variable. Otherwise, the default is `s3` in production, and `app` in test and
93 | development.
94 |
95 | * `s3`
96 | * Shrine storages are set to S3.
97 | * The Uppy [AwsS3] plugin is used to upload files directly to the S3 `cache`
98 | storage.
99 | * The shrine [presign_endpoint] plugin is used to support Uppy AwsS3 plugin,
100 | providing authorized signed S3 URL for upload.
101 | * This is the default in Rails `production` environment.
102 | * `app`
103 | * Shrine storages are set to local filesystem, in `./public`.
104 | * The Uppy [XHRUpload] plugin is used to submit files directly to Rails app.
105 | * The Shrine [upload_endpoint] plugin is used to provide a local app HTTP
106 | action to accept the uploads.
107 | * This is the default in non-production Rails environments (development and
108 | test).
109 | * `s3_multipart`
110 | * Shrine storages are set to S3.
111 | * The Uppy [AwsS3Multipart] plugin is used to upload files directly to the S3
112 | `cache` storage, using S3's multipart upload strategy. This allows files
113 | larger than 5GB to be uploaded to S3, and can have other reliability and
114 | performance advantages from uploading in multiple smaller requests,
115 | especially for large files even if less than 5GB.
116 | * The [uppy-s3_multipart] gem, supporting the shrine `uppy_s3_multipart`
117 | plugin, are used to provide endpoints for the AwsS3Multipart Uppy plugin.
118 | * This is never the default, but you can have the app use it by setting an
119 | ENV variable.
120 |
121 | So if you would like to use the app with the S3 multipart upload server
122 | strategy, launch the rails app with:
123 |
124 | ```sh
125 | $ UPLOAD_SERVER=s3_multipart rails server
126 | ```
127 |
128 | ## Consider access control
129 |
130 | In a real apps, if you only want logged-in users to be able to upload files
131 | directly to your cache storage, you will want to limit access to the signing
132 | and/or file-receiving endpoints in routes.rb. For example, if using devise one
133 | way to do this is:
134 |
135 | ```rb
136 | authenticate :user do
137 | mount Shrine.upload_endpoint(:cache) => "/upload"
138 | end
139 | ```
140 |
141 | ## References
142 |
143 | * Shrine docs: [Direct Uploads to S3]
144 | * Shrine wiki: [Adding Direct App Uploads]
145 | * Shrine wiki: [Adding Direct S3 Uploads]
146 | * [uppy-s3_multipart] gem
147 | * Janko's Blog: [Better File Uploads with Shrine: Direct Uploads]
148 |
149 | [Shrine]: https://github.com/shrinerb/shrine
150 | [setup CORS]: http://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html
151 | [Uppy]: https://uppy.io
152 | [application.js]: /app/javascript/packs/application.js
153 | [cached_attachment_data]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/cached_attachment_data.md#readme
154 | [restore_cached_data]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/restore_cached_data.md#readme
155 | [presign_endpoint]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/presign_endpoint.md#readme
156 | [upload_endpoint]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/upload_endpoint.md#readme
157 | [XHRUpload]: https://uppy.io/docs/xhr-upload/
158 | [AwsS3]: https://uppy.io/docs/aws-s3/
159 | [AwsS3Multipart]: https://uppy.io/docs/aws-s3-multipart/
160 | [uppy-s3_multipart]: https://github.com/janko/uppy-s3_multipart
161 | [Direct Uploads to S3]: https://shrinerb.com/rdoc/files/doc/direct_s3_md.html
162 | [Adding Direct App Uploads]: https://github.com/shrinerb/shrine/wiki/Adding-Direct-App-Uploads
163 | [Adding Direct S3 Uploads]: https://github.com/shrinerb/shrine/wiki/Adding-Direct-S3-Uploads
164 | [Better File Uploads with Shrine: Direct Uploads]: https://twin.github.io/better-file-uploads-with-shrine-direct-uploads/
165 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
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
7 |
--------------------------------------------------------------------------------
/app/controllers/albums_controller.rb:
--------------------------------------------------------------------------------
1 | class AlbumsController < ApplicationController
2 | def index
3 | @albums = Album.all
4 | end
5 |
6 | def new
7 | @album = Album.new
8 | end
9 |
10 | def create
11 | @album = Album.new(album_params)
12 |
13 | if @album.valid?
14 | @album.save
15 | redirect_to @album
16 | else
17 | render :new
18 | end
19 | end
20 |
21 | def show
22 | @album = Album.find(params[:id])
23 | end
24 |
25 | def update
26 | @album = Album.find(params[:id])
27 | @album.assign_attributes(album_params)
28 |
29 | if @album.valid?
30 | @album.save
31 | redirect_to @album
32 | else
33 | render :show
34 | end
35 | end
36 |
37 | def delete
38 | @album.destroy
39 | redirect_to action: :index
40 | end
41 |
42 | private
43 |
44 | def album_params
45 | params.require(:album).permit(:name, :cover_photo, photos_attributes: {})
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | end
3 |
--------------------------------------------------------------------------------
/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erikdahlstrand/shrine-rails-example/5d45bec79c5e2d07339ae207a85b9853a7849c3b/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | def upload_server
3 | Rails.configuration.upload_server
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/javascript/application.css:
--------------------------------------------------------------------------------
1 | .container {
2 | width: 800px;
3 | }
4 |
5 | .file-upload-preview[src=""] {
6 | visibility: hidden;
7 | }
8 |
--------------------------------------------------------------------------------
/app/javascript/application.js:
--------------------------------------------------------------------------------
1 | require("@rails/ujs").start()
2 |
3 | import './src/application'
4 |
5 | import 'bootstrap/dist/css/bootstrap.css'
6 | import 'uppy/dist/uppy.min.css'
7 |
--------------------------------------------------------------------------------
/app/javascript/src/application.js:
--------------------------------------------------------------------------------
1 | import { Application } from "stimulus"
2 | import { definitionsFromContext } from "stimulus/webpack-helpers"
3 |
4 | const application = Application.start()
5 | const context = require.context("./controllers", true, /\.js$/)
6 | application.load(definitionsFromContext(context))
7 |
--------------------------------------------------------------------------------
/app/javascript/src/controllers/multiple_upload_controller.js:
--------------------------------------------------------------------------------
1 | import { Controller } from 'stimulus'
2 | import { Dashboard } from 'uppy'
3 | import { uppyInstance, uploadedFileData } from '../uppy'
4 | import { nanoid } from 'nanoid'
5 |
6 | export default class extends Controller {
7 | static targets = [ 'input' ]
8 | static values = { types: Array, server: String }
9 |
10 | connect() {
11 | this.uppy = this.createUppy()
12 | }
13 |
14 | disconnect() {
15 | this.uppy.close()
16 | }
17 |
18 | createUppy() {
19 | const uppy = uppyInstance({
20 | id: this.inputTarget.id,
21 | types: this.typesValue,
22 | server: this.serverValue,
23 | })
24 | .use(Dashboard, {
25 | target: this.inputTarget.parentNode,
26 | inline: true,
27 | height: 300,
28 | replaceTargetContent: true,
29 | })
30 |
31 | uppy.on('upload-success', (file, response) => {
32 | const hiddenField = document.createElement('input')
33 |
34 | hiddenField.type = 'hidden'
35 | hiddenField.name = `album[photos_attributes][${nanoid()}][image]`
36 | hiddenField.value = uploadedFileData(file, response, this.serverValue)
37 |
38 | this.element.appendChild(hiddenField)
39 | })
40 |
41 | return uppy
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/javascript/src/controllers/single_upload_controller.js:
--------------------------------------------------------------------------------
1 | import { Controller } from 'stimulus'
2 | import { FileInput, Informer, ProgressBar, ThumbnailGenerator } from 'uppy'
3 | import { uppyInstance, uploadedFileData } from '../uppy'
4 |
5 | export default class extends Controller {
6 | static targets = [ 'input', 'result', 'preview' ]
7 | static values = { types: Array, server: String }
8 |
9 | connect() {
10 | this.inputTarget.classList.add('d-none')
11 |
12 | this.uppy = this.createUppy()
13 | }
14 |
15 | disconnect() {
16 | this.uppy.close()
17 | }
18 |
19 | createUppy() {
20 | const uppy = uppyInstance({
21 | id: this.inputTarget.id,
22 | types: this.typesValue,
23 | server: this.serverValue,
24 | })
25 | .use(FileInput, {
26 | target: this.inputTarget.parentNode,
27 | locale: { strings: { chooseFiles: 'Choose file' } },
28 | })
29 | .use(Informer, {
30 | target: this.inputTarget.parentNode,
31 | })
32 | .use(ProgressBar, {
33 | target: this.previewTarget.parentNode,
34 | })
35 | .use(ThumbnailGenerator, {
36 | thumbnailWidth: 600,
37 | })
38 |
39 | uppy.on('upload-success', (file, response) => {
40 | // set hidden field value to the uploaded file data so that it's submitted with the form as the attachment
41 | this.resultTarget.value = uploadedFileData(file, response, this.serverValue)
42 | })
43 |
44 | uppy.on('thumbnail:generated', (file, preview) => {
45 | this.previewTarget.src = preview
46 | })
47 |
48 | return uppy
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/javascript/src/uppy.js:
--------------------------------------------------------------------------------
1 | import { Core, XHRUpload, AwsS3, AwsS3Multipart } from 'uppy'
2 |
3 | export function uppyInstance({ id, types, server }) {
4 | const uppy = new Core({
5 | id: id,
6 | autoProceed: true,
7 | restrictions: {
8 | allowedFileTypes: types,
9 | },
10 | })
11 |
12 | if (server == 's3') {
13 | uppy.use(AwsS3, {
14 | companionUrl: '/', // will call Shrine's presign endpoint mounted on `/s3/params`
15 | })
16 | } else if (server == 's3_multipart') {
17 | uppy.use(AwsS3Multipart, {
18 | companionUrl: '/' // will call uppy-s3_multipart endpoint mounted on `/s3/multipart`
19 | })
20 | } else {
21 | uppy.use(XHRUpload, {
22 | endpoint: '/upload', // Shrine's upload endpoint
23 | })
24 | }
25 |
26 | return uppy
27 | }
28 |
29 | export function uploadedFileData(file, response, server) {
30 | if (server == 's3') {
31 | const id = file.meta['key'].match(/^cache\/(.+)/)[1]; // object key without prefix
32 |
33 | return JSON.stringify(fileData(file, id))
34 | } else if (server == 's3_multipart') {
35 | const id = response.uploadURL.match(/\/cache\/([^\?]+)/)[1]; // object key without prefix
36 |
37 | return JSON.stringify(fileData(file, id))
38 | } else {
39 | return JSON.stringify(response.body)
40 | }
41 | }
42 |
43 | // constructs uploaded file data in the format that Shrine expects
44 | function fileData(file, id) {
45 | return {
46 | id: id,
47 | storage: 'cache',
48 | metadata: {
49 | size: file.size,
50 | filename: file.name,
51 | mime_type: file.type,
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
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
8 |
--------------------------------------------------------------------------------
/app/jobs/attachment/destroy_job.rb:
--------------------------------------------------------------------------------
1 | class Attachment::DestroyJob < ApplicationJob
2 | def perform(data)
3 | attacher = Shrine::Attacher.from_data(data)
4 | attacher.destroy
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/app/jobs/attachment/promote_job.rb:
--------------------------------------------------------------------------------
1 | class Attachment::PromoteJob < ApplicationJob
2 | def perform(record, name, file_data)
3 | attacher = Shrine::Attacher.retrieve(model: record, name: name, file: file_data)
4 | attacher.create_derivatives if record.is_a?(Album)
5 | attacher.atomic_promote
6 | rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
7 | # attachment has changed or the record has been deleted, nothing to do
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: 'from@example.com'
3 | layout 'mailer'
4 | end
5 |
--------------------------------------------------------------------------------
/app/models/album.rb:
--------------------------------------------------------------------------------
1 | class Album < ActiveRecord::Base
2 | has_many :photos, dependent: :destroy
3 | accepts_nested_attributes_for :photos, allow_destroy: true
4 |
5 | include ImageUploader::Attachment(:cover_photo) # ImageUploader will attach and manage `cover_photo`
6 |
7 | validates_presence_of :name, :cover_photo # Normal model validations - optional
8 | end
9 |
--------------------------------------------------------------------------------
/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erikdahlstrand/shrine-rails-example/5d45bec79c5e2d07339ae207a85b9853a7849c3b/app/models/concerns/.keep
--------------------------------------------------------------------------------
/app/models/photo.rb:
--------------------------------------------------------------------------------
1 | class Photo < ActiveRecord::Base
2 | include ImageUploader::Attachment(:image) # ImageUploader will attach and manage `image`
3 |
4 | validates_presence_of :image
5 | end
6 |
--------------------------------------------------------------------------------
/app/uploaders/image_uploader.rb:
--------------------------------------------------------------------------------
1 | # This is a subclass of Shrine base that will be further configured for it's requirements.
2 | # This will be included in the model to manage the file.
3 |
4 | class ImageUploader < Shrine
5 | ALLOWED_TYPES = %w[image/jpeg image/png image/webp]
6 | MAX_SIZE = 10*1024*1024 # 10 MB
7 | MAX_DIMENSIONS = [5000, 5000] # 5000x5000
8 |
9 | THUMBNAILS = {
10 | small: [300, 300],
11 | medium: [600, 600],
12 | large: [800, 800],
13 | }
14 |
15 | plugin :remove_attachment
16 | plugin :pretty_location
17 | plugin :validation_helpers
18 | plugin :store_dimensions, log_subscriber: nil
19 | plugin :derivation_endpoint, prefix: "derivations/image"
20 |
21 | # File validations (requires `validation_helpers` plugin)
22 | Attacher.validate do
23 | validate_size 0..MAX_SIZE
24 |
25 | if validate_mime_type ALLOWED_TYPES
26 | validate_max_dimensions MAX_DIMENSIONS
27 | end
28 | end
29 |
30 | # Thumbnails processor (requires `derivatives` plugin)
31 | Attacher.derivatives do |original|
32 | THUMBNAILS.transform_values do |(width, height)|
33 | GenerateThumbnail.call(original, width, height) # lib/generate_thumbnail.rb
34 | end
35 | end
36 |
37 | # Default to dynamic thumbnail URL (requires `default_url` plugin)
38 | Attacher.default_url do |derivative: nil, **|
39 | file&.derivation_url(:thumbnail, *THUMBNAILS.fetch(derivative)) if derivative
40 | end
41 |
42 | # Dynamic thumbnail definition (requires `derivation_endpoint` plugin)
43 | derivation :thumbnail do |file, width, height|
44 | GenerateThumbnail.call(file, width.to_i, height.to_i) # lib/generate_thumbnail.rb
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/app/views/albums/_form.html.erb:
--------------------------------------------------------------------------------
1 | <%= form_for @album do |f| %>
2 | <% if @album.errors.any? %>
3 |
4 | <% @album.errors.full_messages.each do |message| %>
5 | - Album <%= message %>
6 | <% end %>
7 |
8 | <% end %>
9 |
10 |
11 | <%= f.label :name, class: "form-label" %>
12 | <%= f.text_field :name, class: "form-control" %>
13 |
14 |
15 |
16 |
17 | <%= f.label :cover_photo, class: "form-label" %>
18 |
19 |
20 | <%= f.hidden_field :cover_photo,
21 | value: @album.cached_cover_photo_data,
22 | data: { single_upload_target: "result" },
23 | id: nil %>
24 |
25 |
26 | <%= f.file_field :cover_photo,
27 | class: "form-control",
28 | data: { single_upload_target: "input" } %>
29 |
30 |
31 |
32 |
33 | <%= image_tag @album.cover_photo_url(:medium).to_s,
34 | width: 300,
35 | class: "img-thumbnail file-upload-preview",
36 | data: { single_upload_target: "preview" } %>
37 |
38 |
39 |
40 | <% if @album.persisted? %>
41 |
42 |
43 | <%= f.label :photos, "Select photos", class: "form-label" %>
44 | <%= f.file_field :photos,
45 | multiple: true,
46 | class: "form-control",
47 | data: { multiple_upload_target: "input" } %>
48 |
49 | <% end %>
50 |
51 |
52 | <%= f.fields_for :photos do |pf| %>
53 | <%= render partial: "photo", locals: { photo: pf.object, f: pf } %>
54 | <% end %>
55 |
56 |
57 | <%= f.submit "Save", class: "btn btn-primary" %>
58 | <%= link_to albums_path, class: "btn btn-light" do %>
59 | ← Back to albums
60 | <% end %>
61 | <% end %>
62 |
--------------------------------------------------------------------------------
/app/views/albums/_photo.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= image_tag photo.image.derivation_url(:thumbnail, 300, 300).to_s, width: 150, class: "img-thumbnail" %>
5 |
6 |
7 |
8 |
9 | <%= f.label :title, class: "form-label" %>
10 | <%= f.text_field :title, class: "form-control" %>
11 |
12 |
13 | <%= f.label :_destroy, "Remove Photo", class: "form-check-label" %>
14 | <%= f.check_box :_destroy, class: "form-check-input" %>
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/views/albums/index.html.erb:
--------------------------------------------------------------------------------
1 | Albums
2 |
3 |
4 | <%= link_to "+ New Album", new_album_path, class: "btn btn-success" %>
5 |
6 |
7 |
8 |
9 |
10 | Cover |
11 | Name |
12 | Actions |
13 |
14 |
15 |
16 | <% @albums.each do |album| %>
17 |
18 | <%= image_tag album.cover_photo_url(:small), size: "100x100", class: "img-thumbnail" %> |
19 | <%= album.name %> |
20 | <%= link_to "Edit", album, class: "btn btn-default" %> |
21 |
22 | <% end %>
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/views/albums/new.html.erb:
--------------------------------------------------------------------------------
1 | New album
2 |
3 | <%= render partial: "form" %>
4 |
--------------------------------------------------------------------------------
/app/views/albums/show.html.erb:
--------------------------------------------------------------------------------
1 | <%= @album.name %>
2 |
3 | <%= render partial: "form" %>
4 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ShrineRailsExample
5 | <%= csrf_meta_tags %>
6 | <%= csp_meta_tag %>
7 |
8 | <%= stylesheet_pack_tag 'application' %>
9 | <%= javascript_pack_tag 'application' %>
10 |
11 |
12 |
13 |
14 | <%= yield %>
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'bundle' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "rubygems"
12 |
13 | m = Module.new do
14 | module_function
15 |
16 | def invoked_as_script?
17 | File.expand_path($0) == File.expand_path(__FILE__)
18 | end
19 |
20 | def env_var_version
21 | ENV["BUNDLER_VERSION"]
22 | end
23 |
24 | def cli_arg_version
25 | return unless invoked_as_script? # don't want to hijack other binstubs
26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
27 | bundler_version = nil
28 | update_index = nil
29 | ARGV.each_with_index do |a, i|
30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
31 | bundler_version = a
32 | end
33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
34 | bundler_version = $1
35 | update_index = i
36 | end
37 | bundler_version
38 | end
39 |
40 | def gemfile
41 | gemfile = ENV["BUNDLE_GEMFILE"]
42 | return gemfile if gemfile && !gemfile.empty?
43 |
44 | File.expand_path("../../Gemfile", __FILE__)
45 | end
46 |
47 | def lockfile
48 | lockfile =
49 | case File.basename(gemfile)
50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
51 | else "#{gemfile}.lock"
52 | end
53 | File.expand_path(lockfile)
54 | end
55 |
56 | def lockfile_version
57 | return unless File.file?(lockfile)
58 | lockfile_contents = File.read(lockfile)
59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
60 | Regexp.last_match(1)
61 | end
62 |
63 | def bundler_version
64 | @bundler_version ||=
65 | env_var_version || cli_arg_version ||
66 | lockfile_version
67 | end
68 |
69 | def bundler_requirement
70 | return "#{Gem::Requirement.default}.a" unless bundler_version
71 |
72 | bundler_gem_version = Gem::Version.new(bundler_version)
73 |
74 | requirement = bundler_gem_version.approximate_recommendation
75 |
76 | return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0")
77 |
78 | requirement += ".a" if bundler_gem_version.prerelease?
79 |
80 | requirement
81 | end
82 |
83 | def load_bundler!
84 | ENV["BUNDLE_GEMFILE"] ||= gemfile
85 |
86 | activate_bundler
87 | end
88 |
89 | def activate_bundler
90 | gem_error = activation_error_handling do
91 | gem "bundler", bundler_requirement
92 | end
93 | return if gem_error.nil?
94 | require_error = activation_error_handling do
95 | require "bundler/version"
96 | end
97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
99 | exit 42
100 | end
101 |
102 | def activation_error_handling
103 | yield
104 | nil
105 | rescue StandardError, LoadError => e
106 | e
107 | end
108 | end
109 |
110 | m.load_bundler!
111 |
112 | if m.invoked_as_script?
113 | load Gem.bin_path("bundler", "bundle")
114 | end
115 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path('../config/application', __dir__)
3 | require_relative '../config/boot'
4 | require 'rails/commands'
5 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative '../config/boot'
3 | require 'rake'
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'fileutils'
3 |
4 | # path to your application root.
5 | APP_ROOT = File.expand_path('..', __dir__)
6 |
7 | def system!(*args)
8 | system(*args) || abort("\n== Command #{args} failed ==")
9 | end
10 |
11 | FileUtils.chdir APP_ROOT do
12 | # This script is a way to setup or update your development environment automatically.
13 | # This script is idempotent, so that you can run it at anytime and get an expectable outcome.
14 | # Add necessary setup steps to this file.
15 |
16 | puts '== Installing dependencies =='
17 | system! 'gem install bundler --conservative'
18 | system('bundle check') || system!('bundle install')
19 |
20 | # Install JavaScript dependencies
21 | # system('bin/yarn')
22 |
23 | # puts "\n== Copying sample files =="
24 | # unless File.exist?('config/database.yml')
25 | # FileUtils.cp 'config/database.yml.sample', 'config/database.yml'
26 | # end
27 |
28 | puts "\n== Preparing database =="
29 | system! 'bin/rails db:prepare'
30 |
31 | puts "\n== Removing old logs and tempfiles =="
32 | system! 'bin/rails log:clear tmp:clear'
33 |
34 | puts "\n== Restarting application server =="
35 | system! 'bin/rails restart'
36 | end
37 |
--------------------------------------------------------------------------------
/bin/webpack:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
4 | ENV["NODE_ENV"] ||= "development"
5 |
6 | require "pathname"
7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
8 | Pathname.new(__FILE__).realpath)
9 |
10 | require "bundler/setup"
11 |
12 | require "webpacker"
13 | require "webpacker/webpack_runner"
14 |
15 | APP_ROOT = File.expand_path("..", __dir__)
16 | Dir.chdir(APP_ROOT) do
17 | Webpacker::WebpackRunner.run(ARGV)
18 | end
19 |
--------------------------------------------------------------------------------
/bin/webpack-dev-server:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
4 | ENV["NODE_ENV"] ||= "development"
5 |
6 | require "pathname"
7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
8 | Pathname.new(__FILE__).realpath)
9 |
10 | require "bundler/setup"
11 |
12 | require "webpacker"
13 | require "webpacker/dev_server_runner"
14 |
15 | APP_ROOT = File.expand_path("..", __dir__)
16 | Dir.chdir(APP_ROOT) do
17 | Webpacker::DevServerRunner.run(ARGV)
18 | end
19 |
--------------------------------------------------------------------------------
/bin/yarn:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | APP_ROOT = File.expand_path("..", __dir__)
4 | Dir.chdir(APP_ROOT) do
5 | yarn = ENV["PATH"].split(File::PATH_SEPARATOR).
6 | select { |dir| File.expand_path(dir) != __dir__ }.
7 | product(["yarn", "yarnpkg", "yarn.cmd", "yarn.ps1"]).
8 | map { |dir, file| File.expand_path(file, dir) }.
9 | find { |file| File.executable?(file) }
10 |
11 | if yarn
12 | exec yarn, *ARGV
13 | else
14 | $stderr.puts "Yarn executable was not detected in the system."
15 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
16 | exit 1
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative 'config/environment'
4 |
5 | run Rails.application
6 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative 'boot'
2 |
3 | require "rails"
4 | # Pick the frameworks you want:
5 | require "active_model/railtie"
6 | require "active_job/railtie"
7 | require "active_record/railtie"
8 | # require "active_storage/engine"
9 | require "action_controller/railtie"
10 | require "action_mailer/railtie"
11 | # require "action_mailbox/engine"
12 | # require "action_text/engine"
13 | require "action_view/railtie"
14 | # require "action_cable/engine"
15 | # require "sprockets/railtie"
16 | require "rails/test_unit/railtie"
17 |
18 | # Require the gems listed in Gemfile, including any gems
19 | # you've limited to :test, :development, or :production.
20 | Bundler.require(*Rails.groups)
21 |
22 | module ShrineRailsExample
23 | class Application < Rails::Application
24 | # Initialize configuration defaults for originally generated Rails version.
25 | config.load_defaults 6.0
26 |
27 | # Settings in config/environments/* take precedence over those specified here.
28 | # Application configuration can go into files in config/initializers
29 | # -- all .rb files in that directory are automatically loaded after loading
30 | # the framework and any gems in your application.
31 |
32 | # Use SuckerPunch for background jobs.
33 | config.active_job.queue_adapter = :sucker_punch
34 |
35 | config.autoload_paths += %w[lib]
36 |
37 | # supports :s3, :s3_multipart, or :app
38 | config.upload_server = if ENV["UPLOAD_SERVER"].present?
39 | ENV["UPLOAD_SERVER"].to_sym
40 | elsif Rails.env.production?
41 | :s3
42 | else
43 | :app
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
2 |
3 | require 'bundler/setup' # Set up gems listed in the Gemfile.
4 |
--------------------------------------------------------------------------------
/config/credentials.yml.enc:
--------------------------------------------------------------------------------
1 | AQb9EKMa7anCjm9iI8ybHqHAVABkLoB4a4n6w5+J73niYx/4lnFtJe5KQumXgLSi2xCjmGn5t6O9tvSR2r60g6VkJN2CBeEt7q6u38zdkvASgN8jeBz0vSxoyP88R0WJz4YDOp3+S/u00k8awAjIhAiTqG5yd+LhO4smQgpcmqiKWT0rqM8mUps7afMQG3B7cBVUZyUxsd0hdjNt+mcPwMruYKuxWjDtvIPt8x6kjFC5KZ/q7jJZBOJaHwS+JvXJvWG4Ma3XpqusGyI1rmk+eLzW20JiwU2QWi92Y4Gq3Fi9wLZfLAs49HwgR2ZGYWj3vpGxNg2JaKTT3Js/+XA6WKROSZDlkaALlOudTp+7RN58Pkd/ct9VfwPqj4AC5pQkmYGJngHd+7W0e17hX242qvsecC4ryLQhrPUVWPKIGq9Ggjq/+BjqaxzT6Oi2T0Bxj7ySMMs6voprT9yh7sZN5lfOdF/fZDfoleKPUzQELRwlMTvywWbsjmOSShLKEZWG014gEKWZxF29BubTiRK5LgwN0hmBe4si4A==--ahVbBVUg+KLYOJN/--YL2Id1AGCIBrLWax6aQSlA==
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite. Versions 3.8.0 and up are supported.
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: db/development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: db/test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: db/production.sqlite3
26 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative 'application'
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports.
13 | config.consider_all_requests_local = true
14 |
15 | # Enable/disable caching. By default caching is disabled.
16 | # Run rails dev:cache to toggle caching.
17 | if Rails.root.join('tmp', 'caching-dev.txt').exist?
18 | config.action_controller.perform_caching = true
19 | config.action_controller.enable_fragment_cache_logging = true
20 |
21 | config.cache_store = :memory_store
22 | config.public_file_server.headers = {
23 | 'Cache-Control' => "public, max-age=#{2.days.to_i}"
24 | }
25 | else
26 | config.action_controller.perform_caching = false
27 |
28 | config.cache_store = :null_store
29 | end
30 |
31 | # Don't care if the mailer can't send.
32 | config.action_mailer.raise_delivery_errors = false
33 |
34 | config.action_mailer.perform_caching = false
35 |
36 | # Print deprecation notices to the Rails logger.
37 | config.active_support.deprecation = :log
38 |
39 | # Raise an error on page load if there are pending migrations.
40 | config.active_record.migration_error = :page_load
41 |
42 | # Highlight code that triggered database queries in logs.
43 | config.active_record.verbose_query_logs = true
44 |
45 | # Raises error for missing translations.
46 | # config.action_view.raise_on_missing_translations = true
47 |
48 | # Use an evented file watcher to asynchronously detect changes in source code,
49 | # routes, locales, etc. This feature depends on the listen gem.
50 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
51 | end
52 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
18 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
19 | # config.require_master_key = true
20 |
21 | # Disable serving static files from the `/public` folder by default since
22 | # Apache or NGINX already handles this.
23 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
24 |
25 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
26 | # config.action_controller.asset_host = 'http://assets.example.com'
27 |
28 | # Specifies the header that your server uses for sending files.
29 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
30 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
31 |
32 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
33 | # config.force_ssl = true
34 |
35 | # Use the lowest log level to ensure availability of diagnostic information
36 | # when problems arise.
37 | config.log_level = :debug
38 |
39 | # Prepend all log lines with the following tags.
40 | config.log_tags = [ :request_id ]
41 |
42 | # Use a different cache store in production.
43 | # config.cache_store = :mem_cache_store
44 |
45 | # Use a real queuing backend for Active Job (and separate queues per environment).
46 | # config.active_job.queue_adapter = :resque
47 | # config.active_job.queue_name_prefix = "shrine_rails_example_production"
48 |
49 | config.action_mailer.perform_caching = false
50 |
51 | # Ignore bad email addresses and do not raise email delivery errors.
52 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
53 | # config.action_mailer.raise_delivery_errors = false
54 |
55 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
56 | # the I18n.default_locale when a translation cannot be found).
57 | config.i18n.fallbacks = true
58 |
59 | # Send deprecation notices to registered listeners.
60 | config.active_support.deprecation = :notify
61 |
62 | # Use default logging formatter so that PID and timestamp are not suppressed.
63 | config.log_formatter = ::Logger::Formatter.new
64 |
65 | # Use a different logger for distributed setups.
66 | # require 'syslog/logger'
67 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
68 |
69 | if ENV["RAILS_LOG_TO_STDOUT"].present?
70 | logger = ActiveSupport::Logger.new(STDOUT)
71 | logger.formatter = config.log_formatter
72 | config.logger = ActiveSupport::TaggedLogging.new(logger)
73 | end
74 |
75 | # Do not dump schema after migrations.
76 | config.active_record.dump_schema_after_migration = false
77 |
78 | # Inserts middleware to perform automatic connection switching.
79 | # The `database_selector` hash is used to pass options to the DatabaseSelector
80 | # middleware. The `delay` is used to determine how long to wait after a write
81 | # to send a subsequent read to the primary.
82 | #
83 | # The `database_resolver` class is used by the middleware to determine which
84 | # database is appropriate to use based on the time delay.
85 | #
86 | # The `database_resolver_context` class is used by the middleware to set
87 | # timestamps for the last write to the primary. The resolver uses the context
88 | # class timestamps to determine how long to wait before reading from the
89 | # replica.
90 | #
91 | # By default Rails will store a last write timestamp in the session. The
92 | # DatabaseSelector middleware is designed as such you can define your own
93 | # strategy for connection switching and pass that into the middleware through
94 | # these configuration options.
95 | # config.active_record.database_selector = { delay: 2.seconds }
96 | # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
97 | # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
98 | end
99 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | # The test environment is used exclusively to run your application's
2 | # test suite. You never need to work with it otherwise. Remember that
3 | # your test database is "scratch space" for the test suite and is wiped
4 | # and recreated between test runs. Don't rely on the data there!
5 |
6 | Rails.application.configure do
7 | # Settings specified here will take precedence over those in config/application.rb.
8 |
9 | config.cache_classes = true
10 |
11 | # Do not eager load code on boot. This avoids loading your whole application
12 | # just for the purpose of running a single test. If you are using a tool that
13 | # preloads Rails for running tests, you may have to set it to true.
14 | config.eager_load = false
15 |
16 | # Configure public file server for tests with Cache-Control for performance.
17 | config.public_file_server.enabled = true
18 | config.public_file_server.headers = {
19 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}"
20 | }
21 |
22 | # Show full error reports and disable caching.
23 | config.consider_all_requests_local = true
24 | config.action_controller.perform_caching = false
25 | config.cache_store = :null_store
26 |
27 | # Raise exceptions instead of rendering exception templates.
28 | config.action_dispatch.show_exceptions = false
29 |
30 | # Disable request forgery protection in test environment.
31 | config.action_controller.allow_forgery_protection = false
32 |
33 | config.action_mailer.perform_caching = false
34 |
35 | # Tell Action Mailer not to deliver emails to the real world.
36 | # The :test delivery method accumulates sent emails in the
37 | # ActionMailer::Base.deliveries array.
38 | config.action_mailer.delivery_method = :test
39 |
40 | # Print deprecation notices to the stderr.
41 | config.active_support.deprecation = :stderr
42 |
43 | # Raises error for missing translations.
44 | # config.action_view.raise_on_missing_translations = true
45 | end
46 |
--------------------------------------------------------------------------------
/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ActiveSupport::Reloader.to_prepare do
4 | # ApplicationController.renderer.defaults.merge!(
5 | # http_host: 'example.org',
6 | # https: false
7 | # )
8 | # end
9 |
--------------------------------------------------------------------------------
/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/config/initializers/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Define an application-wide content security policy
4 | # For further information see the following documentation
5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
6 |
7 | # Rails.application.config.content_security_policy do |policy|
8 | # policy.default_src :self, :https
9 | # policy.font_src :self, :https, :data
10 | # policy.img_src :self, :https, :data
11 | # policy.object_src :none
12 | # policy.script_src :self, :https
13 | # policy.style_src :self, :https
14 | # # If you are using webpack-dev-server then specify webpack-dev-server host
15 | # policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development?
16 |
17 | # # Specify URI for violation reports
18 | # # policy.report_uri "/csp-violation-report-endpoint"
19 | # end
20 |
21 | # If you are using UJS then enable automatic nonce generation
22 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
23 |
24 | # Set the nonce only to specific directives
25 | # Rails.application.config.content_security_policy_nonce_directives = %w(script-src)
26 |
27 | # Report CSP violations to a specified URI
28 | # For further information see the following documentation:
29 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
30 | # Rails.application.config.content_security_policy_report_only = true
31 |
--------------------------------------------------------------------------------
/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Specify a serializer for the signed and encrypted cookie jars.
4 | # Valid options are :json, :marshal, and :hybrid.
5 | Rails.application.config.action_dispatch.cookies_serializer = :json
6 |
--------------------------------------------------------------------------------
/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/config/initializers/shrine.rb:
--------------------------------------------------------------------------------
1 | require "shrine"
2 |
3 | # By default use S3 for production and local file for other environments
4 | case Rails.configuration.upload_server
5 | when :s3, :s3_multipart
6 | require "shrine/storage/s3"
7 |
8 | s3_options = Rails.application.credentials.s3
9 |
10 | # both `cache` and `store` storages are needed
11 | Shrine.storages = {
12 | cache: Shrine::Storage::S3.new(prefix: "cache", **s3_options),
13 | store: Shrine::Storage::S3.new(**s3_options),
14 | }
15 | when :app
16 | require "shrine/storage/file_system"
17 |
18 | # both `cache` and `store` storages are needed
19 | Shrine.storages = {
20 | cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"),
21 | store: Shrine::Storage::FileSystem.new("public", prefix: "uploads"),
22 | }
23 | end
24 |
25 | Shrine.plugin :activerecord
26 | Shrine.plugin :instrumentation
27 | Shrine.plugin :determine_mime_type, analyzer: :marcel, log_subscriber: nil
28 | Shrine.plugin :cached_attachment_data
29 | Shrine.plugin :restore_cached_data
30 | Shrine.plugin :derivatives # up front processing
31 | Shrine.plugin :derivation_endpoint, # on-the-fly processing
32 | secret_key: Rails.application.secret_key_base
33 |
34 | case Rails.configuration.upload_server
35 | when :s3
36 | Shrine.plugin :presign_endpoint, presign_options: -> (request) {
37 | # Uppy will send the "filename" and "type" query parameters
38 | filename = request.params["filename"]
39 | type = request.params["type"]
40 |
41 | {
42 | content_disposition: ContentDisposition.inline(filename), # set download filename
43 | content_type: type, # set content type
44 | content_length_range: 0..(10*1024*1024), # limit upload size to 10 MB
45 | }
46 | }
47 | when :s3_multipart
48 | Shrine.plugin :uppy_s3_multipart
49 | when :app
50 | Shrine.plugin :upload_endpoint
51 | end
52 |
53 | # delay promoting and deleting files to a background job (`backgrounding` plugin)
54 | Shrine.plugin :backgrounding
55 | Shrine::Attacher.promote_block { Attachment::PromoteJob.perform_later(record, name.to_s, file_data) }
56 | Shrine::Attacher.destroy_block { Attachment::DestroyJob.perform_later(data) }
57 |
--------------------------------------------------------------------------------
/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # 'true': 'foo'
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at https://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers: a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum; this matches the default thread size of Active Record.
6 | #
7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
9 | threads min_threads_count, max_threads_count
10 |
11 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
12 | #
13 | port ENV.fetch("PORT") { 3000 }
14 |
15 | # Specifies the `environment` that Puma will run in.
16 | #
17 | environment ENV.fetch("RAILS_ENV") { "development" }
18 |
19 | # Specifies the `pidfile` that Puma will use.
20 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
21 |
22 | # Specifies the number of `workers` to boot in clustered mode.
23 | # Workers are forked web server processes. If using threads and workers together
24 | # the concurrency of the application would be max `threads` * `workers`.
25 | # Workers do not work on JRuby or Windows (both of which do not support
26 | # processes).
27 | #
28 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
29 |
30 | # Use the `preload_app!` method when specifying a `workers` number.
31 | # This directive tells Puma to first boot the application and load code
32 | # before forking the application. This takes advantage of Copy On Write
33 | # process behavior so workers use less memory.
34 | #
35 | # preload_app!
36 |
37 | # Allow puma to be restarted by `rails restart` command.
38 | plugin :tmp_restart
39 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | root to: "albums#index"
3 |
4 | resources :albums
5 |
6 | case Rails.configuration.upload_server
7 | when :s3
8 | # By default in production we use s3, including upload directly to S3 with
9 | # signed url.
10 | mount Shrine.presign_endpoint(:cache) => "/s3/params"
11 | when :s3_multipart
12 | # Still upload directly to S3, but using Uppy's AwsS3Multipart plugin
13 | mount Shrine.uppy_s3_multipart(:cache) => "/s3/multipart"
14 | when :app
15 | # In development and test environment by default we're using filesystem storage
16 | # for speed, so on the client side we'll upload files to our app.
17 | mount Shrine.upload_endpoint(:cache) => "/upload"
18 | end
19 |
20 | mount ImageUploader.derivation_endpoint => "/derivations/image"
21 | end
22 |
--------------------------------------------------------------------------------
/config/webpack/base.js:
--------------------------------------------------------------------------------
1 | const { webpackConfig, merge } = require('@rails/webpacker')
2 |
3 | const customConfig = {
4 | resolve: {
5 | extensions: ['.css']
6 | }
7 | }
8 |
9 | module.exports = merge(webpackConfig, customConfig)
10 |
--------------------------------------------------------------------------------
/config/webpack/development.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'
2 |
3 | const webpackConfig = require('./base')
4 |
5 | module.exports = webpackConfig
6 |
--------------------------------------------------------------------------------
/config/webpack/production.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = process.env.NODE_ENV || 'production'
2 |
3 | const webpackConfig = require('./base')
4 |
5 | module.exports = webpackConfig
6 |
--------------------------------------------------------------------------------
/config/webpack/test.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'
2 |
3 | const webpackConfig = require('./base')
4 |
5 | module.exports = webpackConfig
6 |
--------------------------------------------------------------------------------
/config/webpacker.yml:
--------------------------------------------------------------------------------
1 | # Note: You must restart bin/webpack-dev-server for changes to take effect
2 |
3 | default: &default
4 | source_path: app/javascript
5 | source_entry_path: /
6 | public_root_path: public
7 | public_output_path: packs
8 | cache_path: tmp/webpacker
9 | webpack_compile_output: true
10 |
11 | # Additional paths webpack should look up modules
12 | # ['app/assets', 'engine/foo/app/assets']
13 | additional_paths: []
14 |
15 | # Reload manifest.json on all requests so we reload latest compiled packs
16 | cache_manifest: false
17 |
18 | development:
19 | <<: *default
20 | compile: true
21 |
22 | # Reference: https://webpack.js.org/configuration/dev-server/
23 | dev_server:
24 | https: false
25 | host: localhost
26 | port: 3035
27 | # Hot Module Replacement updates modules while the application is running without a full reload
28 | hmr: false
29 | client:
30 | # Should we show a full-screen overlay in the browser when there are compiler errors or warnings?
31 | overlay: true
32 | # May also be a string
33 | # webSocketURL:
34 | # hostname: "0.0.0.0"
35 | # pathname: "/ws"
36 | # port: 8080
37 | # Should we use gzip compression?
38 | compress: true
39 | # Note that apps that do not check the host are vulnerable to DNS rebinding attacks
40 | allowed_hosts: "all"
41 | pretty: true
42 | headers:
43 | 'Access-Control-Allow-Origin': '*'
44 | static:
45 | watch:
46 | ignored: '**/node_modules/**'
47 |
48 | test:
49 | <<: *default
50 | compile: true
51 |
52 | # Compile test packs to a separate directory
53 | public_output_path: packs-test
54 |
55 | production:
56 | <<: *default
57 |
58 | # Production depends on precompilation of packs prior to booting for performance.
59 | compile: false
60 |
61 | # Cache manifest.json for performance
62 | cache_manifest: true
63 |
--------------------------------------------------------------------------------
/db/migrate/20180107214918_create_albums.rb:
--------------------------------------------------------------------------------
1 | class CreateAlbums < ActiveRecord::Migration[5.1]
2 | def change
3 | create_table :albums do |t|
4 | t.string :name
5 | t.text :cover_photo_data
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/db/migrate/20180107214928_create_photos.rb:
--------------------------------------------------------------------------------
1 | class CreatePhotos < ActiveRecord::Migration[5.1]
2 | def change
3 | create_table :photos do |t|
4 | t.references(:album, foreign_key: true)
5 | t.string :title
6 | t.text :image_data
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # This file is the source Rails uses to define your schema when running `bin/rails
6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7 | # be faster and is potentially less error prone than running all of your
8 | # migrations from scratch. Old migrations may fail to apply correctly if those
9 | # migrations use external dependencies or application code.
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema.define(version: 2018_01_07_214928) do
14 |
15 | create_table "albums", force: :cascade do |t|
16 | t.string "name"
17 | t.text "cover_photo_data"
18 | end
19 |
20 | create_table "photos", force: :cascade do |t|
21 | t.integer "album_id"
22 | t.string "title"
23 | t.text "image_data"
24 | t.index ["album_id"], name: "index_photos_on_album_id"
25 | end
26 |
27 | add_foreign_key "photos", "albums"
28 | end
29 |
--------------------------------------------------------------------------------
/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
7 | # Character.create(name: 'Luke', movie: movies.first)
8 |
--------------------------------------------------------------------------------
/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erikdahlstrand/shrine-rails-example/5d45bec79c5e2d07339ae207a85b9853a7849c3b/lib/assets/.keep
--------------------------------------------------------------------------------
/lib/generate_thumbnail.rb:
--------------------------------------------------------------------------------
1 | require "image_processing/mini_magick"
2 |
3 | class GenerateThumbnail
4 | def self.call(file, width, height)
5 | magick = ImageProcessing::MiniMagick.source(file)
6 | magick.resize_to_limit!(width, height)
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erikdahlstrand/shrine-rails-example/5d45bec79c5e2d07339ae207a85b9853a7849c3b/lib/tasks/.keep
--------------------------------------------------------------------------------
/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erikdahlstrand/shrine-rails-example/5d45bec79c5e2d07339ae207a85b9853a7849c3b/log/.keep
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shrine_rails_example",
3 | "private": true,
4 | "dependencies": {
5 | "@popperjs/core": "^2.10.1",
6 | "@rails/ujs": "^6.0.0",
7 | "@rails/webpacker": "^6.0.0-rc.5",
8 | "bootstrap": "^5.1.1",
9 | "css-loader": "^6.3.0",
10 | "css-minimizer-webpack-plugin": "^3.0.2",
11 | "mini-css-extract-plugin": "^2.3.0",
12 | "nanoid": "^3.1.25",
13 | "stimulus": "^2.0.0",
14 | "style-loader": "^3.2.1",
15 | "uppy": "^2.1.0",
16 | "webpack": "^5.51.1",
17 | "webpack-cli": "^4.8.0"
18 | },
19 | "version": "0.1.0",
20 | "devDependencies": {
21 | "@webpack-cli/serve": "^1.5.2",
22 | "webpack-dev-server": "^4.2.1"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/test/application_system_test_case.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
5 | end
6 |
--------------------------------------------------------------------------------
/test/controllers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erikdahlstrand/shrine-rails-example/5d45bec79c5e2d07339ae207a85b9853a7849c3b/test/controllers/.keep
--------------------------------------------------------------------------------
/test/fixtures/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erikdahlstrand/shrine-rails-example/5d45bec79c5e2d07339ae207a85b9853a7849c3b/test/fixtures/.keep
--------------------------------------------------------------------------------
/test/fixtures/files/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erikdahlstrand/shrine-rails-example/5d45bec79c5e2d07339ae207a85b9853a7849c3b/test/fixtures/files/.keep
--------------------------------------------------------------------------------
/test/helpers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erikdahlstrand/shrine-rails-example/5d45bec79c5e2d07339ae207a85b9853a7849c3b/test/helpers/.keep
--------------------------------------------------------------------------------
/test/integration/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erikdahlstrand/shrine-rails-example/5d45bec79c5e2d07339ae207a85b9853a7849c3b/test/integration/.keep
--------------------------------------------------------------------------------
/test/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erikdahlstrand/shrine-rails-example/5d45bec79c5e2d07339ae207a85b9853a7849c3b/test/mailers/.keep
--------------------------------------------------------------------------------
/test/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erikdahlstrand/shrine-rails-example/5d45bec79c5e2d07339ae207a85b9853a7849c3b/test/models/.keep
--------------------------------------------------------------------------------
/test/system/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erikdahlstrand/shrine-rails-example/5d45bec79c5e2d07339ae207a85b9853a7849c3b/test/system/.keep
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] ||= 'test'
2 | require_relative '../config/environment'
3 | require 'rails/test_help'
4 |
5 | class ActiveSupport::TestCase
6 | # Run tests in parallel with specified workers
7 | parallelize(workers: :number_of_processors)
8 |
9 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
10 | fixtures :all
11 |
12 | # Add more helper methods to be used by all tests here...
13 | end
14 |
--------------------------------------------------------------------------------
/tmp/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erikdahlstrand/shrine-rails-example/5d45bec79c5e2d07339ae207a85b9853a7849c3b/tmp/.keep
--------------------------------------------------------------------------------
/vendor/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erikdahlstrand/shrine-rails-example/5d45bec79c5e2d07339ae207a85b9853a7849c3b/vendor/.keep
--------------------------------------------------------------------------------