cursory 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1e5f54cdbbb19b88e5715f8629e0f34031f55521
4
+ data.tar.gz: 3308d79fe12e76fbddece661d10f48ccaf2021a2
5
+ SHA512:
6
+ metadata.gz: 2784d3c0cccdfdebd345edc4fd9cf839eae23e010a5c1640bd7d246e0032561918d3d009f9ef265bb4660389141a1a3f056db91f1db5324fb18f8f7dff67dbc6
7
+ data.tar.gz: e342fcfdb131cd256bd04c03a64102e4fd4821442baba46db536686cf8c9dd342f892540c26b35680c8e7d0c96e7189010be5cc21d28e6c08dee01ce6e0bf3ea
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cursory.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Simon Hildebrandt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # Cursory
2
+
3
+ Cursory is generic paginator framework, intended to provide cursor-based seek-value and offset-based pagination behaviour, particularly for APIs.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'cursory'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install cursory
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Development
26
+
27
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
28
+
29
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
30
+
31
+ ## Contributing
32
+
33
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Trunkplatform/cursory.
34
+
35
+
36
+ ## License
37
+
38
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "cursory"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/cursory.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cursory/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "cursory"
8
+ spec.version = Cursory::VERSION
9
+ spec.authors = ["Simon Hildebrandt"]
10
+ spec.email = ["simon@trunkplatform.com"]
11
+
12
+ spec.summary = %q{Paginator framework with out-of-the-box support for Mongoid.}
13
+ spec.description = %q{Paginator framework with out-of-the-box support for Mongoid.}
14
+ spec.homepage = "https://github.com/Trunkplatform/cursory"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.12"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "rspec", "~> 3.0"
25
+ end
@@ -0,0 +1,155 @@
1
+ require 'json'
2
+ require 'base64'
3
+
4
+ class Cursory::Base
5
+ attr_accessor *%i{criteria sort limit offset cursor}
6
+
7
+ MAX_LIMIT = 100
8
+ SORT_KEY, LIMIT_KEY, CURSOR_KEY, OFFSET_KEY = %i{sort limit cursor offset}
9
+
10
+ def initialize criteria, params
11
+ @criteria = criteria
12
+ @sort = params[SORT_KEY]
13
+ @limit = params[LIMIT_KEY]
14
+ @offset = params[OFFSET_KEY]
15
+ @cursor = params[CURSOR_KEY]
16
+ end
17
+
18
+ def page
19
+ { total_count: count, items: search_results, self: current_cursor }.tap do |p|
20
+ catch(:no_more_results) do
21
+ p[:next] = next_cursor
22
+ end
23
+ end
24
+ end
25
+
26
+ def search
27
+ constrained_search.send(*search_type)
28
+ end
29
+
30
+ def search_type
31
+ fail UnimplementedError
32
+ end
33
+
34
+ def clamped_offset
35
+ [0, offset.to_i].max
36
+ end
37
+
38
+ def constrained_search
39
+ fail UnimplementedError
40
+ end
41
+
42
+ def search_results
43
+ @results ||= uncached_search
44
+ end
45
+
46
+ def uncached_search
47
+ fail UnimplementedError
48
+ end
49
+
50
+ def current_cursor
51
+ cursor || render_cursor
52
+ end
53
+
54
+ def record_for_next_cursor
55
+ search_results[clamped_limit-1] or throw(:no_more_results)
56
+ end
57
+
58
+ def next_cursor
59
+ if record_for_next_cursor
60
+ render_cursor cursor_data(id: record_for_next_cursor.id.to_s)
61
+ end
62
+ end
63
+
64
+ def count
65
+ @count ||= criteria.count
66
+ end
67
+
68
+ def uncached_count
69
+ fail UnimplementedError
70
+ end
71
+
72
+ def sort_keys
73
+ sort || model_sort || ''
74
+ end
75
+
76
+ def model
77
+ fail UnimplementedError
78
+ end
79
+
80
+ def model_sort
81
+ model.respond_to?(:default_sort_key) && model.default_sort_key
82
+ end
83
+
84
+ def order_keys
85
+ sort_keys.split(',').map{ |k| decompose_order_key(k) } + [[:id, 'asc']]
86
+ end
87
+
88
+ def decompose_order_key(k)
89
+ [ k.gsub(/\A[-+]?/,'').to_sym, k.start_with?('-') ? 'desc' : 'asc' ]
90
+ end
91
+
92
+ def order_clause
93
+ fail UnimplementedError
94
+ end
95
+
96
+ def clamped_limit
97
+ [1, limit.to_i, MAX_LIMIT].sort[1]
98
+ end
99
+
100
+ def cursor_clauses
101
+ fail UnimplementedError
102
+ end
103
+
104
+ def cursor_clause_set
105
+ keys = []
106
+ Enumerator.new do |y|
107
+ order_keys.each do |key|
108
+ y.yield cursor_clause(key, keys).reduce(&:merge)
109
+ keys << key
110
+ end
111
+ end
112
+ end
113
+
114
+ def cursor_clause(key, keys=[])
115
+ name, direction = key
116
+ Enumerator.new do |y|
117
+ keys.each do |name, direction|
118
+ y.yield clause_for_key(name, 'eq')
119
+ end
120
+ y.yield clause_for_key(name, direction)
121
+ end
122
+ end
123
+
124
+ def clause_for_key key, direction
125
+ fail UnimplementedError
126
+ end
127
+
128
+ def cursor_object
129
+ @cursor_object ||= uncached_cursor_object
130
+ end
131
+
132
+ def uncached_cursor_object
133
+ fail UnimplementedError
134
+ end
135
+
136
+ def render_cursor(data={})
137
+ ::Base64.urlsafe_encode64(JSON.dump(data))
138
+ end
139
+
140
+ def parsed_cursor
141
+ JSON.parse(::Base64.urlsafe_decode64(cursor || 'e30='))
142
+ rescue ArgumentError, JSON::ParserError
143
+ raise InvalidCursorError
144
+ end
145
+
146
+ def cursor_id
147
+ cursor_data['id']
148
+ end
149
+
150
+ def cursor_data(overrides={})
151
+ (parsed_cursor || {}).merge(overrides)
152
+ end
153
+
154
+ class InvalidCursorError < StandardError; end
155
+ end
@@ -0,0 +1,55 @@
1
+ require 'cursory/base'
2
+
3
+ module Cursory
4
+ class Mongoid < Base
5
+ def search_type
6
+ if cursor
7
+ [:where, cursor_clauses]
8
+ else
9
+ [:skip, clamped_offset]
10
+ end
11
+ end
12
+
13
+ def constrained_search
14
+ criteria.order_by(order_clause).limit(clamped_limit)
15
+ end
16
+
17
+ def uncached_count
18
+ criteria.count
19
+ end
20
+
21
+ def model
22
+ criteria.klass
23
+ end
24
+
25
+ def uncached_search
26
+ search.to_a
27
+ end
28
+
29
+ def order_clause
30
+ order_keys.inject({}) { |hash, (key, value)| hash[key] = value; hash }
31
+ end
32
+
33
+ def cursor_clauses
34
+ if cursor_id
35
+ { '$or' => cursor_clause_set.to_a }
36
+ end
37
+ end
38
+
39
+ def clause_for_key key, direction
40
+ { key.to_sym => { key_for_direction(direction) => cursor_object.send(key) } }
41
+ end
42
+
43
+ def key_for_direction(d)
44
+ {
45
+ 'eq' => '$eq',
46
+ 'asc' => '$gt',
47
+ 'desc' => '$lt'
48
+ }[d]
49
+ end
50
+
51
+ def uncached_cursor_object
52
+ criteria.klass.find(cursor_id)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,3 @@
1
+ module Cursory
2
+ VERSION = "0.1.0"
3
+ end
data/lib/cursory.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "cursory/version"
2
+
3
+ module Cursory
4
+ # Your code goes here...
5
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cursory
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Simon Hildebrandt
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-09-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: Paginator framework with out-of-the-box support for Mongoid.
56
+ email:
57
+ - simon@trunkplatform.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - Gemfile
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - bin/console
69
+ - bin/setup
70
+ - cursory.gemspec
71
+ - lib/cursory.rb
72
+ - lib/cursory/base.rb
73
+ - lib/cursory/mongoid.rb
74
+ - lib/cursory/version.rb
75
+ homepage: https://github.com/Trunkplatform/cursory
76
+ licenses:
77
+ - MIT
78
+ metadata: {}
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.5.1
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: Paginator framework with out-of-the-box support for Mongoid.
99
+ test_files: []