Skip to content

Commit

Permalink
Merge branch 'master' into 1.9-dev
Browse files Browse the repository at this point in the history
  • Loading branch information
rmosolgo committed Oct 5, 2018
2 parents edf4946 + a4b0961 commit dcd0a97
Show file tree
Hide file tree
Showing 38 changed files with 476 additions and 57 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG-pro.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@

### Bug Fix

### 1.7.13 (2 Oct 2018)

### Breaking Change

- `PunditIntegration`: instead of raising `MutationAuthorizationFailed` when an argument fails authorization, it will send a `GraphQL::UnauthorizedError` to your `Schema.unauthorized_object` hook. (This is what all other authorization failures do.) To retain the previous behavior, in your base mutation, add:

```ruby
def unauthorized_by_pundit(owner, value)
# Raise a runtime error to halt query execution
raise "#{value} failed #{owner}'s auth check"
end
```

Otherwise, customize the handling of this behavior with `Schema.unauthorized_object`.

### Bug Fix

- Auth: mutation arguments which have authorization constraints but _don't_ load an object from the database will have _mutation instance_ passed to the auth check, not the input value.

## 1.7.12 (29 Aug 2018)

### New Features
Expand Down
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,36 @@

### Bug fixes

## 1.8.10 (21 Sep 2018)

### Bug fixes

- When using `loads:` with a nullable mutation input field, allow `null` values to be provided. #1851
- When an invalid Base64 encoded cursor is provided, raise a `GraphQL::ExecutionError` instead of `ArgumentError`. #1855
- Fix an issue with `extras: [:path]` would use the field's `path` instead of the `context`. #1859

### New features

- Add scalar type generator `rails g graphql:scalar` #1847
- Add `#dig` method to `Query::Context` #1861

## 1.8.9 (13 Sep 2018)

### Breaking changes

- When `field ... ` is called with a block and the block has one argument, the field is yielded, but `self` inside the block is _not_ changed to the field. #1843

### New features

- `extras: [...]` can inject values from the field instance #1808
- Add `ISO8601DateTime.time_precision` for customization #1845
- Fix input objects with default values of enum #1827
- `Schema.sync_lazy(value)` hook for intercepting lazy-resolved objects #1784

### Bug fixes

- When a field block is provided with an arity of `1`, yield the field #1843

## 1.8.8 (27 Aug 2018)

### Bug fixes
Expand Down
42 changes: 42 additions & 0 deletions guides/authorization/pundit_integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,48 @@ end

In the example above, `PromoteEmployeePolicy#admin?` will be checked before running the mutation.

#### Custom Policy Class

By default, Pundit uses the mutation's class name to look up a policy. You can override this by defining `self.policy_class` on your mutation:

```ruby
class Mutations::PromoteEmployee < Mutations::BaseMutation
def self.policy_class
::UserPolicy
end

pundit_role :admin
end
```

Now, the mutation will check `UserPolicy#admin?` before running.

Another good approach is to have one policy per mutation. You can implement `self.policy_class` to look up a class _within_ the mutation, for example:

```ruby
class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation
def self.policy_class
# Look up a nested `Policy` constant:
self.const_get(:Policy)
end
end
```

Then, each mutation can define its policy inline, for example:

```ruby
class Mutations::PromoteEmployee < Mutations::BaseMutation
# This will be found by `BaseMutation.policy_class`, defined above:
class Policy
# ...
end

pundit_role :admin
end
```

Now, `Mutations::PromoteEmployee::Policy#admin` will be checked before running the mutation.

#### Authorizing Loaded Objects

Mutations can automatically load and authorize objects by ID using the `loads:` option.
Expand Down
9 changes: 9 additions & 0 deletions guides/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ bundle exec guard

When a file in `lib/` is modified, `guard` will run the corresponding file in `spec`. Guard also respects `# test_via:` comments, so it will run that test when the file changes (if there is no corresponding file by name).

#### Integration tests

You need to pick a specific gemfile from gemfiles/ to run integration tests. For example:

```
BUNDLE_GEMFILE=gemfiles/rails_5.1.gemfile bundle install
BUNDLE_GEMFILE=gemfiles/rails_5.1.gemfile bundle exec rake test TEST=spec/integration/rails/graphql/relay/array_connection_spec.rb
```

#### Other tests

There are system tests for checking ActionCable behavior, use:
Expand Down
2 changes: 1 addition & 1 deletion guides/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ <h2>Install the Gem</h2>
<div class="teaser">
<p>
<a href="{{ site.baseurl}}/getting_started">Get going fast</a> with the <code><a href="https://rubygems.org/gems/graphql">graphql</a></code> gem,
battle-tested and trusted by <a href="https://githubengineering.com/the-github-graphql-api/#open-source">GitHub</a> and <a href="http://www.graphql.com/articles/graphql-at-shopify">Shopify</a>.
battle-tested and trusted by <a href="https://githubengineering.com/the-github-graphql-api/#open-source">GitHub</a>, <a href="http://www.graphql.com/articles/graphql-at-shopify">Shopify</a> and <a href="https://www.kickstarter.com/">Kickstarter</a>.
</p>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions guides/pro/checksums/graphql-pro-1.7.13.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
7d9aae99f424473e4e07aa6e119645027d8da99f062c162cc7f67929fddd886192745383b70e053e2df6ae2176208c5781b92203f8a7194c1390ec13dce36a12
4 changes: 2 additions & 2 deletions guides/relay/object_identification.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class MySchema < GraphQL::Schema

def self.object_from_id(id, query_ctx)
type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id)
# Now, based on `type_name` and `id`
# Now, based on `type_name` and `item_id`
# find an object in your application
# ....
end
Expand All @@ -58,7 +58,7 @@ One requirement for Relay's object management is implementing the `"Node"` inter
To implement the node interface, add {{ "GraphQL::Relay::Node.interface" | api_doc }} to your definition:

```ruby
class Types::PostType < GraphQL::Schema::object
class Types::PostType < GraphQL::Schema::Object
# Implement the "Node" interface for Relay
implements GraphQL::Relay::Node.interface
# ...
Expand Down
1 change: 1 addition & 0 deletions guides/schema/generators.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Several generators will add GraphQL types to your project. Run them with `-h` to
- `rails g graphql:interface`
- `rails g graphql:union`
- `rails g graphql:enum`
- `rails g graphql:scalar`


## Scaffolding Mutations
Expand Down
2 changes: 1 addition & 1 deletion guides/schema/introspection.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ This class an object type definition, so you can override fields or add new ones

The GraphQL spec describes a field which may be added to _any_ selection: `__typename`. It returns the name of the current GraphQL type.

You can add fields like this (or override `__typename`) by creating a custom `DynmaicFields` defintion:
You can add fields like this (or override `__typename`) by creating a custom `DynamicFields` defintion:

```ruby
module Introspection
Expand Down
16 changes: 9 additions & 7 deletions guides/type_definitions/objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,19 +294,21 @@ end

At runtime, the requested runtime object will be passed to the field.

### Field Parameter Default Values
__Custom extras__ are also possible. Any method on your field class can be passed to `extras: [...]`, and the value will be injected into the method. For example, `extras: [:owner]` will inject the object type who owns the field. Any new methods on your custom field class may be used, too.

The field method requires you to pass `null:` keyword argument to determine whether the field is nullable or not. Another field you may want to overrid is `camelize`, which is `true` by default. You can override this behavior by adding a custom field.
### Field Parameter Default Values

The field method requires you to pass `null:` keyword argument to determine whether the field is nullable or not. Another field you may want to overrid is `camelize`, which is `true` by default. You can override this behavior by adding a custom field.

```ruby
class CustomField < GraphQL::Schema::Field
# Add `null: false` and `camelize: false` which provide default values
# in case the caller doesn't pass anything for those arguments.
# **kwargs is a catch-all that will get everything else
# Add `null: false` and `camelize: false` which provide default values
# in case the caller doesn't pass anything for those arguments.
# **kwargs is a catch-all that will get everything else
def initialize(*args, null: false, camelize: false, **kwargs, &block)
# Then, call super _without_ any args, where Ruby will take
# Then, call super _without_ any args, where Ruby will take
# _all_ the args originally passed to this method and pass it to the super method.
super
super
end
end
```
Expand Down
6 changes: 5 additions & 1 deletion javascript_client/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# graphql-ruby-client

## 1.4.0 (12 Apr 2017)
## 1.4.1 (19 Sept 2018)

- Add `connectionOptions` to ActionCableLink #1857

## 1.4.0 (12 Apr 2018)

- Add `PusherLink` for Apollo 2 Subscriptions on Pusher
- Add `OperationStoreLink` for Apollo 2 persisted queries
Expand Down
2 changes: 1 addition & 1 deletion javascript_client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "graphql-ruby-client",
"version": "1.4.0",
"version": "1.4.1",
"description": "JavaScript client for graphql-ruby",
"main": "index.js",
"repository": "https://github.com/rmosolgo/graphql-ruby-client",
Expand Down
9 changes: 7 additions & 2 deletions javascript_client/subscriptions/ActionCableLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,20 @@ function ActionCableLink(options) {
var cable = options.cable
var channelName = options.channelName || "GraphqlChannel"
var actionName = options.actionName || "execute"
var connectionParams = options.connectionParams

if (typeof connectionParams !== "object") {
connectionParams = {}
}

return new ApolloLink(function(operation) {
return new Observable(function(observer) {
var channelId = Math.round(Date.now() + Math.random() * 100000).toString(16)

var subscription = cable.subscriptions.create({
var subscription = cable.subscriptions.create(Object.assign({},{
channel: channelName,
channelId: channelId
}, {
}, connectionParams), {
connected: function() {
this.perform(
actionName,
Expand Down
3 changes: 2 additions & 1 deletion lib/generators/graphql/install_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module Generators
# - base_input_object.rb
# - base_interface.rb
# - base_object.rb
# - base_scalar.rb
# - base_union.rb
# - query_type.rb
# - loaders/
Expand Down Expand Up @@ -92,7 +93,7 @@ def create_folder_structure
create_dir("#{options[:directory]}/types")
template("schema.erb", schema_file_path)

["base_object", "base_enum", "base_input_object", "base_interface", "base_union"].each do |base_type|
["base_object", "base_enum", "base_input_object", "base_interface", "base_scalar", "base_union"].each do |base_type|
template("#{base_type}.erb", "#{options[:directory]}/types/#{base_type}.rb")
end

Expand Down
20 changes: 20 additions & 0 deletions lib/generators/graphql/scalar_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true
require 'generators/graphql/type_generator'

module Graphql
module Generators
# Generate a scalar type by given name.
#
# ```
# rails g graphql:scalar Date
# ```
class ScalarGenerator < TypeGeneratorBase
desc "Create a GraphQL::ScalarType with the given name"
source_root File.expand_path('../templates', __FILE__)

def create_type_file
template "scalar.erb", "#{options[:directory]}/types/#{type_file_name}.rb"
end
end
end
end
4 changes: 4 additions & 0 deletions lib/generators/graphql/templates/base_scalar.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module Types
class BaseScalar < GraphQL::Schema::Scalar
end
end
13 changes: 13 additions & 0 deletions lib/generators/graphql/templates/scalar.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Types
class <%= type_ruby_name.split('::')[-1] %> < Types::BaseScalar
def self.coerce_input(input_value, context)
# Override this to prepare a client-provided GraphQL value for your Ruby code
input_value
end

def self.coerce_result(ruby_value, context)
# Override this to serialize a Ruby value for the GraphQL response
ruby_value.to_s
end
end
end
4 changes: 4 additions & 0 deletions lib/graphql/enum_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ class UnresolvedValueError < GraphQL::Error
def coerce_non_null_input(value_name, ctx)
if @values_by_name.key?(value_name)
@values_by_name.fetch(value_name).value
elsif match_by_value = @values_by_name.find { |k, v| v.value == value_name }
# this is for matching default values, which are "inputs", but they're
# the Ruby value, not the GraphQL string.
match_by_value[1].value
else
nil
end
Expand Down
4 changes: 2 additions & 2 deletions lib/graphql/execution/execute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,11 @@ def resolve_field(object, field_ctx)
# If the returned object is finished, continue to coerce
# and resolve child fields
def continue_or_wait(raw_value, field_type, field_ctx)
if (lazy_method = field_ctx.schema.lazy_method_name(raw_value))
if field_ctx.schema.lazy?(raw_value)
field_ctx.value = Execution::Lazy.new {
inner_value = begin
begin
raw_value.public_send(lazy_method)
field_ctx.schema.sync_lazy(raw_value)
rescue GraphQL::UnauthorizedError => err
field_ctx.schema.unauthorized_object(err)
end
Expand Down
8 changes: 1 addition & 7 deletions lib/graphql/field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -323,13 +323,7 @@ def build_default_resolver

module DefaultLazyResolve
def self.call(obj, args, ctx)
method_name = ctx.schema.lazy_method_name(obj)
next_obj = obj.public_send(method_name)
if ctx.schema.lazy?(next_obj)
call(next_obj, args, ctx)
else
next_obj
end
ctx.schema.sync_lazy(obj)
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/graphql/query/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def interpreter?
# @api private
attr_writer :value

def_delegators :@provided_values, :[], :[]=, :to_h, :key?, :fetch
def_delegators :@provided_values, :[], :[]=, :to_h, :key?, :fetch, :dig
def_delegators :@query, :trace

# @!method [](key)
Expand Down Expand Up @@ -230,7 +230,7 @@ def path
end

def_delegators :@context,
:[], :[]=, :key?, :fetch, :to_h, :namespace,
:[], :[]=, :key?, :fetch, :to_h, :namespace, :dig,
:spawn, :warden, :errors,
:execution_strategy, :strategy, :interpreter?

Expand Down
2 changes: 2 additions & 0 deletions lib/graphql/relay/base_connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ def encode(data)

def decode(data)
@encoder.decode(data, nonce: true)
rescue ArgumentError
raise GraphQL::ExecutionError, "Invalid cursor: #{data.inspect}"
end

# The value passed as `first:`, if there was one. Negative numbers become `0`.
Expand Down
Loading

0 comments on commit dcd0a97

Please sign in to comment.