search_object_graphql 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +12 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +3 -2
  5. data/CHANGELOG.md +5 -0
  6. data/Gemfile +2 -0
  7. data/README.md +38 -29
  8. data/Rakefile +3 -1
  9. data/example/.ruby-version +1 -1
  10. data/example/Gemfile +3 -1
  11. data/example/README.md +3 -3
  12. data/example/Rakefile +2 -0
  13. data/example/app/controllers/application_controller.rb +2 -0
  14. data/example/app/controllers/graphql_controller.rb +22 -18
  15. data/example/app/graphql/mutations/.keep +0 -0
  16. data/example/app/graphql/resolvers/base_search_resolver.rb +2 -0
  17. data/example/app/graphql/resolvers/category_search.rb +5 -3
  18. data/example/app/graphql/resolvers/post_search.rb +6 -4
  19. data/example/app/graphql/schema.rb +3 -1
  20. data/example/app/graphql/types/base_enum.rb +6 -0
  21. data/example/app/graphql/types/base_input_object.rb +6 -0
  22. data/example/app/graphql/types/base_interface.rb +7 -0
  23. data/example/app/graphql/types/base_object.rb +6 -0
  24. data/example/app/graphql/types/base_scalar.rb +6 -0
  25. data/example/app/graphql/types/base_union.rb +6 -0
  26. data/example/app/graphql/types/category_type.rb +7 -6
  27. data/example/app/graphql/types/date_time_type.rb +3 -4
  28. data/example/app/graphql/types/mutation_type.rb +6 -0
  29. data/example/app/graphql/types/post_type.rb +13 -11
  30. data/example/app/graphql/types/query_type.rb +6 -5
  31. data/example/app/models/application_record.rb +2 -0
  32. data/example/app/models/category.rb +2 -0
  33. data/example/app/models/post.rb +2 -0
  34. data/example/bin/bundle +3 -1
  35. data/example/bin/rails +2 -0
  36. data/example/bin/rake +2 -0
  37. data/example/bin/setup +4 -2
  38. data/example/bin/update +4 -2
  39. data/example/config.ru +2 -0
  40. data/example/config/application.rb +1 -1
  41. data/example/db/migrate/20170507175133_create_demo_tables.rb +1 -1
  42. data/example/db/schema.rb +1 -1
  43. data/example/public/favicon.ico +0 -0
  44. data/lib/search_object/plugin/graphql.rb +89 -32
  45. data/lib/search_object/plugin/graphql/version.rb +3 -1
  46. data/search_object_graphql.gemspec +9 -9
  47. data/spec/search_object/plugin/graphql_spec.rb +88 -19
  48. data/spec/spec_helper.rb +2 -0
  49. data/spec/spec_helper_active_record.rb +2 -0
  50. metadata +30 -34
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 20d420d2ee1dd08bdfe48da2c5e1e3ba0a3a8233
4
- data.tar.gz: 57a5009d415d6fad948001d18d428676dbd1471c
2
+ SHA256:
3
+ metadata.gz: 3f77197a83b86e6ec05a3cb7f0f814bcd8e2d9d5590319b995657eccc64700f3
4
+ data.tar.gz: f31f8c4374c23da84bcccc42b6fc8307e08661cab1c67bed324aea90c2780faf
5
5
  SHA512:
6
- metadata.gz: d25a679fad52b5605c5720a774f2bee6d273abc369dbfd90cc48a75793d7fd3cc6359092406d178aecd0ae97fdb6e2f938f891b764b2ebfb26c47bf99fc63605
7
- data.tar.gz: f7d5f5e368c50e3bc274b8d665c83ca975f1093e55a7059c906f17e93a40388c097d51ce7288b99394b3d8268913bfe0ea1ef1d2a611bedaa0e7df9380160f5f
6
+ metadata.gz: 223b83055eaa7e7630694aee66c69abaa01d28a00f091535613b26c7d2f037370f6953ebcaba9d534315fcc381d0aecfbda06b0e0b25b9a0e015b01a233f29c8
7
+ data.tar.gz: 24a16c64a585f012bf10a10d0deefe724fec63338b7d901571595ccd92c7f8643373391f63165c2c30bb52d23a7d0e258f936dc7039ff06840140d303f9c5549
@@ -26,10 +26,22 @@ Style/EachWithObject:
26
26
  Style/CollectionMethods:
27
27
  Enabled: false
28
28
 
29
+ # Disables "Avoid the use of double negation (!!)."
30
+ Style/DoubleNegation:
31
+ Enabled: false
32
+
29
33
  # Disables "Block has too many lines."
30
34
  Metrics/BlockLength:
31
35
  Enabled: false
32
36
 
37
+ # Disables "Assignment Branch Condition size for field_options is too high."
38
+ Metrics/AbcSize:
39
+ Enabled: false
40
+
41
+ # Disables "Method has too many line."
42
+ Metrics/MethodLength:
43
+ Enabled: false
44
+
33
45
  # Disables "Example has too many lines."
34
46
  RSpec/ExampleLength:
35
47
  Enabled: false
@@ -0,0 +1 @@
1
+ 2.5.1
@@ -1,7 +1,8 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.2
4
- - 2.3.0
3
+ - 2.4.1
4
+ - 2.5.2
5
+ - 2.6.0
5
6
  script:
6
7
  - bundle exec rubocop
7
8
  - bundle exec rspec spec
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## Version 0.2
4
+
5
+ * Added support for GraphQL::Schema::Resolver (@rstankov)
6
+ * Added support for GraphQL 1.8 class API (@rstankov)
7
+
3
8
  ## Version 0.1
4
9
 
5
10
  * Initial release (@rstankov)
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in search_object.gemspec
data/README.md CHANGED
@@ -1,3 +1,4 @@
1
+ [![Gem Version](https://badge.fury.io/rb/search_object_graphql.svg)](http://badge.fury.io/rb/search_object_graphql)
1
2
  [![Code Climate](https://codeclimate.com/github/RStankov/SearchObjectGraphQL.svg)](https://codeclimate.com/github/RStankov/SearchObjectGraphQL)
2
3
  [![Build Status](https://secure.travis-ci.org/RStankov/SearchObjectGraphQL.svg)](http://travis-ci.org/RStankov/SearchObjectGraphQL)
3
4
  [![Code coverage](https://coveralls.io/repos/RStankov/SearchObjectGraphQL/badge.svg?branch=master#2)](https://coveralls.io/r/RStankov/SearchObjectGraphQL)
@@ -19,13 +20,15 @@
19
20
  * [Accessing Parent Object](#accessing-parent-object)
20
21
  * [Enums Support](#enums-support)
21
22
  * [Relay Support](#relay-support)
23
+ * [Contributing](#contributing)
24
+ * [License](#license)
22
25
 
23
26
  ## Installation
24
27
 
25
28
  Add this line to your application's Gemfile:
26
29
 
27
30
  ```ruby
28
- gem 'search_object'
31
+ gem 'search_object_graphql'
29
32
  ```
30
33
 
31
34
  And then execute:
@@ -34,7 +37,7 @@ And then execute:
34
37
 
35
38
  Or install it yourself as:
36
39
 
37
- $ gem install search_object
40
+ $ gem install search_object_graphql
38
41
 
39
42
  ## Dependencies
40
43
 
@@ -49,19 +52,19 @@ Just include the ```SearchObject.module``` and define your search options and th
49
52
  class PostResolver
50
53
  include SearchObject.module(:graphql)
51
54
 
52
- type PostType
55
+ type [PostType], null: false
53
56
 
54
57
  scope { Post.all }
55
58
 
56
- option(:name, type: types.String) { |scope, value| scope.where name: value }
57
- option(:published, type: types.Boolean) { |scope, value| value ? scope.published : scope.unpublished }
59
+ option(:name, type: String) { |scope, value| scope.where name: value }
60
+ option(:published, type: Boolean) { |scope, value| value ? scope.published : scope.unpublished }
58
61
  end
59
62
  ```
60
63
 
61
- Then you can just use `PostResolver` as [GraphQL::Function](https://rmosolgo.github.io/graphql-ruby/schema/code_reuse#functions):
64
+ Then you can just use `PostResolver` as [GraphQL::Schema::Resolver](https://graphql-ruby.org/fields/resolvers.html):
62
65
 
63
66
  ```ruby
64
- field :posts, function: PostResolver
67
+ field :posts, resolver: PostResolver
65
68
  ```
66
69
 
67
70
  Options are exposed as arguments in the GraphQL query:
@@ -78,26 +81,6 @@ You can find example of most important features and plugins - [here](https://git
78
81
 
79
82
  ## Features
80
83
 
81
- ### Custom Types
82
-
83
- Custom types can be define inside the search object:
84
-
85
- ```ruby
86
- class PostResolver
87
- include SearchObject.module(:graphql)
88
-
89
- type do
90
- name 'Custom Type'
91
-
92
- field :id, !types.ID
93
- field :title, !types.String
94
- field :body, !types.String
95
- end
96
-
97
- # ...
98
- end
99
- ```
100
-
101
84
  ### Documentation
102
85
 
103
86
  Search object itself can be documented, as well as its options:
@@ -174,10 +157,36 @@ end
174
157
 
175
158
  ### Relay Support
176
159
 
177
- Search objects can be used as [Relay Connections](https://rmosolgo.github.io/graphql-ruby/relay/connections):
160
+ Search objects can be used as [Relay Connections](https://graphql-ruby.org/relay/connections.html):
178
161
 
179
162
  ```ruby
180
- connection :posts, Types::PostType.connection_type, function: Resolvers::PostSearch
163
+ class PostResolver
164
+ include SearchObject.module(:graphql)
165
+
166
+ type PostType.connection_type, null: false
167
+
168
+ # ...
169
+ end
170
+ ```
171
+
172
+ ```ruby
173
+ field :posts, resolver: PostResolver
174
+ ```
175
+
176
+ ### Legacy Function Support
177
+
178
+ ```ruby
179
+ class PostResolver
180
+ include SearchObject.module(:graphql)
181
+
182
+ type [PostType], null: false
183
+
184
+ # ...
185
+ end
186
+ ```
187
+
188
+ ```ruby
189
+ field :posts, function: PostResolver
181
190
  ```
182
191
 
183
192
  ## Contributing
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rspec/core/rake_task'
3
5
  require 'rubocop/rake_task'
@@ -5,4 +7,4 @@ require 'rubocop/rake_task'
5
7
  RSpec::Core::RakeTask.new(:spec)
6
8
  RuboCop::RakeTask.new(:rubocop)
7
9
 
8
- task default: [:rubocop, :spec]
10
+ task default: %i[rubocop spec]
@@ -1 +1 @@
1
- 2.3.1
1
+ 2.5.1
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  git_source(:github) do |repo_name|
@@ -5,7 +7,7 @@ git_source(:github) do |repo_name|
5
7
  "https://github.com/#{repo_name}.git"
6
8
  end
7
9
 
8
- gem 'rails', '~> 5.1.0'
10
+ gem 'rails', '~> 5.2.0'
9
11
 
10
12
  gem 'graphql'
11
13
  gem 'puma', '~> 3.7'
@@ -16,9 +16,9 @@ This is example application showing, one of the possible usages of ```SearchObje
16
16
  ```
17
17
  gem install bundler
18
18
  bundle install
19
- rake db:create
20
- rake db:migrate
21
- rake db:seed
19
+ rails db:create
20
+ rails db:migrate
21
+ rails db:seed
22
22
 
23
23
  rails server
24
24
  ```
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Add your own tasks in files placed in lib/tasks ending in .rake,
2
4
  # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
5
 
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ApplicationController < ActionController::API
2
4
  end
@@ -1,29 +1,33 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class GraphqlController < ApplicationController
2
4
  def execute
3
- render json: Schema.execute(query, variables: variables, context: context)
5
+ result = Schema.execute(params[:query],
6
+ variables: ensure_hash(params[:variables]),
7
+ context: {},
8
+ operation_name: params[:operationName])
9
+ render json: result
10
+ rescue StandardError => error
11
+ raise error unless Rails.env.development?
12
+
13
+ handle_error_in_development error
4
14
  end
5
15
 
6
16
  private
7
17
 
8
- def query
9
- params[:query]
10
- end
11
-
12
- def variables
13
- ensure_hash(params[:variables])
18
+ def ensure_hash(ambiguous_param)
19
+ case ambiguous_param
20
+ when String then ambiguous_param.present? ? ensure_hash(JSON.parse(ambiguous_param)) : {}
21
+ when Hash, ActionController::Parameters then ambiguous_param
22
+ when nil then {}
23
+ else raise ArgumentError, "Unexpected parameter: #{ambiguous_param}"
24
+ end
14
25
  end
15
26
 
16
- def context
17
- {}
18
- end
27
+ def handle_error_in_development(error)
28
+ logger.error error.message
29
+ logger.error error.backtrace.join("\n")
19
30
 
20
- # Handle form data, JSON body, or a blank value
21
- def ensure_hash(params)
22
- case params
23
- when String then params.present? ? ensure_hash(JSON.parse(params)) : {}
24
- when Hash, ActionController::Parameters then params
25
- when nil then {}
26
- else raise ArgumentError, "Unexpected parameter: #{params}"
27
- end
31
+ render json: { error: { message: error.message, backtrace: error.backtrace }, data: {} }, status: 500
28
32
  end
29
33
  end
File without changes
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Resolvers
2
4
  class BaseSearchResolver
3
5
  include SearchObject.module(:graphql)
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Resolvers
2
4
  class CategorySearch < Resolvers::BaseSearchResolver
3
- type Types::CategoryType
5
+ type Types::CategoryType.connection_type
4
6
  description 'Lists categories'
5
7
 
6
- OrderEnum = GraphQL::EnumType.define do
7
- name 'CategoryOrder'
8
+ class OrderEnum < Types::BaseEnum
9
+ graphql_name 'CategoryOrder'
8
10
 
9
11
  value 'RECENT'
10
12
  value 'NAME'
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Resolvers
2
4
  class PostSearch < Resolvers::BaseSearchResolver
3
- type Types::PostType
5
+ type Types::PostType.connection_type
4
6
  description 'Lists posts'
5
7
 
6
- OrderEnum = GraphQL::EnumType.define do
7
- name 'PostOrder'
8
+ class OrderEnum < Types::BaseEnum
9
+ graphql_name 'PostOrder'
8
10
 
9
11
  value 'RECENT'
10
12
  value 'VIEWS'
@@ -51,7 +53,7 @@ module Resolvers
51
53
  end
52
54
 
53
55
  def apply_order_with_recent(scope)
54
- scope.order 'published_at IS NOT NULL, published_at DESC'
56
+ scope.order Arel.sql('published_at IS NOT NULL'), published_at: :desc
55
57
  end
56
58
 
57
59
  def apply_order_with_views(scope)
@@ -1,3 +1,5 @@
1
- Schema = GraphQL::Schema.define do
1
+ # frozen_string_literal: true
2
+
3
+ class Schema < GraphQL::Schema
2
4
  query Types::QueryType
3
5
  end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Types
4
+ class BaseEnum < GraphQL::Schema::Enum
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Types
4
+ class BaseInputObject < GraphQL::Schema::InputObject
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Types
4
+ module BaseInterface
5
+ include GraphQL::Schema::Interface
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Types
4
+ class BaseObject < GraphQL::Schema::Object
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Types
4
+ class BaseScalar < GraphQL::Schema::Scalar
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Types
4
+ class BaseUnion < GraphQL::Schema::Union
5
+ end
6
+ end
@@ -1,8 +1,9 @@
1
- Types::CategoryType = GraphQL::ObjectType.define do
2
- name 'Category'
1
+ # frozen_string_literal: true
3
2
 
4
- field :id, !types.ID
5
- field :name, !types.String
6
-
7
- connection :posts, Types::PostType.connection_type, function: Resolvers::PostSearch
3
+ module Types
4
+ class CategoryType < BaseObject
5
+ field :id, ID, null: false
6
+ field :name, String, null: false
7
+ field :posts, function: Resolvers::PostSearch
8
+ end
8
9
  end
@@ -1,6 +1,5 @@
1
- Types::DateTimeType = GraphQL::ScalarType.define do
2
- name 'DateTime'
1
+ # frozen_string_literal: true
3
2
 
4
- coerce_input ->(value, _ctx) { Time.zone.parse(value) }
5
- coerce_result ->(value, _ctx) { value.utc.iso8601 }
3
+ module Types
4
+ DateTimeType = GraphQL::Types::ISO8601DateTime
6
5
  end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Types
4
+ class MutationType < Types::BaseObject
5
+ end
6
+ end
@@ -1,13 +1,15 @@
1
- Types::PostType = GraphQL::ObjectType.define do
2
- name 'Post'
1
+ # frozen_string_literal: true
3
2
 
4
- field :id, !types.ID
5
- field :title, !types.String
6
- field :body, !types.String
7
- field :category, !Types::CategoryType
8
- field :viewsCount, !types.Int, property: :views_count
9
- field :likesCount, !types.Int, property: :likes_count
10
- field :commentsCount, !types.Int, property: :comments_count
11
- field :isPublished, !types.Boolean, property: :published?
12
- field :publishedAt, !Types::DateTimeType, property: :published_at
3
+ module Types
4
+ class PostType < BaseObject
5
+ field :id, ID, null: false
6
+ field :title, String, null: false
7
+ field :body, String, null: false
8
+ field :category, CategoryType, null: false
9
+ field :views_count, Int, null: false
10
+ field :likes_count, Int, null: false
11
+ field :comments_count, Int, null: false
12
+ field :is_published, Boolean, null: false, method: :published?
13
+ field :published_at, DateTimeType, null: false
14
+ end
13
15
  end
@@ -1,7 +1,8 @@
1
- Types::QueryType = GraphQL::ObjectType.define do
2
- name 'Query'
1
+ # frozen_string_literal: true
3
2
 
4
- connection :categories, Types::CategoryType.connection_type, function: Resolvers::CategorySearch
5
-
6
- connection :posts, Types::PostType.connection_type, function: Resolvers::PostSearch
3
+ module Types
4
+ class QueryType < Types::BaseObject
5
+ field :categories, function: Resolvers::CategorySearch
6
+ field :posts, resolver: Resolvers::PostSearch
7
+ end
7
8
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ApplicationRecord < ActiveRecord::Base
2
4
  self.abstract_class = true
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Category < ApplicationRecord
2
4
  validates :name, presence: true, uniqueness: true
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Post < ApplicationRecord
2
4
  validates :title, presence: true, uniqueness: true
3
5
  validates :body, presence: true
@@ -1,3 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
2
+ # frozen_string_literal: true
3
+
4
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
3
5
  load Gem.bin_path('bundler', 'bundle')
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  APP_PATH = File.expand_path('../config/application', __dir__)
3
5
  require_relative '../config/boot'
4
6
  require 'rails/commands'
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  require_relative '../config/boot'
3
5
  require 'rake'
4
6
  Rake.application.run
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  require 'pathname'
3
5
  require 'fileutils'
4
- include FileUtils
6
+ include FileUtils # rubocop:disable Style/MixinUsage
5
7
 
6
8
  # path to your application root.
7
- APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
9
+ APP_ROOT = Pathname.new File.expand_path('..', __dir__)
8
10
 
9
11
  def system!(*args)
10
12
  system(*args) || abort("\n== Command #{args} failed ==")
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  require 'pathname'
3
5
  require 'fileutils'
4
- include FileUtils
6
+ include FileUtils # rubocop:disable Style/MixinUsage
5
7
 
6
8
  # path to your application root.
7
- APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
9
+ APP_ROOT = Pathname.new File.expand_path('..', __dir__)
8
10
 
9
11
  def system!(*args)
10
12
  system(*args) || abort("\n== Command #{args} failed ==")
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is used by Rack-based servers to start the application.
2
4
 
3
5
  require_relative 'config/environment'
@@ -3,7 +3,7 @@ require_relative 'boot'
3
3
  require "rails"
4
4
  # Pick the frameworks you want:
5
5
  require "active_model/railtie"
6
- # require "active_job/railtie"
6
+ require "active_job/railtie"
7
7
  require "active_record/railtie"
8
8
  require "action_controller/railtie"
9
9
  # require "action_mailer/railtie"
@@ -7,7 +7,7 @@ class CreateDemoTables < ActiveRecord::Migration[5.1]
7
7
  end
8
8
 
9
9
  create_table :posts do |t|
10
- t.references :category, foreign_key: true
10
+ t.references :category
11
11
  t.string :title, null: false
12
12
  t.index :title, unique: true
13
13
  t.string :body, null: false
@@ -10,7 +10,7 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- ActiveRecord::Schema.define(version: 20170507175133) do
13
+ ActiveRecord::Schema.define(version: 2017_05_07_175133) do
14
14
 
15
15
  create_table "categories", force: :cascade do |t|
16
16
  t.string "name", null: false
File without changes
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SearchObject
2
4
  module Plugin
3
5
  module Graphql
4
6
  def self.included(base)
5
7
  base.include SearchObject::Plugin::Enum
8
+ base.include ::GraphQL::Schema::Member::GraphQLTypeNames
6
9
  base.extend ClassMethods
7
10
  end
8
11
 
@@ -15,49 +18,117 @@ module SearchObject
15
18
  super filters: filters, scope: scope
16
19
  end
17
20
 
21
+ # NOTE(rstankov): GraphQL::Schema::Resolver interface
22
+ # Documentation - http://graphql-ruby.org/fields/resolvers.html#using-resolver
23
+ def resolve_with_support(args = {})
24
+ self.params = args.to_h
25
+ results
26
+ end
27
+
18
28
  module ClassMethods
29
+ KEYS = %i[type default description].freeze
19
30
  def option(name, options = {}, &block)
20
- argument = Helper.build_argument(name, options)
21
- arguments[argument.name] = argument
31
+ config[:arguments] ||= {}
32
+ config[:arguments][name.to_s] = KEYS.inject({}) do |acc, key|
33
+ acc[key] = options[key] if options.key?(key)
34
+ acc
35
+ end
22
36
 
23
- options[:enum] = argument.type.values.keys if argument.type.is_a? GraphQL::EnumType
37
+ type = options.fetch(:type) { raise MissingTypeDefinitionError, name }
38
+ options[:enum] = type.values.keys if type.respond_to?(:values)
24
39
 
25
40
  super(name, options, &block)
26
41
  end
27
42
 
28
- def types
29
- GraphQL::Define::TypeDefiner.instance
30
- end
31
-
32
- # NOTE(rstankov): GraphQL::Function interface
33
- # Documentation - https://rmosolgo.github.io/graphql-ruby/schema/code_reuse#functions
34
- def call(object, args, context)
35
- new(filters: args.to_h, object: object, context: context).results
36
- end
37
-
38
- def arguments
39
- config[:args] ||= {}
40
- end
41
-
42
- def type(value = :default, &block)
43
+ def type(value = :default, null: true, &block)
43
44
  return config[:type] if value == :default && !block_given?
45
+
44
46
  config[:type] = block_given? ? GraphQL::ObjectType.define(&block) : value
47
+ config[:null] = null
45
48
  end
46
49
 
47
50
  def complexity(value = :default)
48
51
  return config[:complexity] || 1 if value == :default
52
+
49
53
  config[:complexity] = value
50
54
  end
51
55
 
52
56
  def description(value = :default)
53
57
  return config[:description] if value == :default
58
+
54
59
  config[:description] = value
55
60
  end
56
61
 
57
62
  def deprecation_reason(value = :default)
58
63
  return config[:deprecation_reason] if value == :default
64
+
59
65
  config[:deprecation_reason] = value
60
66
  end
67
+
68
+ # NOTE(rstankov): GraphQL::Function interface (deprecated in favour of GraphQL::Schema::Resolver)
69
+ # Documentation - http://graphql-ruby.org/guides
70
+ def call(object, args, context)
71
+ new(filters: args.to_h, object: object, context: context).results
72
+ end
73
+
74
+ # NOTE(rstankov): Used for GraphQL::Function
75
+ def types
76
+ GraphQL::Define::TypeDefiner.instance
77
+ end
78
+
79
+ # NOTE(rstankov): Used for GraphQL::Function
80
+ def arguments
81
+ (config[:arguments] || {}).inject({}) do |acc, (name, options)|
82
+ argument = GraphQL::Argument.new
83
+ argument.name = name.to_s
84
+ argument.type = options.fetch(:type) { raise MissingTypeDefinitionError, name }
85
+ argument.default_value = options[:default] if options.key? :default
86
+ argument.description = options[:description] if options.key? :description
87
+
88
+ acc[name] = argument
89
+ acc
90
+ end
91
+ end
92
+
93
+ # NOTE(rstankov): Used for GraphQL::Schema::Resolver
94
+ def field_options
95
+ {
96
+ type: type,
97
+ description: description,
98
+ extras: [],
99
+ resolver_method: :resolve_with_support,
100
+ resolver_class: self,
101
+ deprecation_reason: deprecation_reason,
102
+ arguments: (config[:arguments] || {}).inject({}) do |acc, (name, options)|
103
+ acc[name] = ::GraphQL::Schema::Argument.new(
104
+ name: name.to_s,
105
+ type: options.fetch(:type) { raise MissingTypeDefinitionError, name },
106
+ description: options[:description],
107
+ required: !!options[:required],
108
+ default_value: options.fetch(:default) { ::GraphQL::Schema::Argument::NO_DEFAULT },
109
+ owner: self
110
+ )
111
+ acc
112
+ end,
113
+ null: !!config[:null],
114
+ complexity: complexity
115
+ }
116
+ end
117
+
118
+ # NOTE(rstankov): Used for GraphQL::Schema::Resolver
119
+ def visible?(_context)
120
+ true
121
+ end
122
+
123
+ # NOTE(rstankov): Used for GraphQL::Schema::Resolver
124
+ def accessible?(_context)
125
+ true
126
+ end
127
+
128
+ # NOTE(rstankov): Used for GraphQL::Schema::Resolver
129
+ def authorized?(_object, _context)
130
+ true
131
+ end
61
132
  end
62
133
 
63
134
  class MissingTypeDefinitionError < ArgumentError
@@ -65,20 +136,6 @@ module SearchObject
65
136
  super "GraphQL type has to passed as :type to '#{name}' option"
66
137
  end
67
138
  end
68
-
69
- # :api: private
70
- module Helper
71
- module_function
72
-
73
- def build_argument(name, options)
74
- argument = GraphQL::Argument.new
75
- argument.name = name.to_s
76
- argument.type = options.fetch(:type) { raise MissingTypeDefinitionError, name }
77
- argument.default_value = options[:default] if options.key? :default
78
- argument.description = options[:description] if options.key? :description
79
- argument
80
- end
81
- end
82
139
  end
83
140
  end
84
141
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SearchObject
2
4
  module Plugin
3
5
  module Graphql
4
- VERSION = '0.1'.freeze
6
+ VERSION = '0.2'
5
7
  end
6
8
  end
7
9
  end
@@ -1,5 +1,6 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'English'
5
6
  require 'search_object/plugin/graphql/version'
@@ -19,13 +20,12 @@ Gem::Specification.new do |spec|
19
20
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
21
  spec.require_paths = ['lib']
21
22
 
22
- spec.add_dependency 'search_object', '~> 1.2'
23
- spec.add_dependency 'graphql', '~> 1.5'
23
+ spec.add_dependency 'graphql', '~> 1.8'
24
+ spec.add_dependency 'search_object', '~> 1.2.2'
24
25
 
25
- spec.add_development_dependency 'bundler', '~> 1.13'
26
- spec.add_development_dependency 'rake'
27
- spec.add_development_dependency 'rspec', '~> 3.5'
28
- spec.add_development_dependency 'rubocop', '0.46.0'
29
- spec.add_development_dependency 'rubocop-rspec', '1.8.0'
30
26
  spec.add_development_dependency 'coveralls'
27
+ spec.add_development_dependency 'rake'
28
+ spec.add_development_dependency 'rspec', '~> 3.8'
29
+ spec.add_development_dependency 'rubocop', '0.62.0'
30
+ spec.add_development_dependency 'rubocop-rspec', '1.31.0'
31
31
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
  require 'graphql'
3
5
  require 'ostruct'
@@ -10,20 +12,18 @@ describe SearchObject::Plugin::Graphql do
10
12
  end
11
13
  end
12
14
 
13
- PostType = GraphQL::ObjectType.define do
14
- name 'Post'
15
-
16
- field :id, !types.ID
15
+ class PostType < GraphQL::Schema::Object
16
+ field :id, ID, null: false
17
17
  end
18
18
 
19
19
  def define_schema(&block)
20
- query_type = GraphQL::ObjectType.define do
21
- name 'Query'
20
+ query_type = Class.new(GraphQL::Schema::Object) do
21
+ graphql_name 'Query'
22
22
 
23
23
  instance_eval(&block)
24
24
  end
25
25
 
26
- GraphQL::Schema.define do
26
+ Class.new(GraphQL::Schema) do
27
27
  query query_type
28
28
 
29
29
  max_complexity 1000
@@ -45,20 +45,60 @@ describe SearchObject::Plugin::Graphql do
45
45
 
46
46
  define_schema do
47
47
  if search_object.type.nil?
48
- field :posts, types[PostType], function: search_object
48
+ field :posts, [PostType], resolver: search_object
49
49
  else
50
- field :posts, function: search_object
50
+ field :posts, resolver: search_object
51
51
  end
52
52
  end
53
53
  end
54
54
 
55
+ it 'can be used as GraphQL::Schema::Resolver' do
56
+ post_type = Class.new(GraphQL::Schema::Object) do
57
+ graphql_name 'Post'
58
+
59
+ field :id, GraphQL::Types::ID, null: false
60
+ end
61
+
62
+ search_object = define_search_class do
63
+ scope { [Post.new('1'), Post.new('2'), Post.new('3')] }
64
+
65
+ type [post_type]
66
+
67
+ option(:id, type: !types.ID) { |scope, value| scope.select { |p| p.id == value } }
68
+ end
69
+
70
+ schema = define_schema do
71
+ field :posts, resolver: search_object
72
+ end
73
+
74
+ result = schema.execute '{ posts(id: "2") { id } }'
75
+
76
+ expect(result).to eq(
77
+ 'data' => {
78
+ 'posts' => [Post.new('2').to_json]
79
+ }
80
+ )
81
+ end
82
+
55
83
  it 'can be used as GraphQL::Function' do
56
- schema = define_search_class_and_return_schema do
84
+ post_type = GraphQL::ObjectType.define do
85
+ name 'Post'
86
+
87
+ field :id, !types.ID
88
+ end
89
+
90
+ search_object = define_search_class do
57
91
  scope { [Post.new('1'), Post.new('2'), Post.new('3')] }
58
92
 
93
+ type types[post_type]
94
+
59
95
  option(:id, type: !types.ID) { |scope, value| scope.select { |p| p.id == value } }
60
96
  end
61
97
 
98
+ schema = define_schema do
99
+ field :posts, function: search_object
100
+ end
101
+
62
102
  result = schema.execute '{ posts(id: "2") { id } }'
63
103
 
64
104
  expect(result).to eq(
@@ -73,19 +113,19 @@ describe SearchObject::Plugin::Graphql do
73
113
  scope { object.posts }
74
114
  end
75
115
 
76
- parent_type = GraphQL::ObjectType.define do
77
- name 'ParentType'
116
+ parent_type = Class.new(GraphQL::Schema::Object) do
117
+ graphql_name 'Parent'
78
118
 
79
- field :posts, types[PostType], function: search_object
119
+ field :posts, [PostType], resolver: search_object
80
120
  end
81
121
 
82
122
  schema = define_schema do
83
- field :parent, parent_type do
84
- resolve ->(_obj, _args, _ctx) { OpenStruct.new posts: [Post.new('from_parent')] }
85
- end
123
+ field :parent, parent_type, null: false
86
124
  end
87
125
 
88
- result = schema.execute '{ parent { posts { id } } }'
126
+ root = OpenStruct.new(parent: OpenStruct.new(posts: [Post.new('from_parent')]))
127
+
128
+ result = schema.execute '{ parent { posts { id } } }', root_value: root
89
129
 
90
130
  expect(result).to eq(
91
131
  'data' => {
@@ -174,7 +214,7 @@ describe SearchObject::Plugin::Graphql do
174
214
 
175
215
  it 'can be marked as deprecated' do
176
216
  schema = define_search_class_and_return_schema do
177
- type types[PostType]
217
+ type [PostType]
178
218
  deprecation_reason 'Not needed any more'
179
219
  end
180
220
 
@@ -189,7 +229,7 @@ describe SearchObject::Plugin::Graphql do
189
229
  }
190
230
  QUERY
191
231
 
192
- expect(result).to eq(
232
+ expect(result.to_h).to eq(
193
233
  'data' => {
194
234
  '__type' => {
195
235
  'name' => 'Query',
@@ -200,6 +240,35 @@ describe SearchObject::Plugin::Graphql do
200
240
  end
201
241
 
202
242
  describe 'option' do
243
+ it 'converts GraphQL::Schema::Enum to SearchObject enum' do
244
+ schema = define_search_class_and_return_schema do
245
+ enum_type = Class.new(GraphQL::Schema::Enum) do
246
+ graphql_name 'PostOrder'
247
+
248
+ value 'PRICE'
249
+ value 'DATE'
250
+ end
251
+
252
+ option(:order, type: enum_type)
253
+
254
+ define_method(:apply_order_with_price) do |_scope|
255
+ [Post.new('price')]
256
+ end
257
+
258
+ define_method(:apply_order_with_date) do |_scope|
259
+ [Post.new('date')]
260
+ end
261
+ end
262
+
263
+ result = schema.execute '{ posts(order: PRICE) { id } }'
264
+
265
+ expect(result).to eq(
266
+ 'data' => {
267
+ 'posts' => [Post.new('price').to_json]
268
+ }
269
+ )
270
+ end
271
+
203
272
  it 'converts GraphQL::EnumType to SearchObject enum' do
204
273
  schema = define_search_class_and_return_schema do
205
274
  enum_type = GraphQL::EnumType.define do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/setup'
2
4
 
3
5
  if ENV['TRAVIS']
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'spec_helper'
2
4
  require 'active_record'
3
5
 
metadata CHANGED
@@ -1,57 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: search_object_graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.1'
4
+ version: '0.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Radoslav Stankov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-07 00:00:00.000000000 Z
11
+ date: 2019-06-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: search_object
14
+ name: graphql
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.2'
19
+ version: '1.8'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.2'
26
+ version: '1.8'
27
27
  - !ruby/object:Gem::Dependency
28
- name: graphql
28
+ name: search_object
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.5'
33
+ version: 1.2.2
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.5'
40
+ version: 1.2.2
41
41
  - !ruby/object:Gem::Dependency
42
- name: bundler
42
+ name: coveralls
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '1.13'
47
+ version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '1.13'
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -72,56 +72,42 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '3.5'
75
+ version: '3.8'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '3.5'
82
+ version: '3.8'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rubocop
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - '='
88
88
  - !ruby/object:Gem::Version
89
- version: 0.46.0
89
+ version: 0.62.0
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - '='
95
95
  - !ruby/object:Gem::Version
96
- version: 0.46.0
96
+ version: 0.62.0
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: rubocop-rspec
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - '='
102
102
  - !ruby/object:Gem::Version
103
- version: 1.8.0
103
+ version: 1.31.0
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - '='
109
109
  - !ruby/object:Gem::Version
110
- version: 1.8.0
111
- - !ruby/object:Gem::Dependency
112
- name: coveralls
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
110
+ version: 1.31.0
125
111
  description: Search Object plugin to working with GraphQL
126
112
  email:
127
113
  - rstankov@gmail.com
@@ -133,6 +119,7 @@ files:
133
119
  - ".projections.json"
134
120
  - ".rspec"
135
121
  - ".rubocop.yml"
122
+ - ".ruby-version"
136
123
  - ".travis.yml"
137
124
  - CHANGELOG.md
138
125
  - Gemfile
@@ -146,13 +133,21 @@ files:
146
133
  - example/Rakefile
147
134
  - example/app/controllers/application_controller.rb
148
135
  - example/app/controllers/graphql_controller.rb
136
+ - example/app/graphql/mutations/.keep
149
137
  - example/app/graphql/resolvers/base_search_resolver.rb
150
138
  - example/app/graphql/resolvers/category_search.rb
151
139
  - example/app/graphql/resolvers/post_search.rb
152
140
  - example/app/graphql/schema.rb
153
141
  - example/app/graphql/types/.keep
142
+ - example/app/graphql/types/base_enum.rb
143
+ - example/app/graphql/types/base_input_object.rb
144
+ - example/app/graphql/types/base_interface.rb
145
+ - example/app/graphql/types/base_object.rb
146
+ - example/app/graphql/types/base_scalar.rb
147
+ - example/app/graphql/types/base_union.rb
154
148
  - example/app/graphql/types/category_type.rb
155
149
  - example/app/graphql/types/date_time_type.rb
150
+ - example/app/graphql/types/mutation_type.rb
156
151
  - example/app/graphql/types/post_type.rb
157
152
  - example/app/graphql/types/query_type.rb
158
153
  - example/app/models/application_record.rb
@@ -181,6 +176,7 @@ files:
181
176
  - example/db/schema.rb
182
177
  - example/db/seeds.rb
183
178
  - example/log/.keep
179
+ - example/public/favicon.ico
184
180
  - example/public/robots.txt
185
181
  - example/vendor/.keep
186
182
  - lib/search_object/plugin/graphql.rb
@@ -209,7 +205,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
209
205
  version: '0'
210
206
  requirements: []
211
207
  rubyforge_project:
212
- rubygems_version: 2.4.5
208
+ rubygems_version: 2.7.6
213
209
  signing_key:
214
210
  specification_version: 4
215
211
  summary: Maps search objects to GraphQL resolvers