graphql-relay 0.8.1 → 0.9.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: 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