tessa 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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