search_object_graphql 0.1

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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.projections.json +8 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +39 -0
  6. data/.travis.yml +11 -0
  7. data/CHANGELOG.md +5 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +194 -0
  11. data/Rakefile +8 -0
  12. data/example/.gitignore +20 -0
  13. data/example/.ruby-version +1 -0
  14. data/example/Gemfile +22 -0
  15. data/example/README.md +83 -0
  16. data/example/Rakefile +6 -0
  17. data/example/app/controllers/application_controller.rb +2 -0
  18. data/example/app/controllers/graphql_controller.rb +29 -0
  19. data/example/app/graphql/resolvers/base_search_resolver.rb +9 -0
  20. data/example/app/graphql/resolvers/category_search.rb +35 -0
  21. data/example/app/graphql/resolvers/post_search.rb +69 -0
  22. data/example/app/graphql/schema.rb +3 -0
  23. data/example/app/graphql/types/.keep +0 -0
  24. data/example/app/graphql/types/category_type.rb +8 -0
  25. data/example/app/graphql/types/date_time_type.rb +6 -0
  26. data/example/app/graphql/types/post_type.rb +13 -0
  27. data/example/app/graphql/types/query_type.rb +7 -0
  28. data/example/app/models/application_record.rb +3 -0
  29. data/example/app/models/category.rb +5 -0
  30. data/example/app/models/post.rb +14 -0
  31. data/example/bin/bundle +3 -0
  32. data/example/bin/rails +4 -0
  33. data/example/bin/rake +4 -0
  34. data/example/bin/setup +34 -0
  35. data/example/bin/update +29 -0
  36. data/example/config.ru +5 -0
  37. data/example/config/application.rb +36 -0
  38. data/example/config/boot.rb +3 -0
  39. data/example/config/cable.yml +10 -0
  40. data/example/config/database.yml +25 -0
  41. data/example/config/environment.rb +5 -0
  42. data/example/config/environments/development.rb +42 -0
  43. data/example/config/environments/production.rb +78 -0
  44. data/example/config/environments/test.rb +36 -0
  45. data/example/config/initializers/wrap_parameters.rb +14 -0
  46. data/example/config/puma.rb +56 -0
  47. data/example/config/routes.rb +5 -0
  48. data/example/config/secrets.yml +32 -0
  49. data/example/config/spring.rb +6 -0
  50. data/example/db/migrate/20170507175133_create_demo_tables.rb +21 -0
  51. data/example/db/schema.rb +36 -0
  52. data/example/db/seeds.rb +35 -0
  53. data/example/log/.keep +0 -0
  54. data/example/public/robots.txt +1 -0
  55. data/example/vendor/.keep +0 -0
  56. data/lib/search_object/plugin/graphql.rb +84 -0
  57. data/lib/search_object/plugin/graphql/version.rb +7 -0
  58. data/search_object_graphql.gemspec +31 -0
  59. data/spec/search_object/plugin/graphql_spec.rb +288 -0
  60. data/spec/spec_helper.rb +12 -0
  61. data/spec/spec_helper_active_record.rb +29 -0
  62. metadata +219 -0
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require_relative 'config/application'
5
+
6
+ Rails.application.load_tasks
@@ -0,0 +1,2 @@
1
+ class ApplicationController < ActionController::API
2
+ end
@@ -0,0 +1,29 @@
1
+ class GraphqlController < ApplicationController
2
+ def execute
3
+ render json: Schema.execute(query, variables: variables, context: context)
4
+ end
5
+
6
+ private
7
+
8
+ def query
9
+ params[:query]
10
+ end
11
+
12
+ def variables
13
+ ensure_hash(params[:variables])
14
+ end
15
+
16
+ def context
17
+ {}
18
+ end
19
+
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
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ module Resolvers
2
+ class BaseSearchResolver
3
+ include SearchObject.module(:graphql)
4
+
5
+ def escape_search_term(term)
6
+ "%#{term.gsub(/\s+/, '%')}%"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,35 @@
1
+ module Resolvers
2
+ class CategorySearch < Resolvers::BaseSearchResolver
3
+ type Types::CategoryType
4
+ description 'Lists categories'
5
+
6
+ OrderEnum = GraphQL::EnumType.define do
7
+ name 'CategoryOrder'
8
+
9
+ value 'RECENT'
10
+ value 'NAME'
11
+ end
12
+
13
+ scope { Category.all }
14
+
15
+ option :id, type: types.String, with: :apply_id_filter
16
+ option :name, type: types.String, with: :apply_name_filter
17
+ option :order, type: OrderEnum, default: 'RECENT'
18
+
19
+ def apply_id_filter(scope, value)
20
+ scope.where id: value
21
+ end
22
+
23
+ def apply_name_filter(scope, value)
24
+ scope.where 'name LIKE ?', escape_search_term(value)
25
+ end
26
+
27
+ def apply_order_with_recent(scope)
28
+ scope.order 'id DESC'
29
+ end
30
+
31
+ def apply_order_with_name(scope)
32
+ scope.order 'name ASC'
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,69 @@
1
+ module Resolvers
2
+ class PostSearch < Resolvers::BaseSearchResolver
3
+ type Types::PostType
4
+ description 'Lists posts'
5
+
6
+ OrderEnum = GraphQL::EnumType.define do
7
+ name 'PostOrder'
8
+
9
+ value 'RECENT'
10
+ value 'VIEWS'
11
+ value 'LIKES'
12
+ value 'COMMENTS'
13
+ end
14
+
15
+ scope { object.respond_to?(:posts) ? object.posts : Post.all }
16
+
17
+ option :id, type: types.String, with: :apply_id_filter
18
+ option :title, type: types.String, with: :apply_title_filter
19
+ option :body, type: types.String, with: :apply_body_filter
20
+ option :categoryId, type: types.String, with: :apply_category_id_filter
21
+ option :categoryName, type: types.String, with: :apply_category_name_filter
22
+ option :published, type: types.Boolean, with: :apply_published_filter
23
+ option :order, type: OrderEnum, default: 'RECENT'
24
+
25
+ def apply_id_filter(scope, value)
26
+ scope.where id: value
27
+ end
28
+
29
+ def apply_title_filter(scope, value)
30
+ scope.where 'title LIKE ?', escape_search_term(value)
31
+ end
32
+
33
+ def apply_body_filter(scope, value)
34
+ scope.where 'body LIKE ?', escape_search_term(value)
35
+ end
36
+
37
+ def apply_category_id_filter(scope, value)
38
+ scope.where category_id: value
39
+ end
40
+
41
+ def apply_category_name_filter(scope, value)
42
+ scope.joins(:category).where 'categories.name LIKE ?', escape_search_term(value)
43
+ end
44
+
45
+ def apply_published_filter(scope, value)
46
+ if value
47
+ scope.published
48
+ else
49
+ scope.unpublished
50
+ end
51
+ end
52
+
53
+ def apply_order_with_recent(scope)
54
+ scope.order 'published_at IS NOT NULL, published_at DESC'
55
+ end
56
+
57
+ def apply_order_with_views(scope)
58
+ scope.order 'views_count DESC'
59
+ end
60
+
61
+ def apply_order_with_likes(scope)
62
+ scope.order 'likes_count DESC'
63
+ end
64
+
65
+ def apply_order_with_comments(scope)
66
+ scope.order 'comments_count DESC'
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ Schema = GraphQL::Schema.define do
2
+ query Types::QueryType
3
+ end
File without changes
@@ -0,0 +1,8 @@
1
+ Types::CategoryType = GraphQL::ObjectType.define do
2
+ name 'Category'
3
+
4
+ field :id, !types.ID
5
+ field :name, !types.String
6
+
7
+ connection :posts, Types::PostType.connection_type, function: Resolvers::PostSearch
8
+ end
@@ -0,0 +1,6 @@
1
+ Types::DateTimeType = GraphQL::ScalarType.define do
2
+ name 'DateTime'
3
+
4
+ coerce_input ->(value, _ctx) { Time.zone.parse(value) }
5
+ coerce_result ->(value, _ctx) { value.utc.iso8601 }
6
+ end
@@ -0,0 +1,13 @@
1
+ Types::PostType = GraphQL::ObjectType.define do
2
+ name 'Post'
3
+
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
13
+ end
@@ -0,0 +1,7 @@
1
+ Types::QueryType = GraphQL::ObjectType.define do
2
+ name 'Query'
3
+
4
+ connection :categories, Types::CategoryType.connection_type, function: Resolvers::CategorySearch
5
+
6
+ connection :posts, Types::PostType.connection_type, function: Resolvers::PostSearch
7
+ end
@@ -0,0 +1,3 @@
1
+ class ApplicationRecord < ActiveRecord::Base
2
+ self.abstract_class = true
3
+ end
@@ -0,0 +1,5 @@
1
+ class Category < ApplicationRecord
2
+ validates :name, presence: true, uniqueness: true
3
+
4
+ has_many :posts, inverse_of: :category, dependent: :destroy
5
+ end
@@ -0,0 +1,14 @@
1
+ class Post < ApplicationRecord
2
+ validates :title, presence: true, uniqueness: true
3
+ validates :body, presence: true
4
+ validates :category_id, presence: true
5
+
6
+ belongs_to :category, inverse_of: :posts
7
+
8
+ scope :published, -> { where "published_at <= date('now')" }
9
+ scope :unpublished, -> { where "published_at IS NULL OR published_at > date('now')" }
10
+
11
+ def published?
12
+ published_at.present? && published_at < Time.current
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3
+ load Gem.bin_path('bundler', 'bundle')
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ APP_PATH = File.expand_path('../config/application', __dir__)
3
+ require_relative '../config/boot'
4
+ require 'rails/commands'
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../config/boot'
3
+ require 'rake'
4
+ Rake.application.run
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+ require 'pathname'
3
+ require 'fileutils'
4
+ include FileUtils
5
+
6
+ # path to your application root.
7
+ APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8
+
9
+ def system!(*args)
10
+ system(*args) || abort("\n== Command #{args} failed ==")
11
+ end
12
+
13
+ chdir APP_ROOT do
14
+ # This script is a starting point to setup your application.
15
+ # Add necessary setup steps to this file.
16
+
17
+ puts '== Installing dependencies =='
18
+ system! 'gem install bundler --conservative'
19
+ system('bundle check') || system!('bundle install')
20
+
21
+ # puts "\n== Copying sample files =="
22
+ # unless File.exist?('config/database.yml')
23
+ # cp 'config/database.yml.sample', 'config/database.yml'
24
+ # end
25
+
26
+ puts "\n== Preparing database =="
27
+ system! 'bin/rails db:setup'
28
+
29
+ puts "\n== Removing old logs and tempfiles =="
30
+ system! 'bin/rails log:clear tmp:clear'
31
+
32
+ puts "\n== Restarting application server =="
33
+ system! 'bin/rails restart'
34
+ end
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ require 'pathname'
3
+ require 'fileutils'
4
+ include FileUtils
5
+
6
+ # path to your application root.
7
+ APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8
+
9
+ def system!(*args)
10
+ system(*args) || abort("\n== Command #{args} failed ==")
11
+ end
12
+
13
+ chdir APP_ROOT do
14
+ # This script is a way to update your development environment automatically.
15
+ # Add necessary update steps to this file.
16
+
17
+ puts '== Installing dependencies =='
18
+ system! 'gem install bundler --conservative'
19
+ system('bundle check') || system!('bundle install')
20
+
21
+ puts "\n== Updating database =="
22
+ system! 'bin/rails db:migrate'
23
+
24
+ puts "\n== Removing old logs and tempfiles =="
25
+ system! 'bin/rails log:clear tmp:clear'
26
+
27
+ puts "\n== Restarting application server =="
28
+ system! 'bin/rails restart'
29
+ end
@@ -0,0 +1,5 @@
1
+ # This file is used by Rack-based servers to start the application.
2
+
3
+ require_relative 'config/environment'
4
+
5
+ run Rails.application
@@ -0,0 +1,36 @@
1
+ require_relative 'boot'
2
+
3
+ require "rails"
4
+ # Pick the frameworks you want:
5
+ require "active_model/railtie"
6
+ # require "active_job/railtie"
7
+ require "active_record/railtie"
8
+ require "action_controller/railtie"
9
+ # require "action_mailer/railtie"
10
+ # require "action_view/railtie"
11
+ # require "action_cable/engine"
12
+ require "sprockets/railtie"
13
+ # require "rails/test_unit/railtie"
14
+
15
+ # Load SearchObject::Plugin::Graphql manually from lib
16
+ require File.expand_path('../../../lib/search_object/plugin/graphql', __FILE__)
17
+
18
+ # Require the gems listed in Gemfile, including any gems
19
+ # you've limited to :test, :development, or :production.
20
+ Bundler.require(*Rails.groups)
21
+
22
+ module Example
23
+ class Application < Rails::Application
24
+ # Initialize configuration defaults for originally generated Rails version.
25
+ config.load_defaults 5.1
26
+
27
+ # Settings in config/environments/* take precedence over those specified here.
28
+ # Application configuration should go into files in config/initializers
29
+ # -- all .rb files in that directory are automatically loaded.
30
+
31
+ # Only loads a smaller set of middleware suitable for API only apps.
32
+ # Middleware like session, flash, cookies can be added back manually.
33
+ # Skip views, helpers and assets when generating a new resource.
34
+ config.api_only = true
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
2
+
3
+ require 'bundler/setup' # Set up gems listed in the Gemfile.
@@ -0,0 +1,10 @@
1
+ development:
2
+ adapter: async
3
+
4
+ test:
5
+ adapter: async
6
+
7
+ production:
8
+ adapter: redis
9
+ url: redis://localhost:6379/1
10
+ channel_prefix: example_production
@@ -0,0 +1,25 @@
1
+ # SQLite version 3.x
2
+ # gem install sqlite3
3
+ #
4
+ # Ensure the SQLite 3 gem is defined in your Gemfile
5
+ # gem 'sqlite3'
6
+ #
7
+ default: &default
8
+ adapter: sqlite3
9
+ pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
10
+ timeout: 5000
11
+
12
+ development:
13
+ <<: *default
14
+ database: db/development.sqlite3
15
+
16
+ # Warning: The database defined as "test" will be erased and
17
+ # re-generated from your development database when you run "rake".
18
+ # Do not set this db to the same as development or production.
19
+ test:
20
+ <<: *default
21
+ database: db/test.sqlite3
22
+
23
+ production:
24
+ <<: *default
25
+ database: db/production.sqlite3
@@ -0,0 +1,5 @@
1
+ # Load the Rails application.
2
+ require_relative 'application'
3
+
4
+ # Initialize the Rails application.
5
+ Rails.application.initialize!
@@ -0,0 +1,42 @@
1
+ Rails.application.configure do
2
+ # Settings specified here will take precedence over those in config/application.rb.
3
+
4
+ # In the development environment your application's code is reloaded on
5
+ # every request. This slows down response time but is perfect for development
6
+ # since you don't have to restart the web server when you make code changes.
7
+ config.cache_classes = false
8
+
9
+ # Do not eager load code on boot.
10
+ config.eager_load = false
11
+
12
+ # Show full error reports.
13
+ config.consider_all_requests_local = true
14
+
15
+ # Enable/disable caching. By default caching is disabled.
16
+ if Rails.root.join('tmp/caching-dev.txt').exist?
17
+ config.action_controller.perform_caching = true
18
+
19
+ config.cache_store = :memory_store
20
+ config.public_file_server.headers = {
21
+ 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}"
22
+ }
23
+ else
24
+ config.action_controller.perform_caching = false
25
+
26
+ config.cache_store = :null_store
27
+ end
28
+
29
+ # Print deprecation notices to the Rails logger.
30
+ config.active_support.deprecation = :log
31
+
32
+ # Raise an error on page load if there are pending migrations.
33
+ config.active_record.migration_error = :page_load
34
+
35
+
36
+ # Raises error for missing translations
37
+ # config.action_view.raise_on_missing_translations = true
38
+
39
+ # Use an evented file watcher to asynchronously detect changes in source code,
40
+ # routes, locales, etc. This feature depends on the listen gem.
41
+ config.file_watcher = ActiveSupport::EventedFileUpdateChecker
42
+ end