├── Rakefile ├── lib ├── elastic-app-search.rb └── elastic │ ├── app-search │ ├── version.rb │ ├── client │ │ ├── click.rb │ │ ├── logs.rb │ │ ├── schema.rb │ │ ├── meta_engines.rb │ │ ├── query_suggestion.rb │ │ ├── engines.rb │ │ ├── analytics.rb │ │ ├── credentials.rb │ │ ├── curations.rb │ │ ├── synonyms.rb │ │ ├── search_settings.rb │ │ ├── search.rb │ │ └── documents.rb │ ├── utils.rb │ ├── exceptions.rb │ ├── client.rb │ └── request.rb │ └── app-search.rb ├── .rspec ├── NOTICE.txt ├── logo-app-search.png ├── Gemfile ├── .gitignore ├── script └── console ├── spec ├── schema_spec.rb ├── config_helper.rb ├── logs_spec.rb ├── list_documents_spec.rb ├── click_spec.rb ├── query_suggestion_spec.rb ├── search_settings_spec.rb ├── analytics_spec.rb ├── curations_spec.rb ├── credentials_spec.rb ├── synonyms_spec.rb ├── client_spec.rb ├── engines_spec.rb ├── exceptions_spec.rb ├── meta_engines_spec.rb ├── search_spec.rb ├── spec_helper.rb └── documents_spec.rb ├── elastic-app-search.gemspec ├── .circleci └── config.yml ├── LICENSE.txt ├── README.md └── .rubocop.yml /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /lib/elastic-app-search.rb: -------------------------------------------------------------------------------- 1 | require 'elastic/app-search' -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --profile 4 | -r spec_helper 5 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Elastic App Search Ruby client. 2 | 3 | Copyright 2012-2019 Elasticsearch B.V. 4 | -------------------------------------------------------------------------------- /logo-app-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/app-search-ruby/master/logo-app-search.png -------------------------------------------------------------------------------- /lib/elastic/app-search/version.rb: -------------------------------------------------------------------------------- 1 | module Elastic 2 | module AppSearch 3 | VERSION = '7.10.0' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Specify your gem's dependencies in elastic-app-search-ruby.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /lib/elastic/app-search.rb: -------------------------------------------------------------------------------- 1 | require 'elastic/app-search/client' 2 | 3 | module Elastic 4 | module AppSearch 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | .DS_Store 6 | .rvmrc 7 | .ruby-version 8 | .ruby-gemset 9 | .yardoc 10 | doc 11 | -------------------------------------------------------------------------------- /script/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $:.unshift File.expand_path('../../lib', __FILE__) 4 | 5 | require 'elastic/app-search' 6 | require 'irb' 7 | require 'irb/completion' 8 | 9 | IRB.start 10 | -------------------------------------------------------------------------------- /lib/elastic/app-search/client/click.rb: -------------------------------------------------------------------------------- 1 | # Click API - https://swiftype.com/documentation/app-search/api/clickthrough 2 | module Elastic 3 | module AppSearch 4 | class Client 5 | module Click 6 | 7 | # Send data about clicked results. 8 | def log_click_through(engine_name, options) 9 | post("engines/#{engine_name}/documents", options) 10 | end 11 | 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/elastic/app-search/client/logs.rb: -------------------------------------------------------------------------------- 1 | # Logs API - https://swiftype.com/documentation/app-search/api/logs 2 | module Elastic 3 | module AppSearch 4 | class Client 5 | module Logs 6 | 7 | # The API Log displays API request and response data at the Engine level. 8 | def get_api_logs(engine_name, options) 9 | post("engines/#{engine_name}/logs/api", options) 10 | end 11 | 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/elastic/app-search/utils.rb: -------------------------------------------------------------------------------- 1 | module Elastic 2 | module AppSearch 3 | module Utils 4 | extend self 5 | 6 | def stringify_keys(hash) 7 | hash.each_with_object({}) do |(key, value), out| 8 | out[key.to_s] = value 9 | end 10 | end 11 | 12 | def symbolize_keys(hash) 13 | hash.each_with_object({}) do |(key, value), out| 14 | new_key = key.respond_to?(:to_sym) ? key.to_sym : key 15 | out[new_key] = value 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/elastic/app-search/client/schema.rb: -------------------------------------------------------------------------------- 1 | # Schema API - https://swiftype.com/documentation/app-search/api/schema 2 | module Elastic 3 | module AppSearch 4 | class Client 5 | module Schema 6 | 7 | # Retrieve schema for the current engine. 8 | def get_schema(engine_name) 9 | get("engines/#{engine_name}/schema") 10 | end 11 | 12 | # Create a new schema field or update existing schema for the current engine. 13 | def update_schema(engine_name, schema) 14 | post("engines/#{engine_name}/schema", schema) 15 | end 16 | 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/schema_spec.rb: -------------------------------------------------------------------------------- 1 | describe Elastic::AppSearch::Client::Schema do 2 | include_context 'App Search Credentials' 3 | include_context 'Test Engine' 4 | 5 | let(:client) { Elastic::AppSearch::Client.new(client_options) } 6 | 7 | context '#get_schema' do 8 | before { client.update_schema(engine_name, 'title' => 'text') } 9 | subject { client.get_schema(engine_name) } 10 | 11 | it 'will retrieve a schema' do 12 | expect(subject).to(eq('title' => 'text')) 13 | end 14 | end 15 | 16 | context '#update_schema' do 17 | subject { client.update_schema(engine_name, 'square_km' => 'number') } 18 | 19 | it 'will update a schema' do 20 | expect(subject).to(eq('square_km' => 'number')) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/elastic/app-search/client/meta_engines.rb: -------------------------------------------------------------------------------- 1 | module Elastic 2 | module AppSearch 3 | class Client 4 | module MetaEngines 5 | 6 | ENGINE_TYPE_META = 'meta'.freeze() 7 | 8 | def create_meta_engine(engine_name, source_engines) 9 | post('engines', :name => engine_name, :type => ENGINE_TYPE_META, :source_engines => source_engines) 10 | end 11 | 12 | def add_meta_engine_sources(engine_name, source_engines) 13 | post("engines/#{engine_name}/source_engines", source_engines) 14 | end 15 | 16 | def delete_meta_engine_sources(engine_name, source_engines) 17 | delete("engines/#{engine_name}/source_engines", source_engines) 18 | end 19 | 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/elastic/app-search/client/query_suggestion.rb: -------------------------------------------------------------------------------- 1 | module Elastic 2 | module AppSearch 3 | class Client 4 | module QuerySuggestion 5 | # Request Query Suggestions 6 | # 7 | # @param [String] engine_name the unique Engine name 8 | # @param [String] query the search query to suggest for 9 | # @options options see the {App Search API}[https://swiftype.com/documentation/app-search/] for supported search options. 10 | # 11 | # @return [Hash] search results 12 | def query_suggestion(engine_name, query, options = {}) 13 | params = Utils.symbolize_keys(options).merge(:query => query) 14 | request(:post, "engines/#{engine_name}/query_suggestion", params) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/config_helper.rb: -------------------------------------------------------------------------------- 1 | module ConfigHelper 2 | def ConfigHelper.get_as_api_key 3 | ENV.fetch('AS_API_KEY', 'API_KEY') 4 | end 5 | 6 | def ConfigHelper.get_as_admin_key 7 | ENV.fetch('AS_ADMIN_KEY', 'ADMIN_KEY') 8 | end 9 | 10 | def ConfigHelper.get_as_host_identifier 11 | ENV['AS_ACCOUNT_HOST_KEY'] || ENV['AS_HOST_IDENTIFIER'] || 'ACCOUNT_HOST_KEY' 12 | end 13 | 14 | def ConfigHelper.get_as_api_endpoint 15 | ENV.fetch('AS_API_ENDPOINT', nil) 16 | end 17 | 18 | def ConfigHelper.get_client_options(as_api_key, as_host_identifier, as_api_endpoint) 19 | { 20 | :api_key => as_api_key, 21 | :host_identifier => as_host_identifier 22 | }.tap do |opts| 23 | opts[:api_endpoint] = as_api_endpoint unless as_api_endpoint.nil? 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/elastic/app-search/client/engines.rb: -------------------------------------------------------------------------------- 1 | # Engines are the core concept representing indexes in App Search. 2 | # 3 | module Elastic 4 | module AppSearch 5 | class Client 6 | module Engines 7 | 8 | def list_engines(current: 1, size: 20) 9 | get("engines", :page => { :current => current, :size => size }) 10 | end 11 | 12 | def get_engine(engine_name) 13 | get("engines/#{engine_name}") 14 | end 15 | 16 | def create_engine(engine_name, language = nil) 17 | params = { :name => engine_name } 18 | params[:language] = language if language 19 | post("engines", params) 20 | end 21 | 22 | def destroy_engine(engine_name) 23 | delete("engines/#{engine_name}") 24 | end 25 | 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/logs_spec.rb: -------------------------------------------------------------------------------- 1 | describe Elastic::AppSearch::Client::Logs do 2 | include_context 'App Search Credentials' 3 | include_context 'Test Engine' 4 | 5 | let(:client) { Elastic::AppSearch::Client.new(client_options) } 6 | 7 | context '#get_api_logs' do 8 | let(:from) { Time.now.iso8601 } 9 | let(:to) { Time.now.iso8601 } 10 | 11 | subject do 12 | options = { 13 | :filters => { 14 | :date => { 15 | :from => from, 16 | :to => to 17 | } 18 | }, 19 | :page => { 20 | :total_results => 100, 21 | :size => 20 22 | }, 23 | :query => 'search', 24 | :sort_direction => 'desc' 25 | } 26 | client.get_api_logs(engine_name, options) 27 | end 28 | 29 | it 'will retrieve api logs' do 30 | expect(subject['results']).to(eq([])) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/elastic/app-search/client/analytics.rb: -------------------------------------------------------------------------------- 1 | # Analytics API - https://swiftype.com/documentation/app-search/api/analytics 2 | module Elastic 3 | module AppSearch 4 | class Client 5 | module Analytics 6 | 7 | # Returns the number of clicks received by a document in descending order. 8 | def get_top_clicks_analytics(engine_name, options) 9 | post("engines/#{engine_name}/analytics/clicks", options) 10 | end 11 | 12 | # Returns queries analytics by usage count 13 | def get_top_queries_analytics(engine_name, options) 14 | post("engines/#{engine_name}/analytics/queries", options) 15 | end 16 | 17 | # Returns the number of clicks and total number of queries over a period. 18 | def get_count_analytics(engine_name, options) 19 | post("engines/#{engine_name}/analytics/counts", options) 20 | end 21 | 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/list_documents_spec.rb: -------------------------------------------------------------------------------- 1 | describe Elastic::AppSearch::Client::Documents do 2 | include_context 'App Search Credentials' 3 | include_context 'Static Test Engine' 4 | 5 | let(:client) { Elastic::AppSearch::Client.new(client_options) } 6 | 7 | context 'Documents' do 8 | describe '#list_documents' do 9 | context 'when no options are specified' do 10 | it 'will return all documents' do 11 | response = client.list_documents(engine_name) 12 | expect(response['results'].size).to(eq(2)) 13 | expect(response['results'].map { |d| d['id'] }).to(include(document1['id'], document2['id'])) 14 | end 15 | end 16 | 17 | context 'when options are specified' do 18 | it 'will return all documents' do 19 | response = client.list_documents(engine_name, :page => { :size => 1, :current => 2 }) 20 | expect(response['results'].size).to(eq(1)) 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/click_spec.rb: -------------------------------------------------------------------------------- 1 | describe Elastic::AppSearch::Client::Click do 2 | include_context 'App Search Credentials' 3 | include_context 'Test Engine' 4 | 5 | let(:client) { Elastic::AppSearch::Client.new(client_options) } 6 | 7 | context '#log_click_through' do 8 | let(:documents) { [first_document, second_document] } 9 | let(:first_document_id) { 'id' } 10 | let(:first_document) { { 'id' => first_document_id } } 11 | let(:second_document_id) { 'another_id' } 12 | let(:second_document) { { 'id' => second_document_id } } 13 | 14 | before { client.index_documents(engine_name, documents) } 15 | 16 | subject do 17 | client.log_click_through( 18 | engine_name, 19 | :query => 'cat videos', 20 | :document_id => first_document_id, 21 | :request_id => 'e4c4dea0bd7ad3d2f676575ef16dc7d2', 22 | :tags => ['firefox', 'web browser'] 23 | ) 24 | end 25 | 26 | it 'will log a click' do 27 | expect(subject[0]['id']).not_to(be_empty) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/elastic/app-search/client/credentials.rb: -------------------------------------------------------------------------------- 1 | # Credentials API - https://swiftype.com/documentation/app-search/api/credentials 2 | module Elastic 3 | module AppSearch 4 | class Client 5 | module Credentials 6 | 7 | # Retrieve available credentials 8 | def list_credentials(current: 1, size: 20) 9 | get("credentials", :page => { :current => current, :size => size }) 10 | end 11 | 12 | # Retrieve a credential 13 | def get_credential(name) 14 | get("credentials/#{name}") 15 | end 16 | 17 | # Create a new credential 18 | def create_credential(options) 19 | post("credentials", options) 20 | end 21 | 22 | # Update an existing credential 23 | def update_credential(name, options) 24 | put("credentials/#{name}", options) 25 | end 26 | 27 | # Destroy an existing credential 28 | def destroy_credential(name) 29 | delete("credentials/#{name}") 30 | end 31 | 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /elastic-app-search.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | require "elastic/app-search/version" 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "elastic-app-search" 6 | s.version = Elastic::AppSearch::VERSION 7 | s.authors = ["Quin Hoxie"] 8 | s.email = ["support@elastic.co"] 9 | s.homepage = "https://github.com/elastic/app-search-ruby" 10 | s.summary = %q{Official gem for accessing the Elastic App Search API} 11 | s.description = %q{API client for accessing the Elastic App Search API with no dependencies.} 12 | s.licenses = ['Apache-2.0'] 13 | 14 | s.files = `git ls-files`.split("\n") 15 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 16 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 17 | s.require_paths = ["lib"] 18 | 19 | s.add_development_dependency 'awesome_print', '~> 1.8' 20 | s.add_development_dependency 'pry', '~> 0.11.3' 21 | s.add_development_dependency 'rspec', '~> 3.0' 22 | s.add_development_dependency 'webmock', '~> 3.3' 23 | 24 | s.add_runtime_dependency 'jwt', '>= 1.5', '< 3.0' 25 | end 26 | -------------------------------------------------------------------------------- /lib/elastic/app-search/client/curations.rb: -------------------------------------------------------------------------------- 1 | # Curations API - https://swiftype.com/documentation/app-search/api/curations 2 | module Elastic 3 | module AppSearch 4 | class Client 5 | module Curations 6 | 7 | # Retrieve available curations for the engine. 8 | def list_curations(engine_name, current: 1, size: 20) 9 | get("engines/#{engine_name}/curations", :page => { :current => current, :size => size }) 10 | end 11 | 12 | # Create a new curation. 13 | def create_curation(engine_name, options) 14 | post("engines/#{engine_name}/curations", options) 15 | end 16 | 17 | # Retrieve a curation by id. 18 | def get_curation(engine_name, id) 19 | get("engines/#{engine_name}/curations/#{id}") 20 | end 21 | 22 | # Update an existing curation. 23 | def update_curation(engine_name, id, options) 24 | put("engines/#{engine_name}/curations/#{id}", options) 25 | end 26 | 27 | # Delete a curation by id. 28 | def destroy_curation(engine_name, id) 29 | delete("engines/#{engine_name}/curations/#{id}") 30 | end 31 | 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/elastic/app-search/client/synonyms.rb: -------------------------------------------------------------------------------- 1 | # Synonyms API - https://swiftype.com/documentation/app-search/api/synonyms 2 | module Elastic 3 | module AppSearch 4 | class Client 5 | module Synonyms 6 | 7 | # Retrieve available synonym sets for the engine. 8 | def list_synonym_sets(engine_name, current: 1, size: 20) 9 | get("engines/#{engine_name}/synonyms", :page => { :current => current, :size => size }) 10 | end 11 | 12 | # Retrieve a synonym set by id 13 | def get_synonym_set(engine_name, id) 14 | get("engines/#{engine_name}/synonyms/#{id}") 15 | end 16 | 17 | # Create a new synonym set 18 | def create_synonym_set(engine_name, body) 19 | post("engines/#{engine_name}/synonyms", body) 20 | end 21 | 22 | # Update an existing synonym set 23 | def update_synonym_set(engine_name, id, body) 24 | put("engines/#{engine_name}/synonyms/#{id}", body) 25 | end 26 | 27 | # Delete a synonym set by id 28 | def destroy_synonym_set(engine_name, id) 29 | delete("engines/#{engine_name}/synonyms/#{id}") 30 | end 31 | 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/query_suggestion_spec.rb: -------------------------------------------------------------------------------- 1 | describe Elastic::AppSearch::Client::Search do 2 | include_context 'App Search Credentials' 3 | include_context 'Static Test Engine' 4 | 5 | let(:client) { Elastic::AppSearch::Client.new(client_options) } 6 | 7 | context 'QuerySuggest' do 8 | describe '#query_suggestion' do 9 | let(:query) { 'cat' } 10 | let(:options) { { :size => 3, :types => { :documents => { :fields => ['title'] } } } } 11 | 12 | context 'when options are provided' do 13 | subject { client.query_suggestion(engine_name, query, options) } 14 | 15 | it 'should request query suggestions' do 16 | expected = { 17 | 'meta' => anything, 18 | 'results' => anything 19 | } 20 | expect(subject).to(match(expected)) 21 | end 22 | end 23 | 24 | context 'when options are omitted' do 25 | subject { client.query_suggestion(engine_name, query) } 26 | 27 | it 'should request query suggestions' do 28 | expected = { 29 | 'meta' => anything, 30 | 'results' => anything 31 | } 32 | expect(subject).to(match(expected)) 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/elastic/app-search/client/search_settings.rb: -------------------------------------------------------------------------------- 1 | # Search Settings is used to adjust weights and boosts 2 | module Elastic 3 | module AppSearch 4 | class Client 5 | module SearchSettings 6 | 7 | # Show all Weights and Boosts applied to the search fields of an Engine. 8 | # 9 | # @param [String] engine_name the unique Engine name 10 | # 11 | # @return [Hash] current Search Settings 12 | def show_settings(engine_name) 13 | get("engines/#{engine_name}/search_settings") 14 | end 15 | 16 | # Update Weights or Boosts for search fields of an Engine. 17 | # 18 | # @param [String] engine_name the unique Engine name 19 | # @param [Hash] settings new Search Settings Hash 20 | # 21 | # @return [Hash] new Search Settings 22 | def update_settings(engine_name, settings) 23 | put("engines/#{engine_name}/search_settings", settings) 24 | end 25 | 26 | # Reset Engine's Search Settings to default values. 27 | # 28 | # @param [String] engine_name the unique Engine name 29 | # 30 | # @return [Hash] default Search Settings 31 | def reset_settings(engine_name) 32 | post("engines/#{engine_name}/search_settings/reset") 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/elastic/app-search/exceptions.rb: -------------------------------------------------------------------------------- 1 | module Elastic 2 | module AppSearch 3 | class ClientException < StandardError 4 | attr_reader :errors 5 | 6 | def extract_messages(response) 7 | errors_value = response['errors'] 8 | return errors_value if errors_value && errors_value.is_a?(Array) 9 | return [errors_value] if errors_value && !errors_value.is_a?(Array) 10 | [response] 11 | end 12 | 13 | def initialize(response) 14 | @errors = response.is_a?(Array) ? response.flat_map { |r| extract_messages(r) } : extract_messages(response) 15 | message = (errors.size == 1) ? "Error: #{errors.first}" : "Errors: #{errors.inspect}" 16 | super(message) 17 | end 18 | end 19 | 20 | class NonExistentRecord < ClientException; end 21 | class InvalidCredentials < ClientException; end 22 | class BadRequest < ClientException; end 23 | class Forbidden < ClientException; end 24 | class InvalidDocument < ClientException; end 25 | class RequestEntityTooLarge < ClientException; end 26 | 27 | class UnexpectedHTTPException < ClientException 28 | def initialize(response, response_json) 29 | errors = (response_json['errors'] || [response.message]).map { |e| "(#{response.code}) #{e}" } 30 | super({ 'errors' => errors }) 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/elastic/app-search/client/search.rb: -------------------------------------------------------------------------------- 1 | module Elastic 2 | module AppSearch 3 | class Client 4 | module Search 5 | # Search for documents 6 | # 7 | # @param [String] engine_name the unique Engine name 8 | # @param [String] query the search query 9 | # @option options see the {App Search API}[https://swiftype.com/documentation/app-search/] for supported search options. 10 | # 11 | # @return [Hash] search results 12 | def search(engine_name, query, options = {}) 13 | params = Utils.symbolize_keys(options).merge(:query => query) 14 | request(:post, "engines/#{engine_name}/search", params) 15 | end 16 | 17 | # Run multiple searches for documents on a single request 18 | # 19 | # @param [String] engine_name the unique Engine name 20 | # @param [{query: String, options: Hash}] searches to execute 21 | # see the {App Search API}[https://swiftype.com/documentation/app-search/] for supported search options. 22 | # 23 | # @return [Array] an Array of searh sesults 24 | def multi_search(engine_name, searches) 25 | params = searches.map do |search| 26 | search = Utils.symbolize_keys(search) 27 | query = search[:query] 28 | options = search[:options] || {} 29 | Utils.symbolize_keys(options).merge(:query => query) 30 | end 31 | request(:post, "engines/#{engine_name}/multi_search", { 32 | queries: params 33 | }) 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/search_settings_spec.rb: -------------------------------------------------------------------------------- 1 | describe Elastic::AppSearch::Client::SearchSettings do 2 | include_context 'App Search Credentials' 3 | include_context 'Test Engine' 4 | 5 | let(:client) { Elastic::AppSearch::Client.new(client_options) } 6 | 7 | context 'SearchSettings' do 8 | let(:default_settings) do 9 | { 10 | 'search_fields' => { 11 | 'id' => { 12 | 'weight' => 1 13 | } 14 | }, 15 | 'result_fields' => { 'id' => { 'raw' => {} } }, 16 | 'boosts' => {} 17 | } 18 | end 19 | 20 | let(:updated_settings) do 21 | { 22 | 'search_fields' => { 23 | 'id' => { 24 | 'weight' => 3 25 | } 26 | }, 27 | 'result_fields' => { 'id' => { 'raw' => {} } }, 28 | 'boosts' => {} 29 | } 30 | end 31 | 32 | describe '#show_settings' do 33 | subject { client.show_settings(engine_name) } 34 | 35 | it 'should return default settings' do 36 | expect(subject).to(match(default_settings)) 37 | end 38 | end 39 | 40 | describe '#update_settings' do 41 | subject { client.show_settings(engine_name) } 42 | 43 | before do 44 | client.update_settings(engine_name, updated_settings) 45 | end 46 | 47 | it 'should update search settings' do 48 | expect(subject).to(match(updated_settings)) 49 | end 50 | end 51 | 52 | describe '#reset_settings' do 53 | subject { client.show_settings(engine_name) } 54 | 55 | before do 56 | client.update_settings(engine_name, updated_settings) 57 | client.reset_settings(engine_name) 58 | end 59 | 60 | it 'should reset search settings' do 61 | expect(subject).to(match(default_settings)) 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/analytics_spec.rb: -------------------------------------------------------------------------------- 1 | describe Elastic::AppSearch::Client::Analytics do 2 | include_context 'App Search Credentials' 3 | include_context 'Test Engine' 4 | 5 | let(:client) { Elastic::AppSearch::Client.new(client_options) } 6 | 7 | context '#get_top_clicks_analytics' do 8 | subject do 9 | client.get_top_clicks_analytics( 10 | engine_name, 11 | :query => 'cats', 12 | :page => { 13 | :size => 20, 14 | }, 15 | :filters => { 16 | :date => { 17 | :from => Time.now.iso8601, 18 | :to => Time.now.iso8601 19 | } 20 | } 21 | ) 22 | end 23 | 24 | it 'will query for analytics' do 25 | expect(subject['results']).to(eq([])) 26 | end 27 | end 28 | 29 | context '#get_top_queries_analytics' do 30 | subject do 31 | client.get_top_queries_analytics( 32 | engine_name, 33 | :page => { 34 | :size => 20 35 | }, 36 | :filters => { 37 | :date => { 38 | :from => Time.now.iso8601, 39 | :to => Time.now.iso8601 40 | } 41 | } 42 | ) 43 | end 44 | 45 | it 'will query for analytics' do 46 | expect(subject['results']).to(eq([])) 47 | end 48 | end 49 | 50 | context '#get_count_analytics' do 51 | let(:from) { Time.now.iso8601 } 52 | let(:to) { Time.now.iso8601 } 53 | 54 | subject do 55 | client.get_count_analytics( 56 | engine_name, 57 | :filters => { 58 | :all => [ 59 | { 60 | :tag => ['mobile', 'web'] 61 | }, { 62 | :query => 'cats' 63 | }, { 64 | :document_id => '163' 65 | }, { 66 | :date => { 67 | :from => from, 68 | :to => to 69 | } 70 | } 71 | ] 72 | }, 73 | :interval => 'hour' 74 | ) 75 | end 76 | 77 | it 'will query for analytics' do 78 | expect(subject['results'][0]['clicks']).to(eq(0)) 79 | expect(subject['results'][0]['queries']).to(eq(0)) 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Ruby CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-ruby/ for more details 4 | # 5 | version: 2.1 6 | 7 | executors: 8 | ruby-24: { docker: [{ image: "ruby:2.4" }] } 9 | ruby-25: { docker: [{ image: "ruby:2.5" }] } 10 | ruby-26: { docker: [{ image: "ruby:2.6" }] } 11 | jruby-92: { docker: [{ image: "jruby:9.2.7-jdk" }] } 12 | 13 | jobs: 14 | build: 15 | parameters: 16 | executor: 17 | type: executor 18 | executor: << parameters.executor >> 19 | 20 | working_directory: ~/repo 21 | 22 | steps: 23 | - checkout 24 | 25 | # Download and cache dependencies 26 | - restore_cache: 27 | keys: 28 | - v1-dependencies-{{ checksum "Gemfile" }} 29 | # fallback to using the latest cache if no exact match is found 30 | - v1-dependencies- 31 | 32 | - run: 33 | name: install dependencies 34 | command: | 35 | bundle install --jobs=4 --retry=3 --path vendor/bundle 36 | 37 | - save_cache: 38 | paths: 39 | - ./vendor/bundle 40 | key: v1-dependencies-{{ checksum "Gemfile" }} 41 | 42 | # run tests! 43 | - run: 44 | name: run tests 45 | command: | 46 | mkdir /tmp/test-results 47 | TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)" 48 | 49 | bundle exec rspec --format progress \ 50 | --out /tmp/test-results/rspec.xml \ 51 | --format progress \ 52 | $TEST_FILES 53 | 54 | # collect reports 55 | - store_test_results: 56 | path: /tmp/test-results 57 | - store_artifacts: 58 | path: /tmp/test-results 59 | destination: test-results 60 | 61 | workflows: 62 | run-tests: 63 | jobs: 64 | - build: { name: run-tests-ruby-2.4, executor: ruby-24 } 65 | - build: { name: run-tests-ruby-2.5, executor: ruby-25 } 66 | - build: { name: run-tests-ruby-2.6, executor: ruby-26 } 67 | - build: { name: run-tests-jruby-92, executor: jruby-92 } 68 | -------------------------------------------------------------------------------- /spec/curations_spec.rb: -------------------------------------------------------------------------------- 1 | describe Elastic::AppSearch::Client::Curations do 2 | include_context 'App Search Credentials' 3 | include_context 'Static Test Engine' 4 | 5 | let(:client) { Elastic::AppSearch::Client.new(client_options) } 6 | 7 | let(:curation) do 8 | { 9 | 'queries' => [ 10 | 'zion' 11 | ], 12 | 'promoted' => [ 13 | document1['id'] 14 | ], 15 | 'hidden' => [ 16 | document2['id'] 17 | ] 18 | } 19 | end 20 | let(:curation_id) { client.create_curation(engine_name, curation)['id'] } 21 | 22 | after(:each) do 23 | begin 24 | client.destroy_curation(engine_name, curation_id) 25 | rescue 26 | # Ignore it 27 | end 28 | end 29 | 30 | context '#create_curation' do 31 | it 'will create a curation' do 32 | expect(curation_id).not_to(be_empty) 33 | end 34 | end 35 | 36 | context '#get_curation' do 37 | subject { client.get_curation(engine_name, curation_id) } 38 | 39 | it 'will retrieve a curation' do 40 | expect(subject['queries']).to(eq(['zion'])) 41 | end 42 | end 43 | 44 | context '#update_curation' do 45 | let(:updated_curation) do 46 | { 47 | 'queries' => [ 48 | 'zion', 'lion' 49 | ], 50 | 'promoted' => [ 51 | document1['id'] 52 | ] 53 | } 54 | end 55 | subject { client.update_curation(engine_name, curation_id, updated_curation) } 56 | 57 | it 'will update a curation' do 58 | expect(subject['id']).to(eq(curation_id)) 59 | end 60 | end 61 | 62 | context '#list_curations' do 63 | subject { client.list_curations(engine_name, :current => 1, :size => 5) } 64 | 65 | it 'will list curations' do 66 | expect(subject['results']).to(eq([])) 67 | end 68 | 69 | it 'supports paging params' do 70 | expect(subject['meta']['page']['current']).to(eq(1)) 71 | expect(subject['meta']['page']['size']).to(eq(5)) 72 | end 73 | end 74 | 75 | context '#destroy_curation' do 76 | subject { client.destroy_curation(engine_name, curation_id) } 77 | 78 | it 'will destroy a curation' do 79 | expect(subject['deleted']).to(eq(true)) 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /spec/credentials_spec.rb: -------------------------------------------------------------------------------- 1 | require 'securerandom' 2 | 3 | describe Elastic::AppSearch::Client::Credentials do 4 | include_context 'App Search Admin Credentials' 5 | include_context 'Test Engine' 6 | 7 | let(:client) { Elastic::AppSearch::Client.new(client_options) } 8 | let(:key_name) { "spec-key-#{SecureRandom.hex}" } 9 | let(:api_key) do 10 | { 11 | :name => key_name, 12 | :type => 'private', 13 | :read => true, 14 | :write => false, 15 | :access_all_engines => false, 16 | :engines => [ 17 | engine_name 18 | ] 19 | } 20 | end 21 | 22 | context '#create_credential' do 23 | after { client.destroy_credential(key_name) } 24 | subject { client.create_credential(api_key) } 25 | 26 | it 'will create an API Key' do 27 | expect(subject['name']).to(eq(key_name)) 28 | end 29 | end 30 | 31 | context '#get_credential' do 32 | after { client.destroy_credential(key_name) } 33 | before { client.create_credential(api_key) } 34 | subject { client.get_credential(key_name) } 35 | 36 | it 'will retrieve an API Key' do 37 | expect(subject['name']).to(eq(key_name)) 38 | end 39 | end 40 | 41 | context '#update_credential' do 42 | let(:updated_api_key) do 43 | api_key['write'] = true 44 | api_key 45 | end 46 | 47 | before { client.create_credential(api_key) } 48 | after { client.destroy_credential(key_name) } 49 | subject { client.update_credential(key_name, updated_api_key) } 50 | 51 | it 'will update an API Key' do 52 | expect(subject['name']).to(eq(key_name)) 53 | expect(subject['write']).to(eq(true)) 54 | end 55 | end 56 | 57 | context '#list_credentials' do 58 | before { client.create_credential(api_key) } 59 | after { client.destroy_credential(key_name) } 60 | subject { client.list_credentials } 61 | 62 | it 'will list all API Keys' do 63 | expect(subject['results'].map { |r| r['name'] }.include?(key_name)).to(eq(true)) 64 | end 65 | end 66 | 67 | context '#destroy_credential' do 68 | before { client.create_credential(api_key) } 69 | subject { client.destroy_credential(key_name) } 70 | 71 | it 'will delete an API Key' do 72 | expect(subject['deleted']).to(eq(true)) 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/synonyms_spec.rb: -------------------------------------------------------------------------------- 1 | describe Elastic::AppSearch::Client::Synonyms do 2 | include_context 'App Search Credentials' 3 | include_context 'Test Engine' 4 | 5 | let(:client) { Elastic::AppSearch::Client.new(client_options) } 6 | 7 | context '#create_synonym_set' do 8 | let(:synonyms) { ['park', 'trail'] } 9 | subject { client.create_synonym_set(engine_name, :synonyms => synonyms) } 10 | 11 | it 'will create a synonym' do 12 | expect(subject['id']).not_to(be_empty) 13 | expect(subject['synonyms']).to(eq(synonyms)) 14 | end 15 | end 16 | 17 | context '#get_synonym_set' do 18 | let(:synonyms) { ['park', 'trail'] } 19 | let(:synonym_set_id) { client.create_synonym_set(engine_name, :synonyms => synonyms)['id'] } 20 | 21 | subject { client.get_synonym_set(engine_name, synonym_set_id) } 22 | 23 | it 'will retrieve a synonym set' do 24 | expect(subject['id']).to(eq(synonym_set_id)) 25 | expect(subject['synonyms']).to(eq(synonyms)) 26 | end 27 | end 28 | 29 | context '#update_synonym_set' do 30 | let(:synonyms) { ['park', 'trail'] } 31 | let(:updated_synonyms) { %w[park trail system] } 32 | let(:synonym_set_id) { client.create_synonym_set(engine_name, :synonyms => synonyms)['id'] } 33 | 34 | subject { client.update_synonym_set(engine_name, synonym_set_id, :synonyms => updated_synonyms) } 35 | 36 | it 'will update a synonym set' do 37 | expect(subject['id']).to(eq(synonym_set_id)) 38 | expect(subject['synonyms']).to(eq(updated_synonyms)) 39 | end 40 | end 41 | 42 | context '#list_synonym_sets' do 43 | subject { client.list_synonym_sets(engine_name, :current => 2, :size => 10) } 44 | 45 | it 'will list synonyms' do 46 | expect(subject['results']).to(eq([])) 47 | end 48 | 49 | it 'support paging params' do 50 | expect(subject['meta']['page']['current']).to(eq(2)) 51 | expect(subject['meta']['page']['size']).to(eq(10)) 52 | end 53 | end 54 | 55 | context '#destroy_synonym_set' do 56 | let(:synonyms) { ['park', 'trail'] } 57 | let(:synonym_set_id) { client.create_synonym_set(engine_name, :synonyms => synonyms)['id'] } 58 | 59 | subject { client.destroy_synonym_set(engine_name, synonym_set_id) } 60 | 61 | it 'will delete a synonym set' do 62 | expect(subject['deleted']).to(eq(true)) 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/client_spec.rb: -------------------------------------------------------------------------------- 1 | require 'config_helper' 2 | require 'securerandom' 3 | 4 | describe Elastic::AppSearch::Client do 5 | include_context 'App Search Credentials' 6 | let(:client) { Elastic::AppSearch::Client.new(client_options) } 7 | 8 | describe '#create_signed_search_key' do 9 | let(:key) { 'private-xxxxxxxxxxxxxxxxxxxx' } 10 | let(:api_key_name) { 'private-key' } 11 | let(:enforced_options) do 12 | { 13 | 'query' => 'cat' 14 | } 15 | end 16 | 17 | subject do 18 | Elastic::AppSearch::Client.create_signed_search_key(key, api_key_name, enforced_options) 19 | end 20 | 21 | it 'should build a valid jwt' do 22 | decoded_token = JWT.decode(subject, key, true, :algorithm => 'HS256') 23 | expect(decoded_token[0]['api_key_name']).to(eq(api_key_name)) 24 | expect(decoded_token[0]['query']).to(eq('cat')) 25 | end 26 | end 27 | 28 | describe 'Requests' do 29 | it 'should include client name and version in headers' do 30 | if (client_options[:api_endpoint]) 31 | stub_request(:any, "#{client_options[:api_endpoint]}engines") 32 | client.list_engines 33 | expect(WebMock).to( 34 | have_requested(:get, "#{client_options[:api_endpoint]}engines") 35 | .with( 36 | :headers => { 37 | 'X-Swiftype-Client' => 'elastic-app-search-ruby', 38 | 'X-Swiftype-Client-Version' => Elastic::AppSearch::VERSION 39 | } 40 | ) 41 | ) 42 | else 43 | # CI runs against saas, so we keep this around for now. CI should be updated 44 | # to use slef-managed and we should drop support "host_identifier" this. 45 | stub_request(:any, "#{client_options[:host_identifier]}.api.swiftype.com/api/as/v1/engines") 46 | client.list_engines 47 | expect(WebMock).to( 48 | have_requested(:get, "https://#{client_options[:host_identifier]}.api.swiftype.com/api/as/v1/engines") 49 | .with( 50 | :headers => { 51 | 'X-Swiftype-Client' => 'elastic-app-search-ruby', 52 | 'X-Swiftype-Client-Version' => Elastic::AppSearch::VERSION 53 | } 54 | ) 55 | ) 56 | end 57 | end 58 | end 59 | 60 | context 'Configuration' do 61 | context 'host_identifier' do 62 | it 'sets the base url correctly' do 63 | client = Elastic::AppSearch::Client.new(:host_identifier => 'host-asdf', :api_key => 'foo') 64 | expect(client.api_endpoint).to(eq('https://host-asdf.api.swiftype.com/api/as/v1/')) 65 | end 66 | 67 | it 'sets the base url correctly using deprecated as_host_key' do 68 | client = Elastic::AppSearch::Client.new(:account_host_key => 'host-asdf', :api_key => 'foo') 69 | expect(client.api_endpoint).to(eq('https://host-asdf.api.swiftype.com/api/as/v1/')) 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/engines_spec.rb: -------------------------------------------------------------------------------- 1 | describe Elastic::AppSearch::Client::Engines do 2 | include_context 'App Search Credentials' 3 | include_context 'Engine Name' 4 | 5 | let(:client) { Elastic::AppSearch::Client.new(client_options) } 6 | 7 | context 'Engines' do 8 | after do 9 | client.destroy_engine(engine_name) rescue Elastic::AppSearch::NonExistentRecord 10 | end 11 | 12 | context '#create_engine' do 13 | it 'should create an engine when given a right set of parameters' do 14 | expect { client.get_engine(engine_name) }.to(raise_error(Elastic::AppSearch::NonExistentRecord)) 15 | client.create_engine(engine_name) 16 | expect { client.get_engine(engine_name) }.to_not(raise_error) 17 | end 18 | 19 | it 'should accept an optional language parameter' do 20 | expect { client.get_engine(engine_name) }.to(raise_error(Elastic::AppSearch::NonExistentRecord)) 21 | client.create_engine(engine_name, 'da') 22 | expect(client.get_engine(engine_name)).to(match('name' => anything, 'type' => anything, 'language' => 'da')) 23 | end 24 | 25 | it 'should return an engine object' do 26 | engine = client.create_engine(engine_name) 27 | expect(engine).to(be_kind_of(Hash)) 28 | expect(engine['name']).to(eq(engine_name)) 29 | end 30 | 31 | it 'should return an error when the engine name has already been taken' do 32 | client.create_engine(engine_name) 33 | expect { client.create_engine(engine_name) }.to(raise_error) do |e| 34 | expect(e).to(be_a(Elastic::AppSearch::BadRequest)) 35 | expect(e.errors).to(eq(['Name is already taken'])) 36 | end 37 | end 38 | end 39 | 40 | context '#list_engines' do 41 | it 'should return an array with a list of engines' do 42 | expect(client.list_engines['results']).to(be_an(Array)) 43 | end 44 | 45 | it 'should include the engine name in listed objects' do 46 | client.create_engine(engine_name) 47 | 48 | engines = client.list_engines['results'] 49 | expect(engines.find { |e| e['name'] == engine_name }).to_not(be_nil) 50 | end 51 | 52 | it 'should include the engine name in listed objects with pagination' do 53 | client.create_engine(engine_name) 54 | 55 | engines = client.list_engines(:current => 1, :size => 20)['results'] 56 | expect(engines.find { |e| e['name'] == engine_name }).to_not(be_nil) 57 | end 58 | end 59 | 60 | context '#destroy_engine' do 61 | it 'should destroy the engine if it exists' do 62 | client.create_engine(engine_name) 63 | expect { client.get_engine(engine_name) }.to_not(raise_error) 64 | 65 | client.destroy_engine(engine_name) 66 | expect { client.get_engine(engine_name) }.to(raise_error(Elastic::AppSearch::NonExistentRecord)) 67 | end 68 | 69 | it 'should raise an error if the engine does not exist' do 70 | expect { client.destroy_engine(engine_name) }.to(raise_error(Elastic::AppSearch::NonExistentRecord)) 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/exceptions_spec.rb: -------------------------------------------------------------------------------- 1 | describe Elastic::AppSearch::ClientException do 2 | describe "when parsing a response body" do 3 | describe "and there is an 'errors' property on the response" do 4 | it 'will parse a single error message' do 5 | expect(Elastic::AppSearch::ClientException.new( 6 | { "errors" => [ "Unauthorized action." ] } 7 | ).message).to eq("Error: Unauthorized action.") 8 | end 9 | 10 | it 'will parse multiple error messages' do 11 | expect(Elastic::AppSearch::ClientException.new( 12 | { "errors" => [ "Unauthorized action.", "Service unavailable" ] } 13 | ).message).to eq("Errors: [\"Unauthorized action.\", \"Service unavailable\"]") 14 | end 15 | 16 | it 'will parse when there is a string instead of an array in errors' do 17 | expect(Elastic::AppSearch::ClientException.new( 18 | { "errors" => "Routing Error. The path you have requested is invalid." } 19 | ).message).to eq("Error: Routing Error. The path you have requested is invalid.") 20 | end 21 | end 22 | 23 | describe "when there is an array of responses" do 24 | it 'will parse a single error message' do 25 | expect(Elastic::AppSearch::ClientException.new( 26 | [ 27 | { "errors" => [ "Unauthorized action." ] }, 28 | { "errors" => [ "Service unavailable" ] } 29 | ] 30 | ).message).to eq("Errors: [\"Unauthorized action.\", \"Service unavailable\"]") 31 | end 32 | 33 | it 'will parse multiple error messages' do 34 | expect(Elastic::AppSearch::ClientException.new( 35 | [ 36 | { "errors" => [ "Unauthorized action.", "Service unavailable" ] }, 37 | { "errors" => [ "Another error" ] } 38 | ] 39 | ).message).to eq("Errors: [\"Unauthorized action.\", \"Service unavailable\", \"Another error\"]") 40 | end 41 | 42 | it 'will parse when there is a string instead of an array in errors' do 43 | expect(Elastic::AppSearch::ClientException.new( 44 | [ 45 | { "errors" => [ "Unauthorized action.", "Service unavailable" ] }, 46 | { "errors" => [ "Another error" ] }, 47 | { "errors" => "Routing Error. The path you have requested is invalid." } 48 | ] 49 | ).message).to eq("Errors: [\"Unauthorized action.\", \"Service unavailable\", \"Another error\", \"Routing Error. The path you have requested is invalid.\"]") 50 | end 51 | end 52 | 53 | describe "and there is a single 'error'" do 54 | it 'will just return the entire response body' do 55 | expect(Elastic::AppSearch::ClientException.new( 56 | { "error" => "Unauthorized action." } 57 | ).message).to eq("Error: {\"error\"=>\"Unauthorized action.\"}") 58 | end 59 | end 60 | 61 | describe "and there is a just a string" do 62 | it 'will just return the entire response body' do 63 | expect(Elastic::AppSearch::ClientException.new( 64 | "Unauthorized action." 65 | ).message).to eq("Error: Unauthorized action.") 66 | end 67 | end 68 | end 69 | end -------------------------------------------------------------------------------- /spec/meta_engines_spec.rb: -------------------------------------------------------------------------------- 1 | describe Elastic::AppSearch::Client::MetaEngines do 2 | include_context 'App Search Credentials' 3 | include_context 'Engine Name' 4 | include_context 'Meta Engine Name' 5 | include_context 'Test Engine' 6 | 7 | let(:client) { Elastic::AppSearch::Client.new(client_options) } 8 | let(:source_engines) { [engine_name] } 9 | 10 | # CI currently runs against SaaS. This feature is a Self-Managed only feature. 11 | context 'Meta Engines', :skip => "Unable to test Self-Managed features in CI." do 12 | 13 | after do 14 | client.destroy_engine(meta_engine_name) rescue Elastic::AppSearch::NonExistentRecord 15 | end 16 | 17 | context '#create_meta_engine' do 18 | it 'should create a meta engine when given a right set of parameters' do 19 | expect { client.get_engine(meta_engine_name) }.to raise_error(Elastic::AppSearch::NonExistentRecord) 20 | client.create_meta_engine(meta_engine_name, source_engines) 21 | expect { client.get_engine(meta_engine_name) }.to_not raise_error 22 | end 23 | 24 | it 'should return a meta engine object' do 25 | engine = client.create_meta_engine(meta_engine_name, source_engines) 26 | expect(engine).to be_kind_of(Hash) 27 | expect(engine['name']).to eq(meta_engine_name) 28 | expect(engine['type']).to eq('meta') 29 | expect(engine['source_engines']).to eq(source_engines) 30 | end 31 | 32 | it 'should return an error when the engine source engine is empty' do 33 | expect { client.create_meta_engine(engine_name, []) }.to(raise_error) do |e| 34 | expect(e).to be_a(Elastic::AppSearch::BadRequest) 35 | expect(e.errors).to eq(['Source engines are required for meta engines']) 36 | end 37 | end 38 | end 39 | 40 | context '#add_meta_engine_sources' do 41 | before do 42 | client.create_meta_engine(meta_engine_name, source_engines) 43 | client.delete_meta_engine_sources(meta_engine_name, source_engines) 44 | end 45 | 46 | it 'should add the source engine' do 47 | expect { client.add_meta_engine_sources(meta_engine_name, source_engines) }.to_not raise_error do |engine| 48 | expect(engine).to be_kind_of(Hash) 49 | expect(engine['name']).to eq(meta_engine_name) 50 | expect(engine['type']).to eq('meta') 51 | expect(engine['source_engines']).to be_kind_of(Array) 52 | expect(engine['source_engines']).to eq(source_engines) 53 | end 54 | end 55 | end 56 | 57 | context '#delete_meta_engine_sources' do 58 | before do 59 | client.create_meta_engine(meta_engine_name, source_engines) 60 | end 61 | 62 | it 'should remove the source engine' do 63 | expect { client.delete_meta_engine_sources(meta_engine_name, source_engines) }.to_not raise_error do |engine| 64 | expect(engine).to be_kind_of(Hash) 65 | expect(engine['name']).to eq(meta_engine_name) 66 | expect(engine['type']).to eq('meta') 67 | expect(engine['source_engines']).to be_kind_of(Array) 68 | expect(engine['source_engines']).to be_empty 69 | end 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/search_spec.rb: -------------------------------------------------------------------------------- 1 | describe Elastic::AppSearch::Client::Search do 2 | include_context 'App Search Credentials' 3 | include_context 'Static Test Engine' 4 | 5 | let(:client) { Elastic::AppSearch::Client.new(client_options) } 6 | 7 | context 'Search' do 8 | describe '#search' do 9 | subject { client.search(engine_name, query, options) } 10 | let(:query) { '' } 11 | let(:options) { { 'page' => { 'size' => 2 } } } 12 | 13 | it 'should execute a search query' do 14 | expected = { 15 | 'meta' => anything, 16 | 'results' => [anything, anything] 17 | } 18 | expect(subject).to(match(expected)) 19 | end 20 | end 21 | 22 | describe '#multi_search' do 23 | subject { client.multi_search(engine_name, queries) } 24 | 25 | context 'when options are provided' do 26 | let(:queries) do 27 | [ 28 | { 'query' => 'gatsby', 'options' => { 'page' => { 'size' => 1 } } }, 29 | { 'query' => 'catcher', 'options' => { 'page' => { 'size' => 1 } } } 30 | ] 31 | end 32 | 33 | it 'should execute a multi search query' do 34 | response = subject 35 | expected = [ 36 | { 37 | 'meta' => anything, 38 | 'results' => [{ 'id' => { 'raw' => '1' }, 'title' => anything, '_meta' => anything }] 39 | }, 40 | { 41 | 'meta' => anything, 42 | 'results' => [{ 'id' => { 'raw' => '2' }, 'title' => anything, '_meta' => anything }] 43 | } 44 | ] 45 | expect(response).to(match(expected)) 46 | end 47 | end 48 | 49 | context 'when options are omitted' do 50 | let(:queries) do 51 | [ 52 | { 'query' => 'gatsby' }, 53 | { 'query' => 'catcher' } 54 | ] 55 | end 56 | 57 | it 'should execute a multi search query' do 58 | response = subject 59 | expected = [ 60 | { 61 | 'meta' => anything, 62 | 'results' => [{ 'id' => { 'raw' => '1' }, 'title' => anything, '_meta' => anything }] 63 | }, 64 | { 65 | 'meta' => anything, 66 | 'results' => [{ 'id' => { 'raw' => '2' }, 'title' => anything, '_meta' => anything }] 67 | } 68 | ] 69 | 70 | expect(response).to(match(expected)) 71 | end 72 | end 73 | 74 | context 'when a search is bad' do 75 | let(:queries) do 76 | [ 77 | { 78 | 'query' => 'cat', 79 | 'options' => { 'search_fields' => { 'taco' => {} } } 80 | }, { 81 | 'query' => 'dog', 82 | 'options' => { 'search_fields' => { 'body' => {} } } 83 | } 84 | ] 85 | end 86 | 87 | it 'should throw an appropriate error' do 88 | expect { subject }.to(raise_error) do |e| 89 | expect(e).to(be_a(Elastic::AppSearch::BadRequest)) 90 | expect(e.errors).to(eq(['Search fields contains invalid field: taco', 'Search fields contains invalid field: body'])) 91 | end 92 | end 93 | end 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'rspec' 3 | require 'webmock/rspec' 4 | require 'awesome_print' 5 | require 'elastic/app-search' 6 | require 'config_helper' 7 | require 'securerandom' 8 | 9 | WebMock.allow_net_connect! 10 | 11 | # 12 | # Uses a static engine that will not change between tests. It comes preloaded 13 | # with documents that *are already indexed*. This means tests can run operations 14 | # that require documents to be indexed, like "search". 15 | # 16 | # This is optimal for tests that perform read-only operations, like "search". 17 | # 18 | RSpec.shared_context 'Static Test Engine' do 19 | before(:all) do 20 | @static_engine_name = "ruby-client-test-static-#{SecureRandom.hex}" 21 | as_api_key = ConfigHelper.get_as_api_key 22 | as_host_identifier = ConfigHelper.get_as_host_identifier 23 | as_api_endpoint = ConfigHelper.get_as_api_endpoint 24 | client_options = ConfigHelper.get_client_options(as_api_key, as_host_identifier, as_api_endpoint) 25 | @static_client = Elastic::AppSearch::Client.new(client_options) 26 | @static_client.create_engine(@static_engine_name) 27 | 28 | @document1 = { 'id' => '1', 'title' => 'The Great Gatsby' } 29 | @document2 = { 'id' => '2', 'title' => 'Catcher in the Rye' } 30 | @documents = [@document1, @document2] 31 | @static_client.index_documents(@static_engine_name, @documents) 32 | 33 | # Wait until documents are indexed 34 | start = Time.now 35 | ready = false 36 | until (ready) 37 | sleep(3) 38 | results = @static_client.search(@static_engine_name, '') 39 | ready = true if results['results'].length == 2 40 | ready = true if (Time.now - start).to_i >= 120 # Time out after 2 minutes 41 | end 42 | end 43 | 44 | let(:engine_name) { @static_engine_name } 45 | let(:document1) { @document1 } 46 | let(:document2) { @document2 } 47 | 48 | after(:all) do 49 | @static_client.destroy_engine(@static_engine_name) 50 | end 51 | end 52 | 53 | RSpec.shared_context 'Engine Name' do 54 | let(:engine_name) { "ruby-client-test-#{SecureRandom.hex}" } 55 | end 56 | 57 | RSpec.shared_context 'Meta Engine Name' do 58 | let(:meta_engine_name) { "ruby-client-test-#{SecureRandom.hex}" } 59 | end 60 | 61 | RSpec.shared_context 'Test Engine' do 62 | let(:engine_name) { "ruby-client-test-#{SecureRandom.hex}" } 63 | 64 | before(:each) do 65 | client.create_engine(engine_name) rescue Elastic::AppSearch::BadRequest 66 | end 67 | 68 | after(:each) do 69 | client.destroy_engine(engine_name) rescue Elastic::AppSearch::NonExistentRecord 70 | end 71 | end 72 | 73 | RSpec.shared_context 'App Search Credentials' do 74 | let(:as_api_key) { ConfigHelper.get_as_api_key } 75 | # AS_ACCOUNT_HOST_KEY is deprecated 76 | let(:as_host_identifier) { ConfigHelper.get_as_host_identifier } 77 | let(:as_api_endpoint) { ConfigHelper.get_as_api_endpoint } 78 | let(:client_options) do 79 | ConfigHelper.get_client_options(as_api_key, as_host_identifier, as_api_endpoint) 80 | end 81 | end 82 | 83 | RSpec.shared_context 'App Search Admin Credentials' do 84 | let(:as_api_key) { ConfigHelper.get_as_admin_key } 85 | # AS_ACCOUNT_HOST_KEY is deprecated 86 | let(:as_host_identifier) { ConfigHelper.get_as_host_identifier } 87 | let(:as_api_endpoint) { ConfigHelper.get_as_api_endpoint } 88 | let(:client_options) do 89 | ConfigHelper.get_client_options(as_api_key, as_host_identifier, as_api_endpoint) 90 | end 91 | end 92 | 93 | RSpec.configure do |config| 94 | # Run specs in random order to surface order dependencies. If you find an 95 | # order dependency and want to debug it, you can fix the order by providing 96 | # the seed, which is printed after each run. 97 | # --seed 1234 98 | config.order = 'random' 99 | end 100 | -------------------------------------------------------------------------------- /lib/elastic/app-search/client.rb: -------------------------------------------------------------------------------- 1 | require 'set' 2 | require 'elastic/app-search/request' 3 | require 'elastic/app-search/utils' 4 | require 'jwt' 5 | 6 | module Elastic 7 | module AppSearch 8 | # API client for the {Elastic App Search API}[https://www.elastic.co/cloud/app-search-service]. 9 | class Client 10 | autoload :Analytics, 'elastic/app-search/client/analytics' 11 | autoload :Click, 'elastic/app-search/client/click' 12 | autoload :Credentials, 'elastic/app-search/client/credentials' 13 | autoload :Curations, 'elastic/app-search/client/curations' 14 | autoload :Documents, 'elastic/app-search/client/documents' 15 | autoload :Engines, 'elastic/app-search/client/engines' 16 | autoload :MetaEngines, 'elastic/app-search/client/meta_engines' 17 | autoload :Logs, 'elastic/app-search/client/logs' 18 | autoload :Schema, 'elastic/app-search/client/schema' 19 | autoload :Search, 'elastic/app-search/client/search' 20 | autoload :SearchSettings, 'elastic/app-search/client/search_settings' 21 | autoload :Synonyms, 'elastic/app-search/client/synonyms' 22 | autoload :QuerySuggestion, 'elastic/app-search/client/query_suggestion' 23 | 24 | DEFAULT_TIMEOUT = 15 25 | 26 | include Elastic::AppSearch::Request 27 | 28 | attr_reader :api_key, :open_timeout, :overall_timeout, :api_endpoint 29 | 30 | # Create a new Elastic::AppSearch::Client client 31 | # 32 | # @param options [Hash] a hash of configuration options that will override what is set on the Elastic::AppSearch class. 33 | # @option options [String] :account_host_key or :host_identifier is your Host Identifier to use with this client. 34 | # @option options [String] :api_key can be any of your API Keys. Each has a different scope, so ensure you are using the correct key. 35 | # @option options [Numeric] :overall_timeout overall timeout for requests in seconds (default: 15s) 36 | # @option options [Numeric] :open_timeout the number of seconds Net::HTTP (default: 15s) 37 | # will wait while opening a connection before raising a Timeout::Error 38 | def initialize(options = {}) 39 | @api_endpoint = options.fetch(:api_endpoint) { "https://#{options.fetch(:account_host_key) { options.fetch(:host_identifier) }}.api.swiftype.com/api/as/v1/" } 40 | @api_key = options.fetch(:api_key) 41 | @open_timeout = options.fetch(:open_timeout, DEFAULT_TIMEOUT).to_f 42 | @overall_timeout = options.fetch(:overall_timeout, DEFAULT_TIMEOUT).to_f 43 | end 44 | 45 | module SignedSearchOptions 46 | ALGORITHM = 'HS256'.freeze 47 | 48 | module ClassMethods 49 | # Build a JWT for authentication 50 | # 51 | # @param [String] api_key the API Key to sign the request with 52 | # @param [String] api_key_name the unique name for the API Key 53 | # @option options see the {App Search API}[https://swiftype.com/documentation/app-search/] for supported search options. 54 | # 55 | # @return [String] the JWT to use for authentication 56 | def create_signed_search_key(api_key, api_key_name, options = {}) 57 | payload = Utils.symbolize_keys(options).merge(:api_key_name => api_key_name) 58 | JWT.encode(payload, api_key, ALGORITHM) 59 | end 60 | end 61 | 62 | def self.included(base) 63 | base.extend(ClassMethods) 64 | end 65 | end 66 | 67 | include Elastic::AppSearch::Client::Analytics 68 | include Elastic::AppSearch::Client::Click 69 | include Elastic::AppSearch::Client::Credentials 70 | include Elastic::AppSearch::Client::Curations 71 | include Elastic::AppSearch::Client::Documents 72 | include Elastic::AppSearch::Client::Engines 73 | include Elastic::AppSearch::Client::MetaEngines 74 | include Elastic::AppSearch::Client::Logs 75 | include Elastic::AppSearch::Client::Schema 76 | include Elastic::AppSearch::Client::Search 77 | include Elastic::AppSearch::Client::SearchSettings 78 | include Elastic::AppSearch::Client::SignedSearchOptions 79 | include Elastic::AppSearch::Client::Synonyms 80 | include Elastic::AppSearch::Client::QuerySuggestion 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/elastic/app-search/client/documents.rb: -------------------------------------------------------------------------------- 1 | # Documents have fields that can be searched or filtered. 2 | # 3 | # For more information on indexing documents, see the {App Search documentation}[https://swiftype.com/documentation/app-search/]. 4 | module Elastic 5 | module AppSearch 6 | class Client 7 | module Documents 8 | 9 | # Retrieve all Documents from the API for the {App Search API}[https://swiftype.com/documentation/app-search/] 10 | # 11 | # @param [String] engine_name the unique Engine name 12 | # @option options see the {App Search API}[https://swiftype.com/documentation/app-search/] for supported options. 13 | # 14 | # @return [Array] an Array of Documents 15 | def list_documents(engine_name, options = {}) 16 | params = Utils.symbolize_keys(options) 17 | request(:get, "engines/#{engine_name}/documents/list", params) 18 | end 19 | 20 | # Retrieve Documents from the API by IDs for the {App Search API}[https://swiftype.com/documentation/app-search/] 21 | # 22 | # @param [String] engine_name the unique Engine name 23 | # @param [Array] ids an Array of Document IDs 24 | # 25 | # @return [Hash] list results 26 | def get_documents(engine_name, ids) 27 | get("engines/#{engine_name}/documents", ids) 28 | end 29 | 30 | # Index a document using the {App Search API}[https://swiftype.com/documentation/app-search/]. 31 | # 32 | # @param [String] engine_name the unique Engine name 33 | # @param [Array] document a Document Hash 34 | # 35 | # @return [Hash] processed Document Status hash 36 | # 37 | # @raise [Elastic::AppSearch::InvalidDocument] when the document has processing errors returned from the api 38 | # @raise [Timeout::Error] when timeout expires waiting for statuses 39 | def index_document(engine_name, document) 40 | response = index_documents(engine_name, [document]) 41 | errors = response.first['errors'] 42 | raise InvalidDocument.new(errors.join('; ')) if errors.any? 43 | response.first.tap { |h| h.delete('errors') } 44 | end 45 | 46 | # Index a batch of documents using the {App Search API}[https://swiftype.com/documentation/app-search/]. 47 | # 48 | # @param [String] engine_name the unique Engine name 49 | # @param [Array] documents an Array of Document Hashes 50 | # 51 | # @return [Array] an Array of processed Document Status hashes 52 | # 53 | # @raise [Elastic::AppSearch::InvalidDocument] when any documents have processing errors returned from the api 54 | # @raise [Timeout::Error] when timeout expires waiting for statuses 55 | def index_documents(engine_name, documents) 56 | documents.map! { |document| normalize_document(document) } 57 | post("engines/#{engine_name}/documents", documents) 58 | end 59 | 60 | # Update a batch of documents using the {App Search API}[https://swiftype.com/documentation/app-search/]. 61 | # 62 | # @param [String] engine_name the unique Engine name 63 | # @param [Array] documents an Array of Document Hashes including valid ids 64 | # 65 | # @return [Array] an Array of processed Document Status hashes 66 | # 67 | # @raise [Elastic::AppSearch::InvalidDocument] when any documents have processing errors returned from the api 68 | # @raise [Timeout::Error] when timeout expires waiting for statuses 69 | def update_documents(engine_name, documents) 70 | documents.map! { |document| normalize_document(document) } 71 | patch("engines/#{engine_name}/documents", documents) 72 | end 73 | 74 | # Destroy a batch of documents given a list of IDs 75 | # 76 | # @param [Array] ids an Array of Document IDs 77 | # 78 | # @return [Array] an Array of Document destroy result hashes 79 | def destroy_documents(engine_name, ids) 80 | delete("engines/#{engine_name}/documents", ids) 81 | end 82 | 83 | private 84 | 85 | def normalize_document(document) 86 | Utils.stringify_keys(document) 87 | end 88 | end 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/elastic/app-search/request.rb: -------------------------------------------------------------------------------- 1 | require 'net/https' 2 | require 'json' 3 | require 'time' 4 | require 'elastic/app-search/exceptions' 5 | require 'elastic/app-search/version' 6 | require 'openssl' 7 | 8 | module Elastic 9 | module AppSearch 10 | CLIENT_NAME = 'elastic-app-search-ruby' 11 | CLIENT_VERSION = Elastic::AppSearch::VERSION 12 | 13 | module Request 14 | attr_accessor :last_request 15 | 16 | def get(path, params={}) 17 | request(:get, path, params) 18 | end 19 | 20 | def post(path, params={}) 21 | request(:post, path, params) 22 | end 23 | 24 | def put(path, params={}) 25 | request(:put, path, params) 26 | end 27 | 28 | def patch(path, params={}) 29 | request(:patch, path, params) 30 | end 31 | 32 | def delete(path, params={}) 33 | request(:delete, path, params) 34 | end 35 | 36 | # Construct and send a request to the API. 37 | # 38 | # @raise [Timeout::Error] when the timeout expires 39 | def request(method, path, params = {}) 40 | Timeout.timeout(overall_timeout) do 41 | uri = URI.parse("#{api_endpoint}#{path}") 42 | 43 | request = build_request(method, uri, params) 44 | http = Net::HTTP.new(uri.host, uri.port) 45 | http.open_timeout = open_timeout 46 | http.read_timeout = overall_timeout 47 | 48 | http.set_debug_output(STDERR) if debug? 49 | 50 | if uri.scheme == 'https' 51 | http.use_ssl = true 52 | # st_ssl_verify_none provides a means to disable SSL verification for debugging purposes. An example 53 | # is Charles, which uses a self-signed certificate in order to inspect https traffic. This will 54 | # not be part of this client's public API, this is more of a development enablement option 55 | http.verify_mode = ENV['st_ssl_verify_none'] == 'true' ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER 56 | http.ca_file = File.join(File.dirname(__FILE__), '../..', 'data', 'ca-bundle.crt') 57 | http.ssl_timeout = open_timeout 58 | end 59 | 60 | @last_request = request 61 | 62 | response = http.request(request) 63 | response_json = parse_response(response) 64 | 65 | case response 66 | when Net::HTTPSuccess 67 | return response_json 68 | when Net::HTTPBadRequest 69 | raise Elastic::AppSearch::BadRequest, response_json 70 | when Net::HTTPUnauthorized 71 | raise Elastic::AppSearch::InvalidCredentials, response_json 72 | when Net::HTTPNotFound 73 | raise Elastic::AppSearch::NonExistentRecord, response_json 74 | when Net::HTTPForbidden 75 | raise Elastic::AppSearch::Forbidden, response_json 76 | when Net::HTTPRequestEntityTooLarge 77 | raise Elastic::AppSearch::RequestEntityTooLarge, response_json 78 | else 79 | raise Elastic::AppSearch::UnexpectedHTTPException.new(response, response_json) 80 | end 81 | end 82 | end 83 | 84 | private 85 | 86 | def parse_response(response) 87 | body = response.body.to_s.strip 88 | body == '' ? {} : JSON.parse(body) 89 | end 90 | 91 | def debug? 92 | @debug ||= (ENV['AS_DEBUG'] == 'true') 93 | end 94 | 95 | def serialize_json(object) 96 | JSON.generate(clean_json(object)) 97 | end 98 | 99 | def clean_json(object) 100 | case object 101 | when Hash 102 | object.inject({}) do |builder, (key, value)| 103 | builder[key] = clean_json(value) 104 | builder 105 | end 106 | when Enumerable 107 | object.map { |value| clean_json(value) } 108 | else 109 | clean_atom(object) 110 | end 111 | end 112 | 113 | def clean_atom(atom) 114 | if atom.is_a?(Time) 115 | atom.to_datetime 116 | else 117 | atom 118 | end 119 | end 120 | 121 | def build_request(method, uri, params) 122 | klass = case method 123 | when :get 124 | Net::HTTP::Get 125 | when :post 126 | Net::HTTP::Post 127 | when :put 128 | Net::HTTP::Put 129 | when :patch 130 | Net::HTTP::Patch 131 | when :delete 132 | Net::HTTP::Delete 133 | end 134 | 135 | req = klass.new(uri.request_uri) 136 | req.body = serialize_json(params) unless params.length == 0 137 | 138 | req['X-Swiftype-Client'] = CLIENT_NAME 139 | req['X-Swiftype-Client-Version'] = CLIENT_VERSION 140 | req['Content-Type'] = 'application/json' 141 | req['Authorization'] = "Bearer #{api_key}" 142 | 143 | req 144 | end 145 | end 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /spec/documents_spec.rb: -------------------------------------------------------------------------------- 1 | describe Elastic::AppSearch::Client::Documents do 2 | include_context 'App Search Credentials' 3 | include_context 'Test Engine' 4 | 5 | let(:client) { Elastic::AppSearch::Client.new(client_options) } 6 | 7 | context 'Documents' do 8 | let(:document) { { 'url' => 'http://www.youtube.com/watch?v=v1uyQZNg2vE' } } 9 | 10 | describe '#index_document' do 11 | subject { client.index_document(engine_name, document) } 12 | 13 | it 'should return a processed document status hash' do 14 | expect(subject).to(match('id' => anything)) 15 | end 16 | 17 | context 'when the document has an id' do 18 | let(:id) { 'some_id' } 19 | let(:document) { { 'id' => id, 'url' => 'http://www.youtube.com/watch?v=v1uyQZNg2vE' } } 20 | 21 | it 'should return a processed document status hash with the same id' do 22 | expect(subject).to(eq('id' => id)) 23 | end 24 | end 25 | 26 | context 'when a document has processing errors' do 27 | let(:document) { { 'id' => 'too long' * 100 } } 28 | 29 | it 'should raise an error when the API returns errors in the response' do 30 | expect do 31 | subject 32 | end.to(raise_error(Elastic::AppSearch::InvalidDocument, /Invalid field/)) 33 | end 34 | end 35 | 36 | context 'when a document has a Ruby Time object' do 37 | let(:time_rfc3339) { '2018-01-01T01:01:01+00:00' } 38 | let(:time_object) { Time.parse(time_rfc3339) } 39 | let(:document) { { 'created_at' => time_object } } 40 | 41 | it 'should serialize the time object in RFC 3339' do 42 | response = subject 43 | expect(response).to(have_key('id')) 44 | document_id = response.fetch('id') 45 | expect do 46 | documents = client.get_documents(engine_name, [document_id]) 47 | expect(documents.size).to(eq(1)) 48 | expect(documents.first['created_at']).to(eq(time_rfc3339)) 49 | end.to_not(raise_error) 50 | end 51 | end 52 | end 53 | 54 | describe '#index_documents' do 55 | let(:documents) { [document, second_document] } 56 | let(:second_document_id) { 'another_id' } 57 | let(:second_document) { { 'id' => second_document_id, 'url' => 'https://www.youtube.com/watch?v=9T1vfsHYiKY' } } 58 | subject { client.index_documents(engine_name, documents) } 59 | 60 | it 'should return an array of document status hashes' do 61 | expected = [ 62 | { 'id' => anything, 'errors' => [] }, 63 | { 'id' => second_document_id, 'errors' => [] } 64 | ] 65 | expect(subject).to(match(expected)) 66 | end 67 | 68 | context 'when one of the documents has processing errors' do 69 | let(:second_document) { { 'id' => 'too long' * 100 } } 70 | 71 | it 'should return respective errors in an array of document processing hashes' do 72 | expected = [ 73 | { 'id' => anything, 'errors' => [] }, 74 | { 'id' => anything, 'errors' => [anything] }, 75 | ] 76 | expect(subject).to(match(expected)) 77 | end 78 | end 79 | end 80 | 81 | describe '#update_documents' do 82 | let(:documents) { [document, second_document] } 83 | let(:second_document_id) { 'another_id' } 84 | let(:second_document) { { 'id' => second_document_id, 'url' => 'https://www.youtube.com/watch?v=9T1vfsHYiKY' } } 85 | let(:updates) do 86 | [ 87 | { 88 | 'id' => second_document_id, 89 | 'url' => 'https://www.example.com' 90 | } 91 | ] 92 | end 93 | 94 | subject { client.update_documents(engine_name, updates) } 95 | 96 | before do 97 | client.index_documents(engine_name, documents) 98 | end 99 | 100 | # Note that since indexing a document takes up to a minute, 101 | # we don't expect this to succeed, so we simply verify that 102 | # the request responded with the correct 'id', even though 103 | # the 'errors' object likely contains errors. 104 | it 'should update existing documents' do 105 | expect(subject).to(match(['id' => second_document_id, 'errors' => anything])) 106 | end 107 | end 108 | 109 | describe '#get_documents' do 110 | let(:documents) { [first_document, second_document] } 111 | let(:first_document_id) { 'id' } 112 | let(:first_document) { { 'id' => first_document_id, 'url' => 'https://www.youtube.com/watch?v=v1uyQZNg2vE' } } 113 | let(:second_document_id) { 'another_id' } 114 | let(:second_document) { { 'id' => second_document_id, 'url' => 'https://www.youtube.com/watch?v=9T1vfsHYiKY' } } 115 | 116 | subject { client.get_documents(engine_name, [first_document_id, second_document_id]) } 117 | 118 | before do 119 | client.index_documents(engine_name, documents) 120 | end 121 | 122 | it 'will return documents by id' do 123 | response = subject 124 | expect(response.size).to(eq(2)) 125 | expect(response[0]['id']).to(eq(first_document_id)) 126 | expect(response[1]['id']).to(eq(second_document_id)) 127 | end 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 Elasticsearch B.V. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > **⚠️ This client is deprecated ⚠️** 2 | > 3 | > As of Enterprise Search version 7.10.0, we are directing users to the new [Enterprise Search Ruby Client](https://github.com/elastic/enterprise-search-ruby) and 4 | > deprecating this client. 5 | > 6 | > This client will be compatible with all Enterprise Search 7.x releases, but will not be compatible with 8.x releases. Our development effort on this project will 7 | > be limited to bug fixes. All future enhancements will be focused on the Enterprise Search Ruby Client. 8 | > 9 | > Thank you! - Elastic 10 | 11 |

Elastic App Search Logo

12 | 13 |

CircleCI build

14 | 15 | > A first-party Ruby client for building excellent, relevant search experiences with [Elastic App Search](https://www.elastic.co/products/app-search). 16 | 17 | ## Contents 18 | 19 | - [Getting started](#getting-started-) 20 | - [Versioning](#versioning) 21 | - [Usage](#usage) 22 | - [Running Tests](#running-tests) 23 | - [Debugging API Calls](#debugging-api-calls) 24 | - [FAQ](#faq-) 25 | - [Contribute](#contribute-) 26 | - [License](#license-) 27 | 28 | --- 29 | 30 | ## Getting started 🐣 31 | 32 | To install the gem, execute: 33 | 34 | ```bash 35 | gem install elastic-app-search 36 | ``` 37 | 38 | Or place `gem 'elastic-app-search', '~> 7.10.0'` in your `Gemfile` and run `bundle install`. 39 | 40 | ## Versioning 41 | 42 | This client is versioned and released alongside App Search. 43 | 44 | To guarantee compatibility, use the most recent version of this library within the major version of the corresponding App Search implementation. 45 | 46 | For example, for App Search `7.3`, use `7.3` of this library or above, but not `8.0`. 47 | 48 | If you are using the [SaaS version available on swiftype.com](https://app.swiftype.com/as) of App Search, you should use the version 7.5.x of the client. 49 | 50 | ## Usage 51 | 52 | #### Setup: Configuring the client and authentication 53 | 54 | Using this client assumes that you have already an instance of [Elastic App Search](https://www.elastic.co/products/app-search) up and running. 55 | 56 | Once done, a client can be instantiated using the `[API_KEY]` and the `[API_ENDPOINT]` URL of your App Search setup: 57 | 58 | ```ruby 59 | require 'elastic-app-search' 60 | 61 | client = Elastic::AppSearch::Client.new(:api_key => 'private-xxxxxxxxxxxxxxxxxxx', :api_endpoint => 'http://localhost:3002/api/as/v1/') 62 | ``` 63 | 64 | Note: 65 | 66 | The `[API_KEY]` authenticates requests to the API. 67 | You can use any key type with the client, however each has a different scope. 68 | For more information on keys, check out the [documentation](https://swiftype.com/documentation/app-search/api/credentials). 69 | 70 | ##### Swiftype.com App Search users: 71 | 72 | When using the [SaaS version available on swiftype.com](https://app.swiftype.com/as) of App Search, you can configure the client using your `[HOST_IDENTIFIER]` instead of the `[API_ENDPOINT]`. 73 | The `[HOST_IDENTIFIER]` can be found within the [Credentials](https://app.swiftype.com/as#/credentials) menu. 74 | 75 | ```ruby 76 | require 'elastic-app-search' 77 | 78 | client = Elastic::AppSearch::Client.new(:host_identifier => 'host-c5s2mj', :api_key => 'private-xxxxxxxxxxxxxxxxxxx') 79 | ``` 80 | 81 | ### API Methods 82 | 83 | This client is a thin interface to the Elastic App Search Api. Additional details for requests and responses can be 84 | found in the [documentation](https://swiftype.com/documentation/app-search). 85 | 86 | #### Indexing: Creating or Updating a Single Document 87 | 88 | ```ruby 89 | engine_name = 'favorite-videos' 90 | document = { 91 | :id => 'INscMGmhmX4', 92 | :url => 'https://www.youtube.com/watch?v=INscMGmhmX4', 93 | :title => 'The Original Grumpy Cat', 94 | :body => 'A wonderful video of a magnificent cat.' 95 | } 96 | 97 | client.index_document(engine_name, document) 98 | ``` 99 | 100 | #### Indexing: Creating or Replacing Documents 101 | 102 | ```ruby 103 | engine_name = 'favorite-videos' 104 | documents = [ 105 | { 106 | :id => 'INscMGmhmX4', 107 | :url => 'https://www.youtube.com/watch?v=INscMGmhmX4', 108 | :title => 'The Original Grumpy Cat', 109 | :body => 'A wonderful video of a magnificent cat.' 110 | }, 111 | { 112 | :id => 'JNDFojsd02', 113 | :url => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', 114 | :title => 'Another Grumpy Cat', 115 | :body => 'A great video of another cool cat.' 116 | } 117 | ] 118 | 119 | client.index_documents(engine_name, documents) 120 | ``` 121 | 122 | #### Indexing: Updating Documents (Partial Updates) 123 | 124 | ```ruby 125 | engine_name = 'favorite-videos' 126 | documents = [ 127 | { 128 | :id => 'INscMGmhmX4', 129 | :title => 'Updated title' 130 | } 131 | ] 132 | 133 | client.update_documents(engine_name, documents) 134 | ``` 135 | 136 | #### Retrieving Documents 137 | 138 | ```ruby 139 | engine_name = 'favorite-videos' 140 | document_ids = ['INscMGmhmX4', 'JNDFojsd02'] 141 | 142 | client.get_documents(engine_name, document_ids) 143 | ``` 144 | 145 | #### Listing Documents 146 | 147 | ```ruby 148 | engine_name = 'favorite-videos' 149 | 150 | client.list_documents(engine_name) 151 | ``` 152 | 153 | #### Destroying Documents 154 | 155 | ```ruby 156 | engine_name = 'favorite-videos' 157 | document_ids = ['INscMGmhmX4', 'JNDFojsd02'] 158 | 159 | client.destroy_documents(engine_name, document_ids) 160 | ``` 161 | 162 | #### Listing Engines 163 | 164 | ```ruby 165 | client.list_engines 166 | ``` 167 | 168 | #### Retrieving Engines 169 | 170 | ```ruby 171 | engine_name = 'favorite-videos' 172 | 173 | client.get_engine(engine_name) 174 | ``` 175 | 176 | #### Creating Engines 177 | 178 | ```ruby 179 | engine_name = 'favorite-videos' 180 | 181 | client.create_engine(engine_name) 182 | ``` 183 | 184 | #### Destroying Engines 185 | 186 | ```ruby 187 | engine_name = 'favorite-videos' 188 | 189 | client.destroy_engine(engine_name) 190 | ``` 191 | 192 | #### Creating Meta Engines 193 | 194 | ```ruby 195 | engine_name = 'videos-engine' 196 | sources_engines = ['favorite-videos', 'all-videos'] 197 | 198 | client.create_meta_engine(engine_name, source_engines) 199 | ``` 200 | 201 | #### Adding Meta Engines Source 202 | 203 | ```ruby 204 | engine_name = 'videos-engine' 205 | sources_engines = ['fun-videos', 'cat-videos'] 206 | 207 | client.add_meta_engine_sources(engine_name, source_engines) 208 | ``` 209 | 210 | #### Adding Meta Engines Source 211 | 212 | ```ruby 213 | engine_name = 'videos-engine' 214 | sources_engines = ['nsfw-videos'] 215 | 216 | client.delete_meta_engine_sources(engine_name, source_engines) 217 | ``` 218 | 219 | #### Searching 220 | 221 | ```ruby 222 | engine_name = 'favorite-videos' 223 | query = 'cat' 224 | search_fields = { :title => {} } 225 | result_fields = { :title => { :raw => {} } } 226 | options = { :search_fields => search_fields, :result_fields => result_fields } 227 | 228 | client.search(engine_name, query, options) 229 | ``` 230 | 231 | #### Multi-Search 232 | 233 | ```ruby 234 | engine_name = 'favorite-videos' 235 | queries = [{ 236 | :query => 'cat', 237 | :options => { :search_fields => { :title => {} }} 238 | },{ 239 | :query => 'dog', 240 | :options => { :search_fields => { :body => {} }} 241 | }] 242 | 243 | client.multi_search(engine_name, queries) 244 | ``` 245 | 246 | #### Query Suggestion 247 | 248 | ```ruby 249 | engine_name = 'favorite-videos' 250 | options = { 251 | :size => 3, 252 | :types => { 253 | :documents => { 254 | :fields => ['title'] 255 | } 256 | } 257 | } 258 | 259 | client.query_suggestion(engine_name, 'cat', options) 260 | ``` 261 | 262 | #### Show Search Settings 263 | 264 | ```ruby 265 | engine_name = 'favorite-videos' 266 | 267 | client.show_settings(engine_name) 268 | ``` 269 | 270 | #### Update Search Settings 271 | 272 | ```ruby 273 | engine_name = 'favorite-videos' 274 | settings = { 275 | 'search_fields' => { 276 | 'id' => { 277 | 'weight' => 1 278 | }, 279 | 'url' => { 280 | 'weight' => 1 281 | }, 282 | 'title' => { 283 | 'weight' => 1 284 | }, 285 | 'body' => { 286 | 'weight' => 1 287 | }, 288 | }, 289 | 'boosts' => { 290 | 'title' => [ 291 | { 292 | 'type' => 'value', 293 | 'factor' => 9.5, 294 | 'operation' => 'multiply', 295 | 'value' => [ 296 | 'Titanic' 297 | ] 298 | } 299 | ] 300 | } 301 | } 302 | 303 | client.update_settings(engine_name, settings) 304 | ``` 305 | 306 | #### Reset Search Settings 307 | 308 | ```ruby 309 | engine_name = 'favorite-videos' 310 | 311 | client.reset_settings(engine_name) 312 | ``` 313 | 314 | #### Create a Signed Search Key 315 | 316 | Creating a search key that will only return the title field. 317 | 318 | ```ruby 319 | public_search_key = 'search-xxxxxxxxxxxxxxxxxxxxxxxx' 320 | # This name must match the name of the key above from your App Search dashboard 321 | public_search_key_name = 'search-key' 322 | enforced_options = { 323 | result_fields: { title: { raw: {} } }, 324 | filters: { world_heritage_site: 'true' } 325 | } 326 | 327 | signed_search_key = Elastic::AppSearch::Client.create_signed_search_key(public_search_key, public_search_key_name, enforced_options) 328 | 329 | client = Elastic::AppSearch::Client.new(:api_key => signed_search_key, :api_endpoint => 'http://localhost:3002/api/as/v1/') 330 | 331 | client.search('national-parks-demo', 'everglade') 332 | ``` 333 | 334 | #### Log click-through 335 | 336 | Logging a click through 337 | 338 | ```ruby 339 | engine_name = 'favorite-videos' 340 | options = { 341 | :query => 'cat videos', 342 | :document_id => 'INscMGmhmX4', 343 | :request_id => 'e4c4dea0bd7ad3d2f676575ef16dc7d2', 344 | :tags => ['firefox', 'web browser'] 345 | } 346 | 347 | client.log_click_through(engine_name, options) 348 | ``` 349 | 350 | #### Analytics - Number of clicks-throughs received by a document 351 | 352 | ```ruby 353 | engine_name = 'favorite-videos' 354 | options = { 355 | :query => 'cats', 356 | :page => { 357 | :size => 20, 358 | }, 359 | :filters => { 360 | :date => { 361 | :from => '2019-04-11T00:00:00+00:00', 362 | :to => '2019-04-13T00:00:00+00:00' 363 | } 364 | } 365 | } 366 | 367 | client.get_top_clicks_analytics(engine_name, options) 368 | ``` 369 | 370 | #### Analytics - Queries, number of queries, and clicks received 371 | 372 | ```ruby 373 | engine_name = 'favorite-videos' 374 | options = { 375 | :page => { 376 | :size => 20 377 | }, 378 | :filters => { 379 | :date => { 380 | :from => '2019-04-11T00:00:00+00:00', 381 | :to => '2019-04-13T00:00:00+00:00' 382 | } 383 | } 384 | } 385 | 386 | client.get_top_queries_analytics(engine_name, options) 387 | ``` 388 | 389 | #### Analytics - Number of clicks and total number of queries 390 | 391 | ```ruby 392 | engine_name = 'favorite-videos' 393 | options = { 394 | :filters => { 395 | :all => [ 396 | { 397 | :tag => ['mobile', 'web'] 398 | },{ 399 | :query => 'cats' 400 | }, { 401 | :document_id => '163' 402 | }, { 403 | :date => { 404 | :from => '2018-07-05T12:00:00+00:00', 405 | :to => '2018-07-05T14:00:00+00:00' 406 | } 407 | } 408 | ] 409 | }, 410 | :interval => 'hour' 411 | } 412 | 413 | client.get_count_analytics(engine_name, options) 414 | ``` 415 | 416 | #### Creating Synonym Sets 417 | 418 | ```ruby 419 | engine_name = 'us-national-parks' 420 | 421 | client.create_synonym_set(engine_name, :synonyms => ['park', 'trail']) 422 | ``` 423 | 424 | #### Retrieving Synonym Sets 425 | 426 | ```ruby 427 | engine_name = 'us-national-parks' 428 | 429 | client.get_synonym_set(engine_name, 'syn-5d8e6b5d40caae7dcb6e1b9c') 430 | ``` 431 | 432 | #### Listing Synonym Sets 433 | 434 | ```ruby 435 | engine_name = 'us-national-parks' 436 | 437 | client.list_synonym_sets(engine_name, :current => 1, :size => 20) 438 | ``` 439 | 440 | #### Updating Synonym Sets 441 | 442 | ```ruby 443 | engine_name = 'us-national-parks' 444 | 445 | client.update_synonym_set(engine_name, 'syn-5d8e6b5d40caae7dcb6e1b9c', :synonyms => ['park', 'trail', 'ground']) 446 | ``` 447 | 448 | #### Destroying Synonym Sets 449 | 450 | ```ruby 451 | engine_name = 'us-national-parks' 452 | 453 | client.destroy_synonym_set(engine_name, 'syn-5d8e6b5d40caae7dcb6e1b9c') 454 | ``` 455 | 456 | #### Listing Credentials 457 | 458 | ```ruby 459 | client.list_credentials(:current => 1, :size => 20) 460 | ``` 461 | 462 | #### Retrieving Credentials 463 | 464 | ```ruby 465 | client.get_credential('reading-private-key') 466 | ``` 467 | 468 | #### Creating Credentials 469 | 470 | ```ruby 471 | client.create_credential({ 472 | :name => 'reading-private-key', 473 | :type => 'private', 474 | :read => true, 475 | :write => false, 476 | :access_all_engines => false, 477 | :engines => [ 478 | 'favorite-videos' 479 | ] 480 | }) 481 | ``` 482 | 483 | #### Updating Credentials 484 | 485 | ```ruby 486 | client.update_credential('reading-private-key', { 487 | :name => 'reading-private-key', 488 | :type => 'private', 489 | :read => true, 490 | :write => true, 491 | :access_all_engines => false, 492 | :engines => [ 493 | 'favorite-videos' 494 | ] 495 | }) 496 | ``` 497 | 498 | #### Destroying Credentials 499 | 500 | ```ruby 501 | client.destroy_credential('reading-private-key') 502 | ``` 503 | 504 | #### Retrieving an Engine's Schema 505 | 506 | ```ruby 507 | engine_name = 'us-national-parks' 508 | 509 | client.get_schema(engine_name) 510 | ``` 511 | 512 | #### Updating an Engine's Schema or Creating a New Schema Field 513 | 514 | ```ruby 515 | engine_name = 'us-national-parks' 516 | 517 | client.update_schema(engine_name, 'square_km' => 'number') 518 | ``` 519 | 520 | #### Creating Curations 521 | 522 | ```ruby 523 | engine_name = 'us-national-parks' 524 | options = { 525 | 'queries' => [ 526 | 'zion' 527 | ], 528 | 'promoted' => [ 529 | 'doc-5d8e413b40caaedab76e3c96' 530 | ], 531 | 'hidden' => [ 532 | 'doc-5d8e413d40caae335e06c374' 533 | ] 534 | } 535 | 536 | client.create_curation(engine_name, options) 537 | ``` 538 | 539 | #### Retrieving Curations 540 | 541 | ```ruby 542 | engine_name = 'us-national-parks' 543 | 544 | client.get_curation(engine_name, 'cur-5d9240d640caaeca6506b600') 545 | ``` 546 | 547 | #### Listing Curations 548 | 549 | ```ruby 550 | engine_name = 'us-national-parks' 551 | 552 | client.list_curations(engine_name, current: 1, size: 20) 553 | ``` 554 | 555 | #### Updating Curations 556 | 557 | ```ruby 558 | engine_name = 'us-national-parks' 559 | id = 'cur-5d9240d640caaeca6506b600' 560 | options = { 561 | 'queries' => [ 562 | 'zion' 563 | ], 564 | 'promoted' => [ 565 | 'doc-5d8e413b40caaedab76e3c96' 566 | ] 567 | } 568 | 569 | client.update_curation(engine_name, id, options) 570 | ``` 571 | 572 | #### Destroying Curations 573 | 574 | ```ruby 575 | engine_name = 'us-national-parks' 576 | 577 | client.destroy_curation(engine_name, 'cur-5d9240d640caaeca6506b600') 578 | ``` 579 | 580 | #### Retrieving API Logs 581 | 582 | ```ruby 583 | engine_name = 'us-national-parks' 584 | options = { 585 | 'filters' => { 586 | 'date' => { 587 | 'from' => '2019-09-23T00:00:00+00:00', 588 | 'to' => '2019-09-28T00:00:00+00:00' 589 | } 590 | }, 591 | 'page' => { 592 | 'total_results' => 100, 593 | 'size' => 20 594 | }, 595 | 'query' => 'search', 596 | 'sort_direction' => 'desc' 597 | } 598 | 599 | client.get_api_logs(engine_name, options) 600 | ``` 601 | 602 | ## Running Tests 603 | 604 | ```bash 605 | export AS_API_KEY="[API_KEY]" 606 | export AS_ADMIN_KEY="[ADMIN_API_KEY]" 607 | export AS_HOST_IDENTIFIER="[HOST_IDENTIFIER]" 608 | bundle exec rspec 609 | ``` 610 | 611 | You can also run tests against a local environment by passing a `AS_API_ENDPOINT` environment variable 612 | 613 | ```bash 614 | export AS_API_KEY="[API_KEY]" 615 | export AS_ADMIN_KEY="[ADMIN_API_KEY]" 616 | export AS_API_ENDPOINT="http://[HOST_IDENTIFIER].api.127.0.0.1.ip.es.io:3002/api/as/v1" 617 | bundle exec rspec 618 | ``` 619 | 620 | ## Debugging API calls 621 | 622 | If you need to debug an API call made by the client, there are a few things you could do: 623 | 624 | 1. Setting `AS_DEBUG` environment variable to `true` would enable HTTP-level debugging and you would 625 | see all requests generated by the client on your console. 626 | 627 | 2. You could use our API logs feature in App Search console to see your requests and responses live. 628 | 629 | 3. In your debug logs you could find a `X-Request-Id` header value. That could be used when talking 630 | to Elastic Customer Support to help us quickly find your API request and help you troubleshoot 631 | your issues. 632 | 633 | ## FAQ 🔮 634 | 635 | ### Where do I report issues with the client? 636 | 637 | If something is not working as expected, please open an [issue](https://github.com/elastic/app-search-ruby/issues/new). 638 | 639 | ### Where can I learn more about App Search? 640 | 641 | Your best bet is to read the [documentation](https://swiftype.com/documentation/app-search). 642 | 643 | ### Where else can I go to get help? 644 | 645 | You can checkout the [Elastic App Search community discuss forums](https://discuss.elastic.co/c/app-search). 646 | 647 | ## Contribute 🚀 648 | 649 | We welcome contributors to the project. Before you begin, a couple notes... 650 | 651 | - Before opening a pull request, please create an issue to [discuss the scope of your proposal](https://github.com/elastic/app-search-ruby/issues). 652 | - Please write simple code and concise documentation, when appropriate. 653 | 654 | ## License 📗 655 | 656 | [Apache 2.0](https://github.com/elastic/app-search-ruby/blob/master/LICENSE.txt) © [Elastic](https://github.com/elastic) 657 | 658 | Thank you to all the [contributors](https://github.com/elastic/app-search-ruby/graphs/contributors)! 659 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | DisabledByDefault: true 3 | 4 | ######################### Layout ########################### 5 | 6 | Layout/AccessModifierIndentation: 7 | Description: Check indentation of private/protected visibility modifiers. 8 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-public-private-protected' 9 | Enabled: false 10 | 11 | Layout/AlignArray: 12 | Description: >- 13 | Align the elements of an array literal if they span more than 14 | one line. 15 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#align-multiline-arrays' 16 | Enabled: true 17 | 18 | Layout/AlignHash: 19 | Description: >- 20 | Align the elements of a hash literal if they span more than 21 | one line. 22 | Enabled: true 23 | 24 | Layout/AlignParameters: 25 | Description: >- 26 | Align the parameters of a method call if they span more 27 | than one line. 28 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent' 29 | Enabled: true 30 | 31 | Layout/BlockEndNewline: 32 | Description: 'Put end statement of multiline block on its own line.' 33 | Enabled: true 34 | 35 | Layout/CaseIndentation: 36 | Description: 'Indentation of when in a case/when/[else/]end.' 37 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-when-to-case' 38 | EnforcedStyle: end 39 | Enabled: true 40 | 41 | Layout/ClosingParenthesisIndentation: 42 | Description: 'Checks the indentation of hanging closing parentheses.' 43 | Enabled: true 44 | 45 | Layout/CommentIndentation: 46 | Description: 'Indentation of comments.' 47 | Enabled: true 48 | 49 | Layout/DotPosition: 50 | Description: 'Checks the position of the dot in multi-line method calls.' 51 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains' 52 | EnforcedStyle: leading 53 | Enabled: true 54 | 55 | Layout/ElseAlignment: 56 | Description: 'Align elses and elsifs correctly.' 57 | Enabled: true 58 | 59 | Layout/EmptyLineBetweenDefs: 60 | Description: 'Use empty lines between defs.' 61 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#empty-lines-between-methods' 62 | Enabled: true 63 | 64 | Layout/EmptyLines: 65 | Description: 'Do not use several empty lines in a row.' 66 | Enabled: true 67 | 68 | Layout/EmptyLinesAroundAccessModifier: 69 | Description: 'Keep blank lines around access modifiers.' 70 | Enabled: true 71 | 72 | Layout/EmptyLinesAroundArguments: 73 | Description: 'Checks if empty lines exist around the arguments of a method invocation.' 74 | Enabled: true 75 | 76 | Layout/EmptyLinesAroundBlockBody: 77 | Description: 'Keeps track of empty lines around block bodies.' 78 | EnforcedStyle: no_empty_lines 79 | Enabled: true 80 | 81 | Layout/EmptyLinesAroundClassBody: 82 | Description: 'Keeps track of empty lines around class bodies.' 83 | Enabled: false 84 | 85 | Layout/EmptyLinesAroundExceptionHandlingKeywords: 86 | Description: 'Checks if empty lines exist around the bodies of `begin` sections. Does not check empty lines at `begin` body beginning/end and around method definition body.' 87 | Enabled: true 88 | 89 | Layout/EmptyLinesAroundMethodBody: 90 | Description: 'Keeps track of empty lines around method bodies.' 91 | Enabled: true 92 | 93 | Layout/EmptyLinesAroundModuleBody: 94 | Description: 'Keeps track of empty lines around module bodies.' 95 | Enabled: false 96 | 97 | Layout/EndOfLine: 98 | Description: 'Use Unix-style line endings.' 99 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#crlf' 100 | Enabled: true 101 | 102 | Layout/ExtraSpacing: 103 | Description: 'Do not use unnecessary spacing.' 104 | Enabled: true 105 | 106 | Layout/FirstArrayElementLineBreak: 107 | Description: 'Checks for a line break before the first element in a multi-line array. @ahoey: Halle-fucking-lujah.' 108 | Enabled: true 109 | 110 | Layout/FirstHashElementLineBreak: 111 | Description: 'Checks for a line break before the first element in a multi-line hash.' 112 | Enabled: true 113 | 114 | Layout/FirstMethodArgumentLineBreak: 115 | Description: 'Checks for a line break before the first argument in a multi-line method call.' 116 | Enabled: true 117 | 118 | Layout/FirstMethodParameterLineBreak: 119 | Description: 'Checks for a line break before the first parameter in a mult-line method parameter definition.' 120 | Enabled: true 121 | 122 | Layout/FirstParameterIndentation: 123 | Description: 'Checks the indentation of the first parameter in a method call.' 124 | EnforcedStyle: consistent 125 | Enabled: true 126 | 127 | Layout/IndentArray: 128 | Description: >- 129 | Checks the indentation of the first element in an array 130 | literal. 131 | EnforcedStyle: consistent 132 | Enabled: true 133 | 134 | Layout/IndentAssignment: 135 | Description: 'Checks the indentation of the first line of the right-hand side of a multi-line assignment.' 136 | Enabled: true 137 | 138 | Layout/IndentHash: 139 | Description: 'Checks the indentation of the first key in a hash literal.' 140 | EnforcedStyle: consistent 141 | Enabled: true 142 | 143 | Layout/IndentationConsistency: 144 | Description: 'Keep indentation straight.' 145 | Enabled: true 146 | 147 | Layout/IndentationWidth: 148 | Description: 'Use 2 spaces for indentation.' 149 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation' 150 | Width: 2 151 | Enabled: true 152 | 153 | Layout/InitialIndentation: 154 | Description: 'Checks the indentation of the first non-blank non-comment line in a file.' 155 | Enabled: true 156 | 157 | Layout/LeadingCommentSpace: 158 | Description: 'Comments should start with a space.' 159 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-space' 160 | Enabled: true 161 | 162 | Layout/MultilineArrayBraceLayout: 163 | Description: 'Checks that the closing brace in an array literal is on a new line.' 164 | EnforcedStyle: new_line 165 | Enabled: true 166 | 167 | Layout/MultilineAssignmentLayout: 168 | EnforcedStyle: same_line 169 | Enabled: true 170 | 171 | Layout/MultilineBlockLayout: 172 | Description: 'Ensures newlines after multiline block do statements.' 173 | Enabled: true 174 | 175 | Layout/MultilineHashBraceLayout: 176 | Description: 'Checks that the closing brace in an hash literal is on a new line.' 177 | EnforcedStyle: new_line 178 | Enabled: true 179 | 180 | Layout/MultilineMethodCallBraceLayout: 181 | Description: 'Checks that the closing brace in a multi-line method call is on a new line.' 182 | EnforcedStyle: new_line 183 | Enabled: true 184 | 185 | Layout/MultilineMethodDefinitionBraceLayout: 186 | Description: 'Checks that the closing brace in a method definition is on a new line.' 187 | EnforcedStyle: new_line 188 | Enabled: true 189 | 190 | Layout/MultilineOperationIndentation: 191 | Description: 'Checks indentation of binary operations that span more than one line.' 192 | EnforcedStyle: indented 193 | Enabled: true 194 | 195 | Layout/RescueEnsureAlignment: 196 | Description: 'Align rescues and ensures correctly.' 197 | Enabled: true 198 | 199 | Layout/SpaceAfterColon: 200 | Description: 'Use spaces after colons.' 201 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 202 | Enabled: true 203 | 204 | Layout/SpaceAfterComma: 205 | Description: 'Use spaces after commas.' 206 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 207 | Enabled: true 208 | 209 | Layout/SpaceAfterMethodName: 210 | Description: 'Do not put a space between a method name and the opening parenthesis in a method definition.' 211 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' 212 | Enabled: true 213 | 214 | Layout/SpaceAfterNot: 215 | Description: 'Tracks redundant space after the ! operator.' 216 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-bang' 217 | Enabled: true 218 | 219 | Layout/SpaceAfterSemicolon: 220 | Description: 'Use spaces after semicolons.' 221 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 222 | Enabled: true 223 | 224 | Layout/SpaceAroundBlockParameters: 225 | Description: 'Checks the spacing inside and after block parameters pipes.' 226 | Enabled: true 227 | 228 | Layout/SpaceAroundEqualsInParameterDefault: 229 | Description: >- 230 | Checks that the equals signs in parameter default assignments 231 | have or don't have surrounding space depending on 232 | configuration. 233 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-around-equals' 234 | EnforcedStyle: space 235 | Enabled: true 236 | 237 | Layout/SpaceAroundKeyword: 238 | Description: 'Use spaces around keywords.' 239 | Enabled: true 240 | 241 | Layout/SpaceAroundOperators: 242 | Description: 'Use a single space around operators.' 243 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 244 | Enabled: true 245 | 246 | Layout/SpaceBeforeBlockBraces: 247 | Description: >- 248 | Checks that the left block brace has or doesn't have space 249 | before it. 250 | Enabled: true 251 | 252 | Layout/SpaceBeforeComma: 253 | Description: 'No spaces before commas.' 254 | Enabled: true 255 | 256 | Layout/SpaceBeforeComment: 257 | Description: >- 258 | Checks for missing space between code and a comment on the 259 | same line. 260 | Enabled: true 261 | 262 | Layout/SpaceBeforeFirstArg: 263 | Description: >- 264 | Checks that exactly one space is used between a method name 265 | and the first argument for method calls without parentheses. 266 | Enabled: true 267 | 268 | Layout/SpaceBeforeSemicolon: 269 | Description: 'No spaces before semicolons.' 270 | Enabled: true 271 | 272 | Layout/SpaceInLambdaLiteral: 273 | Description: 'Require no space between stabby and arguments.' 274 | EnforcedStyle: require_no_space 275 | Enabled: true 276 | 277 | Layout/SpaceInsideArrayLiteralBrackets: 278 | Description: 'Do not space pad array literals.' 279 | EnforcedStyle: no_space 280 | Enabled: true 281 | 282 | Layout/SpaceInsideArrayPercentLiteral: 283 | Description: 'Checks for unnecessary spaces in percent literal arrays.' 284 | Enabled: true 285 | 286 | Layout/SpaceInsideBlockBraces: 287 | Description: 'Checks that blocks braces are space padded.' 288 | Enabled: true 289 | 290 | Layout/SpaceInsideHashLiteralBraces: 291 | Description: 'Use spaces inside hash literal braces - or do not.' 292 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 293 | Enabled: true 294 | 295 | Layout/SpaceInsideParens: 296 | Description: 'No spaces after ( or before ).' 297 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces' 298 | Enabled: true 299 | 300 | Layout/SpaceInsidePercentLiteralDelimiters: 301 | Description: 'Checks for unnecessary spaces inside %i%w%x literals.' 302 | Enabled: true 303 | 304 | Layout/SpaceInsideRangeLiteral: 305 | Description: 'No spaces inside range literals.' 306 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-inside-range-literals' 307 | Enabled: true 308 | 309 | Layout/SpaceInsideReferenceBrackets: 310 | Description: 'No spaces in collection reference array brackets.' 311 | Enabled: true 312 | 313 | Layout/SpaceInsideStringInterpolation: 314 | Description: 'Checks for padding/surrounding spaces inside string interpolation.' 315 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#string-interpolation' 316 | Enabled: true 317 | 318 | Layout/Tab: 319 | Description: 'No hard tabs.' 320 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation' 321 | Enabled: true 322 | 323 | Layout/TrailingBlankLines: 324 | Description: 'Checks trailing blank lines and final newline.' 325 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#newline-eof' 326 | Enabled: true 327 | 328 | Layout/TrailingWhitespace: 329 | Description: 'Avoid trailing whitespace.' 330 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace' 331 | Enabled: true 332 | 333 | ######################### Lint ############################# 334 | 335 | Lint/AmbiguousBlockAssociation: 336 | Description: 'Checks for ambiguous block association with method without params.' 337 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#syntax' 338 | Enabled: true 339 | 340 | Lint/AmbiguousOperator: 341 | Description: >- 342 | Checks for ambiguous operators in the first argument of a 343 | method invocation without parentheses. 344 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-as-args' 345 | Enabled: true 346 | 347 | Lint/AmbiguousRegexpLiteral: 348 | Description: >- 349 | Checks for ambiguous regexp literals in the first argument of 350 | a method invocation without parenthesis. 351 | Enabled: true 352 | 353 | Lint/AssignmentInCondition: 354 | Description: 'Do not use assignment in conditions.' 355 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition' 356 | Enabled: false 357 | 358 | Lint/BlockAlignment: 359 | Description: 'Checks whether the end keywords are aligned properly for do end blocks.' 360 | Enabled: true 361 | 362 | Lint/BooleanSymbol: 363 | Description: 'Do not create :true or :false symbols' 364 | Enabled: true 365 | 366 | Lint/CircularArgumentReference: 367 | Description: 'Do not refer to the keyword argument in the default value.' 368 | Enabled: true 369 | 370 | Lint/ConditionPosition: 371 | Description: >- 372 | Checks for condition placed in a confusing position relative to 373 | the keyword. 374 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition' 375 | Enabled: true 376 | 377 | Lint/Debugger: 378 | Description: 'Check for debugger calls.' 379 | Enabled: true 380 | 381 | Lint/DeprecatedClassMethods: 382 | Description: 'Check for deprecated class method calls.' 383 | Enabled: true 384 | 385 | Lint/DuplicateCaseCondition: 386 | Description: 'Check for there are no duplicate case conditions.' 387 | Enabled: true 388 | 389 | Lint/DuplicateMethods: 390 | Description: 'Check for duplicate methods calls.' 391 | Enabled: true 392 | 393 | Lint/DuplicatedKey: 394 | Description: 'Check for duplicate hash keys.' 395 | Enabled: true 396 | 397 | Lint/EachWithObjectArgument: 398 | Description: 'Check for immutable argument given to each_with_object.' 399 | Enabled: true 400 | 401 | Lint/ElseLayout: 402 | Description: 'Check for odd code arrangement in an else block.' 403 | Enabled: true 404 | 405 | Lint/EmptyEnsure: 406 | Description: 'Checks for empty ensure block.' 407 | Enabled: true 408 | 409 | Lint/EmptyExpression: 410 | Description: 'Checks for empty if/unless/while expressions.' 411 | Enabled: true 412 | 413 | Lint/EndAlignment: 414 | Description: 'Checks whether the end keywords are aligned properly.' 415 | EnforcedStyleAlignWith: 'variable' 416 | Enabled: true 417 | 418 | Lint/EmptyInterpolation: 419 | Description: 'Checks for empty string interpolation.' 420 | Enabled: true 421 | 422 | Lint/EndInMethod: 423 | Description: 'END blocks should not be placed inside method definitions.' 424 | Enabled: true 425 | 426 | Lint/EnsureReturn: 427 | Description: 'Do not use return in an ensure block.' 428 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-return-ensure' 429 | Enabled: true 430 | 431 | Lint/FormatParameterMismatch: 432 | Description: 'The number of parameters to format/sprint must match the fields.' 433 | Enabled: true 434 | 435 | Lint/HandleExceptions: 436 | Description: 'Do not suppress exception.' 437 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions' 438 | Enabled: true 439 | 440 | Lint/ImplicitStringConcatenation: 441 | Description: 'Checks for implicit string concatentation of string literals on the same line.' 442 | Enabled: true 443 | 444 | Lint/IneffectiveAccessModifier: 445 | Description: 'Checks for `private` or `protected` on class methods.' 446 | Enabled: true 447 | 448 | Lint/InheritException: 449 | Description: 'Do not inherit from Exception. Use RuntimeError or StandardError instead.' 450 | EnforcedStyle: standard_error 451 | Enabled: true 452 | 453 | Lint/LiteralAsCondition: 454 | Description: 'Checks of literals used in conditions.' 455 | Enabled: true 456 | 457 | Lint/LiteralInInterpolation: 458 | Description: 'Checks for literals used in interpolation.' 459 | Enabled: true 460 | 461 | Lint/Loop: 462 | Description: >- 463 | Use Kernel#loop with break rather than begin/end/until or 464 | begin/end/while for post-loop tests. 465 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#loop-with-break' 466 | Enabled: true 467 | 468 | Lint/NestedMethodDefinition: 469 | Description: 'Do not use nested method definitions.' 470 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-methods' 471 | Enabled: true 472 | 473 | Lint/NestedPercentLiteral: 474 | Description: 'Do not use nested percent literals.' 475 | Enabled: true 476 | 477 | Lint/NextWithoutAccumulator: 478 | Description: 'Do not emit the accumulator when calling `next` in a `reduce` block' 479 | Enabled: true 480 | 481 | Lint/NonLocalExitFromIterator: 482 | Description: 'Do not use return in iterator to cause non-local exit.' 483 | Enabled: true 484 | 485 | Lint/ParenthesesAsGroupedExpression: 486 | Description: >- 487 | Checks for method calls with a space before the opening 488 | parenthesis. 489 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' 490 | Enabled: true 491 | 492 | Lint/RequireParentheses: 493 | Description: >- 494 | Use parentheses in the method call to avoid confusion 495 | about precedence. 496 | Enabled: true 497 | 498 | Lint/RescueException: 499 | Description: 'Avoid rescuing the Exception class.' 500 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-blind-rescues' 501 | Enabled: true 502 | 503 | Lint/ShadowingOuterLocalVariable: 504 | Description: >- 505 | Do not use the same name as outer local variable 506 | for block arguments or block local variables. 507 | Enabled: true 508 | 509 | Lint/StringConversionInInterpolation: 510 | Description: 'Checks for Object#to_s usage in string interpolation.' 511 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-to-s' 512 | Enabled: true 513 | 514 | Lint/UnderscorePrefixedVariableName: 515 | Description: 'Do not use prefix `_` for a variable that is used.' 516 | Enabled: true 517 | 518 | Lint/UnneededDisable: 519 | Description: >- 520 | Checks for rubocop:disable comments that can be removed. 521 | Note: this cop is not disabled when disabling all cops. 522 | It must be explicitly disabled. 523 | Enabled: true 524 | 525 | Lint/UnneededSplatExpansion: 526 | Description: 'Checks for unneeded usages of splat expansion.' 527 | Enabled: true 528 | 529 | Lint/UnreachableCode: 530 | Description: 'Unreachable code.' 531 | Enabled: true 532 | 533 | Lint/UnusedBlockArgument: 534 | Description: 'Checks for unused block arguments.' 535 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' 536 | Enabled: true 537 | 538 | Lint/UnusedMethodArgument: 539 | Description: 'Checks for unused method arguments.' 540 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' 541 | Enabled: true 542 | 543 | Lint/UselessAccessModifier: 544 | Description: 'Checks for useless access modifiers.' 545 | Enabled: true 546 | 547 | Lint/UselessAssignment: 548 | Description: 'Checks for useless assignment to a local variable.' 549 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' 550 | Enabled: true 551 | 552 | Lint/UselessComparison: 553 | Description: 'Checks for comparison of something with itself.' 554 | Enabled: true 555 | 556 | Lint/UselessElseWithoutRescue: 557 | Description: 'Checks for useless `else` in `begin..end` without `rescue`.' 558 | Enabled: true 559 | 560 | Lint/UselessSetterCall: 561 | Description: 'Checks for useless setter call to a local variable.' 562 | Enabled: true 563 | 564 | Lint/Void: 565 | Description: 'Possible use of operator/literal/variable in void context.' 566 | Enabled: true 567 | 568 | ######################### Metrics ########################## 569 | 570 | Metrics/AbcSize: 571 | Description: >- 572 | A calculated magnitude based on number of assignments, 573 | branches, and conditions. 574 | Reference: 'http://c2.com/cgi/wiki?AbcMetric' 575 | Enabled: false 576 | Max: 20 577 | 578 | Metrics/BlockNesting: 579 | Description: 'Avoid excessive block nesting.' 580 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count' 581 | Enabled: true 582 | Max: 4 583 | 584 | Metrics/ClassLength: 585 | Description: 'Avoid classes longer than 1000 lines of code.' 586 | Enabled: true 587 | Max: 1000 588 | 589 | Metrics/CyclomaticComplexity: 590 | Description: >- 591 | A complexity metric that is strongly correlated to the number 592 | of test cases needed to validate a method. 593 | Enabled: true 594 | Max: 20 595 | 596 | Metrics/LineLength: 597 | Description: 'Limit lines to 80 characters.' 598 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits' 599 | Enabled: false 600 | 601 | Metrics/MethodLength: 602 | Description: 'Avoid methods longer than 30 lines of code.' 603 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods' 604 | Enabled: true 605 | Exclude: 606 | - app/views/**/*.html.rb 607 | Max: 100 608 | 609 | Metrics/ModuleLength: 610 | Description: 'Avoid modules longer than 250 lines of code.' 611 | Enabled: true 612 | Max: 250 613 | 614 | Metrics/ParameterLists: 615 | Description: 'Avoid parameter lists longer than three or four parameters.' 616 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params' 617 | Enabled: true 618 | Max: 6 619 | 620 | Metrics/PerceivedComplexity: 621 | Description: >- 622 | A complexity metric geared towards measuring complexity for a 623 | human reader. 624 | Enabled: false 625 | 626 | ######################### Naming ########################### 627 | 628 | Naming/AccessorMethodName: 629 | Description: Check the naming of accessor methods for get_/set_. 630 | Enabled: false 631 | 632 | Naming/AsciiIdentifiers: 633 | Description: 'Use only ascii symbols in identifiers.' 634 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers' 635 | Enabled: true 636 | 637 | Naming/BinaryOperatorParameterName: 638 | Description: 'When defining binary operators, name the argument other.' 639 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg' 640 | Enabled: false 641 | 642 | Naming/ClassAndModuleCamelCase: 643 | Description: 'Use CamelCase for classes and modules.' 644 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#camelcase-classes' 645 | Enabled: true 646 | 647 | Naming/ConstantName: 648 | Description: 'Constants should use SCREAMING_SNAKE_CASE.' 649 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#screaming-snake-case' 650 | Enabled: true 651 | 652 | Naming/FileName: 653 | Description: 'Use snake_case for source file names.' 654 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files' 655 | Enabled: true 656 | 657 | Naming/MethodName: 658 | Description: 'Use the configured style when naming methods.' 659 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars' 660 | EnforcedStyle: snake_case 661 | Enabled: true 662 | 663 | Naming/PredicateName: 664 | Description: 'Check the names of predicate methods.' 665 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark' 666 | Enabled: false 667 | 668 | Naming/VariableName: 669 | Description: 'Use the configured style when naming variables.' 670 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars' 671 | EnforcedStyle: snake_case 672 | Enabled: true 673 | 674 | ######################### Performance ####################### 675 | 676 | Performance/CompareWithBlock: 677 | Description: 'Identifies places where `sort { |a, b| a.foo <=> b.foo }` can be replaced by `sort_by(&:foo)`.' 678 | Enabled: true 679 | 680 | Performance/Count: 681 | Description: >- 682 | Use `count` instead of `select...size`, `reject...size`, 683 | `select...count`, `reject...count`, `select...length`, 684 | and `reject...length`. 685 | Enabled: true 686 | 687 | Performance/Detect: 688 | Description: >- 689 | Use `detect` instead of `select.first`, `find_all.first`, 690 | `select.last`, and `find_all.last`. 691 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerabledetect-vs-enumerableselectfirst-code' 692 | Enabled: true 693 | 694 | Performance/EndWith: 695 | Description: 'Identifies unnecessary use of a regex where `String.end_with?` would suffice.' 696 | Enabled: true 697 | 698 | Performance/FlatMap: 699 | Description: >- 700 | Use `Enumerable#flat_map` 701 | instead of `Enumerable#map...Array#flatten(1)` 702 | or `Enumberable#collect..Array#flatten(1)` 703 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablemaparrayflatten-vs-enumerableflat_map-code' 704 | Enabled: true 705 | EnabledForFlattenWithoutParams: false 706 | # If enabled, this cop will warn about usages of 707 | # `flatten` being called without any parameters. 708 | # This can be dangerous since `flat_map` will only flatten 1 level, and 709 | # `flatten` without any parameters can flatten multiple levels. 710 | 711 | Performance/RedundantBlockCall: 712 | Description: 'Identifies the use of a `&block` parameter and `block.call`, where `yield` would be sufficient.' 713 | Enabled: true 714 | 715 | Performance/RedundantMatch: 716 | Description: 'Do not use `Regexp#match` or `String#match` where =~ is sufficient.' 717 | Enabled: true 718 | 719 | Performance/RedundantMerge: 720 | Description: 'Use Hash#[]= instead of Hash#merge!' 721 | Enabled: true 722 | 723 | Performance/RedundantSortBy: 724 | Description: 'Use `sort` instead of `sort_by` where possible.' 725 | Enabled: true 726 | 727 | Performance/ReverseEach: 728 | Description: 'Use `reverse_each` instead of `reverse.each`.' 729 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablereverseeach-vs-enumerablereverse_each-code' 730 | Enabled: true 731 | 732 | Performance/Sample: 733 | Description: >- 734 | Use `sample` instead of `shuffle.first`, 735 | `shuffle.last`, and `shuffle[Fixnum]`. 736 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code' 737 | Enabled: true 738 | 739 | Performance/Size: 740 | Description: >- 741 | Use `size` instead of `count` for counting 742 | the number of elements in `Array` and `Hash`. 743 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#arraycount-vs-arraysize-code' 744 | Enabled: true 745 | 746 | Performance/StartWith: 747 | Description: 'Use `String#start_with?` instead of regex where possible' 748 | Enabled: true 749 | 750 | Performance/StringReplacement: 751 | Description: >- 752 | Use `tr` instead of `gsub` when you are replacing the same 753 | number of characters. Use `delete` instead of `gsub` when 754 | you are deleting characters. 755 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#stringgsub-vs-stringtr-code' 756 | Enabled: true 757 | 758 | ######################### Rails ########################### 759 | 760 | Rails/ActionFilter: 761 | Description: 'Enforces consistent use of action filter methods.' 762 | Enabled: false 763 | 764 | Rails/Blank: 765 | Description: 'Checks for code that can be changed to `blank?`' 766 | Enabled: true 767 | 768 | Rails/Date: 769 | Description: >- 770 | Checks the correct usage of date aware methods, 771 | such as Date.today, Date.current etc. 772 | Enabled: false 773 | 774 | Rails/Delegate: 775 | Description: 'Prefer delegate method for delegations.' 776 | Enabled: false 777 | 778 | Rails/FindBy: 779 | Description: 'Prefer find_by over where.first.' 780 | Enabled: false 781 | 782 | Rails/FindEach: 783 | Description: 'Prefer all.find_each over all.find.' 784 | Enabled: false 785 | 786 | Rails/HasAndBelongsToMany: 787 | Description: 'Prefer has_many :through to has_and_belongs_to_many.' 788 | Enabled: false 789 | 790 | Rails/Output: 791 | Description: 'Checks for calls to puts, print, etc.' 792 | Enabled: false 793 | 794 | Rails/ReadWriteAttribute: 795 | Description: >- 796 | Checks for read_attribute(:attr) and 797 | write_attribute(:attr, val). 798 | Enabled: false 799 | 800 | Rails/SafeNavigation: 801 | Description: 'Prefer safe navigation operator to try!' 802 | Enabled: true 803 | 804 | Rails/ScopeArgs: 805 | Description: 'Checks the arguments of ActiveRecord scopes.' 806 | Enabled: false 807 | 808 | Rails/TimeZone: 809 | Description: 'Checks the correct usage of time zone aware methods.' 810 | StyleGuide: 'https://github.com/bbatsov/rails-style-guide#time' 811 | Reference: 'http://danilenko.org/2012/7/6/rails_timezones' 812 | Enabled: false 813 | 814 | Rails/Validation: 815 | Description: 'Use validates :attribute, hash of validations.' 816 | Enabled: false 817 | 818 | ######################### Security ######################### 819 | 820 | Security/Eval: 821 | Description: 'The use of eval represents a serious security risk.' 822 | Enabled: true 823 | 824 | ######################### Style ############################ 825 | 826 | Style/Alias: 827 | Description: 'Use alias_method instead of alias. The lexical scope of alias can lead to unpredicatbility.' 828 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method' 829 | EnforcedStyle: prefer_alias_method 830 | Enabled: true 831 | 832 | Style/AndOr: 833 | Description: 'Use &&/|| instead of and/or.' 834 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-and-or-or' 835 | Enabled: true 836 | 837 | Style/ArrayJoin: 838 | Description: 'Use Array#join instead of Array#*.' 839 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#array-join' 840 | Enabled: true 841 | 842 | Style/AsciiComments: 843 | Description: 'Use only ascii symbols in comments.' 844 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments' 845 | Enabled: false 846 | 847 | Style/Attr: 848 | Description: 'Checks for uses of Module#attr.' 849 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr' 850 | Enabled: true 851 | 852 | Style/BarePercentLiterals: 853 | Description: 'Checks if usage of %() or %Q() matches configuration.' 854 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q-shorthand' 855 | Enabled: false 856 | 857 | Style/BeginBlock: 858 | Description: 'Avoid the use of BEGIN blocks.' 859 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-BEGIN-blocks' 860 | Enabled: true 861 | 862 | Style/BlockComments: 863 | Description: 'Do not use block comments.' 864 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-block-comments' 865 | Enabled: true 866 | 867 | Style/BlockDelimiters: 868 | Description: >- 869 | Avoid using {...} for multi-line blocks (multiline chaining is 870 | always ugly). 871 | Prefer {...} over do...end for single-line blocks. 872 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' 873 | Enabled: true 874 | 875 | Style/BracesAroundHashParameters: 876 | Description: 'Checks for braces around the last argument of method call if the last parameter is a hash.' 877 | Enabled: true 878 | 879 | Style/CaseEquality: 880 | Description: 'Avoid explicit use of the case equality operator(===).' 881 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality' 882 | Enabled: true 883 | 884 | Style/CharacterLiteral: 885 | Description: 'Checks for uses of character literals.' 886 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-character-literals' 887 | Enabled: true 888 | 889 | Style/ClassAndModuleChildren: 890 | Description: 'Checks style of children classes and modules.' 891 | Enabled: false 892 | 893 | Style/ClassCheck: 894 | Description: 'Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.' 895 | Enabled: false 896 | 897 | Style/ClassMethods: 898 | Description: 'Use self when defining module/class methods.' 899 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#def-self-class-methods' 900 | Enabled: false 901 | 902 | Style/ClassVars: 903 | Description: 'Avoid the use of class variables.' 904 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-class-vars' 905 | Enabled: true 906 | 907 | Style/ColonMethodCall: 908 | Description: 'Do not use :: for method call.' 909 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons' 910 | Enabled: true 911 | 912 | Style/ColonMethodDefinition: 913 | Description: 'Do not use :: for method definition.' 914 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons' 915 | Enabled: true 916 | 917 | Style/CommandLiteral: 918 | Description: 'Use `` or %x around command literals.' 919 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-x' 920 | Enabled: false 921 | 922 | Style/CommentAnnotation: 923 | Description: 'Checks formatting of annotation comments.' 924 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords' 925 | Enabled: false 926 | 927 | Style/ConditionalAssignment: 928 | Description: 'Checks for `if` and `case` statements where each branch assigns the same variable.' 929 | Enabled: true 930 | 931 | Style/DateTime: 932 | Description: 'Checks for use of DateTime that should be replaced by `Date` or `Time`' 933 | Enabled: true 934 | 935 | Style/DefWithParentheses: 936 | Description: 'Use def with parentheses when there are arguments.' 937 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens' 938 | Enabled: true 939 | 940 | Style/Documentation: 941 | Description: 'Document classes and non-namespace modules.' 942 | Enabled: false 943 | 944 | Style/DoubleNegation: 945 | Description: 'Checks for uses of double negation (!!).' 946 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-bang-bang' 947 | Enabled: false 948 | 949 | Style/EachWithObject: 950 | Description: 'Prefer `each_with_object` over `inject` or `reduce`.' 951 | Enabled: true 952 | 953 | Style/EmptyBlockParameter: 954 | Description: 'Empty pipes in blocks are redundant.' 955 | Enabled: true 956 | 957 | Style/EmptyElse: 958 | Description: 'Avoid empty else-clauses.' 959 | EnforcedStyle: empty 960 | Enabled: true 961 | 962 | Style/EmptyLambdaParameter: 963 | Description: 'Skip parentheses for empty lambda parameters.' 964 | Enabled: true 965 | 966 | Style/EmptyLiteral: 967 | Description: 'Prefer literals to Array.new/Hash.new/String.new.' 968 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#literal-array-hash' 969 | Enabled: true 970 | 971 | Style/EndBlock: 972 | Description: 'Avoid the use of END blocks.' 973 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-END-blocks' 974 | Enabled: true 975 | 976 | Style/EvenOdd: 977 | Description: 'Favor the use of Fixnum#even? && Fixnum#odd?.' 978 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' 979 | Enabled: true 980 | 981 | Style/FlipFlop: 982 | Description: 'Checks for flip flops.' 983 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops' 984 | Enabled: false 985 | 986 | Style/For: 987 | Description: 'Checks use of for or each in multiline loops.' 988 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-for-loops' 989 | Enabled: true 990 | 991 | Style/FormatString: 992 | Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.' 993 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf' 994 | Enabled: false 995 | 996 | Style/FrozenStringLiteralComment: 997 | Description: 'This enforces that the `# frozen_string_literal: true` comment is at the top of every file to enable frozen string literals.' 998 | Enabled: true 999 | 1000 | Style/GlobalVars: 1001 | Description: 'Do not introduce global variables.' 1002 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#instance-vars' 1003 | Reference: 'http://www.zenspider.com/Languages/Ruby/QuickRef.html' 1004 | Enabled: false 1005 | 1006 | Style/GuardClause: 1007 | Description: 'Check for conditionals that can be replaced with guard clauses.' 1008 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' 1009 | Enabled: false 1010 | 1011 | Style/HashSyntax: 1012 | Description: 'Prefer hash rockets.' 1013 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-literals' 1014 | EnforcedStyle: hash_rockets 1015 | Enabled: true 1016 | 1017 | Style/IfUnlessModifier: 1018 | Description: >- 1019 | Favor modifier if/unless usage when you have a 1020 | single-line body. 1021 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier' 1022 | Enabled: false 1023 | 1024 | Style/IfUnlessModifierOfIfUnless: 1025 | Description: 'Do not use if and unless as modifiers of other if or unless statements.' 1026 | Enabled: true 1027 | 1028 | Style/IfWithSemicolon: 1029 | Description: 'Do not use if x; .... Use the ternary operator instead.' 1030 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs' 1031 | Enabled: true 1032 | 1033 | Style/InfiniteLoop: 1034 | Description: 'Use Kernel#loop for infinite loops.' 1035 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#infinite-loop' 1036 | Enabled: true 1037 | 1038 | Style/InverseMethods: 1039 | Description: 'Do not use `not` or `!` when an inverse method can be used.' 1040 | Enabled: true 1041 | 1042 | Style/Lambda: 1043 | Description: 'Use the new lambda literal syntax for single-line blocks.' 1044 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#lambda-multi-line' 1045 | Enabled: false 1046 | 1047 | Style/LambdaCall: 1048 | Description: 'Use lambda.call(...) instead of lambda.(...).' 1049 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc-call' 1050 | Enabled: false 1051 | 1052 | Style/LineEndConcatenation: 1053 | Description: >- 1054 | Use \ instead of + or << to concatenate two string literals at 1055 | line end. 1056 | Enabled: false 1057 | 1058 | Style/MethodCallWithArgsParentheses: 1059 | Description: 'Use parentheses for method calls with arguments.' 1060 | IgnoredMethods: 1061 | - puts 1062 | - raise 1063 | - require 1064 | - load 1065 | - render 1066 | - respond_to 1067 | - widget 1068 | - extend 1069 | - include 1070 | Exclude: 1071 | - Gemfile 1072 | - db/migrate 1073 | - app/views/**/*.html.rb 1074 | - spec/**/*.rb 1075 | Enabled: true 1076 | 1077 | Style/MethodCallWithoutArgsParentheses: 1078 | Description: 'Do not use parentheses for method calls with no arguments.' 1079 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-args-no-parens' 1080 | Enabled: true 1081 | 1082 | Style/MethodDefParentheses: 1083 | Description: >- 1084 | Checks if the method definitions have or don't have 1085 | parentheses. 1086 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens' 1087 | Enabled: true 1088 | 1089 | Style/MixinGrouping: 1090 | Description: 'Separate include statements.' 1091 | Enabled: true 1092 | 1093 | Style/ModuleFunction: 1094 | Description: 'Checks for usage of `extend self` in modules.' 1095 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#module-function' 1096 | Enabled: false 1097 | 1098 | Style/MultilineBlockChain: 1099 | Description: 'Avoid multi-line chains of blocks.' 1100 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' 1101 | Enabled: false 1102 | 1103 | Style/MultilineIfThen: 1104 | Description: 'Do not use then for multi-line if/unless.' 1105 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-then' 1106 | Enabled: true 1107 | 1108 | Style/MultilineTernaryOperator: 1109 | Description: >- 1110 | Avoid multi-line ?: (the ternary operator); 1111 | use if/unless instead. 1112 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-ternary' 1113 | Enabled: true 1114 | 1115 | Style/NegatedIf: 1116 | Description: >- 1117 | Favor unless over if for negative conditions 1118 | (or control flow or). 1119 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#unless-for-negatives' 1120 | Enabled: true 1121 | 1122 | Style/NegatedWhile: 1123 | Description: 'Favor until over while for negative conditions.' 1124 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#until-for-negatives' 1125 | Enabled: true 1126 | 1127 | Style/NestedTernaryOperator: 1128 | Description: 'Use one expression per branch in a ternary operator.' 1129 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-ternary' 1130 | Enabled: false 1131 | 1132 | Style/NestedParenthesizedCalls: 1133 | Description: 'Use parentheses for nested method calls with arguments.' 1134 | Enabled: true 1135 | 1136 | Style/Next: 1137 | Description: 'Use `next` to skip iteration instead of a condition at the end.' 1138 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' 1139 | Enabled: false 1140 | 1141 | Style/NilComparison: 1142 | Description: 'Prefer x.nil? to x == nil.' 1143 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' 1144 | Enabled: true 1145 | 1146 | Style/NonNilCheck: 1147 | Description: 'Checks for redundant nil checks.' 1148 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-non-nil-checks' 1149 | Enabled: true 1150 | 1151 | Style/Not: 1152 | Description: 'Use ! instead of not.' 1153 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bang-not-not' 1154 | Enabled: true 1155 | 1156 | Style/NumericLiterals: 1157 | Description: >- 1158 | Add underscores to large numeric literals to improve their 1159 | readability. 1160 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics' 1161 | Enabled: false 1162 | 1163 | Style/OneLineConditional: 1164 | Description: >- 1165 | Favor the ternary operator(?:) over 1166 | if/then/else/end constructs. 1167 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator' 1168 | Enabled: true 1169 | 1170 | Style/OptionalArguments: 1171 | Description: >- 1172 | Checks for optional arguments that do not appear at the end 1173 | of the argument list 1174 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#optional-arguments' 1175 | Enabled: true 1176 | 1177 | Style/OrAssignment: 1178 | Description: 'Use ||= where possible.' 1179 | Enabled: true 1180 | 1181 | Style/ParallelAssignment: 1182 | Description: >- 1183 | Check for simple usages of parallel assignment. 1184 | It will only warn when the number of variables 1185 | matches on both sides of the assignment. 1186 | This also provides performance benefits 1187 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parallel-assignment' 1188 | Enabled: false 1189 | 1190 | Style/ParenthesesAroundCondition: 1191 | Description: >- 1192 | Don't use parentheses around the condition of an 1193 | if/unless/while. 1194 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-parens-if' 1195 | Enabled: false 1196 | 1197 | Style/PercentLiteralDelimiters: 1198 | Description: 'Use `%`-literal delimiters consistently.' 1199 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-literal-braces' 1200 | Enabled: false 1201 | 1202 | Style/PercentQLiterals: 1203 | Description: 'Checks if uses of %Q/%q match the configured preference.' 1204 | Enabled: true 1205 | 1206 | Style/PerlBackrefs: 1207 | Description: 'Avoid Perl-style regex back references.' 1208 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers' 1209 | Enabled: false 1210 | 1211 | Style/PreferredHashMethods: 1212 | Description: 'Checks for use of deprecated Hash methods.' 1213 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-key' 1214 | Enabled: false 1215 | 1216 | Style/Proc: 1217 | Description: 'Use proc instead of Proc.new.' 1218 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc' 1219 | Enabled: false 1220 | 1221 | Style/RaiseArgs: 1222 | Description: 'Checks the arguments passed to raise/fail.' 1223 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#exception-class-messages' 1224 | Enabled: false 1225 | 1226 | Style/RandomWithOffset: 1227 | Description: 'Do not add numbers to random numbers. Use ranges instead.' 1228 | Enabled: true 1229 | 1230 | Style/RedundantBegin: 1231 | Description: 'Do not use begin blocks when they are not needed.' 1232 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#begin-implicit' 1233 | Enabled: true 1234 | 1235 | Style/RedundantConditional: 1236 | Description: 'Checks for conditional returning of true/false in conditionals' 1237 | Enabled: true 1238 | 1239 | Style/RedundantException: 1240 | Description: 'Checks for an obsolete RuntimeException argument in raise/fail.' 1241 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-runtimeerror' 1242 | Enabled: false 1243 | 1244 | Style/RedundantReturn: 1245 | Description: 'Do not use return where it is not required.' 1246 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-return' 1247 | Enabled: false 1248 | 1249 | Style/RedundantSelf: 1250 | Description: 'Do not use self where it is not needed.' 1251 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-self-unless-required' 1252 | Enabled: true 1253 | 1254 | Style/RegexpLiteral: 1255 | Description: 'Use / or %r around regular expressions.' 1256 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r' 1257 | Enabled: false 1258 | 1259 | Style/RescueModifier: 1260 | Description: 'Avoid using rescue in its modifier form.' 1261 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-rescue-modifiers' 1262 | Enabled: false 1263 | 1264 | Style/SafeNavigation: 1265 | Description: 'Checks for places where the safe navigation operator should have been used.' 1266 | Enabled: true 1267 | 1268 | Style/SelfAssignment: 1269 | Description: >- 1270 | Checks for places where self-assignment shorthand should have 1271 | been used. 1272 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#self-assignment' 1273 | Enabled: true 1274 | 1275 | Style/Semicolon: 1276 | Description: 'Do not use semicolons to terminate expressions.' 1277 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon' 1278 | Enabled: true 1279 | 1280 | Style/SignalException: 1281 | Description: 'Checks for proper usage of fail and raise.' 1282 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#fail-method' 1283 | EnforcedStyle: only_raise 1284 | Enabled: true 1285 | 1286 | Style/SingleLineBlockParams: 1287 | Description: 'Enforces the names of some block params.' 1288 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#reduce-blocks' 1289 | Enabled: false 1290 | 1291 | Style/SingleLineMethods: 1292 | Description: 'Avoid single-line methods.' 1293 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-single-line-methods' 1294 | Enabled: false 1295 | 1296 | Style/SpecialGlobalVars: 1297 | Description: 'Avoid Perl-style global variables.' 1298 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms' 1299 | Enabled: true 1300 | 1301 | Style/StabbyLambdaParentheses: 1302 | Description: 'Checks for parentheses around stabby lambda arugments.' 1303 | EnforcedStyle: require_parentheses 1304 | Enabled: true 1305 | 1306 | Style/StringLiterals: 1307 | Description: 'Checks if uses of quotes match the configured preference.' 1308 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals' 1309 | EnforcedStyle: single_quotes 1310 | Enabled: true 1311 | 1312 | Style/StringLiteralsInInterpolation: 1313 | Description: >- 1314 | Checks if uses of quotes inside expressions in interpolated 1315 | strings match the configured preference. 1316 | EnforcedStyle: single_quotes 1317 | Enabled: true 1318 | 1319 | Style/StructInheritance: 1320 | Description: 'Checks for inheritance from Struct.new.' 1321 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-extend-struct-new' 1322 | Enabled: false 1323 | 1324 | Style/SymbolLiteral: 1325 | Description: 'Use plain symbols instead of string symbols when possible.' 1326 | Enabled: true 1327 | 1328 | Style/SymbolProc: 1329 | Description: 'Use symbols as procs instead of blocks when possible.' 1330 | Enabled: true 1331 | 1332 | Style/TernaryParentheses: 1333 | Description: 'Use parentheses in ternary conditions only when using complex conditions.' 1334 | EnforcedStyle: require_parentheses_when_complex 1335 | Enabled: true 1336 | 1337 | Style/TrailingCommaInArguments: 1338 | Description: 'Checks for trailing comma in parameter lists.' 1339 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-params-comma' 1340 | EnforcedStyleForMultiline: no_comma 1341 | Enabled: true 1342 | 1343 | Style/TrailingCommaInLiteral: 1344 | Description: 'Checks for trailing comma in array literals.' 1345 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' 1346 | EnforcedStyleForMultiline: consistent_comma 1347 | Enabled: false 1348 | 1349 | Style/TrailingMethodEndStatement: 1350 | Description: 'Checks for trailing end after the method definition body.' 1351 | Enabled: true 1352 | 1353 | Style/TrailingUnderscoreVariable: 1354 | Description: >- 1355 | Checks for the usage of unneeded trailing underscores at the 1356 | end of parallel variable assignment. 1357 | Enabled: true 1358 | 1359 | Style/TrivialAccessors: 1360 | Description: 'Prefer attr_* methods to trivial readers/writers.' 1361 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family' 1362 | Enabled: true 1363 | 1364 | Style/UnlessElse: 1365 | Description: >- 1366 | Do not use unless with else. Rewrite these with the positive 1367 | case first. 1368 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-else-with-unless' 1369 | Enabled: true 1370 | 1371 | Style/UnneededCapitalW: 1372 | Description: 'Checks for %W when interpolation is not needed.' 1373 | Enabled: false 1374 | 1375 | Style/UnneededInterpolation: 1376 | Description: 'Checks for unnecessary interpolation.' 1377 | Enabled: true 1378 | 1379 | Style/UnneededPercentQ: 1380 | Description: 'Checks for %q/%Q when single quotes or double quotes would do.' 1381 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q' 1382 | Enabled: false 1383 | 1384 | Style/VariableInterpolation: 1385 | Description: >- 1386 | Don't interpolate global, instance and class variables 1387 | directly in strings. 1388 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#curlies-interpolate' 1389 | Enabled: false 1390 | 1391 | Style/WhenThen: 1392 | Description: 'Use when x then ... for one-line cases.' 1393 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#one-line-cases' 1394 | Enabled: false 1395 | 1396 | Style/WhileUntilDo: 1397 | Description: 'Checks for redundant do after while or until.' 1398 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-while-do' 1399 | Enabled: true 1400 | 1401 | Style/WhileUntilModifier: 1402 | Description: >- 1403 | Favor modifier while/until usage when you have a 1404 | single-line body. 1405 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier' 1406 | Enabled: true 1407 | 1408 | Style/WordArray: 1409 | Description: 'Use %w or %W for arrays of words.' 1410 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w' 1411 | MinSize: 3 1412 | Enabled: true 1413 | 1414 | Style/ZeroLengthPredicate: 1415 | Description: 'Checks for numeric comparisons that can be replaced by predicate method, such as `empty?`.' 1416 | Enabled: true 1417 | --------------------------------------------------------------------------------