mongoid 9.0.5 → 9.0.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +9 -9
  3. data/lib/mongoid/association/embedded/embeds_one/proxy.rb +1 -1
  4. data/lib/mongoid/association/referenced/has_many/enumerable.rb +21 -4
  5. data/lib/mongoid/contextual/mongo.rb +1 -1
  6. data/lib/mongoid/validatable/associated.rb +1 -1
  7. data/lib/mongoid/validatable/macros.rb +15 -0
  8. data/lib/mongoid/validatable/numericality.rb +19 -0
  9. data/lib/mongoid/validatable.rb +1 -0
  10. data/lib/mongoid/version.rb +5 -2
  11. data/spec/integration/app_spec.rb +10 -0
  12. data/spec/integration/associations/embeds_one_spec.rb +25 -6
  13. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +28 -37
  14. data/spec/mongoid/association_spec.rb +0 -60
  15. data/spec/mongoid/contextual/mongo_spec.rb +6 -0
  16. data/spec/mongoid/validatable/numericality_spec.rb +16 -0
  17. data/spec/shared/LICENSE +20 -0
  18. data/spec/shared/bin/get-mongodb-download-url +17 -0
  19. data/spec/shared/bin/s3-copy +45 -0
  20. data/spec/shared/bin/s3-upload +69 -0
  21. data/spec/shared/lib/mrss/child_process_helper.rb +80 -0
  22. data/spec/shared/lib/mrss/cluster_config.rb +231 -0
  23. data/spec/shared/lib/mrss/constraints.rb +378 -0
  24. data/spec/shared/lib/mrss/docker_runner.rb +298 -0
  25. data/spec/shared/lib/mrss/eg_config_utils.rb +51 -0
  26. data/spec/shared/lib/mrss/event_subscriber.rb +210 -0
  27. data/spec/shared/lib/mrss/lite_constraints.rb +238 -0
  28. data/spec/shared/lib/mrss/release/candidate.rb +281 -0
  29. data/spec/shared/lib/mrss/release/product_data.rb +144 -0
  30. data/spec/shared/lib/mrss/server_version_registry.rb +113 -0
  31. data/spec/shared/lib/mrss/session_registry.rb +69 -0
  32. data/spec/shared/lib/mrss/session_registry_legacy.rb +60 -0
  33. data/spec/shared/lib/mrss/spec_organizer.rb +179 -0
  34. data/spec/shared/lib/mrss/utils.rb +37 -0
  35. data/spec/shared/lib/tasks/candidate.rake +64 -0
  36. data/spec/shared/share/Dockerfile.erb +251 -0
  37. data/spec/shared/share/haproxy-1.conf +16 -0
  38. data/spec/shared/share/haproxy-2.conf +17 -0
  39. data/spec/shared/shlib/config.sh +27 -0
  40. data/spec/shared/shlib/distro.sh +84 -0
  41. data/spec/shared/shlib/server.sh +423 -0
  42. data/spec/shared/shlib/set_env.sh +110 -0
  43. data/spec/support/expectations.rb +20 -18
  44. metadata +59 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb0499892233035ab6e0b07f3df4d08dfe7129c1b32830288e9453478b8aba9d
4
- data.tar.gz: 5b02bd092e46dfb2aadfa580f3c8b3b4548b82d2e354eba4768ee6c1772eed98
3
+ metadata.gz: 72ad16d0232a1f1c1144801896ec7322b6c452100e02e9bc5c1ba6d21e015e62
4
+ data.tar.gz: 5ca24809ea6498805807e80e78d347a8f2d517862d2ace0c39a73fb81918e178
5
5
  SHA512:
6
- metadata.gz: 96c1585ccba204b4c316920662aa5e750f3e0dd46a7849ca142d8b42d8b94ba8516c16ae9a194a30dd648937b94ec9247371035ddf951d93ec251eeb06fa15cd
7
- data.tar.gz: 057f7cf03935fe694f385f186d74e3468d928f44dac47695ab58319e050ae86c25039083c1bf3debebdf8fdea9025347365b0e341040898cfdf2e09acbf72b31
6
+ metadata.gz: 82a45afa6174672d301145d1910eb6ef4d86433f078fd6514c22d644c101637404f5c2562e497dd3c9576935ce0f267fb3993d3493c520645a85e42799d0601f
7
+ data.tar.gz: d01a7d34d5b01aa5b7629b4dad7735f6c86756e86e4cbe6695301f1fe1864043041ccf4beb785bb28e48b2886d85a2c7dc4d54dddbf644a24f07b9852e15ba17
data/Rakefile CHANGED
@@ -11,16 +11,16 @@ $: << File.join(ROOT, 'spec/shared/lib')
11
11
  require "rake"
