graphql-relay 0.8.1 → 0.9.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: 263e38a956354abcb55e391c5bdd4b43367179db
4
- data.tar.gz: ab2a35a584890faac7dac968c6c003ae5386168c
3
+ metadata.gz: 5fb50f9e3030be285c1be81b0f0e302919a49c2f
4
+ data.tar.gz: fd869d0de9f2f47085c12e94c093a0ca791ee004
5
5
  SHA512:
6
- metadata.gz: 2ec4c12612f83c3cfc806a1c8906aef9030354cb54701e5889eca89de0e9b02f75143feebaabc6e5779bf8e5c41fe3fa666f22ec7bcc5f8f286ed59d13b8e813
7
- data.tar.gz: 90be05474c434fcdc4e8ec367e592ed063c0d66fe920e9d1dc7962f5643d8a2b73b1d0c71f4f3afc61175ea5f71d53c6b357a2eb88809b3a71a6b7907484a93d
6
+ metadata.gz: 68734a2849039b20d4fcfe57855217247aebbf47c3252a22b96993e26880f0f4b739f41c75c203ce32fcf4e5e15d7b0c0a2b3f2f447ae838a21f6abfaf6617f2
7
+ data.tar.gz: c76dd82ddc3eef84a26fd1ab949b9e4483d9d2c81ea6c41e1c08497fc900c0b476b55b2d157b8091aab1906f438317822c1f4718f56b1eec00f11d54e62f8e5f
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2016 Robert Mosolgo
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -198,8 +198,6 @@ First, define the custom connection:
198
198
  ```ruby
199
199
  class SetConnection < BaseConnection
200
200
  # derive a cursor from `item`
201
- # (it is used to find next & previous nodes,
202
- # so it should include `order`)
203
201
  def cursor_from_node(item)
204
202
  # ...
205
203
  end
@@ -344,10 +342,10 @@ https://medium.com/@gauravtiwari/graphql-and-relay-on-rails-first-relay-powered-
344
342
 
345
343
  ## Todo
346
344
 
347
- - Allow custom defined ID scheme
348
345
  - Allow custom edge fields (per connection type)
349
346
  - `GlobalNodeIdentification.to_global_id` should receive the type name and _object_, not `id`. (Or, maintain the "`type_name, id` in, `type_name, id` out" pattern?)
350
347
  - Make GlobalId a property of the schema, not a global
348
+ - Reduce duplication in ArrayConnection / RelationConnection
351
349
 
352
350
  ## More Resources
353
351
 
@@ -1,13 +1,9 @@
1
1
  module GraphQL
2
2
  module Relay
3
3
  class ArrayConnection < BaseConnection
4
- # Just to encode data in the cursor, use something that won't conflict
5
- CURSOR_SEPARATOR = "---"
6
-
7
4
  def cursor_from_node(item)
8
- idx = sliced_nodes.find_index(item)
9
- cursor_parts = [(order || "none"), idx]
10
- Base64.strict_encode64(cursor_parts.join(CURSOR_SEPARATOR))
5
+ idx = starting_offset + sliced_nodes.find_index(item) + 1
6
+ Base64.strict_encode64(idx.to_s)
11
7
  end
12
8
 
13
9
  private
@@ -16,10 +12,7 @@ module GraphQL
16
12
  def paged_nodes
17
13
  @paged_nodes = begin
18
14
  items = sliced_nodes
19
- limit = [first, last, max_page_size].compact.min
20
- first && items = items.first(limit)
21
- last && items.length > last && items.last(limit)
22
- items
15
+ items.first(limit)
23
16
  end
24
17
  end
25
18
 
@@ -27,22 +20,43 @@ module GraphQL
27
20
  def sliced_nodes
28
21
  @sliced_nodes ||= begin
29
22
  items = object
30
- if order
31
- # Remove possible direction marker:
32
- order_name = order.sub(/^-/, '')
33
- items = items.sort_by { |item| item.public_send(order_name) }
34
- order.start_with?("-") && items = items.reverse
35
- end
36
- after && items = items[(1 + index_from_cursor(after))..-1]
37
- before && items = items[0..(index_from_cursor(before) - 1)]
38
- items
23
+ items[starting_offset..-1]
39
24
  end
40
25
  end
41
26
 
42
27
  def index_from_cursor(cursor)
43
- decoded = Base64.decode64(cursor)
44
- order, index = decoded.split(CURSOR_SEPARATOR)
45
- index.to_i
28
+ Base64.decode64(cursor).to_i
29
+ end
30
+
31
+ def starting_offset
32
+ @starting_offset = if before
33
+ [previous_offset, 0].max
34
+ else
35
+ previous_offset
36
+ end
37
+ end
38
+
39
+ def previous_offset
40
+ @initial_offset ||= if before
41
+ index_from_cursor(before) - last - 1
42
+ elsif after
43
+ index_from_cursor(after)
44
+ else
45
+ 0
46
+ end
47
+ end
48
+
49
+ def limit
50
+ @limit ||= if first
51
+ [first, max_page_size].compact.min
52
+ else
53
+ last_limit = if previous_offset <= 0
54
+ previous_offset + last
55
+ else
56
+ last
57
+ end
58
+ [last_limit, max_page_size].compact.min
59
+ end
46
60
  end
47
61
  end
48
62
  BaseConnection.register_connection_implementation(Array, ArrayConnection)
@@ -12,7 +12,6 @@ module GraphQL
12
12
  [:after, GraphQL::STRING_TYPE],
13
13
  [:last, GraphQL::INT_TYPE],
14
14
  [:before, GraphQL::STRING_TYPE],
15
- [:order, GraphQL::STRING_TYPE],
16
15
  ]
17
16
 
18
17
  DEFAULT_ARGUMENTS = ARGUMENT_DEFINITIONS.reduce({}) do |memo, arg_defn|
@@ -31,8 +30,7 @@ module GraphQL
31
30
  # @return [GraphQL::Field] A field which serves a connections
32
31
  def self.create(underlying_field, max_page_size: nil)
33
32
  underlying_field.arguments = DEFAULT_ARGUMENTS.merge(underlying_field.arguments)
34
- # TODO: make a public API on GraphQL::Field to expose this proc
35
- original_resolve = underlying_field.instance_variable_get(:@resolve_proc)
33
+ original_resolve = underlying_field.resolve_proc
36
34
  underlying_field.resolve = get_connection_resolve(underlying_field.name, original_resolve, max_page_size: max_page_size)
37
35
  underlying_field
38
36
  end
@@ -1,16 +1,9 @@
1
1
  module GraphQL
2
2
  module Relay
3
3
  class RelationConnection < BaseConnection
4
- DEFAULT_ORDER = "id"
5
-
6
4
  def cursor_from_node(item)
7
- order_value = item.public_send(order_name)
8
- cursor_parts = [order, order_value]
9
- Base64.strict_encode64(cursor_parts.join(CURSOR_SEPARATOR))
10
- end
11
-
12
- def order
13
- @order ||= (super || DEFAULT_ORDER)
5
+ offset = starting_offset + paged_nodes_array.index(item) + 1
6
+ Base64.strict_encode64(offset.to_s)
14
7
  end
15
8
 
16
9
  def has_next_page
@@ -26,12 +19,9 @@ module GraphQL
26
19
 
27
20
  # apply first / last limit results
28
21
  def paged_nodes
29
- @paged_nodes = begin
22
+ @paged_nodes ||= begin
30
23
  items = sliced_nodes
31
- limit = [first, last, max_page_size].compact.min
32
- first && items = items.first(limit)
33
- last && items.count > last && items = items.last(limit)
34
- items
24
+ items.limit(limit)
35
25
  end
36
26
  end
37
27
 
@@ -39,61 +29,51 @@ module GraphQL
39
29
  def sliced_nodes
40
30
  @sliced_nodes ||= begin
41
31
  items = object
42
-
43
- if order
44
- items = items.order(items.table[order_name].public_send(order_direction))
45
- end
46
-
47
- if after
48
- _o, order_value = slice_from_cursor(after)
49
- direction_marker = order_direction == :asc ? ">" : "<"
50
- where_condition = create_order_condition(table_name, order_name, order_value, direction_marker)
51
- items = items.where(where_condition)
52
- end
53
-
54
- if before
55
- _o, order_value = slice_from_cursor(before)
56
- direction_marker = order_direction == :asc ? "<" : ">"
57
- where_condition = create_order_condition(table_name, order_name, order_value, direction_marker)
58
- items = items.where(where_condition)
59
- end
60
-
61
- items
32
+ items.offset(starting_offset)
62
33
  end
63
34
  end
64
35
 
65
- def slice_from_cursor(cursor)
66
- decoded = Base64.decode64(cursor)
67
- order, order_value = decoded.split(CURSOR_SEPARATOR)
36
+ def offset_from_cursor(cursor)
37
+ Base64.decode64(cursor).to_i
68
38
  end
69
39
 
70
- # Remove possible direction marker:
71
- def order_name
72
- @order_name ||= order.sub(/^-/, '')
73
- end
74
-
75
- # Check for direction marker
76
- def order_direction
77
- @order_direction ||= order.start_with?("-") ? :desc : :asc
40
+ def starting_offset
41
+ @initial_offset ||= begin
42
+ if before
43
+ [previous_offset, 0].max
44
+ else
45
+ previous_offset
46
+ end
47
+ end
78
48
  end
79
49
 
80
- def table_name
81
- @table_name ||= object.table.table_name
50
+ # Offset from the previous selection, if there was one
51
+ # Otherwise, zero
52
+ def previous_offset
53
+ @previous_offset ||= if after
54
+ offset_from_cursor(after)
55
+ elsif before
56
+ offset_from_cursor(before) - last - 1
57
+ else
58
+ 0
59
+ end
82
60
  end
83
61
 
84
- # When creating the where constraint, cast the value to correct column data type so
85
- # active record can send it in correct format to db
86
- def create_order_condition(table, column, value, direction_marker)
87
- table_name = ActiveRecord::Base.connection.quote_table_name(table)
88
- name = ActiveRecord::Base.connection.quote_column_name(column)
89
- if ActiveRecord::VERSION::MAJOR == 5
90
- casted_value = object.table.able_to_type_cast? ? object.table.type_cast_for_database(column, value) : value
91
- elsif ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR >= 2
92
- casted_value = object.table.engine.columns_hash[column].cast_type.type_cast_from_user(value)
62
+ def limit
63
+ @limit ||= if first
64
+ [first, max_page_size].compact.min
93
65
  else
94
- casted_value = object.table.engine.columns_hash[column].type_cast(value)
66
+ last_limit = if previous_offset <= 0
67
+ previous_offset + last
68
+ else
69
+ last
70
+ end
71
+ [last_limit, max_page_size].compact.min
95
72
  end
96
- ["#{table_name}.#{name} #{direction_marker} ?", casted_value]
73
+ end
74
+
75
+ def paged_nodes_array
76
+ @paged_nodes_array ||= paged_nodes.to_a
97
77
  end
98
78
  end
99
79
 
@@ -1,5 +1,5 @@
1
1
  module GraphQL
2
2
  module Relay
3
- VERSION = '0.8.1'
3
+ VERSION = '0.9.0'
4
4
  end
5
5
  end
@@ -11,9 +11,9 @@ describe GraphQL::Relay::ArrayConnection do
11
11
  end
12
12
  describe "results" do
13
13
  let(:query_string) {%|
14
- query getShips($first: Int, $after: String, $last: Int, $before: String, $order: String, $nameIncludes: String){
14
+ query getShips($first: Int, $after: String, $last: Int, $before: String, $nameIncludes: String){
15
15
  rebels {
16
- ships(first: $first, after: $after, last: $last, before: $before, order: $order, nameIncludes: $nameIncludes) {
16
+ ships(first: $first, after: $after, last: $last, before: $before, nameIncludes: $nameIncludes) {
17
17
  edges {
18
18
  cursor
19
19
  node {
@@ -48,33 +48,23 @@ describe GraphQL::Relay::ArrayConnection do
48
48
  end
49
49
 
50
50
  it 'slices the result' do
51
- result = query(query_string, "first" => 3)
52
- assert_equal(["X-Wing", "Y-Wing", "A-Wing"], get_names(result))
51
+ result = query(query_string, "first" => 1)
52
+ assert_equal(["X-Wing"], get_names(result))
53
53
 
54
54
  # After the last result, find the next 2:
55
55
  last_cursor = get_last_cursor(result)
56
56
 
57
57
  result = query(query_string, "after" => last_cursor, "first" => 2)
58
- assert_equal(["Millenium Falcon", "Home One"], get_names(result))
59
-
60
- result = query(query_string, "before" => last_cursor, "last" => 2)
61
- assert_equal(["X-Wing", "Y-Wing"], get_names(result))
62
- end
63
-
64
- it 'paginates with order' do
65
- result = query(query_string, "first" => 2, "order" => "name")
66
- assert_equal(["A-Wing", "Home One"], get_names(result))
58
+ assert_equal(["Y-Wing", "A-Wing"], get_names(result))
67
59
 
68
60
  # After the last result, find the next 2:
69
- last_cursor = result["data"]["rebels"]["ships"]["edges"].last["cursor"]
61
+ last_cursor = get_last_cursor(result)
70
62
 
71
- result = query(query_string, "after" => last_cursor, "first" => 2, "order" => "name")
72
- assert_equal(["Millenium Falcon", "X-Wing"], get_names(result))
73
- end
63
+ result = query(query_string, "after" => last_cursor, "first" => 2)
64
+ assert_equal(["Millenium Falcon", "Home One"], get_names(result))
74
65
 
75
- it 'paginates with reverse order' do
76
- result = query(query_string, "first" => 2, "order" => "-name")
77
- assert_equal(["Y-Wing", "X-Wing"], get_names(result))
66
+ result = query(query_string, "before" => last_cursor, "last" => 2)
67
+ assert_equal(["X-Wing", "Y-Wing"], get_names(result))
78
68
  end
79
69
 
80
70
  it 'applies custom arguments' do
@@ -83,7 +73,7 @@ describe GraphQL::Relay::ArrayConnection do
83
73
  assert_equal(2, names.length)
84
74
 
85
75
  after = get_last_cursor(result)
86
- result = query(query_string, "nameIncludes" => "Wing", "after" => after)
76
+ result = query(query_string, "nameIncludes" => "Wing", "after" => after, "first" => 3)
87
77
  names = get_names(result)
88
78
  assert_equal(1, names.length)
89
79
  end
@@ -15,9 +15,9 @@ describe GraphQL::Relay::PageInfo do
15
15
  }
16
16
 
17
17
  let(:query_string) {%|
18
- query getShips($first: Int, $after: String, $last: Int, $before: String, $order: String, $nameIncludes: String){
18
+ query getShips($first: Int, $after: String, $last: Int, $before: String, $nameIncludes: String){
19
19
  empire {
20
- bases(first: $first, after: $after, last: $last, before: $before, order: $order, nameIncludes: $nameIncludes) {
20
+ bases(first: $first, after: $after, last: $last, before: $before, nameIncludes: $nameIncludes) {
21
21
  edges {
22
22
  cursor
23
23
  }
@@ -12,9 +12,9 @@ describe GraphQL::Relay::RelationConnection do
12
12
 
13
13
  describe "results" do
14
14
  let(:query_string) {%|
15
- query getShips($first: Int, $after: String, $last: Int, $before: String, $order: String, $nameIncludes: String){
15
+ query getShips($first: Int, $after: String, $last: Int, $before: String, $nameIncludes: String){
16
16
  empire {
17
- bases(first: $first, after: $after, last: $last, before: $before, order: $order, nameIncludes: $nameIncludes) {
17
+ bases(first: $first, after: $after, last: $last, before: $before, nameIncludes: $nameIncludes) {
18
18
  ... basesConnection
19
19
  }
20
20
  }
@@ -58,27 +58,16 @@ describe GraphQL::Relay::RelationConnection do
58
58
  assert_equal(["Headquarters"], get_names(result))
59
59
 
60
60
  last_cursor = get_last_cursor(result)
61
+
61
62
  result = query(query_string, "before" => last_cursor, "last" => 1)
62
63
  assert_equal(["Shield Generator"], get_names(result))
63
64
 
64
65
  result = query(query_string, "before" => last_cursor, "last" => 2)
65
66
  assert_equal(["Death Star", "Shield Generator"], get_names(result))
66
- end
67
-
68
- it 'paginates with reverse order' do
69
- result = query(query_string, "first" => 2, "order" => "-name")
70
- assert_equal(["Shield Generator", "Headquarters"], get_names(result))
71
- end
72
-
73
- it 'paginates with order' do
74
- result = query(query_string, "first" => 2, "order" => "name")
75
- assert_equal(["Death Star", "Headquarters"], get_names(result))
76
67
 
77
- # After the last result, find the next 2:
78
- last_cursor = get_last_cursor(result)
68
+ result = query(query_string, "before" => last_cursor, "last" => 10)
69
+ assert_equal(["Death Star", "Shield Generator"], get_names(result))
79
70
 
80
- result = query(query_string, "after" => last_cursor, "first" => 2, "order" => "name")
81
- assert_equal(["Shield Generator"], get_names(result))
82
71
  end
83
72
 
84
73
  it "applies custom arguments" do
@@ -138,7 +127,7 @@ describe GraphQL::Relay::RelationConnection do
138
127
  let(:query_string) {%|
139
128
  {
140
129
  empire {
141
- basesClone {
130
+ basesClone(first: 10) {
142
131
  edges {
143
132
  node {
144
133
  name
@@ -154,12 +143,12 @@ describe GraphQL::Relay::RelationConnection do
154
143
  end
155
144
  end
156
145
 
157
- describe "overriding default order" do
146
+ describe "custom ordering" do
158
147
  let(:query_string) {%|
159
148
  query getBases {
160
149
  empire {
161
- basesByName { ... basesFields }
162
- bases { ... basesFields }
150
+ basesByName(first: 30) { ... basesFields }
151
+ bases(first: 30) { ... basesFields }
163
152
  }
164
153
  }
165
154
  fragment basesFields on BaseConnection {
@@ -87,6 +87,13 @@ Faction = GraphQL::ObjectType.define do
87
87
  connection :basesClone, BaseType.connection_type
88
88
  connection :basesByName, BaseType.connection_type, property: :bases do
89
89
  argument :order, types.String, default_value: "name"
90
+ resolve -> (obj, args, ctx) {
91
+ if args[:order].present?
92
+ obj.bases.order(args[:order])
93
+ else
94
+ obj.bases
95
+ end
96
+ }
90
97
  end
91
98
 
92
99
  connection :basesWithMaxLimitRelation, BaseType.connection_type, max_page_size: 2 do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-relay
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-22 00:00:00.000000000 Z
11
+ date: 2016-03-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql
@@ -200,6 +200,7 @@ executables: []
200
200
  extensions: []
201
201
  extra_rdoc_files: []
202
202
  files:
203
+ - MIT-LICENSE
203
204
  - README.md
204
205
  - lib/graphql/relay.rb
205
206
  - lib/graphql/relay/array_connection.rb