physical 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f89e02fd4f91f6d5c79999264b3ff44fb8d60b7b
4
- data.tar.gz: d71b79a2cc53f21c8e1a2d42fc62a29c823e6ec9
3
+ metadata.gz: fdda01f27d8c75a1b222e13ca9f84e54e1f2a4c0
4
+ data.tar.gz: ee3cdd019c063a04141e29a6a1e83f2d25d3b483
5
5
  SHA512:
6
- metadata.gz: 3dcd6833ee6c653af7e7a8aca7f9ccf535e5725ef5dd31b41f90572f99b3f470457caf7bdadcf85495c3523f85a610cfc8ffdbf62a61ac039792bac8a64848dc
7
- data.tar.gz: c2dff46cb937bc709f4df6f8b06551c3112bd34198086033fdc3df12b593755b4f7e3bf66b2c9948231cfd4681cc6076eccb5edcdcb2d3eeeceb023eb14c66f2
6
+ metadata.gz: 48f2da61f204e7ebb71339f5be3c0379928121ce1a94af7e2cc84179e49331789d8316a85fe66c3d1f45557a1089595df1a4ee990634abd4dc30e7f27303edc4
7
+ data.tar.gz: a3170e37c25c1d677bb2028d31bdc8905c4418b3fdb9ff49cf81c9deb7a73faa4e87463ce4b4cdc3a3fb45a42fe98a4dcd6a27bdc1e3245fe74e1f5ed820bbfd
data/README.md CHANGED
@@ -34,42 +34,108 @@ Or install it yourself as:
34
34
 
35
35
  ## Usage
36
36
 
37
+ ### Basic Package
38
+
39
+ A basic Package has no items and is simply assigned a weight and dimensions. Weights and Dimensions have to be given as `Measured::Weight` or as an Array of `Measured::Length` objects:
40
+
37
41
  ```ruby
38
- one_sku = Physical::Item.new(
42
+ Physical::Package.new(
43
+ weight: Measured::Weight(1, :pound),
44
+ dimensions: [
45
+ Measured::Length(3, :inch),
46
+ Measured::Length(4, :inch),
47
+ Measured::Length(5, :inch)
48
+ ]
49
+ )
50
+ ```
51
+ You will be able to retrieve the package's weight and dimensions using the `#weight` and `#dimensions` attribute reader methods.
52
+
53
+ ### Convenience methods
54
+
55
+ The length, width and height of a package are defined as the package's longest, middle, and shortest side, respectively. For the package from the previous example, `package.length` will be 5 inches, `package.width` will be 4 inches, and `package.height` will be 3 inches. Packages do not have a notion of "right side up" yet.
56
+
57
+ The package's `length` is also accessible as `package.x`. The package's `width` is also accessible as `package.y`. The package's `height` is also accessible as `package.z` as well as `package.depth`.
58
+
59
+ ### Packages with Items
60
+
61
+ The following example is a somewhat more elaborate package: We know the items inside! `Physical::Item` objects are Cuboids, so they have three dimensions and a weight. They also have a `properties` hash that can hold things like any hazardous properties that might impede shipping.
62
+
63
+
64
+ ```ruby
65
+ sku_one = Physical::Item.new(
39
66
  id: '12345',
40
- dimensions: [2, 4, 5],
41
- dimension_unit: :inch,
42
- weight: 1,
43
- weight_unit: :kg
67
+ dimensions: [
68
+ Measured::Length(2, :inch),
69
+ Measured::Length(4, :inch),
70
+ Measured::Length(5, :inch)
71
+ ],
72
+ weight: Measured::Weight(1, :kg),
44
73
  )
45
74
 
46
- two_sku = Physical::Item.new(
75
+ sku_two = Physical::Item.new(
47
76
  id: "54321",
48
- dimensions: [1, 1, 1],
49
- dimension_unit: :cm,
50
- weight: 2.3,
51
- weight_unit: :g
77
+ dimensions: [
78
+ Measured::Length(1, :cm),
79
+ Measured::Length(1, :cm),
80
+ Measured::Length(1, :cm)
81
+ ],
82
+ weight: Measured::Weight(23, :g)
83
+ )
84
+ ```
85
+
86
+ You can initialize a package with items as follows:
87
+
88
+ ```ruby
89
+ package_with_items = Physical::Package.new(
90
+ items: [sku_one, sku_two]
52
91
  )
92
+ ```
93
+
94
+ This package has no defined container. This means we assume a box that is infinitely large, and that has zero weight. Thus the weight of this `package_with_items` will be 1023 gram (1 kg + 23 g = 1000 g + 23 g).
53
95
 
96
+ ### Packages with Boxes
97
+
98
+ A package also has a box that wraps it. This box is assumed to be a Cuboid, too - but one that has inner dimensions, and a weight that is it's own weight which must be added to any item's weight in order to find out the total weight of a package.
99
+
100
+ ```ruby
54
101
  my_carton = Physical::Box.new(
55
- dimensions: [10, 15, 15],
56
- dimension_unit: :cm,
57
- weight: 0.8,
58
- weight_unit: :lbs
102
+ dimensions: [
103
+ Measured::Length(10, :cm),
104
+ Measured::Length(15, :cm),
105
+ Measured::Length(15, :cm)
106
+ ],
107
+ inner_dimensions: [
108
+ Measured::Length(9, :cm),
109
+ Measured::Length(14, :cm),
110
+ Measured::Length(14, :cm)
111
+ ],
112
+ weight: Measured::Weight(350, :g),
59
113
  )
114
+ ```
60
115
 
116
+ If you create a carton and omit the inner dimensions, we will assume that the carton's inner dimensions are equal to its outer dimensions. This will, in many cases, be good enough (but in some you'll need the extra precision).
117
+
118
+ ### Calculating Void Fill
119
+
120
+ For an elaborate package with a container box and items, we still cannot find out the full weight of the package without taking into account void fill (styrofoam, bubble wrap or crumpled newspaper maybe). We can instruct the package to fill up all the volume not used up by items with void fill. You can pass the density as a `Measured::Weight` object that refers to the weight of 1 cubic centimeter of void fill:
121
+
122
+ ```ruby
61
123
  package = Physical::Package.new(
62
124
  id: "my_package",
63
125
  container: my_carton,
64
- items: [one_sku, two_sku],
65
- void_fill_density: 0.007
126
+ items: [sku_one, sku_two],
127
+ void_fill_density: Measured::Weight(0.007, :g)
66
128
  )
129
+ ```
130
+
131
+ In this case, the package's weight will be slightly above the sum of carton weight and the sum of item weights, as we incorporate the approximate weight of the void fill material:
67
132
 
133
+ ```ruby
68
134
  package.weight
69
- => #<Measured::Weight: 1376.32851808 #<Measured::Unit: g (gram, grams)>>
135
+ => #<Measured::Weight: 1380.75262208 #<Measured::Unit: g (gram, grams)>>
70
136
 
71
137
  package.remaining_volume
72
- => #<Measured::Volume: 1593.51744 #<Measured::Unit: ml (milliliter, millilitre, milliliters, millilitres) 1/1000 l>>
138
+ => #<Measured::Volume: 1107.51744 #<Measured::Unit: ml (milliliter, millilitre, milliliters, millilitres) 1/1000 l>>
73
139
  ```
74
140
  ## Development
75
141
 
data/lib/physical/box.rb CHANGED
@@ -5,5 +5,27 @@ require 'measured'
5
5
  module Physical
6
6
  class Box < Cuboid
7
7
  DEFAULT_LENGTH = BigDecimal::INFINITY
8
+ attr_reader :inner_dimensions,
9
+ :inner_length,
10
+ :inner_width,
11
+ :inner_height
12
+
13
+ def initialize(inner_dimensions: [], **args)
14
+ super args
15
+ @inner_dimensions = fill_dimensions(Types::Dimensions[inner_dimensions])
16
+ @inner_length, @inner_width, @inner_height = *@inner_dimensions.reverse
17
+ end
18
+
19
+ alias :inner_x :inner_length
20
+ alias :inner_y :inner_width
21
+ alias :inner_z :inner_height
22
+ alias :inner_depth :inner_height
23
+
24
+ def inner_volume
25
+ Measured::Volume(
26
+ inner_dimensions.map { |d| d.convert_to(:cm).value }.reduce(1, &:*),
27
+ :ml
28
+ )
29
+ end
8
30
  end
9
31
  end
@@ -6,11 +6,11 @@ module Physical
6
6
  class Cuboid
7
7
  attr_reader :dimensions, :length, :width, :height, :weight, :id, :properties
8
8
 
9
- def initialize(id: nil, dimensions: [], dimension_unit: :cm, weight: 0, weight_unit: :g, properties: {})
9
+ def initialize(id: nil, dimensions: [], weight: Measured::Weight(0, :g), properties: {})
10
10
  @id = id || SecureRandom.uuid
11
- @weight = Measured::Weight(weight, weight_unit)
12
- @dimensions = dimensions.map { |dimension| Measured::Length.new(dimension, dimension_unit) }.sort
13
- @dimensions.fill(Measured::Length(self.class::DEFAULT_LENGTH, dimension_unit), @dimensions.length..2)
11
+ @weight = Types::Weight[weight]
12
+ @dimensions = []
13
+ @dimensions = fill_dimensions(Types::Dimensions[dimensions])
14
14
  @length, @width, @height = *@dimensions.reverse
15
15
  @properties = properties
16
16
  end
@@ -27,5 +27,13 @@ module Physical
27
27
  def ==(other)
28
28
  id == other.id
29
29
  end
30
+
31
+ private
32
+
33
+ def fill_dimensions(dimensions)
34
+ dimensions.fill(dimensions.length..2) do |index|
35
+ @dimensions[index] || Measured::Length(self.class::DEFAULT_LENGTH, :cm)
36
+ end.sort
37
+ end
30
38
  end
31
39
  end
@@ -5,14 +5,14 @@ module Physical
5
5
  extend Forwardable
6
6
  attr_reader :container, :items, :void_fill_density, :id
7
7
 
8
- def initialize(id: nil, container: Physical::Box.new, items: [], void_fill_density: 0, void_fill_density_unit: :g)
8
+ def initialize(id: nil, container: nil, items: [], void_fill_density: Measured::Weight(0, :g), dimensions: nil, weight: nil, properties: {})
9
9
  @id = id || SecureRandom.uuid
10
- @void_fill_density = measured_void_fill_density(void_fill_density, void_fill_density_unit)
11
- @container = container
10
+ @void_fill_density = Types::Weight[void_fill_density]
11
+ @container = container || Physical::Box.new(dimensions: dimensions || [], weight: weight || Measured::Weight(0, :g), properties: properties)
12
12
  @items = Set[*items]
13
13
  end
14
14
 
15
- delegate [:dimensions, :width, :length, :height, :depth, :x, :y, :z] => :container
15
+ delegate [:dimensions, :width, :length, :height, :depth, :x, :y, :z, :properties] => :container
16
16
 
17
17
  def <<(item)
18
18
  @items.add(item)
@@ -27,7 +27,7 @@ module Physical
27
27
  end
28
28
 
29
29
  def remaining_volume
30
- container.volume - items.map(&:volume).reduce(Measured::Volume(0, :ml), &:+)
30
+ container.inner_volume - items.map(&:volume).reduce(Measured::Volume(0, :ml), &:+)
31
31
  end
32
32
 
33
33
  def void_fill_weight
@@ -35,14 +35,5 @@ module Physical
35
35
 
36
36
  Measured::Weight(void_fill_density.value * remaining_volume.value, void_fill_density.unit)
37
37
  end
38
-
39
- def measured_void_fill_density(void_fill_density, void_fill_density_unit)
40
- case void_fill_density
41
- when Measured::Weight
42
- void_fill_density
43
- else
44
- Measured::Weight(void_fill_density, void_fill_density_unit)
45
- end
46
- end
47
38
  end
48
39
  end
@@ -4,10 +4,9 @@ require 'factory_bot'
4
4
 
5
5
  FactoryBot.define do
6
6
  factory :physical_box, class: "Physical::Box" do
7
- dimensions { [4, 5, 6] }
8
- dimension_unit { :dm }
9
- weight { 0.1 }
10
- weight_unit { :kg }
7
+ dimensions { [4, 5, 6].map { |d| Measured::Length(d, :dm) } }
8
+ inner_dimensions { dimensions.map { |d| d - Measured::Length(1, :cm) } }
9
+ weight { Measured::Weight(0.1, :kg) }
11
10
  initialize_with { new(attributes) }
12
11
  end
13
12
  end
@@ -4,10 +4,8 @@ require 'factory_bot'
4
4
 
5
5
  FactoryBot.define do
6
6
  factory :physical_item, class: "Physical::Item" do
7
- dimensions { [1, 2, 3] }
8
- dimension_unit { :cm }
9
- weight { 50 }
10
- weight_unit { :g }
7
+ dimensions { [1, 2, 3].map { |d| Measured::Length(d, :cm)} }
8
+ weight { Measured::Weight(50, :g) }
11
9
  initialize_with { new(attributes) }
12
10
  end
13
11
  end
@@ -6,7 +6,7 @@ FactoryBot.define do
6
6
  factory :physical_package, class: "Physical::Package" do
7
7
  container { FactoryBot.build(:physical_box) }
8
8
  items { build_list(:physical_item, 2) }
9
- void_fill_density { 0.01 }
9
+ void_fill_density { Measured::Weight(0.01, :g) }
10
10
  initialize_with { new(attributes) }
11
11
  end
12
12
  end
@@ -1,7 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  RSpec.shared_examples 'a cuboid' do
4
- let(:args) { {dimensions: [1.1, 2.2, 3.3].shuffle, dimension_unit: :cm} }
4
+ let(:args) do
5
+ {
6
+ dimensions: [
7
+ Measured::Length.new(1.1, :cm),
8
+ Measured::Length.new(2.2, :cm),
9
+ Measured::Length.new(3.3, :cm)
10
+ ].shuffle
11
+ }
12
+ end
5
13
 
6
14
  it { is_expected.to be_a(Physical::Cuboid) }
7
15
 
@@ -0,0 +1,14 @@
1
+ require 'measured'
2
+ require 'dry-types'
3
+
4
+ module Physical
5
+ module Types
6
+ include Dry::Types.module
7
+
8
+ Weight = Types.Instance(::Measured::Weight)
9
+ Length = Types.Instance(::Measured::Length)
10
+ Volume = Types.Instance(::Measured::Volume)
11
+
12
+ Dimensions = Types::Strict::Array.of(Length)
13
+ end
14
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Physical
4
- VERSION = "0.1.4"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/physical.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "physical/types"
3
4
  require "physical/version"
4
5
  require "physical/cuboid"
5
6
  require "physical/box"
data/physical.gemspec CHANGED
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.required_ruby_version = '>= 2.4'
23
23
  spec.add_runtime_dependency "carmen", "~> 1.0"
24
24
  spec.add_runtime_dependency "measured", "~> 2.4.0"
25
+ spec.add_runtime_dependency "dry-types", "~> 1.0.0"
25
26
 
26
27
  spec.add_development_dependency "bundler", [">= 1.16", "< 3"]
27
28
  spec.add_development_dependency "factory_bot", "~> 4.8"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: physical
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Meyerhoff
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-13 00:00:00.000000000 Z
11
+ date: 2019-05-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: carmen
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 2.4.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-types
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.0.0
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -134,6 +148,7 @@ files:
134
148
  - lib/physical/spec_support/factories/package_factory.rb
135
149
  - lib/physical/spec_support/factories/shipment_factory.rb
136
150
  - lib/physical/spec_support/shared_examples.rb
151
+ - lib/physical/types.rb
137
152
  - lib/physical/version.rb
138
153
  - physical.gemspec
139
154
  homepage: https://github.com/mamhoff/physical