12
12
  require "rspec/core/rake_task"
13
13
 
14
- # stands in for the Bundler-provided `build` task, which builds the
15
- # gem for this project. Our release process builds the gems in a
16
- # particular way, in a GitHub action. This task is just to help remind
17
- # developers of that fact.
14
+ if File.exist?('./spec/shared/lib/tasks/candidate.rake')
15
+ load 'spec/shared/lib/tasks/candidate.rake'
16
+ end
17
+
18
+ desc 'Build the gem'
18
19
  task :build do
19
- abort <<~WARNING
20
- `rake build` does nothing in this project. The gem must be built via
21
- the `Mongoid Release` action on GitHub, which is triggered manually when
22
- a new release is ready.
23
- WARNING
20
+ command = %w[ gem build ]
21
+ command << "--output=#{ENV['GEM_FILE_NAME']}" if ENV['GEM_FILE_NAME']
22
+ command << (ENV['GEMSPEC'] || 'mongoid.gemspec')
23
+ system(*command)
24
24
  end
25
25
 
26
26
  # `rake version` is used by the deployment system so get the release version
@@ -36,8 +36,8 @@ module Mongoid
36
36
  bind_one
37
37
  characterize_one(_target)
38
38
  update_attributes_hash(_target)
39
- _base._reset_memoized_descendants!
40
39
  _target.save if persistable?
40
+ _base._reset_memoized_descendants!
41
41
  end
42
42
  end
43
43
 
@@ -419,11 +419,28 @@ module Mongoid
419
419
  #
420
420
  # @return [ Integer ] The size of the enumerable.
421
421
  def size
422
- count = (_unloaded ? _unloaded.count : _loaded.count)
423
- if count.zero?
424
- count + _added.count
422
+ # If _unloaded is present, then it will match the set of documents
423
+ # that belong to this association, which have already been persisted
424
+ # to the database. This set of documents must be considered when
425
+ # computing the size of the association, along with anything that has
426
+ # since been added.
427
+ if _unloaded
428
+ if _added.any?
429
+ # Note that _added may include records that _unloaded already
430
+ # matches. This is the case if the association is assigned an array
431
+ # of items and some of them were already elements of the association.
432
+ #
433
+ # we need to thus make sure _unloaded.count excludes any elements
434
+ # that already exist in _added.
435
+
436
+ count = _unloaded.not(:_id.in => _added.values.map(&:id)).count
437
+ count + _added.values.count
438
+ else
439
+ _unloaded.count
440
+ end
441
+
425
442
  else
426
- count + _added.values.count { |d| d.new_record? }
443
+ _loaded.count + _added.count
427
444
  end
428
445
  end
429
446
 
@@ -1063,7 +1063,7 @@ module Mongoid
1063
1063
  end
1064
1064
 
1065
1065
  def retrieve_nth_to_last_with_limit(n, limit)
1066
- v = view.sort(inverse_sorting).skip(n).limit(limit || 1)
1066
+ v = view.sort(inverse_sorting).limit(limit || 1)
1067
1067
  v = v.skip(n) if n > 0
1068
1068
  raw_docs = v.to_a.reverse
1069
1069
  process_raw_docs(raw_docs, limit)
@@ -74,7 +74,7 @@ module Mongoid
74
74
  # use map.all? instead of just all?, because all? will do short-circuit
75
75
  # evaluation and terminate on the first failed validation.
76
76
  list.map do |value|
77
- if value && !value.flagged_for_destroy?
77
+ if value && !value.flagged_for_destroy? && (!value.persisted? || value.changed?)
78
78
  value.validated? ? true : value.valid?
79
79
  else
80
80
  true
@@ -89,6 +89,21 @@ module Mongoid
89
89
  def validates_presence_of(*args)
90
90
  validates_with(PresenceValidator, _merge_attributes(args))
91
91
  end
92
+
93
+ # Validates whether or not a field contains a numeric value.
94
+ #
95
+ # @example
96
+ # class Person
97
+ # include Mongoid::Document
98
+ # field :cost
99
+ #
100
+ # validates_numericality_of :cost
101
+ # end
102
+ #
103
+ # @param [ Object... ] *args The names of the field(s) to validate.
104
+ def validates_numericality_of(*args)
105
+ validates_with(NumericalityValidator, _merge_attributes(args))
106
+ end
92
107
  end
93
108
  end
94
109
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ module Validatable
5
+ # A specialization of the ActiveModel numericality validator, which adds
6
+ # logic to recognize and accept BSON::Decimal128 as a number.
7
+ class NumericalityValidator < ActiveModel::Validations::NumericalityValidator
8
+ private
9
+
10
+ # Ensure that BSON::Decimal128 is treated as a BigDecimal during the
11
+ # validation step.
12
+ def prepare_value_for_validation(value, record, attr_name)
13
+ result = super
14
+
15
+ result.is_a?(BSON::Decimal128) ? result.to_big_decimal : result
16
+ end
17
+ end
18
+ end
19
+ end
@@ -6,6 +6,7 @@ require "mongoid/validatable/localizable"
6
6
  require "mongoid/validatable/associated"
7
7
  require "mongoid/validatable/format"
8
8
  require "mongoid/validatable/length"
9
+ require "mongoid/validatable/numericality"
9
10
  require "mongoid/validatable/queryable"
10
11
  require "mongoid/validatable/presence"
11
12
  require "mongoid/validatable/uniqueness"
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
- # rubocop:todo all
3
2
 
4
3
  module Mongoid
5
- VERSION = "9.0.5"
4
+ # The current version of Mongoid
5
+ #
6
+ # Note that this file is automatically updated via `rake candidate:create`.
7
+ # Manual changes to this file will be overwritten by that rake task.
8
+ VERSION = '9.0.7'
6
9
  end
@@ -26,6 +26,10 @@ describe 'Mongoid application tests' do
26
26
  skip 'Set APP_TESTS=1 in environment to run application tests'
27
27
  end
28
28
 
29
+ if SpecConfig.instance.rails_version < '7.1'
30
+ skip 'App tests require Rails > 7.0 (see https://stackoverflow.com/questions/79360526)'
31
+ end
32
+
29
33
  require 'fileutils'
30
34
  require 'mrss/child_process_helper'
31
35
  require 'open-uri'
@@ -123,6 +127,12 @@ describe 'Mongoid application tests' do
123
127
  end
124
128
 
125
129
  context 'new application - rails' do
130
+ before(:all) do
131
+ if SpecConfig.instance.rails_version < '7.1'
132
+ skip '`rails new` with rails < 7.1 fails because modern concurrent-ruby removed logger dependency'
133
+ end
134
+ end
135
+
126
136
  it 'creates' do
127
137
  prepare_new_rails_app 'mongoid-test' do
128
138
  check_call(%w(rails g model post), env: clean_env)
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
- # rubocop:todo all
3
2
 
4
3
  require 'spec_helper'
5
4
 
6
5
  describe 'embeds_one associations' do
7
-
8
- context 're-associating the same object' do
6
+ context 'when re-associating the same object' do
9
7
  context 'with dependent: destroy' do
10
8
  let(:canvas) do
11
9
  Canvas.create!(palette: Palette.new)
@@ -17,7 +15,7 @@ describe 'embeds_one associations' do
17
15
  canvas.palette = canvas.palette
18
16
  canvas.save!
19
17
  canvas.reload
20
- canvas.palette.should == palette
18
+ expect(canvas.palette).to eq palette
21
19
  end
22
20
  end
23
21
  end
@@ -31,12 +29,33 @@ describe 'embeds_one associations' do
31
29
  end
32
30
 
33
31
  it 'loads the association correctly' do
34
- expect { klass }.to_not raise_error
35
- expect { klass.new.address }.to_not raise_error
32
+ expect { klass }.not_to raise_error
33
+ expect { klass.new.address }.not_to raise_error
36
34
  instance = klass.new
37
35
  address = Address.new
38
36
  instance.address = address
39
37
  expect(instance.address).to eq address
40
38
  end
41
39
  end
