tessa 1.0.0.pre.rc3 → 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: 17688b084097b7f79807a5a55ca6a7d26bbc0b08e242876afe101d3cb5073698
4
- data.tar.gz: 739705feff83e9b10c30f119b29bb455f9d8b67263447997fc30255d54b5c4ed
3
+ metadata.gz: 9f9b764a91daef07d23dcc4adf14060ec1546e3f6cdff0b548b10c4caeb56598
4
+ data.tar.gz: e40a45f82d40fb0e3051dc67ffd263541cc2afec0a836912b8dfadbbd9bddf16
5
5
  SHA512:
6
- metadata.gz: 113b0ff0fef87cebead7f296902273448a1333d7abfc8d93a2ba308944df19f169b72579a076a378350435037d7af9001680e83fc27a32cc07db0a4dfbd754cf
7
- data.tar.gz: 7281dbe6f2b6a571d34c3db137fcb593dd64c586b92034333ff3908a5ce0856444b4ee459b40d29660a8d7834592027ac58b57e51036804757150f7a48df81b2
6
+ metadata.gz: 7de9f6c50a229e8cd628fc09d6fcb4bdbb83b0bd7366c12b547870ce245b936026bcabc28412ffe1bcbca929300e3c0693012b439621063da3f755db1716f71d
7
+ data.tar.gz: ec6c2da67ac86e579541bdc485351032718c475077e98fa046cc5cd8959eb728c6a5da915522b73fe7031360c9eb57a0612a27ee39ec467dca619be4e3ea0188
@@ -1,5 +1,9 @@
1
1
  module Tessa::ActiveStorage
2
2
  class AssetWrapper < SimpleDelegator
3
+ def id
4
+ key
5
+ end
6
+
3
7
  def public_url
4
8
  Rails.application.routes.url_helpers.
5
9
  rails_blob_url(__getobj__, disposition: :inline)
@@ -14,7 +18,10 @@ module Tessa::ActiveStorage
14
18
  end
15
19
 
16
20
  def meta
17
- {}
21
+ {
22
+ mime_type: content_type,
23
+ size: byte_size
24
+ }
18
25
  end
19
26
 
20
27
  def failure?
data/lib/tessa/config.rb CHANGED
@@ -11,7 +11,11 @@ module Tessa
11
11
 
12
12
  def connection
13
13
  @connection ||= Faraday.new(url: url) do |conn|
14
- conn.basic_auth username, password
14
+ if conn.respond_to?(:basic_auth)
15
+ conn.basic_auth username, password
16
+ else # Faraday >= 1.0
17
+ conn.request :authorization, :basic, username, password
18
+ end
15
19
  conn.request :url_encoded
16
20
  conn.adapter Faraday.default_adapter
17
21
  end
@@ -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.0-rc3"
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
 
@@ -95,10 +95,6 @@ RSpec.describe Tessa::Config do
95
95
  connection
96
96
  end
97
97
 
98
- it "sets up basic auth params" do
99
- expect(spy).to have_received(:basic_auth).with(args[:username], args[:password])
100
- end
101
-
102
98
  it "sets up url_encoded request handler" do
103
99
  expect(spy).to have_received(:request).with(:url_encoded)
104
100
  end
@@ -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]
@@ -69,14 +69,4 @@ RSpec.describe Tessa::Upload::UploadsFile do
69
69
  end
70
70
  end
71
71
  end
72
-
73
- describe ".connection_factory" do
74
- it "returns a new Faraday::Connection with the default adapter" do
75
- obj = described_class.connection_factory
76
- expect(obj).to be_a(Faraday::Connection)
77
- expect(obj.builder.handlers)
78
- .to eq([Faraday::Adapter::NetHttp])
79
- end
80
- end
81
-
82
72
  end
data/tessa.gemspec CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
24
24
  spec.require_paths = ["lib"]
25
25
 
26
- spec.add_dependency "faraday", "<1"
26
+ spec.add_dependency "faraday"
27
27
  spec.add_dependency "virtus", "~>1.0.4"
28
28
 
29
29
  spec.add_development_dependency "rake", "~> 10.0"
data/yarn.lock CHANGED
@@ -415,11 +415,6 @@ doctrine@^2.1.0:
415
415
  dependencies:
416
416
  esutils "^2.0.2"
417
417
 
418
- dropzone@^4:
419
- version "4.3.0"
420
- resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-4.3.0.tgz#48b0b8f2ad092872e4b535b672a7c3f1a1d67c91"
421
- integrity sha512-KAP4sc9wjaU5xLhZ7olSH1ni72IbXk2l9iF9Ai5p3slwDtKTJunKkQpdFg7voyOcUU+6j75xEhHh+yPqd/Z/PA==
422
-
423
418
  es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5:
424
419
  version "1.20.1"
425
420
  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.1.tgz#027292cd6ef44bd12b1913b828116f54787d1814"
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.0.pre.rc3
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Powell
@@ -9,22 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-06-13 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
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - "<"
18
+ - - ">="
19
19
  - !ruby/object:Gem::Version
20
- version: '1'
20
+ version: '0'
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
- - - "<"
25
+ - - ">="
26
26
  - !ruby/object:Gem::Version
27
- version: '1'
27
+ version: '0'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: virtus
30
30
  requirement: !ruby/object:Gem::Requirement
@@ -262,9 +262,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
262
262
  version: '2.3'
263
263
  required_rubygems_version: !ruby/object:Gem::Requirement
264
264
  requirements:
265
- - - ">"
265
+ - - ">="
266
266
  - !ruby/object:Gem::Version
267
- version: 1.3.1
267
+ version: '0'
268
268
  requirements: []
269
269
  rubygems_version: 3.0.3.1
270
270
  signing_key: