Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Doesn't work in Rails 4.2: LoadError: cannot load such file -- active_support/duration/iso8601_serializer #2

Open
Nowaker opened this issue Nov 6, 2020 · 1 comment

Comments

@Nowaker
Copy link

Nowaker commented Nov 6, 2020

Fails during bundling phase:

	 9: from /Users/nowaker/.rvm/gems/ruby-2.6.3/gems/rack_attack_admin-0.1.2/lib/rack_attack_admin.rb:3:in `<top (required)>'
	 8: from /Users/nowaker/.rvm/gems/ruby-2.6.3/gems/activesupport-4.2.11.1/lib/active_support/dependencies.rb:274:in `require'
	 7: from /Users/nowaker/.rvm/gems/ruby-2.6.3/gems/activesupport-4.2.11.1/lib/active_support/dependencies.rb:240:in `load_dependency'
	 6: from /Users/nowaker/.rvm/gems/ruby-2.6.3/gems/activesupport-4.2.11.1/lib/active_support/dependencies.rb:274:in `block in require'
	 5: from /Users/nowaker/.rvm/gems/ruby-2.6.3/gems/activesupport-4.2.11.1/lib/active_support/dependencies.rb:274:in `require'
	 4: from /Users/nowaker/.rvm/gems/ruby-2.6.3/gems/activesupport-duration-human_string-0.1.1/lib/active_support/duration/human_string.rb:2:in `<top (required)>'
	 3: from /Users/nowaker/.rvm/gems/ruby-2.6.3/gems/activesupport-4.2.11.1/lib/active_support/dependencies.rb:274:in `require'
	 2: from /Users/nowaker/.rvm/gems/ruby-2.6.3/gems/activesupport-4.2.11.1/lib/active_support/dependencies.rb:240:in `load_dependency'
	 1: from /Users/nowaker/.rvm/gems/ruby-2.6.3/gems/activesupport-4.2.11.1/lib/active_support/dependencies.rb:274:in `block in require'
/Users/nowaker/.rvm/gems/ruby-2.6.3/gems/activesupport-4.2.11.1/lib/active_support/dependencies.rb:274:in `require': cannot load such file -- active_support/duration/iso8601_serializer (LoadError)

As per https://apidock.com/rails/ActiveSupport/Duration/ISO8601Serializer, this class was introduced in Rails 5. The gemspec itself depends on Rails >= 4.2.

@Nowaker
Copy link
Author

Nowaker commented Nov 7, 2020

Monkey patches to make the gem work in Rails 4.2:

config.ru:

require_relative 'config/lib/rails5_iso8601_serializer.rb'

config/initializers/rack_attack_admin.rb:

require 'active_support/duration'
class ActiveSupport::Duration
  # Copied from https://github.com/rails/rails/blob/dcd36ffe1acf922429bf185206749693c5df5f8f/activesupport/lib/active_support/duration.rb#L108
  SECONDS_PER_MINUTE = 60
  SECONDS_PER_HOUR   = 3600
  SECONDS_PER_DAY    = 86400
  SECONDS_PER_WEEK   = 604800
  SECONDS_PER_MONTH  = 2629746  # 1/12 of a gregorian year
  SECONDS_PER_YEAR   = 31556952 # length of a gregorian year (365.2425 days)

  PARTS_IN_SECONDS = {
    seconds: 1,
    minutes: SECONDS_PER_MINUTE,
    hours:   SECONDS_PER_HOUR,
    days:    SECONDS_PER_DAY,
    weeks:   SECONDS_PER_WEEK,
    months:  SECONDS_PER_MONTH,
    years:   SECONDS_PER_YEAR
  }.freeze

  PARTS = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].freeze  

  # Copied from https://github.com/rails/rails/blob/dcd36ffe1acf922429bf185206749693c5df5f8f/activesupport/lib/active_support/duration.rb#L183
  def self.build(value)
    unless value.is_a?(::Numeric)
      raise TypeError, "can't build an #{self.name} from a #{value.class.name}"
    end

    parts = {}
    remainder = value.round(9)

    PARTS.each do |part|
      unless part == :seconds
        part_in_seconds = PARTS_IN_SECONDS[part]
        parts[part] = remainder.div(part_in_seconds)
        remainder %= part_in_seconds
      end
    end unless value == 0

    parts[:seconds] = remainder

    new(value, parts)
  end
end

config/lib/rails5_iso8601_serializer.rb:

# Pretend active_support/duration/iso8601_serializer.rb is loaded
$LOADED_FEATURES << 'active_support/duration/iso8601_serializer.rb'

# Copied from: https://github.com/rails/rails/blob/726f86358d5c2bdf4317b9f8f08b30a45fd326a2/activesupport/lib/active_support/duration/iso8601_serializer.rb

# frozen_string_literal: true

require "active_support/core_ext/object/blank"

module ActiveSupport
  class Duration
    # Serializes duration to string according to ISO 8601 Duration format.
    class ISO8601Serializer # :nodoc:
      DATE_COMPONENTS = %i(years months days)

      def initialize(duration, precision: nil)
        @duration = duration
        @precision = precision
      end

      # Builds and returns output string.
      def serialize
        parts = normalize
        return "PT0S" if parts.empty?

        output = +"P"
        output << "#{parts[:years]}Y"   if parts.key?(:years)
        output << "#{parts[:months]}M"  if parts.key?(:months)
        output << "#{parts[:days]}D"    if parts.key?(:days)
        output << "#{parts[:weeks]}W"   if parts.key?(:weeks)
        time = +""
        time << "#{parts[:hours]}H"     if parts.key?(:hours)
        time << "#{parts[:minutes]}M"   if parts.key?(:minutes)
        if parts.key?(:seconds)
          time << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', parts[:seconds])}S"
        end
        output << "T#{time}" unless time.empty?
        output
      end

      private
        # Return pair of duration's parts and whole duration sign.
        # Parts are summarized (as they can become repetitive due to addition, etc).
        # Zero parts are removed as not significant.
        # If all parts are negative it will negate all of them and return minus as a sign.
        def normalize
          parts = @duration.parts.each_with_object(Hash.new(0)) do |(k, v), p|
            p[k] += v  unless v.zero?
          end

          # Convert weeks to days and remove weeks if mixed with date parts
          if week_mixed_with_date?(parts)
            parts[:days] += parts.delete(:weeks) * SECONDS_PER_WEEK / SECONDS_PER_DAY
          end

          parts
        end

        def week_mixed_with_date?(parts)
          parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
        end
    end
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant