diff --git a/README.md b/README.md index ce4edb4..3f642eb 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ creates index with settings for each file from `indices` folder. - `rake elastics:migrate` (`.migrate`) puts mappings from `mappings` folder. -- `rake elastics:migrate full=true` (`.migrate!`) +- `rake elastics:migrate!` (`.migrate!`) performs full migration. - `rake elastics:reindex` (`.reindex`) diff --git a/lib/elastics/active_record/helper_methods.rb b/lib/elastics/active_record/helper_methods.rb index 0ed9621..7f2f3d0 100644 --- a/lib/elastics/active_record/helper_methods.rb +++ b/lib/elastics/active_record/helper_methods.rb @@ -10,26 +10,23 @@ def self.append_features(base) end module ClassMethods + # Performs `_search` request on type and instantiates result object. + # SearchResult is a default result class. It can be overriden with + # :result_class option. def search_elastics(data = {}, options = {}) - request = { - id: :_search, - body: data, - } - if routing = options[:routing] - request[:query] = {routing: routing} - end - SearchResult.new self, request_elastics(request), options + options[:result_class] ||= SearchResult + options[:model] = self + super end + # Finds items by ids and returns array in the order in which ids were given. + # Every missing record is replaced with `nil` in the result. def find_all_ordered(ids) items_by_id = where(id: ids).index_by(&:id) ids.map { |i| items_by_id[i] } end - def elastics_mapping - request_elastics(method: :get, id: :_mapping) - end - + # Indexes all records in current scope. def index_all_elastics(*args) find_in_batches(*args) do |batch| index_batch_elastics(batch) diff --git a/lib/elastics/active_record/search_result.rb b/lib/elastics/active_record/search_result.rb index 1b55b7e..96fd79a 100644 --- a/lib/elastics/active_record/search_result.rb +++ b/lib/elastics/active_record/search_result.rb @@ -1,8 +1,8 @@ module Elastics module ActiveRecord class SearchResult < Result::Search - def initialize(model, response, options = {}) - @model = model + def initialize(response, options = {}) + @model = options[:model] super response, options end diff --git a/lib/elastics/model/helper_methods.rb b/lib/elastics/model/helper_methods.rb index 1e1e0be..4d6083c 100644 --- a/lib/elastics/model/helper_methods.rb +++ b/lib/elastics/model/helper_methods.rb @@ -21,18 +21,37 @@ def elastics_params } end + # Proxies #request method to elastics client with specified index & type. def request_elastics(params) elastics.request(elastics_params.merge!(params)) end + # Proxies #bulk method to elastics client with specified index & type. def bulk_elastics(params = {}, &block) elastics.bulk(elastics_params.merge!(params), &block) end + # Performs `_search` request on type and instantiates result object. + # Result::Search is a default result class. It can be overriden with + # :result_class option. + def search_elastics(data = {}, options = {}) + request = { + id: :_search, + body: data, + } + if routing = options[:routing] + request[:query] = {routing: routing} + end + result_class = options[:result_class] || Result::Search + result_class.new request_elastics(request), options + end + + # Performs `_refresh` request on index. def refresh_elastics request_elastics(method: :post, type: nil, id: :_refresh) end + # Indexes given records using batch API. def index_batch_elastics(batch) bulk_elastics do |bulk| batch.each do |record| @@ -41,8 +60,20 @@ def index_batch_elastics(batch) end end + # Reindexes all records. It requires #find_in_batches method to be defined. def reindex_elastics(options = {}) - raise 'Not implemented' + find_in_batches(options) do |batch| + index_batch_elastics(batch) + end + end + + # Deletes all records in type keeping its mapping using "Delete by query" API. + def clear_elastics + request_elastics method: :delete, id: :_query, body: {query: {match_all: {}}} + end + + def elastics_mapping + request_elastics(id: :_mapping) end end diff --git a/lib/elastics/query_helper.rb b/lib/elastics/query_helper.rb index ae9fdc6..8957180 100644 --- a/lib/elastics/query_helper.rb +++ b/lib/elastics/query_helper.rb @@ -1,11 +1,15 @@ module Elastics module QueryHelper + # Combines multiple filters into `and` filter. Returns unmodified input + # unless it was an array. def normalize_filters(filters) return filters unless filters.is_a?(Array) return filters[0] if 2 > filters.size {and: {filters: filters}} end + # Wraps given query into `filtered` query if filter is present. + # Also replaces empty query with `match_all`. def normalize_query(query, filters) filter = normalize_filters filters query ||= {match_all: {}} @@ -16,12 +20,20 @@ def normalize_query(query, filters) }} end + # Returns `term`(for scalar value) or `terms` (for array) query node + # for specified field. def terms_query(field, val, options = {}) if val.is_a?(Array) {terms: {field => val}.merge(options)} else - result = {term: {field => val}} + {term: {field => val}} end end + + # Returns `nil` if falsy value or empty array is given. Other way + # it returns term(s) query for it. + def terms_array_query(field, val, options = {}) + terms_query(field, val, options) if val && (!val.is_a?(Array) || val.any?) + end end end diff --git a/lib/elastics/tasks/mappings.rb b/lib/elastics/tasks/mappings.rb index 89e93b5..85d0355 100644 --- a/lib/elastics/tasks/mappings.rb +++ b/lib/elastics/tasks/mappings.rb @@ -26,10 +26,34 @@ def mappings flatten.sort. each_with_object({}) do |file, hash| name = File.basename file, '.yml' - hash[name] = YAML.load_file(file) + hash[name] = fix_mapping(name, YAML.load_file(file)) end end + # Adds missing type name in the top-level and updates properties definition. + # It allows to write + # + # properties: + # name: string + # project_id: integer + # + # instead of + # + # task: + # properties: + # name: + # type: string + # project_id: + # type: integer + def fix_mapping(name, mapping) + mapping = {name => mapping} unless mapping.keys == [name] + properties = mapping[name]['properties'] + properties && properties.each do |field, val| + properties[field] = {type: val} if val.is_a?(String) + end + mapping + end + def types @types ||= mappings.keys end diff --git a/lib/elastics/version.rb b/lib/elastics/version.rb index f3b462d..f933b45 100644 --- a/lib/elastics/version.rb +++ b/lib/elastics/version.rb @@ -1,5 +1,5 @@ module Elastics - VERSION = '0.4.0' + VERSION = '0.5.0' def self.gem_version Gem::Version.new VERSION diff --git a/lib/tasks/elastics.rake b/lib/tasks/elastics.rake index 4c522f1..c34d230 100644 --- a/lib/tasks/elastics.rake +++ b/lib/tasks/elastics.rake @@ -29,11 +29,11 @@ namespace 'elastics' do desc 'Creates indices and applies mappings. Full migration when param is present' task migrate: :load_config do - if ENV['full'] - Elastics::Tasks.migrate! @elastics_options - else - Elastics::Tasks.migrate @elastics_options - end + Elastics::Tasks.migrate @elastics_options + end + + task migrate!: :load_config do + Elastics::Tasks.migrate! @elastics_options end desc 'Reindex'