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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 153d1d11991ae3282b26a80ef33694cb8aae8773fe0ceddc5176243beb137581
4
- data.tar.gz: 6c98f75781366eca6d43ba73789fa85e8470f0f2b47971392ee0d8afe61bab18
3
+ metadata.gz: 9f9b764a91daef07d23dcc4adf14060ec1546e3f6cdff0b548b10c4caeb56598
4
+ data.tar.gz: e40a45f82d40fb0e3051dc67ffd263541cc2afec0a836912b8dfadbbd9bddf16
5
5
  SHA512:
6
- metadata.gz: 7aac9e710299385ed4fc9fa6845b9a0974046817149994d8ca15f1c746501a1b9573e9c3455f8707806df69f519d04b5b6f4ac185c70af4f802eec5416bb87f2
7
- data.tar.gz: 938d746824307da74813f937184fee8fb32f2f286e01301a695b5a2871410d2a7bfc1e796e1e2365aa0144898a16d73364b4bb58b78eac5b0526746ef1e4d262
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 do |change|
48
- a.attach(change.id) if change.add?
49
- a.detach if change.remove?
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
- if #{name}_attachments.present?
76
- return #{name}_attachments.map do |a|
77
- Tessa::ActiveStorage::AssetWrapper.new(a)
78
- end
79
- end
80
-
81
- # fall back to old Tessa fetch if not present
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
- # Use the attachment's key
89
- return #{name}_attachments.map(&:key) if #{name}_attachments.present?
90
-
91
- # fallback to Tessa's database column
92
- super
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.attach(change.id) if change.add?
104
- raise 'TODO' if change.remove?
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
@@ -1,3 +1,3 @@
1
1
  module Tessa
2
- VERSION = "1.0.1"
2
+ VERSION = "1.0.2"
3
3
  end
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
- def self.config
20
- @config ||= Config.new
21
- end
19
+ class << self
20
+ def config
21
+ @config ||= Config.new
22
+ end
22
23
 
23
- def self.setup
24
- yield config
25
- end
24
+ def setup
25
+ yield config
26
+ end
26
27
 
27
- def self.find_assets(ids)
28
- if [*ids].empty?
29
- if ids.is_a?(Array)
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
- rescue Tessa::RequestFailed => err
44
- if ids.is_a?(Array)
45
- ids.map do |id|
46
- Tessa::Asset::Failure.factory(id: id, response: err.response)
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
- else
49
- Tessa::Asset::Failure.factory(id: ids, response: err.response)
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
 
@@ -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.1
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-15 00:00:00.000000000 Z
12
+ date: 2022-06-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: faraday