40
+
41
+ context 'when parent is persisted' do
42
+ let!(:person) do
43
+ Person.create!
44
+ end
45
+
46
+ context 'when assigning the new child' do
47
+ context 'when assigning an attribute to the child' do
48
+ before do
49
+ # person.reload
50
+ person.name = Name.new
51
+ person.name.first_name = 'Dmitry'
52
+ person.save!
53
+ end
54
+
55
+ it 'persists the child' do
56
+ expect(person.reload.name.first_name).to eq 'Dmitry'
57
+ end
58
+ end
59
+ end
60
+ end
42
61
  end
@@ -1756,43 +1756,6 @@ describe Mongoid::Association::Referenced::HasAndBelongsToMany::Proxy do
1756
1756
  end
1757
1757
  end
1758
1758
 
1759
- describe "#any?" do
1760
-
1761
- let(:person) do
1762
- Person.create!
1763
- end
1764
-
1765
- context "when nothing exists on the relation" do
1766
-
1767
- context "when no document is added" do
1768
-
1769
- let!(:sandwich) do
1770
- Sandwich.create!
1771
- end
1772
-
1773
- it "returns false" do
1774
- expect(sandwich.meats.any?).to be false
1775
- end
1776
- end
1777
-
1778
- context "when the document is destroyed" do
1779
-
1780
- before do
1781
- Meat.create!
1782
- end
1783
-
1784
- let!(:sandwich) do
1785
- Sandwich.create!
1786
- end
1787
-
1788
- it "returns false" do
1789
- sandwich.destroy
1790
- expect(sandwich.meats.any?).to be false
1791
- end
1792
- end
1793
- end
1794
- end
1795
-
1796
1759
  context "when documents have been persisted" do
1797
1760
 
1798
1761
  let!(:preference) do
@@ -3041,6 +3004,34 @@ describe Mongoid::Association::Referenced::HasAndBelongsToMany::Proxy do
3041
3004
  end
3042
3005
  end
3043
3006
 
3007
+ # MONGOID-5844
3008
+ #
3009
+ # Specifically, this tests the case where the association is
3010
+ # initialized with a single element (so that Proxy#push does not take
3011
+ # the `concat` route), which causes `reset_unloaded` to be called, which
3012
+ # sets the `_unloaded` Criteria object to match only the specific element
3013
+ # that was given.
3014
+ #
3015
+ # The issue now is that when the events list is updated to be both events,
3016
+ # _unloaded matches one of them already, and the other has previously been
3017
+ # persisted so `new_record?` won't match it. We need to make sure the
3018
+ # `#size` logic properly accounts for this case.
3019
+ context 'when documents have been previously persisted' do
3020
+ let(:person1) { Person.create! }
3021
+ let(:person2) { Person.create! }
3022
+ let(:event1) { Event.create!(administrators: [ person1 ]) }
3023
+ let(:event2) { Event.create!(administrators: [ person2 ]) }
3024
+
3025
+ before do
3026
+ person1.administrated_events = [ event1, event2 ]
3027
+ end
3028
+
3029
+ it 'returns the number of associated documents [MONGOID-5844]' do
3030
+ expect(person1.administrated_events.to_a.size).to eq(2)
3031
+ expect(person1.administrated_events.size).to eq(2)
3032
+ end
3033
+ end
3034
+
3044
3035
  context "when documents have not been persisted" do
3045
3036
 
3046
3037
  before do
@@ -115,66 +115,6 @@ describe Mongoid::Association do
115
115
  expect(name).to_not be_an_embedded_many
116
116
  end
117
117
  end
118
-
119
- context "when validation depends on association" do
120
- before(:all) do
121
- class Author
122
- include Mongoid::Document
123
- embeds_many :books, cascade_callbacks: true
124
- field :condition, type: Boolean
125
- end
126
-
127
- class Book
128
- include Mongoid::Document
129
- embedded_in :author
130
- validate :parent_condition_is_not_true
131
-
132
- def parent_condition_is_not_true
133
- return unless author&.condition
134
- errors.add :base, "Author condition is true."
135
- end
136
- end
137
-
138
- Author.delete_all
139
- Book.delete_all
140
- end
141
-
142
- let(:author) { Author.new }
143
- let(:book) { Book.new }
144
-
145
- context "when author is not persisted" do
146
- it "is valid without books" do
147
- expect(author.valid?).to be true
148
- end
149
-
150
- it "is valid with a book" do
151
- author.books << book
152
- expect(author.valid?).to be true
153
- end
154
-
155
- it "is not valid when condition is true with a book" do
156
- author.condition = true
157
- author.books << book
158
- expect(author.valid?).to be false
159
- end
160
- end
161
-
162
- context "when author is persisted" do
163
- before do
164
- author.books << book
165
- author.save
166
- end
167
-
168
- it "remains valid initially" do
169
- expect(author.valid?).to be true
170
- end
171
-
172
- it "becomes invalid when condition is set to true" do
173
- author.update_attributes(condition: true)
174
- expect(author.valid?).to be false
175
- end
176
- end
177
- end
178
118
  end
179
119
 
180
120
  describe "#embedded_one?" do
@@ -3282,6 +3282,12 @@ describe Mongoid::Contextual::Mongo do
3282
3282
  it "limits the results" do
3283
3283
  expect(context.skip(1).entries).to eq([ new_order ])
3284
3284
  end
3285
+
3286
+ context "with #last" do
3287
+ it "returns the nth from last element" do
3288
+ expect(context.skip(1).last).to eq(depeche_mode)
3289
+ end
3290
+ end
3285
3291
  end
3286
3292
 
3287
3293
  describe "#sort" do
@@ -29,5 +29,21 @@ describe ActiveModel::Validations::NumericalityValidator do
29
29
  expect(model).to_not be_valid
30
30
  end
31
31
  end
32
+
33
+ context 'when the value is numeric' do
34
+ let(:model) { TestModel.new(amount: '15.0') }
35
+
36
+ it 'returns true' do
37
+ expect(model).to be_valid
38
+ end
39
+ end
40
+
41
+ context 'when the value is a BSON::Decimal128' do
42
+ let(:model) { TestModel.new(amount: BSON::Decimal128.new('15.0')) }
43
+
44
+ it 'returns true' do
45
+ expect(model).to be_valid
46
+ end
47
+ end
32
48
  end
33
49
  end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2020 MongoDB, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ desired_version, arch = ARGV
4
+ if arch.nil?
5
+ STDERR.puts "Usage: get-mongodb-download-url desired-version arch"
6
+ exit 1
7
+ end
8
+
9
+ $: << File.join(File.dirname(__FILE__), '../lib')
10
+ require 'mrss/server_version_registry'
11
+
12
+ begin
13
+ puts Mrss::ServerVersionRegistry.new(desired_version, arch).download_url
14
+ rescue Mrss::ServerVersionRegistry::Error => exc
15
+ STDERR.puts "Error: #{exc}"
16
+ exit 2
17
+ end
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'aws-sdk-s3'
5
+
6
+ options = {}
7
+ OptionParser.new do |opts|
8
+ opts.banner = "Usage: s3-copy options"
9
+
10
+ opts.on("-r", "--region=REGION", "AWS region to use (default us-east-1)") do |v|
11
+ options[:region] = v
12
+ end
13
+
14
+ opts.on("-p", "--param=KEY=VALUE", "Specify parameter for new files") do |v|
15
+ options[:params] ||= {}
16
+ k, v = v.split('=', 2)
17
+ options[:params][k.to_sym] = v
18
+ end
19
+
20
+ opts.on("-f", "--from=BUCKET:PATH", "Bucket name and key (or path) to copy from") do |v|
21
+ options[:from] = v
22
+ end
23
+
24
+ opts.on("-t", "--to=BUCKET:PATH", "Bucket name and key (or path) to write to (may be specified more than once)") do |v|
25
+ options[:to] ||= []
26
+ options[:to] << v
27
+ end
28
+ end.parse!
29
+
30
+ ENV['AWS_REGION'] ||= options[:region] || 'us-east-1'
31
+
32
+ bucket, key = options.fetch(:from).split(':', 2)
33
+
34
+ s3 = Aws::S3::Client.new
35
+
36
+ options.fetch(:to).each do |dest|
37
+ STDERR.puts "Copying to #{dest}"
38
+ dbucket, dkey = dest.split(':', 2)
39
+ s3.copy_object(
40
+ bucket: dbucket,
41
+ key: dkey,
42
+ copy_source: "/#{bucket}/#{key}",
43
+ **options[:params] || {},
44
+ )
45
+ end
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'aws-sdk-s3'
5
+
6
+ options = {}
7
+ OptionParser.new do |opts|
8
+ opts.banner = "Usage: s3-upload options"
9
+
10
+ opts.on("-r", "--region=REGION", "AWS region to use (default us-east-1)") do |v|
11
+ options[:region] = v
12
+ end
13
+
14
+ opts.on("-p", "--param=KEY=VALUE", "Specify parameter for S3 upload") do |v|
15
+ options[:params] ||= {}
16
+ k, v = v.split('=', 2)
17
+ options[:params][k.to_sym] = v
18
+ end
19
+
20
+ opts.on("-f", "--file=PATH", "Path to the file to upload, - to upload standard input") do |v|
21
+ options[:file] = v
22
+ end
23
+
24
+ opts.on("-w", "--write=BUCKET:PATH", "Bucket name and key (or path) to upload to") do |v|
25
+ options[:write] = v
26
+ end
27
+
28
+ opts.on("-c", "--copy=BUCKET:PATH", "Bucket name and key (or path) to copy to (may be specified more than once)") do |v|
29
+ options[:copy] ||= []
30
+ options[:copy] << v
31
+ end
32
+ end.parse!
33
+
34
+ ENV['AWS_REGION'] ||= options[:region] || 'us-east-1'
35
+
36
+ def upload(f, options)
37
+ s3 = Aws::S3::Client.new
38
+ write = options.fetch(:write)
39
+ STDERR.puts "Writing #{write}"
40
+ bucket, key = write.split(':', 2)
41
+ s3.put_object(
42
+ body: f.read,
43
+ bucket: bucket,
44
+ key: key,
45
+ **options[:params] || {},
46
+ )
47
+ if copy = options[:copy]
48
+ copy.each do |dest|
49
+ STDERR.puts "Copying to #{dest}"
50
+ dbucket, dkey = dest.split(':', 2)
51
+ s3.copy_object(
52
+ bucket: dbucket,
53
+ key: dkey,
54
+ copy_source: "/#{bucket}/#{key}",
55
+ **options[:params] || {},
56
+ )
57
+ end
58
+ end
59
+ end
60
+
61
+ if options[:file] == '-'
62
+ upload(STDIN, options)
63
+ elsif options[:file]
64
+ File.open(options[:file]) do |f|
65
+ upload(f, options)
66
+ end
67
+ else
68
+ upload(STDIN, options)
69
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ autoload :ChildProcess, 'childprocess'
5
+ autoload :Tempfile, 'tempfile'
6
+
7
+ module Mrss
8
+ module ChildProcessHelper
9
+ class SpawnError < StandardError; end
10
+
11
+ module_function def call(cmd, env: nil, cwd: nil)
12
+ process = ChildProcess.new(*cmd)
13
+ process.io.inherit!
14
+ if cwd
15
+ process.cwd = cwd
16
+ end
17
+ if env
18
+ env.each do |k, v|
19
+ process.environment[k.to_s] = v
20
+ end
21
+ end
22
+ process.start
23
+ process.wait
24
+ process
25
+ end
26
+
27
+ module_function def check_call(cmd, env: nil, cwd: nil)
28
+ process = call(cmd, env: env, cwd: cwd)
29
+ unless process.exit_code == 0
30
+ raise SpawnError, "Failed to execute: #{cmd}"
31
+ end
32
+ end
33
+
34
+ module_function def get_output(cmd, env: nil, cwd: nil)
35
+ process = ChildProcess.new(*cmd)
36
+ process.io.inherit!
37
+ if cwd
38
+ process.cwd = cwd
39
+ end
40
+ if env
41
+ env.each do |k, v|
42
+ process.environment[k.to_s] = v
43
+ end
44
+ end
45
+
46
+ output = ''
47
+ r, w = IO.pipe
48
+
49
+ begin
50
+ process.io.stdout = w
51
+ process.start
52
+ w.close
53
+
54
+ thread = Thread.new do
55
+ begin
56
+ loop do
57
+ output << r.readpartial(16384)
58
+ end
59
+ rescue EOFError
60
+ end
61
+ end
62
+
63
+ process.wait
64
+ thread.join
65
+ ensure
66
+ r.close
67
+ end
68
+
69
+ [process, output]
70
+ end
71
+
72
+ module_function def check_output(*args)
73
+ process, output = get_output(*args)
74
+ unless process.exit_code == 0
75
+ raise SpawnError,"Failed to execute: #{args}"
76
+ end
77
+ output
78
+ end
79
+ end
80
+ end