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:
- Each page should roughly has the same number of records (greedy)
- Each group should not be split between pages (atomic)
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.
To install through Rubygems:
gem install install paged_groups
You can also add this to your Gemfile:
bundle add paged_groups
Here is an example data set containing groups with variable number of records:
data = [
[
{ id: 1, name: 'Jordan' },
{ id: 2, name: 'Pippen' },
{ id: 3, name: 'Rodman' },
{ id: 4, name: 'Harper' },
{ id: 5, name: 'Longley' }
],
[
{ id: 6, name: 'Kukoc' }
],
[
{ id: 7, name: 'Kerr' }
],
[
{ id: 8, name: 'Buechler' }
],
[
{ id: 9, name: 'Wennington' },
{ id: 10, name: 'Simpkins' }
],
[
{ id: 11, name: 'Caffey' },
{ id: 12, name: 'Edwards' },
{ id: 13, name: 'Salley' }
]
]
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:
pages = PagedGroups.builder(page_size: 3).add(data).all
Note: Add accepts a two-dimensional array with the first dimension being the group and the second dimension being the record.
our pages
variable should now contain a two-dimensional array where dimension one is page and dimension two is record and would be:
[
[
{ id: 1, name: 'Jordan' },
{ id: 2, name: 'Pippen' },
{ id: 3, name: 'Rodman' },
{ id: 4, name: 'Harper' },
{ id: 5, name: 'Longley' }
],
[
{ id: 6, name: 'Kukoc' },
{ id: 7, name: 'Kerr' },
{ id: 8, name: 'Buechler' }
],
[
{ id: 9, name: 'Wennington' },
{ id: 10, name: 'Simpkins' }
],
[
{ id: 11, name: 'Caffey' },
{ id: 12, name: 'Edwards' },
{ id: 13, name: 'Salley' }
]
]
Notice how each group was kept atomic while each page was tried to be kept as close to the max page size as possible.
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:
pages = PagedGroups.builder(page_size: 3, space: true, spacer: { id: nil, name: '' })
.add(data)
.all
Note: spacer
(boolean) argument is split from space
(object) to allow for spacer to be anything (even false or nil.)
Now, our resulting pages would become:
[
[
{ id: 1, name: 'Jordan' },
{ id: 2, name: 'Pippen' },
{ id: 3, name: 'Rodman' },
{ id: 4, name: 'Harper' },
{ id: 5, name: 'Longley' }
],
[
{ id: 6, name: 'Kukoc' },
{ id: nil, name: '' },
{ id: 7, name: 'Kerr' }
],
[
{ id: 8, name: 'Buechler' },
{ id: nil, name: '' },
{ id: 9, name: 'Wennington' },
{ id: 10, name: 'Simpkins' }
],
[
{ id: 11, name: 'Caffey' },
{ id: 12, name: 'Edwards' },
{ id: 13, name: 'Salley' }
]
]
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:
other_data = [
[
{ id: 14, name: 'Jackson' },
{ id: 14, name: 'Winters' }
]
]
pages = PagedGroups.builder(page_size: 3, space: true, spacer: { id: nil, name: '' })
.add(data)
.add(other_data, force: true)
.all
Note: #add has a fluent interface and can be chained as illustrated above.
Now the result pages would be:
[
[
{ id: 1, name: 'Jordan' },
{ id: 2, name: 'Pippen' },
{ id: 3, name: 'Rodman' },
{ id: 4, name: 'Harper' },
{ id: 5, name: 'Longley' }
],
[
{ id: 6, name: 'Kukoc' },
{ id: nil, name: '' },
{ id: 7, name: 'Kerr' }
],
[
{ id: 8, name: 'Buechler' },
{ id: nil, name: '' },
{ id: 9, name: 'Wennington' },
{ id: 10, name: 'Simpkins' }
],
[
{ id: 11, name: 'Caffey' },
{ id: 12, name: 'Edwards' },
{ id: 13, name: 'Salley' },
{ id: nil, name: '' },
{ id: 14, name: 'Jackson' },
{ id: 14, name: 'Winters' }
]
]
You may run into the case where a group is just so large that it makes more sense to just split it up than keep it atomic. In this case, you can specify a 'limit' option:
pages = PagedGroups.builder(page_size: 50, limit: 150).add(data).all
The above builder can now be interpreted as: Each page should contain a maximum of 50 rows, but split groups over 150 rows
Basic steps to take to get this repository compiling:
- Install Ruby (check paged_groups.gemspec for versions supported)
- Install bundler (gem install bundler)
- Clone the repository (git clone git@github.com:bluemarblepayroll/paged_groups.git)
- Navigate to the root folder (cd paged_groups)
- Install dependencies (bundle)
To execute the test suite run:
bundle exec rspec spec --format documentation
Alternatively, you can have Guard watch for changes:
bundle exec guard
Also, do not forget to run Rubocop:
bundle exec rubocop
Note: ensure you have proper authorization before trying to publish new versions.
After code changes have successfully gone through the Pull Request review process then the following steps should be followed for publishing new versions:
- Merge Pull Request into master
- Update lib/paged_groups/version.rb version number
- Bundle
- Update CHANGELOG.md
- Commit & Push master to remote and ensure CI builds master successfully
- Build the project locally:
gem build paged_groups
- Publish package to NPM:
gem push paged_groups-X.gem
where X is the version to push - Tag master with new version:
git tag <version>
- Push tags remotely:
git push origin --tags
This project is MIT Licensed.