tessa 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/tessa/model/dynamic_extensions.rb +31 -24
- data/lib/tessa/version.rb +1 -1
- data/lib/tessa.rb +41 -27
- data/spec/tessa/model_spec.rb +150 -9
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f9b764a91daef07d23dcc4adf14060ec1546e3f6cdff0b548b10c4caeb56598
|
4
|
+
data.tar.gz: e40a45f82d40fb0e3051dc67ffd263541cc2afec0a836912b8dfadbbd9bddf16
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7de9f6c50a229e8cd628fc09d6fcb4bdbb83b0bd7366c12b547870ce245b936026bcabc28412ffe1bcbca929300e3c0693012b439621063da3f755db1716f71d
|
7
|
+
data.tar.gz: ec6c2da67ac86e579541bdc485351032718c475077e98fa046cc5cd8959eb728c6a5da915522b73fe7031360c9eb57a0612a27ee39ec467dca619be4e3ea0188
|
@@ -44,9 +44,11 @@ class Tessa::DynamicExtensions
|
|
44
44
|
|
45
45
|
case attachable
|
46
46
|
when Tessa::AssetChangeSet
|
47
|
-
attachable.changes.each
|
48
|
-
|
49
|
-
|
47
|
+
attachable.changes.select(&:remove?).each { a.detatch }
|
48
|
+
attachable.changes.select(&:add?).each do |change|
|
49
|
+
next if #{field.id_field} == change.id
|
50
|
+
|
51
|
+
a.attach(change.id)
|
50
52
|
end
|
51
53
|
when nil
|
52
54
|
a.detach
|
@@ -72,24 +74,22 @@ class Tessa::DynamicExtensions
|
|
72
74
|
def build(mod)
|
73
75
|
mod.class_eval <<~CODE, __FILE__, __LINE__ + 1
|
74
76
|
def #{name}
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
if field = self.class.tessa_fields["#{name}".to_sym]
|
83
|
-
@#{name} ||= fetch_tessa_remote_assets(field.id(on: self))
|
84
|
-
end
|
77
|
+
field = self.class.tessa_fields["#{name}".to_sym]
|
78
|
+
tessa_ids = field.id(on: self) - #{name}_attachments.map(&:key)
|
79
|
+
|
80
|
+
@#{name} ||= [
|
81
|
+
*#{name}_attachments.map { |a| Tessa::ActiveStorage::AssetWrapper.new(a) },
|
82
|
+
*fetch_tessa_remote_assets(tessa_ids)
|
83
|
+
]
|
85
84
|
end
|
86
85
|
|
87
86
|
def #{field.id_field}
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
87
|
+
[
|
88
|
+
# Use the attachment's key
|
89
|
+
*#{name}_attachments.map(&:key),
|
90
|
+
# include from Tessa's database column
|
91
|
+
*super
|
92
|
+
]
|
93
93
|
end
|
94
94
|
|
95
95
|
def #{name}=(attachables)
|
@@ -99,18 +99,25 @@ class Tessa::DynamicExtensions
|
|
99
99
|
|
100
100
|
case attachables
|
101
101
|
when Tessa::AssetChangeSet
|
102
|
-
attachables.changes.each do |change|
|
103
|
-
a.
|
104
|
-
|
102
|
+
attachables.changes.select(&:remove?).each do |change|
|
103
|
+
if existing = #{name}_attachments.find { |a| a.key == change.id }
|
104
|
+
existing.destroy
|
105
|
+
else
|
106
|
+
ids = self.#{field.id_field}
|
107
|
+
ids.delete(change.id.to_i)
|
108
|
+
self.#{field.id_field} = ids
|
109
|
+
end
|
110
|
+
end
|
111
|
+
attachables.changes.select(&:add?).each do |change|
|
112
|
+
next if #{field.id_field}.include? change.id
|
113
|
+
|
114
|
+
a.attach(change.id)
|
105
115
|
end
|
106
116
|
when nil
|
107
117
|
a.detach
|
108
118
|
else
|
109
119
|
a.attach(*attachables)
|
110
120
|
end
|
111
|
-
|
112
|
-
# overwrite the tessa ID in the database
|
113
|
-
self.#{field.id_field} = nil
|
114
121
|
end
|
115
122
|
|
116
123
|
def attributes
|
data/lib/tessa/version.rb
CHANGED
data/lib/tessa.rb
CHANGED
@@ -16,37 +16,51 @@ require "tessa/upload"
|
|
16
16
|
require "tessa/view_helpers"
|
17
17
|
|
18
18
|
module Tessa
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
class << self
|
20
|
+
def config
|
21
|
+
@config ||= Config.new
|
22
|
+
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
def setup
|
25
|
+
yield config
|
26
|
+
end
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
else
|
32
|
-
nil
|
33
|
-
end
|
34
|
-
elsif (blobs = ::ActiveStorage::Blob.where(key: ids).to_a).present?
|
35
|
-
if ids.is_a?(Array)
|
36
|
-
blobs.map { |a| Tessa::ActiveStorage::AssetWrapper.new(a) }
|
37
|
-
else
|
38
|
-
Tessa::ActiveStorage::AssetWrapper.new(blobs.first)
|
39
|
-
end
|
40
|
-
else
|
41
|
-
Tessa::Asset.find(ids)
|
28
|
+
def find_assets(ids)
|
29
|
+
return find_all_assets(ids) if ids.is_a?(Array)
|
30
|
+
|
31
|
+
return find_asset(ids)
|
42
32
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def find_asset(id)
|
37
|
+
return nil unless id
|
38
|
+
|
39
|
+
if blob = ::ActiveStorage::Blob.find_by(key: id)
|
40
|
+
return Tessa::ActiveStorage::AssetWrapper.new(blob)
|
47
41
|
end
|
48
|
-
|
49
|
-
Tessa::Asset
|
42
|
+
|
43
|
+
Tessa::Asset.find(id)
|
44
|
+
rescue Tessa::RequestFailed => err
|
45
|
+
Tessa::Asset::Failure.factory(id: id, response: err.response)
|
46
|
+
end
|
47
|
+
|
48
|
+
def find_all_assets(ids)
|
49
|
+
return [] if ids.empty?
|
50
|
+
|
51
|
+
blobs = ::ActiveStorage::Blob.where(key: ids).to_a
|
52
|
+
.map { |a| Tessa::ActiveStorage::AssetWrapper.new(a) }
|
53
|
+
ids = ids - blobs.map(&:key)
|
54
|
+
assets =
|
55
|
+
begin
|
56
|
+
Tessa::Asset.find(ids) if ids.any?
|
57
|
+
rescue Tessa::RequestFailed => err
|
58
|
+
ids.map do |id|
|
59
|
+
Tessa::Asset::Failure.factory(id: id, response: err.response)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
[*blobs, *assets]
|
50
64
|
end
|
51
65
|
end
|
52
66
|
|
data/spec/tessa/model_spec.rb
CHANGED
@@ -41,6 +41,7 @@ RSpec.describe Tessa::Model do
|
|
41
41
|
let(:model) {
|
42
42
|
MultipleAssetModel
|
43
43
|
}
|
44
|
+
let(:instance) { model.new(another_place: []) }
|
44
45
|
|
45
46
|
it "sets all attributes on ModelField properly" do
|
46
47
|
field = model.tessa_fields[:multiple_field]
|
@@ -89,6 +90,7 @@ RSpec.describe Tessa::Model do
|
|
89
90
|
let(:model) {
|
90
91
|
MultipleAssetModel
|
91
92
|
}
|
93
|
+
let(:instance) { model.new(another_place: []) }
|
92
94
|
subject(:getter) { instance.multiple_field }
|
93
95
|
|
94
96
|
it "calls find for each of the file_ids and returns result" do
|
@@ -181,9 +183,11 @@ RSpec.describe Tessa::Model do
|
|
181
183
|
SingleAssetModel
|
182
184
|
}
|
183
185
|
subject(:getter) { instance.avatar }
|
186
|
+
let(:file) {
|
187
|
+
Rack::Test::UploadedFile.new("README.md")
|
188
|
+
}
|
184
189
|
|
185
190
|
it 'attaches uploaded file' do
|
186
|
-
file = Rack::Test::UploadedFile.new("README.md")
|
187
191
|
instance.avatar = file
|
188
192
|
|
189
193
|
expect(getter.name).to eq('avatar')
|
@@ -194,28 +198,65 @@ RSpec.describe Tessa::Model do
|
|
194
198
|
end
|
195
199
|
|
196
200
|
it 'sets the ID to be the ActiveStorage key' do
|
197
|
-
file = Rack::Test::UploadedFile.new("README.md")
|
198
201
|
instance.avatar = file
|
199
202
|
|
200
203
|
expect(instance.avatar_id).to eq(instance.avatar_attachment.key)
|
201
204
|
end
|
202
205
|
|
203
206
|
it 'sets the ID in the attributes' do
|
204
|
-
file = Rack::Test::UploadedFile.new("README.md")
|
205
207
|
instance.avatar = file
|
206
208
|
|
207
209
|
expect(instance.attributes['avatar_id']).to eq(instance.avatar_attachment.key)
|
208
210
|
end
|
211
|
+
|
212
|
+
it 'attaches signed ID from Tessa::AssetChangeSet' do
|
213
|
+
blob = ::ActiveStorage::Blob.create_before_direct_upload!({
|
214
|
+
filename: 'README.md',
|
215
|
+
byte_size: file.size,
|
216
|
+
content_type: file.content_type,
|
217
|
+
checksum: '1234'
|
218
|
+
})
|
219
|
+
|
220
|
+
changeset = Tessa::AssetChangeSet.new(
|
221
|
+
changes: [{ 'id' => blob.signed_id, 'action' => 'add' }]
|
222
|
+
)
|
223
|
+
instance.avatar = changeset
|
224
|
+
|
225
|
+
expect(instance.avatar_id).to eq(instance.avatar_attachment.key)
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'does nothing when "add"ing an existing blob' do
|
229
|
+
# Before this HTTP POST, we've previously uploaded this file
|
230
|
+
instance.avatar = file
|
231
|
+
|
232
|
+
# In this HTTP POST, we re-upload the 'add' action with the same ID
|
233
|
+
changeset = Tessa::AssetChangeSet.new(
|
234
|
+
changes: [{ 'id' => instance.avatar_attachment.key, 'action' => 'add' }]
|
235
|
+
)
|
236
|
+
|
237
|
+
# We expect that we're not going to detatch the existing attachment
|
238
|
+
expect(instance.avatar_attachment).to_not receive(:destroy)
|
239
|
+
|
240
|
+
# act
|
241
|
+
instance.avatar = changeset
|
242
|
+
|
243
|
+
expect(instance.avatar_id).to eq(instance.avatar_attachment.key)
|
244
|
+
end
|
209
245
|
end
|
210
246
|
|
211
247
|
context "with a multiple typed field" do
|
212
248
|
let(:model) {
|
213
249
|
MultipleAssetModel
|
214
250
|
}
|
251
|
+
let(:instance) { model.new(another_place: []) }
|
252
|
+
let(:file) {
|
253
|
+
Rack::Test::UploadedFile.new("README.md")
|
254
|
+
}
|
255
|
+
let(:file2) {
|
256
|
+
Rack::Test::UploadedFile.new("LICENSE.txt")
|
257
|
+
}
|
215
258
|
|
216
259
|
it 'attaches uploaded files' do
|
217
|
-
file = Rack::Test::UploadedFile.new("README.md")
|
218
|
-
file2 = Rack::Test::UploadedFile.new("LICENSE.txt")
|
219
260
|
instance.multiple_field = [file, file2]
|
220
261
|
|
221
262
|
expect(instance.multiple_field[0].name).to eq('multiple_field')
|
@@ -231,20 +272,119 @@ RSpec.describe Tessa::Model do
|
|
231
272
|
end
|
232
273
|
|
233
274
|
it 'sets the ID to be the ActiveStorage key' do
|
234
|
-
file = Rack::Test::UploadedFile.new("README.md")
|
235
|
-
file2 = Rack::Test::UploadedFile.new("LICENSE.txt")
|
236
275
|
instance.multiple_field = [file, file2]
|
237
276
|
|
238
277
|
expect(instance.another_place).to eq(instance.multiple_field_attachments.map(&:key))
|
239
278
|
end
|
240
279
|
|
241
280
|
it 'sets the ID in the attributes' do
|
242
|
-
file = Rack::Test::UploadedFile.new("README.md")
|
243
|
-
file2 = Rack::Test::UploadedFile.new("LICENSE.txt")
|
244
281
|
instance.multiple_field = [file, file2]
|
245
282
|
|
246
283
|
expect(instance.attributes['another_place']).to eq(instance.multiple_field_attachments.map(&:key))
|
247
284
|
end
|
285
|
+
|
286
|
+
it 'attaches signed ID from Tessa::AssetChangeSet' do
|
287
|
+
blob = ::ActiveStorage::Blob.create_before_direct_upload!({
|
288
|
+
filename: 'README.md',
|
289
|
+
byte_size: file.size,
|
290
|
+
content_type: file.content_type,
|
291
|
+
checksum: '1234'
|
292
|
+
})
|
293
|
+
blob2 = ::ActiveStorage::Blob.create_before_direct_upload!({
|
294
|
+
filename: "LICENSE.txt",
|
295
|
+
byte_size: file2.size,
|
296
|
+
content_type: file2.content_type,
|
297
|
+
checksum: '5678'
|
298
|
+
})
|
299
|
+
|
300
|
+
changeset = Tessa::AssetChangeSet.new(
|
301
|
+
changes: [
|
302
|
+
{ 'id' => blob.signed_id, 'action' => 'add' },
|
303
|
+
{ 'id' => blob2.signed_id, 'action' => 'add' },
|
304
|
+
]
|
305
|
+
)
|
306
|
+
instance.multiple_field = changeset
|
307
|
+
|
308
|
+
expect(instance.another_place).to eq([
|
309
|
+
blob.key,
|
310
|
+
blob2.key
|
311
|
+
])
|
312
|
+
end
|
313
|
+
|
314
|
+
it 'does nothing when "add"ing an existing blob' do
|
315
|
+
# Before this HTTP POST, we've previously uploaded these files
|
316
|
+
instance.multiple_field = [file, file2]
|
317
|
+
keys = instance.multiple_field_attachments.map(&:key)
|
318
|
+
|
319
|
+
# In this HTTP POST, we re-upload the 'add' action with the same ID
|
320
|
+
changeset = Tessa::AssetChangeSet.new(
|
321
|
+
changes: [
|
322
|
+
{ 'id' => keys[0], 'action' => 'add' },
|
323
|
+
{ 'id' => keys[1], 'action' => 'add' },
|
324
|
+
]
|
325
|
+
)
|
326
|
+
|
327
|
+
# We expect that we're not going to detatch the existing attachment
|
328
|
+
instance.multiple_field_attachments.each do |a|
|
329
|
+
expect(a).to_not receive(:destroy)
|
330
|
+
end
|
331
|
+
|
332
|
+
# act
|
333
|
+
instance.multiple_field = changeset
|
334
|
+
|
335
|
+
expect(instance.another_place).to eq(keys)
|
336
|
+
end
|
337
|
+
|
338
|
+
it 'replaces Tessa assets with ActiveStorage assets' do
|
339
|
+
# Before deploying this code, we previously had DB records with Tessa IDs
|
340
|
+
instance.update!(another_place: [1, 2, 3])
|
341
|
+
|
342
|
+
# In this HTTP POST, we removed one of the tessa assets and uploaded a
|
343
|
+
# new ActiveStorage asset
|
344
|
+
blob = ::ActiveStorage::Blob.create_before_direct_upload!({
|
345
|
+
filename: 'README.md',
|
346
|
+
byte_size: file.size,
|
347
|
+
content_type: file.content_type,
|
348
|
+
checksum: '1234'
|
349
|
+
})
|
350
|
+
changeset = Tessa::AssetChangeSet.new(
|
351
|
+
changes: [
|
352
|
+
{ 'id' => 1, 'action' => 'add' },
|
353
|
+
{ 'id' => 2, 'action' => 'remove' },
|
354
|
+
{ 'id' => 3, 'action' => 'add' },
|
355
|
+
{ 'id' => blob.signed_id, 'action' => 'add' },
|
356
|
+
]
|
357
|
+
)
|
358
|
+
|
359
|
+
# We'll download these assets when we access #multiple_field
|
360
|
+
allow(Tessa.config.connection).to receive(:get)
|
361
|
+
.with("/assets/1,3")
|
362
|
+
.and_return(double("response",
|
363
|
+
success?: true,
|
364
|
+
body: [
|
365
|
+
{ 'id' => 1, 'public_url' => 'test1' },
|
366
|
+
{ 'id' => 2, 'public_url' => 'test2' }
|
367
|
+
].to_json))
|
368
|
+
|
369
|
+
blob.upload(file)
|
370
|
+
|
371
|
+
# act
|
372
|
+
instance.multiple_field = changeset
|
373
|
+
|
374
|
+
expect(instance.another_place).to eq([
|
375
|
+
blob.key, 1, 3
|
376
|
+
])
|
377
|
+
|
378
|
+
assets = instance.multiple_field
|
379
|
+
expect(assets[0].key).to eq(blob.key)
|
380
|
+
expect(assets[0].service_url)
|
381
|
+
.to start_with('https://www.example.com/rails/active_storage/disk/')
|
382
|
+
|
383
|
+
expect(assets[1].id).to eq(1)
|
384
|
+
expect(assets[1].public_url).to eq('test1')
|
385
|
+
expect(assets[2].id).to eq(2)
|
386
|
+
expect(assets[2].public_url).to eq('test2')
|
387
|
+
end
|
248
388
|
end
|
249
389
|
end
|
250
390
|
|
@@ -385,6 +525,7 @@ RSpec.describe Tessa::Model do
|
|
385
525
|
let(:model) {
|
386
526
|
MultipleAssetModel
|
387
527
|
}
|
528
|
+
let(:instance) { model.new(another_place: []) }
|
388
529
|
|
389
530
|
before do
|
390
531
|
instance.another_place = [2, 3]
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tessa
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Powell
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2022-06-
|
12
|
+
date: 2022-06-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: faraday
|