Skip to content

Commit

Permalink
Merge pull request #4 from bibendi/feature/chewy-connection
Browse files Browse the repository at this point in the history
Elasticsearch Chewy connection
  • Loading branch information
bibendi authored Jul 23, 2021
2 parents 99d6ecb + de8768c commit e1a6aa3
Show file tree
Hide file tree
Showing 16 changed files with 374 additions and 11 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/tmp/
.pry_history
.rspec_status
*_history
Gemfile.lock
.lefthook-local/
lefthook-local.yml
4 changes: 4 additions & 0 deletions .irbrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

IRB.conf[:SAVE_HISTORY] = 1000
IRB.conf[:HISTORY_FILE] = "#{__dir__}/.irb_history"
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ gem "graphql-connections"

## Usage

### ActiveRecord

You can use a stable connection wrapper on a specific field:

```ruby
Expand Down Expand Up @@ -69,14 +71,33 @@ Or you can apply a stable connection to all Active Record relations returning by

```ruby
class ApplicationSchema < GraphQL::Schema
use GraphQL::Pagination::Connections

connections.add(ActiveRecord::Relation, GraphQL::Connections::Stable)
end
```

**NOTE:** Don't use stable connections for relations whose ordering is too complicated for cursor generation.

### Elasticsearch via Chewy

Register connection for all Chewy requests:

```ruby
class ApplicationSchema < GraphQL::Schema
connections.add(Chewy::Search::Request, GraphQL::Connections::ChewyConnection)
end
```

And define field like below:

```ruby
field :messages, Types::Message.connection_type, null: false

def messages
CitiesIndex.query(match: {name: "Moscow"})
end

**NOTE:** Using `first` and `last`arguments simultaneously is not supported yet.

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
Expand Down
24 changes: 20 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ services:
ruby:
image: ruby:${RUBY_IMAGE:-3.0}-buster
environment:
- HISTFILE=/app/tmp/.bash_history
- BUNDLE_PATH=/bundle
- BUNDLE_CONFIG=/app/.bundle/config
- DATABASE_URL=postgres://postgres:@postgres:5432
HISTFILE: /app/.bash_history
BUNDLE_PATH: /bundle
BUNDLE_CONFIG: /app/.bundle/config
DATABASE_URL: postgres://postgres:@postgres:5432
ELASTICSEARCH_URL: http://elasticsearch:9200
command: bash
working_dir: /app
volumes:
Expand All @@ -16,6 +17,8 @@ services:
depends_on:
postgres:
condition: service_healthy
elasticsearch:
condition: service_healthy

postgres:
image: postgres:13
Expand All @@ -27,5 +30,18 @@ services:
test: pg_isready -U postgres -h 127.0.0.1
interval: 10s

elasticsearch:
image: elasticsearch:7.12.1
ports:
- 9200:9200
environment:
ES_JAVA_OPTS: -Xms64m -Xmx256m
discovery.type: single-node
http.cors.enabled: "true"
bootstrap.memory_lock: "true"
healthcheck:
test: curl --silent --fail 0.0.0.0:9200/_cluster/health
interval: 30s

volumes:
bundler_data:
3 changes: 3 additions & 0 deletions graphql-connections.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ Gem::Specification.new do |spec|
spec.add_runtime_dependency "graphql", "~> 1.10"

spec.add_development_dependency "bundler", ">= 1.16"
spec.add_development_dependency "factory_bot_rails", "~> 6.2"
spec.add_development_dependency "faker", "~> 2.7"
spec.add_development_dependency "chewy", "~> 7.2"
spec.add_development_dependency "combustion", "~> 1.3"
spec.add_development_dependency "pg", "~> 1.2"
spec.add_development_dependency "pry-byebug", "~> 3"
Expand Down
2 changes: 2 additions & 0 deletions lib/graphql/connections.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ module Connections
require "graphql/connections/keyset/base"
require "graphql/connections/keyset/asc"
require "graphql/connections/keyset/desc"

require "graphql/connections/chewy"
37 changes: 37 additions & 0 deletions lib/graphql/connections/chewy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

module GraphQL
module Connections
class Chewy < ::GraphQL::Pagination::RelationConnection
private

def load_nodes
@nodes ||= limited_nodes.objects
end

def relation_count(relation)
offset = relation_offset(relation) || 0
limit = relation_limit(relation)
count = relation.count - offset

if limit.nil?
count
else
[count, limit].min
end
end

def relation_limit(relation)
relation.send(:raw_limit_value)&.to_i
end

def relation_offset(relation)
relation.send(:raw_offset_value)&.to_i
end

def null_relation(relation)
relation.none
end
end
end
end
12 changes: 12 additions & 0 deletions spec/factories/messages.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

FactoryBot.define do
factory :message, class: "Message" do
sequence(:username) { |n| Faker::Internet.unique.username(specifier: 3..32, separators: %w[.]) }
body { Faker::Movies::LordOfTheRings.quote }

trait :deleted do
deleted { true }
end
end
end
4 changes: 0 additions & 4 deletions spec/internal/app/graphql/application_schema.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
# frozen_string_literal: true

class ApplicationSchema < GraphQL::Schema
use ::GraphQL::Execution::Interpreter
use ::GraphQL::Analysis::AST
use GraphQL::Pagination::Connections

default_max_page_size 3

query QueryRoot
Expand Down
48 changes: 48 additions & 0 deletions spec/internal/app/indexes/messages_index.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# frozen_string_literal: true

class MessagesIndex < Chewy::Index
settings analysis: {
filter: {
autocomplete_filter: {
type: "edge_ngram",
min_gram: 2,
max_gram: 20
}
},
analyzer: {
autocomplete: {
type: "custom",
tokenizer: "standard",
filter: ["lowercase", "autocomplete_filter"]
},
autocomplete_search: {
type: "custom",
tokenizer: "standard",
filter: ["lowercase"]
}
}
}

index_scope Message.all

field :username, :body, analyzer: "autocomplete", search_analyzer: "autocomplete_search"
field :deleted, type: "boolean"
field :updated_at, type: "date"

class << self
def active_members
filter(term: {deleted: false})
end

def search(query)
query(
multi_match: {
query: query,
type: "most_fields",
fields: %w[username^2 body],
fuzziness: 1
}
)
end
end
end
1 change: 1 addition & 0 deletions spec/internal/app/models/message.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# frozen_string_literal: true

class Message < ActiveRecord::Base
update_index("messages_index") { self }
end
6 changes: 6 additions & 0 deletions spec/internal/config/chewy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
test:
host: <%= ENV["ELASTICSEARCH_URL"] %>
prefix: 'test'
index:
number_of_shards: 1
number_of_replicas: 1
11 changes: 11 additions & 0 deletions spec/internal/config/initializers/chewy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require "chewy"

Chewy.settings = {
transport_options: {
ssl: {
verify: false
}
}
}
5 changes: 4 additions & 1 deletion spec/internal/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

ActiveRecord::Schema.define do
create_table :messages do |t|
t.text :body, null: false
t.text :username, null: true
t.text :body, null: true
t.text :tag, null: true
t.boolean :deleted, null: false, default: false
t.timestamps null: false
end
end
Loading

0 comments on commit e1a6aa3

Please sign in to comment.