class ActiveShipping::ShipmentPacker

Constants

EXCESS_PACKAGE_QUANTITY_THRESHOLD

Public Class Methods

pack(items, dimensions, maximum_weight, currency) click to toggle source
# File lib/active_shipping/shipment_packer.rb, line 16
def pack(items, dimensions, maximum_weight, currency)
  return [] if items.empty?
  packages = []
  items.map!(&:symbolize_keys)

  # Naive in that it assumes weight is equally distributed across all items
  # Should raise early enough in most cases
  validate_total_weight(items, maximum_weight)
  items_to_pack = items.map(&:dup).sort_by! { |i| i[:grams].to_i }

  state = :package_empty
  while state != :packing_finished
    case state
    when :package_empty
      package_weight, package_value = 0, 0
      state = :filling_package
    when :filling_package
      validate_package_quantity(packages.count)

      items_to_pack.each do |item|
        quantity = determine_fillable_quantity_for_package(item, maximum_weight, package_weight)
        package_weight += item_weight(quantity, item[:grams])
        package_value += item_value(quantity, item[:price])
        item[:quantity] = item[:quantity].to_i - quantity
      end

      items_to_pack.reject! { |i| i[:quantity].to_i == 0 }
      state = :package_full
    when :package_full
      packages << ActiveShipping::Package.new(package_weight, dimensions, value: package_value, currency: currency)
      state = items_to_pack.any? ? :package_empty : :packing_finished
    end
  end

  packages
end

Private Class Methods

determine_fillable_quantity_for_package(item, maximum_weight, package_weight) click to toggle source
# File lib/active_shipping/shipment_packer.rb, line 84
def determine_fillable_quantity_for_package(item, maximum_weight, package_weight)
  item_grams = item[:grams].to_i
  item_quantity = item[:quantity].to_i

  if item_grams <= 0
    item_quantity
  else
    # Grab the max amount of this item we can fit into this package
    # Or, if there are fewer than the max for this item, put
    # what is left into this package
    available_grams = (maximum_weight - package_weight).to_i
    [available_grams / item_grams, item_quantity].min
  end
end
item_value(quantity, price) click to toggle source
# File lib/active_shipping/shipment_packer.rb, line 103
def item_value(quantity, price)
  quantity * Package.cents_from(price)
end
item_weight(quantity, grams) click to toggle source
# File lib/active_shipping/shipment_packer.rb, line 99
def item_weight(quantity, grams)
  quantity * grams.to_i
end
maybe_excess_package_quantity?(total_weight, maximum_weight) click to toggle source
# File lib/active_shipping/shipment_packer.rb, line 80
def maybe_excess_package_quantity?(total_weight, maximum_weight)
  total_weight > (maximum_weight * EXCESS_PACKAGE_QUANTITY_THRESHOLD)
end
overweight_item?(grams, maximum_weight) click to toggle source
# File lib/active_shipping/shipment_packer.rb, line 76
def overweight_item?(grams, maximum_weight)
  grams.to_i > maximum_weight
end
raise_excess_quantity_error() click to toggle source
# File lib/active_shipping/shipment_packer.rb, line 72
def raise_excess_quantity_error
  raise ExcessPackageQuantity, "Unable to pack more than #{EXCESS_PACKAGE_QUANTITY_THRESHOLD} packages"
end
validate_package_quantity(number_of_packages) click to toggle source
# File lib/active_shipping/shipment_packer.rb, line 68
def validate_package_quantity(number_of_packages)
  raise_excess_quantity_error if number_of_packages >= EXCESS_PACKAGE_QUANTITY_THRESHOLD
end
validate_total_weight(items, maximum_weight) click to toggle source
# File lib/active_shipping/shipment_packer.rb, line 55
def validate_total_weight(items, maximum_weight)
  total_weight = 0
  items.each do |item|
    total_weight += item[:quantity].to_i * item[:grams].to_i

    if overweight_item?(item[:grams], maximum_weight)
      raise OverweightItem, "The item with weight of #{item[:grams]}g is heavier than the allowable package weight of #{maximum_weight}g"
    end

    raise_excess_quantity_error if maybe_excess_package_quantity?(total_weight, maximum_weight)
  end
end