tessa 1.2.0 → 1.2.1

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: 836d5b9c8929f9d9c4b7c2db37bce1d6a73be8dc8835fd439dab0f6230b72ec5
4
- data.tar.gz: c6f135eae7bf4d7e71b4ad9def77a5a356fe283054dbb5c778aef4c3279bc573
3
+ metadata.gz: 15c6d96fed1ca44a1fa33fb67071070c8728e9741fd06189a73de667143caba0
4
+ data.tar.gz: 2e186558c431ca8c919f248777e8765ffba15d1ff08c03e28b3f3ad864d611dd
5
5
  SHA512:
6
- metadata.gz: f08e31e74894827d96f34696959f24d9e286a86988ec53407d4e65828f657728661eeb54f85fd44e5f030b20f445a2aeeac170abf281a157edccb681dad169e5
7
- data.tar.gz: eba0b301158e3b2a477f97d9451cc83d2b652ddf30d91cae736b88ef033a75f02022f295a1ac943b24b1496524987d2e1f3a153a435afddb1353f892fdba8807
6
+ metadata.gz: daf25db44cd56243f10b22963acdb05bc86e4fb584446c171bf611307f0170cbf43fa53324d2acce63555b6d81b8982c5d72486fe8b17d29dc8e990d95ed4206
7
+ data.tar.gz: 8b92c3f631850f076e850c02aed81c611706509ebb4484d9141da1ec80c6734336e6380cacdcf9e4a73b20dc518b4c9fd9a87e3a494cfa099f1301a1427c7218
data/Gemfile CHANGED
@@ -11,5 +11,4 @@ end
11
11
  group :test do
12
12
  gem 'rspec-rails'
13
13
  gem 'webmock'
14
- gem 'faraday'
15
14
  end
data/README.md CHANGED
@@ -1,27 +1,11 @@
1
- # [Tessa Client][0]
1
+ # [Tessa][0]
2
2
 
3
3
  [ ![Gem Version][1] ][2]
4
4
  [ ![Build Status][3] ][4]
5
5
  [ ![Coverage Status][5] ][6]
6
6
  [ ![Code Climate][7] ][8]
7
7
 
