cursed 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2af86e7660623d2cb8d56b06e3c30c4fd32b4ec3
4
- data.tar.gz: 4b661132d4d8b4551abcdea318bfd9ac5e0a11d1
3
+ metadata.gz: ddec2faf831b8c8a33d1aa2e9bef2f15fee19dae
4
+ data.tar.gz: 0e54d9462488a3db7f7b9558bd137125c1eba0d9
5
5
  SHA512:
6
- metadata.gz: a7d526ef920e90529009fa27fe1bc1808b4565ddd5f57ae4d0dcad2d7fd7f8856165a7c2b3e5b69d957d13863087c52fd03309412569746a669b703a2c2f6b64
7
- data.tar.gz: 2b709005118b8a5158f1f017ae131ae27e9191249a9a3a5fca0afa6882191e4fb4041562b74734ff94328ef02d78ae7a380cdccbc5438d606935aefd9af8cd5c
6
+ metadata.gz: cefdc4be655a383b79111be16db1906d5091d0ec87ef97399493456168e82519ca778d9c340e2d4f6699bf5b094cb13e99c7826fff59bd7ba0736f9d946a3985
7
+ data.tar.gz: 577e3192cd34290aabe16cc8e5f2450099030c82eae83691f63fb7a08add56080be3fffa61de7f5a3a15c6583cc38c6117a979318d4eb2aa4bc91a9a35995b33
@@ -8,6 +8,9 @@ Style/Documentation:
8
8
  Style/ClassAndModuleChildren:
9
9
  Enabled: false
10
10
 
11
+ Style/MethodName:
12
+ Enabled: false
13
+
11
14
  Metrics/LineLength:
12
15
  Max: 120
13
16
 
data/README.md CHANGED
@@ -1,4 +1,6 @@
1
- # Cursed
1
+
2
+
3
+ # Cursed [![CircleCI](https://circleci.com/gh/influitive/cursed.svg?style=svg)](https://circleci.com/gh/influitive/cursed)
2
4
 
3
5
  Cursed is a gem that implements the cursoring pattern in Postgres with the
4
6
  ActiveRecord and Sequel gems. The cursoring pattern is an alternative to
@@ -52,11 +54,16 @@ The `Collection` is enumerable so you can use it as you would us any array
52
54
  ```
53
55
 
54
56
  To generate your next link and previous link merge in the values of `#next_page_params`
55
- and `#prev_page_params` into your URL generator
57
+ and `#prev_page_params` into your URL generator. `#next_page?` and `#prev_page?` can
58
+ be used to determine if there are next or previous pages of records currently.
56
59
 
57
60
  ```erb
58
- <%= link_to 'Previous Page', widgets_path(collection.prev_page_params) %>
59
- <%= link_to 'Next Page', widgets_path(collection.next_page_params) %>
61
+ <% if collection.prev_page? %>
62
+ <%= link_to 'Previous Page', widgets_path(collection.prev_page_params) %>
63
+ <% end %>
64
+ <% if collection.next_page? %>
65
+ <%= link_to 'Next Page', widgets_path(collection.next_page_params) %>
66
+ <% end %>
60
67
  ```
61
68
 
62
69
  ## Development
@@ -3,4 +3,5 @@
3
3
  require 'cursed/version'
4
4
  require 'cursed/cursor'
5
5
  require 'cursed/collection'
6
+ require 'cursed/page'
6
7
  require 'cursed/adapter'
@@ -28,4 +28,15 @@ module Cursed
28
28
  end
29
29
  end
30
30
  end
31
+
32
+ module_function
33
+
34
+ def Adapter(value)
35
+ case value
36
+ when -> (x) { x.is_a?(Class) && x.ancestors.include?(Adapter::Base) } then value
37
+ when Sequel::Dataset then Adapter::Sequel
38
+ when ActiveRecord::Base, ActiveRecord::Relation then Adapter::ActiveRecord
39
+ else raise ArgumentError, "unable to cast #{value.inspect} to Adapter"
40
+ end
41
+ end
31
42
  end
