paged_groups 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +8 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +8 -0
- data/.ruby-version +1 -0
- data/.travis.yml +11 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +87 -0
- data/Guardfile +16 -0
- data/LICENSE +7 -0
- data/README.md +245 -0
- data/bin/console +15 -0
- data/lib/paged_groups.rb +10 -0
- data/lib/paged_groups/builder.rb +107 -0
- data/lib/paged_groups/paged_groups.rb +21 -0
- data/lib/paged_groups/version.rb +12 -0
- data/paged_groups.gemspec +29 -0
- data/spec/paged_groups/builder_spec.rb +64 -0
- data/spec/paged_groups/paged_groups_spec.rb +157 -0
- data/spec/spec_helper.rb +10 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: cb8ac318e3df020ef112bcfd9a44fef6883519682be6501de014e745148b4046
|
4
|
+
data.tar.gz: 7d69dea71a15bf728052cdfe80341e24b2ac32b0bc93bf5ab3f3b0891bd66b4f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f60342818d52bce7432752e19d852f47a8cb28ca2a9d5216b39175e1ba47e9f71b9f2a8bb970751cc59a162c3d6af140dce2e95bfed5fcea53605b1b2b0e0c09
|
7
|
+
data.tar.gz: 86a0b7432ee2cefb13aad7b9e9d13ca1e2d35f60e93ebc58b798a2700816810be09c5b5580f26c10a6144664483aea9877108f1dea44983a20de647dc1c05c95
|
data/.editorconfig
ADDED
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.6.0
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
paged_groups (1.0.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
ast (2.4.0)
|
10
|
+
coderay (1.1.2)
|
11
|
+
diff-lcs (1.3)
|
12
|
+
ffi (1.9.25)
|
13
|
+
formatador (0.2.5)
|
14
|
+
guard (2.15.0)
|
15
|
+
formatador (>= 0.2.4)
|
16
|
+
listen (>= 2.7, < 4.0)
|
17
|
+
lumberjack (>= 1.0.12, < 2.0)
|
18
|
+
nenv (~> 0.1)
|
19
|
+
notiffany (~> 0.0)
|
20
|
+
pry (>= 0.9.12)
|
21
|
+
shellany (~> 0.0)
|
22
|
+
thor (>= 0.18.1)
|
23
|
+
guard-compat (1.2.1)
|
24
|
+
guard-rspec (4.7.3)
|
25
|
+
guard (~> 2.1)
|
26
|
+
guard-compat (~> 1.1)
|
27
|
+
rspec (>= 2.99.0, < 4.0)
|
28
|
+
jaro_winkler (1.5.1)
|
29
|
+
listen (3.1.5)
|
30
|
+
rb-fsevent (~> 0.9, >= 0.9.4)
|
31
|
+
rb-inotify (~> 0.9, >= 0.9.7)
|
32
|
+
ruby_dep (~> 1.2)
|
33
|
+
lumberjack (1.0.13)
|
34
|
+
method_source (0.9.2)
|
35
|
+
nenv (0.3.0)
|
36
|
+
notiffany (0.1.1)
|
37
|
+
nenv (~> 0.1)
|
38
|
+
shellany (~> 0.0)
|
39
|
+
parallel (1.12.1)
|
40
|
+
parser (2.5.3.0)
|
41
|
+
ast (~> 2.4.0)
|
42
|
+
powerpack (0.1.2)
|
43
|
+
pry (0.12.2)
|
44
|
+
coderay (~> 1.1.0)
|
45
|
+
method_source (~> 0.9.0)
|
46
|
+
rainbow (3.0.0)
|
47
|
+
rb-fsevent (0.10.3)
|
48
|
+
rb-inotify (0.9.10)
|
49
|
+
ffi (>= 0.5.0, < 2)
|
50
|
+
rspec (3.8.0)
|
51
|
+
rspec-core (~> 3.8.0)
|
52
|
+
rspec-expectations (~> 3.8.0)
|
53
|
+
rspec-mocks (~> 3.8.0)
|
54
|
+
rspec-core (3.8.0)
|
55
|
+
rspec-support (~> 3.8.0)
|
56
|
+
rspec-expectations (3.8.2)
|
57
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
58
|
+
rspec-support (~> 3.8.0)
|
59
|
+
rspec-mocks (3.8.0)
|
60
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
61
|
+
rspec-support (~> 3.8.0)
|
62
|
+
rspec-support (3.8.0)
|
63
|
+
rubocop (0.59.2)
|
64
|
+
jaro_winkler (~> 1.5.1)
|
65
|
+
parallel (~> 1.10)
|
66
|
+
parser (>= 2.5, != 2.5.1.1)
|
67
|
+
powerpack (~> 0.1)
|
68
|
+
rainbow (>= 2.2.2, < 4.0)
|
69
|
+
ruby-progressbar (~> 1.7)
|
70
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
71
|
+
ruby-progressbar (1.10.0)
|
72
|
+
ruby_dep (1.5.0)
|
73
|
+
shellany (0.0.1)
|
74
|
+
thor (0.20.3)
|
75
|
+
unicode-display_width (1.4.0)
|
76
|
+
|
77
|
+
PLATFORMS
|
78
|
+
ruby
|
79
|
+
|
80
|
+
DEPENDENCIES
|
81
|
+
guard-rspec (~> 4.7)
|
82
|
+
paged_groups!
|
83
|
+
rspec (~> 3.8)
|
84
|
+
rubocop (~> 0.59)
|
85
|
+
|
86
|
+
BUNDLED WITH
|
87
|
+
1.17.3
|
data/Guardfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
guard :rspec, cmd: 'bundle exec rspec' do
|
4
|
+
require 'guard/rspec/dsl'
|
5
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
6
|
+
|
7
|
+
# RSpec files
|
8
|
+
rspec = dsl.rspec
|
9
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
10
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
11
|
+
watch(rspec.spec_files)
|
12
|
+
|
13
|
+
# Ruby files
|
14
|
+
ruby = dsl.ruby
|
15
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
16
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright 2018 Blue Marble Payroll, LLC
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,245 @@
|
|
1
|
+
# paged_groups
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/bluemarblepayroll/paged_groups.svg?branch=master)](https://travis-ci.org/bluemarblepayroll/paged_groups)
|
4
|
+
|
5
|
+
Imagine a two-dimensional data set (first dimension being group and second dimension being record) for which you wanted to page with the following rules:
|
6
|
+
|
7
|
+
1. Each page should roughly has the same number of records ([greedy](https://en.wikipedia.org/wiki/Greedy_algorithm))
|
8
|
+
2. Each group should not be split between pages ([atomic](https://en.wikipedia.org/wiki/Atomicity_(database_systems)))
|
9
|
+
|
10
|
+
This library helps you page grouped-data when the grouped data can have different sizes. It provides a builder that understands how to split pages in a fashion where each page tries to conform to a maximum page size but also ensures groups are not split up.
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
To install through Rubygems:
|
15
|
+
|
16
|
+
````
|
17
|
+
gem install install paged_groups
|
18
|
+
````
|
19
|
+
|
20
|
+
You can also add this to your Gemfile:
|
21
|
+
|
22
|
+
````
|
23
|
+
bundle add paged_groups
|
24
|
+
````
|
25
|
+
|
26
|
+
## Examples
|
27
|
+
|
28
|
+
### Standard Use-Case
|
29
|
+
|
30
|
+
Here is an example data set containing groups with variable number of records:
|
31
|
+
|
32
|
+
````ruby
|
33
|
+
data = [
|
34
|
+
[
|
35
|
+
{ id: 1, name: 'Jordan' },
|
36
|
+
{ id: 2, name: 'Pippen' },
|
37
|
+
{ id: 3, name: 'Rodman' },
|
38
|
+
{ id: 4, name: 'Harper' },
|
39
|
+
{ id: 5, name: 'Longley' }
|
40
|
+
],
|
41
|
+
[
|
42
|
+
{ id: 6, name: 'Kukoc' }
|
43
|
+
],
|
44
|
+
[
|
45
|
+
{ id: 7, name: 'Kerr' }
|
46
|
+
],
|
47
|
+
[
|
48
|
+
{ id: 8, name: 'Buechler' }
|
49
|
+
],
|
50
|
+
[
|
51
|
+
{ id: 9, name: 'Wennington' },
|
52
|
+
{ id: 10, name: 'Simpkins' }
|
53
|
+
],
|
54
|
+
[
|
55
|
+
{ id: 11, name: 'Caffey' },
|
56
|
+
{ id: 12, name: 'Edwards' },
|
57
|
+
{ id: 13, name: 'Salley' }
|
58
|
+
]
|
59
|
+
]
|
60
|
+
````
|
61
|
+
|
62
|
+
Let's use max page size of three for this example. In the real-world a max page size of three is most likely too small but it fits our example data set above for illustration purpose; you should be able to extrapolate this out with larger sets and larger max page sizes. To page this set we would execute:
|
63
|
+
|
64
|
+
````ruby
|
65
|
+
pages = PagedGroups.builder(page_size: 3).add(*data).all
|
66
|
+
````
|
67
|
+
|
68
|
+
*Note:* We are using the splat (asterisk) operator to indicate each group is an argument.
|
69
|
+
|
70
|
+
our ````pages```` variable should now contain a two-dimensional array where dimension one is page and dimension two is record and would be:
|
71
|
+
|
72
|
+
````ruby
|
73
|
+
[
|
74
|
+
[
|
75
|
+
{ id: 1, name: 'Jordan' },
|
76
|
+
{ id: 2, name: 'Pippen' },
|
77
|
+
{ id: 3, name: 'Rodman' },
|
78
|
+
{ id: 4, name: 'Harper' },
|
79
|
+
{ id: 5, name: 'Longley' }
|
80
|
+
],
|
81
|
+
[
|
82
|
+
{ id: 6, name: 'Kukoc' },
|
83
|
+
{ id: 7, name: 'Kerr' },
|
84
|
+
{ id: 8, name: 'Buechler' }
|
85
|
+
],
|
86
|
+
[
|
87
|
+
{ id: 9, name: 'Wennington' },
|
88
|
+
{ id: 10, name: 'Simpkins' }
|
89
|
+
],
|
90
|
+
[
|
91
|
+
{ id: 11, name: 'Caffey' },
|
92
|
+
{ id: 12, name: 'Edwards' },
|
93
|
+
{ id: 13, name: 'Salley' }
|
94
|
+
]
|
95
|
+
]
|
96
|
+
````
|
97
|
+
|
98
|
+
Notice how each group was kept atomic while each page was tried to be kept as close to the max page size as possible.
|
99
|
+
|
100
|
+
### Spacing Customization
|
101
|
+
|
102
|
+
There may be merit in placing *separator* record in between groups in the same page. This separate record can act as a spacer. You can specify this spacer row in the builder's constructor:
|
103
|
+
|
104
|
+
````ruby
|
105
|
+
pages = PagedGroups.builder(page_size: 3, space: true, spacer: { id: nil, name: '' })
|
106
|
+
.add(*data)
|
107
|
+
.all
|
108
|
+
````
|
109
|
+
|
110
|
+
*Note:* ````spacer```` (boolean) argument is split from ````space```` (object) to allow for spacer to be anything (even false or nil.)
|
111
|
+
|
112
|
+
Now, our resulting pages would become:
|
113
|
+
|
114
|
+
````ruby
|
115
|
+
[
|
116
|
+
[
|
117
|
+
{ id: 1, name: 'Jordan' },
|
118
|
+
{ id: 2, name: 'Pippen' },
|
119
|
+
{ id: 3, name: 'Rodman' },
|
120
|
+
{ id: 4, name: 'Harper' },
|
121
|
+
{ id: 5, name: 'Longley' }
|
122
|
+
],
|
123
|
+
[
|
124
|
+
{ id: 6, name: 'Kukoc' },
|
125
|
+
{ id: nil, name: '' },
|
126
|
+
{ id: 7, name: 'Kerr' }
|
127
|
+
],
|
128
|
+
[
|
129
|
+
{ id: 8, name: 'Buechler' },
|
130
|
+
{ id: nil, name: '' },
|
131
|
+
{ id: 9, name: 'Wennington' },
|
132
|
+
{ id: 10, name: 'Simpkins' }
|
133
|
+
],
|
134
|
+
[
|
135
|
+
{ id: 11, name: 'Caffey' },
|
136
|
+
{ id: 12, name: 'Edwards' },
|
137
|
+
{ id: 13, name: 'Salley' }
|
138
|
+
]
|
139
|
+
]
|
140
|
+
````
|
141
|
+
|
142
|
+
|
143
|
+
### Forcing Same Page
|
144
|
+
|
145
|
+
Another customization that may come in handy is the ability to force-add groups to the current page. For example, say we wanted to add another group to our initial data set, but we want it to end up on the same page as the last records:
|
146
|
+
|
147
|
+
````ruby
|
148
|
+
other_data = [
|
149
|
+
[
|
150
|
+
{ id: 14, name: 'Jackson' },
|
151
|
+
{ id: 14, name: 'Winters' }
|
152
|
+
]
|
153
|
+
]
|
154
|
+
pages = PagedGroups.builder(page_size: 3, space: true, spacer: { id: nil, name: '' })
|
155
|
+
.add(*data)
|
156
|
+
.add(*other_data, force: true)
|
157
|
+
.all
|
158
|
+
````
|
159
|
+
|
160
|
+
*Note:* #add has a [fluent interface](https://en.wikipedia.org/wiki/Fluent_interface) and can be chained as illustrated above.
|
161
|
+
|
162
|
+
Now the result pages would be:
|
163
|
+
|
164
|
+
````ruby
|
165
|
+
[
|
166
|
+
[
|
167
|
+
{ id: 1, name: 'Jordan' },
|
168
|
+
{ id: 2, name: 'Pippen' },
|
169
|
+
{ id: 3, name: 'Rodman' },
|
170
|
+
{ id: 4, name: 'Harper' },
|
171
|
+
{ id: 5, name: 'Longley' }
|
172
|
+
],
|
173
|
+
[
|
174
|
+
{ id: 6, name: 'Kukoc' },
|
175
|
+
{ id: nil, name: '' },
|
176
|
+
{ id: 7, name: 'Kerr' }
|
177
|
+
],
|
178
|
+
[
|
179
|
+
{ id: 8, name: 'Buechler' },
|
180
|
+
{ id: nil, name: '' },
|
181
|
+
{ id: 9, name: 'Wennington' },
|
182
|
+
{ id: 10, name: 'Simpkins' }
|
183
|
+
],
|
184
|
+
[
|
185
|
+
{ id: 11, name: 'Caffey' },
|
186
|
+
{ id: 12, name: 'Edwards' },
|
187
|
+
{ id: 13, name: 'Salley' },
|
188
|
+
{ id: nil, name: '' },
|
189
|
+
{ id: 14, name: 'Jackson' },
|
190
|
+
{ id: 14, name: 'Winters' }
|
191
|
+
]
|
192
|
+
]
|
193
|
+
````
|
194
|
+
|
195
|
+
## Contributing
|
196
|
+
|
197
|
+
### Development Environment Configuration
|
198
|
+
|
199
|
+
Basic steps to take to get this repository compiling:
|
200
|
+
|
201
|
+
1. Install [Ruby](https://www.ruby-lang.org/en/documentation/installation/) (check paged_groups.gemspec for versions supported)
|
202
|
+
2. Install bundler (gem install bundler)
|
203
|
+
3. Clone the repository (git clone git@github.com:bluemarblepayroll/paged_groups.git)
|
204
|
+
4. Navigate to the root folder (cd paged_groups)
|
205
|
+
5. Install dependencies (bundle)
|
206
|
+
|
207
|
+
### Running Tests
|
208
|
+
|
209
|
+
To execute the test suite run:
|
210
|
+
|
211
|
+
````
|
212
|
+
bundle exec rspec spec --format documentation
|
213
|
+
````
|
214
|
+
|
215
|
+
Alternatively, you can have Guard watch for changes:
|
216
|
+
|
217
|
+
````
|
218
|
+
bundle exec guard
|
219
|
+
````
|
220
|
+
|
221
|
+
Also, do not forget to run Rubocop:
|
222
|
+
|
223
|
+
````
|
224
|
+
bundle exec rubocop
|
225
|
+
````
|
226
|
+
|
227
|
+
### Publishing
|
228
|
+
|
229
|
+
Note: ensure you have proper authorization before trying to publish new versions.
|
230
|
+
|
231
|
+
After code changes have successfully gone through the Pull Request review process then the following steps should be followed for publishing new versions:
|
232
|
+
|
233
|
+
1. Merge Pull Request into master
|
234
|
+
2. Update [lib/paged_groups/version.rb](https://github.com/bluemarblepayroll/paged_groups/blob/master/lib/paged_groups/version.rb) [version number](https://semver.org/)
|
235
|
+
3. Bundle
|
236
|
+
4. Update CHANGELOG.md
|
237
|
+
5. Commit & Push master to remote and ensure CI builds master successfully
|
238
|
+
6. Build the project locally: `gem build paged_groups`
|
239
|
+
7. Publish package to NPM: `gem push paged_groups-X.gem` where X is the version to push
|
240
|
+
8. Tag master with new version: `git tag <version>`
|
241
|
+
9. Push tags remotely: `git push origin --tags`
|
242
|
+
|
243
|
+
## License
|
244
|
+
|
245
|
+
This project is MIT Licensed.
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'paged_groups'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start
|
data/lib/paged_groups.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require_relative 'paged_groups/paged_groups'
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module PagedGroups
|
11
|
+
# This is the Public API for this library.
|
12
|
+
class Builder
|
13
|
+
DEFAULT_PAGE_SIZE = 50
|
14
|
+
|
15
|
+
attr_reader :page_size,
|
16
|
+
:page_count,
|
17
|
+
:row_count,
|
18
|
+
:space,
|
19
|
+
:spacer
|
20
|
+
|
21
|
+
def initialize(page_size: DEFAULT_PAGE_SIZE, space: false, spacer: nil)
|
22
|
+
@page_size = page_size ? page_size.to_i : DEFAULT_PAGE_SIZE
|
23
|
+
@space = space || false
|
24
|
+
@spacer = spacer
|
25
|
+
|
26
|
+
clear
|
27
|
+
end
|
28
|
+
|
29
|
+
def add(*groups, force: false)
|
30
|
+
dirty!
|
31
|
+
|
32
|
+
groups.each { |group| insert(Array(group), force: force) }
|
33
|
+
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def clear
|
38
|
+
dirty!
|
39
|
+
|
40
|
+
@pages = []
|
41
|
+
@page_count = 0
|
42
|
+
@current_page = []
|
43
|
+
@row_count = 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def all
|
47
|
+
return @all if @all
|
48
|
+
|
49
|
+
@all = top? ? @pages : @pages + [@current_page]
|
50
|
+
end
|
51
|
+
alias to_a all
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
"[#{self.class.name}] Page Count: #{page_count}, Row Count: #{row_count}"
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def insert(rows, force: false)
|
60
|
+
cut! if start_new_page?(rows) && !force
|
61
|
+
|
62
|
+
space_if_needed
|
63
|
+
|
64
|
+
@current_page += rows
|
65
|
+
|
66
|
+
@row_count += rows.length
|
67
|
+
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
def not_top?
|
72
|
+
!top?
|
73
|
+
end
|
74
|
+
|
75
|
+
def top?
|
76
|
+
@current_page.empty?
|
77
|
+
end
|
78
|
+
|
79
|
+
def dirty!
|
80
|
+
@all = nil
|
81
|
+
end
|
82
|
+
|
83
|
+
def cut!
|
84
|
+
@page_count += 1
|
85
|
+
@pages << @current_page
|
86
|
+
@current_page = []
|
87
|
+
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def space_if_needed
|
92
|
+
@current_page << spacer if space && not_top?
|
93
|
+
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
|
97
|
+
def start_new_page?(next_items)
|
98
|
+
proposed_current_page_length = @current_page.length + next_items.length
|
99
|
+
|
100
|
+
not_top? && proposed_current_page_length > page_size
|
101
|
+
end
|
102
|
+
|
103
|
+
def end_of_current_page?
|
104
|
+
@current_page.length >= page_size
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require_relative 'builder'
|
11
|
+
|
12
|
+
# Top-level namespace for primary public API.
|
13
|
+
module PagedGroups
|
14
|
+
class << self
|
15
|
+
# This is syntactic sugar and is equivalent to: PagedGroups::Builder.new(args)
|
16
|
+
# Check the constructor signature of PagedGroups::Builder for argument definition.
|
17
|
+
def builder(*args)
|
18
|
+
::PagedGroups::Builder.new(*args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module PagedGroups
|
11
|
+
VERSION = '1.0.0'
|
12
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require './lib/paged_groups/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'paged_groups'
|
7
|
+
s.version = PagedGroups::VERSION
|
8
|
+
s.summary = 'Create an evenly paged dataset of un-evenly sized groups'
|
9
|
+
|
10
|
+
s.description = <<-DESCRIPTION
|
11
|
+
This library helps you page grouped-data when the grouped data can have different sizes.
|
12
|
+
It provides a builder that understands how to split pages in a fashion where each page tries to
|
13
|
+
conform to a maximum page size (greedy) but also ensures groups are not split up (atomic.)
|
14
|
+
DESCRIPTION
|
15
|
+
|
16
|
+
s.authors = ['Matthew Ruggio']
|
17
|
+
s.email = ['mruggio@bluemarblepayroll.com']
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
21
|
+
s.homepage = 'https://github.com/bluemarblepayroll/paged_groups'
|
22
|
+
s.license = 'MIT'
|
23
|
+
|
24
|
+
s.required_ruby_version = '>= 2.3.8'
|
25
|
+
|
26
|
+
s.add_development_dependency('guard-rspec', '~>4.7')
|
27
|
+
s.add_development_dependency('rspec', '~> 3.8')
|
28
|
+
s.add_development_dependency('rubocop', '~> 0.59')
|
29
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require './spec/spec_helper'
|
11
|
+
|
12
|
+
describe ::PagedGroups::Builder do
|
13
|
+
let(:page_size) { 44 }
|
14
|
+
|
15
|
+
let(:spacer) { 'SPACER' }
|
16
|
+
|
17
|
+
let(:page_builder) do
|
18
|
+
::PagedGroups::Builder.new(page_size: page_size, spacer: spacer, space: true)
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:hundred_rows) { Array.new(100) }
|
22
|
+
|
23
|
+
let(:twenty_rows) { Array.new(20) }
|
24
|
+
|
25
|
+
subject { page_builder }
|
26
|
+
|
27
|
+
it { expect(page_builder.page_size).to eq(page_size) }
|
28
|
+
|
29
|
+
describe '#add and #all' do
|
30
|
+
it 'should page groups' do
|
31
|
+
page_builder.add(hundred_rows)
|
32
|
+
|
33
|
+
expect(page_builder.all.length).to eq(1)
|
34
|
+
|
35
|
+
page_builder.add(twenty_rows)
|
36
|
+
page_builder.add(twenty_rows)
|
37
|
+
|
38
|
+
expect(page_builder.all.length).to eq(2)
|
39
|
+
|
40
|
+
page_builder.add(twenty_rows)
|
41
|
+
|
42
|
+
expect(page_builder.all.length).to eq(3)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should force add and space' do
|
46
|
+
page_builder.add(hundred_rows)
|
47
|
+
|
48
|
+
expect(page_builder.all.length).to eq(1)
|
49
|
+
expect(page_builder.all.first.length).to eq(100)
|
50
|
+
|
51
|
+
page_builder.add(twenty_rows, force: true)
|
52
|
+
page_builder.add(twenty_rows, force: true)
|
53
|
+
|
54
|
+
expect(page_builder.all.length).to eq(1)
|
55
|
+
expect(page_builder.all.first.length).to eq(100 + 20 * 2 + 2)
|
56
|
+
|
57
|
+
first_spacer_index = 100
|
58
|
+
second_spacer_index = 100 + 20 + 1
|
59
|
+
|
60
|
+
expect(page_builder.all.first[first_spacer_index]).to eq(spacer)
|
61
|
+
expect(page_builder.all.first[second_spacer_index]).to eq(spacer)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require './spec/spec_helper'
|
11
|
+
|
12
|
+
describe ::PagedGroups do
|
13
|
+
describe 'README examples' do
|
14
|
+
let(:data) do
|
15
|
+
[
|
16
|
+
[
|
17
|
+
{ id: 1, name: 'Jordan' },
|
18
|
+
{ id: 2, name: 'Pippen' },
|
19
|
+
{ id: 3, name: 'Rodman' },
|
20
|
+
{ id: 4, name: 'Harper' },
|
21
|
+
{ id: 5, name: 'Longley' }
|
22
|
+
],
|
23
|
+
[
|
24
|
+
{ id: 6, name: 'Kukoc' }
|
25
|
+
],
|
26
|
+
[
|
27
|
+
{ id: 7, name: 'Kerr' }
|
28
|
+
],
|
29
|
+
[
|
30
|
+
{ id: 8, name: 'Buechler' }
|
31
|
+
],
|
32
|
+
[
|
33
|
+
{ id: 9, name: 'Wennington' },
|
34
|
+
{ id: 10, name: 'Simpkins' }
|
35
|
+
],
|
36
|
+
[
|
37
|
+
{ id: 11, name: 'Caffey' },
|
38
|
+
{ id: 12, name: 'Edwards' },
|
39
|
+
{ id: 13, name: 'Salley' }
|
40
|
+
]
|
41
|
+
]
|
42
|
+
end
|
43
|
+
|
44
|
+
let(:page_size) { 3 }
|
45
|
+
|
46
|
+
let(:spacer) { { id: nil, name: '' } }
|
47
|
+
|
48
|
+
specify 'Standard use-case example works as advertised' do
|
49
|
+
pages = PagedGroups.builder(page_size: page_size).add(*data).all
|
50
|
+
|
51
|
+
expected_pages = [
|
52
|
+
[
|
53
|
+
{ id: 1, name: 'Jordan' },
|
54
|
+
{ id: 2, name: 'Pippen' },
|
55
|
+
{ id: 3, name: 'Rodman' },
|
56
|
+
{ id: 4, name: 'Harper' },
|
57
|
+
{ id: 5, name: 'Longley' }
|
58
|
+
],
|
59
|
+
[
|
60
|
+
{ id: 6, name: 'Kukoc' },
|
61
|
+
{ id: 7, name: 'Kerr' },
|
62
|
+
{ id: 8, name: 'Buechler' }
|
63
|
+
],
|
64
|
+
[
|
65
|
+
{ id: 9, name: 'Wennington' },
|
66
|
+
{ id: 10, name: 'Simpkins' }
|
67
|
+
],
|
68
|
+
[
|
69
|
+
{ id: 11, name: 'Caffey' },
|
70
|
+
{ id: 12, name: 'Edwards' },
|
71
|
+
{ id: 13, name: 'Salley' }
|
72
|
+
]
|
73
|
+
]
|
74
|
+
|
75
|
+
expect(pages).to eq(expected_pages)
|
76
|
+
end
|
77
|
+
|
78
|
+
specify 'Spacing customization example works as advertised' do
|
79
|
+
pages = PagedGroups.builder(page_size: page_size, space: true, spacer: spacer)
|
80
|
+
.add(*data)
|
81
|
+
.all
|
82
|
+
|
83
|
+
expected_pages = [
|
84
|
+
[
|
85
|
+
{ id: 1, name: 'Jordan' },
|
86
|
+
{ id: 2, name: 'Pippen' },
|
87
|
+
{ id: 3, name: 'Rodman' },
|
88
|
+
{ id: 4, name: 'Harper' },
|
89
|
+
{ id: 5, name: 'Longley' }
|
90
|
+
],
|
91
|
+
[
|
92
|
+
{ id: 6, name: 'Kukoc' },
|
93
|
+
{ id: nil, name: '' },
|
94
|
+
{ id: 7, name: 'Kerr' }
|
95
|
+
],
|
96
|
+
[
|
97
|
+
{ id: 8, name: 'Buechler' },
|
98
|
+
{ id: nil, name: '' },
|
99
|
+
{ id: 9, name: 'Wennington' },
|
100
|
+
{ id: 10, name: 'Simpkins' }
|
101
|
+
],
|
102
|
+
[
|
103
|
+
{ id: 11, name: 'Caffey' },
|
104
|
+
{ id: 12, name: 'Edwards' },
|
105
|
+
{ id: 13, name: 'Salley' }
|
106
|
+
]
|
107
|
+
]
|
108
|
+
|
109
|
+
expect(pages).to eq(expected_pages)
|
110
|
+
end
|
111
|
+
|
112
|
+
specify 'Forcing same page example works as advertised' do
|
113
|
+
other_data = [
|
114
|
+
[
|
115
|
+
{ id: 14, name: 'Jackson' },
|
116
|
+
{ id: 14, name: 'Winters' }
|
117
|
+
]
|
118
|
+
]
|
119
|
+
|
120
|
+
pages = PagedGroups.builder(page_size: page_size, space: true, spacer: spacer)
|
121
|
+
.add(*data)
|
122
|
+
.add(*other_data, force: true)
|
123
|
+
.all
|
124
|
+
|
125
|
+
expected_pages = [
|
126
|
+
[
|
127
|
+
{ id: 1, name: 'Jordan' },
|
128
|
+
{ id: 2, name: 'Pippen' },
|
129
|
+
{ id: 3, name: 'Rodman' },
|
130
|
+
{ id: 4, name: 'Harper' },
|
131
|
+
{ id: 5, name: 'Longley' }
|
132
|
+
],
|
133
|
+
[
|
134
|
+
{ id: 6, name: 'Kukoc' },
|
135
|
+
{ id: nil, name: '' },
|
136
|
+
{ id: 7, name: 'Kerr' }
|
137
|
+
],
|
138
|
+
[
|
139
|
+
{ id: 8, name: 'Buechler' },
|
140
|
+
{ id: nil, name: '' },
|
141
|
+
{ id: 9, name: 'Wennington' },
|
142
|
+
{ id: 10, name: 'Simpkins' }
|
143
|
+
],
|
144
|
+
[
|
145
|
+
{ id: 11, name: 'Caffey' },
|
146
|
+
{ id: 12, name: 'Edwards' },
|
147
|
+
{ id: 13, name: 'Salley' },
|
148
|
+
{ id: nil, name: '' },
|
149
|
+
{ id: 14, name: 'Jackson' },
|
150
|
+
{ id: 14, name: 'Winters' }
|
151
|
+
]
|
152
|
+
]
|
153
|
+
|
154
|
+
expect(pages).to eq(expected_pages)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2019-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require './lib/paged_groups'
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: paged_groups
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matthew Ruggio
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-01-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: guard-rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.8'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.8'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.59'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.59'
|
55
|
+
description: |2
|
56
|
+
This library helps you page grouped-data when the grouped data can have different sizes.
|
57
|
+
It provides a builder that understands how to split pages in a fashion where each page tries to
|
58
|
+
conform to a maximum page size (greedy) but also ensures groups are not split up (atomic.)
|
59
|
+
email:
|
60
|
+
- mruggio@bluemarblepayroll.com
|
61
|
+
executables:
|
62
|
+
- console
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- ".editorconfig"
|
67
|
+
- ".gitignore"
|
68
|
+
- ".rubocop.yml"
|
69
|
+
- ".ruby-version"
|
70
|
+
- ".travis.yml"
|
71
|
+
- CHANGELOG.md
|
72
|
+
- Gemfile
|
73
|
+
- Gemfile.lock
|
74
|
+
- Guardfile
|
75
|
+
- LICENSE
|
76
|
+
- README.md
|
77
|
+
- bin/console
|
78
|
+
- lib/paged_groups.rb
|
79
|
+
- lib/paged_groups/builder.rb
|
80
|
+
- lib/paged_groups/paged_groups.rb
|
81
|
+
- lib/paged_groups/version.rb
|
82
|
+
- paged_groups.gemspec
|
83
|
+
- spec/paged_groups/builder_spec.rb
|
84
|
+
- spec/paged_groups/paged_groups_spec.rb
|
85
|
+
- spec/spec_helper.rb
|
86
|
+
homepage: https://github.com/bluemarblepayroll/paged_groups
|
87
|
+
licenses:
|
88
|
+
- MIT
|
89
|
+
metadata: {}
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: 2.3.8
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubygems_version: 3.0.1
|
106
|
+
signing_key:
|
107
|
+
specification_version: 4
|
108
|
+
summary: Create an evenly paged dataset of un-evenly sized groups
|
109
|
+
test_files:
|
110
|
+
- spec/paged_groups/builder_spec.rb
|
111
|
+
- spec/paged_groups/paged_groups_spec.rb
|
112
|
+
- spec/spec_helper.rb
|