tessa 2.0 → 6.0.0.rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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