tessa 2.0 → 6.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -18
  3. data/app/assets/javascripts/tessa.esm.js +212 -173
  4. data/app/assets/javascripts/tessa.js +206 -167
  5. data/app/javascript/activestorage/file_checksum.ts +76 -0
  6. data/app/javascript/tessa/index.ts +264 -0
  7. data/app/javascript/tessa/types.ts +34 -0
  8. data/config/routes.rb +0 -1
  9. data/lib/tessa/simple_form/asset_input.rb +24 -25
  10. data/lib/tessa/version.rb +1 -1
  11. data/lib/tessa.rb +0 -80
  12. data/package.json +7 -2
  13. data/rollup.config.js +5 -5
  14. data/spec/dummy/app/models/single_asset_model.rb +1 -1
  15. data/spec/dummy/app/models/single_asset_model_form.rb +3 -5
  16. data/spec/dummy/bin/rails +2 -2
  17. data/spec/dummy/bin/rake +2 -2
  18. data/spec/dummy/bin/setup +14 -6
  19. data/spec/dummy/bin/yarn +9 -3
  20. data/spec/dummy/config/application.rb +11 -9
  21. data/spec/dummy/config/boot.rb +1 -1
  22. data/spec/dummy/config/environment.rb +1 -1
  23. data/spec/dummy/config/environments/development.rb +34 -5
  24. data/spec/dummy/config/environments/production.rb +49 -10
  25. data/spec/dummy/config/environments/test.rb +28 -12
  26. data/spec/dummy/config/initializers/backtrace_silencers.rb +4 -3
  27. data/spec/dummy/config/initializers/content_security_policy.rb +5 -0
  28. data/spec/dummy/config/initializers/filter_parameter_logging.rb +3 -1
  29. data/spec/dummy/config/initializers/new_framework_defaults_6_1.rb +67 -0
  30. data/spec/dummy/config/initializers/permissions_policy.rb +11 -0
  31. data/spec/dummy/config/initializers/wrap_parameters.rb +5 -0
  32. data/spec/dummy/config/locales/en.yml +1 -1
  33. data/spec/dummy/config/routes.rb +1 -1
  34. data/spec/dummy/config/storage.yml +31 -0
  35. data/spec/dummy/config.ru +2 -1
  36. data/spec/dummy/db/migrate/20230406194400_add_service_name_to_active_storage_blobs.active_storage.rb +22 -0
  37. data/spec/dummy/db/migrate/20230406194401_create_active_storage_variant_records.active_storage.rb +27 -0
  38. data/spec/dummy/db/schema.rb +15 -7
  39. data/tessa.gemspec +4 -5
  40. data/tsconfig.json +10 -0
  41. data/yarn.lock +74 -7
  42. metadata +36 -74
  43. data/app/javascript/activestorage/file_checksum.js +0 -53
  44. data/app/javascript/tessa/index.js.coffee +0 -183
  45. data/lib/tasks/tessa.rake +0 -18
  46. data/lib/tessa/active_storage/asset_wrapper.rb +0 -32
  47. data/lib/tessa/asset/failure.rb +0 -37
  48. data/lib/tessa/asset.rb +0 -47
  49. data/lib/tessa/asset_change.rb +0 -49
  50. data/lib/tessa/asset_change_set.rb +0 -49
  51. data/lib/tessa/config.rb +0 -16
  52. data/lib/tessa/controller_helpers.rb +0 -16
  53. data/lib/tessa/fake_connection.rb +0 -29
  54. data/lib/tessa/jobs/migrate_assets_job.rb +0 -222
  55. data/lib/tessa/model/dynamic_extensions.rb +0 -145
  56. data/lib/tessa/model/field.rb +0 -77
  57. data/lib/tessa/model.rb +0 -94
  58. data/lib/tessa/rack_upload_proxy.rb +0 -41
  59. data/lib/tessa/response_factory.rb +0 -15
  60. data/lib/tessa/upload/uploads_file.rb +0 -18
  61. data/lib/tessa/upload.rb +0 -31
  62. data/lib/tessa/view_helpers.rb +0 -23
  63. data/spec/dummy/app/models/multiple_asset_model.rb +0 -8
  64. data/spec/support/remote_call_macro.rb +0 -40
  65. data/spec/tessa/asset/failure_spec.rb +0 -48
  66. data/spec/tessa/asset_change_set_spec.rb +0 -198
  67. data/spec/tessa/asset_change_spec.rb +0 -86
  68. data/spec/tessa/asset_spec.rb +0 -196
  69. data/spec/tessa/config_spec.rb +0 -70
  70. data/spec/tessa/controller_helpers_spec.rb +0 -55
  71. data/spec/tessa/jobs/migrate_assets_job_spec.rb +0 -247
  72. data/spec/tessa/model_field_spec.rb +0 -72
  73. data/spec/tessa/model_spec.rb +0 -325
  74. data/spec/tessa/rack_upload_proxy_spec.rb +0 -83
  75. data/spec/tessa/upload/uploads_file_spec.rb +0 -72
  76. data/spec/tessa/upload_spec.rb +0 -125
  77. data/spec/tessa_spec.rb +0 -23
