0
0
Fork 0

Add Ruby 3.0 support (#16046)

* Fix issues with POSIX::Spawn, Terrapin and Ruby 3.0

Also improve the Terrapin monkey-patch for the stderr/stdout issue.

* Fix keyword argument handling throughout the codebase

* Monkey-patch Paperclip to fix keyword arguments handling in validators

* Change validation_extensions to please CodeClimate

* Bump microformats from 4.2.1 to 4.3.1

* Allow Ruby 3.0

* Add Ruby 3.0 test target to CircleCI

* Add test for admin dashboard warnings

* Fix admin dashboard warnings on Ruby 3.0
This commit is contained in:
Claire 2021-05-06 14:22:54 +02:00 committed by GitHub
parent 0a3fa034fc
commit 566fc90913
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 178 additions and 75 deletions

View file

@ -0,0 +1,58 @@
# frozen_string_literal: true
# Monkey-patch various Paperclip validators for Ruby 3.0 compatibility
module Paperclip
module Validators
module AttachmentSizeValidatorExtensions
def validate_each(record, attr_name, _value)
base_attr_name = attr_name
attr_name = "#{attr_name}_file_size".to_sym
value = record.send(:read_attribute_for_validation, attr_name)
if value.present?
options.slice(*Paperclip::Validators::AttachmentSizeValidator::AVAILABLE_CHECKS).each do |option, option_value|
option_value = option_value.call(record) if option_value.is_a?(Proc)
option_value = extract_option_value(option, option_value)
next if value.send(Paperclip::Validators::AttachmentSizeValidator::CHECKS[option], option_value)
error_message_key = options[:in] ? :in_between : option
[attr_name, base_attr_name].each do |error_attr_name|
record.errors.add(error_attr_name, error_message_key, **filtered_options(value).merge(
min: min_value_in_human_size(record),
max: max_value_in_human_size(record),
count: human_size(option_value)
))
end
end
end
end
end
module AttachmentContentTypeValidatorExtensions
def mark_invalid(record, attribute, types)
record.errors.add attribute, :invalid, **options.merge({ types: types.join(', ') })
end
end
module AttachmentPresenceValidatorExtensions
def validate_each(record, attribute, _value)
if record.send("#{attribute}_file_name").blank?
record.errors.add(attribute, :blank, **options)
end
end
end
module AttachmentFileNameValidatorExtensions
def mark_invalid(record, attribute, patterns)
record.errors.add attribute, :invalid, options.merge({ names: patterns.join(', ') })
end
end
end
end
Paperclip::Validators::AttachmentSizeValidator.prepend(Paperclip::Validators::AttachmentSizeValidatorExtensions)
Paperclip::Validators::AttachmentContentTypeValidator.prepend(Paperclip::Validators::AttachmentContentTypeValidatorExtensions)
Paperclip::Validators::AttachmentPresenceValidator.prepend(Paperclip::Validators::AttachmentPresenceValidatorExtensions)
Paperclip::Validators::AttachmentFileNameValidator.prepend(Paperclip::Validators::AttachmentFileNameValidatorExtensions)

View file

@ -1,61 +1,64 @@
# frozen_string_literal: false
# Fix adapted from https://github.com/thoughtbot/terrapin/pull/5
require 'fcntl'
module Terrapin
module MultiPipeExtensions
def initialize
@stdout_in, @stdout_out = IO.pipe
@stderr_in, @stderr_out = IO.pipe
clear_nonblocking_flags!
end
def pipe_options
# Add some flags to explicitly close the other end of the pipes
{ out: @stdout_out, err: @stderr_out, @stdout_in => :close, @stderr_in => :close }
end
def read
read_streams(@stdout_in, @stderr_in)
end
# While we are patching Terrapin, fix child process potentially getting stuck on writing
# to stderr.
def close_read
begin
@stdout_in.close
rescue IOError
# Do nothing
end
@stdout_output = +''
@stderr_output = +''
begin
@stderr_in.close
rescue IOError
# Do nothing
fds_to_read = [@stdout_in, @stderr_in]
until fds_to_read.empty?
rs, = IO.select(fds_to_read)
read_nonblocking!(@stdout_in, @stdout_output, fds_to_read) if rs.include?(@stdout_in)
read_nonblocking!(@stderr_in, @stderr_output, fds_to_read) if rs.include?(@stderr_in)
end
end
def read_streams(output, error)
@stdout_output = ''
@stderr_output = ''
private
read_fds = [output, error]
until read_fds.empty?
to_read, = IO.select(read_fds)
if to_read.include?(output)
@stdout_output << read_stream(output)
read_fds.delete(output) if output.closed?
end
if to_read.include?(error)
@stderr_output << read_stream(error)
read_fds.delete(error) if error.closed?
end
# @param [IO] io IO Stream to read until there is nothing to read
# @param [String] result Mutable string to which read values will be appended to
# @param [Array<IO>] fds_to_read Mutable array from which `io` should be removed on EOF
def read_nonblocking!(io, result, fds_to_read)
while (partial_result = io.read_nonblock(8192))
result << partial_result
end
rescue IO::WaitReadable
# Do nothing
rescue EOFError
fds_to_read.delete(io)
end
def read_stream(io)
result = ''
def clear_nonblocking_flags!
# Ruby 3.0 sets pipes to non-blocking mode, and resets the flags as
# needed when calling fork/exec-related syscalls, but posix-spawn does
# not currently do that, so we need to do it manually for the time being
# so that the child process do not error out when the buffers are full.
stdout_flags = @stdout_out.fcntl(Fcntl::F_GETFL)
@stdout_out.fcntl(Fcntl::F_SETFL, stdout_flags & ~Fcntl::O_NONBLOCK) if stdout_flags & Fcntl::O_NONBLOCK
begin
while (partial_result = io.read_nonblock(8192))
result << partial_result
end
rescue EOFError, Errno::EPIPE
io.close
rescue Errno::EINTR, Errno::EWOULDBLOCK, Errno::EAGAIN
# Do nothing
end
result
stderr_flags = @stderr_out.fcntl(Fcntl::F_GETFL)
@stderr_out.fcntl(Fcntl::F_SETFL, stderr_flags & ~Fcntl::O_NONBLOCK) if stderr_flags & Fcntl::O_NONBLOCK
rescue NameError, NotImplementedError, Errno::EINVAL
# Probably on windows, where pipes are blocking by default
end
end
end