8
- This gem wraps ActiveStorage to provide some convenience helpers for direct uploading
9
- assets, as well as generating public and private asset URLs.
10
-
11
- ## History
12
-
13
- This gem was initially conceived as a client to the
14
- [Tessa asset management service](https://github.com/watermarkchurch/tessa). That service has been deprecated and is
15
- being phased out.
16
-
17
- In 2022, we moved away from using Tessa to manage our assets and got onto the official Rails ActiveStorage library.
18
- [Here is the issue tracking the effort](https://github.com/watermarkchurch/tessa/issues/27). As part of that process,
19
- we released the v1.x version of this gem which supports both ActiveStorage and Tessa but uploads all new assets to
20
- ActiveStorage.
21
-
22
- When that migration is completed, v2.x of this gem will remove all code that accesses the old Tessa service. This gem
23
- will transition to being simply a convenience wrapper around ActiveStorage, so that we don't have to re-write as much
24
- of our code in our frontend apps.
8
+ Manage your assets.
25
9
 
26
10
  ## Installation
27
11
 
@@ -41,65 +25,7 @@ Or install it yourself as:
41
25
 
42
26
  ## Usage
43
27
 
44
- We provide no guarantee of support for your use of this gem. It is for internal Watermark use only.
45
- **Use at your own risk!**
46
-
47
- The main thing this gem still gives us is direct-uploads using [Dropzone.js](https://github.com/dropzone/dropzone).
48
- Before using this gem it's worthwhile to understand how it works:
49
-
50
- ![Sequence Diagram](./docs/tessa-activestorage-sequence-diagram.drawio.png)
51
-
52
- When an Asset input is configured using the SimpleForm helper (TODO: https://github.com/watermarkchurch/tessa-client/issues/22),
53
- it creates a DropZone div with the css class `.tessa-upload`. The javascript
54
- in `app/javascript/tessa/index.js.coffee` scans for these divs and configures
55
- Dropzone.js.
56
-
57
- When a user initiates a file upload via Dropzone, it does an XHR POST to `/tessa/uploads`. This triggers
58
- the `lib/tessa/rack_upload_proxy.rb` middleware to create a new ActiveStorage blob, and returns the
59
- signed upload URL. Dropzone then uploads the file directly to S3 (or wherever ActiveStorage is configured)
60
- from the user's browser.
61
-
62
- To get all this working, follow these steps:
63
-
64
- 1. Mount the engine
65
- in your `config/routes.rb`, `mount Tessa::Engine, at: '/'`. It's important that it is mounted at root.
66
-
67
- You can use Authentication around the engine to prevent unauthorized uploads. With devise it's as simple as:
68
- ```rb
69
- authenticated :user do
70
- mount Tessa::Engine, at: '/'
71
- end
72
- ```
73
-
74
- 2. In your application.js, require the js libraries:
75
- ```js
76
- //= require dropzone
77
- //= require tessa
78
- ```
79
- Note that this only works if you are using Sprockets.
80
- If you are using another bundler, we don't support that yet.
81
-
82
- 3. In your model, use the Tessa `asset` declaration instead of `has_one_attached`. The SimpleForm helper only works
83
- with Tessa `asset` declarations so far.
84
-
85
- ```rb
86
- class Model < ApplicationRecord
87
- include Tessa::Model
88
-
89
- asset :image # Note: this essentially delegates to has_one_attached
90
- ```
91
-
92
- 4. When rendering your form, use the SimpleForm helper to render the dropzone div:
93
-
94
- ```erb
95
- <%= f.input :image,
96
- as: :asset,
97
- dropzone: { acceptedFiles: "image/*" },
98
- hint: "Use an image that is 1440 x 288 in size (5:1 aspect ratio)" %>
99
- ```
100
-
101
- 5. Configure your ActiveStorage service to accept direct uploads.
102
- The disk service does this automatically. The S3 service requires additional CORS configuration.
28
+ TODO: Write usage instructions here
103
29
 
104
30
  ## Contributing
105
31
 
data/lib/tasks/tessa.rake CHANGED
@@ -2,17 +2,6 @@
2
2
  namespace :tessa do
3
3
  desc "Begins the migration of all Tessa assets to ActiveStorage."
4
4
  task :migrate => :environment do
5
- abort "Tessa::MigrateAssetsJob can no longer be performed because the Tessa connection was removed. "\
6
- "Please downgrade to tessa ~>1.0 and try again."
7
- end
8
-
9
- desc "Verifies that the migration has completed"
10
- task :verify => :environment do
11
- unless Tessa::MigrateAssetsJob.complete?
12
- state = Tessa::MigrateAssetsJob::ProcessingState.initialize_from_models
13
-
14
- abort "Tessa::MigrateAssetsJob not yet complete! #{state.count} records remain to be migrated. "\
15
- "Please downgrade to tessa ~>1.0 and try again."
16
- end
5
+ Tessa::MigrateAssetsJob.perform_later
17
6
  end
18
7
  end
data/lib/tessa/config.rb CHANGED
@@ -10,7 +10,15 @@ module Tessa
10
10
  attribute :strategy, String, default: -> (*_) { ENV['TESSA_STRATEGY'] || DEFAULT_STRATEGY }
11
11
 
12
12
  def connection
13
- @connection ||= Tessa::FakeConnection.new
13
+ @connection ||= Faraday.new(url: url) do |conn|
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
19
+ conn.request :url_encoded
20
+ conn.adapter Faraday.default_adapter
21
+ end
14
22
  end
15
23
  end
16
24
  end
@@ -127,24 +127,18 @@ class Tessa::MigrateAssetsJob < ActiveJob::Base
127
127
  end
128
128
 
129
129
  def load_models_from_registry
130
- # Initialize our Record Keeping object
131
- ProcessingState.initialize_from_models
132
- end
130
+ Rails.application.eager_load!
131
+
132
+ # Load all Tessa models that can have attachments (not form objects)
133
+ models = Tessa.model_registry
134
+ .select { |m| m.respond_to?(:has_one_attached) }
133
135
 
134
- # Determines whether the migrate asset job is completed. If true, running the
135
- # job again will not do anything.
136
- def self.complete?
137
- ProcessingState.initialize_from_models.fully_processed?
136
+ # Initialize our Record Keeping object
137
+ ProcessingState.initialize_from_models(models)
138
138
  end
139
139
 
140
140
  ProcessingState = Struct.new(:model_queue, :batch_count) do
141
- def self.initialize_from_models(models = nil)
142
- unless models
143
- # Load all Tessa models that can have attachments (not form objects)
144
- Rails.application.eager_load!
145
- models = Tessa.model_registry.select { |m| m.respond_to?(:has_one_attached) }
146
- end
147
-
141
+ def self.initialize_from_models(models)
148
142
  new(
149
143
  models.map do |model|
150
144
  ModelProcessingState.initialize_from_model(model)
@@ -22,11 +22,19 @@ class Tessa::DynamicExtensions
22
22
  if #{name}_attachment.present?
23
23
  return Tessa::ActiveStorage::AssetWrapper.new(#{name}_attachment)
24
24
  end
25
+
26
+ # fall back to old Tessa fetch if not present
27
+ if field = self.class.tessa_fields["#{name}".to_sym]
28
+ @#{name} ||= fetch_tessa_remote_assets(field.id(on: self))
29
+ end
25
30
  end
26
31
 
27
32
  def #{field.id_field}
28
33
  # Use the attachment's key
29
34
  return #{name}_attachment.key if #{name}_attachment.present?
35
+
36
+ # fallback to Tessa's database column
37
+ super
30
38
  end
31
39
 
32
40
  def #{name}=(attachable)
@@ -72,6 +80,7 @@ class Tessa::DynamicExtensions
72
80
 
73
81
  @#{name} ||= [
74
82
  *#{name}_attachments.map { |a| Tessa::ActiveStorage::AssetWrapper.new(a) },
83
+ *fetch_tessa_remote_assets(tessa_ids)
75
84
  ]
76
85
  end
77
86
 
@@ -79,6 +88,8 @@ class Tessa::DynamicExtensions
79
88
  [
80
89
  # Use the attachment's key
81
90
  *#{name}_attachments.map(&:key),
91
+ # include from Tessa's database column
92
+ *super
82
93
  ]
83
94
  end
84
95
 
@@ -94,8 +105,8 @@ class Tessa::DynamicExtensions
94
105
  existing.destroy
95
106
  else
96
107
  ids = self.#{field.id_field}
97
- ids&.delete(change.id.to_i)
98
- self.#{field.id_field} = ids&.any? ? ids : nil
108
+ ids.delete(change.id.to_i)
109
+ self.#{field.id_field} = ids.any? ? ids : nil
99
110
  end
100
111
  end
101
112
  attachables.changes.select(&:add?).each do |change|
@@ -127,7 +138,10 @@ class Tessa::DynamicExtensions
127
138
  def build(mod)
128
139
  mod.class_eval <<~CODE, __FILE__, __LINE__ + 1
129
140
  attr_accessor :#{name}_id
130
- attr_accessor :#{name}
141
+ attr_writer :#{name}
142
+ def #{name}
143
+ @#{name} ||= fetch_tessa_remote_assets(#{name}_id)
144
+ end
131
145
  CODE
132
146
  mod
133
147
  end
@@ -137,9 +151,12 @@ class Tessa::DynamicExtensions
137
151
  def build(mod)
138
152
  mod.class_eval <<~CODE, __FILE__, __LINE__ + 1
139
153
  attr_accessor :#{name}_ids
140
- attr_accessor :#{name}
154
+ attr_writer :#{name}
155
+ def #{name}
156
+ @#{name} ||= fetch_tessa_remote_assets(#{name}_ids)
157
+ end
141
158
  CODE
142
159
  mod
143
160
  end
144
161
  end
145
- end
162
+ end
data/lib/tessa/model.rb CHANGED
@@ -8,6 +8,8 @@ module Tessa
8
8
  def self.included(base)
9
9
  base.send :include, InstanceMethods
10
10
  base.extend ClassMethods
11
+ base.after_commit :apply_tessa_change_sets if base.respond_to?(:after_commit)
12
+ base.before_destroy :remove_all_tessa_assets if base.respond_to?(:before_destroy)
11
13
 
12
14
  Tessa.model_registry << base
13
15
  end
@@ -19,9 +21,9 @@ module Tessa
19
21
  end
20
22
 
21
23
  def apply_tessa_change_sets
22
- # Pretend like the application was successful but we didn't do anything
23
- # because everything is in ActiveStorage now
24
- pending_tessa_change_sets.clear
24
+ pending_tessa_change_sets.delete_if do |_, change_set|
25
+ change_set.apply
26
+ end
25
27
  end
26
28
 
27
29
  def remove_all_tessa_assets
@@ -35,9 +37,17 @@ module Tessa
35
37
  end
36
38
 
37
39
  def fetch_tessa_remote_assets(ids)
38
- # This should just always return Tessa::AssetFailure
39
40
  Tessa.find_assets(ids)
40
41
  end
42
+
43
+ private def reapplying_asset?(field, change_set)
44
+ additions = change_set.changes.select(&:add?)
45
+
46
+ return false if additions.none?
47
+ return false if change_set.changes.size > additions.size
48
+
49
+ additions.all? { |a| field.ids(on: self).include?(a.id) }
50
+ end
41
51
  end
42
52
 
43
53
  module ClassMethods
@@ -13,6 +13,8 @@ class Tessa::Upload::UploadsFile
13
13
  end
14
14
 
15
15
  def self.connection_factory
16
- Tessa::FakeConnection.new
16
+ Faraday.new do |conn|
17
+ conn.adapter Faraday.default_adapter
18
+ end
17
19
  end
18
20
  end
data/lib/tessa/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Tessa
2
- VERSION = "1.2.0"
2
+ VERSION = "1.2.1"
3
3
  end
data/lib/tessa.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  require "tessa/version"
2
2
 
3
+ require "faraday"
3
4
  require "virtus"
4
5
  require "json"
5
6
 
6
- require "tessa/fake_connection"
7
7
  require "tessa/config"
8
8
  require "tessa/response_factory"
9
9
  require "tessa/asset"
@@ -24,11 +24,16 @@ RSpec.shared_examples_for "remote call macro" do |method, path, return_type|
24
24
  end
25
25
 
26
26
  context "when response is not successful" do
27
- let(:connection) { Tessa::FakeConnection.new }
27
+ let(:faraday_stubs) {
28
+ Faraday::Adapter::Test::Stubs.new do |stub|
29
+ stub.send(method, path) { |env| [422, {}, { "error" => "error" }.to_json] }
30
+ end
31
+ }
28
32
 
29
33
  it "raises Tessa::RequestFailed" do
30
34
  expect{ call }.to raise_error { |error|
31
35
  expect(error).to be_a(Tessa::RequestFailed)
36
+ expect(error.response).to be_a(Faraday::Response)
32
37
  }
33
38
  end
34
39
  end
@@ -67,4 +67,46 @@ RSpec.describe Tessa::Config do
67
67
  expect(cfg.strategy).to eq("my-new-value")
68
68
  end
69
69
  end
70
+
71
+ describe "#connection" do
72
+ it "is a Faraday::Connection" do
73
+ expect(cfg.connection).to be_a(Faraday::Connection)
74
+ end
75
+
76
+ context "with values cfgured" do
77
+ subject { cfg.connection }
78
+ before { args.each { |k, v| cfg.send("#{k}=", v) } }
79
+ let(:args) {
80
+ {
81
+ url: "http://tessa.test",
82
+ username: "username",
83
+ password: "password",
84
+ }
85
+ }
86
+
87
+ it "sets faraday's url prefix to our url" do
88
+ expect(subject.url_prefix.to_s).to match(cfg.url)
89
+ end
90
+
91
+ context "with faraday spy" do
92
+ let(:spy) { instance_spy(Faraday::Connection) }
93
+ before do
94
+ expect(Faraday).to receive(:new).and_yield(spy)
95
+ subject
96
+ end
97
+
98
+ it "sets up url_encoded request handler" do
99
+ expect(spy).to have_received(:request).with(:url_encoded)
100
+ end
101
+
102
+ it "cfgures the default adapter" do
103
+ expect(spy).to have_received(:adapter).with(:net_http)
104
+ end
105
+ end
106
+ end
107
+
108
+ it "caches the result" do
109
+ expect(cfg.connection.object_id).to eq(cfg.connection.object_id)
110
+ end
111
+ end
70
112
  end
@@ -93,10 +93,17 @@ RSpec.describe Tessa::Model do
93
93
  let(:instance) { model.new(another_place: []) }
94
94
  subject(:getter) { instance.multiple_field }
95
95
 
96
- it "No longer calls Tessa::Asset#find" do
96
+ it "calls find for each of the file_ids and returns result" do
97
97
  instance.another_place = [1, 2, 3]
98
- expect(Tessa::Asset).to_not receive(:find)
99
- expect(getter).to eq([])
98
+ expect(Tessa::Asset).to receive(:find).with([1, 2, 3]).and_return([:a1, :a2, :a3])
99
+ expect(getter).to eq([:a1, :a2, :a3])
100
+ end
101
+
102
+ it "caches the result" do
103
+ instance.another_place = [1]
104
+ expect(Tessa::Asset).to receive(:find).and_return(:val).once
105
+ instance.multiple_field
106
+ instance.multiple_field
100
107
  end
101
108
 
102
109
  context "with no values" do
@@ -114,10 +121,17 @@ RSpec.describe Tessa::Model do
114
121
  }
115
122
  subject(:getter) { instance.avatar }
116
123
 
117
- it "No longer calls Tessa::Asset#find" do
124
+ it "calls find for file_id and returns result" do
125
+ instance.avatar_id = 1
126
+ expect(Tessa::Asset).to receive(:find).with(1).and_return(:a1)
127
+ expect(getter).to eq(:a1)
128
+ end
129
+
130
+ it "caches the result" do
118
131
  instance.avatar_id = 1
119
- expect(Tessa::Asset).to_not receive(:find)
120
- expect(getter).to eq(nil)
132
+ expect(Tessa::Asset).to receive(:find).and_return(:val).once
133
+ instance.avatar
134
+ instance.avatar
121
135
  end
122
136
 
123
137
  it "wraps ActiveStorage uploads with AssetWrapper" do
@@ -320,6 +334,245 @@ RSpec.describe Tessa::Model do
320
334
 
321
335
  expect(instance.another_place).to eq(keys)
322
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
388
+ end
389
+ end
390
+
391
+ describe "#apply_tessa_change_sets" do
392
+ let(:instance) { model.new }
393
+ let(:sets) { [ instance_spy(Tessa::AssetChangeSet) ] }
394
+
395
+ before do
396
+ instance.instance_variable_set(
397
+ :@pending_tessa_change_sets,
398
+ {
399
+ avatar: sets[0],
400
+ }
401
+ )
402
+ end
403
+
404
+ it "iterates over all pending changesets calling apply" do
405
+ instance.apply_tessa_change_sets
406
+ expect(sets[0]).to have_received(:apply)
407
+ end
408
+
409
+ it "removes all changesets from list" do
410
+ instance.apply_tessa_change_sets
411
+ expect(instance.pending_tessa_change_sets).to be_empty
412
+ end
413
+
414
+ context "no @pending_tessa_change_sets ivar" do
415
+ before do
416
+ instance.instance_variable_set(
417
+ :@pending_tessa_change_sets,
418
+ nil
419
+ )
420
+ end
421
+
422
+ it "doesn't raise error" do
423
+ expect { instance.apply_tessa_change_sets }.to_not raise_error
424
+ end
323
425
  end
324
426
  end
427
+
428
+ describe "#fetch_tessa_remote_assets" do
429
+ subject(:result) { model.new.fetch_tessa_remote_assets(arg) }
430
+
431
+ context "argument is `nil`" do
432
+ let(:arg) { nil }
433
+
434
+ it "returns nil" do
435
+ expect(result).to be_nil
436
+ end
437
+ end
438
+
439
+ context "argument is `[]`" do
440
+ let(:arg) { [] }
441
+
442
+ it "returns []" do
443
+ expect(result).to be_a(Array)
444
+ expect(result).to be_empty
445
+ end
446
+ end
447
+
448
+ context "when argument is not blank" do
449
+ let(:id) { rand(100) }
450
+ let(:arg) { id }
451
+
452
+ it "calls Tessa::Asset.find with arguments" do
453
+ expect(Tessa::Asset).to receive(:find).with(arg)
454
+ result
455
+ end
456
+
457
+ context "when Tessa::Asset.find raises RequestFailed exception" do
458
+ let(:error) {
459
+ Tessa::RequestFailed.new("test exception", double(status: '500'))
460
+ }
461
+
462
+ before do
463
+ allow(Tessa::Asset).to receive(:find).and_raise(error)
464
+ end
465
+
466
+ context "argument is single id" do
467
+ let(:arg) { id }
468
+
469
+ it "returns Failure" do
470
+ expect(result).to be_a(Tessa::Asset::Failure)
471
+ end
472
+
473
+ it "returns asset with proper data" do
474
+ expect(result.id).to eq(arg)
475
+ end
476
+ end
477
+
478
+ context "argument is array" do
479
+ let(:arg) { [ id, id * 2 ] }
480
+
481
+ it "returns array" do
482
+ expect(result).to be_a(Array)
483
+ end
484
+
485
+ it "returns instances of Failure" do
486
+ expect(result).to all( be_a(Tessa::Asset::Failure) )
487
+ end
488
+
489
+ it "returns array with an asset for each id passed" do
490
+ arg.zip(result) do |a, r|
491
+ expect(r.id).to eq(a)
492
+ end
493
+ end
494
+ end
495
+ end
496
+ end
497
+ end
498
+
499
+ describe "#remove_all_tessa_assets" do
500
+ let(:instance) { model.new }
501
+
502
+ context "with a single typed field" do
503
+ let(:model) {
504
+ SingleAssetModel
505
+ }
506
+
507
+ before do
508
+ instance.avatar_id = 1
509
+ end
510
+
511
+ it "adds pending change sets for each field removing all current assets" do
512
+ instance.remove_all_tessa_assets
513
+ changes = instance.pending_tessa_change_sets.values
514
+ .reduce(Tessa::AssetChangeSet.new, :+)
515
+ .changes
516
+ .map { |change| [change.id, change.action.to_sym] }
517
+ expect(changes).to eq([
518
+ [1, :remove]
519
+ ])
520
+ end
521
+ end
522
+
523
+
524
+ context "with a multiple typed field" do
525
+ let(:model) {
526
+ MultipleAssetModel
527
+ }
528
+ let(:instance) { model.new(another_place: []) }
529
+
530
+ before do
531
+ instance.another_place = [2, 3]
532
+ end
533
+
534
+ it "adds pending change sets for each field removing all current assets" do
535
+ instance.remove_all_tessa_assets
536
+ changes = instance.pending_tessa_change_sets.values
537
+ .reduce(Tessa::AssetChangeSet.new, :+)
538
+ .changes
539
+ .map { |change| [change.id, change.action.to_sym] }
540
+ expect(changes).to eq([
541
+ [2, :remove],
542
+ [3, :remove],
543
+ ])
544
+ end
545
+ end
546
+ end
547
+
548
+ describe "adds callbacks" do
549
+ context "model responds to after_commit" do
550
+ let(:model) {
551
+ Class.new do
552
+ def self.after_commit(arg=nil)
553
+ @after_commit ||= arg
554
+ end
555
+ end.tap { |c| c.send(:include, described_module) }
556
+ }
557
+
558
+ it "calls it with :apply_tessa_change_sets" do
559
+ expect(model.after_commit).to eq(:apply_tessa_change_sets)
560
+ end
561
+ end
562
+
563
+ context "model responds to before_destroy" do
564
+ let(:model) {
565
+ Class.new do
566
+ def self.before_destroy(arg=nil)
567
+ @before_destroy ||= arg
568
+ end
569
+ end.tap { |c| c.send(:include, described_module) }
570
+ }
571
+
572
+ it "calls it with :remove_all_tessa_assets" do
573
+ expect(model.before_destroy).to eq(:remove_all_tessa_assets)
574
+ end
575
+ end
576
+ end
577
+
325
578
  end
data/tessa.gemspec CHANGED
@@ -23,6 +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"
26
27
  spec.add_dependency "virtus", "~>1.0.4"
27
28
 
28
29
  spec.add_development_dependency "rake", "~> 10.0"
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.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Powell
@@ -11,6 +11,20 @@ bindir: bin
11
11
  cert_chain: []
12
12
  date: 2023-02-09 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: faraday
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
14
28
  - !ruby/object:Gem::Dependency
15
29
  name: virtus
16
30
  requirement: !ruby/object:Gem::Requirement
@@ -137,7 +151,6 @@ files:
137
151
  - app/javascript/tessa/index.js.coffee
138
152
  - bin/rspec
139
153
  - config/routes.rb
140
- - docs/tessa-activestorage-sequence-diagram.drawio.png
141
154
  - lib/tasks/tessa.rake
142
155
  - lib/tessa.rb
143
156
  - lib/tessa/active_storage/asset_wrapper.rb
@@ -148,7 +161,6 @@ files:
148
161
  - lib/tessa/config.rb
149
162
  - lib/tessa/controller_helpers.rb
150
163
  - lib/tessa/engine.rb
151
- - lib/tessa/fake_connection.rb
152
164
  - lib/tessa/jobs/migrate_assets_job.rb
153
165
  - lib/tessa/model.rb
154
166
  - lib/tessa/model/dynamic_extensions.rb
@@ -238,7 +250,6 @@ files:
238
250
  - spec/tessa/rack_upload_proxy_spec.rb
239
251
  - spec/tessa/upload/uploads_file_spec.rb
240
252
  - spec/tessa/upload_spec.rb
241
- - spec/tessa_spec.rb
242
253
  - tessa.gemspec
243
254
  - yarn.lock
244
255
  homepage: https://github.com/watermarkchurch/tessa-client
@@ -340,4 +351,3 @@ test_files:
340
351
  - spec/tessa/rack_upload_proxy_spec.rb
341
352
  - spec/tessa/upload/uploads_file_spec.rb
342
353
  - spec/tessa/upload_spec.rb
343
- - spec/tessa_spec.rb
@@ -1,29 +0,0 @@
1
- module Tessa
2
- # Since we no longer connect to the Tessa service, fake out the Tessa connection
3
- # so that it always returns 503
4
- class FakeConnection
5
-
6
- [:get, :head, :put, :post, :patch, :delete].each do |method|
7
- define_method(method) do |*args|
8
- if defined?(Bugsnag)
9
- Bugsnag.notify("Tessa::FakeConnection##{method} invoked")
10
- end
11
- Tessa::FakeConnection::Response.new()
12
- end
13
- end
14
-
15
- class Response
16
- def success?
17
- false
18
- end
19
-
20
- def status
21
- 503
22
- end
23
-
24
- def body
25
- '{ "error": "The Tessa connection is no longer implemented." }'
26
- end
27
- end
28
- end
29
- end
data/spec/tessa_spec.rb DELETED
@@ -1,23 +0,0 @@
1
- require 'rails_helper'
2
-
3
- RSpec.describe Tessa do
4
-
5
- describe '#find_assets' do
6
- it 'returns AssetFailure for singular asset' do
7
- result = Tessa.find_assets(1)
8
-
9
- expect(result).to be_a(Tessa::Asset::Failure)
10
- expect(result.message).to eq("The service is unavailable at this time.")
11
- end
12
-
13
- it 'returns AssetFailure array for multiple assets' do
14
- result = Tessa.find_assets([1, 2, 3])
15
-
16
- expect(result.count).to eq(3)
17
- result.each do |r|
18
- expect(r).to be_a(Tessa::Asset::Failure)
19
- expect(r.message).to eq("The service is unavailable at this time.")
20
- end
21
- end
22
- end
23
- end