@@ -1,9 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'forwardable'
4
+
3
5
  module Cursed
4
6
  class Collection
7
+ extend Forwardable
5
8
  include Enumerable
6
9
 
10
+ def_delegators :current_page, :each, :maximum_id, :minimum_id, :next_page_params, :prev_page_params
11
+
7
12
  attr_reader :relation, :cursor, :adapter
8
13
 
9
14
  # @param relation [ActiveRecord::Relation or Sequel::Dataset] the relation to cursor on
@@ -12,72 +17,46 @@ module Cursed
12
17
  def initialize(relation:, cursor:, adapter: nil)
13
18
  @relation = relation
14
19
  @cursor = cursor
15
- @adapter = adapter || determine_adapter(relation)
20
+ @adapter = Cursed::Adapter(adapter || relation)
16
21
  end
17
22
 
18
- # Iterates through each element in the current page
19
- def each(*args, &block)
20
- collection.each(*args, &block)
21
- end
22
-
23
- # Invalidates the local cache of the current page - the next call to {#each}
24
- # (or any Enumerable method that calls it) will fetch a fresh page.
23
+ # invalidates the {#current_page}, {#next_page} and {#prev_page}
24
+ # @see Page#invalidate!
25
25
  def invalidate!
26
- @collection = nil
26
+ [prev_page, next_page, current_page].each(&:invalidate!)
27
27
  end
28
28
 
29
- # Returns the maximum cursor index in the current page
30
- def maximum_id
31
- collection.map(&cursor.attribute).max
29
+ # @return [Page] the current page
30
+ def current_page
31
+ @current_page ||= build_page(cursor)
32
32
  end
33
33
 
34
- # Returns the minimum cursor index in the current page
35
- def minimum_id
36
- collection.map(&cursor.attribute).min
34
+ # @return [Page] the page following this one
35
+ def next_page
36
+ @next_page ||= build_page(current_page.next_page_cursor)
37
37
  end
38
38
 
39
- # Returns a hash of parameters which should be used for generating a next
40
- # page link.
41
- # @return [Hash] a hash containing any combination of :before, :after, :limit
42
- def next_page_params
43
- if cursor.forward?
44
- after_maximum_params
45
- else
46
- before_minimum_params
47
- end
39
+ # @return [Page] the page previous to this one
40
+ def prev_page
41
+ @prev_page ||= build_page(current_page.prev_page_cursor)
48
42
  end
49
43
 
50
- # Returns a hash of parameters which should be used for generating a previous
51
- # page link.
52
- # @return [Hash] a hash containing any combination of :before, :after, :limit
53
- def prev_page_params
54
- if cursor.forward?
55
- before_minimum_params
56
- else
57
- after_maximum_params
58
- end
44
+ # @return [Boolean] true if there are records that follow records in the current page
45
+ def next_page?
46
+ next_page.any?
59
47
  end
60
48
 
61
- private
62
-
63
- def collection
64
- @collection ||= adapter.new(relation).apply_to(cursor).to_a
49
+ # @return [Boolean] true if there are records that preceede records in the current page
50
+ def prev_page?
51
+ prev_page.any?
65
52
  end
66
53
 
67
- def determine_adapter(relation)
68
- case relation
69
- when Sequel::Dataset then Adapter::Sequel
70
- when ActiveRecord::Base, ActiveRecord::Relation then Adapter::ActiveRecord
71
- else raise ArgumentError, "unable to determine adapter for #{relation.inspect}"
72
- end
73
- end
54
+ private
74
55
 
75
- def after_maximum_params
76
- { after: maximum_id, limit: cursor.clamped_limit }
77
- end
56
+ attr_reader :relation, :cursor, :adapter
78
57
 
