saviour 0.6.2 → 0.6.7

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: 299270bf4c0f5d46a9fd7a57d51062a5a86a1c9216187baf3d0d3a6258295811
4
- data.tar.gz: a0ed19b72e62d0fc006698958bd057eaf0543c8595ae080e9279c07e33fddb95
3
+ metadata.gz: 43509e7e421eab9a8b45159d62c58513dbcb894b0ccecbd96a70cc0a29db74a6
4
+ data.tar.gz: 91b2eb4cf47dbac1b642c5ccc198ae0040ea0e81ef993c580e894065f1e58ecf
5
5
  SHA512:
6
- metadata.gz: 90ac6353b42b4c3422bb3154b983422658837e9c0c307bb4871e2c3c0e50d7101286524984c036fcf68abb91b830d82aa39886f88d362490ddf86d459401c9a3
7
- data.tar.gz: 82d6cdeab9db03a38a8819109c2cf24d4824a9ca1a9c77830bdb8d95efb98c50664ad1eeb8862224f795e9c334d8d36a9f071955e0f232ae14d2198b95124720
6
+ metadata.gz: e4a1fb654e3290d827236f115ebfd93e2f6d4994e57d022dd473f41436365de36182e689d20526cea2c9bb113dd6b558901da5deda945f526242ab2e78e7c633
7
+ data.tar.gz: 8f3f81305ceb56195d67eb2a6700a5f05bb3e4567c892a983fe845689c952864497e2bd29c47a4256a88a6f003527855952d2ed78e6edc7cff80d66adacca7b0
@@ -2,14 +2,14 @@ language: ruby
2
2
  sudo: false
3
3
  cache: bundler
4
4
  rvm:
5
- - 2.2.8
6
- - 2.3.5
7
- - 2.4.2
8
- - 2.5.0
5
+ - 2.5.8
6
+ - 2.6.6
7
+ - 2.7.1
9
8
 
10
9
  gemfile:
11
10
  - gemfiles/5.1.gemfile
12
11
  - gemfiles/5.2.gemfile
12
+ - gemfiles/6.0.gemfile
13
13
 
14
14
  addons:
15
15
  code_climate:
data/README.md CHANGED
@@ -593,7 +593,7 @@ Now, both attachments are independent:
593
593
 
594
594
  ```ruby
595
595
  # `image_thumb` can be changed independently
596
- a.update_attributes! image_thumb: File.open("/path/another_file.png")
596
+ a.update! image_thumb: File.open("/path/another_file.png")
597
597
 
598
598
  # or removed
599
599
  a.remove_file_thumb!
@@ -714,7 +714,7 @@ class ImageUploader < Saviour::BaseUploader
714
714
  end
715
715
 
716
716
  after_upload do |stash|
717
- model.update_attributes!(size: stash[:size], width: stash[:width], height: stash[:height])
717
+ model.update!(size: stash[:size], width: stash[:width], height: stash[:height])
718
718
  end
719
719
  end
720
720
  ```
@@ -817,11 +817,14 @@ as in the processor's case.
817
817
 
818
818
  ### Introspection
819
819
 
820
- Two methods are added to any class including `Saviour::Model` to give you information about what attachments
820
+ Three methods are added to any class including `Saviour::Model` to give you information about what attachments
821
821
  have been defined in that class.
822
822
 
823
823
  `Model.attached_files` will give an array of symbols, representing all the attachments declared in that class.
824
824
 
825
+ `Model.uploader_classes` will give you a hash with all the uploader classes being used in that model, key-ed by
826
+ the name of each attachment.
827
+
825
828
  `Model.attached_followers_per_leader` will give a hash where the keys are attachments that have versions
826
829
  assigned, and the values being an array of symbols, representing the attachments that are following that attachment.
827
830
 
@@ -837,6 +840,7 @@ end
837
840
 
838
841
  Post.attached_files # => [:image, :image_thumb, :image_thumb_2, :cover]
839
842
  Post.attached_followers_per_leader # => { image: [:image_thumb, :image_thumb_2] }
843
+ Post.uploader_classes # => { image: SomeUploader, image_thumb: SomeUploader, ... }
840
844
  ```
841
845
 
842
846
 
@@ -964,6 +968,38 @@ Any additional information the storage may require can be provided on instance c
964
968
  this is not used by Saviour.
965
969
 
966
970
 
971
+ ### Using `ReadOnlyFile`
972
+
973
+ Sometimes you may find the need to have a saviour `File`-like object but you don't have the original model available.
974
+
975
+ For example, if you're working directly fetching data from database, you have the `path` to an asset but you're not
976
+ loading an ActiveRecord object.
977
+
978
+ In such cases you can use the class `ReadOnlyFile`, like this:
979
+
980
+ ```ruby
981
+ class Product
982
+ # ...
983
+ attach_file :image
984
+ end
985
+
986
+ my_cheap_products = ActiveRecord::Base.connection.select_all "select image, ... from products"
987
+
988
+ # In this case, you have the path to the assets and want to convert it into an object so that the
989
+ # rest of the application can work normally with it, you can do:
990
+
991
+ storage = Product.uploader_classes[:image].storage
992
+ file = Saviour::ReadOnlyFile.new(path, storage)
993
+
994
+ file.read # -> contents
995
+ file.public_url # -> url
996
+ # etc ...
997
+ ```
998
+
999
+ You'll need to get the appropriate storage object from the same uploader that attachment is using,
1000
+ and create a new `ReadOnlyFile` instance only with the asset's path and the storage.
1001
+
1002
+
967
1003
  ### Bypassing Saviour
968
1004
 
969
1005
  The only reference to stored files Saviour holds and uses is the path persisted in the database. If you want to,
@@ -1096,7 +1132,7 @@ Then it can be used as:
1096
1132
  a = Post.find(42)
1097
1133
 
1098
1134
  # Params received from a form
1099
- a.update_attributes(remove_image: "t")
1135
+ a.update(remove_image: "t")
1100
1136
  ```
1101
1137
 
1102
1138
 
@@ -1136,7 +1172,7 @@ The job then should take the model and the attachment to process and run the pro
1136
1172
  a = Post.find(42)
1137
1173
  a.image.with_copy do |f|
1138
1174
  # manipulate f as desired
1139
- a.update_attributes! image: f
1175
+ a.update! image: f
1140
1176
  end
1141
1177
  ```
1142
1178
 
@@ -1,5 +1,3 @@
1
- # This file was generated by Appraisal
2
-
3
1
  source "https://rubygems.org"
4
2
 
5
3
  gem "codeclimate-test-reporter", :group => :test, :require => nil
@@ -1,5 +1,3 @@
1
- # This file was generated by Appraisal
2
-
3
1
  source "https://rubygems.org"
4
2
 
5
3
  gem "codeclimate-test-reporter", :group => :test, :require => nil
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "codeclimate-test-reporter", :group => :test, :require => nil
4
+ gem "activesupport", "~> 6.0.0"
5
+ gem "activerecord", "~> 6.0.0"
6
+
7
+ gemspec :path => "../"
@@ -17,7 +17,8 @@ require 'saviour/db_helpers'
17
17
 
18
18
  require 'tempfile'
19
19
  require 'fileutils'
20
- require 'concurrent/future'
20
+ require 'concurrent/edge/throttle'
21
+ require 'concurrent/edge/lock_free_queue'
21
22
 
22
23
  require 'active_support/dependencies'
23
24
 
@@ -79,6 +79,8 @@ module Saviour
79
79
 
80
80
  def storage
81
81
  @storage ||= Config.storage
82
+
83
+ @storage.respond_to?(:call) ? @storage.call : @storage
82
84
  end
83
85
 
84
86
  def process(name = nil, opts = {}, type = :memory, &block)
@@ -22,6 +22,9 @@ module Saviour
22
22
  def committed!(*)
23
23
  @block.call
24
24
  end
25
+
26
+ def trigger_transactional_callbacks?(*)
27
+ end
25
28
  end
26
29
 
27
30
  class RollbackDummy
@@ -42,6 +45,9 @@ module Saviour
42
45
 
43
46
  def committed!(*)
44
47
  end
48
+
49
+ def trigger_transactional_callbacks?(*)
50
+ end
45
51
  end
46
52
 
47
53
 
@@ -2,26 +2,26 @@ require 'securerandom'
2
2
 
3
3
  module Saviour
4
4
  class File
5
- attr_reader :persisted_path
6
- attr_reader :source
5
+ attr_reader :persisted_path, :source, :storage
7
6
 
8
7
  def initialize(uploader_klass, model, attached_as, persisted_path = nil)
9
8
  @uploader_klass, @model, @attached_as = uploader_klass, model, attached_as
10
9
  @source_was = @source = nil
11
10
  @persisted_path = persisted_path
11
+ @storage = @uploader_klass.storage
12
12
 
13
13
  if persisted_path
14
- @model.instance_variable_set("@__uploader_#{@attached_as}_was", ReadOnlyFile.new(persisted_path, @uploader_klass))
14
+ @model.instance_variable_set("@__uploader_#{@attached_as}_was", ReadOnlyFile.new(persisted_path, @uploader_klass.storage))
15
15
  end
16
16
  end
17
17
 
18
18
  def exists?
19
- persisted? && @uploader_klass.storage.exists?(@persisted_path)
19
+ persisted? && @storage.exists?(@persisted_path)
20
20
  end
21
21
 
22
22
  def read
23
23
  return nil unless persisted?
24
- @uploader_klass.storage.read(@persisted_path)
24
+ @storage.read(@persisted_path)
25
25
  end
26
26
 
27
27
  def delete
@@ -32,7 +32,7 @@ module Saviour
32
32
 
33
33
  def public_url
34
34
  return nil unless persisted?
35
- @uploader_klass.storage.public_url(@persisted_path)
35
+ @storage.public_url(@persisted_path)
36
36
  end
37
37
 
38
38
  def ==(another_file)
@@ -111,7 +111,7 @@ module Saviour
111
111
  temp_file.binmode
112
112
 
113
113
  begin
114
- @uploader_klass.storage.read_to_file(@persisted_path, temp_file)
114
+ @storage.read_to_file(@persisted_path, temp_file)
115
115
 
116
116
  yield(temp_file)
117
117
  ensure
@@ -154,13 +154,13 @@ module Saviour
154
154
 
155
155
  case source_type
156
156
  when :stream
157
- @uploader_klass.storage.write(contents, path)
157
+ @storage.write(contents, path)
158
158
  when :file
159
- @uploader_klass.storage.write_from_file(contents, path)
159
+ @storage.write_from_file(contents, path)
160
160
  end
161
161
 
162
162
  @persisted_path = path
163
- @model.instance_variable_set("@__uploader_#{@attached_as}_was", ReadOnlyFile.new(persisted_path, @uploader_klass))
163
+ @model.instance_variable_set("@__uploader_#{@attached_as}_was", ReadOnlyFile.new(persisted_path, @storage))
164
164
  path
165
165
  end
166
166
  end
@@ -12,6 +12,8 @@ module Saviour
12
12
  @klass.attached_files = []
13
13
  @klass.class_attribute :followers_per_leader_config
14
14
  @klass.followers_per_leader_config = {}
15
+ @klass.class_attribute :uploader_classes
16
+ @klass.uploader_classes = {}
15
17
 
16
18
  persistence_klass = @persistence_klass
17
19
 
@@ -36,10 +38,13 @@ module Saviour
36
38
  raise ConfigurationError, "you must provide either an UploaderClass or a block to define it."
37
39
  end
38
40
 
41
+ uploader_klass = Class.new(Saviour::BaseUploader, &block) if block
42
+
43
+ self.uploader_classes[attach_as] = uploader_klass
44
+
39
45
  mod = Module.new do
40
46
  define_method(attach_as) do
41
47
  instance_variable_get("@__uploader_#{attach_as}") || begin
42
- uploader_klass = Class.new(Saviour::BaseUploader, &block) if block
43
48
  layer = persistence_klass.new(self)
44
49
  new_file = ::Saviour::File.new(uploader_klass, self, attach_as, layer.read(attach_as))
45
50
 
@@ -79,6 +84,22 @@ module Saviour
79
84
  end
80
85
  end
81
86
 
87
+ define_method(:changed) do
88
+ if ActiveRecord::VERSION::MAJOR == 6 && send("#{attach_as}_changed?")
89
+ super() + [attach_as.to_s]
90
+ else
91
+ super()
92
+ end
93
+ end
94
+
95
+ define_method(:changed?) do
96
+ if ActiveRecord::VERSION::MAJOR == 6
97
+ send("#{attach_as}_changed?") || super()
98
+ else
99
+ super()
100
+ end
101
+ end
102
+
82
103
  define_method("#{attach_as}_change") do
83
104
  [send("#{attach_as}_was"), send(attach_as)]
84
105
  end
@@ -1,5 +1,7 @@
1
1
  module Saviour
2
2
  class LifeCycle
3
+ SHOULD_USE_INTERLOCK = defined?(Rails)
4
+
3
5
  class FileCreator
4
6
  def initialize(current_path, file, column, connection)
5
7
  @file = file
@@ -49,7 +51,7 @@ module Saviour
49
51
  end
50
52
 
51
53
  @new_path = @file.write(
52
- before_write: ->(path) { dup_file.call if @current_path == path }
54
+ before_write: ->(path) { dup_file.call if @current_path == path }
53
55
  )
54
56
 
55
57
  return unless @new_path
@@ -84,26 +86,19 @@ module Saviour
84
86
 
85
87
  def delete!
86
88
  DbHelpers.run_after_commit do
87
- pool = Concurrent::FixedThreadPool.new(Saviour::Config.concurrent_workers)
89
+ pool = Concurrent::Throttle.new Saviour::Config.concurrent_workers
88
90
 
89
91
  futures = attached_files.map do |column|
90
- Concurrent::Future.execute(executor: pool) {
91
- file = @model.send(column)
92
+ pool.future(@model.send(column)) do |file|
92
93
  path = file.persisted_path
93
94
  file.uploader.storage.delete(path) if path
94
95
  file.delete
95
- }
96
+ end
96
97
  end
97
98
 
98
99
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
99
- futures.each do |future|
100
- future.value
101
- raise(future.reason) if future.rejected?
102
- end
100
+ futures.each(&:value!)
103
101
  end
104
-
105
- pool.shutdown
106
- pool.wait_for_termination
107
102
  end
108
103
  end
109
104
 
@@ -124,34 +119,32 @@ module Saviour
124
119
  next unless @model.send(column).changed?
125
120
 
126
121
  klass.new(
127
- persistence_layer.read(column),
128
- @model.send(column),
129
- column,
130
- ActiveRecord::Base.connection
122
+ persistence_layer.read(column),
123
+ @model.send(column),
124
+ column,
125
+ ActiveRecord::Base.connection
131
126
  )
132
127
  end.compact
133
128
 
134
- pool = Concurrent::FixedThreadPool.new(Saviour::Config.concurrent_workers)
129
+ pool = Concurrent::Throttle.new Saviour::Config.concurrent_workers
130
+
135
131
  futures = uploaders.map { |uploader|
136
- Concurrent::Future.execute(executor: pool) {
137
- if defined?(Rails)
138
- Rails.application.executor.wrap { uploader.upload }
132
+ pool.future(uploader) { |given_uploader|
133
+ if SHOULD_USE_INTERLOCK
134
+ Rails.application.executor.wrap { given_uploader.upload }
139
135
  else
140
- uploader.upload
136
+ given_uploader.upload
141
137
  end
142
138
  }
143
139
  }
144
140
 
145
- result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
146
- futures.map do |x|
147
- x.value.tap do
148
- raise(x.reason) if x.rejected?
149
- end
150
- end.compact
151
- end
141
+ work = -> { futures.map(&:value!).compact }
152
142
 
153
- pool.shutdown
154
- pool.wait_for_termination
143
+ result = if SHOULD_USE_INTERLOCK
144
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads(&work)
145
+ else
146
+ work.call
147
+ end
155
148
 
156
149
  attrs = result.to_h
157
150
 
@@ -1,24 +1,24 @@
1
1
  module Saviour
2
2
  class ReadOnlyFile
3
- attr_reader :persisted_path
3
+ attr_reader :persisted_path, :storage
4
4
 
5
- def initialize(persisted_path, uploader_klass)
5
+ def initialize(persisted_path, storage)
6
6
  @persisted_path = persisted_path
7
- @uploader_klass = uploader_klass
7
+ @storage = storage
8
8
  end
9
9
 
10
10
  def exists?
11
- persisted? && Config.storage.exists?(@persisted_path)
11
+ persisted? && @storage.exists?(@persisted_path)
12
12
  end
13
13
 
14
14
  def read
15
15
  return nil unless persisted?
16
- @uploader_klass.storage.read(@persisted_path)
16
+ @storage.read(@persisted_path)
17
17
  end
18
18
 
19
19
  def public_url
20
20
  return nil unless persisted?
21
- @uploader_klass.storage.public_url(@persisted_path)
21
+ @storage.public_url(@persisted_path)
22
22
  end
23
23
  alias_method :url, :public_url
24
24
 
@@ -11,6 +11,7 @@ module Saviour
11
11
  def initialize(conf = {})
12
12
  @bucket = conf.delete(:bucket)
13
13
  @public_url_prefix = conf.delete(:public_url_prefix)
14
+ @extra_aws_client_options = conf.delete(:aws_client_opts)
14
15
  @conf = conf
15
16
  @create_options = conf.delete(:create_options) { {} }
16
17
  conf.fetch(:aws_access_key_id) { raise(ArgumentError, "aws_access_key_id is required") }
@@ -119,9 +120,11 @@ module Saviour
119
120
 
120
121
  def client
121
122
  @client ||= Aws::S3::Client.new(
122
- access_key_id: @conf[:aws_access_key_id],
123
- secret_access_key: @conf[:aws_secret_access_key],
124
- region: @region
123
+ {
124
+ access_key_id: @conf[:aws_access_key_id],
125
+ secret_access_key: @conf[:aws_secret_access_key],
126
+ region: @region
127
+ }.merge(@extra_aws_client_options || {})
125
128
  )
126
129
  end
127
130
  end
@@ -58,7 +58,7 @@ module Saviour
58
58
  result = run_method_or_block(method_or_block, opts, file)
59
59
 
60
60
  self.file = result[0]
61
- file.reopen(file.path)
61
+ file.reopen(file.path, "r+")
62
62
 
63
63
  self.filename = result[1]
64
64
  end
@@ -1,3 +1,3 @@
1
1
  module Saviour
2
- VERSION = "0.6.2"
2
+ VERSION = "0.6.7"
3
3
  end
@@ -13,17 +13,17 @@ Gem::Specification.new do |spec|
13
13
  spec.files = `git ls-files`.split($/)
14
14
  spec.require_paths = ["lib"]
15
15
 
16
- spec.required_ruby_version = ">= 2.2.0"
16
+ spec.required_ruby_version = ">= 2.5.0"
17
17
 
18
18
  spec.add_dependency "activerecord", ">= 5.1"
19
19
  spec.add_dependency "activesupport", ">= 5.1"
20
20
  spec.add_dependency "concurrent-ruby", ">= 1.0.5"
21
+ spec.add_dependency "concurrent-ruby-edge", ">= 0.6.0"
21
22
 
22
23
  spec.add_development_dependency "bundler"
23
24
  spec.add_development_dependency "rspec"
24
25
  spec.add_development_dependency "rake"
25
26
  spec.add_development_dependency "sqlite3"
26
- spec.add_development_dependency "appraisal"
27
27
  spec.add_development_dependency "aws-sdk-s3"
28
28
  spec.add_development_dependency "mime-types"
29
29
  spec.add_development_dependency "get_process_mem"
@@ -52,7 +52,7 @@ describe "concurrency on operations" do
52
52
 
53
53
  Saviour::Config.concurrent_workers = 4
54
54
 
55
- a.update_attributes! file: Saviour::StringSource.new("contents", "file.txt"),
55
+ a.update! file: Saviour::StringSource.new("contents", "file.txt"),
56
56
  file_thumb: Saviour::StringSource.new("contents", "file_2.txt"),
57
57
  file_thumb_2: Saviour::StringSource.new("contents", "file_3.txt"),
58
58
  file_thumb_3: Saviour::StringSource.new("contents", "file_4.txt")
@@ -66,7 +66,7 @@ describe "concurrency on operations" do
66
66
 
67
67
  Saviour::Config.concurrent_workers = 1
68
68
 
69
- a.update_attributes! file: Saviour::StringSource.new("contents", "file.txt"),
69
+ a.update! file: Saviour::StringSource.new("contents", "file.txt"),
70
70
  file_thumb: Saviour::StringSource.new("contents", "file_2.txt"),
71
71
  file_thumb_2: Saviour::StringSource.new("contents", "file_3.txt"),
72
72
  file_thumb_3: Saviour::StringSource.new("contents", "file_4.txt")
@@ -80,7 +80,7 @@ describe "concurrency on operations" do
80
80
 
81
81
  Saviour::Config.concurrent_workers = 2
82
82
 
83
- a.update_attributes! file: Saviour::StringSource.new("contents", "file.txt"),
83
+ a.update! file: Saviour::StringSource.new("contents", "file.txt"),
84
84
  file_thumb: Saviour::StringSource.new("contents", "file_2.txt"),
85
85
  file_thumb_2: Saviour::StringSource.new("contents", "file_3.txt"),
86
86
  file_thumb_3: Saviour::StringSource.new("contents", "file_4.txt")
@@ -19,14 +19,14 @@ describe "CRUD" do
19
19
  it do
20
20
  with_test_file("example.xml") do |example|
21
21
  a = klass.create!
22
- expect(a.update_attributes(file: example)).to be_truthy
22
+ expect(a.update(file: example)).to be_truthy
23
23
  end
24
24
  end
25
25
 
26
26
  it do
27
27
  with_test_file("example.xml") do |example|
28
28
  a = klass.create!
29
- a.update_attributes(file: example)
29
+ a.update(file: example)
30
30
 
31
31
  expect(Saviour::Config.storage.exists?(a[:file])).to be_truthy
32
32
  end
@@ -35,7 +35,7 @@ describe "CRUD" do
35
35
  it do
36
36
  with_test_file("example.xml") do |example, real_filename|
37
37
  a = klass.create!
38
- a.update_attributes(file: example)
38
+ a.update(file: example)
39
39
  expect(a[:file]).to eq "/store/dir/#{real_filename}"
40
40
  end
41
41
  end
@@ -43,7 +43,7 @@ describe "CRUD" do
43
43
  it do
44
44
  with_test_file("example.xml") do |example|
45
45
  a = klass.create!
46
- a.update_attributes(file: example)
46
+ a.update(file: example)
47
47
 
48
48
  example.rewind
49
49
  expect(a.file.read).to eq example.read
@@ -53,7 +53,7 @@ describe "CRUD" do
53
53
  it do
54
54
  with_test_file("example.xml") do |example|
55
55
  a = klass.create!
56
- a.update_attributes(file: example)
56
+ a.update(file: example)
57
57
 
58
58
  expect(a.file.exists?).to be_truthy
59
59
  end
@@ -62,7 +62,7 @@ describe "CRUD" do
62
62
  it do
63
63
  with_test_file("example.xml") do |example, real_filename|
64
64
  a = klass.create!
65
- a.update_attributes(file: example)
65
+ a.update(file: example)
66
66
 
67
67
  expect(a.file.filename).to eq real_filename
68
68
  end
@@ -71,7 +71,7 @@ describe "CRUD" do
71
71
  it do
72
72
  with_test_file("example.xml") do |example, real_filename|
73
73
  a = klass.create!
74
- a.update_attributes(file: example)
74
+ a.update(file: example)
75
75
 
76
76
  expect(a.file.url).to eq "http://domain.com/store/dir/#{real_filename}"
77
77
  expect(a.file.public_url).to eq a.file.url
@@ -137,7 +137,7 @@ describe "CRUD" do
137
137
  it do
138
138
  with_test_file("example.xml") do |example|
139
139
  a = klass.create!
140
- a.update_attributes(file: example)
140
+ a.update(file: example)
141
141
  expect(a.file.exists?).to be_truthy
142
142
  expect(a.destroy).to be_truthy
143
143
 
@@ -150,13 +150,13 @@ describe "CRUD" do
150
150
  it do
151
151
  with_test_file("example.xml") do |example|
152
152
  a = klass.create!
153
- a.update_attributes(file: example)
153
+ a.update(file: example)
154
154
 
155
155
  expect(Saviour::Config.storage.exists?(a[:file])).to be_truthy
156
156
  previous_location = a[:file]
157
157
 
158
158
  with_test_file("camaloon.jpg") do |example_2|
159
- a.update_attributes(file: example_2)
159
+ a.update(file: example_2)
160
160
  expect(Saviour::Config.storage.exists?(a[:file])).to be_truthy
161
161
 
162
162
  expect(Saviour::Config.storage.exists?(previous_location)).to be_falsey
@@ -167,7 +167,7 @@ describe "CRUD" do
167
167
  it "does allow to update the same file to another contents in the same path" do
168
168
  a = klass.create! file: Saviour::StringSource.new("contents", "file.txt")
169
169
 
170
- a.update_attributes! file: Saviour::StringSource.new("foo", "file.txt")
170
+ a.update! file: Saviour::StringSource.new("foo", "file.txt")
171
171
  expect(Saviour::Config.storage.read(a[:file])).to eq "foo"
172
172
  end
173
173
 
@@ -175,7 +175,7 @@ describe "CRUD" do
175
175
  a = klass.create!
176
176
 
177
177
  expect_to_yield_queries(count: 1) do
178
- a.update_attributes! file: Saviour::StringSource.new("foo", "file.txt")
178
+ a.update! file: Saviour::StringSource.new("foo", "file.txt")
179
179
  end
180
180
  end
181
181
 
@@ -183,7 +183,7 @@ describe "CRUD" do
183
183
  a = klass.create!
184
184
 
185
185
  expect_to_yield_queries(count: 2) do
186
- a.update_attributes! name: "Text",
186
+ a.update! name: "Text",
187
187
  file: Saviour::StringSource.new("foo", "file.txt")
188
188
  end
189
189
  end
@@ -192,7 +192,7 @@ describe "CRUD" do
192
192
  it "touches updated_at if the model has it" do
193
193
  time = Time.now - 4.years
194
194
  a = klass.create! updated_at: time
195
- a.update_attributes! file: Saviour::StringSource.new("foo", "file.txt")
195
+ a.update! file: Saviour::StringSource.new("foo", "file.txt")
196
196
 
197
197
  expect(a.updated_at).to be > time + 2.years
198
198
  end
@@ -207,7 +207,7 @@ describe "CRUD" do
207
207
  it "works with models that do not have updated_at" do
208
208
  a = klass.create!
209
209
  expect(a).not_to respond_to(:updated_at)
210
- a.update_attributes! file: Saviour::StringSource.new("foo", "file.txt")
210
+ a.update! file: Saviour::StringSource.new("foo", "file.txt")
211
211
  expect(a.file.read).to eq "foo"
212
212
  end
213
213
  end
@@ -226,7 +226,7 @@ describe "CRUD" do
226
226
 
227
227
  expected_query = %Q{UPDATE "tests" SET "file" = '/store/dir/file.txt', "file_thumb" = '/store/dir/file.txt'}
228
228
  expect_to_yield_queries(count: 1, including: [expected_query]) do
229
- a.update_attributes!(
229
+ a.update!(
230
230
  file: Saviour::StringSource.new("foo", "file.txt"),
231
231
  file_thumb: Saviour::StringSource.new("foo", "file.txt")
232
232
  )
@@ -14,7 +14,7 @@ describe "dirty model" do
14
14
 
15
15
  with_test_file("example.xml") do |xml_file|
16
16
  with_test_file("camaloon.jpg") do |jpg_file|
17
- a.update_attributes! file: xml_file
17
+ a.update! file: xml_file
18
18
 
19
19
  expect(a.changed_attributes).to eq({})
20
20
 
@@ -23,7 +23,7 @@ describe "halt processor behavior" do
23
23
 
24
24
  expect(Saviour::Config.storage).to_not receive(:write)
25
25
 
26
- a.update_attributes! file: StringIO.new("contents")
26
+ a.update! file: StringIO.new("contents")
27
27
  expect(a.reload.read_attribute(:file)).to be_nil
28
28
  end
29
29
 
@@ -9,8 +9,6 @@ describe "memory usage" do
9
9
  a
10
10
  }
11
11
 
12
- CHUNK = ("A" * 1024).freeze
13
-
14
12
  let(:size_to_test) { 10 } # Test with 10Mb files
15
13
 
16
14
  def with_tempfile
@@ -18,7 +16,7 @@ describe "memory usage" do
18
16
 
19
17
  size_to_test.times do
20
18
  1024.times do
21
- f.write CHUNK
19
+ f.write SecureRandom.hex(512)
22
20
  end
23
21
  end
24
22
  f.flush
@@ -56,7 +54,7 @@ describe "memory usage" do
56
54
  with_no_gc do
57
55
  base_line = GetProcessMem.new.mb
58
56
 
59
- a.update_attributes! file: f
57
+ a.update! file: f
60
58
 
61
59
  # Expect memory usage to grow below 10% of the file size
62
60
  expect(GetProcessMem.new.mb - base_line).to be < size_to_test / 10
@@ -72,22 +70,22 @@ describe "memory usage" do
72
70
 
73
71
  process do |contents, filename|
74
72
  digest = Digest::MD5.hexdigest(contents)
73
+
75
74
  [contents, "#{digest}-#{filename}"]
76
75
  end
77
76
  }
78
77
  }
79
78
 
80
79
  it do
81
- a = base_klass.create!
82
-
83
80
  with_tempfile do |f|
84
81
  with_no_gc do
85
- base_line = GetProcessMem.new.mb
82
+ a = base_klass.create!
86
83
 
87
- a.update_attributes! file: f
84
+ base_line = GetProcessMem.new.mb
88
85
 
89
- # Expect memory usage to grow at least the size of the file
90
- expect(GetProcessMem.new.mb - base_line).to be > size_to_test
86
+ a.update! file: f
87
+ # Expect memory usage to grow at least half the file size
88
+ expect(GetProcessMem.new.mb - base_line).to be >= size_to_test / 2
91
89
  end
92
90
  end
93
91
  end
@@ -11,7 +11,7 @@ describe "persisted path" do
11
11
 
12
12
  with_test_file("example.xml") do |example|
13
13
  a = klass.create!
14
- expect(a.update_attributes(file: example)).to be_truthy
14
+ expect(a.update(file: example)).to be_truthy
15
15
  expect(Saviour::Config.storage.exists?(a[:file])).to be_truthy
16
16
  expect(File.dirname(a[:file])).to eq "/store/dir"
17
17
 
@@ -20,7 +20,7 @@ describe "persisted path" do
20
20
 
21
21
  with_test_file("camaloon.jpg") do |example_2|
22
22
  b = klass.create!
23
- expect(b.update_attributes(file: example_2)).to be_truthy
23
+ expect(b.update(file: example_2)).to be_truthy
24
24
 
25
25
  expect(Saviour::Config.storage.exists?(b[:file])).to be_truthy
26
26
  expect(Saviour::Config.storage.exists?(a[:file])).to be_truthy
@@ -88,7 +88,7 @@ describe "processor's API" do
88
88
  a = klass.create!
89
89
 
90
90
  expect {
91
- a.update_attributes! file: Saviour::StringSource.new("contents", "filename.txt")
91
+ a.update! file: Saviour::StringSource.new("contents", "filename.txt")
92
92
  }.to raise_error.with_message("custom problem!")
93
93
  end
94
94
 
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe "direct usage of ReadOnlyFile" do
4
+ before { allow(Saviour::Config).to receive(:storage).and_return(Saviour::LocalStorage.new(local_prefix: @tmpdir, public_url_prefix: "http://domain.com")) }
5
+
6
+ let(:uploader) {
7
+ Class.new(Saviour::BaseUploader) do
8
+ store_dir { "/store/dir" }
9
+ end
10
+ }
11
+
12
+ let(:klass) {
13
+ klass = Class.new(Test) { include Saviour::Model }
14
+ klass.attach_file :file, uploader
15
+ klass
16
+ }
17
+
18
+ it "can be created" do
19
+ a = klass.create! file: Saviour::StringSource.new("contents", "file.txt")
20
+
21
+ path = a[:file]
22
+
23
+ read_only_file = Saviour::ReadOnlyFile.new(path, klass.uploader_classes[:file].storage)
24
+
25
+ expect(read_only_file.read).to eq "contents"
26
+ expect(read_only_file.public_url).to eq "http://domain.com/store/dir/file.txt"
27
+ end
28
+ end
@@ -12,7 +12,7 @@ describe "reload" do
12
12
  b = klass.find(a.id)
13
13
 
14
14
  with_test_file("example.xml") do |example|
15
- a.update_attributes! file: example
15
+ a.update! file: example
16
16
  expect(a.file.exists?).to be_truthy
17
17
  expect(b.file.exists?).to be_falsey
18
18
 
@@ -116,7 +116,7 @@ describe "remove attachment" do
116
116
  expect(Saviour::Config.storage.exists?(path)).to be_truthy
117
117
  expect(Saviour::Config.storage.read(path)).to eq "Some contents"
118
118
 
119
- a.update_attributes!(file: Saviour::StringSource.new("Other contents", "filename.txt"))
119
+ a.update!(file: Saviour::StringSource.new("Other contents", "filename.txt"))
120
120
  expect(a.file.persisted?).to be_truthy
121
121
  expect(a.file.read).to eq "Other contents"
122
122
  expect(Saviour::Config.storage.exists?(path)).to be_truthy
@@ -14,7 +14,7 @@ describe "stash data on process" do
14
14
  end
15
15
 
16
16
  after_upload do |stash|
17
- model.update_attributes!(file_size: stash[:file_size])
17
+ model.update!(file_size: stash[:file_size])
18
18
  end
19
19
  }
20
20
 
@@ -23,7 +23,7 @@ describe "stash data on process" do
23
23
 
24
24
  a = klass.create!
25
25
 
26
- a.update_attributes! file: Saviour::StringSource.new("a" * 74, "file.txt")
26
+ a.update! file: Saviour::StringSource.new("a" * 74, "file.txt")
27
27
  expect(a.file_size).to eq 74
28
28
  end
29
29
 
@@ -38,7 +38,7 @@ describe "stash data on process" do
38
38
  end
39
39
 
40
40
  after_upload do |stash|
41
- model.update_attributes!(file_size: stash[:file_size])
41
+ model.update!(file_size: stash[:file_size])
42
42
  end
43
43
  }
44
44
 
@@ -61,7 +61,7 @@ describe "stash data on process" do
61
61
  end
62
62
 
63
63
  after_upload do |stash|
64
- model.update_attributes!("size_#{attached_as}" => stash[:size])
64
+ model.update!("size_#{attached_as}" => stash[:size])
65
65
  end
66
66
  }
67
67
 
@@ -74,7 +74,7 @@ describe "stash data on process" do
74
74
  # - 2 queries to update size
75
75
  # - 1 query to assign stored paths
76
76
  expect_to_yield_queries(count: 3) do
77
- a.update_attributes! file: Saviour::StringSource.new("a" * 74, "file.txt"),
77
+ a.update! file: Saviour::StringSource.new("a" * 74, "file.txt"),
78
78
  file_thumb: Saviour::StringSource.new("a" * 31, "file_2.txt")
79
79
  end
80
80
 
@@ -99,7 +99,7 @@ describe "stash data on process" do
99
99
  end
100
100
 
101
101
  after_upload do |stash|
102
- model.update_attributes!(stash[:model])
102
+ model.update!(stash[:model])
103
103
  end
104
104
  }
105
105
 
@@ -123,11 +123,11 @@ describe "stash data on process" do
123
123
  end
124
124
 
125
125
  after_upload do |stash|
126
- model.update_attributes!(file_size: stash[:file_size])
126
+ model.update!(file_size: stash[:file_size])
127
127
  end
128
128
 
129
129
  after_upload do |stash|
130
- model.update_attributes!(name: stash[:name])
130
+ model.update!(name: stash[:name])
131
131
  end
132
132
  }
133
133
 
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+
3
+ describe "uploader declaration" do
4
+ let!(:default_storage) do
5
+ Saviour::LocalStorage.new(
6
+ local_prefix: @tmpdir,
7
+ public_url_prefix: "http://domain.com"
8
+ )
9
+ end
10
+
11
+ let!(:another_storage) do
12
+ Saviour::LocalStorage.new(
13
+ local_prefix: @tmpdir,
14
+ public_url_prefix: "http://custom-domain.com"
15
+ )
16
+ end
17
+
18
+ before { allow(Saviour::Config).to receive(:storage).and_return(default_storage) }
19
+
20
+ it "lets you override storage on attachment basis" do
21
+ klass = Class.new(Test) { include Saviour::Model }
22
+ custom_storage = another_storage
23
+
24
+ klass.attach_file(:file) do
25
+ store_dir { "/store/dir" }
26
+ end
27
+
28
+ klass.attach_file(:file_thumb) do
29
+ store_dir { "/store/dir" }
30
+ with_storage custom_storage
31
+ end
32
+
33
+ a = klass.create!(
34
+ file: Saviour::StringSource.new("content", "houhou.txt"),
35
+ file_thumb: Saviour::StringSource.new("content", "custom_houhou.txt")
36
+ )
37
+
38
+ expect(a.file.filename).to eq "houhou.txt"
39
+ expect(a.file.url).to eq 'http://domain.com/store/dir/houhou.txt'
40
+
41
+ expect(a.file_thumb.filename).to eq "custom_houhou.txt"
42
+ expect(a.file_thumb.url).to eq 'http://custom-domain.com/store/dir/custom_houhou.txt'
43
+ end
44
+
45
+ context do
46
+ it "allows for lambda storages" do
47
+ allow(Saviour::Config).to receive(:storage).and_return(-> { default_storage })
48
+
49
+ klass = Class.new(Test) { include Saviour::Model }
50
+
51
+ klass.attach_file(:file) do
52
+ store_dir { "/store/dir" }
53
+ end
54
+
55
+ a = klass.create!(file: Saviour::StringSource.new("content", "houhou.txt"))
56
+
57
+ expect(a.file.filename).to eq "houhou.txt"
58
+ expect(a.file.url).to eq 'http://domain.com/store/dir/houhou.txt'
59
+ end
60
+
61
+ it "allow to change storage on the fly" do
62
+ dynamic_storage = default_storage
63
+ allow(Saviour::Config).to receive(:storage).and_return(-> { dynamic_storage })
64
+
65
+ klass = Class.new(Test) { include Saviour::Model }
66
+
67
+ klass.attach_file(:file) do
68
+ store_dir { "/store/dir" }
69
+ end
70
+
71
+ a = klass.create!(
72
+ file: Saviour::StringSource.new("content", "houhou.txt"),
73
+ file_thumb: Saviour::StringSource.new("content", "custom_houhou.txt")
74
+ )
75
+
76
+ expect(a.file.filename).to eq "houhou.txt"
77
+ expect(a.file.url).to eq 'http://domain.com/store/dir/houhou.txt'
78
+
79
+ dynamic_storage = another_storage # Lambda will pick up the new storage.
80
+
81
+ a = klass.create!(file: Saviour::StringSource.new("content", "houhou.txt"))
82
+
83
+ expect(a.file.filename).to eq "houhou.txt"
84
+ expect(a.file.url).to eq 'http://custom-domain.com/store/dir/houhou.txt'
85
+ end
86
+ end
87
+ end
@@ -87,7 +87,7 @@ describe "transactional behavior" do
87
87
 
88
88
  with_test_file("camaloon.jpg") do |file2|
89
89
  ActiveRecord::Base.transaction do
90
- a.update_attributes! file: file2
90
+ a.update! file: file2
91
91
 
92
92
  expect(Saviour::Config.storage.exists?(path1)).to be_truthy
93
93
  expect(Saviour::Config.storage.exists?(a[:file])).to be_truthy
@@ -107,7 +107,7 @@ describe "transactional behavior" do
107
107
 
108
108
  with_test_file("camaloon.jpg") do |file2|
109
109
  ActiveRecord::Base.transaction do
110
- a.update_attributes! file: file2
110
+ a.update! file: file2
111
111
  path2 = a[:file]
112
112
 
113
113
  expect(Saviour::Config.storage.exists?(path1)).to be_truthy
@@ -135,7 +135,7 @@ describe "transactional behavior" do
135
135
  expect(Saviour::Config.storage.read(a[:file])).to eq "original content"
136
136
 
137
137
  ActiveRecord::Base.transaction do
138
- a.update_attributes! file: Saviour::StringSource.new("new content", "file.txt")
138
+ a.update! file: Saviour::StringSource.new("new content", "file.txt")
139
139
  expect(Saviour::Config.storage.read(a[:file])).to eq "new content"
140
140
  raise ActiveRecord::Rollback
141
141
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saviour
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.6.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roger Campos
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-16 00:00:00.000000000 Z
11
+ date: 2020-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -53,21 +53,21 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: 1.0.5
55
55
  - !ruby/object:Gem::Dependency
56
- name: bundler
56
+ name: concurrent-ruby-edge
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
61
+ version: 0.6.0
62
+ type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: 0.6.0
69
69
  - !ruby/object:Gem::Dependency
70
- name: rspec
70
+ name: bundler
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -81,7 +81,7 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: rake
84
+ name: rspec
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - ">="
@@ -95,7 +95,7 @@ dependencies:
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
- name: sqlite3
98
+ name: rake
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - ">="
@@ -109,7 +109,7 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
- name: appraisal
112
+ name: sqlite3
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - ">="
@@ -173,13 +173,13 @@ extra_rdoc_files: []
173
173
  files:
174
174
  - ".gitignore"
175
175
  - ".travis.yml"
176
- - Appraisals
177
176
  - Gemfile
178
177
  - LICENSE.txt
179
178
  - README.md
180
179
  - Rakefile
181
180
  - gemfiles/5.1.gemfile
182
181
  - gemfiles/5.2.gemfile
182
+ - gemfiles/6.0.gemfile
183
183
  - lib/saviour.rb
184
184
  - lib/saviour/base_uploader.rb
185
185
  - lib/saviour/config.rb
@@ -211,12 +211,13 @@ files:
211
211
  - spec/feature/original_assigned_file_is_not_modified_spec.rb
212
212
  - spec/feature/persisted_path_spec.rb
213
213
  - spec/feature/processors_api_spec.rb
214
+ - spec/feature/read_only_file_spec.rb
214
215
  - spec/feature/reload_model_spec.rb
215
216
  - spec/feature/remove_attachment_spec.rb
216
217
  - spec/feature/reopens_file_at_every_process_spec.rb
217
218
  - spec/feature/rewind_source_before_read_spec.rb
218
219
  - spec/feature/stash_spec.rb
219
- - spec/feature/storage_overriding_spec.rb
220
+ - spec/feature/storages_spec.rb
220
221
  - spec/feature/transactional_behavior_spec.rb
221
222
  - spec/feature/uploader_declaration_spec.rb
222
223
  - spec/feature/validations_spec.rb
@@ -247,15 +248,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
247
248
  requirements:
248
249
  - - ">="
249
250
  - !ruby/object:Gem::Version
250
- version: 2.2.0
251
+ version: 2.5.0
251
252
  required_rubygems_version: !ruby/object:Gem::Requirement
252
253
  requirements:
253
254
  - - ">="
254
255
  - !ruby/object:Gem::Version
255
256
  version: '0'
256
257
  requirements: []
257
- rubyforge_project:
258
- rubygems_version: 2.7.6
258
+ rubygems_version: 3.0.3
259
259
  signing_key:
260
260
  specification_version: 4
261
261
  summary: File storage handler following active record model lifecycle
data/Appraisals DELETED
@@ -1,19 +0,0 @@
1
- appraise "4.0" do
2
- gem "activesupport", "~> 4.0.0"
3
- gem "activerecord", "~> 4.0.0"
4
- end
5
-
6
- appraise "4.1" do
7
- gem "activesupport", "~> 4.1.0"
8
- gem "activerecord", "~> 4.1.0"
9
- end
10
-
11
- appraise "4.2" do
12
- gem "activesupport", "~> 4.2.0"
13
- gem "activerecord", "~> 4.2.0"
14
- end
15
-
16
- appraise "5.0" do
17
- gem "activesupport", "~> 5.0.0"
18
- gem "activerecord", "~> 5.0.0"
19
- end
@@ -1,44 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe "uploader declaration" do
4
- let!(:default_storage) do
5
- Saviour::LocalStorage.new(
6
- local_prefix: @tmpdir,
7
- public_url_prefix: "http://domain.com"
8
- )
9
- end
10
-
11
- let!(:custom_storage) do
12
- Saviour::LocalStorage.new(
13
- local_prefix: @tmpdir,
14
- public_url_prefix: "http://custom-domain.com"
15
- )
16
- end
17
-
18
- before { allow(Saviour::Config).to receive(:storage).and_return(default_storage) }
19
-
20
- it "lets you override storage on attachment basis" do
21
- klass = Class.new(Test) { include Saviour::Model }
22
- kustom_storage = custom_storage
23
-
24
- klass.attach_file(:file) do
25
- store_dir { "/store/dir" }
26
- end
27
-
28
- klass.attach_file(:file_thumb) do
29
- store_dir { "/store/dir" }
30
- with_storage kustom_storage
31
- end
32
-
33
- a = klass.create!(
34
- file: Saviour::StringSource.new("content", "houhou.txt"),
35
- file_thumb: Saviour::StringSource.new("content", "custom_houhou.txt")
36
- )
37
-
38
- expect(a.file.filename).to eq "houhou.txt"
39
- expect(a.file.url).to eq 'http://domain.com/store/dir/houhou.txt'
40
-
41
- expect(a.file_thumb.filename).to eq "custom_houhou.txt"
42
- expect(a.file_thumb.url).to eq 'http://custom-domain.com/store/dir/custom_houhou.txt'
43
- end
44
- end