data/lib/tessa/model.rb DELETED
@@ -1,94 +0,0 @@
1
- require 'tessa/model/field'
2
- require 'tessa/model/dynamic_extensions'
3
- require 'tessa/active_storage/asset_wrapper'
4
-
5
- module Tessa
6
- module Model
7
-
8
- def self.included(base)
9
- base.send :include, InstanceMethods
10
- base.extend ClassMethods
11
-
12
- Tessa.model_registry << base
13
- end
14
-
15
- module InstanceMethods
16
-
17
- def pending_tessa_change_sets
18
- @pending_tessa_change_sets ||= Hash.new { AssetChangeSet.new }
19
- end
20
-
21
- 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
25
- end
26
-
27
- def remove_all_tessa_assets
28
- self.class.tessa_fields.each do |name, field|
29
- change_set = pending_tessa_change_sets[name]
30
- field.ids(on: self).each do |asset_id|
31
- change_set.remove(asset_id)
32
- end
33
- pending_tessa_change_sets[name] = change_set
34
- end
35
- end
36
-
37
- def fetch_tessa_remote_assets(ids)
38
- # This should just always return Tessa::AssetFailure
39
- Tessa.find_assets(ids)
40
- end
41
- end
42
-
43
- module ClassMethods
44
- def asset(name, args={})
45
- field = tessa_fields[name] = Field.new(args.merge(name: name))
46
-
47
- multiple = args[:multiple]
48
-
49
-
50
- if respond_to?(:has_one_attached)
51
- if multiple
52
- has_many_attached(name)
53
- else
54
- has_one_attached(name)
55
- end
56
-
57
- # We have to replace the after_destroy_commit callback added above
58
- callbacks = get_callbacks(:commit)
59
- callbacks.delete(callbacks.to_a.last)
60
- after_destroy_commit { public_send("#{name}_attachment")&.purge_later }
61
- end
62
-
63
- dynamic_extensions =
64
- if respond_to?(:has_one_attached)
65
- if multiple
66
- ::Tessa::DynamicExtensions::MultipleRecord.new(field)
67
- else
68
- ::Tessa::DynamicExtensions::SingleRecord.new(field)
69
- end
70
- else
71
- if multiple
72
- ::Tessa::DynamicExtensions::MultipleFormObject.new(field)
73
- else
74
- ::Tessa::DynamicExtensions::SingleFormObject.new(field)
75
- end
76
- end
77
- include dynamic_extensions.build(Module.new)
78
-
79
- # Undefine the activestorage default attribute method so it falls back
80
- # to our dynamic module
81
- remove_method "#{name}" rescue nil
82
- remove_method "#{name}=" rescue nil
83
- end
84
-
85
- def tessa_fields
86
- @tessa_fields ||= {}
87
- end
88
-
89
- def inherited(subclass)
90
- subclass.instance_variable_set(:@tessa_fields, @tessa_fields.dup)
91
- end
92
- end
93
- end
94
- end
@@ -1,41 +0,0 @@
1
- module Tessa
2
- class RackUploadProxy
3
-
4
- def call(env)
5
- request = Rack::Request.new(env)
6
- ::ActiveStorage::Current.host ||= request.base_url
7
-
8
- # Call in to ActiveStorage to create a DirectUpload blob
9
- params = env['rack.request.form_hash']
10
-
11
- blob = ::ActiveStorage::Blob.create_before_direct_upload!({
12
- filename: params["name"],
13
- byte_size: params["size"],
14
- content_type: params["mime_type"],
15
- checksum: params["checksum"]
16
- })
17
-
18
- env['rack.session'][:tessa_upload_asset_ids] ||= []
19
- env['rack.session'][:tessa_upload_asset_ids] << blob.signed_id
20
-
21
- response = {
22
- asset_id: blob.signed_id,
23
- upload_url: blob.service_url_for_direct_upload,
24
- upload_method: 'PUT', # ActiveStorage is always PUT
25
- upload_headers: blob.service_headers_for_direct_upload
26
- }
27
-
28
- [200, {"Content-Type" => "application/json"}, [response.to_json]]
29
- rescue Tessa::RequestFailed
30
- [500, {"Content-Type" => "application/json"}, [{ "error" => "Failed to retreive upload URL" }.to_json]]
31
- rescue ActiveRecord::NotNullViolation => e
32
- [400, {"Content-Type" => "application/json"}, [{ "error" => e.message }.to_json]]
33
- end
34
-
35
- def self.call(*args)
36
- new.call(*args)
37
- end
38
-
39
- end
40
- end
41
-
@@ -1,15 +0,0 @@
1
- module Tessa
2
- module ResponseFactory
3
-
4
- def new_from_response(response)
5
- raise RequestFailed.new("Tessa responded with #{response.status}", response) unless response.success?
6
- case json = JSON.parse(response.body)
7
- when Array
8
- json.map { |record| new record }
9
- when Hash
10
- new json
11
- end
12
- end
13
-
14
- end
15
- end
@@ -1,18 +0,0 @@
1
- class Tessa::Upload::UploadsFile
2
- attr_reader :upload, :connection
3
-
4
- def initialize(upload:, connection: self.class.connection_factory)
5
- @upload = upload
6
- @connection = connection
7
- end
8
-
9
- def call(file)
10
- connection
11
- .public_send(upload.upload_method, upload.upload_url, File.read(file))
12
- .success?
13
- end
14
-
15
- def self.connection_factory
16
- Tessa::FakeConnection.new
17
- end
18
- end
data/lib/tessa/upload.rb DELETED
@@ -1,31 +0,0 @@
1
- module Tessa
2
- class Upload
3
- include Virtus.model
4
- extend ResponseFactory
5
-
6
- attribute :asset_id, Integer
7
- attribute :upload_url, String
8
- attribute :upload_method, String
9
-
10
- def upload_file(file)
11
- if UploadsFile.new(upload: self).(file)
12
- asset.complete!
13
- else
14
- asset.cancel!
15
- false
16
- end
17
- end
18
-
19
- def self.create(connection: Tessa.config.connection,
20
- strategy: Tessa.config.strategy,
21
- **options)
22
- new_from_response connection.post('/uploads', options.merge(strategy: strategy))
23
- end
24
-
25
- private def asset
26
- Asset.new(id: asset_id)
27
- end
28
- end
29
- end
30
-
31
- require 'tessa/upload/uploads_file'
@@ -1,23 +0,0 @@
1
- module Tessa
2
- module ViewHelpers
3
- def tessa_image_tag(asset, private: false)
4
- handle_asset_failure(asset) do
5
- image_tag(
6
- private ? asset.private_url : asset.public_url
7
- )
8
- end
9
- end
10
-
11
- private
12
-
13
- def handle_asset_failure(asset)
14
- if asset.failure?
15
- content_tag(:div, asset.message, class: "tessa-asset-failure")
16
- else
17
- yield
18
- end
19
- end
20
- end
21
- end
22
-
23
- ActionView::Base.send :include, Tessa::ViewHelpers if defined?(ActionView)
@@ -1,8 +0,0 @@
1
- class MultipleAssetModel < ActiveRecord::Base
2
- # Necessary to have an array of integers in sqlite
3
- serialize :another_place, Array
4
-
5
- include Tessa::Model
6
-
7
- asset :multiple_field, multiple: true, id_field: "another_place"
8
- end
@@ -1,40 +0,0 @@
1
- RSpec.shared_examples_for "remote call macro" do |method, path, return_type|
2
- let(:remote_response) { {} }
3
- let(:faraday_stubs) {
4
- Faraday::Adapter::Test::Stubs.new do |stub|
5
- stub.send(method, path) { |env| [200, {}, remote_response.to_json] }
6
- end
7
- }
8
- let(:connection) { Faraday.new { |f| f.adapter :test, faraday_stubs } }
9
- let(:call_args) { { connection: connection } }
10
-
11
- it "calls #{method} method with #{path}" do
12
- expect(connection).to receive(method).with(path, any_args).and_call_original
13
- call
14
- end
15
-
16
- context "with no connection passed" do
17
- let(:call_args) { {} }
18
-
19
- it "defaults connection to Tessa.config.connection" do
20
- expect(Tessa.config).to receive(:connection).and_return(connection)
21
- expect(connection).to receive(method).and_call_original
22
- call
23
- end
24
- end
25
-
26
- context "when response is not successful" do
27
- let(:connection) { Tessa::FakeConnection.new }
28
-
29
- it "raises Tessa::RequestFailed" do
30
- expect{ call }.to raise_error { |error|
31
- expect(error).to be_a(Tessa::RequestFailed)
32
- }
33
- end
34
- end
35
-
36
- it "returns an instance of #{return_type}" do
37
- expect(call).to be_a(return_type)
38
- end
39
- end
40
-
@@ -1,48 +0,0 @@
1
- require 'spec_helper'
2
-
3
- RSpec.describe Tessa::Asset::Failure do
4
- subject(:failure) { described_class.new(args) }
5
- let(:id) { rand(100) }
6
- let(:args) {
7
- { id: id, message: "Test" }
8
- }
9
-
10
- describe "#initialize" do
11
- it "sets id to attribute" do
12
- expect(failure.id).to eq(args[:id])
13
- end
14
-
15
- it "sets message to attribute" do
16
- expect(failure.message).to eq(args[:message])
17
- end
18
- end
19
-
20
- describe ".factory" do
21
- let(:message) { 'test message' }
22
- let(:response) { double(status: 500) }
23
- subject(:failure) { described_class.factory(id: id, response: response) }
24
-
25
-
26
- it "returns instance of Failure" do
27
- expect(described_class).to receive(:message_from_status)
28
- .with(response.status).and_return(message)
29
- expect(failure.id).to eq(id)
30
- expect(failure.message).to eq(message)
31
- end
32
- end
33
-
34
- it "responds like a blank asset" do
35
- asset = Tessa::Asset.new
36
- expect(failure.status).to eq(asset.status)
37
- expect(failure.strategy).to eq(asset.strategy)
38
- expect(failure.public_url).to eq(asset.public_url)
39
- expect(failure.private_url).to eq(asset.private_url)
40
- expect(failure.delete_url).to eq(asset.delete_url)
41
- end
42
-
43
- describe "#failure?" do
44
- it "returns true" do
45
- expect(failure).to be_failure
46
- end
47
- end
48
- end
@@ -1,198 +0,0 @@
1
- require 'spec_helper'
2
-
3
- RSpec.describe Tessa::AssetChangeSet do
4
- subject(:set) { described_class.new(args) }
5
- let(:args) {
6
- {
7
- changes: Array.new(2) { Tessa::AssetChange.new },
8
- scoped_ids: [1, 2],
9
- }
10
- }
11
-
12
- describe "#initialize" do
13
- it "sets :changes to attribute" do
14
- expect(set.changes).to eq(args[:changes])
15
- end
16
-
17
- it "sets :scoped_ids to attribute" do
18
- expect(set.scoped_ids).to eq([1, 2])
19
- end
20
-
21
- context "with various scoped_id input types" do
22
- before { args[:scoped_ids] = ["1", 2, nil] }
23
-
24
- it "ensures all values are integers and ignores nils" do
25
- expect(set.scoped_ids).to eq([1, 2])
26
- end
27
- end
28
-
29
- context "with hash format for changes" do
30
- before do
31
- args[:changes] = { "123" => { "action" => "add" }, "456" => { "action" => "remove" } }
32
- end
33
-
34
- it "initializes AssetChange objects for each hash" do
35
- expect(set.changes[0].id).to eq(123)
36
- expect(set.changes[0].action).to eq("add")
37
- expect(set.changes[1].id).to eq(456)
38
- expect(set.changes[1].action).to eq("remove")
39
- end
40
- end
41
- end
42
-
43
- describe "#scoped_changes" do
44
- context "with changes not contained in scoped ids" do
45
- let(:args) {
46
- {
47
- changes: [
48
- Tessa::AssetChange.new(id: 1),
49
- Tessa::AssetChange.new(id: 2),
50
- Tessa::AssetChange.new(id: 3),
51
- Tessa::AssetChange.new(id: 4)],
52
- scoped_ids: [1, 2],
53
- }
54
- }
55
-
56
- it "only returns the scoped changes" do
57
- expect(set.scoped_changes.size).to eq(2)
58
- expect(set.scoped_changes.map(&:id)).to eq([1, 2])
59
- end
60
- end
61
- end
62
-
63
- describe "#apply" do
64
-
65
- context "with unscoped changes" do
66
- let(:args) {
67
- {
68
- changes: [
69
- Tessa::AssetChange.new(id: 1),
70
- Tessa::AssetChange.new(id: 2)],
71
- scoped_ids: [1],
72
- }
73
- }
74
-
75
- it "calls apply on each scoped change" do
76
- expect(set.changes[0]).to receive(:apply)
77
- expect(set.changes[1]).not_to receive(:apply)
78
- set.apply
79
- end
80
- end
81
-
82
- context "with duplicate changes" do
83
- let(:args) {
84
- {
85
- changes: [
86
- Tessa::AssetChange.new(id: 1, action: 'remove'),
87
- Tessa::AssetChange.new(id: 1, action: 'remove')],
88
- scoped_ids: [1],
89
- }
90
- }
91
-
92
- it "only calls apply on unique elements" do
93
- expect(el = set.changes.uniq.first).to receive(:apply)
94
- (set.changes - [el]).each { |o| expect(el).to_not receive(:apply) }
95
- set.apply
96
- end
97
- end
98
- end
99
-
100
- describe "#+" do
101
- let(:a) {
102
- described_class.new(
103
- changes: [Tessa::AssetChange.new(id: 1, action: 'add')],
104
- scoped_ids: [1],
105
- )
106
- }
107
- let(:b) {
108
- described_class.new(
109
- changes: [Tessa::AssetChange.new(id: 2, action: 'add')],
110
- scoped_ids: [2],
111
- )
112
- }
113
- subject(:sum) { a + b }
114
-
115
- it "concatenates the values of changes" do
116
- expect(sum.changes.collect(&:id)).to eq([1, 2])
117
- end
118
-
119
- it "concatenates the values of scoped_ids" do
120
- expect(sum.scoped_ids).to eq([1, 2])
121
- end
122
-
123
- context "with duplicate entries" do
124
- subject(:sum) { a + b + b }
125
-
126
- it "only includes unique values" do
127
- expect(sum.changes.collect(&:id)).to eq([1, 2])
128
- expect(sum.scoped_ids).to eq([1, 2])
129
- end
130
- end
131
- end
132
-
133
- describe "#add" do
134
- let(:args) { {} }
135
- let(:asset) { Tessa::Asset.new(id: 1) }
136
-
137
- shared_examples_for "adds the represented asset" do
138
- it "adds an 'add' change for this asset" do
139
- expect(set.changes[0].id).to eq(1)
140
- expect(set.changes[0].action).to eq("add")
141
- end
142
-
143
- it "adds the asset id to the scoped_ids array" do
144
- expect(set.scoped_ids).to eq([1])
145
- end
146
- end
147
-
148
- context "when passed an asset" do
149
- before do
150
- set.add(asset)
151
- end
152
-
153
- it_behaves_like "adds the represented asset"
154
- end
155
-
156
- context "when passed an integer id" do
157
- before do
158
- set.add(1)
159
- end
160
-
161
- it_behaves_like "adds the represented asset"
162
- end
163
- end
164
-
165
- describe "#remove" do
166
- let(:args) { {} }
167
- let(:asset) { Tessa::Asset.new(id: 1) }
168
-
169
- shared_examples_for "removes the represented asset" do
170
- it "adds a 'remove' change for this asset" do
171
- expect(set.changes[0].id).to eq(1)
172
- expect(set.changes[0].action).to eq("remove")
173
- end
174
-
175
- it "adds the asset id to the scoped_ids array" do
176
- expect(set.scoped_ids).to eq([1])
177
- end
178
- end
179
-
180
- context "when passed an asset" do
181
- before do
182
- set.remove(asset)
183
- end
184
-
185
- it_behaves_like "removes the represented asset"
186
- end
187
-
188
- context "when passed an integer id" do
189
- before do
190
- set.remove(1)
191
- end
192
-
193
- it_behaves_like "removes the represented asset"
194
- end
195
-
196
- end
197
-
198
- end
@@ -1,86 +0,0 @@
1
- require 'spec_helper'
2
-
3
- RSpec.describe Tessa::AssetChange do
4
- subject(:change) { described_class.new(args) }
5
- let(:args) {
6
- {
7
- id: 123,
8
- action: "add",
9
- }
10
- }
11
-
12
- describe "#initialize" do
13
- context "with hash args" do
14
- it "sets id" do
15
- expect(subject.id).to eq(123)
16
- end
17
-
18
- it "sets action" do
19
- expect(subject.action).to eq("add")
20
- end
21
- end
22
-
23
- context "with array arg" do
24
- let(:args) { [123, { "action" => "add" }] }
25
-
26
- it "sets id" do
27
- expect(subject.id).to eq(123)
28
- end
29
-
30
- it "sets action" do
31
- expect(subject.action).to eq("add")
32
- end
33
- end
34
- end
35
-
36
- describe "#apply" do
37
- let(:asset) { instance_spy(Tessa::Asset) }
38
- before do
39
- expect(Tessa::Asset).to receive(:new).with(id: 123).and_return(asset)
40
- end
41
-
42
- context "with action 'add'" do
43
- before { args[:action] = "add" }
44
- it "calls complete! on asset with :id" do
45
- change.apply
46
- expect(asset).to have_received(:complete!)
47
- end
48
- end
49
-
50
- context "with action 'remove'" do
51
- before { args[:action] = "remove" }
52
- it "calls delete! on asset with :id" do
53
- change.apply
54
- expect(asset).to have_received(:delete!)
55
- end
56
- end
57
- end
58
-
59
- describe "#add?" do
60
- subject(:add?) { change.add? }
61
-
62
- context "action = 'add'" do
63
- before { change.action = 'add' }
64
- it { is_expected.to eq(true) }
65
- end
66
-
67
- context "action != 'add'" do
68
- before { change.action = 'something' }
69
- it { is_expected.to eq(false) }
70
- end
71
- end
72
-
73
- describe "#remove?" do
74
- subject(:remove?) { change.remove? }
75
-
76
- context "action = 'remove'" do
77
- before { change.action = 'remove' }
78
- it { is_expected.to eq(true) }
79
- end
80
-
81
- context "action != 'remove'" do
82
- before { change.action = 'something' }
83
- it { is_expected.to eq(false) }
84
- end
85
- end
86
- end