79
- def before_minimum_params
80
- { before: minimum_id, limit: cursor.clamped_limit }
58
+ def build_page(cursor)
59
+ Page.new(relation: adapter.new(relation.dup).apply_to(cursor), cursor: cursor)
81
60
  end
82
61
  end
83
62
  end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cursed
4
+ class Page
5
+ include Enumerable
6
+
7
+ def initialize(relation:, cursor:)
8
+ @relation = relation
9
+ @cursor = cursor
10
+ end
11
+
12
+ # Iterates through each element in the current page
13
+ def each(*args, &block)
14
+ collection.each(*args, &block)
15
+ end
16
+
17
+ # Invalidates the local cache of the current page - the next call to {#each}
18
+ # (or any Enumerable method that calls it) will fetch a fresh page.
19
+ def invalidate!
20
+ @collection = nil
21
+ @count = nil
22
+ end
23
+
24
+ # @return [Integer] the number of records in this page
25
+ def count
26
+ @count ||= @collection.try(:length) || relation.count
27
+ end
28
+
29
+ # @return [Boolean] if there are records on this page
30
+ def any?
31
+ count > 0
32
+ end
33
+
34
+ # @return [Integer] the maximum cursor index in the current page
35
+ def maximum_id
36
+ collection.map(&cursor.attribute).max
37
+ end
38
+
39
+ # @return [Integer] the minimum cursor index in the current page
40
+ def minimum_id
41
+ collection.map(&cursor.attribute).min
42
+ end
43
+
44
+ # Returns a hash of parameters which should be used for generating a next
45
+ # page link.
46
+ # @return [Hash] a hash containing any combination of :before, :after, :limit
47
+ def next_page_params
48
+ if cursor.forward?
49
+ after_maximum_params
50
+ else
51
+ before_minimum_params
52
+ end
53
+ end
54
+
55
+ # Returns a hash of parameters which should be used for generating a previous
56
+ # page link.
57
+ # @return [Hash] a hash containing any combination of :before, :after, :limit
58
+ def prev_page_params
59
+ if cursor.forward?
60
+ before_minimum_params
61
+ else
62
+ after_maximum_params
63
+ end
64
+ end
65
+
66
+ # @return [Cursor] with the values to fetch the next page
67
+ def next_page_cursor
68
+ params = next_page_params.merge(
69
+ maximum: cursor.maximum,
70
+ direction: cursor.direction,
71
+ attribute: cursor.attribute
72
+ )
73
+
74
+ Cursor.new(params)
75
+ end
76
+
77
+ # @return [Cursor] with the values to fetch the previous page
78
+ def prev_page_cursor
79
+ params = prev_page_params.merge(
80
+ maximum: cursor.maximum,
81
+ direction: cursor.direction,
82
+ attribute: cursor.attribute
83
+ )
84
+
85
+ Cursor.new(params)
86
+ end
87
+
88
+ private
89
+
90
+ attr_reader :relation, :cursor
91
+
92
+ def collection
93
+ @collection ||= relation.to_a
94
+ end
95
+
96
+ def after_maximum_params
97
+ { after: maximum_id || cursor.after, limit: cursor.clamped_limit }
98
+ end
99
+
100
+ def before_minimum_params
101
+ { before: minimum_id || cursor.before, limit: cursor.clamped_limit }
102
+ end
103
+ end
104
+ end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Cursed
3
- VERSION = '0.1.0'
3
+ VERSION = '0.2.0'
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cursed
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Will Howard
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-30 00:00:00.000000000 Z
11
+ date: 2016-07-04 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -37,6 +37,7 @@ files:
37
37
  - lib/cursed/adapter/sequel.rb
38
38
  - lib/cursed/collection.rb
39
39
  - lib/cursed/cursor.rb
40
+ - lib/cursed/page.rb
40
41
  - lib/cursed/version.rb
41
42
  homepage: https://www.github.com/influitive/cursed
42
43
  licenses: