tessa 2.0 → 6.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -18
  3. data/app/assets/javascripts/tessa.esm.js +212 -173
  4. data/app/assets/javascripts/tessa.js +206 -167
  5. data/app/javascript/activestorage/file_checksum.ts +76 -0
  6. data/app/javascript/tessa/index.ts +264 -0
  7. data/app/javascript/tessa/types.ts +34 -0
  8. data/config/routes.rb +0 -1
  9. data/lib/tessa/simple_form/asset_input.rb +24 -25
  10. data/lib/tessa/version.rb +1 -1
  11. data/lib/tessa.rb +0 -80
  12. data/package.json +7 -2
  13. data/rollup.config.js +5 -5
  14. data/spec/dummy/app/models/single_asset_model.rb +1 -1
  15. data/spec/dummy/app/models/single_asset_model_form.rb +3 -5
  16. data/spec/dummy/bin/rails +2 -2
  17. data/spec/dummy/bin/rake +2 -2
  18. data/spec/dummy/bin/setup +14 -6
  19. data/spec/dummy/bin/yarn +9 -3
  20. data/spec/dummy/config/application.rb +11 -9
  21. data/spec/dummy/config/boot.rb +1 -1
  22. data/spec/dummy/config/environment.rb +1 -1
  23. data/spec/dummy/config/environments/development.rb +34 -5
  24. data/spec/dummy/config/environments/production.rb +49 -10
  25. data/spec/dummy/config/environments/test.rb +28 -12
  26. data/spec/dummy/config/initializers/backtrace_silencers.rb +4 -3
  27. data/spec/dummy/config/initializers/content_security_policy.rb +5 -0
  28. data/spec/dummy/config/initializers/filter_parameter_logging.rb +3 -1
  29. data/spec/dummy/config/initializers/new_framework_defaults_6_1.rb +67 -0
  30. data/spec/dummy/config/initializers/permissions_policy.rb +11 -0
  31. data/spec/dummy/config/initializers/wrap_parameters.rb +5 -0
  32. data/spec/dummy/config/locales/en.yml +1 -1
  33. data/spec/dummy/config/routes.rb +1 -1
  34. data/spec/dummy/config/storage.yml +31 -0
  35. data/spec/dummy/config.ru +2 -1
  36. data/spec/dummy/db/migrate/20230406194400_add_service_name_to_active_storage_blobs.active_storage.rb +22 -0
  37. data/spec/dummy/db/migrate/20230406194401_create_active_storage_variant_records.active_storage.rb +27 -0
  38. data/spec/dummy/db/schema.rb +15 -7
  39. data/tessa.gemspec +4 -5
  40. data/tsconfig.json +10 -0
  41. data/yarn.lock +74 -7
  42. metadata +36 -74
  43. data/app/javascript/activestorage/file_checksum.js +0 -53
  44. data/app/javascript/tessa/index.js.coffee +0 -183
  45. data/lib/tasks/tessa.rake +0 -18
  46. data/lib/tessa/active_storage/asset_wrapper.rb +0 -32
  47. data/lib/tessa/asset/failure.rb +0 -37
  48. data/lib/tessa/asset.rb +0 -47
  49. data/lib/tessa/asset_change.rb +0 -49
  50. data/lib/tessa/asset_change_set.rb +0 -49
  51. data/lib/tessa/config.rb +0 -16
  52. data/lib/tessa/controller_helpers.rb +0 -16
  53. data/lib/tessa/fake_connection.rb +0 -29
  54. data/lib/tessa/jobs/migrate_assets_job.rb +0 -222
  55. data/lib/tessa/model/dynamic_extensions.rb +0 -145
  56. data/lib/tessa/model/field.rb +0 -77
  57. data/lib/tessa/model.rb +0 -94
  58. data/lib/tessa/rack_upload_proxy.rb +0 -41
  59. data/lib/tessa/response_factory.rb +0 -15
  60. data/lib/tessa/upload/uploads_file.rb +0 -18
  61. data/lib/tessa/upload.rb +0 -31
  62. data/lib/tessa/view_helpers.rb +0 -23
  63. data/spec/dummy/app/models/multiple_asset_model.rb +0 -8
  64. data/spec/support/remote_call_macro.rb +0 -40
  65. data/spec/tessa/asset/failure_spec.rb +0 -48
  66. data/spec/tessa/asset_change_set_spec.rb +0 -198
  67. data/spec/tessa/asset_change_spec.rb +0 -86
  68. data/spec/tessa/asset_spec.rb +0 -196
  69. data/spec/tessa/config_spec.rb +0 -70
  70. data/spec/tessa/controller_helpers_spec.rb +0 -55
  71. data/spec/tessa/jobs/migrate_assets_job_spec.rb +0 -247
  72. data/spec/tessa/model_field_spec.rb +0 -72
  73. data/spec/tessa/model_spec.rb +0 -325
  74. data/spec/tessa/rack_upload_proxy_spec.rb +0 -83
  75. data/spec/tessa/upload/uploads_file_spec.rb +0 -72
  76. data/spec/tessa/upload_spec.rb +0 -125
  77. data/spec/tessa_spec.rb +0 -23
@@ -0,0 +1,264 @@
1
+ import type Dropzone from 'dropzone'
2
+ import {FileChecksum} from '../activestorage/file_checksum'
3
+ import type { ActiveStorageDirectUploadParams, ActiveStorageDirectUploadResponse } from './types'
4
+
5
+ declare global {
6
+ interface Window {
7
+ WCC: any,
8
+ jQuery: typeof jQuery,
9
+ Dropzone: typeof Dropzone,
10
+ }
11
+ }
12
+
13
+ window.WCC ||= {}
14
+ const $ = window.jQuery
15
+
16
+ window.Dropzone.autoDiscover = false
17
+
18
+ interface WCCDropzoneFile extends Dropzone.DropzoneFile {
19
+ uploadURL: string,
20
+ uploadMethod: string,
21
+ uploadHeaders: Record<string, string> | undefined,
22
+ signedID: string,
23
+ }
24
+
25
+ interface WCCDropzoneOptions extends Dropzone.DropzoneOptions {
26
+ directUploadURL: string,
27
+ }
28
+
29
+ const BaseDropzone = window.Dropzone
30
+ class WCCDropzone extends BaseDropzone {
31
+ readonly options!: WCCDropzoneOptions
32
+
33
+ /**
34
+ * Performs a direct upload to the signed upload URL created in "accept".
35
+ * On complete, calls the Dropzone "success" callback.
36
+ */
37
+ uploadFile(file: WCCDropzoneFile): void {
38
+ const xhr = new XMLHttpRequest()
39
+ file.xhr = xhr
40
+
41
+ xhr.open(file.uploadMethod, file.uploadURL, true)
42
+ let response: any = null
43
+
44
+ const handleError = () => {
45
+ (this as any)._errorProcessing([file], response || this.options.dictResponseError?.replace("{{statusCode}}", xhr.status.toString()), xhr)
46
+ }
47
+
48
+ const updateProgress = (e?: any) => {
49
+ let progress: number
50
+ if (e) {
51
+ progress = 100 * e.loaded / e.total
52
+
53
+ file.upload = {
54
+ ...file.upload,
55
+ progress: progress,
56
+ total: e.total,
57
+ bytesSent: e.loaded
58
+ } as Dropzone.DropzoneFileUpload
59
+ } else {
60
+ // Called when the file finished uploading
61
+ progress = 100
62
+ let allFilesFinished = false
63
+ if (file.upload!.progress == 100 && file.upload!.bytesSent == file.upload!.total) {
64
+ allFilesFinished = true
65
+ }
66
+ file.upload!.progress = progress
67
+ file.upload!.bytesSent = file.upload?.total!
68
+
69
+ // Nothing to do, all files already at 100%
70
+ if (allFilesFinished) { return }
71
+ }
72
+
73
+ this.emit("uploadprogress", file, progress, file.upload!.bytesSent)
74
+ }
75
+
76
+ xhr.onload = (e) => {
77
+ if (file.status == WCCDropzone.CANCELED) { return }
78
+ if (xhr.readyState != 4) {return }
79
+
80
+ response = xhr.responseText
81
+
82
+ if (xhr.getResponseHeader("content-type") &&
83
+ xhr.getResponseHeader("content-type")!.indexOf("application/json") >= 0) {
84
+ try {
85
+ response = JSON.parse(response)
86
+ } catch(e) {
87
+ response = "Invalid JSON response from server."
88
+ }
89
+ }
90
+
91
+ updateProgress()
92
+
93
+ if (xhr.status < 200 || xhr.status >= 300)
94
+ handleError()
95
+ else {
96
+ (this as any)._finished([file], response, e)
97
+ }
98
+ }
99
+
100
+ xhr.onerror = () => {
101
+ if (file.status == WCCDropzone.CANCELED) { return }
102
+ handleError()
103
+ }
104
+
105
+ // Some browsers do not have the .upload property
106
+ let progressObj = xhr.upload ?? xhr
107
+ progressObj.onprogress = updateProgress
108
+
109
+ let headers = {
110
+ "Accept": "application/json",
111
+ "Cache-Control": "no-cache",
112
+ "X-Requested-With": "XMLHttpRequest",
113
+ }
114
+
115
+ if (this.options.headers) { Object.assign(headers, this.options.headers) }
116
+ if (file.uploadHeaders) { Object.assign(headers, file.uploadHeaders) }
117
+
118
+ for (let [headerName, headerValue] of Object.entries(headers)) {
119
+ xhr.setRequestHeader(headerName, headerValue)
120
+ }
121
+
122
+ this.emit("sending", file, xhr)
123
+
124
+ xhr.send(file)
125
+ }
126
+
127
+ uploadFiles(files: WCCDropzoneFile[]): void {
128
+ for (const file of files) {
129
+ this.uploadFile(file as WCCDropzoneFile)
130
+ }
131
+ }
132
+ }
133
+
134
+ const uploadPendingWarning = "File uploads have not yet completed. If you submit the form now they will not be saved. Are you sure you want to continue?"
135
+
136
+ /**
137
+ * Called on page load to initialize the Dropzone
138
+ */
139
+ function tessaInit() {
140
+ $('.tessa-upload').each(function(i: number, item: HTMLElement) {
141
+ const $item = $(item)
142
+
143
+ const directUploadURL = $item.data('direct-upload-url') || '/rails/active_storage/direct_uploads'
144
+
145
+ const options: WCCDropzoneOptions = {
146
+ maxFiles: 1,
147
+ addRemoveLinks: true,
148
+ url: 'UNUSED',
149
+ dictDefaultMessage: 'Drop files or click to upload.',
150
+ accept: createAcceptFn({ directUploadURL }),
151
+ ...$item.data("dropzone-options")
152
+ }
153
+
154
+ if ($item.hasClass("multiple")) { options.maxFiles = undefined }
155
+
156
+ const dropzone = new WCCDropzone(item, options)
157
+
158
+ /**
159
+ * On load, if an asset already exists, add it's thumbnail to the dropzone.
160
+ */
161
+ $item.find('input[type="hidden"]').each(function(i: number, input: HTMLElement) {
162
+ const $input = $(input)
163
+ const mockFile = $input.data("meta")
164
+ if (!mockFile) { return }
165
+
166
+ mockFile.accepted = true
167
+ dropzone.options.addedfile?.call(dropzone, mockFile)
168
+ dropzone.options.thumbnail?.call(dropzone, mockFile, mockFile.url)
169
+ dropzone.emit("complete", mockFile)
170
+ dropzone.files.push(mockFile)
171
+ })
172
+
173
+ const inputName = $item.data('input-name') || $item.find('input[type="hidden"]').attr('name');
174
+
175
+ /**
176
+ * On successful dropzone upload, create the hidden input with the signed ID.
177
+ * On the server side, ActiveStorage can then use the signed ID to create an attachment to the blob.
178
+ */
179
+ dropzone.on('success', (file: WCCDropzoneFile) => {
180
+ $(`input[name="${inputName}"]`).val(file.signedID)
181
+ })
182
+
183
+ /**
184
+ * On dropzone file removal, delete the hidden input. This removes the attachment from the record.
185
+ */
186
+ dropzone.on('removedfile', (file: WCCDropzoneFile) => {
187
+ $item.find(`input[name="${inputName}"]`).val('')
188
+ })
189
+ })
190
+
191
+ $('form:has(.tessa-upload)').each((i, form) => {
192
+ const $form = $(form)
193
+ $form.on('submit', (event: any) => {
194
+ let safeToSubmit = true
195
+ $form.find('.tessa-upload').each((j, dropzoneElement) => {
196
+ (dropzoneElement.dropzone.files as WCCDropzoneFile[]).forEach((file) => {
197
+ if (file.status && file.status != WCCDropzone.SUCCESS) {
198
+ safeToSubmit = false
199
+ }
200
+ })
201
+ })
202
+ if (!safeToSubmit && !confirm(uploadPendingWarning)) {
203
+ // prevent form submission
204
+ return false
205
+ }
206
+ })
207
+ })
208
+ }
209
+
210
+ interface AcceptOptions {
211
+ directUploadURL: string
212
+ }
213
+
214
+ /**
215
+ * Accepts a file upload from Dropzone, and creates an ActiveStorage blob via the Tessa::RackUploadProxy.
216
+ *
217
+ * Upon successfully creating the blob, retrieves a signed upload URL for direct upload. The
218
+ * signed URL is attached to the File object which is then passed to "uploadFile".
219
+ *
220
+ * @param file Binary file data uploaded by the user
221
+ * @param done Callback when file is accepted or rejected by the Tessa::RackUploadProxy
222
+ */
223
+ function createAcceptFn({ directUploadURL }: AcceptOptions) {
224
+ return function(file: WCCDropzoneFile, done: (error?: string | Error) => void) {
225
+ const postData: ActiveStorageDirectUploadParams = {
226
+ blob: {
227
+ filename: file.name,
228
+ byte_size: file.size,
229
+ content_type: file.type,
230
+ checksum: ''
231
+ }
232
+ }
233
+
234
+ FileChecksum.create(file, (error, checksum) => {
235
+ if (error) {
236
+ return done(error)
237
+ }
238
+ if (!checksum) {
239
+ return done(`Failed to generate checksum for file '${file.name}'`)
240
+ }
241
+
242
+ postData.blob['checksum'] = checksum
243
+
244
+ $.ajax(directUploadURL, {
245
+ type: 'POST',
246
+ data: postData,
247
+ success: (response: ActiveStorageDirectUploadResponse) => {
248
+ file.uploadURL = response.direct_upload.url
249
+ file.uploadMethod = 'PUT' // ActiveStorage is always PUT
250
+ file.uploadHeaders = response.direct_upload.headers
251
+ file.signedID = response.signed_id
252
+ done()
253
+ },
254
+ error: (response: any) => {
255
+ console.error(response)
256
+ done("Failed to initiate the upload process!")
257
+ }
258
+ })
259
+
260
+ })
261
+ }
262
+ }
263
+
264
+ $(tessaInit)
@@ -0,0 +1,34 @@
1
+
2
+ export interface ActiveStorageDirectUploadParams {
3
+ blob: {
4
+ filename: string,
5
+ byte_size: number,
6
+ checksum: string,
7
+ content_type: string
8
+ metadata?: Record<string, string>
9
+ }
10
+ }
11
+
12
+ type Stringable = { toString(): string }
13
+
14
+ export interface ActiveStorageDirectUploadResponse {
15
+ "id": Stringable,
16
+ "key": string,
17
+ "filename": string,
18
+ "content_type": string,
19
+ "metadata"?: {
20
+ "identified"?: boolean
21
+ },
22
+ "byte_size": number,
23
+ "checksum": string,
24
+ "created_at": string,
25
+ "service_name": string,
26
+ "signed_id": string,
27
+ "attachable_sgid": string,
28
+ "direct_upload": {
29
+ "url": string,
30
+ "headers"?: {
31
+ "Content-Type"?: string
32
+ }
33
+ }
34
+ }
data/config/routes.rb CHANGED
@@ -1,3 +1,2 @@
1
1
  Tessa::Engine.routes.draw do
2
- post '/tessa/uploads', to: Tessa::RackUploadProxy
3
2
  end
@@ -1,44 +1,43 @@
1
1
  module Tessa
2
2
  class AssetInput < SimpleForm::Inputs::Base
3
3
  def input(wrapper_options=nil)
4
- merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
5
- field = object.class.tessa_fields[attribute_name]
4
+ raise StandardError, "AssetInput with multiple: true not yet supported" if options[:multiple]
6
5
 
7
6
  template.content_tag(
8
7
  :div,
9
- hidden_fields_for(object.public_send(attribute_name)),
10
- "class" => "tessa-upload dropzone #{"multiple" if field.multiple?}",
11
- "data-asset-field-prefix" => tessa_field_prefix,
8
+ hidden_fields_for(attribute_name),
9
+ "class" => "tessa-upload dropzone #{"multiple" if options[:multiple]}",
12
10
  "data-dropzone-options" => (options[:dropzone] || {}).to_json,
13
- "data-tessa-params" => (options[:tessa_params] || {}).to_json,
11
+ "data-input-name" => "#{object_name}[#{attribute_name}]",
12
+ "data-direct-upload-url" => Rails.application.routes.url_helpers.rails_direct_uploads_path,
14
13
  )
15
14
  end
16
15
 
17
16
  private
18
17
 
19
- def tessa_field_prefix
20
- @tessa_field_prefix ||= "#{lookup_model_names.reduce { |str, item| "#{str}[#{item}]" }}[#{attribute_name}]"
21
- end
18
+ def hidden_fields_for(attribute_name)
19
+ asset = object.public_send(attribute_name)
20
+ unless asset&.key.present?
21
+ return @builder.hidden_field("#{attribute_name}", value: nil)
22
+ end
22
23
 
23
- def hidden_fields_for(assets)
24
- [*assets].collect do |asset|
25
- template.hidden_field_tag(
26
- "#{tessa_field_prefix}[#{asset.id}][action]",
27
- "add",
28
- "data-meta" => meta_for_asset(asset),
29
- "id" => "tessa_asset_action_#{asset.id}"
30
- )
31
- end.join.html_safe
24
+ @builder.hidden_field("#{attribute_name}",
25
+ value: asset.key,
26
+ data: {
27
+ # These get read by the JS to populate the preview in Dropzone
28
+ meta: meta_for_asset(asset)
29
+ })
32
30
  end
33
31
 
34
32
  def meta_for_asset(asset)
35
33
  {
36
- "assetID" => asset.id,
37
- "name" => asset.meta[:name],
38
- "size" => asset.meta[:size],
39
- "mimeType" => asset.meta[:mime_type],
40
- "url" => asset.private_url,
34
+ # this allows us to find the hidden HTML input to remove it if we remove the asset
35
+ "signedID" => asset.key,
36
+ "name" => asset.filename,
37
+ "size" => asset.byte_size,
38
+ "mimeType" => asset.content_type,
39
+ "url" => asset.service_url(disposition: :inline, expires_in: 1.hour),
41
40
  }.to_json
42
- end
41
+ end
43
42
  end
44
- end
43
+ end
data/lib/tessa/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Tessa
2
- VERSION = "2.0"
2
+ VERSION = "6.0.0.rc2"
3
3
  end
data/lib/tessa.rb CHANGED
@@ -1,90 +1,10 @@
1
1
  require "tessa/version"
2
2
 
3
- require "virtus"
4
- require "json"
5
-
6
- require "tessa/fake_connection"
7
- require "tessa/config"
8
- require "tessa/response_factory"
9
- require "tessa/asset"
10
- require "tessa/asset_change"
11
- require "tessa/asset_change_set"
12
- require "tessa/controller_helpers"
13
- require "tessa/model"
14
- require "tessa/rack_upload_proxy"
15
- require "tessa/upload"
16
- require "tessa/view_helpers"
17
-
18
- if defined?(ActiveJob)
19
- require "tessa/jobs/migrate_assets_job"
20
- end
21
-
22
3
  if defined?(SimpleForm)
23
4
  require "tessa/simple_form"
24
5
  end
25
6
 
26
7
  module Tessa
27
- class << self
28
- def config
29
- @config ||= Config.new
30
- end
31
-
32
- def setup
33
- yield config
34
- end
35
-
36
- def find_assets(ids)
37
- return find_all_assets(ids) if ids.is_a?(Array)
38
-
39
- return find_asset(ids)
40
- end
41
-
42
- def model_registry
43
- @model_registry ||= []
44
- end
45
-
46
- private
47
-
48
- def find_asset(id)
49
- return nil unless id
50
-
51
- if blob = ::ActiveStorage::Blob.find_by(key: id)
52
- return Tessa::ActiveStorage::AssetWrapper.new(blob)
53
- end
54
-
55
- Tessa::Asset.find(id)
56
- rescue Tessa::RequestFailed => err
57
- Tessa::Asset::Failure.factory(id: id, response: err.response)
58
- end
59
-
60
- def find_all_assets(ids)
61
- return [] if ids.empty?
62
-
63
- blobs = ::ActiveStorage::Blob.where(key: ids).to_a
64
- .map { |a| Tessa::ActiveStorage::AssetWrapper.new(a) }
65
- ids = ids - blobs.map(&:key)
66
- assets =
67
- begin
68
- Tessa::Asset.find(ids) if ids.any?
69
- rescue Tessa::RequestFailed => err
70
- ids.map do |id|
71
- Tessa::Asset::Failure.factory(id: id, response: err.response)
72
- end
73
- end
74
-
75
- [*blobs, *assets]
76
- end
77
- end
78
-
79
- class RequestFailed < StandardError
80
- attr_reader :response
81
-
82
- def initialize(message=nil, response=nil)
83
- super(message)
84
- @response = response
85
- end
86
- end
87
-
88
8
  end
89
9
 
90
10
  if defined?(Rails::Railtie)
data/package.json CHANGED
@@ -27,12 +27,17 @@
27
27
  "devDependencies": {
28
28
  "@rollup/plugin-commonjs": "^19.0.1",
29
29
  "@rollup/plugin-node-resolve": "^11.0.1",
30
- "coffeescript": "^2.7.0",
30
+ "@rollup/plugin-typescript": "^11.1.0",
31
+ "@types/dropzone": "^5.7.4",
32
+ "@types/jquery": "^3.5.16",
33
+ "@types/spark-md5": "^3.0.2",
31
34
  "eslint": "^4.3.0",
32
35
  "eslint-plugin-import": "^2.23.4",
33
36
  "rollup": "^2.35.1",
34
37
  "rollup-plugin-coffee-script": "^2.0.0",
35
- "rollup-plugin-terser": "^7.0.2"
38
+ "rollup-plugin-terser": "^7.0.2",
39
+ "tslib": "^2.5.0",
40
+ "typescript": "^5.0.4"
36
41
  },
37
42
  "scripts": {
38
43
  "prebuild": "yarn lint",
data/rollup.config.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import resolve from "@rollup/plugin-node-resolve"
2
2
  import commonjs from "@rollup/plugin-commonjs"
3
3
  import { terser } from "rollup-plugin-terser"
4
- import coffeescript from 'rollup-plugin-coffee-script'
4
+ import typescript from "@rollup/plugin-typescript"
5
5
 
6
6
  const terserOptions = {
7
7
  mangle: false,
@@ -14,7 +14,7 @@ const terserOptions = {
14
14
 
15
15
  export default [
16
16
  {
17
- input: "app/javascript/tessa/index.js.coffee",
17
+ input: "app/javascript/tessa/index.ts",
18
18
  output: {
19
19
  file: "app/assets/javascripts/tessa.js",
20
20
  format: "umd",
@@ -22,21 +22,21 @@ export default [
22
22
  },
23
23
  plugins: [
24
24
  resolve(),
25
- coffeescript(),
25
+ typescript(),
26
26
  commonjs(),
27
27
  terser(terserOptions)
28
28
  ]
29
29
  },
30
30
 
31
31
  {
32
- input: "app/javascript/tessa/index.js.coffee",
32
+ input: "app/javascript/tessa/index.ts",
33
33
  output: {
34
34
  file: "app/assets/javascripts/tessa.esm.js",
35
35
  format: "es"
36
36
  },
37
37
  plugins: [
38
38
  resolve(),
39
- coffeescript(),
39
+ typescript(),
40
40
  commonjs(),
41
41
  terser(terserOptions)
42
42
  ]
@@ -1,5 +1,5 @@
1
1
  class SingleAssetModel < ActiveRecord::Base
2
2
  include Tessa::Model
3
3
 
4
- asset :avatar
4
+ has_one_attached :asset
5
5
  end
@@ -5,21 +5,19 @@ class SingleAssetModelForm
5
5
 
6
6
  ATTRIBUTES = %w[
7
7
  title
8
- avatar_id
8
+ avatar
9
9
  ]
10
10
 
11
11
  attr_accessor :single_asset_model
12
12
  attr_accessor *ATTRIBUTES
13
13
 
14
- asset :avatar
15
-
16
14
  def self.from_single_asset_model(model, attrs = {})
17
15
  new(
18
16
  model.attributes
19
17
  .slice(*ATTRIBUTES)
20
18
  .merge(attrs)
21
- .merge(single_asset_model: model)
19
+ .merge(single_asset_model: model)
22
20
  )
23
21
  end
24
22
 
25
- end
23
+ end
data/spec/dummy/bin/rails CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  APP_PATH = File.expand_path('../config/application', __dir__)
3
- require_relative '../config/boot'
4
- require 'rails/commands'
3
+ require_relative "../config/boot"
4
+ require "rails/commands"
data/spec/dummy/bin/rake CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- require_relative '../config/boot'
3
- require 'rake'
2
+ require_relative "../config/boot"
3
+ require "rake"
4
4
  Rake.application.run
data/spec/dummy/bin/setup CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- require 'fileutils'
3
- include FileUtils
2
+ require "fileutils"
4
3
 
5
4
  # path to your application root.
6
5
  APP_ROOT = File.expand_path('..', __dir__)
@@ -9,16 +8,25 @@ def system!(*args)
9
8
  system(*args) || abort("\n== Command #{args} failed ==")
10
9
  end
11
10
 
12
- chdir APP_ROOT do
13
- # This script is a starting point to setup your application.
11
+ FileUtils.chdir APP_ROOT do
12
+ # This script is a way to set up or update your development environment automatically.
13
+ # This script is idempotent, so that you can run it at any time and get an expectable outcome.
14
14
  # Add necessary setup steps to this file.
15
15
 
16
16
  puts '== Installing dependencies =='
17
17
  system! 'gem install bundler --conservative'
18
18
  system('bundle check') || system!('bundle install')
19
19
 
20
- # Install JavaScript dependencies if using Yarn
21
- # system('bin/yarn')
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'
22
30
 
23
31
  puts "\n== Removing old logs and tempfiles =="
24
32
  system! 'bin/rails log:clear tmp:clear'
data/spec/dummy/bin/yarn CHANGED
@@ -1,9 +1,15 @@
1
1
  #!/usr/bin/env ruby
2
2
  APP_ROOT = File.expand_path('..', __dir__)
3
3
  Dir.chdir(APP_ROOT) do
4
- begin
5
- exec "yarnpkg", *ARGV
6
- rescue Errno::ENOENT
4
+ yarn = ENV["PATH"].split(File::PATH_SEPARATOR).
5
+ select { |dir| File.expand_path(dir) != __dir__ }.
6
+ product(["yarn", "yarn.cmd", "yarn.ps1"]).
7
+ map { |dir, file| File.expand_path(file, dir) }.
8
+ find { |file| File.executable?(file) }
9
+
10
+ if yarn
11
+ exec yarn, *ARGV
12
+ else
7
13
  $stderr.puts "Yarn executable was not detected in the system."
8
14
  $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
9
15
  exit 1
@@ -1,4 +1,4 @@
1
- require_relative 'boot'
1
+ require_relative "boot"
2
2
 
3
3
  require "rails"
4
4
  # Pick the frameworks you want:
@@ -8,10 +8,12 @@ require "active_record/railtie"
8
8
  require "active_storage/engine"
9
9
  require "action_controller/railtie"
10
10
  require "action_mailer/railtie"
11
+ require "action_mailbox/engine"
12
+ require "action_text/engine"
11
13
  require "action_view/railtie"
12
14
  # require "action_cable/engine"
13
15
  require "sprockets/railtie"
14
- # require "rails/test_unit/railtie"
16
+ require "rails/test_unit/railtie"
15
17
 
16
18
  # Require the gems listed in Gemfile, including any gems
17
19
  # you've limited to :test, :development, or :production.
@@ -22,12 +24,12 @@ module Dummy
22
24
  # Initialize configuration defaults for originally generated Rails version.
23
25
  config.load_defaults 5.2
24
26
 
25
- # Settings in config/environments/* take precedence over those specified here.
26
- # Application configuration can go into files in config/initializers
27
- # -- all .rb files in that directory are automatically loaded after loading
28
- # the framework and any gems in your application.
29
-
30
- # Don't generate system test files.
31
- config.generators.system_tests = nil
27
+ # Configuration for the application, engines, and railties goes here.
28
+ #
29
+ # These settings can be overridden in specific environments using the files
30
+ # in config/environments, which are processed later.
31
+ #
32
+ # config.time_zone = "Central Time (US & Canada)"
33
+ # config.eager_load_paths << Rails.root.join("extras")
32
34
  end
33
35
  end