shipping_materials 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +232 -0
- data/Rakefile +1 -0
- data/examples/example.rb +33 -0
- data/lib/shipping_materials.rb +19 -0
- data/lib/shipping_materials/config.rb +35 -0
- data/lib/shipping_materials/csv_dsl.rb +82 -0
- data/lib/shipping_materials/group.rb +27 -0
- data/lib/shipping_materials/mixins/sortable.rb +26 -0
- data/lib/shipping_materials/packager.rb +52 -0
- data/lib/shipping_materials/packing_slips.rb +17 -0
- data/lib/shipping_materials/s3.rb +23 -0
- data/lib/shipping_materials/sorter.rb +49 -0
- data/lib/shipping_materials/storage.rb +54 -0
- data/lib/shipping_materials/version.rb +3 -0
- data/shipping_materials.gemspec +28 -0
- data/test/data.rb +217 -0
- data/test/files/template.mustache +16 -0
- data/test/files/test.html +3 -0
- data/test/models.rb +23 -0
- data/test/suite.rb +6 -0
- data/test/test_helper.rb +9 -0
- data/test/unit/config_test.rb +14 -0
- data/test/unit/csv_dsl_test.rb +117 -0
- data/test/unit/group_test.rb +61 -0
- data/test/unit/packager_test.rb +63 -0
- data/test/unit/packing_slips_test.rb +9 -0
- data/test/unit/s3_test.rb +17 -0
- data/test/unit/sorter_test.rb +48 -0
- data/test/unit/storage_test.rb +37 -0
- metadata +162 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 09d4424dc7c0604733b0a036401f36ce73e2c18c
|
4
|
+
data.tar.gz: 8775e800e7580de9203ad819bb4c8337d6444f7c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 001907c0d0a1ebda644b76221a44de39d5813a3fc542eaac733fc221dbd963d1d707ab6f812eda8f9ee8a2268327309ad4606146307c49d303c5b5ce06a176cd
|
7
|
+
data.tar.gz: f03e05bfec70bc67692286dcf4bfe0b681a23fa3090a4d0c3eb4e068c94a5093097777b22343eb2d57ab1cb5f913a1db5e452bdeeca4925df690791d42b87ac8
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Andrew Haust
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
# Shipping Materials
|
2
|
+
|
3
|
+
Shipping Materials provides a simple DSL for grouping and sorting a collection
|
4
|
+
of orders and their items and creating print materials for them. So far this
|
5
|
+
includes packing slips and CSVs for label makers.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
$ gem install shipping_materials
|
10
|
+
|
11
|
+
## Dependencies
|
12
|
+
|
13
|
+
`wkhtmltopdf` if used for PDF generation. The call to it is made as a linux
|
14
|
+
command therefore this plugin will not work on Windows yet.
|
15
|
+
|
16
|
+
`gzip` is used for the zip functionality.
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
### Setup
|
21
|
+
|
22
|
+
There is a little bit of configuration you are going to want to do first and
|
23
|
+
that is to add a save_path.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
ShippingMaterials.config do |config|
|
27
|
+
config.save_path = 'local/save/path'
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
If you would like to use S3, add the following:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
ShippingMaterials.config do |config|
|
35
|
+
config.s3_bucket = 'bucket.domain.com'
|
36
|
+
config.s3_access_key_id = ENV['AWS_ACCESS_KEY']
|
37
|
+
config.s3_secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
### The Packager
|
42
|
+
|
43
|
+
The DSL is provided via the ShippingMaterials::Packager class.
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
packager = ShippingMaterials::Packager.new
|
47
|
+
```
|
48
|
+
|
49
|
+
The Packager's `#package` method takes a collection of objects of the same
|
50
|
+
type.
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
orders = Order.where(state: 'placed')
|
54
|
+
|
55
|
+
packager.package :orders do
|
56
|
+
# ...
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
Because we are creating shipping materials here, at the very least, it is
|
61
|
+
assumed you are going to want packing slips. You may specify a global template
|
62
|
+
with the `#pdf` method:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
packager.package orders do
|
66
|
+
pdf 'path/to/template.mustache'
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
Now, at the simplest level, we can start breaking these objects down into
|
71
|
+
groups.
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
packager.package orders do
|
75
|
+
pdf 'path/to/template.mustache'
|
76
|
+
|
77
|
+
group 'Canadian Standard Post' do
|
78
|
+
filter {
|
79
|
+
ship_method == 'std' && country == 'CA'
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
group 'United States UPS Expedited' do
|
84
|
+
filter {
|
85
|
+
ship_method == 'UPSexp' && country == 'US'
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
group 'International World Ship' do
|
90
|
+
filter {
|
91
|
+
ship_method == 'UPS' && !%w[ US CA ].include?(country)
|
92
|
+
}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
97
|
+
PDFs (one per group) will now be created. With groups named as above, you can
|
98
|
+
expect the file names 'CanadianStandardPost.pdf', 'UnitedStatesUPSExpedite.pdf'
|
99
|
+
and 'InternationalWorldShip.pdf'.
|
100
|
+
|
101
|
+
### Templating
|
102
|
+
|
103
|
+
Right now, templating is done with Mustache. I plan on adding more in the
|
104
|
+
future (or feel free to send me a pull request). Here is an example template:
|
105
|
+
|
106
|
+
```html
|
107
|
+
<html>
|
108
|
+
{{# objects }}
|
109
|
+
<div>
|
110
|
+
<p>{{ number }}</p>
|
111
|
+
{{# line_items }}
|
112
|
+
<p>{{ name }}: ${{ price }} x {{ quantity }} = {{ total }}</p>
|
113
|
+
{{/ line_items }}
|
114
|
+
</div>
|
115
|
+
{{/ objects }}
|
116
|
+
</html>
|
117
|
+
```
|
118
|
+
|
119
|
+
|
120
|
+
I chose the 'objects' as the default variable name, though you may change this
|
121
|
+
in config:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
ShippingMaterials.config do |config|
|
125
|
+
config.base_context = 'orders'
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
Each group will produce one PDF.
|
130
|
+
|
131
|
+
### CSV (for shipping labels)
|
132
|
+
|
133
|
+
Any label printer I know -- as well as things like UPS Worldship -- use CSVs,
|
134
|
+
so Shipping Materials provides a little CSV templating DSL. This is provided
|
135
|
+
via the `Group#csv` method.
|
136
|
+
|
137
|
+
The `#csv` method takes a block which exposes the `#row` method. `#row` takes
|
138
|
+
a hash or an array and may be called multiple times.
|
139
|
+
|
140
|
+
Here is an example with hashes:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
group 'Canadian Standard Post' do
|
144
|
+
csv(headers: true) {
|
145
|
+
row 'Code' => 'Q',
|
146
|
+
'Order Number' => :number,
|
147
|
+
'Name' => [ :shipping_address, :name ]
|
148
|
+
# ...
|
149
|
+
'Country' => [ :shipping_address, :country, :iso ]
|
150
|
+
|
151
|
+
row line_items: [ 'H', :id, :name, :quantity, :price ]
|
152
|
+
}
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
In this example, the first call to row is evaluated in the context of each
|
157
|
+
order. Symbols are called as methods whereas string values are kept as-is. In
|
158
|
+
order to chain method calls, use an array of symbols as a value. For example,
|
159
|
+
`[ :shipping_address, :country, :iso ]` will call
|
160
|
+
`order.shipping_method.country.iso`.
|
161
|
+
|
162
|
+
Passing `headers: true` to the csv method will use the keys from the _first_
|
163
|
+
call to row (if it is a hash) as the headers for the CSV.
|
164
|
+
|
165
|
+
As demonstrated in the second call to row, you can evalute your row in the
|
166
|
+
context of your line items (or other one-to-many relationship) using its method
|
167
|
+
name as a key.
|
168
|
+
|
169
|
+
### Sorting
|
170
|
+
|
171
|
+
While most sorting should probably be done at the query level, Shipping
|
172
|
+
Materials provides a sorting DSL for more complex sorts. For example:
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
packager.package orders do
|
176
|
+
pdf 'path/to/template.mustache'
|
177
|
+
|
178
|
+
sort do
|
179
|
+
# put orders containing only Vinyl at the top
|
180
|
+
rule {
|
181
|
+
return true unless line_items.detect {|li| type != 'Vinyl' }
|
182
|
+
}
|
183
|
+
|
184
|
+
# next come orders that have both Vinyl and CDs and nothing else
|
185
|
+
rule {
|
186
|
+
line_items.select {|li| %w[ Vinyl CD ].include?(li.type) }.uniq.size == 2
|
187
|
+
}
|
188
|
+
|
189
|
+
# get the picture?
|
190
|
+
end
|
191
|
+
|
192
|
+
group # ...
|
193
|
+
end
|
194
|
+
```
|
195
|
+
|
196
|
+
A merge sort will be performed sorting the orders within each group according
|
197
|
+
to each rule in the order they are defined.
|
198
|
+
|
199
|
+
While it is definitely recommended to sort line items at the query level, you
|
200
|
+
can operate in the context of line items by passing the name of the association
|
201
|
+
to the sort method (ie: your association doesn't have to be called "line_items"
|
202
|
+
specifically):
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
sort(:line_items) do
|
206
|
+
rule { type == 'Vinyl' }
|
207
|
+
rule { type == 'CD' }
|
208
|
+
rule { type == 'Cassette' }
|
209
|
+
rule { type == '8-Track' }
|
210
|
+
end
|
211
|
+
```
|
212
|
+
|
213
|
+
This will sort your line items within each packing slip.
|
214
|
+
|
215
|
+
## Documentation
|
216
|
+
|
217
|
+
Other than this README, there is no documentation yet. There are a few other
|
218
|
+
experimental and volatile features not mentioned here. They are certainly
|
219
|
+
going to change soon.
|
220
|
+
|
221
|
+
## Contributing
|
222
|
+
|
223
|
+
This is my first foray into the world of library authoring. I welcome any and
|
224
|
+
all advice and pull requests with open arms, but for the love of whoever or
|
225
|
+
whatever you believe in: please follow [these
|
226
|
+
guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
227
|
+
when writing your commit messages.
|
228
|
+
|
229
|
+
## A note about the tests
|
230
|
+
|
231
|
+
I am still learning to test properly. Tests are passing _but_ some of them are
|
232
|
+
writing to the filesystem. These wrongs will be righted in due time.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/examples/example.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
ShippingMaterials.config do |config|
|
2
|
+
config.save_path = MyProject::Config[:asset_storage_location]
|
3
|
+
end
|
4
|
+
|
5
|
+
packager = ShippingMaterials::Packager.new
|
6
|
+
|
7
|
+
orders = Orders.where(state: 'placed')
|
8
|
+
|
9
|
+
packager.package orders do
|
10
|
+
pdf 'path/to/template',
|
11
|
+
|
12
|
+
sort(:line_items) {
|
13
|
+
rule { type == 'Vinyl' }
|
14
|
+
rule { type == 'CD' }
|
15
|
+
rule { type == 'Cassette' }
|
16
|
+
}
|
17
|
+
|
18
|
+
sort {
|
19
|
+
rule { line_items.detect {|li| li.type == 'vinyl' } }
|
20
|
+
}
|
21
|
+
|
22
|
+
group 'canada_standard_shipping' do
|
23
|
+
filter { country == 'CA' && shipping_method == 'std' }
|
24
|
+
|
25
|
+
csv(extenstion: 'txt', headers: true) {
|
26
|
+
row code: 'Q',
|
27
|
+
order_id: :id,
|
28
|
+
hello: :hello
|
29
|
+
|
30
|
+
row line_items: [ 'H', :id, :name, :price, :quantity ]
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'mustache'
|
2
|
+
|
3
|
+
require 'shipping_materials/version'
|
4
|
+
require 'shipping_materials/mixins/sortable'
|
5
|
+
|
6
|
+
require 'shipping_materials/config'
|
7
|
+
require 'shipping_materials/storage'
|
8
|
+
require 'shipping_materials/packager'
|
9
|
+
require 'shipping_materials/group'
|
10
|
+
require 'shipping_materials/csv_dsl'
|
11
|
+
require 'shipping_materials/packing_slips'
|
12
|
+
require 'shipping_materials/sorter'
|
13
|
+
require 'shipping_materials/s3'
|
14
|
+
|
15
|
+
module ShippingMaterials
|
16
|
+
def self.config
|
17
|
+
yield Config
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ShippingMaterials
|
2
|
+
class Config
|
3
|
+
class << self
|
4
|
+
attr_accessor :s3_bucket,
|
5
|
+
:s3_access_key,
|
6
|
+
:s3_secret,
|
7
|
+
:gzip_file_name
|
8
|
+
|
9
|
+
def base_context
|
10
|
+
@base_context || :objects
|
11
|
+
end
|
12
|
+
|
13
|
+
def base_context=(bc)
|
14
|
+
@base_context = bc.to_sym
|
15
|
+
end
|
16
|
+
|
17
|
+
def save_path=(save_path)
|
18
|
+
@save_path = save_path.sub(/(\/)+$/, '')
|
19
|
+
end
|
20
|
+
|
21
|
+
def save_path
|
22
|
+
@default ||= Time.now.to_i
|
23
|
+
@save_path || "/tmp/shipping_materials#{@default}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def use_s3?
|
27
|
+
@s3_bucket
|
28
|
+
end
|
29
|
+
|
30
|
+
def use_gzip?
|
31
|
+
@gzip_file_name
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module ShippingMaterials
|
2
|
+
class CSVDSL
|
3
|
+
require 'csv'
|
4
|
+
|
5
|
+
attr_accessor :objects, :row_maps
|
6
|
+
|
7
|
+
def initialize(objects, options={})
|
8
|
+
@objects = objects
|
9
|
+
@row_maps = {}
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
# This method is on the complex side. It is a DSL method that
|
14
|
+
# performs type-checking and also sets the headers.
|
15
|
+
# Be sure to see headers=() defined below
|
16
|
+
def row(collection)
|
17
|
+
if collection.is_a? Array
|
18
|
+
@row_maps[:object] = collection
|
19
|
+
elsif collection.is_a? Hash
|
20
|
+
f = collection.first
|
21
|
+
if f[1].is_a? Array
|
22
|
+
@row_maps[f[0]] = f[1]
|
23
|
+
elsif f[1].is_a? Hash
|
24
|
+
self.headers = collection.first[1]
|
25
|
+
@row_maps[f[0]] = f[1].values
|
26
|
+
else
|
27
|
+
self.headers = collection
|
28
|
+
@row_maps[:object] = collection.values
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_csv
|
34
|
+
CSV.generate do |csv|
|
35
|
+
csv << headers if headers?
|
36
|
+
@objects.each do |object|
|
37
|
+
@row_maps.each do |context, methods|
|
38
|
+
if context == :object
|
39
|
+
csv << get_row(object, methods)
|
40
|
+
else
|
41
|
+
object.send(context).each do |c|
|
42
|
+
csv << get_row(c, methods)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
alias_method :to_s, :to_csv
|
51
|
+
|
52
|
+
def extension
|
53
|
+
@options[:extension] || 'csv'
|
54
|
+
end
|
55
|
+
|
56
|
+
def headers
|
57
|
+
@headers
|
58
|
+
end
|
59
|
+
|
60
|
+
def headers=(object)
|
61
|
+
@headers ||= object.keys.map {|h| h.to_s } if self.headers?
|
62
|
+
end
|
63
|
+
|
64
|
+
def headers?
|
65
|
+
@options[:headers]
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def get_row(object, methods)
|
71
|
+
methods.map do |meth|
|
72
|
+
if meth.is_a? Symbol
|
73
|
+
object.send(meth)
|
74
|
+
elsif meth.is_a? Array
|
75
|
+
meth.reduce(object) {|obj, m| obj.send(m) }
|
76
|
+
elsif meth.is_a? String
|
77
|
+
meth
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|