cursed 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: