From a09ec4a4b627b589e349dffa8b1c2cf2b70abf88 Mon Sep 17 00:00:00 2001 From: Brian Alexander Date: Tue, 18 Mar 2014 13:12:36 -0600 Subject: [PATCH 001/582] [RAILS] Added displaying error count in the import Rake task Prior to this pull-request, these errors would be silently ignored and users might assume that the task had completed successfully. Now the number of errors is printed: $ bundle exec rake environment elasticsearch:import:model CLASS=User [IMPORT] 4 errors occurred [IMPORT] Done It would be nice to have more details about the errors, but this is an improvement over failing silently. Closes #48 --- .../lib/elasticsearch/rails/tasks/import.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb b/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb index 709164f51..10e912eaf 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb @@ -56,16 +56,17 @@ rescue NoMethodError; end end - klass.import force: ENV.fetch('FORCE', false), - batch_size: ENV.fetch('BATCH', 1000).to_i, - index: ENV.fetch('INDEX', nil), - type: ENV.fetch('TYPE', nil) do |response| + total_errors = klass.import force: ENV.fetch('FORCE', false), + batch_size: ENV.fetch('BATCH', 1000).to_i, + index: ENV.fetch('INDEX', nil), + type: ENV.fetch('TYPE', nil) do |response| pbar.inc response['items'].size if pbar STDERR.flush STDOUT.flush end pbar.finish if pbar + puts "[IMPORT] #{total_errors} errors occurred" unless total_errors.zero? puts '[IMPORT] Done' end From 619e5383cd3c707a31223aeebf13691ca995cb91 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Wed, 19 Mar 2014 17:55:45 +0100 Subject: [PATCH 002/582] [MODEL] Small typo fix in the README Thanks to @bobrenjc93 for the catch! Closes #26 --- elasticsearch-model/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 0061a457b..2dd0b27a6 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -97,8 +97,8 @@ all its functionality. To prevent polluting your model namespace, this functiona available via the `__elasticsearch__` class and instance level proxy methods; see the `Elasticsearch::Model::Proxy` class documentation for technical information. -The module will include important methods, such as `search`, into the includeing class or module -only when they haven't been defined already. Following two calls are thus functionally equivalent: +The module will include important methods, such as `search`, into the class or module only +when they haven't been defined already. Following two calls are thus functionally equivalent: ```ruby Article.__elasticsearch__.search 'fox' From e8a7f49aea479cd335e3afd62d15f52ab8ed6b6d Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 25 Mar 2014 15:12:06 +0100 Subject: [PATCH 003/582] [MODEL] Added callback in the `activerecord_associations.rb` example --- elasticsearch-model/examples/activerecord_associations.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/elasticsearch-model/examples/activerecord_associations.rb b/elasticsearch-model/examples/activerecord_associations.rb index aba804b8f..74713c649 100644 --- a/elasticsearch-model/examples/activerecord_associations.rb +++ b/elasticsearch-model/examples/activerecord_associations.rb @@ -68,6 +68,8 @@ class Category < ActiveRecord::Base class Author < ActiveRecord::Base has_many :authorships + after_update { self.authorships.each(&:touch) } + def full_name [first_name, last_name].compact.join(' ') end From 62ba1712e01f1edb9b786b910908306dffbf3079 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 25 Mar 2014 19:12:11 +0100 Subject: [PATCH 004/582] [RAILS] Add the description for the `03-expert.rb` template into the README --- elasticsearch-rails/README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/elasticsearch-rails/README.md b/elasticsearch-rails/README.md index bebdff4de..9c346e8f1 100644 --- a/elasticsearch-rails/README.md +++ b/elasticsearch-rails/README.md @@ -91,8 +91,15 @@ a custom `Article.search` method, result highlighting and [_Bootstrap_](http://g rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/02-pretty.rb ``` -NOTE: A third, much more complex template, demonstrating other features such as faceted navigation or - query suggestions is being worked on. +Run the same command with the `03-expert.rb` template to refactor the application into a more complex use case, +with couple of hundreds of The New York Times articles as the example content. +The template will extract the Elasticsearch integration into a `Searchable` "concern" module, +define complex mapping, custom serialization, implement faceted navigation and suggestions as a part of +a complex query, and add a _Sidekiq_-based worker for updating the index in the background. + +```bash +rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/03-expert.rb +``` ## TODO From a5ca35d10e14f6f3d0092885f4c9a9c55d10b0fb Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 25 Mar 2014 19:16:37 +0100 Subject: [PATCH 005/582] [RAILS] Added links to the Rails application templates in the README --- elasticsearch-rails/README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/elasticsearch-rails/README.md b/elasticsearch-rails/README.md index 9c346e8f1..586eb87a5 100644 --- a/elasticsearch-rails/README.md +++ b/elasticsearch-rails/README.md @@ -78,20 +78,24 @@ You should see the duration of the request to Elasticsearch as part of each log You can generate a fully working example Ruby on Rails application, with an `Article` model and a search form, to play with (it even downloads _Elasticsearch_ itself, generates the application skeleton and leaves you with -a _Git_ repository to explore the steps and the code): +a _Git_ repository to explore the steps and the code) with the +[`01-basic.rb`](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-rails/lib/rails/templates/01-basic.rb) template: ```bash rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/01-basic.rb ``` -Run the same command again, in the same folder, with the `02-pretty` template to add features such as -a custom `Article.search` method, result highlighting and [_Bootstrap_](http://getbootstrap.com) integration: +Run the same command again, in the same folder, with the +[`02-pretty`](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-rails/lib/rails/templates/02-pretty.rb) +template to add features such as a custom `Article.search` method, result highlighting and +[_Bootstrap_](http://getbootstrap.com) integration: ```bash rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/02-pretty.rb ``` -Run the same command with the `03-expert.rb` template to refactor the application into a more complex use case, +Run the same command with the [`03-expert.rb`](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-rails/lib/rails/templates/03-expert.rb) +template to refactor the application into a more complex use case, with couple of hundreds of The New York Times articles as the example content. The template will extract the Elasticsearch integration into a `Searchable` "concern" module, define complex mapping, custom serialization, implement faceted navigation and suggestions as a part of From f99b479a8fbba3af8fbea9de54ec28e8edc8d6e5 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 25 Mar 2014 19:17:01 +0100 Subject: [PATCH 006/582] [RAILS] Removed old TODOs --- elasticsearch-rails/README.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/elasticsearch-rails/README.md b/elasticsearch-rails/README.md index 586eb87a5..46fa8e724 100644 --- a/elasticsearch-rails/README.md +++ b/elasticsearch-rails/README.md @@ -105,15 +105,6 @@ a complex query, and add a _Sidekiq_-based worker for updating the index in the rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/03-expert.rb ``` -## TODO - -This is an initial release of the `elasticsearch-rails` library. Many more features are planned and/or -being worked on, such as: - -* Rake tasks for convenient (re)indexing your models from the command line -* Hooking into Rails' notification system to display Elasticsearch related statistics in the application log -* Instrumentation support for NewRelic integration - ## License This software is licensed under the Apache 2 license, quoted below. From cc90739c0252e839a2099c2fae789fd995217e7b Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Wed, 9 Apr 2014 15:13:49 +0200 Subject: [PATCH 007/582] [MODEL] Added `client.cluster.health wait_for_status: 'yellow'` to prevent "no shard available" errors on CI --- .../test/integration/active_record_import_test.rb | 1 + elasticsearch-model/test/integration/mongoid_basic_test.rb | 2 ++ 2 files changed, 3 insertions(+) diff --git a/elasticsearch-model/test/integration/active_record_import_test.rb b/elasticsearch-model/test/integration/active_record_import_test.rb index 3c1f7f19a..0283d98b9 100644 --- a/elasticsearch-model/test/integration/active_record_import_test.rb +++ b/elasticsearch-model/test/integration/active_record_import_test.rb @@ -26,6 +26,7 @@ class ::ImportArticle < ActiveRecord::Base ImportArticle.delete_all ImportArticle.__elasticsearch__.create_index! force: true + ImportArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' 100.times { |i| ImportArticle.create! title: "Test #{i}" } end diff --git a/elasticsearch-model/test/integration/mongoid_basic_test.rb b/elasticsearch-model/test/integration/mongoid_basic_test.rb index b95947e2f..fb2789d13 100644 --- a/elasticsearch-model/test/integration/mongoid_basic_test.rb +++ b/elasticsearch-model/test/integration/mongoid_basic_test.rb @@ -61,6 +61,7 @@ def as_indexed_json(options={}) MongoidArticle.create! title: 'Coding' MongoidArticle.__elasticsearch__.refresh_index! + MongoidArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' end should "index and find a document" do @@ -147,6 +148,7 @@ def as_indexed_json(options={}) MongoidArticle.delete_all 97.times { |i| MongoidArticle.create! title: "Test #{i}" } MongoidArticle.__elasticsearch__.create_index! force: true + MongoidArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' end should "import all the documents" do From e254fb6ad5a72899b3946770ab31b594ac52f17f Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Wed, 9 Apr 2014 18:25:08 +0200 Subject: [PATCH 008/582] [RAILS] Fixed the 01 and 02 templates on Rails 3.2.x Related: #73 --- elasticsearch-rails/lib/rails/templates/01-basic.rb | 5 +++-- elasticsearch-rails/lib/rails/templates/02-pretty.rb | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/elasticsearch-rails/lib/rails/templates/01-basic.rb b/elasticsearch-rails/lib/rails/templates/01-basic.rb index 4940b86ff..20bf32005 100644 --- a/elasticsearch-rails/lib/rails/templates/01-basic.rb +++ b/elasticsearch-rails/lib/rails/templates/01-basic.rb @@ -184,6 +184,7 @@ class Article < ActiveRecord::Base include Elasticsearch::Model include Elasticsearch::Model::Callbacks + #{'attr_accessible :title, :content, :published_on' if Rails::VERSION::STRING < '4'} end CODE @@ -236,7 +237,7 @@ def search end CODE -gsub_file 'test/controllers/articles_controller_test.rb', %r{setup do.*?end}m, <<-CODE +gsub_file "#{Rails::VERSION::STRING > '4' ? 'test/controllers' : 'test/functional'}/articles_controller_test.rb", %r{setup do.*?end}m, <<-CODE setup do @article = articles(:one) @@ -245,7 +246,7 @@ def search end CODE -inject_into_file 'test/controllers/articles_controller_test.rb', after: %r{test "should get index" do.*?end}m do +inject_into_file "#{Rails::VERSION::STRING > '4' ? 'test/controllers' : 'test/functional'}/articles_controller_test.rb", after: %r{test "should get index" do.*?end}m do <<-CODE diff --git a/elasticsearch-rails/lib/rails/templates/02-pretty.rb b/elasticsearch-rails/lib/rails/templates/02-pretty.rb index af3f1ec4c..cce289262 100644 --- a/elasticsearch-rails/lib/rails/templates/02-pretty.rb +++ b/elasticsearch-rails/lib/rails/templates/02-pretty.rb @@ -86,7 +86,7 @@ def self.search(query) end CODE -gsub_file "test/models/article_test.rb", %r{# test "the truth" do.*?# end}m, <<-CODE +gsub_file "#{Rails::VERSION::STRING > '4' ? 'test/models' : 'test/unit' }/article_test.rb", %r{# test "the truth" do.*?# end}m, <<-CODE test "has a search method delegating to __elasticsearch__" do Article.__elasticsearch__.expects(:search).with do |definition| @@ -98,7 +98,7 @@ def self.search(query) CODE git add: "app/models/article.rb" -git add: "test/models/article_test.rb" +git add: "test/**/article_test.rb" git commit: "-m 'Added an `Article.search` method'" # ----- Add loading Bootstrap assets -------------------------------------------------------------- From ef1f55cddf96ebbf03a1c723d3f9700340547160 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Wed, 9 Apr 2014 20:50:29 +0200 Subject: [PATCH 009/582] [TESTS] Added dependency on "minitest" to prevent test failures All the thanks goes to https://github.com/theforeman/smart-proxy/pull/84 --- elasticsearch-model/elasticsearch-model.gemspec | 1 + elasticsearch-rails/elasticsearch-rails.gemspec | 1 + 2 files changed, 2 insertions(+) diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index 2e9c81630..95ae2b0a8 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -38,6 +38,7 @@ Gem::Specification.new do |s| s.add_development_dependency "kaminari" # NOTE: Do not add Mongoid here, keep only in 3/4 files + s.add_development_dependency "minitest", "~> 4.0" s.add_development_dependency "shoulda-context" s.add_development_dependency "mocha" s.add_development_dependency "turn" diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index 43c2e5ae8..6aee99b11 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -31,6 +31,7 @@ Gem::Specification.new do |s| s.add_development_dependency "lograge" + s.add_development_dependency "minitest", "~> 4.0" s.add_development_dependency "shoulda-context" s.add_development_dependency "mocha" s.add_development_dependency "turn" From 26a3ba246968df736757dfa7ac1547c88fefe915 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 15 Apr 2014 19:07:08 +0200 Subject: [PATCH 010/582] Fixed, that models with custom `as_indexed_json` method are properly serialized during update Previously, the `update_document` method simply intercepted the changes to the model, via the `@__changed_attributes` variable, and used these directly. This caused models with a custom serialization method to be incorrectly serialized, namely unwanted attributes were added. This patch looks for `as_indexed_json` defined on the model, and when it finds it, filters the changed attributes through the keys. Closes #75 Related: * #59 * #57 * #52 * #40 * #37 * #5 --- .../lib/elasticsearch/model/indexing.rb | 8 ++- ...active_record_custom_serialization_test.rb | 61 +++++++++++++++++++ .../test/unit/indexing_test.rb | 38 ++++++++++++ 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 elasticsearch-model/test/integration/active_record_custom_serialization_test.rb diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index 84aebcc75..1a2beda4f 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -333,11 +333,17 @@ def delete_document(options={}) # def update_document(options={}) if changed_attributes = self.instance_variable_get(:@__changed_attributes) + attributes = if respond_to?(:as_indexed_json) + changed_attributes.select { |k,v| self.as_indexed_json.keys.include? k } + else + changed_attributes + end + client.update( { index: index_name, type: document_type, id: self.id, - body: { doc: changed_attributes } }.merge(options) + body: { doc: attributes } }.merge(options) ) else index_document(options) diff --git a/elasticsearch-model/test/integration/active_record_custom_serialization_test.rb b/elasticsearch-model/test/integration/active_record_custom_serialization_test.rb new file mode 100644 index 000000000..40b6bc4ce --- /dev/null +++ b/elasticsearch-model/test/integration/active_record_custom_serialization_test.rb @@ -0,0 +1,61 @@ +require 'test_helper' + +module Elasticsearch + module Model + class ActiveRecordCustomSerializationTest < Elasticsearch::Test::IntegrationTestCase + + class ::ArticleWithCustomSerialization < ActiveRecord::Base + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + mapping do + indexes :title + end + + def as_indexed_json(options={}) + as_json(options.merge root: false).slice('title') + end + end + + context "ActiveRecord model with custom JSON serialization" do + setup do + ActiveRecord::Schema.define(:version => 1) do + create_table ArticleWithCustomSerialization.table_name do |t| + t.string :title + t.string :status + end + end + + ArticleWithCustomSerialization.delete_all + ArticleWithCustomSerialization.__elasticsearch__.create_index! force: true + end + + should "index only the title attribute when creating" do + ArticleWithCustomSerialization.create! title: 'Test', status: 'green' + + a = ArticleWithCustomSerialization.__elasticsearch__.client.get \ + index: 'article_with_custom_serializations', + type: 'article_with_custom_serialization', + id: '1' + + assert_equal( { 'title' => 'Test' }, a['_source'] ) + end + + should "index only the title attribute when updating" do + ArticleWithCustomSerialization.create! title: 'Test', status: 'green' + + article = ArticleWithCustomSerialization.first + article.update_attributes title: 'UPDATED', status: 'red' + + a = ArticleWithCustomSerialization.__elasticsearch__.client.get \ + index: 'article_with_custom_serializations', + type: 'article_with_custom_serialization', + id: '1' + + assert_equal( { 'title' => 'UPDATED' }, a['_source'] ) + end + end + + end + end +end diff --git a/elasticsearch-model/test/unit/indexing_test.rb b/elasticsearch-model/test/unit/indexing_test.rb index a2758fa6a..d5f1b2687 100644 --- a/elasticsearch-model/test/unit/indexing_test.rb +++ b/elasticsearch-model/test/unit/indexing_test.rb @@ -129,6 +129,25 @@ def changes end end + class ::DummyIndexingModelWithCallbacksAndCustomAsIndexedJson + extend Elasticsearch::Model::Indexing::ClassMethods + include Elasticsearch::Model::Indexing::InstanceMethods + + def self.before_save(&block) + (@callbacks ||= {})[block.hash] = block + end + + def changed_attributes; [:foo, :bar]; end + + def changes + {:foo => ['A', 'B'], :bar => ['C', 'D']} + end + + def as_indexed_json(options={}) + { :foo => 'B' } + end + end + should "register before_save callback when included" do ::DummyIndexingModelWithCallbacks.expects(:before_save).returns(true) ::DummyIndexingModelWithCallbacks.__send__ :include, Elasticsearch::Model::Indexing::InstanceMethods @@ -251,6 +270,25 @@ def changes instance.update_document end + + should "exclude attributes not contained in custom as_indexed_json during partial update" do + client = mock('client') + instance = ::DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new + + # Set the fake `changes` hash + instance.instance_variable_set(:@__changed_attributes, {foo: 'B', bar: 'D' }) + + client.expects(:update).with do |payload| + assert_equal({foo: 'B'}, payload[:body][:doc]) + end + + instance.expects(:client).returns(client) + instance.expects(:index_name).returns('foo') + instance.expects(:document_type).returns('bar') + instance.expects(:id).returns('1') + + instance.update_document + end end context "Re-creating the index" do From f5708eb584c76e11788c15c03d489c481ab1fa58 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Wed, 16 Apr 2014 18:41:44 +0200 Subject: [PATCH 011/582] [TEST] Added an integration test for combining pagination with scopes See: http://guides.rubyonrails.org/active_record_querying.html#scopes Related: #16 --- .../active_record_pagination_test.rb | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/elasticsearch-model/test/integration/active_record_pagination_test.rb b/elasticsearch-model/test/integration/active_record_pagination_test.rb index 51c79d468..11f176086 100644 --- a/elasticsearch-model/test/integration/active_record_pagination_test.rb +++ b/elasticsearch-model/test/integration/active_record_pagination_test.rb @@ -4,9 +4,11 @@ module Elasticsearch module Model class ActiveRecordPaginationTest < Elasticsearch::Test::IntegrationTestCase - class ::Article < ActiveRecord::Base + class ::ArticleForPagination < ActiveRecord::Base include Elasticsearch::Model + scope :published, -> { where(published: true) } + settings index: { number_of_shards: 1, number_of_replicas: 0 } do mapping do indexes :title, type: 'string', analyzer: 'snowball' @@ -20,23 +22,26 @@ class ::Article < ActiveRecord::Base context "ActiveRecord pagination" do setup do ActiveRecord::Schema.define(:version => 1) do - create_table :articles do |t| + create_table ::ArticleForPagination.table_name do |t| t.string :title t.datetime :created_at, :default => 'NOW()' + t.boolean :published end end - Article.delete_all - Article.__elasticsearch__.create_index! force: true + ArticleForPagination.delete_all + ArticleForPagination.__elasticsearch__.create_index! force: true - 68.times do |i| ::Article.create! title: "Test #{i}" end + 68.times do |i| + ::ArticleForPagination.create! title: "Test #{i}", published: (i % 2 == 0) + end - Article.import - Article.__elasticsearch__.refresh_index! + ArticleForPagination.import + ArticleForPagination.__elasticsearch__.refresh_index! end should "be on the first page by default" do - records = Article.search('title:test').page(1).records + records = ArticleForPagination.search('title:test').page(1).records assert_equal 25, records.size assert_equal 1, records.current_page @@ -50,7 +55,7 @@ class ::Article < ActiveRecord::Base end should "load next page" do - records = Article.search('title:test').page(2).records + records = ArticleForPagination.search('title:test').page(2).records assert_equal 25, records.size assert_equal 2, records.current_page @@ -64,7 +69,7 @@ class ::Article < ActiveRecord::Base end should "load last page" do - records = Article.search('title:test').page(3).records + records = ArticleForPagination.search('title:test').page(3).records assert_equal 18, records.size assert_equal 3, records.current_page @@ -78,7 +83,7 @@ class ::Article < ActiveRecord::Base end should "not load invalid page" do - records = Article.search('title:test').page(6).records + records = ArticleForPagination.search('title:test').page(6).records assert_equal 0, records.size assert_equal 6, records.current_page @@ -91,16 +96,22 @@ class ::Article < ActiveRecord::Base assert records.out_of_range?, "Should be out of range" end + should "be combined with scopes" do + records = ArticleForPagination.search('title:test').page(2).records.published + assert records.all? { |r| r.published? } + assert_equal 12, records.size + end + context "with specific model settings" do teardown do - Article.instance_variable_set(:@_default_per_page, nil) + ArticleForPagination.instance_variable_set(:@_default_per_page, nil) end end should "respect paginates_per" do - Article.paginates_per 50 + ArticleForPagination.paginates_per 50 - assert_equal 50, Article.search('*').page(1).records.size + assert_equal 50, ArticleForPagination.search('*').page(1).records.size end end From fa37bc6d4c74a21f31e15e85c3557ea67de88965 Mon Sep 17 00:00:00 2001 From: joker1007 Date: Sat, 15 Mar 2014 01:31:02 +0900 Subject: [PATCH 012/582] Fixed the incorrect behaviour of limit() and page() methods Previously: When klass.default_per_page is 25, response.limit(35).page(3) => :size is back to 25, :from is 25 * 2 response.page(3).limit(35) => :size is 35, but :from is 25 * 2 After this patch: response.limit(35).page(3) => :size is 35, :from is 35 * 2 response.page(3).limit(35) => :size is 35, :from is 35 * 2 Closes #42 Related: #72, #55 --- .../model/response/pagination.rb | 14 +++++++--- .../active_record_pagination_test.rb | 20 +++++++++++--- .../test/unit/response_pagination_test.rb | 27 +++++++++++++++++++ 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb index fa312fdde..d3008906c 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb @@ -30,8 +30,12 @@ def #{::Kaminari.config.page_method_name}(num=nil) @results = nil @records = nil @response = nil - self.search.definition.update size: klass.default_per_page, - from: klass.default_per_page * ([num.to_i, 1].max - 1) + @page = [num.to_i, 1].max + @per_page ||= klass.default_per_page + + self.search.definition.update size: @per_page, + from: @per_page * (@page - 1) + self end RUBY @@ -69,7 +73,10 @@ def limit(value) @results = nil @records = nil @response = nil - search.definition.update :size => value + @per_page = value + + search.definition.update :size => @per_page + search.definition.update :from => @per_page * (@page - 1) if @page self end @@ -79,6 +86,7 @@ def offset(value) @results = nil @records = nil @response = nil + @page = nil search.definition.update :from => value self end diff --git a/elasticsearch-model/test/integration/active_record_pagination_test.rb b/elasticsearch-model/test/integration/active_record_pagination_test.rb index 11f176086..f0778896b 100644 --- a/elasticsearch-model/test/integration/active_record_pagination_test.rb +++ b/elasticsearch-model/test/integration/active_record_pagination_test.rb @@ -102,16 +102,28 @@ class ::ArticleForPagination < ActiveRecord::Base assert_equal 12, records.size end + should "set the limit per request" do + records = ArticleForPagination.search('title:test').limit(50).page(2).records + + assert_equal 18, records.size + assert_equal 2, records.current_page + assert_equal 1, records.prev_page + assert_equal nil, records.next_page + assert_equal 2, records.total_pages + + assert records.last_page?, "Should be the last page" + end + context "with specific model settings" do teardown do ArticleForPagination.instance_variable_set(:@_default_per_page, nil) end - end - should "respect paginates_per" do - ArticleForPagination.paginates_per 50 + should "respect paginates_per" do + ArticleForPagination.paginates_per 50 - assert_equal 50, ArticleForPagination.search('*').page(1).records.size + assert_equal 50, ArticleForPagination.search('*').page(1).records.size + end end end diff --git a/elasticsearch-model/test/unit/response_pagination_test.rb b/elasticsearch-model/test/unit/response_pagination_test.rb index 856e2ed3c..17eb50efa 100644 --- a/elasticsearch-model/test/unit/response_pagination_test.rb +++ b/elasticsearch-model/test/unit/response_pagination_test.rb @@ -103,6 +103,33 @@ def self.document_type; 'bar'; end end end + context "with the page() and limit() methods" do + setup do + @response.records + @response.results + end + + should "set the values" do + @response.page(3).limit(35) + assert_equal 35, @response.search.definition[:size] + assert_equal 70, @response.search.definition[:from] + end + + should "set the values when limit is called first" do + @response.limit(35).page(3) + assert_equal 35, @response.search.definition[:size] + assert_equal 70, @response.search.definition[:from] + end + + should "reset the instance variables" do + @response.page(3).limit(35) + + assert_nil @response.instance_variable_get(:@response) + assert_nil @response.instance_variable_get(:@records) + assert_nil @response.instance_variable_get(:@results) + end + end + context "offset setter" do setup do @response.records From e58ebfefcf9b3f1a00a2194f4918dd2db93a50b1 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Wed, 16 Apr 2014 20:24:03 +0200 Subject: [PATCH 013/582] Changed, that the default `limit_value` for pagination is based on `default_per_page` Closes #68 --- .../lib/elasticsearch/model/response/pagination.rb | 2 +- elasticsearch-model/test/unit/response_pagination_test.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb index d3008906c..923ef4e6e 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb @@ -50,7 +50,7 @@ def limit_value when search.definition[:size] search.definition[:size] else - 0 + search.klass.default_per_page end end diff --git a/elasticsearch-model/test/unit/response_pagination_test.rb b/elasticsearch-model/test/unit/response_pagination_test.rb index 17eb50efa..e6e7d2db2 100644 --- a/elasticsearch-model/test/unit/response_pagination_test.rb +++ b/elasticsearch-model/test/unit/response_pagination_test.rb @@ -13,7 +13,7 @@ def self.document_type; 'bar'; end 'hits' => { 'total' => 100, 'hits' => (1..100).to_a.map { |i| { _id: i } } } } setup do - @search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, '*' + @search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, '*' @response = Elasticsearch::Model::Response::Response.new ModelClass, @search, RESPONSE @response.klass.stubs(:client).returns mock('client') end @@ -62,7 +62,7 @@ def self.document_type; 'bar'; end context "limit/offset readers" do should "return the default" do - assert_equal 0, @response.limit_value + assert_equal Kaminari.config.default_per_page, @response.limit_value assert_equal 0, @response.offset_value end From 8f543ad826cf0e3efdbbc42bc5206eb7b9e461ad Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Wed, 16 Apr 2014 20:57:27 +0200 Subject: [PATCH 014/582] Removed the extraction of `from` and `size` from request body in Kaminari methods Closes #16 --- .../lib/elasticsearch/model/response/pagination.rb | 4 ---- .../test/unit/response_pagination_test.rb | 9 +++++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb index 923ef4e6e..212a532c2 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb @@ -45,8 +45,6 @@ def #{::Kaminari.config.page_method_name}(num=nil) # def limit_value case - when search.definition[:body] && search.definition[:body][:size] - search.definition[:body][:size] when search.definition[:size] search.definition[:size] else @@ -58,8 +56,6 @@ def limit_value # def offset_value case - when search.definition[:body] && search.definition[:body][:from] - search.definition[:body][:from] when search.definition[:from] search.definition[:from] else diff --git a/elasticsearch-model/test/unit/response_pagination_test.rb b/elasticsearch-model/test/unit/response_pagination_test.rb index e6e7d2db2..7394d4913 100644 --- a/elasticsearch-model/test/unit/response_pagination_test.rb +++ b/elasticsearch-model/test/unit/response_pagination_test.rb @@ -74,12 +74,13 @@ def self.document_type; 'bar'; end assert_equal 50, @response.offset_value end - should "return the value from body" do - search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, { query: { match_all: {} }, from: 10, size: 50 } + should "ignore the value from request body" do + search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, + { query: { match_all: {} }, from: 333, size: 999 } @response = Elasticsearch::Model::Response::Response.new ModelClass, search, RESPONSE - assert_equal 50, @response.limit_value - assert_equal 10, @response.offset_value + assert_equal Kaminari.config.default_per_page, @response.limit_value + assert_equal 0, @response.offset_value end end From 43f34da99fbd256ea2435b1d4c2e21aed5ad6667 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Thu, 17 Apr 2014 09:15:15 +0200 Subject: [PATCH 015/582] Added CHANGELOG files --- elasticsearch-model/CHANGELOG.md | 1 + elasticsearch-rails/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) create mode 100644 elasticsearch-model/CHANGELOG.md create mode 100644 elasticsearch-rails/CHANGELOG.md diff --git a/elasticsearch-model/CHANGELOG.md b/elasticsearch-model/CHANGELOG.md new file mode 100644 index 000000000..4e3adb6d2 --- /dev/null +++ b/elasticsearch-model/CHANGELOG.md @@ -0,0 +1 @@ +## 0.1.0 (Initial Version) diff --git a/elasticsearch-rails/CHANGELOG.md b/elasticsearch-rails/CHANGELOG.md new file mode 100644 index 000000000..4e3adb6d2 --- /dev/null +++ b/elasticsearch-rails/CHANGELOG.md @@ -0,0 +1 @@ +## 0.1.0 (Initial Version) From 8d6db59ce80d5f66e42bc923e0c2040a1a3a698a Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Thu, 17 Apr 2014 09:35:07 +0200 Subject: [PATCH 016/582] Release 0.1.1 --- elasticsearch-model/CHANGELOG.md | 5 +++++ elasticsearch-model/lib/elasticsearch/model/version.rb | 2 +- elasticsearch-rails/CHANGELOG.md | 7 ++++++- elasticsearch-rails/lib/elasticsearch/rails/version.rb | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/CHANGELOG.md b/elasticsearch-model/CHANGELOG.md index 4e3adb6d2..8ecdb6fd7 100644 --- a/elasticsearch-model/CHANGELOG.md +++ b/elasticsearch-model/CHANGELOG.md @@ -1 +1,6 @@ +## 0.1.1 + +* Improved documentation and tests +* Fixed Kaminari implementation bugs and inconsistencies + ## 0.1.0 (Initial Version) diff --git a/elasticsearch-model/lib/elasticsearch/model/version.rb b/elasticsearch-model/lib/elasticsearch/model/version.rb index f1c6ffe1c..d2228a2c7 100644 --- a/elasticsearch-model/lib/elasticsearch/model/version.rb +++ b/elasticsearch-model/lib/elasticsearch/model/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Model - VERSION = "0.1.0" + VERSION = "0.1.1" end end diff --git a/elasticsearch-rails/CHANGELOG.md b/elasticsearch-rails/CHANGELOG.md index 4e3adb6d2..18a3035b5 100644 --- a/elasticsearch-rails/CHANGELOG.md +++ b/elasticsearch-rails/CHANGELOG.md @@ -1 +1,6 @@ -## 0.1.0 (Initial Version) +## 0.1.1 + +* Improved the Rake tasks +* Improved the example application templates + +## 0.1.1 (Initial Version) diff --git a/elasticsearch-rails/lib/elasticsearch/rails/version.rb b/elasticsearch-rails/lib/elasticsearch/rails/version.rb index 1cbcedc0e..c0fb4a9aa 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/version.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Rails - VERSION = "0.1.0" + VERSION = "0.1.1" end end From 2aec359d78178951e9d28c323448eb670af25cb4 Mon Sep 17 00:00:00 2001 From: Jon Roberts Date: Wed, 2 Apr 2014 17:43:48 -0500 Subject: [PATCH 017/582] [MODEL] Delegate `Hashie::Mash` query/existence methods to @result._source Closes: #64 --- .../lib/elasticsearch/model/response/result.rb | 16 ++++++++++++---- .../test/unit/response_result_test.rb | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/result.rb b/elasticsearch-model/lib/elasticsearch/model/response/result.rb index 9cb7ccb82..9cd76a618 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/result.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/result.rb @@ -21,10 +21,12 @@ def initialize(attributes={}) # def method_missing(method_name, *arguments) case - when @result.respond_to?(method_name.to_sym) - @result.__send__ method_name.to_sym, *arguments - when @result._source && @result._source.respond_to?(method_name.to_sym) - @result._source.__send__ method_name.to_sym, *arguments + when method_name.to_s.end_with?('?') + delegate_to_source(method_name, *arguments) + when @result.respond_to?(method_name) + @result.__send__ method_name, *arguments + when @result._source && @result._source.respond_to?(method_name) + delegate_to_source(method_name, *arguments) else super end @@ -44,6 +46,12 @@ def as_json(options={}) # TODO: #to_s, #inspect, with support for Pry + private + + def delegate_to_source(method, *args) + @result._source.__send__ method, *args + end + end end end diff --git a/elasticsearch-model/test/unit/response_result_test.rb b/elasticsearch-model/test/unit/response_result_test.rb index e82b4758e..0f2b61d84 100644 --- a/elasticsearch-model/test/unit/response_result_test.rb +++ b/elasticsearch-model/test/unit/response_result_test.rb @@ -27,6 +27,20 @@ class Elasticsearch::Model::ResultTest < Test::Unit::TestCase assert_equal 'baz', result.bar end + should "delegate existence method calls to `_source`" do + result = Elasticsearch::Model::Response::Result.new foo: 'bar', _source: { bar: {baz: 'blargh'} } + + assert_respond_to result, :bar? + assert_respond_to result._source, :bar? + + assert_equal true, result._source.bar? + assert_equal true, result.bar? + assert_equal false, result.baz? + + assert_equal true, result.bar.baz? + assert_equal false, result.bar.boo? + end + should "delegate methods to @result" do result = Elasticsearch::Model::Response::Result.new foo: 'bar' From cc53850add08e761b1d31fe72b7164db53347d06 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Thu, 17 Apr 2014 11:02:54 +0200 Subject: [PATCH 018/582] [MODEL] Minor improvements and retouches to the query methods (`result.foo?`) implementation and tests Closes ##70 Related: #41, #64 --- .../elasticsearch/model/response/result.rb | 21 +++++--------- .../integration/active_record_basic_test.rb | 13 +++++++++ .../test/unit/response_result_test.rb | 29 +++++++++++++------ 3 files changed, 40 insertions(+), 23 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/result.rb b/elasticsearch-model/lib/elasticsearch/model/response/result.rb index 9cd76a618..ca994f421 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/result.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/result.rb @@ -19,14 +19,14 @@ def initialize(attributes={}) # Delegate methods to `@result` or `@result._source` # - def method_missing(method_name, *arguments) + def method_missing(name, *arguments) case - when method_name.to_s.end_with?('?') - delegate_to_source(method_name, *arguments) - when @result.respond_to?(method_name) - @result.__send__ method_name, *arguments - when @result._source && @result._source.respond_to?(method_name) - delegate_to_source(method_name, *arguments) + when name.to_s.end_with?('?') + @result.__send__(name, *arguments) || ( @result._source && @result._source.__send__(name, *arguments) ) + when @result.respond_to?(name) + @result.__send__ name, *arguments + when @result._source && @result._source.respond_to?(name) + @result._source.__send__ name, *arguments else super end @@ -45,13 +45,6 @@ def as_json(options={}) end # TODO: #to_s, #inspect, with support for Pry - - private - - def delegate_to_source(method, *args) - @result._source.__send__ method, *args - end - end end end diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index f17d9807e..8ac7e89e1 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -52,6 +52,19 @@ class ::Article < ActiveRecord::Base assert_equal 'Test', response.records.first.title end + should "provide access to result" do + response = Article.search query: { match: { title: 'test' } }, highlight: { fields: { title: {} } } + + assert_equal 'Test', response.results.first.title + + assert_equal true, response.results.first.title? + assert_equal false, response.results.first.boo? + + assert_equal true, response.results.first.highlight? + assert_equal true, response.results.first.highlight.title? + assert_equal false, response.results.first.highlight.boo? + end + should "iterate over results" do response = Article.search('title:test') diff --git a/elasticsearch-model/test/unit/response_result_test.rb b/elasticsearch-model/test/unit/response_result_test.rb index 0f2b61d84..b9267b664 100644 --- a/elasticsearch-model/test/unit/response_result_test.rb +++ b/elasticsearch-model/test/unit/response_result_test.rb @@ -28,25 +28,26 @@ class Elasticsearch::Model::ResultTest < Test::Unit::TestCase end should "delegate existence method calls to `_source`" do - result = Elasticsearch::Model::Response::Result.new foo: 'bar', _source: { bar: {baz: 'blargh'} } + result = Elasticsearch::Model::Response::Result.new foo: 'bar', _source: { bar: { bam: 'baz' } } - assert_respond_to result, :bar? assert_respond_to result._source, :bar? + assert_respond_to result, :bar? - assert_equal true, result._source.bar? - assert_equal true, result.bar? - assert_equal false, result.baz? + assert_equal true, result._source.bar? + assert_equal true, result.bar? + assert_equal false, result.boo? - assert_equal true, result.bar.baz? + assert_equal true, result.bar.bam? assert_equal false, result.bar.boo? end should "delegate methods to @result" do result = Elasticsearch::Model::Response::Result.new foo: 'bar' - assert_equal 'bar', result.foo - assert_equal 'bar', result.fetch('foo') - assert_equal 'moo', result.fetch('NOT_EXIST', 'moo') + assert_equal 'bar', result.foo + assert_equal 'bar', result.fetch('foo') + assert_equal 'moo', result.fetch('NOT_EXIST', 'moo') + assert_equal ['foo'], result.keys assert_respond_to result, :to_hash assert_equal({'foo' => 'bar'}, result.to_hash) @@ -54,6 +55,16 @@ class Elasticsearch::Model::ResultTest < Test::Unit::TestCase assert_raise(NoMethodError) { result.does_not_exist } end + should "delegate existence method calls to @result" do + result = Elasticsearch::Model::Response::Result.new foo: 'bar', _source: { bar: 'bam' } + assert_respond_to result, :foo? + + assert_equal true, result.foo? + assert_equal false, result.boo? + assert_equal false, result._source.foo? + assert_equal false, result._source.boo? + end + should "delegate as_json to @result even when ActiveSupport changed half of Ruby" do require 'active_support/json/encoding' result = Elasticsearch::Model::Response::Result.new foo: 'bar' From 6516521844c238f9517c3200097976eaeb7043d2 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Sun, 27 Apr 2014 18:04:49 +0200 Subject: [PATCH 019/582] [MODEL] Added, that exception is raised when `type` is not passed to Mappings#new --- elasticsearch-model/lib/elasticsearch/model/indexing.rb | 2 ++ elasticsearch-model/test/unit/indexing_test.rb | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index 1a2beda4f..360b8ea85 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -37,6 +37,8 @@ class Mappings attr_accessor :options def initialize(type, options={}) + raise ArgumentError, "`type` is missing" if type.nil? + @type = type @options = options @mapping = {} diff --git a/elasticsearch-model/test/unit/indexing_test.rb b/elasticsearch-model/test/unit/indexing_test.rb index d5f1b2687..57d121d61 100644 --- a/elasticsearch-model/test/unit/indexing_test.rb +++ b/elasticsearch-model/test/unit/indexing_test.rb @@ -47,6 +47,12 @@ def self.foo assert_instance_of Elasticsearch::Model::Indexing::Mappings, DummyIndexingModel.mappings end + should "raise an exception when not passed type" do + assert_raise ArgumentError do + Elasticsearch::Model::Indexing::Mappings.new + end + end + should "be convertible to hash" do mappings = Elasticsearch::Model::Indexing::Mappings.new :mytype, { foo: 'bar' } assert_equal( { :mytype => { foo: 'bar', :properties => {} } }, mappings.to_hash ) From fe16964cfad8f1860917eca2aac34d821679796c Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 6 May 2014 13:22:01 +0200 Subject: [PATCH 020/582] [MODEL] Added information about converting the results to Array with `to_a` Closes #94 --- elasticsearch-model/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 2dd0b27a6..b907e6d52 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -194,6 +194,13 @@ response.any? { |r| r.title =~ /fox|dog/ } # => true ``` +To use `Array`'s methods (including any _ActiveSupport_ extensions), just call `to_a` on the object: + +```ruby +response.to_a.last.title +# "Fast black dogs" +``` + #### Search results as database records Instead of returning documents from Elasticsearch, the `records` method will return a collection From 9b0e0d01bc514f40b31090163ce3ba23e8ede0db Mon Sep 17 00:00:00 2001 From: Aaron Rustad Date: Mon, 5 May 2014 16:23:13 -0600 Subject: [PATCH 021/582] [MODEL] Allow passing an ActiveRecord scope to the `import` method Article.import scope: 'published' Related: #104 --- .../model/adapters/active_record.rb | 6 +++++- .../lib/elasticsearch/model/importing.rb | 4 ++++ .../test/unit/adapter_active_record_test.rb | 21 ++++++++++++++++--- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb index e589a5830..3bec65b57 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb @@ -83,7 +83,11 @@ module Importing # @see http://api.rubyonrails.org/classes/ActiveRecord/Batches.html ActiveRecord::Batches.find_in_batches # def __find_in_batches(options={}, &block) - find_in_batches(options) do |batch| + named_scope = options.delete(:scope) + + scope = named_scope ? self.send(named_scope) : self + + scope.find_in_batches(options) do |batch| batch_for_bulk = batch.map { |a| { index: { _id: a.id, data: a.__elasticsearch__.as_indexed_json } } } yield batch_for_bulk end diff --git a/elasticsearch-model/lib/elasticsearch/model/importing.rb b/elasticsearch-model/lib/elasticsearch/model/importing.rb index 56063f4f2..f67113f35 100644 --- a/elasticsearch-model/lib/elasticsearch/model/importing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/importing.rb @@ -64,6 +64,10 @@ module ClassMethods # # Article.import index: 'my-new-index', type: 'my-other-type' # + # @example Pass an ActiveRecord scope to limit the imported records + # + # Article.import scope: 'published' + # def import(options={}, &block) errors = 0 refresh = options.delete(:refresh) || false diff --git a/elasticsearch-model/test/unit/adapter_active_record_test.rb b/elasticsearch-model/test/unit/adapter_active_record_test.rb index 80df07d71..fb6a70dac 100644 --- a/elasticsearch-model/test/unit/adapter_active_record_test.rb +++ b/elasticsearch-model/test/unit/adapter_active_record_test.rb @@ -82,13 +82,28 @@ def ids end context "Importing" do + setup do + DummyClassForActiveRecord.__send__ :extend, Elasticsearch::Model::Adapter::ActiveRecord::Importing + end + + should "raise an exception when passing an invalid scope" do + assert_raise NoMethodError do + DummyClassForActiveRecord.__find_in_batches(scope: :not_found_method) do; end + end + end + should "implement the __find_in_batches method" do DummyClassForActiveRecord.expects(:find_in_batches).returns([]) - - DummyClassForActiveRecord.__send__ :extend, Elasticsearch::Model::Adapter::ActiveRecord::Importing DummyClassForActiveRecord.__find_in_batches do; end end - end + should "limit the relation to a specific scope" do + DummyClassForActiveRecord.expects(:find_in_batches).returns([]) + DummyClassForActiveRecord.expects(:published).returns(DummyClassForActiveRecord) + + DummyClassForActiveRecord.__find_in_batches(scope: :published) do; end + end + + end end end From 90d0055f3a0d6a9c762220930339c8f802c7fc69 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 6 May 2014 13:45:55 +0200 Subject: [PATCH 022/582] [MODEL] Added an integration test for the ActiveRecord scope support for import Related: #104 --- .../integration/active_record_import_test.rb | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/test/integration/active_record_import_test.rb b/elasticsearch-model/test/integration/active_record_import_test.rb index 0283d98b9..4fc6f6aad 100644 --- a/elasticsearch-model/test/integration/active_record_import_test.rb +++ b/elasticsearch-model/test/integration/active_record_import_test.rb @@ -7,9 +7,12 @@ class ActiveRecordImportIntegrationTest < Elasticsearch::Test::IntegrationTestCa class ::ImportArticle < ActiveRecord::Base include Elasticsearch::Model + scope :popular, -> { where('views >= 50') } + mapping do indexes :title, type: 'string' indexes :views, type: 'integer' + indexes :numeric, type: 'integer' indexes :created_at, type: 'date' end end @@ -19,7 +22,8 @@ class ::ImportArticle < ActiveRecord::Base ActiveRecord::Schema.define(:version => 1) do create_table :import_articles do |t| t.string :title - t.string :views # For the sake of invalid data sent to Elasticsearch + t.integer :views + t.string :numeric # For the sake of invalid data sent to Elasticsearch t.datetime :created_at, :default => 'NOW()' end end @@ -28,7 +32,7 @@ class ::ImportArticle < ActiveRecord::Base ImportArticle.__elasticsearch__.create_index! force: true ImportArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' - 100.times { |i| ImportArticle.create! title: "Test #{i}" } + 100.times { |i| ImportArticle.create! title: "Test #{i}", views: i } end should "import all the documents" do @@ -49,8 +53,17 @@ class ::ImportArticle < ActiveRecord::Base assert_equal 100, ImportArticle.search('*').results.total end + should "import only documents from a specific scope" do + assert_equal 100, ImportArticle.count + + assert_equal 0, ImportArticle.import(scope: 'popular') + + ImportArticle.__elasticsearch__.refresh_index! + assert_equal 50, ImportArticle.search('*').results.total + end + should "report and not store/index invalid documents" do - ImportArticle.create! title: "Test INVALID", views: "INVALID" + ImportArticle.create! title: "Test INVALID", numeric: "INVALID" assert_equal 101, ImportArticle.count From 14abe9626b511538b1546b0e8f74c31f4206a493 Mon Sep 17 00:00:00 2001 From: Aaron Rustad Date: Mon, 5 May 2014 16:23:13 -0600 Subject: [PATCH 023/582] [RAILS] Allow passing an ActiveRecord scope to the importing Rake task $ bundle exec rake environment elasticsearch:import:model CLASS='Article' SCOPE='published' Closes #104 --- elasticsearch-rails/README.md | 8 ++++++++ .../lib/elasticsearch/rails/tasks/import.rb | 6 +++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/elasticsearch-rails/README.md b/elasticsearch-rails/README.md index 46fa8e724..476054938 100644 --- a/elasticsearch-rails/README.md +++ b/elasticsearch-rails/README.md @@ -40,6 +40,14 @@ To import the records from your `Article` model, run: $ bundle exec rake environment elasticsearch:import:model CLASS='Article' ``` +To limit the imported records to a certain +ActiveRecord [scope](http://guides.rubyonrails.org/active_record_querying.html#scopes), +pass it to the task: + +```bash +$ bundle exec rake environment elasticsearch:import:model CLASS='Article' SCOPE='published' +``` + Run this command to display usage instructions: ```bash diff --git a/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb b/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb index 10e912eaf..91f9ca10d 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb @@ -35,6 +35,9 @@ Set target index name: $ rake environment elasticsearch:import:model CLASS='Article' INDEX='articles-new' + + Pass an ActiveRecord scope to limit the imported records: + $ rake environment elasticsearch:import:model CLASS='Article' SCOPE='published' DESC task :model do if ENV['CLASS'].to_s == '' @@ -59,7 +62,8 @@ total_errors = klass.import force: ENV.fetch('FORCE', false), batch_size: ENV.fetch('BATCH', 1000).to_i, index: ENV.fetch('INDEX', nil), - type: ENV.fetch('TYPE', nil) do |response| + type: ENV.fetch('TYPE', nil), + scope: ENV.fetch('SCOPE', nil) do |response| pbar.inc response['items'].size if pbar STDERR.flush STDOUT.flush From 9bbeaddf6b65fc6d3682cd81c4504ae248204f3a Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 6 May 2014 15:04:53 +0200 Subject: [PATCH 024/582] [MODEL] Use `__send__`, not `send` in ActiveRecord's `__find_in_batches` Fixes 9b0e0d0 --- .../lib/elasticsearch/model/adapters/active_record.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb index 3bec65b57..c154510a4 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb @@ -85,7 +85,7 @@ module Importing def __find_in_batches(options={}, &block) named_scope = options.delete(:scope) - scope = named_scope ? self.send(named_scope) : self + scope = named_scope ? self.__send__(named_scope) : self scope.find_in_batches(options) do |batch| batch_for_bulk = batch.map { |a| { index: { _id: a.id, data: a.__elasticsearch__.as_indexed_json } } } From d3e5c8b721121d12212612cf0f5e7cd623ea4ece Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Wed, 7 May 2014 11:36:17 +0200 Subject: [PATCH 025/582] [MODEL] Added, that `each_with_hit` and `map_with_hit` in `Elasticsearch::Model::Response::Records` call `to_a` The `zip` method in `records.rb` didn't call `to_a` on the collection, thus the records were not returned in the same order as Elasticsearch's results. Related #99 Related #102 --- .../lib/elasticsearch/model/response/records.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/records.rb b/elasticsearch-model/lib/elasticsearch/model/response/records.rb index 6383e7e9d..cd936559b 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/records.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/records.rb @@ -43,13 +43,13 @@ def results # Yields [record, hit] pairs to the block # def each_with_hit(&block) - records.zip(results).each(&block) + records.to_a.zip(results).each(&block) end # Yields [record, hit] pairs and returns the result # def map_with_hit(&block) - records.zip(results).map(&block) + records.to_a.zip(results).map(&block) end # Delegate methods to `@records` From 104f91f24ba8d3dc7629b0eaf058329dc2cdc08c Mon Sep 17 00:00:00 2001 From: Dong Wook Koo Date: Mon, 5 May 2014 12:38:17 -0400 Subject: [PATCH 026/582] [MODEL] Added tests for `each_with_hit` and `map_with_hit` These are tests for commit 83831b3. Closes #99 Closes #102 --- .../test/integration/active_record_basic_test.rb | 12 ++++++++++++ .../test/integration/mongoid_basic_test.rb | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index 8ac7e89e1..456e86195 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -81,6 +81,18 @@ class ::Article < ActiveRecord::Base end end + should "preserve the search results order for records" do + response = Article.search('title:code') + + response.records.each_with_hit do |r, h| + assert_equal h._id, r.id.to_s + end + + response.records.map_with_hit do |r, h| + assert_equal h._id, r.id.to_s + end + end + should "remove document from index on destroy" do article = Article.first diff --git a/elasticsearch-model/test/integration/mongoid_basic_test.rb b/elasticsearch-model/test/integration/mongoid_basic_test.rb index fb2789d13..749446369 100644 --- a/elasticsearch-model/test/integration/mongoid_basic_test.rb +++ b/elasticsearch-model/test/integration/mongoid_basic_test.rb @@ -95,6 +95,18 @@ def as_indexed_json(options={}) end end + should "preserve the search results order for records" do + response = MongoidArticle.search('title:code') + + response.records.each_with_hit do |r, h| + assert_equal h._id, r.id.to_s + end + + response.records.map_with_hit do |r, h| + assert_equal h._id, r.id.to_s + end + end + should "remove document from index on destroy" do article = MongoidArticle.first From 750ccad9037f4a56023936e6ab20022b4968d37b Mon Sep 17 00:00:00 2001 From: Brian Alexander Date: Wed, 23 Apr 2014 09:41:45 -0600 Subject: [PATCH 027/582] [MODEL] Added support for `will_paginate` pagination library This patch adds support for the [`will_paginate`](https://github.com/mislav/will_paginate) gem, functionally equivalent to the Kaminari integration. Closes #49 --- README.md | 4 +- elasticsearch-model/README.md | 7 +- .../elasticsearch-model.gemspec | 1 + .../lib/elasticsearch/model.rb | 5 +- .../model/response/pagination.rb | 65 ++++++ ...b => response_pagination_kaminari_test.rb} | 2 +- .../response_pagination_will_paginate_test.rb | 189 ++++++++++++++++++ 7 files changed, 266 insertions(+), 7 deletions(-) rename elasticsearch-model/test/unit/{response_pagination_test.rb => response_pagination_kaminari_test.rb} (98%) create mode 100644 elasticsearch-model/test/unit/response_pagination_will_paginate_test.rb diff --git a/README.md b/README.md index d0c6df584..b1de11e2c 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ This repository contains ActiveModel, ActiveRecord and Ruby on Rails integration * ActiveRecord::Relation-based wrapper for returning search results as records * Convenience model methods such as `search`, `mapping`, `import`, etc * Rake tasks for importing the data -* Kaminari-based pagination support -* Integration with Rails's instrumentation framework +* Support for Kaminari and WillPaginate pagination +* Integration with Rails' instrumentation framework * Templates for generating example Rails application Elasticsearch client and Ruby API is provided by the diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index b907e6d52..8e21a8768 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -252,9 +252,10 @@ response.records.each_with_hit { |record, hit| puts "* #{record.title}: #{hit._s #### Pagination You can implement pagination with the `from` and `size` search parameters. However, search results -can be automatically paginated with the [`kaminari`](http://rubygems.org/gems/kaminari) gem. +can be automatically paginated with the [`kaminari`](http://rubygems.org/gems/kaminari) or +[`will_paginate`](https://github.com/mislav/will_paginate) gems. -If Kaminari is loaded, use the familiar paging methods: +If Kaminari or WillPaginate is loaded, use the familiar paging methods: ```ruby response.page(2).results @@ -271,7 +272,7 @@ In a Rails controller, use the the `params[:page]` parameter to paginate through @articles.next_page # => 3 ``` -To initialize and include the pagination support manually: +To initialize and include the Kaminari pagination support manually: ```ruby Kaminari::Hooks.init diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index 95ae2b0a8..1ced78c75 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -36,6 +36,7 @@ Gem::Specification.new do |s| s.add_development_dependency "oj" s.add_development_dependency "kaminari" + s.add_development_dependency "will_paginate" # NOTE: Do not add Mongoid here, keep only in 3/4 files s.add_development_dependency "minitest", "~> 4.0" diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index c69d21ac9..46fd80741 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -31,8 +31,11 @@ require 'elasticsearch/model/ext/active_record' -if defined?(::Kaminari) +case +when defined?(::Kaminari) Elasticsearch::Model::Response::Response.__send__ :include, Elasticsearch::Model::Response::Pagination::Kaminari +when defined?(::WillPaginate) + Elasticsearch::Model::Response::Response.__send__ :include, Elasticsearch::Model::Response::Pagination::WillPaginate end module Elasticsearch diff --git a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb index 212a532c2..cc4e6c3c9 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb @@ -93,6 +93,71 @@ def total_count results.total end end + + # Allow models to be paginated with the "will_paginate" gem [https://github.com/mislav/will_paginate] + # + module WillPaginate + def self.included(base) + base.__send__ :include, ::WillPaginate::CollectionMethods + + # Include the paging methods in results and records + # + methods = [:current_page, :per_page, :total_entries, :total_pages, :previous_page, :next_page, :out_of_bounds?] + Elasticsearch::Model::Response::Results.__send__ :delegate, *methods, to: :response + Elasticsearch::Model::Response::Records.__send__ :delegate, *methods, to: :response + end + + # Main pagination method + # + # @example + # + # Article.search('foo').paginate(page: 1, per_page: 30) + # + def paginate(options) + page = [options[:page].to_i, 1].max + per_page = (options[:per_page] || klass.per_page).to_i + + search.definition.update size: per_page, + from: (page - 1) * per_page + self + end + + # Return the current page + # + def current_page + search.definition[:from] / per_page + 1 if search.definition[:from] && per_page + end + + # Pagination method + # + # @example + # + # Article.search('foo').page(2) + # + def page(num) + paginate(page: num, per_page: per_page) # shorthand + end + + # Return or set the "size" value + # + # @example + # + # Article.search('foo').per_page(15).page(2) + # + def per_page(num = nil) + if num.nil? + search.definition[:size] + else + paginate(page: current_page, per_page: num) # shorthand + end + end + + # Returns the total number of results + # + def total_entries + results.total + end + end end end diff --git a/elasticsearch-model/test/unit/response_pagination_test.rb b/elasticsearch-model/test/unit/response_pagination_kaminari_test.rb similarity index 98% rename from elasticsearch-model/test/unit/response_pagination_test.rb rename to elasticsearch-model/test/unit/response_pagination_kaminari_test.rb index 7394d4913..2d2f411b9 100644 --- a/elasticsearch-model/test/unit/response_pagination_test.rb +++ b/elasticsearch-model/test/unit/response_pagination_kaminari_test.rb @@ -1,6 +1,6 @@ require 'test_helper' -class Elasticsearch::Model::ResponsePaginationTest < Test::Unit::TestCase +class Elasticsearch::Model::ResponsePaginationKaminariTest < Test::Unit::TestCase context "Response pagination" do class ModelClass include ::Kaminari::ConfigurationMethods diff --git a/elasticsearch-model/test/unit/response_pagination_will_paginate_test.rb b/elasticsearch-model/test/unit/response_pagination_will_paginate_test.rb new file mode 100644 index 000000000..0ea94fc91 --- /dev/null +++ b/elasticsearch-model/test/unit/response_pagination_will_paginate_test.rb @@ -0,0 +1,189 @@ +require 'test_helper' +require 'will_paginate' +require 'will_paginate/collection' + +class Elasticsearch::Model::ResponsePaginationWillPaginateTest < Test::Unit::TestCase + context "Response pagination" do + class ModelClass + def self.index_name; 'foo'; end + def self.document_type; 'bar'; end + + # WillPaginate adds this method to models (see WillPaginate::PerPage module) + def self.per_page + 33 + end + end + + # Subsclass Response so we can include WillPaginate module without conflicts with Kaminari. + class WillPaginateResponse < Elasticsearch::Model::Response::Response + include Elasticsearch::Model::Response::Pagination::WillPaginate + end + + RESPONSE = { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, + 'hits' => { 'total' => 100, 'hits' => (1..100).to_a.map { |i| { _id: i } } } } + + setup do + @search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, '*' + @response = WillPaginateResponse.new ModelClass, @search, RESPONSE + @response.klass.stubs(:client).returns mock('client') + + @expected_methods = [ + # methods needed by WillPaginate::CollectionMethods + :current_page, + :per_page, + :total_entries, + + # methods defined by WillPaginate::CollectionMethods + :total_pages, + :previous_page, + :next_page, + :out_of_bounds?, + ] + end + + should "have pagination methods" do + assert_respond_to @response, :paginate + + @expected_methods.each do |method| + assert_respond_to @response, method + end + end + + context "response.results" do + should "have pagination methods" do + @expected_methods.each do |method| + assert_respond_to @response.results, method + end + end + end + + context "response.records" do + should "have pagination methods" do + @expected_methods.each do |method| + @response.klass.stubs(:find).returns([]) + assert_respond_to @response.records, method + end + end + end + + context "#paginate method" do + should "set from/size using defaults" do + @response.klass.client + .expects(:search) + .with do |definition| + assert_equal 0, definition[:from] + assert_equal 33, definition[:size] + end + .returns(RESPONSE) + + assert_nil @response.search.definition[:from] + assert_nil @response.search.definition[:size] + + @response.paginate(page: nil).to_a + assert_equal 0, @response.search.definition[:from] + assert_equal 33, @response.search.definition[:size] + end + + should "set from/size using default per_page" do + @response.klass.client + .expects(:search) + .with do |definition| + assert_equal 33, definition[:from] + assert_equal 33, definition[:size] + end + .returns(RESPONSE) + + assert_nil @response.search.definition[:from] + assert_nil @response.search.definition[:size] + + @response.paginate(page: 2).to_a + assert_equal 33, @response.search.definition[:from] + assert_equal 33, @response.search.definition[:size] + end + + should "set from/size using custom page and per_page" do + @response.klass.client + .expects(:search) + .with do |definition| + assert_equal 18, definition[:from] + assert_equal 9, definition[:size] + end + .returns(RESPONSE) + + assert_nil @response.search.definition[:from] + assert_nil @response.search.definition[:size] + + @response.paginate(page: 3, per_page: 9).to_a + assert_equal 18, @response.search.definition[:from] + assert_equal 9, @response.search.definition[:size] + end + + should "searches for page 1 if specified page is < 1" do + @response.klass.client + .expects(:search) + .with do |definition| + assert_equal 0, definition[:from] + assert_equal 33, definition[:size] + end + .returns(RESPONSE) + + assert_nil @response.search.definition[:from] + assert_nil @response.search.definition[:size] + + @response.paginate(page: "-1").to_a + assert_equal 0, @response.search.definition[:from] + assert_equal 33, @response.search.definition[:size] + end + end + + context "#page and #per_page shorthand methods" do + should "set from/size using default per_page" do + @response.page(5) + assert_equal 132, @response.search.definition[:from] + assert_equal 33, @response.search.definition[:size] + end + + should "set from/size when calling #page then #per_page" do + @response.page(5).per_page(3) + assert_equal 12, @response.search.definition[:from] + assert_equal 3, @response.search.definition[:size] + end + + should "set from/size when calling #per_page then #page" do + @response.per_page(3).page(5) + assert_equal 12, @response.search.definition[:from] + assert_equal 3, @response.search.definition[:size] + end + end + + context "#current_page method" do + should "return 1 by default" do + @response.paginate({}) + assert_equal 1, @response.current_page + end + + should "return current page number" do + @response.paginate(page: 3, per_page: 9) + assert_equal 3, @response.current_page + end + + should "return nil if not pagination set" do + assert_equal nil, @response.current_page + end + end + + context "#per_page method" do + should "return value set in paginate call" do + @response.paginate(per_page: 8) + assert_equal 8, @response.per_page + end + end + + context "#total_entries method" do + should "return total from response" do + @response.expects(:results).returns(mock('results', total: 100)) + assert_equal 100, @response.total_entries + end + end + end +end From c4b42b5bd21790d491494934588c9f8921337a69 Mon Sep 17 00:00:00 2001 From: Aaron Rustad Date: Sat, 10 May 2014 19:46:00 -0600 Subject: [PATCH 028/582] [MODEL] Added the ability to transform models during indexing For instance, to add the `_parent` field to indexed documents: transform = lambda do |a| { index: {_id: a.id, _parent: a.author_id, data: a.__elasticsearch__.as_indexed_json} } end Article.import transform: transform Closes #113 --- .../model/adapters/active_record.rb | 9 ++--- .../elasticsearch/model/adapters/default.rb | 6 ++++ .../elasticsearch/model/adapters/mongoid.rb | 10 +++--- .../lib/elasticsearch/model/importing.rb | 24 ++++++++++--- .../test/unit/adapter_active_record_test.rb | 14 ++++++++ .../test/unit/adapter_default_test.rb | 18 +++++++--- .../test/unit/adapter_mongoid_test.rb | 15 ++++++++ .../test/unit/importing_test.rb | 35 ++++++++++++++++++- 8 files changed, 114 insertions(+), 17 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb index c154510a4..bb07d1351 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb @@ -88,14 +88,15 @@ def __find_in_batches(options={}, &block) scope = named_scope ? self.__send__(named_scope) : self scope.find_in_batches(options) do |batch| - batch_for_bulk = batch.map { |a| { index: { _id: a.id, data: a.__elasticsearch__.as_indexed_json } } } - yield batch_for_bulk + yield batch end end - end + def __transform + lambda {|model| { index: { _id: model.id, data: model.__elasticsearch__.as_indexed_json } }} + end + end end - end end end diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/default.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/default.rb index e04ffd0ec..e58cf4ceb 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/default.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/default.rb @@ -36,6 +36,12 @@ module Importing def __find_in_batches(options={}, &block) raise NotImplemented, "Method not implemented for default adapter" end + + # @abstract Implement this method in your adapter + # + def __transform + raise NotImplemented, "Method not implemented for default adapter" + end end end diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb index cd052c8ff..51c9e1be3 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb @@ -70,17 +70,19 @@ def __find_in_batches(options={}, &block) items << item if items.length % options[:batch_size] == 0 - batch_for_bulk = items.map { |a| { index: { _id: a.id.to_s, data: a.as_indexed_json } } } - yield batch_for_bulk + yield items items = [] end end unless items.empty? - batch_for_bulk = items.map { |a| { index: { _id: a.id.to_s, data: a.as_indexed_json } } } - yield batch_for_bulk + yield items end end + + def __transform + lambda {|a| { index: { _id: a.id.to_s, data: a.as_indexed_json } }} + end end end diff --git a/elasticsearch-model/lib/elasticsearch/model/importing.rb b/elasticsearch-model/lib/elasticsearch/model/importing.rb index f67113f35..b367493df 100644 --- a/elasticsearch-model/lib/elasticsearch/model/importing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/importing.rb @@ -68,11 +68,24 @@ module ClassMethods # # Article.import scope: 'published' # + # @example Customize how each record is [bulk imported](https://github.com/elasticsearch/elasticsearch-ruby/blob/master/elasticsearch-api/lib/elasticsearch/api/actions/bulk.rb) + # + # transform = lambda do |article| + # {index: {_id: article.id, _parent: article.author_id, data: article.__elasticsearch__.as_indexed_json}} + # end + # + # Article.import transform: transform + # def import(options={}, &block) errors = 0 - refresh = options.delete(:refresh) || false - target_index = options.delete(:index) || index_name - target_type = options.delete(:type) || document_type + refresh = options.delete(:refresh) || false + target_index = options.delete(:index) || index_name + target_type = options.delete(:type) || document_type + transform = options.delete(:transform) || __transform + + if !transform.respond_to?(:call) + raise ArgumentError, "You must pass an object that supports #call method, #{transform.class} given" + end if options.delete(:force) self.create_index! force: true, index: target_index @@ -82,7 +95,7 @@ def import(options={}, &block) response = client.bulk \ index: target_index, type: target_type, - body: batch + body: __batch_to_bulk(batch, transform) yield response if block_given? @@ -94,6 +107,9 @@ def import(options={}, &block) return errors end + def __batch_to_bulk(batch, transform) + batch.map {|model| transform.call(model)} + end end end diff --git a/elasticsearch-model/test/unit/adapter_active_record_test.rb b/elasticsearch-model/test/unit/adapter_active_record_test.rb index fb6a70dac..4999f901e 100644 --- a/elasticsearch-model/test/unit/adapter_active_record_test.rb +++ b/elasticsearch-model/test/unit/adapter_active_record_test.rb @@ -104,6 +104,20 @@ def ids DummyClassForActiveRecord.__find_in_batches(scope: :published) do; end end + context "when transforming models" do + setup do + @transform = DummyClassForActiveRecord.__transform + end + + should "provide an object that responds to #call" do + assert_respond_to @transform, :call + end + + should "provide basic transformation" do + model = mock("model", id: 1, __elasticsearch__: stub(as_indexed_json: {})) + assert_equal @transform.call(model), { index: { _id: 1, data: {} } } + end + end end end end diff --git a/elasticsearch-model/test/unit/adapter_default_test.rb b/elasticsearch-model/test/unit/adapter_default_test.rb index 161fef9cc..48edd205d 100644 --- a/elasticsearch-model/test/unit/adapter_default_test.rb +++ b/elasticsearch-model/test/unit/adapter_default_test.rb @@ -19,11 +19,21 @@ class ::DummyClassForDefaultAdapter; end assert_instance_of Module, Elasticsearch::Model::Adapter::Default::Callbacks end - should "have the default Importing implementation" do - DummyClassForDefaultAdapter.__send__ :include, Elasticsearch::Model::Adapter::Default::Importing + context "concerning abstract methods" do + setup do + DummyClassForDefaultAdapter.__send__ :include, Elasticsearch::Model::Adapter::Default::Importing + end + + should "have the default Importing implementation" do + assert_raise Elasticsearch::Model::NotImplemented do + DummyClassForDefaultAdapter.new.__find_in_batches + end + end - assert_raise Elasticsearch::Model::NotImplemented do - DummyClassForDefaultAdapter.new.__find_in_batches + should "have the default transform implementation" do + assert_raise Elasticsearch::Model::NotImplemented do + DummyClassForDefaultAdapter.new.__transform + end end end diff --git a/elasticsearch-model/test/unit/adapter_mongoid_test.rb b/elasticsearch-model/test/unit/adapter_mongoid_test.rb index 945f4e33f..fbec800d0 100644 --- a/elasticsearch-model/test/unit/adapter_mongoid_test.rb +++ b/elasticsearch-model/test/unit/adapter_mongoid_test.rb @@ -81,6 +81,21 @@ def ids DummyClassForMongoid.__send__ :extend, Elasticsearch::Model::Adapter::Mongoid::Importing DummyClassForMongoid.__find_in_batches do; end end + + context "when transforming models" do + setup do + @transform = DummyClassForMongoid.__transform + end + + should "provide an object that responds to #call" do + assert_respond_to @transform, :call + end + + should "provide basic transformation" do + model = mock("model", id: 1, as_indexed_json: {}) + assert_equal @transform.call(model), { index: { _id: "1", data: {} } } + end + end end end diff --git a/elasticsearch-model/test/unit/importing_test.rb b/elasticsearch-model/test/unit/importing_test.rb index 4d84c89be..70ac58bb5 100644 --- a/elasticsearch-model/test/unit/importing_test.rb +++ b/elasticsearch-model/test/unit/importing_test.rb @@ -10,6 +10,9 @@ module ImportingMixin def __find_in_batches(options={}, &block) yield if block_given? end + def __transform + lambda {|a|} + end end def importing_mixin @@ -41,7 +44,7 @@ def importing_mixin DummyImportingModel.expects(:client).returns(client) DummyImportingModel.expects(:index_name).returns('foo') DummyImportingModel.expects(:document_type).returns('foo') - + DummyImportingModel.stubs(:__batch_to_bulk) assert_equal 0, DummyImportingModel.import end @@ -58,6 +61,7 @@ def importing_mixin DummyImportingModel.stubs(:client).returns(client) DummyImportingModel.stubs(:index_name).returns('foo') DummyImportingModel.stubs(:document_type).returns('foo') + DummyImportingModel.stubs(:__batch_to_bulk) assert_equal 1, DummyImportingModel.import end @@ -75,6 +79,7 @@ def importing_mixin DummyImportingModel.stubs(:client).returns(client) DummyImportingModel.stubs(:index_name).returns('foo') DummyImportingModel.stubs(:document_type).returns('foo') + DummyImportingModel.stubs(:__batch_to_bulk) DummyImportingModel.import do |response| assert_equal 2, response['items'].size @@ -116,8 +121,36 @@ def importing_mixin .returns({'items' => [ {'index' => {} }]}) DummyImportingModel.stubs(:client).returns(client) + DummyImportingModel.stubs(:__batch_to_bulk) DummyImportingModel.import index: 'my-new-index', type: 'my-other-type' end + + should "default to the adapter's bulk transform" do + client = mock('client', bulk: {'items' => []}) + transform = lambda {|a|} + + DummyImportingModel.stubs(:client).returns(client) + DummyImportingModel.expects(:__transform).returns(transform) + DummyImportingModel.expects(:__batch_to_bulk).with(anything, transform) + + DummyImportingModel.import index: 'foo', type: 'bar' + end + + should "use the optioned transform" do + client = mock('client', bulk: {'items' => []}) + transform = lambda {|a|} + + DummyImportingModel.stubs(:client).returns(client) + DummyImportingModel.expects(:__batch_to_bulk).with(anything, transform) + + DummyImportingModel.import index: 'foo', type: 'bar', transform: transform + end + + should "raise an ArgumentError if transform is an object that doesn't respond to #call" do + assert_raise ArgumentError do + DummyImportingModel.import index: 'foo', type: 'bar', transform: "not_callable" + end + end end end From b2fce1f0f5fcb896ca5df5b8339520a13ba6e610 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Wed, 21 May 2014 17:58:55 +0200 Subject: [PATCH 029/582] [MODEL] Minor changes and tweaks to the `transform` feature for import Related: c4b42b5, #113 --- .../elasticsearch/model/adapters/active_record.rb | 2 +- .../lib/elasticsearch/model/importing.rb | 13 +++++++------ .../test/integration/active_record_import_test.rb | 12 ++++++++++++ .../test/unit/adapter_active_record_test.rb | 2 +- elasticsearch-model/test/unit/importing_test.rb | 6 +++--- 5 files changed, 24 insertions(+), 11 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb index bb07d1351..e15f81439 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb @@ -93,7 +93,7 @@ def __find_in_batches(options={}, &block) end def __transform - lambda {|model| { index: { _id: model.id, data: model.__elasticsearch__.as_indexed_json } }} + lambda { |model| { index: { _id: model.id, data: model.__elasticsearch__.as_indexed_json } } } end end end diff --git a/elasticsearch-model/lib/elasticsearch/model/importing.rb b/elasticsearch-model/lib/elasticsearch/model/importing.rb index b367493df..a8e8e929a 100644 --- a/elasticsearch-model/lib/elasticsearch/model/importing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/importing.rb @@ -68,10 +68,10 @@ module ClassMethods # # Article.import scope: 'published' # - # @example Customize how each record is [bulk imported](https://github.com/elasticsearch/elasticsearch-ruby/blob/master/elasticsearch-api/lib/elasticsearch/api/actions/bulk.rb) + # @example Transform records during the import with a lambda # - # transform = lambda do |article| - # {index: {_id: article.id, _parent: article.author_id, data: article.__elasticsearch__.as_indexed_json}} + # transform = lambda do |a| + # {index: {_id: a.id, _parent: a.author_id, data: a.__elasticsearch__.as_indexed_json}} # end # # Article.import transform: transform @@ -83,8 +83,9 @@ def import(options={}, &block) target_type = options.delete(:type) || document_type transform = options.delete(:transform) || __transform - if !transform.respond_to?(:call) - raise ArgumentError, "You must pass an object that supports #call method, #{transform.class} given" + unless transform.respond_to?(:call) + raise ArgumentError, + "Pass an object responding to `call` as the :transport option, #{transform.class} given" end if options.delete(:force) @@ -108,7 +109,7 @@ def import(options={}, &block) end def __batch_to_bulk(batch, transform) - batch.map {|model| transform.call(model)} + batch.map { |model| transform.call(model) } end end diff --git a/elasticsearch-model/test/integration/active_record_import_test.rb b/elasticsearch-model/test/integration/active_record_import_test.rb index 4fc6f6aad..759b74572 100644 --- a/elasticsearch-model/test/integration/active_record_import_test.rb +++ b/elasticsearch-model/test/integration/active_record_import_test.rb @@ -81,6 +81,18 @@ class ::ImportArticle < ActiveRecord::Base ImportArticle.__elasticsearch__.refresh_index! assert_equal 100, ImportArticle.search('*').results.total end + + should "transform documents with the option" do + assert_equal 100, ImportArticle.count + + assert_equal 0, ImportArticle.import( transform: ->(a) {{ index: { data: { name: a.title, foo: 'BAR' } }}} ) + + ImportArticle.__elasticsearch__.refresh_index! + assert_contains ImportArticle.search('*').results.first._source.keys, 'name' + assert_contains ImportArticle.search('*').results.first._source.keys, 'foo' + assert_equal 100, ImportArticle.search('test').results.total + assert_equal 100, ImportArticle.search('bar').results.total + end end end diff --git a/elasticsearch-model/test/unit/adapter_active_record_test.rb b/elasticsearch-model/test/unit/adapter_active_record_test.rb index 4999f901e..361732982 100644 --- a/elasticsearch-model/test/unit/adapter_active_record_test.rb +++ b/elasticsearch-model/test/unit/adapter_active_record_test.rb @@ -113,7 +113,7 @@ def ids assert_respond_to @transform, :call end - should "provide basic transformation" do + should "provide default transformation" do model = mock("model", id: 1, __elasticsearch__: stub(as_indexed_json: {})) assert_equal @transform.call(model), { index: { _id: 1, data: {} } } end diff --git a/elasticsearch-model/test/unit/importing_test.rb b/elasticsearch-model/test/unit/importing_test.rb index 70ac58bb5..31eb086cd 100644 --- a/elasticsearch-model/test/unit/importing_test.rb +++ b/elasticsearch-model/test/unit/importing_test.rb @@ -126,7 +126,7 @@ def importing_mixin DummyImportingModel.import index: 'my-new-index', type: 'my-other-type' end - should "default to the adapter's bulk transform" do + should "use the default transform from adapter" do client = mock('client', bulk: {'items' => []}) transform = lambda {|a|} @@ -137,7 +137,7 @@ def importing_mixin DummyImportingModel.import index: 'foo', type: 'bar' end - should "use the optioned transform" do + should "use the transformer from options" do client = mock('client', bulk: {'items' => []}) transform = lambda {|a|} @@ -147,7 +147,7 @@ def importing_mixin DummyImportingModel.import index: 'foo', type: 'bar', transform: transform end - should "raise an ArgumentError if transform is an object that doesn't respond to #call" do + should "raise an ArgumentError if transform doesn't respond to the call method" do assert_raise ArgumentError do DummyImportingModel.import index: 'foo', type: 'bar', transform: "not_callable" end From 5486a87d61926cb1bcbe821b0509900c0a58411a Mon Sep 17 00:00:00 2001 From: Berislav Babic Date: Mon, 28 Apr 2014 12:26:05 +0200 Subject: [PATCH 030/582] [MODEL] Add `type` and `id` methods to Response::Result to prevent errors with Hashie::Mash Since Hashie::Mash explicitely [defines](https://github.com/intridea/hashie/blob/953edec/lib/hashie/mash.rb#L75-L81) `id` and `type` methods, the method_missing couldn't kick in. The `id` and `type` attributes should be referenced from the attributes hash directly, ie. from the main `_id` and `_type` properties, and not from `_source`. Closes #90 --- .../lib/elasticsearch/model/response/result.rb | 18 ++++++++++++++++++ .../integration/active_record_basic_test.rb | 7 +++++++ .../test/unit/response_result_test.rb | 11 +++++++++++ 3 files changed, 36 insertions(+) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/result.rb b/elasticsearch-model/lib/elasticsearch/model/response/result.rb index ca994f421..94a956d6c 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/result.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/result.rb @@ -17,12 +17,30 @@ def initialize(attributes={}) @result = Hashie::Mash.new(attributes) end + # Alias `id` to `_id` + # + def id + @result['_id'] + end + + # Alias `type` to `_type` + # + def type + @result['_type'] + end + # Delegate methods to `@result` or `@result._source` # def method_missing(name, *arguments) case when name.to_s.end_with?('?') @result.__send__(name, *arguments) || ( @result._source && @result._source.__send__(name, *arguments) ) + when name.to_s == "id" + if @result._source && @result._source.respond_to?(name) + @result._source.__send__ name, *arguments + else + super + end when @result.respond_to?(name) @result.__send__ name, *arguments when @result._source && @result._source.respond_to?(name) diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index 456e86195..53d6492f5 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -72,6 +72,13 @@ class ::Article < ActiveRecord::Base assert_equal [1, 2], response.records.map(&:id) end + should "iterate aliased id and type over results" do + response = Article.search('title:test') + + assert_equal ['1', '2'], response.results.map(&:id) + assert_equal ['article', 'article'], response.results.map(&:type) + end + should "access results from records" do response = Article.search('title:test') diff --git a/elasticsearch-model/test/unit/response_result_test.rb b/elasticsearch-model/test/unit/response_result_test.rb index b9267b664..6be4f8b54 100644 --- a/elasticsearch-model/test/unit/response_result_test.rb +++ b/elasticsearch-model/test/unit/response_result_test.rb @@ -73,5 +73,16 @@ class Elasticsearch::Model::ResultTest < Test::Unit::TestCase result.as_json(except: 'foo') end + should "map the _id column to id" do + result = Elasticsearch::Model::Response::Result.new foo: 'bar', _id: 42 + + assert_equal 42, result.id + end + + should "map the _type column to type" do + result = Elasticsearch::Model::Response::Result.new foo: 'bar', _type: 'baz' + + assert_equal 'baz', result.type + end end end From cd9cc679005292d5f9cf27d71146ce75bbfc20d3 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Fri, 23 May 2014 10:13:32 +0200 Subject: [PATCH 031/582] [MODEL] Fixes for the change id/type for Response::Result in #90 * Removed the code which returned id based on _source in `method_missing` * Reorganized and renamed some tests * Added a test for demonstrating difference between `result.type` and `result._source.type` (same with `id`) Related: #90 --- .../elasticsearch/model/response/result.rb | 10 ++----- .../integration/active_record_basic_test.rb | 6 ++--- .../test/unit/response_result_test.rb | 26 ++++++++++--------- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/result.rb b/elasticsearch-model/lib/elasticsearch/model/response/result.rb index 94a956d6c..217723e8b 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/result.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/result.rb @@ -17,13 +17,13 @@ def initialize(attributes={}) @result = Hashie::Mash.new(attributes) end - # Alias `id` to `_id` + # Return document `_id` as `id` # def id @result['_id'] end - # Alias `type` to `_type` + # Return document `_type` as `_type` # def type @result['_type'] @@ -35,12 +35,6 @@ def method_missing(name, *arguments) case when name.to_s.end_with?('?') @result.__send__(name, *arguments) || ( @result._source && @result._source.__send__(name, *arguments) ) - when name.to_s == "id" - if @result._source && @result._source.respond_to?(name) - @result._source.__send__ name, *arguments - else - super - end when @result.respond_to?(name) @result.__send__ name, *arguments when @result._source && @result._source.respond_to?(name) diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index 53d6492f5..7f9a1c9ed 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -72,11 +72,11 @@ class ::Article < ActiveRecord::Base assert_equal [1, 2], response.records.map(&:id) end - should "iterate aliased id and type over results" do + should "return _id and _type as #id and #type" do response = Article.search('title:test') - assert_equal ['1', '2'], response.results.map(&:id) - assert_equal ['article', 'article'], response.results.map(&:type) + assert_equal '1', response.results.first.id + assert_equal 'article', response.results.first.type end should "access results from records" do diff --git a/elasticsearch-model/test/unit/response_result_test.rb b/elasticsearch-model/test/unit/response_result_test.rb index 6be4f8b54..ff78d2579 100644 --- a/elasticsearch-model/test/unit/response_result_test.rb +++ b/elasticsearch-model/test/unit/response_result_test.rb @@ -15,6 +15,20 @@ class Elasticsearch::Model::ResultTest < Test::Unit::TestCase assert_raise(NoMethodError) { result.xoxo } end + should "return _id as #id" do + result = Elasticsearch::Model::Response::Result.new foo: 'bar', _id: 42, _source: { id: 12 } + + assert_equal 42, result.id + assert_equal 12, result._source.id + end + + should "return _type as #type" do + result = Elasticsearch::Model::Response::Result.new foo: 'bar', _type: 'baz', _source: { type: 'BAM' } + + assert_equal 'baz', result.type + assert_equal 'BAM', result._source.type + end + should "delegate method calls to `_source` when available" do result = Elasticsearch::Model::Response::Result.new foo: 'bar', _source: { bar: 'baz' } @@ -72,17 +86,5 @@ class Elasticsearch::Model::ResultTest < Test::Unit::TestCase result.instance_variable_get(:@result).expects(:as_json) result.as_json(except: 'foo') end - - should "map the _id column to id" do - result = Elasticsearch::Model::Response::Result.new foo: 'bar', _id: 42 - - assert_equal 42, result.id - end - - should "map the _type column to type" do - result = Elasticsearch::Model::Response::Result.new foo: 'bar', _type: 'baz' - - assert_equal 'baz', result.type - end end end From 7157dd8256b9d8e345b46be646cef1b16d8fa9f7 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Fri, 23 May 2014 15:15:34 +0200 Subject: [PATCH 032/582] Release 0.1.2 --- elasticsearch-model/CHANGELOG.md | 10 ++++++++++ elasticsearch-model/lib/elasticsearch/model/version.rb | 2 +- elasticsearch-rails/CHANGELOG.md | 6 +++++- elasticsearch-rails/lib/elasticsearch/rails/version.rb | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/CHANGELOG.md b/elasticsearch-model/CHANGELOG.md index 8ecdb6fd7..818fb3f07 100644 --- a/elasticsearch-model/CHANGELOG.md +++ b/elasticsearch-model/CHANGELOG.md @@ -1,3 +1,13 @@ +## 0.1.2 + +* Properly delegate existence methods like `result.foo?` to `result._source.foo` +* Exception is raised when `type` is not passed to Mappings#new +* Allow passing an ActiveRecord scope to the `import` method +* Added, that `each_with_hit` and `map_with_hit` in `Elasticsearch::Model::Response::Records` call `to_a` +* Added support for [`will_paginate`](https://github.com/mislav/will_paginate) pagination library +* Added the ability to transform models during indexing +* Added explicit `type` and `id` methods to Response::Result, aliasing `_type` and `_id` + ## 0.1.1 * Improved documentation and tests diff --git a/elasticsearch-model/lib/elasticsearch/model/version.rb b/elasticsearch-model/lib/elasticsearch/model/version.rb index d2228a2c7..b1692ab2e 100644 --- a/elasticsearch-model/lib/elasticsearch/model/version.rb +++ b/elasticsearch-model/lib/elasticsearch/model/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Model - VERSION = "0.1.1" + VERSION = "0.1.2" end end diff --git a/elasticsearch-rails/CHANGELOG.md b/elasticsearch-rails/CHANGELOG.md index 18a3035b5..7c29ea663 100644 --- a/elasticsearch-rails/CHANGELOG.md +++ b/elasticsearch-rails/CHANGELOG.md @@ -1,6 +1,10 @@ +# 0.1.2 + +* Allow passing an ActiveRecord scope to the importing Rake task + ## 0.1.1 * Improved the Rake tasks * Improved the example application templates -## 0.1.1 (Initial Version) +## 0.1.0 (Initial Version) diff --git a/elasticsearch-rails/lib/elasticsearch/rails/version.rb b/elasticsearch-rails/lib/elasticsearch/rails/version.rb index c0fb4a9aa..c717d0e67 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/version.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Rails - VERSION = "0.1.1" + VERSION = "0.1.2" end end From b75582f8bb621066b9c140ebfe16c055d2ffc683 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Fri, 23 May 2014 15:20:55 +0200 Subject: [PATCH 033/582] Added Ruby version requirement to gemfiles (>= 1.9.3) --- elasticsearch-model/elasticsearch-model.gemspec | 2 ++ elasticsearch-rails/elasticsearch-rails.gemspec | 2 ++ 2 files changed, 4 insertions(+) diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index 1ced78c75..00e249338 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -21,6 +21,8 @@ Gem::Specification.new do |s| s.extra_rdoc_files = [ "README.md", "LICENSE.txt" ] s.rdoc_options = [ "--charset=UTF-8" ] + s.required_ruby_version = ">= 1.9.3" + s.add_dependency "elasticsearch", '> 0.4' s.add_dependency "activesupport", '> 3' s.add_dependency "hashie" diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index 6aee99b11..a22ac0d2b 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -21,6 +21,8 @@ Gem::Specification.new do |s| s.extra_rdoc_files = [ "README.md", "LICENSE.txt" ] s.rdoc_options = [ "--charset=UTF-8" ] + s.required_ruby_version = ">= 1.9.3" + s.add_development_dependency "bundler", "~> 1.3" s.add_development_dependency "rake" From 304615e5c548448e95cdbca29de197b5a789d0d3 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Sun, 25 May 2014 18:28:48 +0200 Subject: [PATCH 034/582] [MODEL] Fixed the incorrect protection for existing model methods (such as `search`) Due to the different scope in the `class << self`, the `respond_to?` condition always returned false, even when the model had in fact defined the method in question (such as `search`). Instead of using `respond_to?`, `self.public_instance_methods.include?` has been used. The concrete example is eg. [Ransack's](https://github.com/activerecord-hackery/ransack) `search` method. Closes #96 --- elasticsearch-model/lib/elasticsearch/model.rb | 11 ++++------- elasticsearch-model/test/unit/module_test.rb | 11 +++++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index 46fd80741..f73c9a5d9 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -62,6 +62,7 @@ module Elasticsearch # # ... # module Model + METHODS = [:search, :mapping, :mappings, :settings, :index_name, :document_type, :import] # Adds the `Elasticsearch::Model` functionality to the including class. # @@ -107,13 +108,9 @@ def as_indexed_json(options={}) # Delegate important methods to the `__elasticsearch__` proxy, unless they are defined already # class << self - delegate :search, to: :__elasticsearch__ unless respond_to?(:search) - delegate :mapping, to: :__elasticsearch__ unless respond_to?(:mapping) - delegate :mappings, to: :__elasticsearch__ unless respond_to?(:mappings) - delegate :settings, to: :__elasticsearch__ unless respond_to?(:settings) - delegate :index_name, to: :__elasticsearch__ unless respond_to?(:index_name) - delegate :document_type, to: :__elasticsearch__ unless respond_to?(:document_type) - delegate :import, to: :__elasticsearch__ unless respond_to?(:import) + METHODS.each do |method| + delegate method, to: :__elasticsearch__ unless self.public_instance_methods.include?(method) + end end # Mix the importing module into the proxy diff --git a/elasticsearch-model/test/unit/module_test.rb b/elasticsearch-model/test/unit/module_test.rb index 289f6e3b6..a429b3d11 100644 --- a/elasticsearch-model/test/unit/module_test.rb +++ b/elasticsearch-model/test/unit/module_test.rb @@ -22,6 +22,11 @@ class Elasticsearch::Model::ModuleTest < Test::Unit::TestCase context "when included in module/class, " do class ::DummyIncludingModel; end + class ::DummyIncludingModelWithSearchMethodDefined + def self.search(query, options={}) + "SEARCH" + end + end should "include and set up the proxy" do DummyIncludingModel.__send__ :include, Elasticsearch::Model @@ -40,6 +45,12 @@ class ::DummyIncludingModel; end assert_respond_to DummyIncludingModel, :document_type assert_respond_to DummyIncludingModel, :import end + + should "not override existing method" do + DummyIncludingModelWithSearchMethodDefined.__send__ :include, Elasticsearch::Model + + assert_equal 'SEARCH', DummyIncludingModelWithSearchMethodDefined.search('foo') + end end end From 6e199e43234a076654233d585b9d450ae4ef105a Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 25 Mar 2014 19:22:30 +0100 Subject: [PATCH 035/582] [STORE] Added the blank skeleton of the "elasticsearch-persistence" gem --- elasticsearch-persistence/.gitignore | 17 ++++++ elasticsearch-persistence/Gemfile | 4 ++ elasticsearch-persistence/LICENSE.txt | 13 +++++ elasticsearch-persistence/README.md | 21 +++++++ elasticsearch-persistence/Rakefile | 57 +++++++++++++++++++ .../elasticsearch-persistence.gemspec | 39 +++++++++++++ .../lib/elasticsearch/persistence.rb | 7 +++ .../lib/elasticsearch/persistence/version.rb | 5 ++ elasticsearch-persistence/test/test_helper.rb | 42 ++++++++++++++ 9 files changed, 205 insertions(+) create mode 100644 elasticsearch-persistence/.gitignore create mode 100644 elasticsearch-persistence/Gemfile create mode 100644 elasticsearch-persistence/LICENSE.txt create mode 100644 elasticsearch-persistence/README.md create mode 100644 elasticsearch-persistence/Rakefile create mode 100644 elasticsearch-persistence/elasticsearch-persistence.gemspec create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence.rb create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/version.rb create mode 100644 elasticsearch-persistence/test/test_helper.rb diff --git a/elasticsearch-persistence/.gitignore b/elasticsearch-persistence/.gitignore new file mode 100644 index 000000000..d87d4be66 --- /dev/null +++ b/elasticsearch-persistence/.gitignore @@ -0,0 +1,17 @@ +*.gem +*.rbc +.bundle +.config +.yardoc +Gemfile.lock +InstalledFiles +_yardoc +coverage +doc/ +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp diff --git a/elasticsearch-persistence/Gemfile b/elasticsearch-persistence/Gemfile new file mode 100644 index 000000000..a60150cf4 --- /dev/null +++ b/elasticsearch-persistence/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in elasticsearch-persistence.gemspec +gemspec diff --git a/elasticsearch-persistence/LICENSE.txt b/elasticsearch-persistence/LICENSE.txt new file mode 100644 index 000000000..489007102 --- /dev/null +++ b/elasticsearch-persistence/LICENSE.txt @@ -0,0 +1,13 @@ +Copyright (c) 2014 Elasticsearch + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md new file mode 100644 index 000000000..b0eda41b0 --- /dev/null +++ b/elasticsearch-persistence/README.md @@ -0,0 +1,21 @@ +# Elasticsearch::Persistence + +WIP> Persistence layer for Ruby domain objects using the Repository and ActiveRecord patterns + +## License + +This software is licensed under the Apache 2 license, quoted below. + + Copyright (c) 2014 Elasticsearch + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/elasticsearch-persistence/Rakefile b/elasticsearch-persistence/Rakefile new file mode 100644 index 000000000..303f43997 --- /dev/null +++ b/elasticsearch-persistence/Rakefile @@ -0,0 +1,57 @@ +require "bundler/gem_tasks" + +desc "Run unit tests" +task :default => 'test:unit' +task :test => 'test:unit' + +# ----- Test tasks ------------------------------------------------------------ + +require 'rake/testtask' +namespace :test do + task :ci_reporter do + ENV['CI_REPORTS'] ||= 'tmp/reports' + require 'ci/reporter/rake/minitest' + Rake::Task['ci:setup:minitest'].invoke + end + + Rake::TestTask.new(:unit) do |test| + Rake::Task['test:ci_reporter'].invoke if ENV['CI'] + test.libs << 'lib' << 'test' + test.test_files = FileList["test/unit/**/*_test.rb"] + # test.verbose = true + # test.warning = true + end + + Rake::TestTask.new(:integration) do |test| + Rake::Task['test:ci_reporter'].invoke if ENV['CI'] + test.libs << 'lib' << 'test' + test.test_files = FileList["test/integration/**/*_test.rb"] + end + + Rake::TestTask.new(:all) do |test| + Rake::Task['test:ci_reporter'].invoke if ENV['CI'] + test.libs << 'lib' << 'test' + test.test_files = FileList["test/unit/**/*_test.rb", "test/integration/**/*_test.rb"] + end +end + +# ----- Documentation tasks --------------------------------------------------- + +require 'yard' +YARD::Rake::YardocTask.new(:doc) do |t| + t.options = %w| --embed-mixins --markup=markdown | +end + +# ----- Code analysis tasks --------------------------------------------------- + +if defined?(RUBY_VERSION) && RUBY_VERSION > '1.9' + begin + require 'cane/rake_task' + Cane::RakeTask.new(:quality) do |cane| + cane.abc_max = 15 + cane.style_measure = 120 + end + rescue LoadError + warn "cane not available, quality task not provided." + end +end diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec new file mode 100644 index 000000000..2a656f6b6 --- /dev/null +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -0,0 +1,39 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'elasticsearch/persistence/version' + +Gem::Specification.new do |s| + s.name = "elasticsearch-persistence" + s.version = Elasticsearch::Persistence::VERSION + s.authors = ["Karel Minarik"] + s.email = ["karel.minarik@elasticsearch.org"] + s.description = "Persistence layer for Ruby models and Elasticsearch." + s.summary = "Persistence layer for Ruby models and Elasticsearch." + s.homepage = "https://github.com/elasticsearch/elasticsearch-rails/" + s.license = "Apache 2" + + s.files = `git ls-files -z`.split("\x0") + s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) } + s.test_files = s.files.grep(%r{^(test|spec|features)/}) + s.require_paths = ["lib"] + + s.extra_rdoc_files = [ "README.md", "LICENSE.txt" ] + s.rdoc_options = [ "--charset=UTF-8" ] + + s.add_development_dependency "bundler", "~> 1.5" + s.add_development_dependency "rake" + + s.add_development_dependency "shoulda-context" + s.add_development_dependency "mocha" + s.add_development_dependency "turn" + s.add_development_dependency "yard" + s.add_development_dependency "ruby-prof" + s.add_development_dependency "pry" + s.add_development_dependency "ci_reporter" + + if defined?(RUBY_VERSION) && RUBY_VERSION > '1.9' + s.add_development_dependency "simplecov" + s.add_development_dependency "cane" + end +end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence.rb b/elasticsearch-persistence/lib/elasticsearch/persistence.rb new file mode 100644 index 000000000..03147e68e --- /dev/null +++ b/elasticsearch-persistence/lib/elasticsearch/persistence.rb @@ -0,0 +1,7 @@ +require "elasticsearch/persistence/version" + +module Elasticsearch + module Persistence + # Your code goes here... + end +end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb new file mode 100644 index 000000000..cf1e84452 --- /dev/null +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb @@ -0,0 +1,5 @@ +module Elasticsearch + module Persistence + VERSION = "0.0.1" + end +end diff --git a/elasticsearch-persistence/test/test_helper.rb b/elasticsearch-persistence/test/test_helper.rb new file mode 100644 index 000000000..5a76e67ae --- /dev/null +++ b/elasticsearch-persistence/test/test_helper.rb @@ -0,0 +1,42 @@ +RUBY_1_8 = defined?(RUBY_VERSION) && RUBY_VERSION < '1.9' + +exit(0) if RUBY_1_8 + +require 'simplecov' and SimpleCov.start { add_filter "/test|test_/" } if ENV["COVERAGE"] + +# Register `at_exit` handler for integration tests shutdown. +# MUST be called before requiring `test/unit`. +at_exit { Elasticsearch::Test::IntegrationTestCase.__run_at_exit_hooks } if ENV['SERVER'] + +require 'test/unit' +require 'shoulda-context' +require 'mocha/setup' +require 'turn' unless ENV["TM_FILEPATH"] || ENV["NOTURN"] || RUBY_1_8 + +require 'ansi' +require 'oj' + +require 'elasticsearch/extensions/test/cluster' +require 'elasticsearch/extensions/test/startup_shutdown' + +require 'elasticsearch/persistence' + +module Elasticsearch + module Test + class IntegrationTestCase < ::Test::Unit::TestCase + extend Elasticsearch::Extensions::Test::StartupShutdown + + startup { Elasticsearch::Extensions::Test::Cluster.start(nodes: 1) if ENV['SERVER'] and not Elasticsearch::Extensions::Test::Cluster.running? } + shutdown { Elasticsearch::Extensions::Test::Cluster.stop if ENV['SERVER'] && started? } + context "IntegrationTest" do; should "noop on Ruby 1.8" do; end; end if RUBY_1_8 + + def setup + tracer = ::Logger.new(STDERR) + tracer.formatter = lambda { |s, d, p, m| "#{m.gsub(/^.*$/) { |n| ' ' + n }.ansi(:faint)}\n" } + Elasticsearch::Persistence.client = Elasticsearch::Client.new \ + host: "localhost:#{(ENV['TEST_CLUSTER_PORT'] || 9250)}", + tracer: (ENV['QUIET'] ? nil : tracer) + end + end + end +end From 4488ea570c10f8af59338135890a29a288ff20e4 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 25 Mar 2014 21:11:22 +0100 Subject: [PATCH 036/582] [STORE] Added the Persistence::Repository module and client integration --- .../elasticsearch-persistence.gemspec | 4 +++ .../lib/elasticsearch/persistence.rb | 21 +++++++++++++-- .../lib/elasticsearch/persistence/client.rb | 15 +++++++++++ .../elasticsearch/persistence/repository.rb | 8 ++++++ .../test/unit/persistence_test.rb | 23 ++++++++++++++++ .../test/unit/repository_client_test.rb | 27 +++++++++++++++++++ .../test/unit/repository_module_test.rb | 6 +++++ 7 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/client.rb create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb create mode 100644 elasticsearch-persistence/test/unit/persistence_test.rb create mode 100644 elasticsearch-persistence/test/unit/repository_client_test.rb create mode 100644 elasticsearch-persistence/test/unit/repository_module_test.rb diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index 2a656f6b6..dddd98849 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -21,9 +21,13 @@ Gem::Specification.new do |s| s.extra_rdoc_files = [ "README.md", "LICENSE.txt" ] s.rdoc_options = [ "--charset=UTF-8" ] + s.add_dependency "elasticsearch", '> 0.4' + s.add_development_dependency "bundler", "~> 1.5" s.add_development_dependency "rake" + s.add_development_dependency "elasticsearch-extensions" + s.add_development_dependency "shoulda-context" s.add_development_dependency "mocha" s.add_development_dependency "turn" diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence.rb b/elasticsearch-persistence/lib/elasticsearch/persistence.rb index 03147e68e..1e060a315 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence.rb @@ -1,7 +1,24 @@ -require "elasticsearch/persistence/version" +require 'elasticsearch' + +require 'elasticsearch/persistence/version' + +require 'elasticsearch/persistence/client' +require 'elasticsearch/persistence/repository' module Elasticsearch module Persistence - # Your code goes here... + + # :nodoc: + module ClassMethods + def client + @client ||= Elasticsearch::Client.new + end + + def client=(client) + @client = client + end + end + + extend ClassMethods end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/client.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/client.rb new file mode 100644 index 000000000..c7a8bfa51 --- /dev/null +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/client.rb @@ -0,0 +1,15 @@ +module Elasticsearch + module Persistence + + module Client + def client client=nil + @client ||= Elasticsearch::Persistence.client + end + + def client=(client) + @client = client + end + end + + end +end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb new file mode 100644 index 000000000..2fff43e22 --- /dev/null +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -0,0 +1,8 @@ +module Elasticsearch + module Persistence + + module Repository + include Elasticsearch::Persistence::Client + end + end +end diff --git a/elasticsearch-persistence/test/unit/persistence_test.rb b/elasticsearch-persistence/test/unit/persistence_test.rb new file mode 100644 index 000000000..9381203d3 --- /dev/null +++ b/elasticsearch-persistence/test/unit/persistence_test.rb @@ -0,0 +1,23 @@ +require 'test_helper' + +class Elasticsearch::Persistence::ModuleTest < Test::Unit::TestCase + context "The Persistence module" do + + context "client" do + should "have a default client" do + client = Elasticsearch::Persistence.client + assert_not_nil client + assert_instance_of Elasticsearch::Transport::Client, client + end + + should "allow to set a client" do + begin + Elasticsearch::Persistence.client = "Foobar" + assert_equal "Foobar", Elasticsearch::Persistence.client + ensure + Elasticsearch::Persistence.client = nil + end + end + end + end +end diff --git a/elasticsearch-persistence/test/unit/repository_client_test.rb b/elasticsearch-persistence/test/unit/repository_client_test.rb new file mode 100644 index 000000000..246631afc --- /dev/null +++ b/elasticsearch-persistence/test/unit/repository_client_test.rb @@ -0,0 +1,27 @@ +require 'test_helper' + +class Elasticsearch::Persistence::RepositoryClientTest < Test::Unit::TestCase + context "A repository client" do + class DummyReposistory + include Elasticsearch::Persistence::Repository + end + + setup do + @shoulda_subject = DummyReposistory.new + end + + should "have a default client" do + assert_not_nil subject.client + assert_instance_of Elasticsearch::Transport::Client, subject.client + end + + should "allow to set a client" do + begin + subject.client = "Foobar" + assert_equal "Foobar", subject.client + ensure + subject.client = nil + end + end + end +end diff --git a/elasticsearch-persistence/test/unit/repository_module_test.rb b/elasticsearch-persistence/test/unit/repository_module_test.rb new file mode 100644 index 000000000..a31d7ac84 --- /dev/null +++ b/elasticsearch-persistence/test/unit/repository_module_test.rb @@ -0,0 +1,6 @@ +require 'test_helper' + +class Elasticsearch::Persistence::RepositoryModuleTest < Test::Unit::TestCase + context "The repository module" do + end +end From 48d7132245f836f0aa04d2778a574414cb3b75bd Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Sat, 29 Mar 2014 10:56:21 +0100 Subject: [PATCH 037/582] [STORE] Added the default `Repository::Class` for convenience Instead of: class MyRepository include Elasticsearch::Persistence::Repository end repository = MyRepository.new you can do: repository = Elasticsearch::Persistence::Repository.new The module function `new` returns an Elasticsearch::Persistence::Repository::Class instance. --- .../lib/elasticsearch/persistence.rb | 2 ++ .../elasticsearch/persistence/repository.rb | 4 +++ .../persistence/repository/class.rb | 18 +++++++++++ .../test/unit/repository_class_test.rb | 32 +++++++++++++++++++ 4 files changed, 56 insertions(+) create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb create mode 100644 elasticsearch-persistence/test/unit/repository_class_test.rb diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence.rb b/elasticsearch-persistence/lib/elasticsearch/persistence.rb index 1e060a315..021c97473 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence.rb @@ -5,6 +5,8 @@ require 'elasticsearch/persistence/client' require 'elasticsearch/persistence/repository' +require 'elasticsearch/persistence/repository/class' + module Elasticsearch module Persistence diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index 2fff43e22..c7e8a1d49 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -3,6 +3,10 @@ module Persistence module Repository include Elasticsearch::Persistence::Client + + def new(options={}, &block) + Elasticsearch::Persistence::Repository::Class.new options, &block + end; module_function :new end end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb new file mode 100644 index 000000000..7ff11e828 --- /dev/null +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb @@ -0,0 +1,18 @@ +module Elasticsearch + module Persistence + module Repository + + class Class + include Elasticsearch::Persistence::Repository + + attr_reader :options + + def initialize(options={}, &block) + @options = options + block.arity < 1 ? instance_eval(&block) : block.call(self) if block_given? + end + end + + end + end +end diff --git a/elasticsearch-persistence/test/unit/repository_class_test.rb b/elasticsearch-persistence/test/unit/repository_class_test.rb new file mode 100644 index 000000000..b20ac3e8b --- /dev/null +++ b/elasticsearch-persistence/test/unit/repository_class_test.rb @@ -0,0 +1,32 @@ +require 'test_helper' + +class Elasticsearch::Persistence::RepositoryClassTest < Test::Unit::TestCase + context "The default repository class" do + + should "be created from the module" do + repository = Elasticsearch::Persistence::Repository.new + assert_instance_of Elasticsearch::Persistence::Repository::Class, repository + end + + should "store and access the options" do + repository = Elasticsearch::Persistence::Repository::Class.new foo: 'bar' + assert_equal 'bar', repository.options[:foo] + end + + should "instance eval a passed block" do + $foo = 100 + repository = Elasticsearch::Persistence::Repository::Class.new() { $foo += 1 } + assert_equal 101, $foo + end + + should "call a passed block with self" do + foo = 100 + repository = Elasticsearch::Persistence::Repository::Class.new do |r| + assert_instance_of Elasticsearch::Persistence::Repository::Class, r + foo += 1 + end + assert_equal 101, foo + end + + end +end From e5d9ffbe51e0674ee49ddafb5dda783fde38f3d5 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Sat, 29 Mar 2014 14:11:23 +0100 Subject: [PATCH 038/582] [STORE] Added the `Naming` module Provides getting Ruby class from Elasticsearch type and vice versa, ID from the document (Hash), as well as setting the `klass` for the whole Repository instance. --- .../elasticsearch-persistence.gemspec | 1 + .../lib/elasticsearch/persistence.rb | 3 + .../elasticsearch/persistence/repository.rb | 1 + .../persistence/repository/naming.rb | 32 ++++++++++ .../test/unit/repository_naming_test.rb | 64 +++++++++++++++++++ 5 files changed, 101 insertions(+) create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb create mode 100644 elasticsearch-persistence/test/unit/repository_naming_test.rb diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index dddd98849..aa7647452 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -22,6 +22,7 @@ Gem::Specification.new do |s| s.rdoc_options = [ "--charset=UTF-8" ] s.add_dependency "elasticsearch", '> 0.4' + s.add_dependency "active_support" s.add_development_dependency "bundler", "~> 1.5" s.add_development_dependency "rake" diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence.rb b/elasticsearch-persistence/lib/elasticsearch/persistence.rb index 021c97473..f629f0f58 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence.rb @@ -1,8 +1,11 @@ require 'elasticsearch' +require 'active_support/inflector' + require 'elasticsearch/persistence/version' require 'elasticsearch/persistence/client' +require 'elasticsearch/persistence/repository/naming' require 'elasticsearch/persistence/repository' require 'elasticsearch/persistence/repository/class' diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index c7e8a1d49..7855db7da 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -3,6 +3,7 @@ module Persistence module Repository include Elasticsearch::Persistence::Client + include Elasticsearch::Persistence::Repository::Naming def new(options={}, &block) Elasticsearch::Persistence::Repository::Class.new options, &block diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb new file mode 100644 index 000000000..1d00a575d --- /dev/null +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb @@ -0,0 +1,32 @@ +module Elasticsearch + module Persistence + module Repository + + module Naming + def klass + @klass + end + + def klass=klass + @klass = klass + end + + def __get_klass_from_type(type) + klass = type.classify + klass.constantize + rescue NameError => e + raise NameError, "Attempted to get class '#{klass}' from the '#{type}' type, but no such class can be found." + end + + def __get_type_from_class(klass) + klass.to_s.underscore + end + + def __get_id_from_document(document) + document[:id] || document['id'] || document[:_id] || document['_id'] + end + end + + end + end +end diff --git a/elasticsearch-persistence/test/unit/repository_naming_test.rb b/elasticsearch-persistence/test/unit/repository_naming_test.rb new file mode 100644 index 000000000..24b290177 --- /dev/null +++ b/elasticsearch-persistence/test/unit/repository_naming_test.rb @@ -0,0 +1,64 @@ +require 'test_helper' + +class Elasticsearch::Persistence::RepositoryNamingTest < Test::Unit::TestCase + context "The repository naming" do + # Fake class for the naming tests + class ::Foobar; end + class ::FooBar; end + module ::Foo; class Bar; end; end + + setup do + @shoulda_subject = Class.new() { include Elasticsearch::Persistence::Repository::Naming }.new + end + + context "get Ruby class from the Elasticsearch type" do + should "get a simple class" do + assert_equal Foobar, subject.__get_klass_from_type('foobar') + end + should "get a camelcased class" do + assert_equal FooBar, subject.__get_klass_from_type('foo_bar') + end + should "get a namespaced class" do + assert_equal Foo::Bar, subject.__get_klass_from_type('foo/bar') + end + should "re-raise a NameError exception" do + assert_raise NameError do + subject.__get_klass_from_type('foobarbazbam') + end + end + end + + context "get Elasticsearch type from the Ruby class" do + should "encode a simple class" do + assert_equal 'foobar', subject.__get_type_from_class(Foobar) + end + should "encode a camelcased class" do + assert_equal 'foo_bar', subject.__get_type_from_class(FooBar) + end + should "encode a namespaced class" do + assert_equal 'foo/bar', subject.__get_type_from_class(Foo::Bar) + end + end + + context "get an ID from the document" do + should "get an ID from Hash" do + assert_equal 1, subject.__get_id_from_document(id: 1) + assert_equal 1, subject.__get_id_from_document(_id: 1) + assert_equal 1, subject.__get_id_from_document('id' => 1) + assert_equal 1, subject.__get_id_from_document('_id' => 1) + end + end + + context " document class name" do + should "be nil by default" do + assert_nil subject.klass + end + + should "be settable" do + subject.klass = Foobar + assert_equal Foobar, subject.klass + end + end + + end +end From 7165f3d0075977492c3718cd69f4013b98440214 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Sat, 29 Mar 2014 16:37:00 +0100 Subject: [PATCH 039/582] [STORE] Added the `Serialize` module The repository uses two symmetric methods: `serialize` and `deserialize` to convert documents when: 1. passing them to the storage, 2. initializing Ruby objects when retrieving documents from storage Every repository can easily customize (overload) these methods, to provide (de)serialization for complex use-cases, such as storing PDF files or images in the storage. See: * https://www.braintreepayments.com/braintrust/untangle-domain-and-persistence-logic-with-curator * https://github.com/braintree/curator/blob/master/lib/curator/repository.rb --- .../lib/elasticsearch/persistence.rb | 1 + .../elasticsearch/persistence/repository.rb | 1 + .../persistence/repository/serialize.rb | 18 ++++++ .../test/unit/repository_serialize_test.rb | 57 +++++++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb create mode 100644 elasticsearch-persistence/test/unit/repository_serialize_test.rb diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence.rb b/elasticsearch-persistence/lib/elasticsearch/persistence.rb index f629f0f58..6975f358e 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence.rb @@ -6,6 +6,7 @@ require 'elasticsearch/persistence/client' require 'elasticsearch/persistence/repository/naming' +require 'elasticsearch/persistence/repository/serialize' require 'elasticsearch/persistence/repository' require 'elasticsearch/persistence/repository/class' diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index 7855db7da..2e216200e 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -4,6 +4,7 @@ module Persistence module Repository include Elasticsearch::Persistence::Client include Elasticsearch::Persistence::Repository::Naming + include Elasticsearch::Persistence::Repository::Serialize def new(options={}, &block) Elasticsearch::Persistence::Repository::Class.new options, &block diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb new file mode 100644 index 000000000..5e4b4be00 --- /dev/null +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb @@ -0,0 +1,18 @@ +module Elasticsearch + module Persistence + module Repository + + module Serialize + def serialize(document) + document.to_hash + end + + def deserialize(document) + _klass = klass || __get_klass_from_type(document['_type']) + _klass.new document['_source'] + end + end + + end + end +end diff --git a/elasticsearch-persistence/test/unit/repository_serialize_test.rb b/elasticsearch-persistence/test/unit/repository_serialize_test.rb new file mode 100644 index 000000000..48d0323d8 --- /dev/null +++ b/elasticsearch-persistence/test/unit/repository_serialize_test.rb @@ -0,0 +1,57 @@ +require 'test_helper' + +class Elasticsearch::Persistence::RepositorySerializeTest < Test::Unit::TestCase + context "The repository serialization" do + class DummyDocument + def to_hash + { foo: 'bar' } + end + end + + class MyDocument; end + + setup do + @shoulda_subject = Class.new() { include Elasticsearch::Persistence::Repository::Serialize }.new + end + + context "serialize" do + should "call #to_hash on passed object" do + document = DummyDocument.new + assert_equal( { foo: 'bar' }, subject.serialize(document)) + end + end + + context "deserialize" do + should "get the class name from #klass" do + subject.expects(:klass) + .returns(MyDocument) + + MyDocument.expects(:new) + + subject.deserialize( {} ) + end + + should "get the class name from Elasticsearch _type" do + subject.expects(:klass) + .returns(nil) + + subject.expects(:__get_klass_from_type) + .returns(MyDocument) + + MyDocument.expects(:new) + + subject.deserialize( {} ) + end + + should "create the class instance with _source attributes" do + subject.expects(:klass).returns(nil) + + subject.expects(:__get_klass_from_type).returns(MyDocument) + + MyDocument.expects(:new).with({ 'foo' => 'bar' }) + + subject.deserialize( {'_source' => { 'foo' => 'bar' } } ) + end + end + end +end From 5938ef051fb322dfe9b1010a8512d90606e64a22 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Sat, 29 Mar 2014 17:28:24 +0100 Subject: [PATCH 040/582] [STORE] Added the `Store` module The `Store` module saves and deletes the documents in Elasticsearch via the `save` and `delete` methods. --- .../lib/elasticsearch/persistence.rb | 1 + .../elasticsearch/persistence/repository.rb | 1 + .../persistence/repository/store.rb | 28 ++++ .../test/unit/repository_store_test.rb | 120 ++++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb create mode 100644 elasticsearch-persistence/test/unit/repository_store_test.rb diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence.rb b/elasticsearch-persistence/lib/elasticsearch/persistence.rb index 6975f358e..60df2c53d 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence.rb @@ -7,6 +7,7 @@ require 'elasticsearch/persistence/client' require 'elasticsearch/persistence/repository/naming' require 'elasticsearch/persistence/repository/serialize' +require 'elasticsearch/persistence/repository/store' require 'elasticsearch/persistence/repository' require 'elasticsearch/persistence/repository/class' diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index 2e216200e..8877512be 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -5,6 +5,7 @@ module Repository include Elasticsearch::Persistence::Client include Elasticsearch::Persistence::Repository::Naming include Elasticsearch::Persistence::Repository::Serialize + include Elasticsearch::Persistence::Repository::Store def new(options={}, &block) Elasticsearch::Persistence::Repository::Class.new options, &block diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb new file mode 100644 index 000000000..95cbff9ee --- /dev/null +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb @@ -0,0 +1,28 @@ +module Elasticsearch + module Persistence + module Repository + + module Store + def save(document, options={}) + serialized = serialize(document) + id = __get_id_from_document(serialized) + type = klass || __get_type_from_class(document.class) + client.index( { index: 'test', type: type, id: id, body: serialized }.merge(options) ) + end + + def delete(document, options={}) + if document.is_a?(String) || document.is_a?(Integer) + id = document + type = klass + else + serialized = serialize(document) + id = __get_id_from_document(serialized) + type = klass || __get_type_from_class(document.class) + end + client.delete( { index: 'test', type: type, id: id }.merge(options) ) + end + end + + end + end +end diff --git a/elasticsearch-persistence/test/unit/repository_store_test.rb b/elasticsearch-persistence/test/unit/repository_store_test.rb new file mode 100644 index 000000000..463bf2915 --- /dev/null +++ b/elasticsearch-persistence/test/unit/repository_store_test.rb @@ -0,0 +1,120 @@ +require 'test_helper' + +class Elasticsearch::Persistence::RepositoryStoreTest < Test::Unit::TestCase + context "The repository store" do + class MyDocument; end + + setup do + @shoulda_subject = Class.new() { include Elasticsearch::Persistence::Repository::Store }.new + end + + context "save" do + should "serialize the document, get type from klass and index it" do + subject.expects(:serialize).returns({foo: 'bar'}) + subject.expects(:klass).returns('foo_type') + subject.expects(:__get_id_from_document).returns('1') + + client = mock + client.expects(:index).with do |arguments| + assert_equal 'foo_type', arguments[:type] + assert_equal '1', arguments[:id] + assert_equal({foo: 'bar'}, arguments[:body]) + end + subject.expects(:client).returns(client) + + subject.save({foo: 'bar'}) + end + + should "serialize the document, get type from document class and index it" do + subject.expects(:serialize).returns({foo: 'bar'}) + subject.expects(:klass).returns(nil) + subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') + subject.expects(:__get_id_from_document).returns('1') + + client = mock + client.expects(:index).with do |arguments| + assert_equal 'my_document', arguments[:type] + assert_equal '1', arguments[:id] + assert_equal({foo: 'bar'}, arguments[:body]) + end + subject.expects(:client).returns(client) + + subject.save(MyDocument.new) + end + + should "pass the options to the client" do + subject.expects(:serialize).returns({foo: 'bar'}) + subject.expects(:klass).returns('foo') + subject.expects(:__get_id_from_document).returns('1') + + client = mock + client.expects(:index).with do |arguments| + assert_equal 'bambam', arguments[:routing] + end + subject.expects(:client).returns(client) + + subject.save({foo: 'bar'}, routing: 'bambam') + end + end + + context "delete" do + should "get type from klass when passed only ID" do + subject.expects(:serialize).never + subject.expects(:klass).returns('foo_type') + subject.expects(:__get_id_from_document).never + + client = mock + client.expects(:delete).with do |arguments| + assert_equal 'foo_type', arguments[:type] + assert_equal '1', arguments[:id] + end + subject.expects(:client).returns(client) + + subject.delete('1') + end + + should "get ID from document and type from klass when passed a document" do + subject.expects(:serialize).returns({id: '1', foo: 'bar'}) + subject.expects(:klass).returns('foo_type') + subject.expects(:__get_id_from_document).with({id: '1', foo: 'bar'}).returns('1') + + client = mock + client.expects(:delete).with do |arguments| + assert_equal 'foo_type', arguments[:type] + assert_equal '1', arguments[:id] + end + subject.expects(:client).returns(client) + + subject.delete({id: '1', foo: 'bar'}) + end + + should "get ID and type from document when passed a document" do + subject.expects(:serialize).returns({id: '1', foo: 'bar'}) + subject.expects(:klass).returns(nil) + subject.expects(:__get_id_from_document).with({id: '1', foo: 'bar'}).returns('1') + subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') + + client = mock + client.expects(:delete).with do |arguments| + assert_equal 'my_document', arguments[:type] + assert_equal '1', arguments[:id] + end + subject.expects(:client).returns(client) + + subject.delete(MyDocument.new) + end + + should "pass the options to the client" do + subject.expects(:klass).returns('foo') + + client = mock + client.expects(:delete).with do |arguments| + assert_equal 'bambam', arguments[:routing] + end + subject.expects(:client).returns(client) + + subject.delete('1', routing: 'bambam') + end + end + end +end From 41a36952ac2bc41fe9748498461754fd1d4ac890 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 31 Mar 2014 12:35:13 +0200 Subject: [PATCH 041/582] [STORE] Added the `Find` module The module provides method to find one or multiple documents and to check for documents existence. The methods return `deserialize`-d Ruby objects based on `klass` or the document Elasticsearch `_type`. Missing documents are kept in the resulting Array as `nil` objects. --- .../lib/elasticsearch/persistence.rb | 1 + .../elasticsearch/persistence/repository.rb | 1 + .../persistence/repository/find.rb | 43 +++ .../test/unit/repository_find_test.rb | 311 ++++++++++++++++++ 4 files changed, 356 insertions(+) create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb create mode 100644 elasticsearch-persistence/test/unit/repository_find_test.rb diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence.rb b/elasticsearch-persistence/lib/elasticsearch/persistence.rb index 60df2c53d..19c997984 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence.rb @@ -8,6 +8,7 @@ require 'elasticsearch/persistence/repository/naming' require 'elasticsearch/persistence/repository/serialize' require 'elasticsearch/persistence/repository/store' +require 'elasticsearch/persistence/repository/find' require 'elasticsearch/persistence/repository' require 'elasticsearch/persistence/repository/class' diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index 8877512be..8ead25dd3 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -6,6 +6,7 @@ module Repository include Elasticsearch::Persistence::Repository::Naming include Elasticsearch::Persistence::Repository::Serialize include Elasticsearch::Persistence::Repository::Store + include Elasticsearch::Persistence::Repository::Find def new(options={}, &block) Elasticsearch::Persistence::Repository::Class.new options, &block diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb new file mode 100644 index 000000000..07ef337a2 --- /dev/null +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb @@ -0,0 +1,43 @@ +module Elasticsearch + module Persistence + module Repository + class DocumentNotFound < StandardError; end + + module Find + def find(*args) + options = args.last.is_a?(Hash) ? args.pop : {} + ids = args + + if args.size == 1 + id = args.pop + id.is_a?(Array) ? __find_many(id, options) : __find_one(id, options) + else + __find_many args, options + end + end + + def exists?(id, options={}) + type = (klass ? __get_type_from_class(klass) : '_all') + client.exists( { index: 'test', type: type, id: id }.merge(options) ) + end + + def __find_one(id, options={}) + type = (klass ? __get_type_from_class(klass) : '_all') + document = client.get( { index: 'test', type: type, id: id }.merge(options) ) + + deserialize(document) + rescue Elasticsearch::Transport::Transport::Errors::NotFound => e + raise DocumentNotFound, e.message, caller + end + + def __find_many(ids, options={}) + type = (klass ? __get_type_from_class(klass) : '_all') + documents = client.mget( { index: 'test', type: type, body: { ids: ids } }.merge(options) ) + + documents['docs'].map { |document| document['found'] ? deserialize(document) : nil } + end + end + + end + end +end diff --git a/elasticsearch-persistence/test/unit/repository_find_test.rb b/elasticsearch-persistence/test/unit/repository_find_test.rb new file mode 100644 index 000000000..9fa8bb87e --- /dev/null +++ b/elasticsearch-persistence/test/unit/repository_find_test.rb @@ -0,0 +1,311 @@ +require 'test_helper' + +class Elasticsearch::Persistence::RepositoryFindTest < Test::Unit::TestCase + class MyDocument; end + + context "The repository" do + setup do + @shoulda_subject = Class.new() { include Elasticsearch::Persistence::Repository::Find }.new + + @client = mock + @shoulda_subject.stubs(:klass).returns(nil) + @shoulda_subject.stubs(:client).returns(@client) + end + + context "find method" do + should "find one document when passed a single, literal ID" do + subject.expects(:__find_one).with(1, {}) + subject.find(1) + end + + should "find multiple documents when passed multiple IDs" do + subject.expects(:__find_many).with([1, 2], {}) + subject.find(1, 2) + end + + should "find multiple documents when passed an array of IDs" do + subject.expects(:__find_many).with([1, 2], {}) + subject.find([1, 2]) + end + + should "pass the options" do + subject.expects(:__find_one).with(1, { foo: 'bar' }) + subject.find(1, foo: 'bar') + + subject.expects(:__find_many).with([1, 2], { foo: 'bar' }) + subject.find([1, 2], foo: 'bar') + + subject.expects(:__find_many).with([1, 2], { foo: 'bar' }) + subject.find(1, 2, foo: 'bar') + end + end + + context "'exists?' method" do + should "return false when the document does not exist" do + @client.expects(:exists).returns(false) + assert_equal false, subject.exists?('1') + end + + should "return whether document for klass exists" do + subject.expects(:klass).returns(MyDocument).at_least_once + subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') + + @client + .expects(:exists) + .with do |arguments| + assert_equal 'my_document', arguments[:type] + assert_equal '1', arguments[:id] + end + .returns(true) + + assert_equal true, subject.exists?('1') + end + + should "return whether document exists" do + subject.expects(:klass).returns(nil) + subject.expects(:__get_type_from_class).never + + @client + .expects(:exists) + .with do |arguments| + assert_equal '_all', arguments[:type] + assert_equal '1', arguments[:id] + end + .returns(true) + + assert_equal true, subject.exists?('1') + end + + should "pass options to the client" do + @client.expects(:exists).with do |arguments| + assert_equal 'bambam', arguments[:routing] + end + + subject.exists? '1', routing: 'bambam' + end + end + + context "'__find_one' method" do + should "find document based on klass and return a deserialized object" do + subject.expects(:klass).returns(MyDocument).at_least_once + subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') + + subject.expects(:deserialize).with({'_source' => {'foo' => 'bar'}}).returns(MyDocument.new) + + @client + .expects(:get) + .with do |arguments| + assert_equal 'my_document', arguments[:type] + assert_equal '1', arguments[:id] + end + .returns({'_source' => { 'foo' => 'bar' }}) + + assert_instance_of MyDocument, subject.__find_one('1') + end + + should "find document and return a deserialized object" do + subject.expects(:klass).returns(nil).at_least_once + subject.expects(:__get_type_from_class).never + + subject.expects(:deserialize).with({'_source' => {'foo' => 'bar'}}).returns(MyDocument.new) + + @client + .expects(:get) + .with do |arguments| + assert_equal '_all', arguments[:type] + assert_equal '1', arguments[:id] + end + .returns({'_source' => { 'foo' => 'bar' }}) + + assert_instance_of MyDocument, subject.__find_one('1') + end + + should "raise DocumentNotFound exception when the document cannot be found" do + subject.expects(:klass).returns(nil).at_least_once + + subject.expects(:deserialize).never + + @client + .expects(:get) + .raises(Elasticsearch::Transport::Transport::Errors::NotFound) + + assert_raise Elasticsearch::Persistence::Repository::DocumentNotFound do + subject.__find_one('foobar') + end + end + + should "pass other exceptions" do + subject.expects(:klass).returns(nil).at_least_once + + subject.expects(:deserialize).never + + @client + .expects(:get) + .raises(RuntimeError) + + assert_raise RuntimeError do + subject.__find_one('foobar') + end + end + + should "pass options to the client" do + subject.expects(:klass).returns(nil).at_least_once + subject.expects(:deserialize) + + @client + .expects(:get) + .with do |arguments| + assert_equal 'bambam', arguments[:routing] + end + .returns({'_source' => { 'foo' => 'bar' }}) + + subject.__find_one '1', routing: 'bambam' + end + end + + context "'__find_many' method" do + setup do + @response = {"docs"=> + [ {"_index"=>"test", + "_type"=>"note", + "_id"=>"1", + "_version"=>1, + "found"=>true, + "_source"=>{"id"=>"1", "title"=>"Test 1"}}, + + {"_index"=>"test", + "_type"=>"note", + "_id"=>"2", + "_version"=>1, + "found"=>true, + "_source"=>{"id"=>"2", "title"=>"Test 2"}} + ]} + end + + should "find documents based on klass and return an Array of deserialized objects" do + subject.expects(:klass).returns(MyDocument).at_least_once + subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') + + subject + .expects(:deserialize) + .with(@response['docs'][0]) + .returns(MyDocument.new) + + subject + .expects(:deserialize) + .with(@response['docs'][1]) + .returns(MyDocument.new) + + @client + .expects(:mget) + .with do |arguments| + assert_equal 'my_document', arguments[:type] + assert_equal ['1', '2'], arguments[:body][:ids] + end + .returns(@response) + + results = subject.__find_many(['1', '2']) + assert_instance_of MyDocument, results[0] + assert_instance_of MyDocument, results[1] + end + + should "find documents and return an Array of deserialized objects" do + subject.expects(:klass).returns(nil).at_least_once + subject.expects(:__get_type_from_class).never + + subject + .expects(:deserialize) + .with(@response['docs'][0]) + .returns(MyDocument.new) + + subject + .expects(:deserialize) + .with(@response['docs'][1]) + .returns(MyDocument.new) + + @client + .expects(:mget) + .with do |arguments| + assert_equal '_all', arguments[:type] + assert_equal ['1', '2'], arguments[:body][:ids] + end + .returns(@response) + + results = subject.__find_many(['1', '2']) + + assert_equal 2, results.size + + assert_instance_of MyDocument, results[0] + assert_instance_of MyDocument, results[1] + end + + should "find keep missing documents in the result as nil" do + @response = {"docs"=> + [ {"_index"=>"test", + "_type"=>"note", + "_id"=>"1", + "_version"=>1, + "found"=>true, + "_source"=>{"id"=>"1", "title"=>"Test 1"}}, + + {"_index"=>"test", + "_type"=>"note", + "_id"=>"3", + "_version"=>1, + "found"=>false}, + + {"_index"=>"test", + "_type"=>"note", + "_id"=>"2", + "_version"=>1, + "found"=>true, + "_source"=>{"id"=>"2", "title"=>"Test 2"}} + ]} + + subject.expects(:klass).returns(MyDocument).at_least_once + subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') + + subject + .expects(:deserialize) + .with(@response['docs'][0]) + .returns(MyDocument.new) + + subject + .expects(:deserialize) + .with(@response['docs'][2]) + .returns(MyDocument.new) + + @client + .expects(:mget) + .with do |arguments| + assert_equal 'my_document', arguments[:type] + assert_equal ['1', '3', '2'], arguments[:body][:ids] + end + .returns(@response) + + results = subject.__find_many(['1', '3', '2']) + + assert_equal 3, results.size + + assert_instance_of MyDocument, results[0] + assert_instance_of NilClass, results[1] + assert_instance_of MyDocument, results[2] + end + + should "pass options to the client" do + subject.expects(:klass).returns(nil).at_least_once + subject.expects(:deserialize).twice + + @client + .expects(:mget) + .with do |arguments| + assert_equal 'bambam', arguments[:routing] + end + .returns(@response) + + subject.__find_many ['1', '2'], routing: 'bambam' + end + end + + end +end From 7bcd91d28261a35be00c27b54531f7a672e5fed3 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 31 Mar 2014 17:51:12 +0200 Subject: [PATCH 042/582] [STORE] Added the `Search` module The module provides an interface for getting objects from the repository based on a search query. The results are wrapped in the `Response::Results` instance, which proxies methods to the `results` property. --- .../elasticsearch-persistence.gemspec | 1 + .../lib/elasticsearch/persistence.rb | 3 + .../elasticsearch/persistence/repository.rb | 1 + .../repository/response/results.rb | 54 +++++++++++ .../persistence/repository/search.rb | 23 +++++ .../unit/repository_response_results_test.rb | 94 +++++++++++++++++++ .../test/unit/repository_search_test.rb | 80 ++++++++++++++++ 7 files changed, 256 insertions(+) create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb create mode 100644 elasticsearch-persistence/test/unit/repository_response_results_test.rb create mode 100644 elasticsearch-persistence/test/unit/repository_search_test.rb diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index aa7647452..2260126be 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -23,6 +23,7 @@ Gem::Specification.new do |s| s.add_dependency "elasticsearch", '> 0.4' s.add_dependency "active_support" + s.add_dependency "hashie" s.add_development_dependency "bundler", "~> 1.5" s.add_development_dependency "rake" diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence.rb b/elasticsearch-persistence/lib/elasticsearch/persistence.rb index 19c997984..bbbbb66a8 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence.rb @@ -1,14 +1,17 @@ require 'elasticsearch' +require 'hashie' require 'active_support/inflector' require 'elasticsearch/persistence/version' require 'elasticsearch/persistence/client' +require 'elasticsearch/persistence/repository/response/results' require 'elasticsearch/persistence/repository/naming' require 'elasticsearch/persistence/repository/serialize' require 'elasticsearch/persistence/repository/store' require 'elasticsearch/persistence/repository/find' +require 'elasticsearch/persistence/repository/search' require 'elasticsearch/persistence/repository' require 'elasticsearch/persistence/repository/class' diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index 8ead25dd3..b255fb968 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -7,6 +7,7 @@ module Repository include Elasticsearch::Persistence::Repository::Serialize include Elasticsearch::Persistence::Repository::Store include Elasticsearch::Persistence::Repository::Find + include Elasticsearch::Persistence::Repository::Search def new(options={}, &block) Elasticsearch::Persistence::Repository::Class.new options, &block diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb new file mode 100644 index 000000000..b269076b6 --- /dev/null +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb @@ -0,0 +1,54 @@ +module Elasticsearch + module Persistence + module Repository + module Response + + class Results + include Enumerable + + attr_reader :repository, :response, :response + + def initialize(repository, response, options={}) + @repository = repository + @response = Hashie::Mash.new(response) + @options = options + end + + def method_missing(method_name, *arguments, &block) + results.respond_to?(method_name) ? results.__send__(method_name, *arguments, &block) : super + end + + def respond_to?(method_name, include_private = false) + results.respond_to?(method_name) || super + end + + def total + response['hits']['total'] + end + + def max_score + response['hits']['max_score'] + end + + # Yields [object, hit] pairs to the block + # + def each_with_hit(&block) + results.zip(response['hits']['hits']).each(&block) + end + + # Yields [object, hit] pairs and returns the result + # + def map_with_hit(&block) + results.zip(response['hits']['hits']).map(&block) + end + + def results + @results ||= response['hits']['hits'].map do |document| + repository.deserialize(document.to_hash) + end + end + end + end + end + end +end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb new file mode 100644 index 000000000..1e6c25cf3 --- /dev/null +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb @@ -0,0 +1,23 @@ +module Elasticsearch + module Persistence + module Repository + + module Search + def search(query_or_definition, options={}) + type = (klass ? __get_type_from_class(klass) : nil ) + case + when query_or_definition.respond_to?(:to_hash) + response = client.search( { index: 'test', type: type, body: query_or_definition.to_hash }.merge(options) ) + when query_or_definition.is_a?(String) + response = client.search( { index: 'test', type: type, q: query_or_definition }.merge(options) ) + else + raise ArgumentError, "[!] Pass the search definition as a Hash-like object or pass the query as a String" + + " -- #{query_or_definition.class} given." + end + Response::Results.new(self, response) + end + end + + end + end +end diff --git a/elasticsearch-persistence/test/unit/repository_response_results_test.rb b/elasticsearch-persistence/test/unit/repository_response_results_test.rb new file mode 100644 index 000000000..34a9a3ba4 --- /dev/null +++ b/elasticsearch-persistence/test/unit/repository_response_results_test.rb @@ -0,0 +1,94 @@ +require 'test_helper' + +class Elasticsearch::Persistence::RepositoryResponseResultsTest < Test::Unit::TestCase + include Elasticsearch::Persistence + class MyDocument; end + + context "Response results" do + setup do + @repository = Repository.new + + @response = { "took" => 2, + "timed_out" => false, + "_shards" => {"total" => 5, "successful" => 5, "failed" => 0}, + "hits" => + { "total" => 2, + "max_score" => 0.19, + "hits" => + [{"_index" => "test", + "_type" => "note", + "_id" => "1", + "_score" => 0.19, + "_source" => {"id" => 1, "title" => "Test 1"}}, + + {"_index" => "test", + "_type" => "note", + "_id" => "2", + "_score" => 0.19, + "_source" => {"id" => 2, "title" => "Test 2"}} + ] + } + } + + @shoulda_subject = Repository::Response::Results.new @repository, @response + end + + should "provide the access to the repository" do + assert_instance_of Repository::Class, subject.repository + end + + should "provide the access to the response" do + assert_equal 5, subject.response['_shards']['total'] + end + + should "wrap the response in Hashie::Mash" do + assert_equal 5, subject.response._shards.total + end + + should "return the total" do + assert_equal 2, subject.total + end + + should "return the max_score" do + assert_equal 0.19, subject.max_score + end + + should "delegate methods to results" do + subject.repository + .expects(:deserialize) + .twice + .returns(MyDocument.new) + + assert_equal 2, subject.size + assert_respond_to subject, :each + end + + should "yield each object with hit" do + @shoulda_subject = Repository::Response::Results.new \ + @repository, + { 'hits' => { 'hits' => [{'_id' => '1', 'foo' => 'bar'}] } } + + subject.repository + .expects(:deserialize) + .returns('FOO') + + subject.each_with_hit do |object, hit| + assert_equal 'FOO', object + assert_equal 'bar', hit.foo + end + end + + should "map objects and hits" do + @shoulda_subject = Repository::Response::Results.new \ + @repository, + { 'hits' => { 'hits' => [{'_id' => '1', 'foo' => 'bar'}] } } + + subject.repository + .expects(:deserialize) + .returns('FOO') + + assert_equal ['FOO---bar'], subject.map_with_hit { |object, hit| "#{object}---#{hit.foo}" } + end + end + +end diff --git a/elasticsearch-persistence/test/unit/repository_search_test.rb b/elasticsearch-persistence/test/unit/repository_search_test.rb new file mode 100644 index 000000000..8bcb56f9c --- /dev/null +++ b/elasticsearch-persistence/test/unit/repository_search_test.rb @@ -0,0 +1,80 @@ +require 'test_helper' + +class Elasticsearch::Persistence::RepositorySearchTest < Test::Unit::TestCase + class MyDocument; end + + context "The repository search" do + setup do + @shoulda_subject = Class.new() { include Elasticsearch::Persistence::Repository::Search }.new + + @client = mock + @shoulda_subject.stubs(:klass).returns(nil) + @shoulda_subject.stubs(:client).returns(@client) + end + + should "search in type based on klass" do + subject.expects(:klass).returns(MyDocument).at_least_once + subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') + + @client.expects(:search).with do |arguments| + assert_equal 'test', arguments[:index] + assert_equal 'my_document', arguments[:type] + + assert_equal({foo: 'bar'}, arguments[:body]) + end + + subject.search foo: 'bar' + end + + should "search across all types" do + subject.expects(:klass).returns(nil).at_least_once + subject.expects(:__get_type_from_class).never + + @client.expects(:search).with do |arguments| + assert_equal 'test', arguments[:index] + assert_equal nil, arguments[:type] + + assert_equal({foo: 'bar'}, arguments[:body]) + end + + assert_instance_of Elasticsearch::Persistence::Repository::Response::Results, + subject.search(foo: 'bar') + end + + should "pass options to the client" do + subject.expects(:klass).returns(nil).at_least_once + subject.expects(:__get_type_from_class).never + + @client.expects(:search).twice.with do |arguments| + assert_equal 'bambam', arguments[:routing] + end + + assert_instance_of Elasticsearch::Persistence::Repository::Response::Results, + subject.search( {foo: 'bar'}, { routing: 'bambam' } ) + assert_instance_of Elasticsearch::Persistence::Repository::Response::Results, + subject.search( 'foobar', { routing: 'bambam' } ) + end + + should "search with simple search" do + subject.expects(:klass).returns(nil).at_least_once + subject.expects(:__get_type_from_class).never + + @client.expects(:search).with do |arguments| + assert_equal 'foobar', arguments[:q] + end + + assert_instance_of Elasticsearch::Persistence::Repository::Response::Results, + subject.search('foobar') + end + + should "raise error for incorrect search definitions" do + subject.expects(:klass).returns(nil).at_least_once + subject.expects(:__get_type_from_class).never + + assert_raise ArgumentError do + subject.search 123 + end + end + end + +end From 485a4f917448c7aea90e791c4e0986d5a1135f60 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 31 Mar 2014 20:36:37 +0200 Subject: [PATCH 043/582] [STORE] Added the DSL variant of `klass` setter method So: reposistory.klass = Foobar is equivalent to: repository.klass Foobar --- .../lib/elasticsearch/persistence/repository/naming.rb | 4 ++-- .../test/unit/repository_naming_test.rb | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb index 1d00a575d..6aef96e18 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb @@ -3,8 +3,8 @@ module Persistence module Repository module Naming - def klass - @klass + def klass name=nil + @klass = name || @klass end def klass=klass diff --git a/elasticsearch-persistence/test/unit/repository_naming_test.rb b/elasticsearch-persistence/test/unit/repository_naming_test.rb index 24b290177..babb394e7 100644 --- a/elasticsearch-persistence/test/unit/repository_naming_test.rb +++ b/elasticsearch-persistence/test/unit/repository_naming_test.rb @@ -49,7 +49,7 @@ module ::Foo; class Bar; end; end end end - context " document class name" do + context "document class name" do should "be nil by default" do assert_nil subject.klass end @@ -58,6 +58,11 @@ module ::Foo; class Bar; end; end subject.klass = Foobar assert_equal Foobar, subject.klass end + + should "be settable by DSL" do + subject.klass Foobar + assert_equal Foobar, subject.klass + end end end From c4b1d4147726eba00f6698e80ecf17f011af50ae Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 31 Mar 2014 22:17:38 +0200 Subject: [PATCH 044/582] [STORE] Added the methods from the "elasticsearch-model" gem Included `Elasticsearch::Model::Indexing::ClassMethods` to support setting the index name and document type, and to allow configuring the mappings and settings for the index. See: https://github.com/elasticsearch/elasticsearch-rails/blob/6f4a57a/elasticsearch-model/lib/elasticsearch/model/indexing.rb --- .../elasticsearch-persistence.gemspec | 3 +- .../lib/elasticsearch/persistence.rb | 1 + .../elasticsearch/persistence/repository.rb | 2 + .../persistence/repository/naming.rb | 12 ++++++ .../test/unit/repository_indexing_test.rb | 37 +++++++++++++++++++ .../test/unit/repository_naming_test.rb | 28 ++++++++++++++ 6 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 elasticsearch-persistence/test/unit/repository_indexing_test.rb diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index 2260126be..cb66916ed 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -22,7 +22,8 @@ Gem::Specification.new do |s| s.rdoc_options = [ "--charset=UTF-8" ] s.add_dependency "elasticsearch", '> 0.4' - s.add_dependency "active_support" + s.add_dependency "elasticsearch-model", '>= 0.1' + s.add_dependency "activesupport", '> 3' s.add_dependency "hashie" s.add_development_dependency "bundler", "~> 1.5" diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence.rb b/elasticsearch-persistence/lib/elasticsearch/persistence.rb index bbbbb66a8..5e86351c7 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence.rb @@ -1,4 +1,5 @@ require 'elasticsearch' +require 'elasticsearch/model/indexing' require 'hashie' require 'active_support/inflector' diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index b255fb968..d15e45960 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -9,6 +9,8 @@ module Repository include Elasticsearch::Persistence::Repository::Find include Elasticsearch::Persistence::Repository::Search + include Elasticsearch::Model::Indexing::ClassMethods + def new(options={}, &block) Elasticsearch::Persistence::Repository::Class.new options, &block end; module_function :new diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb index 6aef96e18..82344ad28 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb @@ -11,6 +11,18 @@ def klass=klass @klass = klass end + def index_name name=nil + @index_name = name || @index_name || self.class.to_s.underscore.gsub(/\//, '-') + end + + def index_name=(name) + @index_name = name + end + + def document_type + klass.to_s.underscore + end + def __get_klass_from_type(type) klass = type.classify klass.constantize diff --git a/elasticsearch-persistence/test/unit/repository_indexing_test.rb b/elasticsearch-persistence/test/unit/repository_indexing_test.rb new file mode 100644 index 000000000..5ee22578d --- /dev/null +++ b/elasticsearch-persistence/test/unit/repository_indexing_test.rb @@ -0,0 +1,37 @@ +require 'test_helper' + +class Elasticsearch::Persistence::RepositoryIndexingTest < Test::Unit::TestCase + context "The repository index methods" do + class MyDocument; end + + setup do + @shoulda_subject = Class.new() { include Elasticsearch::Model::Indexing::ClassMethods }.new + @shoulda_subject.stubs(:index_name).returns('my_index') + @shoulda_subject.stubs(:document_type).returns('my_document') + end + + should "have the convenience index management methods" do + %w( create_index! delete_index! refresh_index! ).each do |method| + assert_respond_to subject, method + end + end + + context "mappings" do + should "configure the mappings for the type" do + subject.mappings do + indexes :title + end + + assert_equal( {:"my_document"=>{:properties=>{:title=>{:type=>"string"}}}}, subject.mappings.to_hash ) + end + end + + context "settings" do + should "configure the settings for the index" do + subject.settings foo: 'bar' + assert_equal( {foo: 'bar'}, subject.settings.to_hash) + end + end + + end +end diff --git a/elasticsearch-persistence/test/unit/repository_naming_test.rb b/elasticsearch-persistence/test/unit/repository_naming_test.rb index babb394e7..b7d588bca 100644 --- a/elasticsearch-persistence/test/unit/repository_naming_test.rb +++ b/elasticsearch-persistence/test/unit/repository_naming_test.rb @@ -65,5 +65,33 @@ module ::Foo; class Bar; end; end end end + context "index_name" do + should "default to the class name" do + subject.instance_eval do + def self.class + 'FakeRepository' + end + end + + assert_equal 'fake_repository', subject.index_name + end + + should "be settable" do + subject.index_name = 'foobar1' + assert_equal 'foobar1', subject.index_name + + subject.index_name 'foobar2' + assert_equal 'foobar2', subject.index_name + end + end + + context "document_type" do + should "default to klass" do + assert_equal '', subject.document_type + + subject.klass Foobar + assert_equal 'foobar', subject.document_type + end + end end end From 5160424a5694cfcba5823f71c49ca5bddde1a002 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 31 Mar 2014 22:32:05 +0200 Subject: [PATCH 045/582] [STORE] Refactored the `:index` paramter to use repository `index_name` --- .../persistence/repository/find.rb | 6 ++--- .../persistence/repository/search.rb | 4 +-- .../persistence/repository/store.rb | 4 +-- .../test/unit/repository_find_test.rb | 26 +++++++++++-------- .../unit/repository_response_results_test.rb | 4 +-- .../test/unit/repository_search_test.rb | 1 + .../test/unit/repository_store_test.rb | 7 +++-- 7 files changed, 30 insertions(+), 22 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb index 07ef337a2..15fde4d69 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb @@ -18,12 +18,12 @@ def find(*args) def exists?(id, options={}) type = (klass ? __get_type_from_class(klass) : '_all') - client.exists( { index: 'test', type: type, id: id }.merge(options) ) + client.exists( { index: index_name, type: type, id: id }.merge(options) ) end def __find_one(id, options={}) type = (klass ? __get_type_from_class(klass) : '_all') - document = client.get( { index: 'test', type: type, id: id }.merge(options) ) + document = client.get( { index: index_name, type: type, id: id }.merge(options) ) deserialize(document) rescue Elasticsearch::Transport::Transport::Errors::NotFound => e @@ -32,7 +32,7 @@ def __find_one(id, options={}) def __find_many(ids, options={}) type = (klass ? __get_type_from_class(klass) : '_all') - documents = client.mget( { index: 'test', type: type, body: { ids: ids } }.merge(options) ) + documents = client.mget( { index: index_name, type: type, body: { ids: ids } }.merge(options) ) documents['docs'].map { |document| document['found'] ? deserialize(document) : nil } end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb index 1e6c25cf3..01bc3916c 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb @@ -7,9 +7,9 @@ def search(query_or_definition, options={}) type = (klass ? __get_type_from_class(klass) : nil ) case when query_or_definition.respond_to?(:to_hash) - response = client.search( { index: 'test', type: type, body: query_or_definition.to_hash }.merge(options) ) + response = client.search( { index: index_name, type: type, body: query_or_definition.to_hash }.merge(options) ) when query_or_definition.is_a?(String) - response = client.search( { index: 'test', type: type, q: query_or_definition }.merge(options) ) + response = client.search( { index: index_name, type: type, q: query_or_definition }.merge(options) ) else raise ArgumentError, "[!] Pass the search definition as a Hash-like object or pass the query as a String" + " -- #{query_or_definition.class} given." diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb index 95cbff9ee..ed0320e26 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb @@ -7,7 +7,7 @@ def save(document, options={}) serialized = serialize(document) id = __get_id_from_document(serialized) type = klass || __get_type_from_class(document.class) - client.index( { index: 'test', type: type, id: id, body: serialized }.merge(options) ) + client.index( { index: index_name, type: type, id: id, body: serialized }.merge(options) ) end def delete(document, options={}) @@ -19,7 +19,7 @@ def delete(document, options={}) id = __get_id_from_document(serialized) type = klass || __get_type_from_class(document.class) end - client.delete( { index: 'test', type: type, id: id }.merge(options) ) + client.delete( { index: index_name, type: type, id: id }.merge(options) ) end end diff --git a/elasticsearch-persistence/test/unit/repository_find_test.rb b/elasticsearch-persistence/test/unit/repository_find_test.rb index 9fa8bb87e..617fedf00 100644 --- a/elasticsearch-persistence/test/unit/repository_find_test.rb +++ b/elasticsearch-persistence/test/unit/repository_find_test.rb @@ -9,6 +9,7 @@ class MyDocument; end @client = mock @shoulda_subject.stubs(:klass).returns(nil) + @shoulda_subject.stubs(:index_name).returns('my_index') @shoulda_subject.stubs(:client).returns(@client) end @@ -78,10 +79,11 @@ class MyDocument; end should "pass options to the client" do @client.expects(:exists).with do |arguments| - assert_equal 'bambam', arguments[:routing] + assert_equal 'foobarbam', arguments[:index] + assert_equal 'bambam', arguments[:routing] end - subject.exists? '1', routing: 'bambam' + subject.exists? '1', index: 'foobarbam', routing: 'bambam' end end @@ -155,25 +157,26 @@ class MyDocument; end @client .expects(:get) .with do |arguments| - assert_equal 'bambam', arguments[:routing] + assert_equal 'foobarbam', arguments[:index] + assert_equal 'bambam', arguments[:routing] end .returns({'_source' => { 'foo' => 'bar' }}) - subject.__find_one '1', routing: 'bambam' + subject.__find_one '1', index: 'foobarbam', routing: 'bambam' end end context "'__find_many' method" do setup do @response = {"docs"=> - [ {"_index"=>"test", + [ {"_index"=>"my_index", "_type"=>"note", "_id"=>"1", "_version"=>1, "found"=>true, "_source"=>{"id"=>"1", "title"=>"Test 1"}}, - {"_index"=>"test", + {"_index"=>"my_index", "_type"=>"note", "_id"=>"2", "_version"=>1, @@ -241,20 +244,20 @@ class MyDocument; end should "find keep missing documents in the result as nil" do @response = {"docs"=> - [ {"_index"=>"test", + [ {"_index"=>"my_index", "_type"=>"note", "_id"=>"1", "_version"=>1, "found"=>true, "_source"=>{"id"=>"1", "title"=>"Test 1"}}, - {"_index"=>"test", + {"_index"=>"my_index", "_type"=>"note", "_id"=>"3", "_version"=>1, "found"=>false}, - {"_index"=>"test", + {"_index"=>"my_index", "_type"=>"note", "_id"=>"2", "_version"=>1, @@ -299,11 +302,12 @@ class MyDocument; end @client .expects(:mget) .with do |arguments| - assert_equal 'bambam', arguments[:routing] + assert_equal 'foobarbam', arguments[:index] + assert_equal 'bambam', arguments[:routing] end .returns(@response) - subject.__find_many ['1', '2'], routing: 'bambam' + subject.__find_many ['1', '2'], index: 'foobarbam', routing: 'bambam' end end diff --git a/elasticsearch-persistence/test/unit/repository_response_results_test.rb b/elasticsearch-persistence/test/unit/repository_response_results_test.rb index 34a9a3ba4..ea088e019 100644 --- a/elasticsearch-persistence/test/unit/repository_response_results_test.rb +++ b/elasticsearch-persistence/test/unit/repository_response_results_test.rb @@ -15,13 +15,13 @@ class MyDocument; end { "total" => 2, "max_score" => 0.19, "hits" => - [{"_index" => "test", + [{"_index" => "my_index", "_type" => "note", "_id" => "1", "_score" => 0.19, "_source" => {"id" => 1, "title" => "Test 1"}}, - {"_index" => "test", + {"_index" => "my_index", "_type" => "note", "_id" => "2", "_score" => 0.19, diff --git a/elasticsearch-persistence/test/unit/repository_search_test.rb b/elasticsearch-persistence/test/unit/repository_search_test.rb index 8bcb56f9c..711ded396 100644 --- a/elasticsearch-persistence/test/unit/repository_search_test.rb +++ b/elasticsearch-persistence/test/unit/repository_search_test.rb @@ -9,6 +9,7 @@ class MyDocument; end @client = mock @shoulda_subject.stubs(:klass).returns(nil) + @shoulda_subject.stubs(:index_name).returns('test') @shoulda_subject.stubs(:client).returns(@client) end diff --git a/elasticsearch-persistence/test/unit/repository_store_test.rb b/elasticsearch-persistence/test/unit/repository_store_test.rb index 463bf2915..58afd002e 100644 --- a/elasticsearch-persistence/test/unit/repository_store_test.rb +++ b/elasticsearch-persistence/test/unit/repository_store_test.rb @@ -6,6 +6,7 @@ class MyDocument; end setup do @shoulda_subject = Class.new() { include Elasticsearch::Persistence::Repository::Store }.new + @shoulda_subject.stubs(:index_name).returns('test') end context "save" do @@ -49,11 +50,12 @@ class MyDocument; end client = mock client.expects(:index).with do |arguments| + assert_equal 'foobarbam', arguments[:index] assert_equal 'bambam', arguments[:routing] end subject.expects(:client).returns(client) - subject.save({foo: 'bar'}, routing: 'bambam') + subject.save({foo: 'bar'}, { index: 'foobarbam', routing: 'bambam' }) end end @@ -109,11 +111,12 @@ class MyDocument; end client = mock client.expects(:delete).with do |arguments| + assert_equal 'foobarbam', arguments[:index] assert_equal 'bambam', arguments[:routing] end subject.expects(:client).returns(client) - subject.delete('1', routing: 'bambam') + subject.delete('1', index: 'foobarbam', routing: 'bambam') end end end From 098978445aadb4b52fb333a9ea460c435f359de8 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 1 Apr 2014 12:00:52 +0200 Subject: [PATCH 046/582] [STORE] Changed that `document_type` method returns `nil` when no `klass` is set --- .../lib/elasticsearch/persistence/repository/naming.rb | 2 +- .../test/unit/repository_naming_test.rb | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb index 82344ad28..3ee61232c 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb @@ -20,7 +20,7 @@ def index_name=(name) end def document_type - klass.to_s.underscore + klass ? klass.to_s.underscore : nil end def __get_klass_from_type(type) diff --git a/elasticsearch-persistence/test/unit/repository_naming_test.rb b/elasticsearch-persistence/test/unit/repository_naming_test.rb index b7d588bca..64998c061 100644 --- a/elasticsearch-persistence/test/unit/repository_naming_test.rb +++ b/elasticsearch-persistence/test/unit/repository_naming_test.rb @@ -86,9 +86,11 @@ def self.class end context "document_type" do - should "default to klass" do - assert_equal '', subject.document_type + should "be nil when no klass is set" do + assert_equal nil, subject.document_type + end + should "default to klass" do subject.klass Foobar assert_equal 'foobar', subject.document_type end From caecfa9f38c80e70d7939042360266560b3fb9ba Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 1 Apr 2014 12:16:16 +0200 Subject: [PATCH 047/582] [STORE] Changed that the `Store` methods reflect that `klass` returns a Ruby class, not a string --- .../persistence/repository/store.rb | 6 ++--- .../test/unit/repository_store_test.rb | 27 +++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb index ed0320e26..3ca7995ce 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb @@ -6,18 +6,18 @@ module Store def save(document, options={}) serialized = serialize(document) id = __get_id_from_document(serialized) - type = klass || __get_type_from_class(document.class) + type = __get_type_from_class(klass || document.class) client.index( { index: index_name, type: type, id: id, body: serialized }.merge(options) ) end def delete(document, options={}) if document.is_a?(String) || document.is_a?(Integer) id = document - type = klass + type = __get_type_from_class(klass) else serialized = serialize(document) id = __get_id_from_document(serialized) - type = klass || __get_type_from_class(document.class) + type = __get_type_from_class(klass || document.class) end client.delete( { index: index_name, type: type, id: id }.merge(options) ) end diff --git a/elasticsearch-persistence/test/unit/repository_store_test.rb b/elasticsearch-persistence/test/unit/repository_store_test.rb index 58afd002e..721791981 100644 --- a/elasticsearch-persistence/test/unit/repository_store_test.rb +++ b/elasticsearch-persistence/test/unit/repository_store_test.rb @@ -12,12 +12,13 @@ class MyDocument; end context "save" do should "serialize the document, get type from klass and index it" do subject.expects(:serialize).returns({foo: 'bar'}) - subject.expects(:klass).returns('foo_type') + subject.expects(:klass).at_least_once.returns(MyDocument) + subject.expects(:__get_type_from_class).with(MyDocument).at_least_once.returns('my_document') subject.expects(:__get_id_from_document).returns('1') client = mock client.expects(:index).with do |arguments| - assert_equal 'foo_type', arguments[:type] + assert_equal 'my_document', arguments[:type] assert_equal '1', arguments[:id] assert_equal({foo: 'bar'}, arguments[:body]) end @@ -28,7 +29,7 @@ class MyDocument; end should "serialize the document, get type from document class and index it" do subject.expects(:serialize).returns({foo: 'bar'}) - subject.expects(:klass).returns(nil) + subject.expects(:klass).at_least_once.returns(nil) subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') subject.expects(:__get_id_from_document).returns('1') @@ -45,7 +46,8 @@ class MyDocument; end should "pass the options to the client" do subject.expects(:serialize).returns({foo: 'bar'}) - subject.expects(:klass).returns('foo') + subject.expects(:klass).at_least_once.returns(MyDocument) + subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') subject.expects(:__get_id_from_document).returns('1') client = mock @@ -62,12 +64,13 @@ class MyDocument; end context "delete" do should "get type from klass when passed only ID" do subject.expects(:serialize).never - subject.expects(:klass).returns('foo_type') + subject.expects(:klass).at_least_once.returns(MyDocument) + subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') subject.expects(:__get_id_from_document).never client = mock client.expects(:delete).with do |arguments| - assert_equal 'foo_type', arguments[:type] + assert_equal 'my_document', arguments[:type] assert_equal '1', arguments[:id] end subject.expects(:client).returns(client) @@ -77,12 +80,13 @@ class MyDocument; end should "get ID from document and type from klass when passed a document" do subject.expects(:serialize).returns({id: '1', foo: 'bar'}) - subject.expects(:klass).returns('foo_type') + subject.expects(:klass).at_least_once.returns(MyDocument) + subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') subject.expects(:__get_id_from_document).with({id: '1', foo: 'bar'}).returns('1') client = mock client.expects(:delete).with do |arguments| - assert_equal 'foo_type', arguments[:type] + assert_equal 'my_document', arguments[:type] assert_equal '1', arguments[:id] end subject.expects(:client).returns(client) @@ -92,9 +96,9 @@ class MyDocument; end should "get ID and type from document when passed a document" do subject.expects(:serialize).returns({id: '1', foo: 'bar'}) - subject.expects(:klass).returns(nil) - subject.expects(:__get_id_from_document).with({id: '1', foo: 'bar'}).returns('1') + subject.expects(:klass).at_least_once.returns(nil) subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') + subject.expects(:__get_id_from_document).with({id: '1', foo: 'bar'}).returns('1') client = mock client.expects(:delete).with do |arguments| @@ -107,7 +111,8 @@ class MyDocument; end end should "pass the options to the client" do - subject.expects(:klass).returns('foo') + subject.expects(:klass).at_least_once.returns(MyDocument) + subject.expects(:__get_type_from_class).returns('my_document') client = mock client.expects(:delete).with do |arguments| From 2fbc3aab1c4d7e6b3c4a2e860bc8669d778c6ca3 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 1 Apr 2014 12:32:13 +0200 Subject: [PATCH 048/582] [STORE] Added the `index` and `type` aliases for `index_name` and `document_type` --- .../lib/elasticsearch/persistence/repository/naming.rb | 6 +++--- .../test/unit/repository_naming_test.rb | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb index 3ee61232c..cd9b9e3ac 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb @@ -13,15 +13,15 @@ def klass=klass def index_name name=nil @index_name = name || @index_name || self.class.to_s.underscore.gsub(/\//, '-') - end + end; alias :index :index_name def index_name=(name) @index_name = name - end + end; alias :index= :index_name= def document_type klass ? klass.to_s.underscore : nil - end + end; alias :type :document_type def __get_klass_from_type(type) klass = type.classify diff --git a/elasticsearch-persistence/test/unit/repository_naming_test.rb b/elasticsearch-persistence/test/unit/repository_naming_test.rb index 64998c061..3efde1fd4 100644 --- a/elasticsearch-persistence/test/unit/repository_naming_test.rb +++ b/elasticsearch-persistence/test/unit/repository_naming_test.rb @@ -83,6 +83,11 @@ def self.class subject.index_name 'foobar2' assert_equal 'foobar2', subject.index_name end + + should "be aliased as `index`" do + subject.index_name = 'foobar1' + assert_equal 'foobar1', subject.index + end end context "document_type" do @@ -94,6 +99,11 @@ def self.class subject.klass Foobar assert_equal 'foobar', subject.document_type end + + should "be aliased as `type`" do + subject.klass Foobar + assert_equal 'foobar', subject.type + end end end end From 6d739dde7e55c6706b5454d706156ee21b0eef8c Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 1 Apr 2014 22:11:50 +0200 Subject: [PATCH 049/582] [STORE] Added, that `document_type` can set the document type for repository Also available as `document_type="foo"`. --- .../lib/elasticsearch/persistence/repository/naming.rb | 8 ++++++-- .../test/unit/repository_naming_test.rb | 10 ++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb index cd9b9e3ac..3df35f863 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb @@ -19,10 +19,14 @@ def index_name=(name) @index_name = name end; alias :index= :index_name= - def document_type - klass ? klass.to_s.underscore : nil + def document_type name=nil + @document_type = name || @document_type || (klass ? klass.to_s.underscore : nil) end; alias :type :document_type + def document_type=(name) + @document_type = name + end; alias :type= :document_type= + def __get_klass_from_type(type) klass = type.classify klass.constantize diff --git a/elasticsearch-persistence/test/unit/repository_naming_test.rb b/elasticsearch-persistence/test/unit/repository_naming_test.rb index 3efde1fd4..c54eaf64f 100644 --- a/elasticsearch-persistence/test/unit/repository_naming_test.rb +++ b/elasticsearch-persistence/test/unit/repository_naming_test.rb @@ -104,6 +104,16 @@ def self.class subject.klass Foobar assert_equal 'foobar', subject.type end + + should "be settable" do + subject.document_type = 'foobar' + assert_equal 'foobar', subject.document_type + end + + should "be settable by DSL" do + subject.document_type 'foobar' + assert_equal 'foobar', subject.document_type + end end end end From e4e6ff2634feb29f6f43672542bf4d8e41c224f4 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 1 Apr 2014 23:10:35 +0200 Subject: [PATCH 050/582] [STORE] Changed, that repository methods respect `document_type` when it's set When the `document_type` is configured for the repository, it is used in `save`, `delete`, `find`, etc method to set the `type` parameter for the client. When the `document_type` is not set, the old behaviour of inferring from `klass`, `document.class`, etc. is preserved. --- .../persistence/repository/find.rb | 6 +- .../persistence/repository/store.rb | 6 +- .../test/unit/repository_find_test.rb | 60 +++++++++++++++++++ .../test/unit/repository_store_test.rb | 48 +++++++++++++++ 4 files changed, 114 insertions(+), 6 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb index 15fde4d69..8c224da65 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb @@ -17,12 +17,12 @@ def find(*args) end def exists?(id, options={}) - type = (klass ? __get_type_from_class(klass) : '_all') + type = document_type || (klass ? __get_type_from_class(klass) : '_all') client.exists( { index: index_name, type: type, id: id }.merge(options) ) end def __find_one(id, options={}) - type = (klass ? __get_type_from_class(klass) : '_all') + type = document_type || (klass ? __get_type_from_class(klass) : '_all') document = client.get( { index: index_name, type: type, id: id }.merge(options) ) deserialize(document) @@ -31,7 +31,7 @@ def __find_one(id, options={}) end def __find_many(ids, options={}) - type = (klass ? __get_type_from_class(klass) : '_all') + type = document_type || (klass ? __get_type_from_class(klass) : '_all') documents = client.mget( { index: index_name, type: type, body: { ids: ids } }.merge(options) ) documents['docs'].map { |document| document['found'] ? deserialize(document) : nil } diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb index 3ca7995ce..936aa1d79 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb @@ -6,18 +6,18 @@ module Store def save(document, options={}) serialized = serialize(document) id = __get_id_from_document(serialized) - type = __get_type_from_class(klass || document.class) + type = document_type || __get_type_from_class(klass || document.class) client.index( { index: index_name, type: type, id: id, body: serialized }.merge(options) ) end def delete(document, options={}) if document.is_a?(String) || document.is_a?(Integer) id = document - type = __get_type_from_class(klass) + type = document_type || __get_type_from_class(klass) else serialized = serialize(document) id = __get_id_from_document(serialized) - type = __get_type_from_class(klass || document.class) + type = document_type || __get_type_from_class(klass || document.class) end client.delete( { index: index_name, type: type, id: id }.merge(options) ) end diff --git a/elasticsearch-persistence/test/unit/repository_find_test.rb b/elasticsearch-persistence/test/unit/repository_find_test.rb index 617fedf00..fda7e7105 100644 --- a/elasticsearch-persistence/test/unit/repository_find_test.rb +++ b/elasticsearch-persistence/test/unit/repository_find_test.rb @@ -8,6 +8,7 @@ class MyDocument; end @shoulda_subject = Class.new() { include Elasticsearch::Persistence::Repository::Find }.new @client = mock + @shoulda_subject.stubs(:document_type).returns(nil) @shoulda_subject.stubs(:klass).returns(nil) @shoulda_subject.stubs(:index_name).returns('my_index') @shoulda_subject.stubs(:client).returns(@client) @@ -48,6 +49,7 @@ class MyDocument; end end should "return whether document for klass exists" do + subject.expects(:document_type).returns(nil) subject.expects(:klass).returns(MyDocument).at_least_once subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') @@ -62,6 +64,22 @@ class MyDocument; end assert_equal true, subject.exists?('1') end + should "return whether document for document_type exists" do + subject.expects(:document_type).returns('my_document') + subject.expects(:klass).returns(MyDocument).at_most_once + subject.expects(:__get_type_from_class).never + + @client + .expects(:exists) + .with do |arguments| + assert_equal 'my_document', arguments[:type] + assert_equal '1', arguments[:id] + end + .returns(true) + + assert_equal true, subject.exists?('1') + end + should "return whether document exists" do subject.expects(:klass).returns(nil) subject.expects(:__get_type_from_class).never @@ -89,6 +107,7 @@ class MyDocument; end context "'__find_one' method" do should "find document based on klass and return a deserialized object" do + subject.expects(:document_type).returns(nil) subject.expects(:klass).returns(MyDocument).at_least_once subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') @@ -105,7 +124,26 @@ class MyDocument; end assert_instance_of MyDocument, subject.__find_one('1') end + should "find document based on document_type and return a deserialized object" do + subject.expects(:document_type).returns('my_document') + subject.expects(:klass).returns(MyDocument).at_most_once + subject.expects(:__get_type_from_class).never + + subject.expects(:deserialize).with({'_source' => {'foo' => 'bar'}}).returns(MyDocument.new) + + @client + .expects(:get) + .with do |arguments| + assert_equal 'my_document', arguments[:type] + assert_equal '1', arguments[:id] + end + .returns({'_source' => { 'foo' => 'bar' }}) + + assert_instance_of MyDocument, subject.__find_one('1') + end + should "find document and return a deserialized object" do + subject.expects(:document_type).returns(nil) subject.expects(:klass).returns(nil).at_least_once subject.expects(:__get_type_from_class).never @@ -123,6 +161,7 @@ class MyDocument; end end should "raise DocumentNotFound exception when the document cannot be found" do + subject.expects(:document_type).returns(nil) subject.expects(:klass).returns(nil).at_least_once subject.expects(:deserialize).never @@ -186,6 +225,7 @@ class MyDocument; end end should "find documents based on klass and return an Array of deserialized objects" do + subject.expects(:document_type).returns(nil) subject.expects(:klass).returns(MyDocument).at_least_once subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') @@ -212,7 +252,26 @@ class MyDocument; end assert_instance_of MyDocument, results[1] end + should "find documents based on document_type and return an Array of deserialized objects" do + subject.expects(:document_type).returns('my_document') + subject.expects(:klass).returns(MyDocument).at_most_once + subject.expects(:__get_type_from_class).never + + subject.expects(:deserialize).twice + + @client + .expects(:mget) + .with do |arguments| + assert_equal 'my_document', arguments[:type] + assert_equal ['1', '2'], arguments[:body][:ids] + end + .returns(@response) + + subject.__find_many(['1', '2']) + end + should "find documents and return an Array of deserialized objects" do + subject.expects(:document_type).returns(nil) subject.expects(:klass).returns(nil).at_least_once subject.expects(:__get_type_from_class).never @@ -265,6 +324,7 @@ class MyDocument; end "_source"=>{"id"=>"2", "title"=>"Test 2"}} ]} + subject.expects(:document_type).returns(nil) subject.expects(:klass).returns(MyDocument).at_least_once subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') diff --git a/elasticsearch-persistence/test/unit/repository_store_test.rb b/elasticsearch-persistence/test/unit/repository_store_test.rb index 721791981..48b916098 100644 --- a/elasticsearch-persistence/test/unit/repository_store_test.rb +++ b/elasticsearch-persistence/test/unit/repository_store_test.rb @@ -12,6 +12,7 @@ class MyDocument; end context "save" do should "serialize the document, get type from klass and index it" do subject.expects(:serialize).returns({foo: 'bar'}) + subject.expects(:document_type).returns(nil) subject.expects(:klass).at_least_once.returns(MyDocument) subject.expects(:__get_type_from_class).with(MyDocument).at_least_once.returns('my_document') subject.expects(:__get_id_from_document).returns('1') @@ -29,6 +30,7 @@ class MyDocument; end should "serialize the document, get type from document class and index it" do subject.expects(:serialize).returns({foo: 'bar'}) + subject.expects(:document_type).returns(nil) subject.expects(:klass).at_least_once.returns(nil) subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') subject.expects(:__get_id_from_document).returns('1') @@ -44,8 +46,30 @@ class MyDocument; end subject.save(MyDocument.new) end + should "serialize the document, get type from document_type and index it" do + subject.expects(:serialize).returns({foo: 'bar'}) + + subject.expects(:document_type).returns('my_document') + + subject.expects(:klass).never + subject.expects(:__get_type_from_class).never + + subject.expects(:__get_id_from_document).returns('1') + + client = mock + client.expects(:index).with do |arguments| + assert_equal 'my_document', arguments[:type] + assert_equal '1', arguments[:id] + assert_equal({foo: 'bar'}, arguments[:body]) + end + subject.expects(:client).returns(client) + + subject.save(MyDocument.new) + end + should "pass the options to the client" do subject.expects(:serialize).returns({foo: 'bar'}) + subject.expects(:document_type).returns(nil) subject.expects(:klass).at_least_once.returns(MyDocument) subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') subject.expects(:__get_id_from_document).returns('1') @@ -64,6 +88,7 @@ class MyDocument; end context "delete" do should "get type from klass when passed only ID" do subject.expects(:serialize).never + subject.expects(:document_type).returns(nil) subject.expects(:klass).at_least_once.returns(MyDocument) subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') subject.expects(:__get_id_from_document).never @@ -80,6 +105,7 @@ class MyDocument; end should "get ID from document and type from klass when passed a document" do subject.expects(:serialize).returns({id: '1', foo: 'bar'}) + subject.expects(:document_type).returns(nil) subject.expects(:klass).at_least_once.returns(MyDocument) subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') subject.expects(:__get_id_from_document).with({id: '1', foo: 'bar'}).returns('1') @@ -94,8 +120,29 @@ class MyDocument; end subject.delete({id: '1', foo: 'bar'}) end + should "get ID from document and type from document_type when passed a document" do + subject.expects(:serialize).returns({id: '1', foo: 'bar'}) + + subject.expects(:document_type).returns('my_document') + + subject.expects(:klass).never + subject.expects(:__get_type_from_class).never + + subject.expects(:__get_id_from_document).with({id: '1', foo: 'bar'}).returns('1') + + client = mock + client.expects(:delete).with do |arguments| + assert_equal 'my_document', arguments[:type] + assert_equal '1', arguments[:id] + end + subject.expects(:client).returns(client) + + subject.delete({id: '1', foo: 'bar'}) + end + should "get ID and type from document when passed a document" do subject.expects(:serialize).returns({id: '1', foo: 'bar'}) + subject.expects(:document_type).returns(nil) subject.expects(:klass).at_least_once.returns(nil) subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') subject.expects(:__get_id_from_document).with({id: '1', foo: 'bar'}).returns('1') @@ -111,6 +158,7 @@ class MyDocument; end end should "pass the options to the client" do + subject.expects(:document_type).returns(nil) subject.expects(:klass).at_least_once.returns(MyDocument) subject.expects(:__get_type_from_class).returns('my_document') From c391a3b287dcca2556b6d67f34437c83315df568 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Wed, 2 Apr 2014 10:34:01 +0200 Subject: [PATCH 051/582] [STORE] Implemented the gateway pattern for the repository integration To provide flexibility to the end-user, the integration of the "Repository" module has been refactored to proxy all repository methods via a gateway. This allows users to set up the repository in a custom class, with class methods, in a DSL-like fashion: class NoteRepository include Elasticsearch::Persistence::Repository klass Note index :my_notes mapping do indexes :title, analyzer: 'snowball' end client.transport.logger = Logger.new(STDERR) gateway do def serialize(document) super.merge(special: 'stuff') end end end The bundled Repository class can be configured via a block passed to the initializer: repository = Elasticsearch::Persistence::Repository.new do klass Note index :my_notes mapping do indexes :title, analyzer: 'snowball' end client.transport.logger = Logger.new(STDERR) end The repository methods can be accessed via the class or instance methods: NoteRepository.klass Note repository.klass Note --- .../lib/elasticsearch/persistence.rb | 3 +- .../elasticsearch/persistence/repository.rb | 36 ++++-- .../persistence/repository/class.rb | 9 +- .../test/unit/repository_class_test.rb | 50 ++++++--- .../test/unit/repository_module_test.rb | 106 ++++++++++++++++++ 5 files changed, 176 insertions(+), 28 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence.rb b/elasticsearch-persistence/lib/elasticsearch/persistence.rb index 5e86351c7..18db3ceb5 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence.rb @@ -13,9 +13,8 @@ require 'elasticsearch/persistence/repository/store' require 'elasticsearch/persistence/repository/find' require 'elasticsearch/persistence/repository/search' -require 'elasticsearch/persistence/repository' - require 'elasticsearch/persistence/repository/class' +require 'elasticsearch/persistence/repository' module Elasticsearch module Persistence diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index d15e45960..8ffddbf21 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -1,15 +1,37 @@ module Elasticsearch module Persistence + module GatewayDelegation + def method_missing(method_name, *arguments, &block) + gateway.respond_to?(method_name) ? gateway.__send__(method_name, *arguments, &block) : super + end + + def respond_to?(method_name, include_private=false) + gateway.respond_to?(method_name) || super + end + end module Repository - include Elasticsearch::Persistence::Client - include Elasticsearch::Persistence::Repository::Naming - include Elasticsearch::Persistence::Repository::Serialize - include Elasticsearch::Persistence::Repository::Store - include Elasticsearch::Persistence::Repository::Find - include Elasticsearch::Persistence::Repository::Search + def self.included(base) + gateway = Elasticsearch::Persistence::Repository::Class.new + + base.class_eval do + define_method :gateway do + @gateway ||= gateway + end + + include GatewayDelegation + end + + (class << base; self; end).class_eval do + define_method :gateway do |&block| + @gateway ||= gateway + @gateway.instance_eval(&block) if block + @gateway + end - include Elasticsearch::Model::Indexing::ClassMethods + include GatewayDelegation + end + end def new(options={}, &block) Elasticsearch::Persistence::Repository::Class.new options, &block diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb index 7ff11e828..8a74df431 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb @@ -3,7 +3,14 @@ module Persistence module Repository class Class - include Elasticsearch::Persistence::Repository + include Elasticsearch::Persistence::Client + include Elasticsearch::Persistence::Repository::Naming + include Elasticsearch::Persistence::Repository::Serialize + include Elasticsearch::Persistence::Repository::Store + include Elasticsearch::Persistence::Repository::Find + include Elasticsearch::Persistence::Repository::Search + + include Elasticsearch::Model::Indexing::ClassMethods attr_reader :options diff --git a/elasticsearch-persistence/test/unit/repository_class_test.rb b/elasticsearch-persistence/test/unit/repository_class_test.rb index b20ac3e8b..e4e20028d 100644 --- a/elasticsearch-persistence/test/unit/repository_class_test.rb +++ b/elasticsearch-persistence/test/unit/repository_class_test.rb @@ -3,29 +3,43 @@ class Elasticsearch::Persistence::RepositoryClassTest < Test::Unit::TestCase context "The default repository class" do - should "be created from the module" do - repository = Elasticsearch::Persistence::Repository.new - assert_instance_of Elasticsearch::Persistence::Repository::Class, repository - end + context "when initialized" do + should "be created from the module" do + repository = Elasticsearch::Persistence::Repository.new + assert_instance_of Elasticsearch::Persistence::Repository::Class, repository + end - should "store and access the options" do - repository = Elasticsearch::Persistence::Repository::Class.new foo: 'bar' - assert_equal 'bar', repository.options[:foo] - end + should "store and access the options" do + repository = Elasticsearch::Persistence::Repository::Class.new foo: 'bar' + assert_equal 'bar', repository.options[:foo] + end - should "instance eval a passed block" do - $foo = 100 - repository = Elasticsearch::Persistence::Repository::Class.new() { $foo += 1 } - assert_equal 101, $foo + should "instance eval a passed block" do + $foo = 100 + repository = Elasticsearch::Persistence::Repository::Class.new() { $foo += 1 } + assert_equal 101, $foo + end + + should "call a passed block with self" do + foo = 100 + repository = Elasticsearch::Persistence::Repository::Class.new do |r| + assert_instance_of Elasticsearch::Persistence::Repository::Class, r + foo += 1 + end + assert_equal 101, foo + end end - should "call a passed block with self" do - foo = 100 - repository = Elasticsearch::Persistence::Repository::Class.new do |r| - assert_instance_of Elasticsearch::Persistence::Repository::Class, r - foo += 1 + should "include the repository methods" do + repository = Elasticsearch::Persistence::Repository::Class.new + + %w( index_name document_type klass + mappings settings client client= + create_index! delete_index! refresh_index! + save delete serialize deserialize + exists? find search ).each do |method| + assert_respond_to repository, method end - assert_equal 101, foo end end diff --git a/elasticsearch-persistence/test/unit/repository_module_test.rb b/elasticsearch-persistence/test/unit/repository_module_test.rb index a31d7ac84..f615cb990 100644 --- a/elasticsearch-persistence/test/unit/repository_module_test.rb +++ b/elasticsearch-persistence/test/unit/repository_module_test.rb @@ -2,5 +2,111 @@ class Elasticsearch::Persistence::RepositoryModuleTest < Test::Unit::TestCase context "The repository module" do + + class DummyModel + def initialize(attributes={}) + @attributes = attributes + end + + def to_hash + @attributes + end + + def inspect + "" + end + end + + setup do + class DummyRepository + include Elasticsearch::Persistence::Repository + end + end + + teardown do + Elasticsearch::Persistence::RepositoryModuleTest.__send__ :remove_const, :DummyRepository + end + + context "when included" do + should "set up the gateway for the class and instance" do + assert_respond_to DummyRepository, :gateway + assert_respond_to DummyRepository.new, :gateway + + assert_instance_of Elasticsearch::Persistence::Repository::Class, DummyRepository.gateway + assert_instance_of Elasticsearch::Persistence::Repository::Class, DummyRepository.new.gateway + end + + should "proxy repository methods from the class to the gateway" do + class DummyRepository + include Elasticsearch::Persistence::Repository + + index :foobar + klass DummyModel + type :my_dummy_model + mapping { indexes :title, analyzer: 'snowball' } + end + + repository = DummyRepository.new + + assert_equal :foobar, DummyRepository.index + assert_equal DummyModel, DummyRepository.klass + assert_equal :my_dummy_model, DummyRepository.type + assert_equal 'snowball', DummyRepository.mappings.to_hash[:my_dummy_model][:properties][:title][:analyzer] + + assert_equal :foobar, repository.index + assert_equal DummyModel, repository.klass + assert_equal :my_dummy_model, repository.type + assert_equal 'snowball', repository.mappings.to_hash[:my_dummy_model][:properties][:title][:analyzer] + end + + should "proxy repository methods from the instance to the gateway" do + class DummyRepository + include Elasticsearch::Persistence::Repository + end + + repository = DummyRepository.new + repository.index :foobar + repository.klass DummyModel + repository.type :my_dummy_model + repository.mapping { indexes :title, analyzer: 'snowball' } + + assert_equal :foobar, DummyRepository.index + assert_equal DummyModel, DummyRepository.klass + assert_equal :my_dummy_model, DummyRepository.type + assert_equal 'snowball', DummyRepository.mappings.to_hash[:my_dummy_model][:properties][:title][:analyzer] + + assert_equal :foobar, repository.index + assert_equal DummyModel, repository.klass + assert_equal :my_dummy_model, repository.type + assert_equal 'snowball', repository.mappings.to_hash[:my_dummy_model][:properties][:title][:analyzer] + end + + should "allow to define gateway methods in the class definition" do + class DummyRepository + include Elasticsearch::Persistence::Repository + + gateway do + def serialize(document) + 'FAKE!' + end + end + end + + repository = DummyRepository.new + repository.client.transport.logger = Logger.new(STDERR) + + client = mock + client.expects(:index).with do |arguments| + assert_equal('xxx', arguments[:id]) + assert_equal('FAKE!', arguments[:body]) + end + repository.gateway.expects(:client).returns(client) + + repository.gateway.expects(:__get_id_from_document).returns('xxx') + + repository.save( id: '123', foo: 'bar' ) + end + end + end end From 63da64aef4352d6c3280106bdf2875d781835b22 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Wed, 2 Apr 2014 15:15:26 +0200 Subject: [PATCH 052/582] [STORE] Added, that `index_name` is inferred from the including class class NoteRepository include Elasticsearch::Persistence::Repository end NoteRepository.index_name => "note_repository" --- .../lib/elasticsearch/persistence/repository.rb | 2 +- .../lib/elasticsearch/persistence/repository/class.rb | 4 ++++ .../lib/elasticsearch/persistence/repository/naming.rb | 8 +++++++- .../test/unit/repository_module_test.rb | 4 ++++ .../test/unit/repository_naming_test.rb | 6 ++++++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index 8ffddbf21..e35a57d7d 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -12,7 +12,7 @@ def respond_to?(method_name, include_private=false) module Repository def self.included(base) - gateway = Elasticsearch::Persistence::Repository::Class.new + gateway = Elasticsearch::Persistence::Repository::Class.new host: base base.class_eval do define_method :gateway do diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb index 8a74df431..bda7e9f30 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb @@ -18,6 +18,10 @@ def initialize(options={}, &block) @options = options block.arity < 1 ? instance_eval(&block) : block.call(self) if block_given? end + + def host + options[:host] + end end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb index 3df35f863..4d58b7248 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb @@ -12,7 +12,13 @@ def klass=klass end def index_name name=nil - @index_name = name || @index_name || self.class.to_s.underscore.gsub(/\//, '-') + @index_name = name || @index_name || begin + if respond_to?(:host) && host && host.is_a?(Module) + self.host.to_s.underscore.gsub(/\//, '-') + else + self.class.to_s.underscore.gsub(/\//, '-') + end + end end; alias :index :index_name def index_name=(name) diff --git a/elasticsearch-persistence/test/unit/repository_module_test.rb b/elasticsearch-persistence/test/unit/repository_module_test.rb index f615cb990..1850effc7 100644 --- a/elasticsearch-persistence/test/unit/repository_module_test.rb +++ b/elasticsearch-persistence/test/unit/repository_module_test.rb @@ -108,5 +108,9 @@ def serialize(document) end end + should_eventually "configure the index name in the shortcut initializer" do + assert_equal 'repository', Elasticsearch::Persistence::Repository.new.index_name + end + end end diff --git a/elasticsearch-persistence/test/unit/repository_naming_test.rb b/elasticsearch-persistence/test/unit/repository_naming_test.rb index c54eaf64f..0c6f9833e 100644 --- a/elasticsearch-persistence/test/unit/repository_naming_test.rb +++ b/elasticsearch-persistence/test/unit/repository_naming_test.rb @@ -88,6 +88,12 @@ def self.class subject.index_name = 'foobar1' assert_equal 'foobar1', subject.index end + + should "be inferred from the host class" do + class ::MySpecialRepository; end + subject.define_singleton_method(:host) { MySpecialRepository } + assert_equal 'my_special_repository', subject.index_name + end end context "document_type" do From 1299c0bd6cb696588e0a60cbfd32be2025f53e5c Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Wed, 2 Apr 2014 15:43:18 +0200 Subject: [PATCH 053/582] [STORE] Added, that the repository class reflects the `:index` option Also, when using the "shortcut" to create the repository, a default name of `repository` is set. --- .../lib/elasticsearch/persistence/repository.rb | 2 +- .../lib/elasticsearch/persistence/repository/class.rb | 1 + elasticsearch-persistence/test/unit/repository_class_test.rb | 5 +++++ .../test/unit/repository_module_test.rb | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index e35a57d7d..fe1c93a22 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -34,7 +34,7 @@ def self.included(base) end def new(options={}, &block) - Elasticsearch::Persistence::Repository::Class.new options, &block + Elasticsearch::Persistence::Repository::Class.new( {index: 'repository'}.merge(options), &block ) end; module_function :new end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb index bda7e9f30..0579e779e 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb @@ -16,6 +16,7 @@ class Class def initialize(options={}, &block) @options = options + index_name options.delete(:index) block.arity < 1 ? instance_eval(&block) : block.call(self) if block_given? end diff --git a/elasticsearch-persistence/test/unit/repository_class_test.rb b/elasticsearch-persistence/test/unit/repository_class_test.rb index e4e20028d..d29711248 100644 --- a/elasticsearch-persistence/test/unit/repository_class_test.rb +++ b/elasticsearch-persistence/test/unit/repository_class_test.rb @@ -28,6 +28,11 @@ class Elasticsearch::Persistence::RepositoryClassTest < Test::Unit::TestCase end assert_equal 101, foo end + + should "configure the index name based on options" do + repository = Elasticsearch::Persistence::Repository::Class.new index: 'foobar' + assert_equal 'foobar', repository.index_name + end end should "include the repository methods" do diff --git a/elasticsearch-persistence/test/unit/repository_module_test.rb b/elasticsearch-persistence/test/unit/repository_module_test.rb index 1850effc7..d215e7279 100644 --- a/elasticsearch-persistence/test/unit/repository_module_test.rb +++ b/elasticsearch-persistence/test/unit/repository_module_test.rb @@ -108,7 +108,7 @@ def serialize(document) end end - should_eventually "configure the index name in the shortcut initializer" do + should "configure the index name in the shortcut initializer" do assert_equal 'repository', Elasticsearch::Persistence::Repository.new.index_name end From a929413ab9881286c78c43dc1557f8adb5e1146d Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Thu, 3 Apr 2014 16:46:38 +0200 Subject: [PATCH 054/582] [STORE] Added that the `client` can be set in a DSL-like way Both: repository.client = MyClient.new and: repository.client MyClient.new are equivalent now. --- .../lib/elasticsearch/persistence.rb | 4 ++-- .../lib/elasticsearch/persistence/client.rb | 17 ++++++++++------- .../persistence/repository/class.rb | 2 +- .../test/unit/persistence_test.rb | 9 +++++++++ .../test/unit/repository_client_test.rb | 17 +++++++++++------ 5 files changed, 33 insertions(+), 16 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence.rb b/elasticsearch-persistence/lib/elasticsearch/persistence.rb index 18db3ceb5..d27c01c7f 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence.rb @@ -21,8 +21,8 @@ module Persistence # :nodoc: module ClassMethods - def client - @client ||= Elasticsearch::Client.new + def client client=nil + @client = client || @client || Elasticsearch::Client.new end def client=(client) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/client.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/client.rb index c7a8bfa51..7fee129e0 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/client.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/client.rb @@ -1,15 +1,18 @@ module Elasticsearch module Persistence + module Repository - module Client - def client client=nil - @client ||= Elasticsearch::Persistence.client - end + module Client + def client client=nil + @client = client || @client || Elasticsearch::Persistence.client + end - def client=(client) - @client = client + def client=(client) + @client = client + @client + end end - end + end end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb index 0579e779e..cce2a9ea2 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb @@ -3,7 +3,7 @@ module Persistence module Repository class Class - include Elasticsearch::Persistence::Client + include Elasticsearch::Persistence::Repository::Client include Elasticsearch::Persistence::Repository::Naming include Elasticsearch::Persistence::Repository::Serialize include Elasticsearch::Persistence::Repository::Store diff --git a/elasticsearch-persistence/test/unit/persistence_test.rb b/elasticsearch-persistence/test/unit/persistence_test.rb index 9381203d3..cd17ba7dc 100644 --- a/elasticsearch-persistence/test/unit/persistence_test.rb +++ b/elasticsearch-persistence/test/unit/persistence_test.rb @@ -18,6 +18,15 @@ class Elasticsearch::Persistence::ModuleTest < Test::Unit::TestCase Elasticsearch::Persistence.client = nil end end + + should "allow to set a client with DSL" do + begin + Elasticsearch::Persistence.client "Foobar" + assert_equal "Foobar", Elasticsearch::Persistence.client + ensure + Elasticsearch::Persistence.client = nil + end + end end end end diff --git a/elasticsearch-persistence/test/unit/repository_client_test.rb b/elasticsearch-persistence/test/unit/repository_client_test.rb index 246631afc..88e40193e 100644 --- a/elasticsearch-persistence/test/unit/repository_client_test.rb +++ b/elasticsearch-persistence/test/unit/repository_client_test.rb @@ -1,13 +1,9 @@ require 'test_helper' class Elasticsearch::Persistence::RepositoryClientTest < Test::Unit::TestCase - context "A repository client" do - class DummyReposistory - include Elasticsearch::Persistence::Repository - end - + context "The repository client" do setup do - @shoulda_subject = DummyReposistory.new + @shoulda_subject = Class.new() { include Elasticsearch::Persistence::Repository::Client }.new end should "have a default client" do @@ -23,5 +19,14 @@ class DummyReposistory subject.client = nil end end + + should "allow to set the client with DSL" do + begin + subject.client "Foobar" + assert_equal "Foobar", subject.client + ensure + subject.client = nil + end + end end end From cbbff17d97a7e3f0b185113dbb07cf8583712872 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Thu, 3 Apr 2014 16:55:22 +0200 Subject: [PATCH 055/582] [STORE] Added `respond_to_missing?` to the proxy objects See: * http://robots.thoughtbot.com/always-define-respond-to-missing-when-overriding * http://blog.marc-andre.ca/2010/11/15/methodmissing-politely/ --- .../lib/elasticsearch/persistence/repository.rb | 4 ++++ .../test/unit/repository_module_test.rb | 6 +++++- .../test/unit/repository_response_results_test.rb | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index fe1c93a22..7b69a39b5 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -8,6 +8,10 @@ def method_missing(method_name, *arguments, &block) def respond_to?(method_name, include_private=false) gateway.respond_to?(method_name) || super end + + def respond_to_missing?(method_name, *) + gateway.respond_to?(method_name) || super + end end module Repository diff --git a/elasticsearch-persistence/test/unit/repository_module_test.rb b/elasticsearch-persistence/test/unit/repository_module_test.rb index d215e7279..d559ae34c 100644 --- a/elasticsearch-persistence/test/unit/repository_module_test.rb +++ b/elasticsearch-persistence/test/unit/repository_module_test.rb @@ -59,6 +59,11 @@ class DummyRepository assert_equal 'snowball', repository.mappings.to_hash[:my_dummy_model][:properties][:title][:analyzer] end + should "correctly delegate to the gateway" do + repository = DummyRepository.new + assert_instance_of Method, repository.method(:index) + end + should "proxy repository methods from the instance to the gateway" do class DummyRepository include Elasticsearch::Persistence::Repository @@ -111,6 +116,5 @@ def serialize(document) should "configure the index name in the shortcut initializer" do assert_equal 'repository', Elasticsearch::Persistence::Repository.new.index_name end - end end diff --git a/elasticsearch-persistence/test/unit/repository_response_results_test.rb b/elasticsearch-persistence/test/unit/repository_response_results_test.rb index ea088e019..294a96efd 100644 --- a/elasticsearch-persistence/test/unit/repository_response_results_test.rb +++ b/elasticsearch-persistence/test/unit/repository_response_results_test.rb @@ -63,6 +63,10 @@ class MyDocument; end assert_respond_to subject, :each end + should "respond to missing" do + assert_instance_of Method, subject.method(:to_a) + end + should "yield each object with hit" do @shoulda_subject = Repository::Response::Results.new \ @repository, From 44c9952a2b570143201b94213090c156d7a6e778 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Fri, 4 Apr 2014 20:37:05 +0200 Subject: [PATCH 056/582] [STORE] Added the `method_added` hook to allow defining gateway methods directly in the class Instead of calling the `gateway` method with a block, to redefine the serialize/deserialize methods: class NoteRepository gateway do def serialize(document) Hash[document.to_hash.map() { |k,v| v.upcase! if k == :title; [k,v] }] end def deserialize(document) MyNote.new ActiveSupport::HashWithIndifferentAccess.new(document['_source']).deep_symbolize_keys end end end Define them directly in the class, and they will be intercepted by the hook, and (re)defined directly on the gateway: class NoteRepository def serialize(document) Hash[document.to_hash.map() { |k,v| v.upcase! if k == :title; [k,v] }] end def deserialize(document) MyNote.new ActiveSupport::HashWithIndifferentAccess.new(document['_source']).deep_symbolize_keys end end See: http://www.ruby-doc.org/core-2.1.1/Module.html#method-i-method_added (COMITTED WITH FINGERS CROSSED :) --- .../elasticsearch/persistence/repository.rb | 6 ++++ .../test/unit/repository_module_test.rb | 30 +++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index 7b69a39b5..8f08d3fd5 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -35,6 +35,12 @@ def self.included(base) include GatewayDelegation end + + def base.method_added(name) + if :gateway != name && respond_to?(:gateway) && (gateway.public_methods - Object.public_methods).include?(name) + gateway.define_singleton_method(name, self.new.method(name).to_proc) + end + end end def new(options={}, &block) diff --git a/elasticsearch-persistence/test/unit/repository_module_test.rb b/elasticsearch-persistence/test/unit/repository_module_test.rb index d559ae34c..d85fcf0a7 100644 --- a/elasticsearch-persistence/test/unit/repository_module_test.rb +++ b/elasticsearch-persistence/test/unit/repository_module_test.rb @@ -86,8 +86,8 @@ class DummyRepository assert_equal 'snowball', repository.mappings.to_hash[:my_dummy_model][:properties][:title][:analyzer] end - should "allow to define gateway methods in the class definition" do - class DummyRepository + should "allow to define gateway methods in the class definition via block passed to the gateway method" do + class DummyRepositoryWithGatewaySerialize include Elasticsearch::Persistence::Repository gateway do @@ -97,7 +97,7 @@ def serialize(document) end end - repository = DummyRepository.new + repository = DummyRepositoryWithGatewaySerialize.new repository.client.transport.logger = Logger.new(STDERR) client = mock @@ -113,6 +113,30 @@ def serialize(document) end end + should "allow to define gateway methods in the class definition via regular method definition" do + class DummyRepositoryWithDirectSerialize + include Elasticsearch::Persistence::Repository + + def serialize(document) + 'FAKE IN CLASS!' + end + end + + repository = DummyRepositoryWithDirectSerialize.new + repository.client.transport.logger = Logger.new(STDERR) + + client = mock + client.expects(:index).with do |arguments| + assert_equal('xxx', arguments[:id]) + assert_equal('FAKE IN CLASS!', arguments[:body]) + end + repository.gateway.expects(:client).returns(client) + + repository.gateway.expects(:__get_id_from_document).returns('xxx') + + repository.save( id: '123', foo: 'bar' ) + end + should "configure the index name in the shortcut initializer" do assert_equal 'repository', Elasticsearch::Persistence::Repository.new.index_name end From ca1270ddcfc3e1234612ea9459d08d0d7d6c3cc0 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Fri, 4 Apr 2014 22:19:12 +0200 Subject: [PATCH 057/582] [STORE] Added code annotation, documentation and examples --- .../lib/elasticsearch/persistence.rb | 58 +++++++++++++++++++ .../lib/elasticsearch/persistence/client.rb | 30 ++++++++++ .../persistence/repository/class.rb | 39 +++++++++++++ .../persistence/repository/find.rb | 30 ++++++++++ .../persistence/repository/naming.rb | 37 ++++++++++++ .../repository/response/results.rb | 40 ++++++++++++- .../persistence/repository/search.rb | 36 ++++++++++++ .../persistence/repository/serialize.rb | 13 +++++ .../persistence/repository/store.rb | 7 +++ 9 files changed, 288 insertions(+), 2 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence.rb b/elasticsearch-persistence/lib/elasticsearch/persistence.rb index d27c01c7f..e6d215ad0 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence.rb @@ -17,14 +17,72 @@ require 'elasticsearch/persistence/repository' module Elasticsearch + + # Persistence for Ruby domain objects and models in Elasticsearch + # =============================================================== + # + # `Elasticsearch::Persistence` contains modules for storing and retrieving Ruby domain objects and models + # in Elasticsearch. + # + # == Repository + # + # The repository patterns allows to store and retrieve Ruby objects in Elasticsearch. + # + # require 'elasticsearch/persistence' + # + # class Note + # def to_hash; {foo: 'bar'}; end + # end + # + # repository = Elasticsearch::Persistence::Repository.new + # + # repository.save Note.new + # # => {"_index"=>"repository", "_type"=>"note", "_id"=>"mY108X9mSHajxIy2rzH2CA", ...} + # + # Customize your repository by including the main module in a Ruby class + # class MyRepository + # include Elasticsearch::Persistence::Repository + # + # index 'my_notes' + # klass Note + # + # client Elasticsearch::Client.new log: true + # end + # + # repository = MyRepository.new + # + # repository.save Note.new + # # 2014-04-04 22:15:25 +0200: POST http://localhost:9200/my_notes/note [status:201, request:0.009s, query:n/a] + # # 2014-04-04 22:15:25 +0200: > {"foo":"bar"} + # # 2014-04-04 22:15:25 +0200: < {"_index":"my_notes","_type":"note","_id":"-d28yXLFSlusnTxb13WIZQ", ...} + # module Persistence # :nodoc: module ClassMethods + + # Get or set the default client for all repositories and models + # + # @example Set and configure the default client + # + # Elasticsearch::Persistence.client Elasticsearch::Client.new host: 'http://localhost:9200', tracer: true + # + # @example Perform an API request through the client + # + # Elasticsearch::Persistence.client.cluster.health + # # => { "cluster_name" => "elasticsearch" ... } + # def client client=nil @client = client || @client || Elasticsearch::Client.new end + # Set the default client for all repositories and models + # + # @example Set and configure the default client + # + # Elasticsearch::Persistence.client = Elasticsearch::Client.new host: 'http://localhost:9200', tracer: true + # => # + # def client=(client) @client = client end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/client.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/client.rb index 7fee129e0..ece71b12b 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/client.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/client.rb @@ -3,10 +3,40 @@ module Persistence module Repository module Client + + # Get or set the default client for this repository + # + # @example Set and configure the client for the repository class + # + # class MyRepository + # include Elasticsearch::Persistence::Repository + # client Elasticsearch::Client.new host: 'http://localhost:9200', log: true + # end + # + # @example Set and configure the client for this repository instance + # + # repository.client Elasticsearch::Client.new host: 'http://localhost:9200', tracer: true + # + # @example Perform an API request through the client + # + # MyRepository.client.cluster.health + # repository.client.cluster.health + # # => { "cluster_name" => "elasticsearch" ... } + # def client client=nil @client = client || @client || Elasticsearch::Persistence.client end + # Set the default client for this repository + # + # @example Set and configure the client for the repository class + # + # MyRepository.client = Elasticsearch::Client.new host: 'http://localhost:9200', log: true + # + # @example Set and configure the client for this repository instance + # + # repository.client = Elasticsearch::Client.new host: 'http://localhost:9200', tracer: true + # def client=(client) @client = client @client diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb index cce2a9ea2..55b6bc8d4 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb @@ -2,6 +2,41 @@ module Elasticsearch module Persistence module Repository + # The default repository class, to be used either directly, or as a gateway in a custom repository class + # + # @example Standalone use + # + # repository = Elasticsearch::Persistence::Repository::Class.new + # # => # + # # > repository.save(my_object) + # # => {"_index"=> ... } + # + # + # @example Shortcut use + # + # repository = Elasticsearch::Persistence::Repository.new + # # => # + # + # @example Configuration via a block + # + # repository = Elasticsearch::Persistence::Repository.new do + # index 'my_notes' + # end + # # => # + # # > repository.save(my_object) + # # => {"_index"=> ... } + # + # @example Accessing the gateway in a custom class + # + # class MyRepository + # include Elasticsearch::Persistence::Repository + # end + # + # repository = MyRepository.new + # + # repository.gateway.client.info + # => {"status"=>200, "name"=>"Venom", ... } + # class Class include Elasticsearch::Persistence::Repository::Client include Elasticsearch::Persistence::Repository::Naming @@ -20,6 +55,10 @@ def initialize(options={}, &block) block.arity < 1 ? instance_eval(&block) : block.call(self) if block_given? end + # Return the "host" class, if this repository is a gateway hosted in another class + # + # @return [nil, Class] + # def host options[:host] end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb index 8c224da65..c6a9a6a4e 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb @@ -3,7 +3,24 @@ module Persistence module Repository class DocumentNotFound < StandardError; end + # Retrieves one or more domain objects from the repository + # module Find + + # Retrieve a single object or multiple objects from Elasticsearch by ID or IDs + # + # @example Retrieve a single object by ID + # + # repository.find(1) + # # => + # + # @example Retrieve multiple objects by IDs + # + # repository.find(1, 2) + # # => [, + # + # @return [Object,Array] + # def find(*args) options = args.last.is_a?(Hash) ? args.pop : {} ids = args @@ -16,11 +33,22 @@ def find(*args) end end + # Return if object exists in the repository + # + # @example + # + # repository.exists?(1) + # => true + # + # @return [true, false] + # def exists?(id, options={}) type = document_type || (klass ? __get_type_from_class(klass) : '_all') client.exists( { index: index_name, type: type, id: id }.merge(options) ) end + # @api private + # def __find_one(id, options={}) type = document_type || (klass ? __get_type_from_class(klass) : '_all') document = client.get( { index: index_name, type: type, id: id }.merge(options) ) @@ -30,6 +58,8 @@ def __find_one(id, options={}) raise DocumentNotFound, e.message, caller end + # @api private + # def __find_many(ids, options={}) type = document_type || (klass ? __get_type_from_class(klass) : '_all') documents = client.mget( { index: index_name, type: type, body: { ids: ids } }.merge(options) ) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb index 4d58b7248..220abbed4 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb @@ -3,14 +3,21 @@ module Persistence module Repository module Naming + + # Get or set the class used to initialize domain objects when deserializing them + # def klass name=nil @klass = name || @klass end + # Set the class used to initialize domain objects when deserializing them + # def klass=klass @klass = klass end + # Get or set the index name used when storing and retrieving documents + # def index_name name=nil @index_name = name || @index_name || begin if respond_to?(:host) && host && host.is_a?(Module) @@ -21,18 +28,32 @@ def index_name name=nil end end; alias :index :index_name + # Set the index name used when storing and retrieving documents + # def index_name=(name) @index_name = name end; alias :index= :index_name= + # Get or set the document type used when storing and retrieving documents + # def document_type name=nil @document_type = name || @document_type || (klass ? klass.to_s.underscore : nil) end; alias :type :document_type + # Set the document type used when storing and retrieving documents + # def document_type=(name) @document_type = name end; alias :type= :document_type= + # Get the Ruby class from the Elasticsearch `_type` + # + # @example + # repository.__get_klass_from_type 'note' + # => Note + # + # @api private + # def __get_klass_from_type(type) klass = type.classify klass.constantize @@ -40,10 +61,26 @@ def __get_klass_from_type(type) raise NameError, "Attempted to get class '#{klass}' from the '#{type}' type, but no such class can be found." end + # Get the Elasticsearch `_type` from the Ruby class + # + # @example + # repository.__get_type_from_class Note + # => "note" + # + # @api private + # def __get_type_from_class(klass) klass.to_s.underscore end + # Get a document ID from the document (assuming Hash or Hash-like object) + # + # @example + # repository.__get_id_from_document title: 'Test', id: 'abc123' + # => "abc123" + # + # @api private + # def __get_id_from_document(document) document[:id] || document['id'] || document[:_id] || document['_id'] end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb index b269076b6..fe64ac9b0 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb @@ -1,13 +1,21 @@ module Elasticsearch module Persistence module Repository - module Response + module Response # :nodoc: + # Encapsulates the domain objects and documents returned from Elasticsearch when searching + # + # Implements `Enumerable` and forwards its methods to the {#results} object. + # class Results include Enumerable - attr_reader :repository, :response, :response + attr_reader :repository + # @param repository [Elasticsearch::Persistence::Repository::Class] The repository instance + # @param response [Hash] The full response returned from the Elasticsearch client + # @param options [Hash] Optional parameters + # def initialize(repository, response, options={}) @repository = repository @response = Hashie::Mash.new(response) @@ -22,10 +30,14 @@ def respond_to?(method_name, include_private = false) results.respond_to?(method_name) || super end + # The number of total hits for a query + # def total response['hits']['total'] end + # The maximum score for a query + # def max_score response['hits']['max_score'] end @@ -42,11 +54,35 @@ def map_with_hit(&block) results.zip(response['hits']['hits']).map(&block) end + # Return the collection of domain objects + # + # @example Iterate over the results + # + # results.map { |r| r.attributes[:title] } + # => ["Fox", "Dog"] + # + # @return [Array] + # def results @results ||= response['hits']['hits'].map do |document| repository.deserialize(document.to_hash) end end + + # Access the response returned from Elasticsearch by the client + # + # @example Access the aggregations in the response + # + # results = repository.search query: { match: { title: 'fox dog' } }, + # aggregations: { titles: { terms: { field: 'title' } } } + # results.response.aggregations.titles.buckets.map { |term| "#{term['key']}: #{term['doc_count']}" } + # # => ["brown: 1", "dog: 1", ...] + # + # @return [Hashie::Mash] + # + def response + @response + end end end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb index 01bc3916c..5f03a6526 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb @@ -2,7 +2,43 @@ module Elasticsearch module Persistence module Repository + # Returns a collection of domain objects by an Elasticsearch query + # module Search + + # Returns a collection of domain objects by an Elasticsearch query + # + # Pass the query either as a string or a Hash-like object + # + # @example Return objects matching a simple query + # + # repository.search('fox or dog') + # + # @example Return objects matching a query in the Elasticsearch DSL + # + # repository.search(query: { match: { title: 'fox dog' } }) + # + # @example Define additional search parameters, such as highlighted excerpts + # + # results = repository.search(query: { match: { title: 'fox dog' } }, highlight: { fields: { title: {} } }) + # results.map_with_hit { |d,h| h.highlight.title.join } + # # => ["quick brown fox", "fast white dog"] + # + # @example Perform aggregations as part of the request + # + # results = repository.search query: { match: { title: 'fox dog' } }, + # aggregations: { titles: { terms: { field: 'title' } } } + # results.response.aggregations.titles.buckets.map { |term| "#{term['key']}: #{term['doc_count']}" } + # # => ["brown: 1", "dog: 1", ... ] + # + # @example Pass additional options to the search request, such as `size` + # + # repository.search query: { match: { title: 'fox dog' } }, size: 25 + # # GET http://localhost:9200/notes/note/_search + # # > {"query":{"match":{"title":"fox dog"}},"size":25} + # + # @return [Elasticsearch::Persistence::Repository::Response::Results] + # def search(query_or_definition, options={}) type = (klass ? __get_type_from_class(klass) : nil ) case diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb index 5e4b4be00..027f000b6 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb @@ -2,11 +2,24 @@ module Elasticsearch module Persistence module Repository + # Provide serialization and deserialization between Ruby objects and Elasticsearch documents + # + # Override these methods in your repository class to customize the logic. + # module Serialize + + # Serialize the object for storing it in Elasticsearch + # + # In the default implementation, call the `to_hash` method on the passed object. + # def serialize(document) document.to_hash end + # Deserialize the document retrieved from Elasticsearch into a Ruby object + # + # Use the `klass` property, if defined, otherwise try to get the class from the document's `_type`. + # def deserialize(document) _klass = klass || __get_klass_from_type(document['_type']) _klass.new document['_source'] diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb index 936aa1d79..07bdce569 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb @@ -2,7 +2,12 @@ module Elasticsearch module Persistence module Repository + # Save and delete documents in Elasticsearch + # module Store + + # Store the serialized object in Elasticsearch + # def save(document, options={}) serialized = serialize(document) id = __get_id_from_document(serialized) @@ -10,6 +15,8 @@ def save(document, options={}) client.index( { index: index_name, type: type, id: id, body: serialized }.merge(options) ) end + # Remove the serialized object or document with specified ID from Elasticsearch + # def delete(document, options={}) if document.is_a?(String) || document.is_a?(Integer) id = document From 94a19dee3d31b5d706c13470bb45414df4d178b8 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Fri, 4 Apr 2014 20:42:14 +0200 Subject: [PATCH 058/582] [STORE] Added a comprehensive usage information / tutorial to the README --- elasticsearch-persistence/README.md | 405 +++++++++++++++++++++++++++- 1 file changed, 404 insertions(+), 1 deletion(-) diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index b0eda41b0..b02aa99a7 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -1,6 +1,409 @@ # Elasticsearch::Persistence -WIP> Persistence layer for Ruby domain objects using the Repository and ActiveRecord patterns +This library provides a persistence layer for Ruby domain objects in Elasticsearch, +using the Repository and ActiveRecord patterns. + +The library is compatible with Ruby 1.9.3 (or higher) and Elasticsearch 1.0 (or higher). + +## Installation + +Install the package from [Rubygems](https://rubygems.org): + + gem install elasticsearch-persistence + +To use an unreleased version, either add it to your `Gemfile` for [Bundler](http://bundler.io): + + gem 'elasticsearch-persistence', git: 'git://github.com/elasticsearch/elasticsearch-rails.git' + +or install it from a source code checkout: + + git clone https://github.com/elasticsearch/elasticsearch-rails.git + cd elasticsearch-rails/elasticsearch-persistence + bundle install + rake install + +## Usage + +### The Repository Pattern + +The `Elasticsearch::Persistence::Repository` module provides an implementation of the +[repository pattern](http://martinfowler.com/eaaCatalog/repository.html) and allows +to save, delete, find and search objects stored in Elasticsearch, as well as configure +mappings and settings for the index. + +Let's have a simple, plain old Ruby object (PORO): + +```ruby +class Note + attr_reader :attributes + + def initialize(attributes={}) + @attributes = attributes + end + + def to_hash + @attributes + end +end +``` + +Let's create a default, "dumb" repository, as a first step: + +```ruby +require 'elasticsearch/persistence' +repository = Elasticsearch::Persistence::Repository.new +``` + +We can save a `Note` instance into the repository... + +```ruby +note = Note.new id: 1, text: 'Test' + +repository.save(note) +# PUT http://localhost:9200/repository/note/1 [status:201, request:0.210s, query:n/a] +# > {"id":1,"text":"Test"} +# < {"_index":"repository","_type":"note","_id":"1","_version":1,"created":true} +``` + +...find it... + +```ruby +n = repository.find(1) +# GET http://localhost:9200/repository/_all/1 [status:200, request:0.003s, query:n/a] +# < {"_index":"repository","_type":"note","_id":"1","_version":2,"found":true, "_source" : {"id":1,"text":"Test"}} +=> 1, "text"=>"Test"}> +``` + +...search for it... + +```ruby +repository.search(query: { match: { text: 'test' } }).first +# GET http://localhost:9200/repository/_search [status:200, request:0.005s, query:0.002s] +# > {"query":{"match":{"text":"test"}}} +# < {"took":2, ... "hits":{"total":1, ... "hits":[{ ... "_source" : {"id":1,"text":"Test"}}]}} +=> 1, "text"=>"Test"}> +``` + +...or delete it: + +```ruby +repository.delete(note) +# DELETE http://localhost:9200/repository/note/1 [status:200, request:0.014s, query:n/a] +# < {"found":true,"_index":"repository","_type":"note","_id":"1","_version":3} +=> {"found"=>true, "_index"=>"repository", "_type"=>"note", "_id"=>"1", "_version"=>2} +``` + +The repository module provides a number of features and facilities to configure and customize the behaviour: + +* Configuring the Elasticsearch [client](https://github.com/elasticsearch/elasticsearch-ruby#usage) being used +* Setting the index name, document type, and object class for deserialization +* Composing mappings and settings for the index +* Creating, deleting or refreshing the index +* Finding or searching for documents +* Providing access both to domain objects and hits for search results +* Providing access to the Elasticsearch response for search results (aggregations, total, ...) +* Defining the methods for serialization and deserialization + +You can use the default repository class, or include the module in your own. Let's review it in detail. + +#### The Default Class + +For simple cases, you can use the default, bundled repository class, and configure/customize it: + +```ruby +repository = Elasticsearch::Persistence::Repository.new do + # Configure the Elasticsearch client + client Elasticsearch::Client.new url: ENV['ELASTICSEARCH_URL'], log: true + + # Set a custom index name + index :my_notes + + # Set a custom document type + type :my_note + + # Specify the class to inicialize when deserializing documents + klass Note + + # Configure the settings and mappings for the Elasticsearch index + settings number_of_shards: 1 do + mapping do + indexes :text, analyzer: 'snowball' + end + end + + # Customize the serialization logic + def serialize(document) + super.merge(my_special_key: 'my_special_stuff') + end + + # Customize the de-serialization logic + def deserialize(document) + puts "# ***** CUSTOM DESERIALIZE LOGIC KICKING IN... *****" + super + end +end +``` + +The custom Elasticsearch client will be used now, with a custom index and type names, +as well as the custom serialization and de-serialization logic. + +We can create the index with the desired settings and mappings: + +```ruby +repository.create_index! force: true +# PUT http://localhost:9200/my_notes +# > {"settings":{"number_of_shards":1},"mappings":{ ... {"text":{"analyzer":"snowball","type":"string"}}}}} +``` + +Save the document with extra properties added by the `serialize` method: + +```ruby +repository.save(note) +# PUT http://localhost:9200/my_notes/my_note/1 +# > {"id":1,"text":"Test","my_special_key":"my_special_stuff"} +{"_index"=>"my_notes", "_type"=>"my_note", "_id"=>"1", "_version"=>4, ... } +``` + +And `deserialize` it: + +```ruby +repository.find(1) +# ***** CUSTOM DESERIALIZE LOGIC KICKING IN... ***** +"my_special_stuff"}> +``` + +#### A Custom Class + +In most cases, though, you'll want to use a custom class for the repository, so let's do that: + +```ruby +require 'base64' + +class NoteRepository + include Elasticsearch::Persistence::Repository + + def initialize(options={}) + index options[:index] || 'notes' + client Elasticsearch::Client.new url: options[:url], log: options[:log] + end + + klass Note + + settings number_of_shards: 1 do + mapping do + indexes :text, analyzer: 'snowball' + # Do not index images + indexes :image, index: 'no' + end + end + + # Base64 encode the "image" field in the document + # + def serialize(document) + hash = document.to_hash.clone + hash['image'] = Base64.encode64(hash['image']) if hash['image'] + hash.to_hash + end + + # Base64 decode the "image" field in the document + # + def deserialize(document) + hash = document['_source'] + hash['image'] = Base64.decode64(hash['image']) if hash['image'] + klass.new hash + end +end +``` + +Include the `Elasticsearch::Persistence::Repository` module to add the repository methods into the class. + +You can customize the repository in the familiar way, by calling the DSL-like methods. + +You can implement a custom initializer for your repository, add complex logic in its +class and instance methods -- in general, have all the freedom of a standard Ruby class. + +```ruby +repository = NoteRepository.new url: 'http://localhost:9200', log: true + +# Configure the repository instance +repository.index = 'notes_development' +repository.client.transport.logger.formatter = proc { |s, d, p, m| "\e[2m# #{m}\n\e[0m" } + +repository.create_index! force: true + +note = Note.new 'id' => 1, 'text' => 'Document with image', 'image' => '... BINARY DATA ...' + +repository.save(note) +# PUT http://localhost:9200/notes_development/note/1 +# > {"id":1,"text":"Document with image","image":"Li4uIEJJTkFSWSBEQVRBIC4uLg==\n"} +puts repository.find(1).attributes['image'] +# GET http://localhost:9200/notes_development/note/1 +# < {... "_source" : { ... "image":"Li4uIEJJTkFSWSBEQVRBIC4uLg==\n"}} +# => ... BINARY DATA ... +``` + +#### Methods Provided by the Repository + +##### Client + +The repository uses the standard Elasticsearch [client](https://github.com/elasticsearch/elasticsearch-ruby#usage), +which is accessible with the `client` getter and setter methods: + +```ruby +repository.client = Elasticsearch::Client.new url: 'http://search.server.org' +repository.client.transport.logger = Logger.new(STDERR) +``` + +##### Naming + +The `index` method specifies the Elasticsearch index to use for storage, lookup and search +(when not set, the value is inferred from the repository class name): + +```ruby +repository.index = 'notes_development' +``` + +The `type` method specifies the Elasticsearch document type to use for storage, lookup and search +(when not set, the value is inferred from the document class name, or `_all` is used): + +```ruby +repository.type = 'my_note' +``` + +The `klass` method specifies the Ruby class name to use when initializing objects from +documents retrieved from the repository (when not set, the value is inferred from the +document `_type` as fetched from Elasticsearch): + +```ruby +repository.klass = MyNote +``` + +##### Index Configuration + +The `settings` and `mappings` methods, provided by the +[`elasticsearch-model`](http://rubydoc.info/gems/elasticsearch-model/Elasticsearch/Model/Indexing/ClassMethods) +gem, allow to configure the index properties: + +```ruby +repository.settings number_of_shards: 1 +repository.settings.to_hash +# => {:number_of_shards=>1} + +repository.mappings { indexes :title, analyzer: 'snowball' } +repository.mappings.to_hash +# => { :note => {:properties=> ... }} +``` + +The convenience methods `create_index!`, `delete_index!` and `refresh_index!` allow you to manage the index lifecycle. + +##### Serialization + +The `serialize` and `deserialize` methods allow you to customize the serialization of the document when passing it +to the storage, and the initialization procedure when loading it from the storage: + +```ruby +class NoteRepository + def serialize(document) + Hash[document.to_hash.map() { |k,v| v.upcase! if k == :title; [k,v] }] + end + def deserialize(document) + MyNote.new ActiveSupport::HashWithIndifferentAccess.new(document['_source']).deep_symbolize_keys + end +end +``` + +##### Storage + +The `save` method allows you to store a domain object in the repository: + +```ruby +note = Note.new id: 1, title: 'Quick Brown Fox' +repository.save(note) +# => {"_index"=>"notes_development", "_type"=>"my_note", "_id"=>"1", "_version"=>1, "created"=>true} +``` + +The `delete` method allows to remove objects from the repository (pass either the object itself or its ID): + +```ruby +repository.delete(note) +repository.delete(1) +``` + +##### Finding + +The `find` method allows to find one or many documents in the storage and returns them as deserialized Ruby objects: + +```ruby +repository.save Note.new(id: 2, title: 'Fast White Dog') + +note = repository.find(1) +# => + +notes = repository.find(1, 2) +# => [, ] +``` + +When the document with a specific ID isn't found, a `nil` is returned instead of the deserialized object: + +```ruby +notes = repository.find(1, 3, 2) +# => [, nil, ] +``` + +Handle the missing objects in the application code, or call `compact` on the result. + +##### Search + +The `search` method to retrieve objects from the repository by a query string or definition in the Elasticsearch DSL: + +```ruby +repository.search('fox or dog').to_a +# GET http://localhost:9200/notes_development/my_note/_search?q=fox +# => [, ] + +repository.search(query: { match: { title: 'fox dog' } }).to_a +# GET http://localhost:9200/notes_development/my_note/_search +# > {"query":{"match":{"title":"fox dog"}}} +# => [, ] +``` + +The returned object is an instance of the `Elasticsearch::Persistence::Repository::Response::Results` class, +which provides access to the results, the full returned response and hits. + +```ruby +results = repository.search(query: { match: { title: 'fox dog' } }) + +# Iterate over the objects +# +results.each do |note| + puts "* #{note.attributes[:title]}" +end +# * QUICK BROWN FOX +# * FAST WHITE DOG + +# Iterate over the objects and hits +# +results.each_with_hit do |note, hit| + puts "* #{note.attributes[:title]}, score: #{hit._score}" +end +# * QUICK BROWN FOX, score: 0.29930896 +# * FAST WHITE DOG, score: 0.29930896 + +# Get total results +# +results.total +# => 2 + +# Access the raw response as a Hashie::Mash instance +results.response._shards.failed +# => 0 +``` + +### The ActiveRecord Pattern + +_Work in progress_. The ActiveRecord [pattern](http://www.martinfowler.com/eaaCatalog/activeRecord.html) will work +in a very similar way as `Tire::Model::Persistence`, allowing a drop-in replacement of an Elasticsearch-backed model +in Ruby on Rails applications. ## License From e853785ec76c66ce461cd693993e300c6979bd63 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 7 Apr 2014 14:24:33 +0200 Subject: [PATCH 059/582] [STORE] Added an example Sinatra web application for the repository pattern --- .../examples/sinatra/.gitignore | 7 + .../examples/sinatra/Gemfile | 28 +++ .../examples/sinatra/README.markdown | 36 +++ .../examples/sinatra/application.rb | 238 ++++++++++++++++++ .../examples/sinatra/config.ru | 7 + .../examples/sinatra/test.rb | 118 +++++++++ 6 files changed, 434 insertions(+) create mode 100644 elasticsearch-persistence/examples/sinatra/.gitignore create mode 100644 elasticsearch-persistence/examples/sinatra/Gemfile create mode 100644 elasticsearch-persistence/examples/sinatra/README.markdown create mode 100644 elasticsearch-persistence/examples/sinatra/application.rb create mode 100644 elasticsearch-persistence/examples/sinatra/config.ru create mode 100644 elasticsearch-persistence/examples/sinatra/test.rb diff --git a/elasticsearch-persistence/examples/sinatra/.gitignore b/elasticsearch-persistence/examples/sinatra/.gitignore new file mode 100644 index 000000000..e9d847d61 --- /dev/null +++ b/elasticsearch-persistence/examples/sinatra/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +Gemfile.lock +tmp/* +log/* +doc/ +.yardoc +.vagrant diff --git a/elasticsearch-persistence/examples/sinatra/Gemfile b/elasticsearch-persistence/examples/sinatra/Gemfile new file mode 100644 index 000000000..63d5d75c5 --- /dev/null +++ b/elasticsearch-persistence/examples/sinatra/Gemfile @@ -0,0 +1,28 @@ +source 'https://rubygems.org' + +gem 'rake' +gem 'ansi' + +gem 'multi_json' +gem 'oj' +gem 'hashie' + +gem 'patron' +gem 'elasticsearch' +gem 'elasticsearch-model', path: File.expand_path('../../../../elasticsearch-model', __FILE__) +gem 'elasticsearch-persistence', path: File.expand_path('../../../', __FILE__) + +gem 'sinatra', require: false +gem 'thin' + +group :development do + gem 'sinatra-contrib' +end + +group :test do + gem 'elasticsearch-extensions' + gem 'rack-test' + gem 'shoulda-context' + gem 'turn' + gem 'mocha' +end diff --git a/elasticsearch-persistence/examples/sinatra/README.markdown b/elasticsearch-persistence/examples/sinatra/README.markdown new file mode 100644 index 000000000..2aa15ec32 --- /dev/null +++ b/elasticsearch-persistence/examples/sinatra/README.markdown @@ -0,0 +1,36 @@ +Demo Aplication for the Repository Pattern +========================================== + +This directory contains a simple demo application for the repository pattern of the `Elasticsearch::Persistence` +module in the [Sinatra](http://www.sinatrarb.com) framework. + +To run the application, first install the required gems and start the application: + +``` +bundle install +bundle exec ruby application.rb +``` + +The application demonstrates: + +* How to use a plain old Ruby object (PORO) as the domain model +* How to set up, configure and use the repository instance +* How to use the repository in tests + +## License + +This software is licensed under the Apache 2 license, quoted below. + + Copyright (c) 2014 Elasticsearch + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/elasticsearch-persistence/examples/sinatra/application.rb b/elasticsearch-persistence/examples/sinatra/application.rb new file mode 100644 index 000000000..50f56dece --- /dev/null +++ b/elasticsearch-persistence/examples/sinatra/application.rb @@ -0,0 +1,238 @@ +$LOAD_PATH.unshift File.expand_path('../../../lib/', __FILE__) + +require 'sinatra/base' + +require 'multi_json' +require 'oj' +require 'hashie/mash' + +require 'elasticsearch' +require 'elasticsearch/model' +require 'elasticsearch/persistence' + +class Note + attr_reader :attributes + + def initialize(attributes={}) + @attributes = Hashie::Mash.new(attributes) + __add_date + __extract_tags + __truncate_text + self + end + + def method_missing(method_name, *arguments, &block) + attributes.respond_to?(method_name) ? attributes.__send__(method_name, *arguments, &block) : super + end + + def respond_to?(method_name, include_private=false) + attributes.respond_to?(method_name) || super + end + + def tags; attributes.tags || []; end + + def to_hash + @attributes.to_hash + end + + def __extract_tags + tags = attributes['text'].scan(/(\[\w+\])/).flatten if attributes['text'] + unless tags.nil? || tags.empty? + attributes.update 'tags' => tags.map { |t| t.tr('[]', '') } + attributes['text'].gsub!(/(\[\w+\])/, '').strip! + end + end + + def __add_date + attributes['created_at'] ||= Time.now.utc.iso8601 + end + + def __truncate_text + attributes['text'] = attributes['text'][0...80] + ' (...)' if attributes['text'] && attributes['text'].size > 80 + end +end + +class NoteRepository + include Elasticsearch::Persistence::Repository + + client Elasticsearch::Client.new url: ENV['ELASTICSEARCH_URL'], log: true + + index :notes + type :note + + mapping do + indexes :text, analyzer: 'snowball' + indexes :tags, analyzer: 'keyword' + indexes :created_at, type: 'date' + end + + create_index! + + def deserialize(document) + Note.new document['_source'].merge('id' => document['_id']) + end +end unless defined?(NoteRepository) + +class Application < Sinatra::Base + enable :logging + enable :inline_templates + enable :method_override + + configure :development do + enable :dump_errors + disable :show_exceptions + + require 'sinatra/reloader' + register Sinatra::Reloader + end + + set :repository, NoteRepository.new + set :per_page, 25 + + get '/' do + @page = [ params[:p].to_i, 1 ].max + + @notes = settings.repository.search \ + query: ->(q, t) do + query = if q && !q.empty? + { match: { text: q } } + else + { match_all: {} } + end + + filter = if t && !t.empty? + { term: { tags: t } } + end + + if filter + { filtered: { query: query, filter: filter } } + else + query + end + end.(params[:q], params[:t]), + + sort: [{created_at: {order: 'desc'}}], + + size: settings.per_page, + from: settings.per_page * (@page-1), + + aggregations: { tags: { terms: { field: 'tags' } } }, + + highlight: { fields: { text: { fragment_size: 0, pre_tags: [''],post_tags: [''] } } } + + erb :index + end + + post '/' do + unless params[:text].empty? + @note = Note.new params + settings.repository.save(@note, refresh: true) + end + + redirect back + end + + delete '/:id' do |id| + settings.repository.delete(id, refresh: true) + redirect back + end +end + +Application.run! if $0 == __FILE__ + +__END__ + +@@ layout + + + + Notes + + + + +<%= yield %> + + + +@@ index + +
+

Notes

+
+ +
+
+ +
+

All notes <%= @notes.size %>

+
    + <% @notes.response.aggregations.tags.buckets.each do |term| %> +
  • <%= term['key'] %> <%= term['doc_count'] %>
  • + <% end %> +
+

Add a note

+
+

+

+
+
+ +
+<% if @notes.empty? %> +

No notes found.

+<% end %> + +<% @notes.each_with_hit do |note, hit| %> +
+

+ <%= hit.highlight && hit.highlight.size > 0 ? hit.highlight.text.first : note.text %> + + <% note.tags.each do |tag| %> <%= tag %><% end %> + <%= Time.parse(note.created_at).strftime('%d/%m/%Y %H:%M') %> + +

+

+
+<% end %> + +<% if @notes.size > 0 && @page.next <= @notes.total / settings.per_page %> +

→ Load next

+<% end %> +
diff --git a/elasticsearch-persistence/examples/sinatra/config.ru b/elasticsearch-persistence/examples/sinatra/config.ru new file mode 100644 index 000000000..98f8403ad --- /dev/null +++ b/elasticsearch-persistence/examples/sinatra/config.ru @@ -0,0 +1,7 @@ +#\ --port 3000 --server thin + +require File.expand_path('../application', __FILE__) + +map '/' do + run Application +end diff --git a/elasticsearch-persistence/examples/sinatra/test.rb b/elasticsearch-persistence/examples/sinatra/test.rb new file mode 100644 index 000000000..cb9528747 --- /dev/null +++ b/elasticsearch-persistence/examples/sinatra/test.rb @@ -0,0 +1,118 @@ +ENV['RACK_ENV'] = 'test' + +at_exit { Elasticsearch::Test::IntegrationTestCase.__run_at_exit_hooks } if ENV['SERVER'] + +require 'test/unit' +require 'shoulda-context' +require 'mocha/setup' +require 'rack/test' +require 'turn' + +require 'elasticsearch/extensions/test/cluster' +require 'elasticsearch/extensions/test/startup_shutdown' + +require_relative 'application' + +NoteRepository.index_name = 'notes_test' + +class Elasticsearch::Persistence::ExampleApplicationTest < Test::Unit::TestCase + include Rack::Test::Methods + alias :response :last_response + + def app + Application.new + end + + context "Note" do + should "be initialized with a Hash" do + note = Note.new 'foo' => 'bar' + assert_equal 'bar', note.attributes['foo'] + end + + should "add created_at when it's not passed" do + note = Note.new + assert_not_nil note.created_at + assert_match /#{Time.now.year}/, note.created_at + end + + should "not add created_at when it's passed" do + note = Note.new 'created_at' => 'FOO' + assert_equal 'FOO', note.created_at + end + + should "trim long text" do + assert_equal 'Hello World', Note.new('text' => 'Hello World').text + assert_equal 'FOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFO (...)', + Note.new('text' => 'FOO'*200).text + end + + should "delegate methods to attributes" do + note = Note.new 'foo' => 'bar' + assert_equal 'bar', note.foo + end + + should "have tags" do + assert_not_nil Note.new.tags + end + + should "provide a `to_hash` method" do + note = Note.new 'foo' => 'bar' + assert_instance_of Hash, note.to_hash + assert_equal ['created_at', 'foo'], note.to_hash.keys.sort + end + + should "extract tags from the text" do + note = Note.new 'text' => 'Hello [foo] [bar]' + assert_equal 'Hello', note.text + assert_equal ['foo', 'bar'], note.tags + end + end + + context "Application" do + setup do + app.settings.repository.client = Elasticsearch::Client.new \ + hosts: [{ host: 'localhost', port: ENV.fetch('TEST_CLUSTER_PORT', 9250)}], + log: true + app.settings.repository.client.transport.logger.formatter = proc { |s, d, p, m| "\e[2m#{m}\n\e[0m" } + app.settings.repository.create_index! force: true + app.settings.repository.client.cluster.health wait_for_status: 'yellow' + end + + should "have the correct index name" do + assert_equal 'notes_test', app.settings.repository.index + end + + should "display empty page when there are no notes" do + get '/' + assert response.ok?, response.status.to_s + assert_match /No notes found/, response.body.to_s + end + + should "display the notes" do + app.settings.repository.save Note.new('text' => 'Hello') + app.settings.repository.refresh_index! + + get '/' + assert response.ok?, response.status.to_s + assert_match /

\s*Hello/, response.body.to_s + end + + should "create a note" do + post '/', { 'text' => 'Hello World' } + follow_redirect! + + assert response.ok?, response.status.to_s + assert_match /Hello World/, response.body.to_s + end + + should "delete a note" do + app.settings.repository.save Note.new('id' => 'foobar', 'text' => 'Perish...') + delete "/foobar" + follow_redirect! + + assert response.ok?, response.status.to_s + assert_no_match /Perish/, response.body.to_s + end + end + +end From 4881db84ac5271bfa278f7b00f8af6bc079b1cfa Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Thu, 17 Apr 2014 11:16:43 +0200 Subject: [PATCH 060/582] [STORE] Added development dependency on "oj" --- elasticsearch-persistence/elasticsearch-persistence.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index cb66916ed..cfbf2fcf0 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -30,6 +30,7 @@ Gem::Specification.new do |s| s.add_development_dependency "rake" s.add_development_dependency "elasticsearch-extensions" + s.add_development_dependency "oj" s.add_development_dependency "shoulda-context" s.add_development_dependency "mocha" From afafa1239e7081013dffa592063dd3321cf22d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Pospi=CC=81s=CC=8Cil?= Date: Tue, 15 Apr 2014 17:20:22 +0200 Subject: [PATCH 061/582] [STORE] Fixed, that `search` respects `document_type` set in class Closes #77 --- .../lib/elasticsearch/persistence/repository/search.rb | 2 +- elasticsearch-persistence/test/unit/repository_search_test.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb index 5f03a6526..a3843df51 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb @@ -40,7 +40,7 @@ module Search # @return [Elasticsearch::Persistence::Repository::Response::Results] # def search(query_or_definition, options={}) - type = (klass ? __get_type_from_class(klass) : nil ) + type = document_type || (klass ? __get_type_from_class(klass) : nil ) case when query_or_definition.respond_to?(:to_hash) response = client.search( { index: index_name, type: type, body: query_or_definition.to_hash }.merge(options) ) diff --git a/elasticsearch-persistence/test/unit/repository_search_test.rb b/elasticsearch-persistence/test/unit/repository_search_test.rb index 711ded396..8229075bb 100644 --- a/elasticsearch-persistence/test/unit/repository_search_test.rb +++ b/elasticsearch-persistence/test/unit/repository_search_test.rb @@ -8,6 +8,7 @@ class MyDocument; end @shoulda_subject = Class.new() { include Elasticsearch::Persistence::Repository::Search }.new @client = mock + @shoulda_subject.stubs(:document_type).returns(nil) @shoulda_subject.stubs(:klass).returns(nil) @shoulda_subject.stubs(:index_name).returns('test') @shoulda_subject.stubs(:client).returns(@client) From 0e66341937b58bef00757ba9fb868fddae722081 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Thu, 17 Apr 2014 11:17:03 +0200 Subject: [PATCH 062/582] [STORE] Added a unit test for searching in type based on `document_type` Related: #77 --- .../persistence/repository/search.rb | 1 + .../test/unit/repository_search_test.rb | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb index a3843df51..5ed018dab 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb @@ -41,6 +41,7 @@ module Search # def search(query_or_definition, options={}) type = document_type || (klass ? __get_type_from_class(klass) : nil ) + case when query_or_definition.respond_to?(:to_hash) response = client.search( { index: index_name, type: type, body: query_or_definition.to_hash }.merge(options) ) diff --git a/elasticsearch-persistence/test/unit/repository_search_test.rb b/elasticsearch-persistence/test/unit/repository_search_test.rb index 8229075bb..df3f7dc61 100644 --- a/elasticsearch-persistence/test/unit/repository_search_test.rb +++ b/elasticsearch-persistence/test/unit/repository_search_test.rb @@ -28,7 +28,22 @@ class MyDocument; end subject.search foo: 'bar' end + should "search in type based on document_type" do + subject.expects(:document_type).returns('my_special_document').at_least_once + subject.expects(:__get_type_from_class).never + + @client.expects(:search).with do |arguments| + assert_equal 'test', arguments[:index] + assert_equal 'my_special_document', arguments[:type] + + assert_equal({foo: 'bar'}, arguments[:body]) + end + + subject.search foo: 'bar' + end + should "search across all types" do + subject.expects(:document_type).returns(nil).at_least_once subject.expects(:klass).returns(nil).at_least_once subject.expects(:__get_type_from_class).never From 44f6bcaa36360d657484d2b39bd12b624ef3b523 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Thu, 17 Apr 2014 18:09:15 +0200 Subject: [PATCH 063/582] [STORE] Added integration tests for the Repository pattern Related: #71 --- .../elasticsearch-persistence.gemspec | 4 +- .../repository/custom_class_test.rb | 85 ++++++++++++++++++ .../repository/customized_class_test.rb | 68 ++++++++++++++ .../repository/default_class_test.rb | 89 +++++++++++++++++++ .../repository/virtus_model_test.rb | 45 ++++++++++ elasticsearch-persistence/test/test_helper.rb | 4 + 6 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 elasticsearch-persistence/test/integration/repository/custom_class_test.rb create mode 100644 elasticsearch-persistence/test/integration/repository/customized_class_test.rb create mode 100644 elasticsearch-persistence/test/integration/repository/default_class_test.rb create mode 100644 elasticsearch-persistence/test/integration/repository/virtus_model_test.rb diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index cfbf2fcf0..a1fdc4dad 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -29,8 +29,10 @@ Gem::Specification.new do |s| s.add_development_dependency "bundler", "~> 1.5" s.add_development_dependency "rake" - s.add_development_dependency "elasticsearch-extensions" s.add_development_dependency "oj" + s.add_development_dependency "virtus" + + s.add_development_dependency "elasticsearch-extensions" s.add_development_dependency "shoulda-context" s.add_development_dependency "mocha" diff --git a/elasticsearch-persistence/test/integration/repository/custom_class_test.rb b/elasticsearch-persistence/test/integration/repository/custom_class_test.rb new file mode 100644 index 000000000..0aaae91ff --- /dev/null +++ b/elasticsearch-persistence/test/integration/repository/custom_class_test.rb @@ -0,0 +1,85 @@ +require 'test_helper' + +module Elasticsearch + module Persistence + class RepositoryCustomClassIntegrationTest < Elasticsearch::Test::IntegrationTestCase + + class ::MyNote + attr_reader :attributes + + def initialize(attributes={}) + @attributes = Hashie::Mash.new(attributes) + end + + def method_missing(method_name, *arguments, &block) + attributes.respond_to?(method_name) ? attributes.__send__(method_name, *arguments, &block) : super + end + + def respond_to?(method_name, include_private=false) + attributes.respond_to?(method_name) || super + end + + def to_hash + @attributes + end + end + + context "A custom repository class" do + setup do + class ::MyNotesRepository + include Elasticsearch::Persistence::Repository + + klass MyNote + + settings number_of_shards: 1 do + mapping do + indexes :title, analyzer: 'snowball' + end + end + + create_index! + + def deserialize(document) + klass.new document.merge(document['_source']) + end + end + + @repository = MyNotesRepository.new + + @repository.client.cluster.health wait_for_status: 'yellow' + end + + should "save the object under a correct index and type" do + @repository.save MyNote.new(id: '1', title: 'Test') + result = @repository.find(1) + + assert_instance_of MyNote, result + assert_equal 'Test', result.title + + assert_not_nil Elasticsearch::Persistence.client.get index: 'my_notes_repository', + type: 'my_note', + id: '1' + end + + should "delete the object" do + note = MyNote.new id: 1, title: 'Test' + @repository.save note + + assert_not_nil @repository.find(1) + + @repository.delete(note) + assert_raise(Elasticsearch::Persistence::Repository::DocumentNotFound) { @repository.find(1) } + end + + should "retrieve the object via a search query" do + note = MyNote.new title: 'Testing' + @repository.save note, refresh: true + + results = @repository.search query: { match: { title: 'Test' } } + assert_equal 'Testing', results.first.title + end + end + + end + end +end diff --git a/elasticsearch-persistence/test/integration/repository/customized_class_test.rb b/elasticsearch-persistence/test/integration/repository/customized_class_test.rb new file mode 100644 index 000000000..4903ccfac --- /dev/null +++ b/elasticsearch-persistence/test/integration/repository/customized_class_test.rb @@ -0,0 +1,68 @@ +require 'test_helper' + +module Elasticsearch + module Persistence + class RepositoryCustomizedClassIntegrationTest < Elasticsearch::Test::IntegrationTestCase + + module ::My + class Note + attr_reader :attributes + + def initialize(attributes={}) + @attributes = attributes + end + + def to_hash + @attributes + end + end + end + + context "A custom repository class" do + setup do + @repository = Elasticsearch::Persistence::Repository.new do + index 'my_notes' + type 'my_note' + klass My::Note + + settings number_of_shards: 1 do + mapping do + indexes :title, analyzer: 'snowball' + end + end + + create_index! + end + + @repository.client.cluster.health wait_for_status: 'yellow' + end + + should "save the object under a correct index and type" do + @repository.save My::Note.new(id: '1', title: 'Test') + + assert_instance_of My::Note, @repository.find(1) + assert_not_nil Elasticsearch::Persistence.client.get index: 'my_notes', type: 'my_note', id: '1' + end + + should "delete the object" do + note = My::Note.new id: 1, title: 'Test' + @repository.save note + + assert_not_nil @repository.find(1) + + @repository.delete(note) + assert_raise(Elasticsearch::Persistence::Repository::DocumentNotFound) { @repository.find(1) } + end + + should "create the index with correct mapping" do + note = My::Note.new title: 'Testing' + @repository.save note, refresh: true + + results = @repository.search query: { match: { title: 'Test' } } + assert_equal 'Testing', results.first.attributes['title'] + end + end + + end + end +end diff --git a/elasticsearch-persistence/test/integration/repository/default_class_test.rb b/elasticsearch-persistence/test/integration/repository/default_class_test.rb new file mode 100644 index 000000000..18652cb64 --- /dev/null +++ b/elasticsearch-persistence/test/integration/repository/default_class_test.rb @@ -0,0 +1,89 @@ +require 'test_helper' + +module Elasticsearch + module Persistence + class RepositoryDefaultClassIntegrationTest < Elasticsearch::Test::IntegrationTestCase + + class ::Note + attr_reader :attributes + + def initialize(attributes={}) + @attributes = attributes + end + + def to_hash + @attributes + end + end + + context "The default repository class" do + setup do + @repository = Elasticsearch::Persistence::Repository.new + @repository.client.cluster.health wait_for_status: 'yellow' + end + + should "save the object with a custom ID and find it" do + @repository.save Note.new(id: '1', title: 'Test') + + assert_equal 'Test', @repository.find(1).attributes['title'] + end + + should "save the object with an auto-generated ID and find it" do + response = @repository.save Note.new(title: 'Test') + + assert_equal 'Test', @repository.find(response['_id']).attributes['title'] + end + + should "delete an object" do + note = Note.new(id: '1', title: 'Test') + + @repository.save(note) + assert_not_nil @repository.find(1) + @repository.delete(note) + assert_raise(Elasticsearch::Persistence::Repository::DocumentNotFound) { @repository.find(1) } + end + + should "find multiple objects" do + (1..5).each { |i| @repository.save Note.new(id: i, title: "Test #{i}") } + + assert_equal 5, @repository.find(1, 2, 3, 4, 5).size + assert_equal 5, @repository.find([1, 2, 3, 4, 5]).size + end + + should "pass options to save and find" do + note = Note.new(id: '1', title: 'Test') + @repository.save note, routing: 'ABC' + + assert_raise Elasticsearch::Persistence::Repository::DocumentNotFound do + @repository.find(1, routing: 'DEF') + end + + assert_nothing_raised do + note = @repository.find(1, routing: 'ABC') + assert_instance_of Note, note + end + end + + should "find notes with full text search" do + @repository.save Note.new(title: 'Test') + @repository.save Note.new(title: 'Test Test') + @repository.save Note.new(title: 'Crust') + @repository.client.indices.refresh index: @repository.index_name + + results = @repository.search 'test' + assert_equal 2, results.size + + results = @repository.search query: { match: { title: 'Test' } } + assert_equal 2, results.size + end + + should "save and find a plain hash" do + @repository.save id: 1, title: 'Hash' + result = @repository.find(1) + assert_equal 'Hash', result['_source']['title'] + end + end + + end + end +end diff --git a/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb b/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb new file mode 100644 index 000000000..09544b39f --- /dev/null +++ b/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb @@ -0,0 +1,45 @@ +require 'test_helper' + +require 'virtus' + +module Elasticsearch + module Persistence + class RepositoryWithVirtusIntegrationTest < Elasticsearch::Test::IntegrationTestCase + + class ::Page + include Virtus.model + + attribute :title, String + attribute :views, Integer, default: 0 + attribute :published, Boolean, default: false + attribute :slug, String, default: lambda { |page, attribute| page.title.downcase.gsub(' ', '-') } + end + + context "The repository with a Virtus model" do + setup do + @repository = Elasticsearch::Persistence::Repository.new do + index :pages + end + end + + should "save and find the object" do + page = Page.new title: 'Test Page' + + response = @repository.save page + id = response['_id'] + + result = @repository.find(id) + + assert_instance_of Page, result + assert_equal 'Test Page', result.title + assert_equal 0, result.views + + assert_not_nil Elasticsearch::Persistence.client.get index: 'pages', + type: 'page', + id: id + end + end + + end + end +end diff --git a/elasticsearch-persistence/test/test_helper.rb b/elasticsearch-persistence/test/test_helper.rb index 5a76e67ae..2ac1ae262 100644 --- a/elasticsearch-persistence/test/test_helper.rb +++ b/elasticsearch-persistence/test/test_helper.rb @@ -37,6 +37,10 @@ def setup host: "localhost:#{(ENV['TEST_CLUSTER_PORT'] || 9250)}", tracer: (ENV['QUIET'] ? nil : tracer) end + + def teardown + Elasticsearch::Persistence.client.indices.delete index: '_all' + end end end end From c44c34308d904a0f54baf347746558f9e1d7d022 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Sat, 19 Apr 2014 11:13:45 +0200 Subject: [PATCH 064/582] [STORE] Added the `__extract_id_from_document` method to Naming module --- .../persistence/repository/naming.rb | 15 +++++++++++++ .../test/unit/repository_naming_test.rb | 21 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb index 220abbed4..d5052c0ae 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb @@ -84,6 +84,21 @@ def __get_type_from_class(klass) def __get_id_from_document(document) document[:id] || document['id'] || document[:_id] || document['_id'] end + + # Extract a document ID from the document (assuming Hash or Hash-like object) + # + # @example + # options = { title: 'Test', id: 'abc123' } + # repository.__extract_id_from_document options + # # => "abc123" + # options + # # => { title: 'Test' } + # + # @api private + # + def __extract_id_from_document(document) + document.delete(:id) || document.delete('id') || document.delete(:_id) || document.delete('_id') + end end end diff --git a/elasticsearch-persistence/test/unit/repository_naming_test.rb b/elasticsearch-persistence/test/unit/repository_naming_test.rb index 0c6f9833e..a4845674d 100644 --- a/elasticsearch-persistence/test/unit/repository_naming_test.rb +++ b/elasticsearch-persistence/test/unit/repository_naming_test.rb @@ -49,6 +49,27 @@ module ::Foo; class Bar; end; end end end + context "extract an ID from the document" do + should "delete the key from theHash" do + d1 = { :id => 1 } + d2 = { :_id => 1 } + d3 = { 'id' => 1 } + d4 = { '_id' => 1 } + + assert_equal 1, subject.__extract_id_from_document(d1) + assert_nil d1[:id] + + assert_equal 1, subject.__extract_id_from_document(d2) + assert_nil d1[:_id] + + assert_equal 1, subject.__extract_id_from_document(d3) + assert_nil d1['id'] + + assert_equal 1, subject.__extract_id_from_document(d4) + assert_nil d1['_id'] + end + end + context "document class name" do should "be nil by default" do assert_nil subject.klass From b97491a4eeede979e1f3dd7bae406ec0f6f804f4 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Sat, 19 Apr 2014 11:17:58 +0200 Subject: [PATCH 065/582] [STORE] Added a `update` method for the repository Store module Allows to use Elasticsearch's "Update API" to update the document either with partial document or a script. Examples in tests and the README. --- elasticsearch-persistence/README.md | 16 +++ .../persistence/repository/store.rb | 35 ++++++ .../repository/customized_class_test.rb | 14 +++ .../repository/default_class_test.rb | 19 +++ .../repository/virtus_model_test.rb | 71 ++++++++++- .../test/unit/repository_store_test.rb | 113 +++++++++++++++++- 6 files changed, 266 insertions(+), 2 deletions(-) diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index b02aa99a7..2421a9b9c 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -322,6 +322,22 @@ repository.save(note) # => {"_index"=>"notes_development", "_type"=>"my_note", "_id"=>"1", "_version"=>1, "created"=>true} ``` +The `update` method allows you to perform a partial update of a document in the repository. +Use either a partial document: + +```ruby +repository.update id: 1, title: 'UPDATED', tags: [] +# => {"_index"=>"notes_development", "_type"=>"note", "_id"=>"1", "_version"=>2} +``` + +Or a script (optionally with parameters): + +```ruby +repository.update 1, script: 'if (!ctx._source.tags.contains(t)) { ctx._source.tags += t }', params: { t: 'foo' } +# => {"_index"=>"notes_development", "_type"=>"note", "_id"=>"1", "_version"=>3} +``` + + The `delete` method allows to remove objects from the repository (pass either the object itself or its ID): ```ruby diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb index 07bdce569..eec87e0e0 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb @@ -15,6 +15,41 @@ def save(document, options={}) client.index( { index: index_name, type: type, id: id, body: serialized }.merge(options) ) end + # Update the serialized object in Elasticsearch with partial data or script + # + def update(document, options={}) + case + when document.is_a?(String) || document.is_a?(Integer) + id = document + when document.respond_to?(:to_hash) + serialized = document.to_hash + id = __extract_id_from_document(serialized) + else + raise ArgumentError, "Expected a document ID or a Hash-like object, #{document.class} given" + end + + type = options.delete(:type) || \ + (defined?(serialized) && serialized && serialized.delete(:type)) || \ + document_type || \ + __get_type_from_class(klass) + + if defined?(serialized) && serialized + body = if serialized[:script] + serialized.select { |k, v| [:script, :params, :upsert].include? k } + else + { doc: serialized } + end + else + body = {} + body.update( doc: options.delete(:doc)) if options[:doc] + body.update( script: options.delete(:script)) if options[:script] + body.update( params: options.delete(:params)) if options[:params] + body.update( upsert: options.delete(:upsert)) if options[:upsert] + end + + client.update( { index: index_name, type: type, id: id, body: body }.merge(options) ) + end + # Remove the serialized object or document with specified ID from Elasticsearch # def delete(document, options={}) diff --git a/elasticsearch-persistence/test/integration/repository/customized_class_test.rb b/elasticsearch-persistence/test/integration/repository/customized_class_test.rb index 4903ccfac..06289b12d 100644 --- a/elasticsearch-persistence/test/integration/repository/customized_class_test.rb +++ b/elasticsearch-persistence/test/integration/repository/customized_class_test.rb @@ -44,6 +44,20 @@ def to_hash assert_not_nil Elasticsearch::Persistence.client.get index: 'my_notes', type: 'my_note', id: '1' end + should "update the document" do + @repository.save Note.new(id: 1, title: 'Test') + + @repository.update 1, doc: { title: 'UPDATED' } + assert_equal 'UPDATED', @repository.find(1).attributes['title'] + end + + should "update the document with a script" do + @repository.save Note.new(id: 1, title: 'Test') + + @repository.update 1, script: 'ctx._source.title = "UPDATED"' + assert_equal 'UPDATED', @repository.find(1).attributes['title'] + end + should "delete the object" do note = My::Note.new id: 1, title: 'Test' @repository.save note diff --git a/elasticsearch-persistence/test/integration/repository/default_class_test.rb b/elasticsearch-persistence/test/integration/repository/default_class_test.rb index 18652cb64..efe926cfd 100644 --- a/elasticsearch-persistence/test/integration/repository/default_class_test.rb +++ b/elasticsearch-persistence/test/integration/repository/default_class_test.rb @@ -34,6 +34,25 @@ def to_hash assert_equal 'Test', @repository.find(response['_id']).attributes['title'] end + should "update the document" do + @repository.save Note.new(id: 1, title: 'Test') + + @repository.update 1, type: 'note', doc: { title: 'UPDATED' } + assert_equal 'UPDATED', @repository.find(1).attributes['title'] + end + + should "update the document with a script" do + @repository.save Note.new(id: 1, title: 'Test') + + @repository.update 1, type: 'note', script: 'ctx._source.title = "UPDATED"' + assert_equal 'UPDATED', @repository.find(1).attributes['title'] + end + + should "save the document with an upsert" do + @repository.update 1, type: 'note', script: 'ctx._source.clicks += 1', upsert: { clicks: 1 } + assert_equal 1, @repository.find(1).attributes['clicks'] + end + should "delete an object" do note = Note.new(id: '1', title: 'Test') diff --git a/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb b/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb index 09544b39f..fbef15ba7 100644 --- a/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb +++ b/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb @@ -9,16 +9,28 @@ class RepositoryWithVirtusIntegrationTest < Elasticsearch::Test::IntegrationTest class ::Page include Virtus.model + attribute :id, String, writer: :private attribute :title, String attribute :views, Integer, default: 0 attribute :published, Boolean, default: false - attribute :slug, String, default: lambda { |page, attribute| page.title.downcase.gsub(' ', '-') } + attribute :slug, String, default: lambda { |page, attr| page.title.downcase.gsub(' ', '-') } + + def set_id(id) + self.id = id + end end context "The repository with a Virtus model" do setup do @repository = Elasticsearch::Persistence::Repository.new do index :pages + klass Page + + def deserialize(document) + page = klass.new document['_source'] + page.set_id document['_id'] + page + end end end @@ -38,6 +50,63 @@ class ::Page type: 'page', id: id end + + should "update the object with a partial document" do + response = @repository.save Page.new(title: 'Test') + id = response['_id'] + + page = @repository.find(id) + + assert_equal 'Test', page.title + + @repository.update page.id, doc: { title: 'UPDATE' } + + page = @repository.find(id) + assert_equal 'UPDATE', page.title + end + + should "update the object with a Hash" do + response = @repository.save Page.new(title: 'Test') + id = response['_id'] + + page = @repository.find(id) + + assert_equal 'Test', page.title + + @repository.update id: page.id, title: 'UPDATE' + + page = @repository.find(id) + assert_equal 'UPDATE', page.title + end + + should "update the object with a script" do + response = @repository.save Page.new(title: 'Test Page') + id = response['_id'] + + page = @repository.find(id) + + assert_not_nil page.id + assert_equal 0, page.views + + @repository.update page.id, script: 'ctx._source.views += 1' + + page = @repository.find(id) + assert_equal 1, page.views + + @repository.update id: page.id, script: 'ctx._source.views += 1' + + page = @repository.find(id) + assert_equal 2, page.views + end + + should "update the object with a script and params" do + response = @repository.save Page.new(title: 'Test Page') + + @repository.update id: response['_id'], script: 'ctx._source.views += count', params: { count: 3 } + + page = @repository.find(response['_id']) + assert_equal 3, page.views + end end end diff --git a/elasticsearch-persistence/test/unit/repository_store_test.rb b/elasticsearch-persistence/test/unit/repository_store_test.rb index 48b916098..077840bfc 100644 --- a/elasticsearch-persistence/test/unit/repository_store_test.rb +++ b/elasticsearch-persistence/test/unit/repository_store_test.rb @@ -5,7 +5,10 @@ class Elasticsearch::Persistence::RepositoryStoreTest < Test::Unit::TestCase class MyDocument; end setup do - @shoulda_subject = Class.new() { include Elasticsearch::Persistence::Repository::Store }.new + @shoulda_subject = Class.new() do + include Elasticsearch::Persistence::Repository::Store + include Elasticsearch::Persistence::Repository::Naming + end.new @shoulda_subject.stubs(:index_name).returns('test') end @@ -85,6 +88,114 @@ class MyDocument; end end end + context "update" do + should "get the ID from first argument and :doc from options" do + subject.expects(:serialize).never + subject.expects(:document_type).returns('mydoc') + subject.expects(:__extract_id_from_document).never + + client = mock + client.expects(:update).with do |arguments| + assert_equal '1', arguments[:id] + assert_equal 'mydoc', arguments[:type] + assert_equal({doc: { foo: 'bar' }}, arguments[:body]) + end + subject.expects(:client).returns(client) + + subject.update('1', doc: { foo: 'bar' }) + end + + should "get the ID from first argument and :script from options" do + subject.expects(:document_type).returns('mydoc') + subject.expects(:__extract_id_from_document).never + + client = mock + client.expects(:update).with do |arguments| + assert_equal '1', arguments[:id] + assert_equal 'mydoc', arguments[:type] + assert_equal({script: 'ctx._source.foo += 1'}, arguments[:body]) + end + subject.expects(:client).returns(client) + + subject.update('1', script: 'ctx._source.foo += 1') + end + + should "get the ID from first argument and :script with :upsert from options" do + subject.expects(:document_type).returns('mydoc') + subject.expects(:__extract_id_from_document).never + + client = mock + client.expects(:update).with do |arguments| + assert_equal '1', arguments[:id] + assert_equal 'mydoc', arguments[:type] + assert_equal({script: 'ctx._source.foo += 1', upsert: { foo: 1 }}, arguments[:body]) + end + subject.expects(:client).returns(client) + + subject.update('1', script: 'ctx._source.foo += 1', upsert: { foo: 1 }) + end + + should "get the ID and :doc from document" do + subject.expects(:document_type).returns('mydoc') + + client = mock + client.expects(:update).with do |arguments| + assert_equal '1', arguments[:id] + assert_equal 'mydoc', arguments[:type] + assert_equal({doc: { foo: 'bar' }}, arguments[:body]) + end + subject.expects(:client).returns(client) + + subject.update(id: '1', foo: 'bar') + end + + should "get the ID and :script from document" do + subject.expects(:document_type).returns('mydoc') + + client = mock + client.expects(:update).with do |arguments| + assert_equal '1', arguments[:id] + assert_equal 'mydoc', arguments[:type] + assert_equal({script: 'ctx._source.foo += 1'}, arguments[:body]) + end + subject.expects(:client).returns(client) + + subject.update(id: '1', script: 'ctx._source.foo += 1') + end + + should "get the ID and :script with :upsert from document" do + subject.expects(:document_type).returns('mydoc') + + client = mock + client.expects(:update).with do |arguments| + assert_equal '1', arguments[:id] + assert_equal 'mydoc', arguments[:type] + assert_equal({script: 'ctx._source.foo += 1', upsert: { foo: 1 } }, arguments[:body]) + end + subject.expects(:client).returns(client) + + subject.update(id: '1', script: 'ctx._source.foo += 1', upsert: { foo: 1 }) + end + + should "override the type from params" do + subject.expects(:document_type).never + + client = mock + client.expects(:update).with do |arguments| + assert_equal '1', arguments[:id] + assert_equal 'foo', arguments[:type] + assert_equal({script: 'ctx._source.foo += 1'}, arguments[:body]) + end + subject.expects(:client).returns(client) + + subject.update(id: '1', script: 'ctx._source.foo += 1', type: 'foo') + end + + should "raise an exception when passed incorrect argument" do + assert_raise(ArgumentError) { subject.update(MyDocument.new, foo: 'bar') } + end + end + context "delete" do should "get type from klass when passed only ID" do subject.expects(:serialize).never From 91814b30380f5d7517e3a4dd845ff8932f2dd997 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 27 May 2014 11:47:04 +0200 Subject: [PATCH 066/582] [STORE] Improved the documentation across the persistence gem Closes #71 --- elasticsearch-persistence/README.md | 19 +++++++++----- .../elasticsearch-persistence.gemspec | 2 ++ .../lib/elasticsearch/persistence/client.rb | 3 +++ .../elasticsearch/persistence/repository.rb | 24 ++++++++++++++++++ .../persistence/repository/class.rb | 8 +++--- .../persistence/repository/naming.rb | 9 +++++++ .../persistence/repository/store.rb | 25 +++++++++++++++++++ 7 files changed, 81 insertions(+), 9 deletions(-) diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index 2421a9b9c..5af250444 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -1,7 +1,6 @@ # Elasticsearch::Persistence -This library provides a persistence layer for Ruby domain objects in Elasticsearch, -using the Repository and ActiveRecord patterns. +Persistence layer for Ruby domain objects in Elasticsearch, using the Repository and ActiveRecord patterns. The library is compatible with Ruby 1.9.3 (or higher) and Elasticsearch 1.0 (or higher). @@ -31,7 +30,7 @@ The `Elasticsearch::Persistence::Repository` module provides an implementation o to save, delete, find and search objects stored in Elasticsearch, as well as configure mappings and settings for the index. -Let's have a simple, plain old Ruby object (PORO): +Let's have a simple plain old Ruby object (PORO): ```ruby class Note @@ -415,11 +414,19 @@ results.response._shards.failed # => 0 ``` +#### Example Application + +An example Sinatra application is available in +[`examples/sinatra/application.rb`](examples/sinatra/application.rb), +and demonstrates a rich set of features of the repository. + + ### The ActiveRecord Pattern -_Work in progress_. The ActiveRecord [pattern](http://www.martinfowler.com/eaaCatalog/activeRecord.html) will work -in a very similar way as `Tire::Model::Persistence`, allowing a drop-in replacement of an Elasticsearch-backed model -in Ruby on Rails applications. +[_Work in progress_](https://github.com/elasticsearch/elasticsearch-rails/pull/91). +The ActiveRecord [pattern](http://www.martinfowler.com/eaaCatalog/activeRecord.html) will work +in a very similar way as `Tire::Model::Persistence`, allowing a drop-in replacement of +an Elasticsearch-backed model in Ruby on Rails applications. ## License diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index a1fdc4dad..b0b618a78 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -21,6 +21,8 @@ Gem::Specification.new do |s| s.extra_rdoc_files = [ "README.md", "LICENSE.txt" ] s.rdoc_options = [ "--charset=UTF-8" ] + s.required_ruby_version = ">= 1.9.3" + s.add_dependency "elasticsearch", '> 0.4' s.add_dependency "elasticsearch-model", '>= 0.1' s.add_dependency "activesupport", '> 3' diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/client.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/client.rb index ece71b12b..3c9d618d4 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/client.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/client.rb @@ -2,6 +2,9 @@ module Elasticsearch module Persistence module Repository + # Wraps the Elasticsearch Ruby + # [client](https://github.com/elasticsearch/elasticsearch-ruby/tree/master/elasticsearch#usage) + # module Client # Get or set the default client for this repository diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index 8f08d3fd5..e6e40769d 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -1,5 +1,8 @@ module Elasticsearch module Persistence + + # Delegate methods to the repository (acting as a gateway) + # module GatewayDelegation def method_missing(method_name, *arguments, &block) gateway.respond_to?(method_name) ? gateway.__send__(method_name, *arguments, &block) : super @@ -14,10 +17,20 @@ def respond_to_missing?(method_name, *) end end + # When included, creates an instance of the {Repository::Class} class as a "gateway" + # + # @example Include the repository in a custom class + # + # class MyRepository + # include Elasticsearch::Persistence::Repository + # end + # module Repository def self.included(base) gateway = Elasticsearch::Persistence::Repository::Class.new host: base + # Define the instance level gateway + # base.class_eval do define_method :gateway do @gateway ||= gateway @@ -26,6 +39,8 @@ def self.included(base) include GatewayDelegation end + # Define the class level gateway + # (class << base; self; end).class_eval do define_method :gateway do |&block| @gateway ||= gateway @@ -36,6 +51,9 @@ def self.included(base) include GatewayDelegation end + # Catch repository methods (such as `serialize` and others) defined in the receiving class, + # and overload the default definition in the gateway + # def base.method_added(name) if :gateway != name && respond_to?(:gateway) && (gateway.public_methods - Object.public_methods).include?(name) gateway.define_singleton_method(name, self.new.method(name).to_proc) @@ -43,6 +61,12 @@ def base.method_added(name) end end + # Shortcut method to allow concise repository initialization + # + # @example Create a new default repository + # + # repository = Elasticsearch::Persistence::Repository.new + # def new(options={}, &block) Elasticsearch::Persistence::Repository::Class.new( {index: 'repository'}.merge(options), &block ) end; module_function :new diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb index 55b6bc8d4..0dcd2d576 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb @@ -8,10 +8,9 @@ module Repository # # repository = Elasticsearch::Persistence::Repository::Class.new # # => # - # # > repository.save(my_object) + # repository.save(my_object) # # => {"_index"=> ... } # - # # @example Shortcut use # # repository = Elasticsearch::Persistence::Repository.new @@ -22,6 +21,7 @@ module Repository # repository = Elasticsearch::Persistence::Repository.new do # index 'my_notes' # end + # # # => # # # > repository.save(my_object) # # => {"_index"=> ... } @@ -35,7 +35,7 @@ module Repository # repository = MyRepository.new # # repository.gateway.client.info - # => {"status"=>200, "name"=>"Venom", ... } + # # => {"status"=>200, "name"=>"Venom", ... } # class Class include Elasticsearch::Persistence::Repository::Client @@ -59,6 +59,8 @@ def initialize(options={}, &block) # # @return [nil, Class] # + # @api private + # def host options[:host] end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb index d5052c0ae..4c5794446 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb @@ -2,6 +2,8 @@ module Elasticsearch module Persistence module Repository + # Wraps all naming-related features of the repository (index name, the domain object class, etc) + # module Naming # Get or set the class used to initialize domain objects when deserializing them @@ -52,6 +54,9 @@ def document_type=(name) # repository.__get_klass_from_type 'note' # => Note # + # @return [Class] The class corresponding to the passed type + # @raise [NameError] if the class cannot be found + # # @api private # def __get_klass_from_type(type) @@ -67,6 +72,8 @@ def __get_klass_from_type(type) # repository.__get_type_from_class Note # => "note" # + # @return [String] The type corresponding to the passed class + # # @api private # def __get_type_from_class(klass) @@ -87,6 +94,8 @@ def __get_id_from_document(document) # Extract a document ID from the document (assuming Hash or Hash-like object) # + # @note Calling this method will *remove* the `id` or `_id` key from the passed object. + # # @example # options = { title: 'Test', id: 'abc123' } # repository.__extract_id_from_document options diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb index eec87e0e0..5bec487ce 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb @@ -8,6 +8,12 @@ module Store # Store the serialized object in Elasticsearch # + # @example + # repository.save(myobject) + # => {"_index"=>"...", "_type"=>"...", "_id"=>"...", "_version"=>1, "created"=>true} + # + # @return {Hash} The response from Elasticsearch + # def save(document, options={}) serialized = serialize(document) id = __get_id_from_document(serialized) @@ -17,6 +23,18 @@ def save(document, options={}) # Update the serialized object in Elasticsearch with partial data or script # + # @example Update the document with partial data + # + # repository.update id: 1, title: 'UPDATED', tags: [] + # # => {"_index"=>"...", "_type"=>"...", "_id"=>"1", "_version"=>2} + # + # @example Update the document with a script + # + # repository.update 1, script: 'ctx._source.views += 1' + # # => {"_index"=>"...", "_type"=>"...", "_id"=>"1", "_version"=>3} + # + # @return {Hash} The response from Elasticsearch + # def update(document, options={}) case when document.is_a?(String) || document.is_a?(Integer) @@ -52,6 +70,13 @@ def update(document, options={}) # Remove the serialized object or document with specified ID from Elasticsearch # + # @example Remove the document with ID 1 + # + # repository.delete(1) + # # => {"_index"=>"...", "_type"=>"...", "_id"=>"1", "_version"=>4} + # + # @return {Hash} The response from Elasticsearch + # def delete(document, options={}) if document.is_a?(String) || document.is_a?(Integer) id = document From 67ea513639a366ba215d4ad5158e32d178796303 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 27 May 2014 15:44:36 +0200 Subject: [PATCH 067/582] [RAILS] Changed the dependency on "elasticsearch-model" from local to Rubygems Otherwise, Bundler is stuck in endless "Resolving dependencies" loop. --- elasticsearch-rails/Gemfile | 7 ++++--- elasticsearch-rails/elasticsearch-rails.gemspec | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/elasticsearch-rails/Gemfile b/elasticsearch-rails/Gemfile index 204d24d2d..1aeec6c9a 100644 --- a/elasticsearch-rails/Gemfile +++ b/elasticsearch-rails/Gemfile @@ -3,6 +3,7 @@ source 'https://rubygems.org' # Specify your gem's dependencies in elasticsearch-rails.gemspec gemspec -if File.exists? File.expand_path("../../elasticsearch-model", __FILE__) - gem 'elasticsearch-model', :path => File.expand_path("../../elasticsearch-model", __FILE__), :require => true -end +# TODO: Figure out how to specify dependency on local elasticsearch-model without endless "Resolving dependencies" +# if File.exists? File.expand_path("../../elasticsearch-model", __FILE__) +# gem 'elasticsearch-model', :path => File.expand_path("../../elasticsearch-model", __FILE__), :require => true +# end diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index a22ac0d2b..f542ab490 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -27,6 +27,7 @@ Gem::Specification.new do |s| s.add_development_dependency "rake" s.add_development_dependency "elasticsearch-extensions" + s.add_development_dependency "elasticsearch-model" s.add_development_dependency "oj" s.add_development_dependency "rails", "> 3.0" From 49eb1cbe7b3df928c4b8e3292d0e45f2e32010ff Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 27 May 2014 16:06:46 +0200 Subject: [PATCH 068/582] Added the "elasticsearch-persistence" gem to the list of subprojects in the main README --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 7c5ef852c..61d48d28e 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,6 @@ require 'pathname' -subprojects = %w| elasticsearch-model elasticsearch-rails | +subprojects = %w| elasticsearch-model elasticsearch-rails elasticsearch-persistence | __current__ = Pathname( File.expand_path('..', __FILE__) ) From 0577d55f0734ba52668a84998379348f10df2d4d Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Sat, 31 May 2014 11:29:16 +0200 Subject: [PATCH 069/582] Added the information about `elasticsearch-persistence` to the main README --- README.md | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b1de11e2c..a5c5dadeb 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Elasticsearch -This repository contains ActiveModel, ActiveRecord and Ruby on Rails integrations for -[Elasticsearch](http://elasticsearch.org): +This repository contains various Ruby and Rails integrations for [Elasticsearch](http://elasticsearch.org): * ActiveModel integration with adapters for ActiveRecord and Mongoid +* _Repository Pattern_ based persistence layer for Ruby objects * Enumerable-based wrapper for search results * ActiveRecord::Relation-based wrapper for returning search results as records * Convenience model methods such as `search`, `mapping`, `import`, etc @@ -41,13 +41,16 @@ or install it from a source code checkout: ## Usage -This project is split into two separate gems: +This project is split into three separate gems: * [**`elasticsearch-model`**](https://github.com/elasticsearch/elasticsearch-rails/tree/master/elasticsearch-model), - which contains model-related features such as setting up indices, `search` method, pagination, etc + which contains search integration for Ruby/Rails models such as ActiveRecord::Base and Mongoid, + +* [**`elasticsearch-persistence`**](https://github.com/elasticsearch/elasticsearch-rails/tree/master/elasticsearch-persistence), + which provides standalone persistence layer for Ruby/Rails objects and models * [**`elasticsearch-rails`**](https://github.com/elasticsearch/elasticsearch-rails/tree/master/elasticsearch-rails), - which contains features for Ruby on Rails applications + which contains various features for Ruby on Rails applications Example of a basic integration into an ActiveRecord-based model: @@ -64,13 +67,30 @@ Article.import @articles = Article.search('foobar').records ``` +Example of using Elasticsearch as a repository for a Ruby model: + +```ruby +require 'virtus' +class Article + include Virtus.model + attribute :title, String +end + +require 'elasticsearch/persistence' +repository = Elasticsearch::Persistence::Repository.new + +repository.save Article.new(title: 'Test') +# POST http://localhost:9200/repository/article [status:201, request:0.760s, query:n/a] +# => {"_index"=>"repository", "_type"=>"article", "_id"=>"Ak75E0U9Q96T5Y999_39NA", ...} +``` + You can generate a fully working Ruby on Rails application with a single command: ```bash rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/01-basic.rb ``` -Please refer to each library documentation for detailed information and examples. +**Please refer to each library documentation for detailed information and examples.** ### Model @@ -78,6 +98,12 @@ Please refer to each library documentation for detailed information and examples * [[Documentation]](http://rubydoc.info/gems/elasticsearch-model/) * [[Test Suite]](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-model/test) +### Persistence + +* [[README]](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-persistence/README.md) +* [[Documentation]](http://rubydoc.info/gems/elasticsearch-persistence/) +* [[Test Suite]](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-persistence/test) + ### Rails * [[README]](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-rails/README.md) From 250eb4194fd9eef10a2f5170286cd9d95b0af5ae Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Sat, 31 May 2014 15:33:34 +0200 Subject: [PATCH 070/582] Release 0.1.3 --- elasticsearch-model/CHANGELOG.md | 2 ++ elasticsearch-model/lib/elasticsearch/model/version.rb | 2 +- elasticsearch-persistence/CHANGELOG.md | 7 +++++++ .../lib/elasticsearch/persistence/version.rb | 2 +- elasticsearch-rails/CHANGELOG.md | 4 +++- elasticsearch-rails/lib/elasticsearch/rails/version.rb | 2 +- 6 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 elasticsearch-persistence/CHANGELOG.md diff --git a/elasticsearch-model/CHANGELOG.md b/elasticsearch-model/CHANGELOG.md index 818fb3f07..734599d4f 100644 --- a/elasticsearch-model/CHANGELOG.md +++ b/elasticsearch-model/CHANGELOG.md @@ -1,3 +1,5 @@ +## 0.1.3 + ## 0.1.2 * Properly delegate existence methods like `result.foo?` to `result._source.foo` diff --git a/elasticsearch-model/lib/elasticsearch/model/version.rb b/elasticsearch-model/lib/elasticsearch/model/version.rb index b1692ab2e..16d1b7f24 100644 --- a/elasticsearch-model/lib/elasticsearch/model/version.rb +++ b/elasticsearch-model/lib/elasticsearch/model/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Model - VERSION = "0.1.2" + VERSION = "0.1.3" end end diff --git a/elasticsearch-persistence/CHANGELOG.md b/elasticsearch-persistence/CHANGELOG.md new file mode 100644 index 000000000..8abc96421 --- /dev/null +++ b/elasticsearch-persistence/CHANGELOG.md @@ -0,0 +1,7 @@ +## 0.1.3 + +* Released the "elasticsearch-persistence" Rubygem + +## 0.0.1 + +* Initial infrastructure for the gem diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb index cf1e84452..3f7590a67 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Persistence - VERSION = "0.0.1" + VERSION = "0.1.3" end end diff --git a/elasticsearch-rails/CHANGELOG.md b/elasticsearch-rails/CHANGELOG.md index 7c29ea663..b5c8251e5 100644 --- a/elasticsearch-rails/CHANGELOG.md +++ b/elasticsearch-rails/CHANGELOG.md @@ -1,4 +1,6 @@ -# 0.1.2 +## 0.1.3 + +## 0.1.2 * Allow passing an ActiveRecord scope to the importing Rake task diff --git a/elasticsearch-rails/lib/elasticsearch/rails/version.rb b/elasticsearch-rails/lib/elasticsearch/rails/version.rb index c717d0e67..24bda88b3 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/version.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Rails - VERSION = "0.1.2" + VERSION = "0.1.3" end end From f2ca55f8891152420be50a7684bf6a58b3bec633 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 3 Jun 2014 09:42:22 +0200 Subject: [PATCH 071/582] [MODEL] Added, that "raw" response from Elasticsearch is wrapped in Hashie::Mash for easier access response = Article.search query: { match: { title: { query: 'test' } } }, aggregations: { dates: { date_histogram: { field: 'created_at', interval: 'hour' } } } assert_equal 2, response.response.aggregations.dates.buckets.first.doc_count # => 2 --- .../lib/elasticsearch/model/response.rb | 4 +++- .../test/integration/active_record_basic_test.rb | 8 ++++++++ elasticsearch-model/test/unit/response_test.rb | 10 ++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response.rb b/elasticsearch-model/lib/elasticsearch/model/response.rb index 04e802eda..b62ba822c 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response.rb @@ -27,7 +27,9 @@ def initialize(klass, search, options={}) # @return [Hash] # def response - @response ||= search.execute! + @response ||= begin + Hashie::Mash.new(search.execute!) + end end # Returns the collection of "hits" from Elasticsearch diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index 7f9a1c9ed..45d41a6eb 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -164,6 +164,14 @@ class ::Article < ActiveRecord::Base assert_equal 'Testing Coding', response.records.order('title DESC').first.title end end + + should "allow dot access to response" do + response = Article.search query: { match: { title: { query: 'test' } } }, + aggregations: { dates: { date_histogram: { field: 'created_at', interval: 'hour' } } } + + response.response.respond_to?(:aggregations) + assert_equal 2, response.response.aggregations.dates.buckets.first.doc_count + end end end diff --git a/elasticsearch-model/test/unit/response_test.rb b/elasticsearch-model/test/unit/response_test.rb index 3f0a36c1a..ff14e5b22 100644 --- a/elasticsearch-model/test/unit/response_test.rb +++ b/elasticsearch-model/test/unit/response_test.rb @@ -25,6 +25,16 @@ def self.document_type; 'bar'; end assert_equal 'OK', response.shards.one end + should "wrap the raw Hash response in Hashie::Mash" do + @search = Elasticsearch::Model::Searching::SearchRequest.new OriginClass, '*' + @search.stubs(:execute!).returns({'hits' => { 'hits' => [] }, 'aggregations' => { 'dates' => 'FOO' }}) + + response = Elasticsearch::Model::Response::Response.new OriginClass, @search + + assert_respond_to response.response, :aggregations + assert_equal 'FOO', response.response.aggregations.dates + end + should "load and access the results" do @search.expects(:execute!).returns(RESPONSE) From 31510278e06d1c00d213f695640d9e4efdfaaab5 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Sun, 27 Apr 2014 22:51:10 +0200 Subject: [PATCH 072/582] [STORE] Added the first version of the ActiveRecord-based model persistence Using the `Elasticsearch::Persistence::Repository` from previous commits, this patch adds support for the ActiveRecord pattern of persistance for Ruby objects in Elasticsearch. The goal is to have a 1:1 implementation to the ActiveRecord::Base implementation, allowing to use it as a drop-in replacement for similar OxMs in Rails applications, with minimal changes to the model definition and application code. The model implementation uses [Virtus](https://github.com/solnic/virtus) for handling the model attributes, and [ActiveModel](https://github.com/rails/rails/tree/master/activemodel) for validations, callbacks, and similar model-related features. Example: -------- require 'elasticsearch/persistence/model' class Person include Elasticsearch::Persistence::Model settings index: { number_of_shards: 1 } attribute :name, String, mapping: { fields: { name: { type: 'string', analyzer: 'snowball' }, raw: { type: 'string', analyzer: 'keyword' } } } attribute :birthday, Date attribute :department, String attribute :salary, Integer attribute :admin, Boolean, default: false validates :name, presence: true before_save do puts "About to save: #{self}" end end Person.gateway.create_index! force: true person = Person.create name: 'John Smith', salary: 10_000 About to save: # => # person.id => "zNf3yxZDQsOTZfNfTX4E5A" person = Person.find(person.id) => # person.salary => 10000 person.increment :salary => { ... "_version"=>2} person.salary => 10001 person.update admin: true => { ... "_version"=>3} Person.search('smith').to_a => [#] --- .../elasticsearch-persistence.gemspec | 5 +- .../lib/elasticsearch/persistence/model.rb | 109 ++++++ .../elasticsearch/persistence/model/errors.rb | 8 + .../elasticsearch/persistence/model/store.rb | 95 +++++ .../integration/model/model_basic_test.rb | 101 +++++ .../test/unit/model_gateway_test.rb | 85 +++++ .../test/unit/model_rails_test.rb | 61 +++ .../test/unit/model_store_test.rb | 348 ++++++++++++++++++ 8 files changed, 811 insertions(+), 1 deletion(-) create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/model.rb create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/model/errors.rb create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb create mode 100644 elasticsearch-persistence/test/integration/model/model_basic_test.rb create mode 100644 elasticsearch-persistence/test/unit/model_gateway_test.rb create mode 100644 elasticsearch-persistence/test/unit/model_rails_test.rb create mode 100644 elasticsearch-persistence/test/unit/model_store_test.rb diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index b0b618a78..5c1c8f752 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -26,13 +26,16 @@ Gem::Specification.new do |s| s.add_dependency "elasticsearch", '> 0.4' s.add_dependency "elasticsearch-model", '>= 0.1' s.add_dependency "activesupport", '> 3' + s.add_dependency "activemodel", '> 3' s.add_dependency "hashie" + s.add_dependency "virtus" s.add_development_dependency "bundler", "~> 1.5" s.add_development_dependency "rake" s.add_development_dependency "oj" - s.add_development_dependency "virtus" + + s.add_development_dependency "rails" s.add_development_dependency "elasticsearch-extensions" diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb new file mode 100644 index 000000000..d6a78b12e --- /dev/null +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb @@ -0,0 +1,109 @@ +require 'active_support/core_ext/module/delegation' + +require 'active_model' +require 'virtus' + +require 'elasticsearch/persistence' +require 'elasticsearch/persistence/model/errors' +require 'elasticsearch/persistence/model/store' + +module Elasticsearch + module Persistence + + module Model + module Utils + def lookup_type(type) + case + when type == String + 'string' + when type == Integer + 'integer' + when type == Float + 'float' + when type == Date || type == Time || type == DateTime + 'date' + when type == Virtus::Attribute::Boolean + 'boolean' + end + end; module_function :lookup_type + end + + def self.included(base) + base.class_eval do + include ActiveModel::Naming + include ActiveModel::Conversion + include ActiveModel::Serialization + include ActiveModel::Serializers::JSON + include ActiveModel::Validations + + include Virtus.model + + extend ActiveModel::Callbacks + define_model_callbacks :create, :save, :update, :destroy + define_model_callbacks :find, :touch, only: :after + + extend Elasticsearch::Persistence::Model::Store::ClassMethods + include Elasticsearch::Persistence::Model::Store::InstanceMethods + + class << self + def attribute(name, type=nil, options={}, &block) + mapping = options.delete(:mapping) || {} + super + + gateway.mapping do + indexes name, {type: Utils::lookup_type(type)}.merge(mapping) + end + + gateway.mapping(&block) if block_given? + end + + def gateway(&block) + @gateway ||= Elasticsearch::Persistence::Repository::Class.new host: self + block.arity < 1 ? @gateway.instance_eval(&block) : block.call(@gateway) if block_given? + @gateway + end + + delegate :settings, + :mappings, + :mapping, + :document_type, + :document_type=, + :index_name, + :index_name=, + :search, + :find, + :exists?, + to: :gateway + end + + gateway do + klass base + index_name base.model_name.collection.gsub(/\//, '-') + document_type base.model_name.element + + def serialize(document) + document.to_hash.except(:id, 'id') + end + + def deserialize(document) + object = klass.new document['_source'] + object.set_id document['_id'] + object.instance_variable_set(:@persisted, true) + object + end + end + + attribute :id, String, writer: :private + attribute :created_at, DateTime, default: lambda { |o,a| Time.now.utc } + attribute :updated_at, DateTime, default: lambda { |o,a| Time.now.utc } + + def set_id(id) + self.id = id + end + end + + end + end + + end +end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/errors.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/errors.rb new file mode 100644 index 000000000..3d8ebb88e --- /dev/null +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/errors.rb @@ -0,0 +1,8 @@ +module Elasticsearch + module Persistence + module Model + class DocumentNotSaved < StandardError; end + class DocumentNotPersisted < StandardError; end + end + end +end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb new file mode 100644 index 000000000..451671f87 --- /dev/null +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb @@ -0,0 +1,95 @@ +module Elasticsearch + module Persistence + module Model + + module Store + module ClassMethods + def create(attributes, options={}) + object = self.new(attributes) + object.run_callbacks :create do + object.save(options) + object + end + end + end + + module InstanceMethods + def save(options={}) + return false unless valid? + run_callbacks :save do + response = self.class.gateway.save(self, options.merge(id: self.id)) + self[:updated_at] = Time.now.utc + @persisted = true + set_id(response['_id']) if respond_to?(:set_id) + response + end + end + + def destroy(options={}) + raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? + + run_callbacks :destroy do + response = self.class.gateway.delete(self.id, options) + @destroyed = true + @persisted = false + self.freeze + response + end + end; alias :delete :destroy + + def update(attributes={}, options={}) + raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? + + run_callbacks :update do + attributes.update( { updated_at: Time.now.utc } ) + response = self.class.gateway.update(self.id, { doc: attributes}.merge(options)) + self.attributes = self.attributes.merge(attributes) + response + end + end; alias :update_attributes :update + + def increment(attribute, value=1, options={}) + raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? + + response = self.class.gateway.update(self.id, { script: "ctx._source.#{attribute} += #{value}"}.merge(options)) + self[attribute] += value + response + end + + def decrement(attribute, value=1, options={}) + raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? + + response = self.class.gateway.update(self.id, { script: "ctx._source.#{attribute} = ctx._source.#{attribute} - #{value}"}.merge(options)) + self[attribute] -= value + response + end + + def touch(attribute=:updated_at, options={}) + raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? + raise ArgumentError, "Object does not have '#{attribute}' attribute" unless respond_to?(attribute) + + run_callbacks :touch do + value = Time.now.utc + response = self.class.gateway.update(self.id, { doc: { attribute => value.iso8601 }}.merge(options)) + self[attribute] = value + response + end + end + + def destroyed? + !!@destroyed + end + + def persisted? + !!@persisted && !destroyed? + end + + def new_record? + !persisted? && !destroyed? + end + end + end + + end + end +end diff --git a/elasticsearch-persistence/test/integration/model/model_basic_test.rb b/elasticsearch-persistence/test/integration/model/model_basic_test.rb new file mode 100644 index 000000000..e67c0b43d --- /dev/null +++ b/elasticsearch-persistence/test/integration/model/model_basic_test.rb @@ -0,0 +1,101 @@ +require 'test_helper' + +require 'elasticsearch/persistence/model' + +module Elasticsearch + module Persistence + class PersistenceModelBasicIntegrationTest < Elasticsearch::Test::IntegrationTestCase + + class ::Person + include Elasticsearch::Persistence::Model + + settings index: { number_of_shards: 1 } + + attribute :name, String, + mapping: { fields: { + name: { type: 'string', analyzer: 'snowball' }, + raw: { type: 'string', analyzer: 'keyword' } + } } + + attribute :birthday, Date + attribute :department, String + attribute :salary, Integer + attribute :admin, Boolean, default: false + + validates :name, presence: true + end + + context "A basic persistence model" do + should "save and find the object" do + person = Person.new name: 'John Smith', birthday: Date.parse('1970-01-01') + person.save + + assert_not_nil person.id + document = Person.find(person.id) + + assert_instance_of Person, document + assert_equal 'John Smith', document.name + assert_equal 'John Smith', Person.find(person.id).name + + assert_not_nil Elasticsearch::Persistence.client.get index: 'people', type: 'person', id: person.id + end + + should "delete the object" do + person = Person.create name: 'John Smith', birthday: Date.parse('1970-01-01') + + person.destroy + assert person.frozen? + + assert_raise Elasticsearch::Transport::Transport::Errors::NotFound do + Elasticsearch::Persistence.client.get index: 'people', type: 'person', id: person.id + end + end + + should "update an object attribute" do + person = Person.create name: 'John Smith' + + person.update name: 'UPDATED' + + assert_equal 'UPDATED', person.name + assert_equal 'UPDATED', Person.find(person.id).name + end + + should "increment an object attribute" do + person = Person.create name: 'John Smith', salary: 1_000 + + person.increment :salary + + assert_equal 1_001, person.salary + assert_equal 1_001, Person.find(person.id).salary + end + + should "update the object timestamp" do + person = Person.create name: 'John Smith' + updated_at = person.updated_at + + sleep 1 + person.touch + + assert person.updated_at > updated_at, [person.updated_at, updated_at].inspect + + found = Person.find(person.id) + assert found.updated_at > updated_at, [found.updated_at, updated_at].inspect + end + + should "find instances by search" do + Person.create name: 'John Smith' + Person.create name: 'Mary Smith' + Person.gateway.refresh_index! + + people = Person.search query: { match: { name: 'smith' } } + + assert_equal 2, people.total + assert_equal 2, people.size + + assert people.map_with_hit { |o,h| h._score }.all? { |s| s > 0 } + end + end + + end + end +end diff --git a/elasticsearch-persistence/test/unit/model_gateway_test.rb b/elasticsearch-persistence/test/unit/model_gateway_test.rb new file mode 100644 index 000000000..1915e9135 --- /dev/null +++ b/elasticsearch-persistence/test/unit/model_gateway_test.rb @@ -0,0 +1,85 @@ +require 'test_helper' + +require 'elasticsearch/persistence/model' + +class Elasticsearch::Persistence::ModelGatewayTest < Test::Unit::TestCase + context "The model gateway" do + setup do + class DummyGatewayModel + include Elasticsearch::Persistence::Model + end + end + + teardown do + Elasticsearch::Persistence::ModelGatewayTest.__send__ :remove_const, :DummyGatewayModel \ + rescue NameError; nil + end + + should "be accessible" do + assert_instance_of Elasticsearch::Persistence::Repository::Class, DummyGatewayModel.gateway + + $a = 0 + DummyGatewayModel.gateway { $a += 1 } + assert_equal 1, $a + + @b = 0 + def run!; DummyGatewayModel.gateway { |g| @b += 1 }; end + run! + assert_equal 1, @b + + assert_equal DummyGatewayModel, DummyGatewayModel.gateway.klass + end + + should "define common attributes" do + d = DummyGatewayModel.new + + assert_respond_to d, :id + assert_respond_to d, :updated_at + assert_respond_to d, :created_at + end + + should "allow to configure settings" do + DummyGatewayModel.settings(number_of_shards: 1) + + assert_equal 1, DummyGatewayModel.settings.to_hash[:number_of_shards] + end + + should "allow to configure mappings" do + DummyGatewayModel.mapping { indexes :name, analyzer: 'snowball' } + + assert_equal 'snowball', + DummyGatewayModel.mapping.to_hash[:dummy_gateway_model][:properties][:name][:analyzer] + end + + should "configure the mapping via attribute" do + DummyGatewayModel.attribute :name, String, mapping: { analyzer: 'snowball' } + + assert_respond_to DummyGatewayModel, :name + assert_equal 'snowball', + DummyGatewayModel.mapping.to_hash[:dummy_gateway_model][:properties][:name][:analyzer] + end + + should "configure the mapping via an attribute block" do + DummyGatewayModel.attribute :name, String do + indexes :name, analyzer: 'custom' + end + + assert_respond_to DummyGatewayModel, :name + assert_equal 'custom', + DummyGatewayModel.mapping.to_hash[:dummy_gateway_model][:properties][:name][:analyzer] + end + + should "remove IDs from hash when serializing" do + assert_equal( {foo: 'bar'}, DummyGatewayModel.gateway.serialize(id: '123', foo: 'bar') ) + end + + should "set IDs from hash when deserializing" do + assert_equal 'abc123', DummyGatewayModel.gateway.deserialize('_id' => 'abc123').id + end + + should "set @persisted variable from hash when deserializing" do + assert DummyGatewayModel.gateway.deserialize('_id' => 'abc123').instance_variable_get(:@persisted) + end + + end +end diff --git a/elasticsearch-persistence/test/unit/model_rails_test.rb b/elasticsearch-persistence/test/unit/model_rails_test.rb new file mode 100644 index 000000000..f262b57d2 --- /dev/null +++ b/elasticsearch-persistence/test/unit/model_rails_test.rb @@ -0,0 +1,61 @@ +require 'test_helper' + +require 'elasticsearch/persistence/model' + +require 'rails' +require 'action_controller/railtie' +require 'action_view/railtie' + +class ::MyRailsModel + include Elasticsearch::Persistence::Model + attribute :name, String, mapping: { analyzer: 'string' } +end + +class Application < Rails::Application + config.eager_load = false + config.root = File.dirname(File.expand_path('../../../tmp', __FILE__)) + config.logger = Logger.new($stderr) + + routes.append do + resources :my_rails_models + end +end + +class ApplicationController < ActionController::Base + include Application.routes.url_helpers + include ActionController::UrlFor +end +ApplicationController.default_url_options = { host: 'localhost' } +ApplicationController._routes.append { resources :my_rails_models } + +class MyRailsModelController < ApplicationController; end + +Application.initialize! + +class Elasticsearch::Persistence::ModelRailsTest < Test::Unit::TestCase + context "The model in a Rails application" do + + should "generate proper URLs and paths" do + model = MyRailsModel.new name: 'Test' + model.stubs(:id).returns(1) + model.stubs(:persisted?).returns(true) + + controller = MyRailsModelController.new + controller.request = ActionDispatch::Request.new({}) + + assert_equal 'http://localhost/my_rails_models/1', controller.url_for(model) + assert_equal '/my_rails_models/1/edit', controller.edit_my_rails_model_path(model) + end + + should "generate a link" do + class MyView; include ActionView::Helpers::UrlHelper; end + + model = MyRailsModel.new name: 'Test' + view = MyView.new + view.expects(:url_for).with(model).returns('foo/bar') + + assert_equal 'Show', view.link_to('Show', model) + end + + end +end diff --git a/elasticsearch-persistence/test/unit/model_store_test.rb b/elasticsearch-persistence/test/unit/model_store_test.rb new file mode 100644 index 000000000..2ed8af091 --- /dev/null +++ b/elasticsearch-persistence/test/unit/model_store_test.rb @@ -0,0 +1,348 @@ +require 'test_helper' + +require 'active_model' +require 'virtus' + +require 'elasticsearch/persistence/model/errors' +require 'elasticsearch/persistence/model/store' + +class Elasticsearch::Persistence::ModelStoreTest < Test::Unit::TestCase + context "The model store module," do + + class DummyStoreModel + include ActiveModel::Naming + include ActiveModel::Conversion + include ActiveModel::Serialization + include ActiveModel::Serializers::JSON + include ActiveModel::Validations + + include Virtus.model + + extend Elasticsearch::Persistence::Model::Store::ClassMethods + include Elasticsearch::Persistence::Model::Store::InstanceMethods + + extend ActiveModel::Callbacks + define_model_callbacks :create, :save, :update, :destroy + define_model_callbacks :find, :touch, only: :after + + attribute :id, String, writer: :private + attribute :title, String + attribute :count, Integer, default: 0 + attribute :created_at, DateTime, default: lambda { |o,a| Time.now.utc } + attribute :updated_at, DateTime, default: lambda { |o,a| Time.now.utc } + + def set_id(id); self.id = id; end + end + + setup do + @shoulda_subject = DummyStoreModel.new title: 'Test' + @gateway = stub + DummyStoreModel.stubs(:gateway).returns(@gateway) + end + + teardown do + Elasticsearch::Persistence::ModelStoreTest.__send__ :remove_const, :DummyStoreModelWithCallback \ + rescue NameError; nil + end + + should "be new_record" do + assert subject.new_record? + end + + context "when creating," do + should "save the object and return it" do + DummyStoreModel.any_instance.expects(:save).returns({'_id' => 'X'}) + + assert_instance_of DummyStoreModel, DummyStoreModel.create(title: 'Test') + end + + should "execute the callbacks" do + DummyStoreModelWithCallback = Class.new(DummyStoreModel) + @gateway.expects(:save).returns({'_id' => 'X'}) + + DummyStoreModelWithCallback.after_create { $stderr.puts "CREATED" } + DummyStoreModelWithCallback.after_save { $stderr.puts "SAVED" } + + $stderr.expects(:puts).with('CREATED') + $stderr.expects(:puts).with('SAVED') + + DummyStoreModelWithCallback.create name: 'test' + end + end + + context "when saving," do + should "save the model" do + @gateway + .expects(:save) + .with do |object, options| + assert_equal subject, object + assert_equal nil, options[:id] + end + .returns({'_id' => 'abc123'}) + + assert ! subject.persisted? + + assert subject.save + assert subject.persisted? + end + + should "save the model and set the ID" do + @gateway + .expects(:save) + .returns({'_id' => 'abc123'}) + + assert_nil subject.id + + subject.save + assert_equal 'abc123', subject.id + end + + should "save the model and update the timestamp" do + Time.expects(:now).returns(Time.parse('2014-01-01T00:00:00Z')).at_least_once + @gateway + .expects(:save) + .returns({'_id' => 'abc123'}) + + subject.save + assert_equal Time.parse('2014-01-01T00:00:00Z'), subject.updated_at + end + + should "pass the options to gateway" do + @gateway + .expects(:save) + .with do |object, options| + assert_equal 'ABC', options[:routing] + end + .returns({'_id' => 'abc123'}) + + assert subject.save routing: 'ABC' + end + + should "return the response" do + @gateway + .expects(:save) + .returns('FOOBAR') + + assert_equal 'FOOBAR', subject.save + end + + should "execute the callbacks" do + @gateway.expects(:save).returns({'_id' => 'abc'}) + DummyStoreModelWithCallback = Class.new(DummyStoreModel) + + DummyStoreModelWithCallback.after_save { $stderr.puts "SAVED" } + + $stderr.expects(:puts).with('SAVED') + d = DummyStoreModelWithCallback.new name: 'Test' + d.save + end + end + + context "when destroying," do + should "remove the model from Elasticsearch" do + subject.expects(:persisted?).returns(true) + subject.expects(:id).returns('abc123') + subject.expects(:freeze).returns(subject) + + @gateway + .expects(:delete) + .with('abc123', {}) + .returns({'_id' => 'abc123', 'version' => 2}) + + assert subject.destroy + assert subject.destroyed? + end + + should "pass the options to gateway" do + subject.expects(:persisted?).returns(true) + subject.expects(:freeze).returns(subject) + + @gateway + .expects(:delete) + .with do |object, options| + assert_equal 'ABC', options[:routing] + end + .returns({'_id' => 'abc123'}) + + assert subject.destroy routing: 'ABC' + end + + should "return the response" do + subject.expects(:persisted?).returns(true) + subject.expects(:freeze).returns(subject) + + @gateway + .expects(:delete) + .returns('FOOBAR') + + assert_equal 'FOOBAR', subject.destroy + end + + should "execute the callbacks" do + @gateway.expects(:delete).returns({'_id' => 'abc'}) + DummyStoreModelWithCallback = Class.new(DummyStoreModel) + + DummyStoreModelWithCallback.after_destroy { $stderr.puts "DELETED" } + + $stderr.expects(:puts).with('DELETED') + d = DummyStoreModelWithCallback.new name: 'Test' + d.expects(:persisted?).returns(true) + d.expects(:freeze).returns(d) + d.destroy + end + end + + context "when updating," do + should "update the document with partial attributes" do + subject.expects(:persisted?).returns(true) + subject.expects(:id).returns('abc123').at_least_once + + @gateway + .expects(:update) + .with do |id, options| + assert_equal 'abc123', id + assert_equal 'UPDATED', options[:doc][:title] + end + .returns({'_id' => 'abc123', 'version' => 2}) + + assert subject.update title: 'UPDATED' + + assert_equal 'UPDATED', subject.title + end + + should "allow to update the document with a custom script" do + subject.expects(:persisted?).returns(true) + subject.expects(:id).returns('abc123').at_least_once + + @gateway + .expects(:update) + .with do |id, options| + assert_equal 'abc123', id + assert_equal 'EXEC', options[:script] + end + .returns({'_id' => 'abc123', 'version' => 2}) + + assert subject.update( {}, { script: 'EXEC' } ) + end + + should "pass the options to gateway" do + subject.expects(:persisted?).returns(true) + + @gateway + .expects(:update) + .with do |object, options| + assert_equal 'ABC', options[:routing] + end + .returns({'_id' => 'abc123'}) + + assert subject.update( { title: 'UPDATED' }, { routing: 'ABC' } ) + end + + should "return the response" do + subject.expects(:persisted?).returns(true) + + @gateway + .expects(:update) + .returns('FOOBAR') + + assert_equal 'FOOBAR', subject.update + end + + should "execute the callbacks" do + @gateway.expects(:update).returns({'_id' => 'abc'}) + DummyStoreModelWithCallback = Class.new(DummyStoreModel) + + DummyStoreModelWithCallback.after_update { $stderr.puts "UPDATED" } + + $stderr.expects(:puts).with('UPDATED') + d = DummyStoreModelWithCallback.new name: 'Test' + d.expects(:persisted?).returns(true) + d.update name: 'Update' + end + end + + context "when incrementing," do + should "increment the attribute" do + subject.expects(:persisted?).returns(true) + + @gateway + .expects(:update) + .with do |id, options| + assert_equal 'ctx._source.count += 1', options[:script] + end + .returns({'_id' => 'abc123', 'version' => 2}) + + assert subject.increment :count + + assert_equal 1, subject.count + end + end + + context "when decrement," do + should "decrement the attribute" do + subject.expects(:persisted?).returns(true) + + @gateway + .expects(:update) + .with do |id, options| + assert_equal 'ctx._source.count = ctx._source.count - 1', options[:script] + end + .returns({'_id' => 'abc123', 'version' => 2}) + + assert subject.decrement :count + + assert_equal -1, subject.count + end + end + + context "when touching," do + should "raise exception when touching not existing attribute" do + subject.expects(:persisted?).returns(true) + assert_raise(ArgumentError) { subject.touch :foobar } + end + + should "update updated_at by default" do + subject.expects(:persisted?).returns(true) + Time.expects(:now).returns(Time.parse('2014-01-01T00:00:00Z')).at_least_once + + @gateway + .expects(:update) + .with do |id, options| + assert_equal '2014-01-01T00:00:00Z', options[:doc][:updated_at] + end + .returns({'_id' => 'abc123', 'version' => 2}) + + subject.touch + assert_equal Time.parse('2014-01-01T00:00:00Z'), subject.updated_at + end + + should "update a custom attribute by default" do + subject.expects(:persisted?).returns(true) + Time.expects(:now).returns(Time.parse('2014-01-01T00:00:00Z')).at_least_once + + @gateway + .expects(:update) + .with do |id, options| + assert_equal '2014-01-01T00:00:00Z', options[:doc][:created_at] + end + .returns({'_id' => 'abc123', 'version' => 2}) + + subject.touch :created_at + assert_equal Time.parse('2014-01-01T00:00:00Z'), subject.created_at + end + + should "execute the callbacks" do + @gateway.expects(:update).returns({'_id' => 'abc'}) + DummyStoreModelWithCallback = Class.new(DummyStoreModel) + + DummyStoreModelWithCallback.after_touch { $stderr.puts "TOUCHED" } + + $stderr.expects(:puts).with('TOUCHED') + d = DummyStoreModelWithCallback.new name: 'Test' + d.expects(:persisted?).returns(true) + d.touch + end + end + + end +end From ed0a5528e0bae04217e65d4b344d72e16cd85663 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 28 Apr 2014 19:02:23 +0200 Subject: [PATCH 073/582] [STORE] Added a rudimentary support for extracting date/time from Rails forms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Started POST "/articles" for 127.0.0.1 at 2014-04-28 19:03:35 +0200 Processing by ArticlesController#create as HTML Parameters: {"utf8"=>"✓", "authenticity_token"=>"RS4ZqcdL8SPbo0g9kNzPG24D+PpspIit4SyOXcLhYXk=", "article"=>{"title"=>"With Date", "content"=>"", "published_on(1i)"=>"2014", "published_on(2i)"=>"4", "published_on(3i)"=>"1", "published_on(4i)"=>"15", "published_on(5i)"=>"00"}, "commit"=>"Create Article"} POST http://localhost:9250/articles/article [status:201, request:0.155s, query:n/a] > {"created_at":"2014-04-28T17:03:35.190+00:00","updated_at":"2014-04-28T17:03:35.190+00:00","title":"With Date","content":"","published_on":"2014-04-01T15:00:00.000+00:00"} This has to be refactored into a Realtie --- .../elasticsearch/persistence/model/rails.rb | 22 +++++++++++++++++++ .../test/unit/model_gateway_test.rb | 5 +++-- .../test/unit/model_rails_test.rb | 16 ++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb new file mode 100644 index 000000000..469d4fb9e --- /dev/null +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb @@ -0,0 +1,22 @@ +module Elasticsearch + module Persistence + module Model + + module Rails + def self.included(base) + base.class_eval do + def initialize(attributes={}) + day = attributes.select { |p| p =~ /\([1-3]/ }.reduce({}) { |sum, item| (sum[item.first.gsub(/\(.+\)/, '')] ||= '' )<< item.last+'-'; sum } + time = attributes.select { |p| p =~ /\([4-6]/ }.reduce({}) { |sum, item| (sum[item.first.gsub(/\(.+\)/, '')] ||= '' )<< item.last+':'; sum } + unless day.empty? && time.empty? + attributes.update day.reduce({}) { |sum, item| sum[item.first] = item.last + ' ' + time[item.first]; sum } + end + super(attributes) + end + end + end + end + + end + end +end diff --git a/elasticsearch-persistence/test/unit/model_gateway_test.rb b/elasticsearch-persistence/test/unit/model_gateway_test.rb index 1915e9135..011a7afb8 100644 --- a/elasticsearch-persistence/test/unit/model_gateway_test.rb +++ b/elasticsearch-persistence/test/unit/model_gateway_test.rb @@ -1,6 +1,7 @@ require 'test_helper' require 'elasticsearch/persistence/model' +require 'elasticsearch/persistence/model/rails' class Elasticsearch::Persistence::ModelGatewayTest < Test::Unit::TestCase context "The model gateway" do @@ -74,11 +75,11 @@ def run!; DummyGatewayModel.gateway { |g| @b += 1 }; end end should "set IDs from hash when deserializing" do - assert_equal 'abc123', DummyGatewayModel.gateway.deserialize('_id' => 'abc123').id + assert_equal 'abc123', DummyGatewayModel.gateway.deserialize('_id' => 'abc123', '_source' => {}).id end should "set @persisted variable from hash when deserializing" do - assert DummyGatewayModel.gateway.deserialize('_id' => 'abc123').instance_variable_get(:@persisted) + assert DummyGatewayModel.gateway.deserialize('_id' => 'abc123', '_source' => {}).instance_variable_get(:@persisted) end end diff --git a/elasticsearch-persistence/test/unit/model_rails_test.rb b/elasticsearch-persistence/test/unit/model_rails_test.rb index f262b57d2..260f21f8e 100644 --- a/elasticsearch-persistence/test/unit/model_rails_test.rb +++ b/elasticsearch-persistence/test/unit/model_rails_test.rb @@ -1,6 +1,7 @@ require 'test_helper' require 'elasticsearch/persistence/model' +require 'elasticsearch/persistence/model/rails' require 'rails' require 'action_controller/railtie' @@ -8,7 +9,10 @@ class ::MyRailsModel include Elasticsearch::Persistence::Model + include Elasticsearch::Persistence::Model::Rails + attribute :name, String, mapping: { analyzer: 'string' } + attribute :published_on, DateTime end class Application < Rails::Application @@ -57,5 +61,17 @@ class MyView; include ActionView::Helpers::UrlHelper; end assert_equal 'Show', view.link_to('Show', model) end + should "parse DateTime from Rails forms" do + params = { "published_on(1i)"=>"2014", + "published_on(2i)"=>"1", + "published_on(3i)"=>"1", + "published_on(4i)"=>"12", + "published_on(5i)"=>"00" + } + + m = MyRailsModel.new params + assert_equal "2014-01-01T12:00:00+00:00", m.published_on.iso8601 + end + end end From ff9e664077eaff34103346b531b9bf7f642b0d7b Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 5 May 2014 15:01:42 +0200 Subject: [PATCH 074/582] [STORE] Added `MyModel.all` finder method Person.all.to_a # 2014-05-05 15:02:24 +0200: GET http://localhost:9250/people/person/_search [status:200, request:0.047s, query:0.024s] # 2014-05-05 15:02:24 +0200: > {"query":{"match_all":{}},"size":10000} # 2014-05-05 15:02:24 +0200: < # {"took":24,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":100,"max_score":1.0,"hits":[ .... ]}} # => [# [# [# Date: Tue, 27 May 2014 21:25:40 +0200 Subject: [PATCH 075/582] [STORE] Added missing test for Elasticsearch::Persistence::Model::Utils.lookup_type --- elasticsearch-persistence/test/unit/model_gateway_test.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/elasticsearch-persistence/test/unit/model_gateway_test.rb b/elasticsearch-persistence/test/unit/model_gateway_test.rb index 011a7afb8..d7bdb9086 100644 --- a/elasticsearch-persistence/test/unit/model_gateway_test.rb +++ b/elasticsearch-persistence/test/unit/model_gateway_test.rb @@ -70,6 +70,14 @@ def run!; DummyGatewayModel.gateway { |g| @b += 1 }; end DummyGatewayModel.mapping.to_hash[:dummy_gateway_model][:properties][:name][:analyzer] end + should "properly look up types for classes" do + assert_equal 'string', Elasticsearch::Persistence::Model::Utils::lookup_type(String) + assert_equal 'integer', Elasticsearch::Persistence::Model::Utils::lookup_type(Integer) + assert_equal 'float', Elasticsearch::Persistence::Model::Utils::lookup_type(Float) + assert_equal 'date', Elasticsearch::Persistence::Model::Utils::lookup_type(Date) + assert_equal 'boolean', Elasticsearch::Persistence::Model::Utils::lookup_type(Virtus::Attribute::Boolean) + end + should "remove IDs from hash when serializing" do assert_equal( {foo: 'bar'}, DummyGatewayModel.gateway.serialize(id: '123', foo: 'bar') ) end From 2b74c05304cc22a2e03d6f3657d249d917952db8 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 27 May 2014 21:26:05 +0200 Subject: [PATCH 076/582] [STORE] Added code annotations across the Persistence::Model codebase --- .../lib/elasticsearch/persistence/model.rb | 27 ++++++ .../elasticsearch/persistence/model/rails.rb | 17 ++++ .../elasticsearch/persistence/model/store.rb | 97 ++++++++++++++++++- .../elasticsearch/persistence/repository.rb | 4 +- 4 files changed, 143 insertions(+), 2 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb index ab7c13024..9a38cfdcf 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb @@ -11,8 +11,24 @@ module Elasticsearch module Persistence + # When included, extends a plain Ruby class with persistence-related features via the ActiveRecord pattern + # + # @example Include the repository in a custom class + # + # require 'elasticsearch/persistence/model' + # + # class MyObject + # include Elasticsearch::Persistence::Repository + # end + # module Model + + # Utility methods for {Elasticsearch::Persistence::Model} + # module Utils + + # Return Elasticsearch type based on passed Ruby class (used in the `attribute` method) + # def lookup_type(type) case when type == String @@ -49,6 +65,9 @@ def self.included(base) extend Elasticsearch::Persistence::Model::Find::ClassMethods class << self + + # Re-define the Virtus' `attribute` method, to configure Elasticsearch mapping as well + # def attribute(name, type=nil, options={}, &block) mapping = options.delete(:mapping) || {} super @@ -60,12 +79,16 @@ def attribute(name, type=nil, options={}, &block) gateway.mapping(&block) if block_given? end + # Return the {Repository::Class} instance + # def gateway(&block) @gateway ||= Elasticsearch::Persistence::Repository::Class.new host: self block.arity < 1 ? @gateway.instance_eval(&block) : block.call(@gateway) if block_given? @gateway end + # Delegate methods to repository + # delegate :settings, :mappings, :mapping, @@ -79,6 +102,8 @@ def gateway(&block) to: :gateway end + # Configure the repository based on the model (set up index_name, etc) + # gateway do klass base index_name base.model_name.collection.gsub(/\//, '-') @@ -96,6 +121,8 @@ def deserialize(document) end end + # Set up common attributes + # attribute :id, String, writer: :private attribute :created_at, DateTime, default: lambda { |o,a| Time.now.utc } attribute :updated_at, DateTime, default: lambda { |o,a| Time.now.utc } diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb index 469d4fb9e..46f0e3074 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb @@ -2,15 +2,32 @@ module Elasticsearch module Persistence module Model + # Make the `Persistence::Model` models compatible with Ruby On Rails applications + # module Rails def self.included(base) base.class_eval do + + # Decorates the passed in `attributes` so they extract the date & time values from Rails forms + # + # @example Correctly combine the date and time to a datetime string + # + # params = { "published_on(1i)"=>"2014", + # "published_on(2i)"=>"1", + # "published_on(3i)"=>"1", + # "published_on(4i)"=>"12", + # "published_on(5i)"=>"00" + # } + # MyRailsModel.new(params).published_on.iso8601 + # # => "2014-01-01T12:00:00+00:00" + # def initialize(attributes={}) day = attributes.select { |p| p =~ /\([1-3]/ }.reduce({}) { |sum, item| (sum[item.first.gsub(/\(.+\)/, '')] ||= '' )<< item.last+'-'; sum } time = attributes.select { |p| p =~ /\([4-6]/ }.reduce({}) { |sum, item| (sum[item.first.gsub(/\(.+\)/, '')] ||= '' )<< item.last+':'; sum } unless day.empty? && time.empty? attributes.update day.reduce({}) { |sum, item| sum[item.first] = item.last + ' ' + time[item.first]; sum } end + super(attributes) end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb index 451671f87..85f1abb2c 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb @@ -2,8 +2,20 @@ module Elasticsearch module Persistence module Model + # This module contains the storage related features of {Elasticsearch::Persistence::Model} + # module Store - module ClassMethods + module ClassMethods #:nodoc: + + # Creates a class instance, saves it, if validations pass, and returns it + # + # @example Create a new person + # + # Person.create name: 'John Smith' + # # => # + # + # @return [Object] The model instance + # def create(attributes, options={}) object = self.new(attributes) object.run_callbacks :create do @@ -14,6 +26,23 @@ def create(attributes, options={}) end module InstanceMethods + + # Saves the model (if validations pass) and returns the response (or `false`) + # + # @example Save a valid model instance + # + # p = Person.new(name: 'John') + # p.save + # => {"_index"=>"people", ... "_id"=>"RzFSXFR0R8u1CZIWNs2Gvg", "_version"=>1, "created"=>true} + # + # @example Save an invalid model instance + # + # p = Person.new(name: nil) + # p.save + # # => false + # + # @return [Hash,FalseClass] The Elasticsearch response as a Hash or `false` + # def save(options={}) return false unless valid? run_callbacks :save do @@ -25,6 +54,15 @@ def save(options={}) end end + # Deletes the model from Elasticsearch (if it's persisted), freezes it, and returns the response + # + # @example Delete a model instance + # + # p.destroy + # => {"_index"=>"people", ... "_id"=>"RzFSXFR0R8u1CZIWNs2Gvg", "_version"=>2 ...} + # + # @return [Hash] The Elasticsearch response as a Hash + # def destroy(options={}) raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? @@ -37,6 +75,15 @@ def destroy(options={}) end end; alias :delete :destroy + # Updates the model (via Elasticsearch's "Update" API) and returns the response + # + # @example Update a model with partial attributes + # + # p.update name: 'UPDATED' + # => {"_index"=>"people", ... "_version"=>2} + # + # @return [Hash] The Elasticsearch response as a Hash + # def update(attributes={}, options={}) raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? @@ -48,6 +95,18 @@ def update(attributes={}, options={}) end end; alias :update_attributes :update + # Increments a numeric attribute (via Elasticsearch's "Update" API) and returns the response + # + # @example Increment the `salary` attribute by 1 + # + # p.increment :salary + # + # @example Increment the `salary` attribute by 100 + # + # p.increment :salary, 100 + # + # @return [Hash] The Elasticsearch response as a Hash + # def increment(attribute, value=1, options={}) raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? @@ -56,6 +115,18 @@ def increment(attribute, value=1, options={}) response end + # Decrements a numeric attribute (via Elasticsearch's "Update" API) and returns the response + # + # @example Decrement the `salary` attribute by 1 + # + # p.decrement :salary + # + # @example Decrement the `salary` attribute by 100 + # + # p.decrement :salary, 100 + # + # @return [Hash] The Elasticsearch response as a Hash + # def decrement(attribute, value=1, options={}) raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? @@ -64,6 +135,18 @@ def decrement(attribute, value=1, options={}) response end + # Updates the `updated_at` attribute, saves the model and returns the response + # + # @example Update the `updated_at` attribute (default) + # + # p.touch + # + # @example Update a custom attribute: `saved_on` + # + # p.touch :saved_on + # + # @return [Hash] The Elasticsearch response as a Hash + # def touch(attribute=:updated_at, options={}) raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? raise ArgumentError, "Object does not have '#{attribute}' attribute" unless respond_to?(attribute) @@ -76,14 +159,26 @@ def touch(attribute=:updated_at, options={}) end end + # Returns true when the model has been destroyed, false otherwise + # + # @return [TrueClass,FalseClass] + # def destroyed? !!@destroyed end + # Returns true when the model has been already saved to the database, false otherwise + # + # @return [TrueClass,FalseClass] + # def persisted? !!@persisted && !destroyed? end + # Returns true when the model has not been saved yet, false otherwise + # + # @return [TrueClass,FalseClass] + # def new_record? !persisted? && !destroyed? end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index e6e40769d..f6a202029 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -17,10 +17,12 @@ def respond_to_missing?(method_name, *) end end - # When included, creates an instance of the {Repository::Class} class as a "gateway" + # When included, creates an instance of the {Repository::Class Repository} class as a "gateway" # # @example Include the repository in a custom class # + # require 'elasticsearch/persistence' + # # class MyRepository # include Elasticsearch::Persistence::Repository # end From cc1c9475921d6289e9fe9071ef3d420b660bceee Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 27 May 2014 22:49:15 +0200 Subject: [PATCH 077/582] [STORE] Added the `find_in_batches` method for models Person.find_in_batches { |batch| puts batch.map(&:name) } See: http://api.rubyonrails.org/classes/ActiveRecord/Batches.html#method-i-find_in_batches --- .../elasticsearch/persistence/model/find.rb | 77 ++++++++++++++++++- .../integration/model/model_basic_test.rb | 18 +++++ .../test/unit/model_find_test.rb | 32 +++++++- 3 files changed, 125 insertions(+), 2 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb index 8a3527f39..9c794bc6c 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb @@ -5,7 +5,7 @@ module Model module Find module ClassMethods - # Return all documents (up to 10,000) for a model + # Returns all models (up to 10,000) # # @example Retrieve all people # @@ -20,6 +20,81 @@ module ClassMethods def all(options={}) gateway.search( { query: { match_all: {} }, size: 10_000 }.merge(options) ) end + + # Returns all models efficiently via the Elasticsearch's scan/scroll API + # + # You can restrict the models being returned with a query. + # + # The full {Persistence::Repository::Response::Result} instance is yielded to the passed + # block in each batch, so you can access any of its properties. Calling `to_a` will + # convert the object to an Array of model instances. + # + # @example Return all models in batches of 20 x number of primary shards + # + # Person.find_in_batches { |batch| puts batch.map(&:name) } + # + # @example Return all models in batches of 100 x number of primary shards + # + # Person.find_in_batches(size: 100) { |batch| puts batch.map(&:name) } + # + # @example Return all models matching a specific query + # + # Person.find_in_batches(query: { match: { name: 'test' } }) { |batch| puts batch.map(&:name) } + # + # @example Return all models, fetching only the `name` attribute from Elasticsearch + # + # Person.find_in_batches( _source_include: 'name') { |_| puts _.response.hits.hits.map(&:to_hash) } + # + # @return [String] The `scroll_id` for the request + # + def find_in_batches(options={}, &block) + search_params = options.extract!( + :index, + :type, + :scroll, + :size, + :explain, + :ignore_indices, + :ignore_unavailable, + :allow_no_indices, + :expand_wildcards, + :preference, + :q, + :routing, + :source, + :_source, + :_source_include, + :_source_exclude, + :stats, + :timeout) + + scroll = search_params.delete(:scroll) || '5m' + + body = options + + # Get the initial scroll_id + # + response = gateway.client.search( { index: gateway.index_name, + type: gateway.document_type, + search_type: 'scan', + scroll: scroll, + size: 20, + body: body }.merge(search_params) ) + + # Get the initial batch of documents + # + response = gateway.client.scroll( { scroll_id: response['_scroll_id'], scroll: scroll } ) + + # Break when receiving an empty array of hits + # + while response['hits']['hits'].any? do + yield Repository::Response::Results.new(gateway, response) + + response = gateway.client.scroll( { scroll_id: response['_scroll_id'], scroll: scroll } ) + end + + return response['_scroll_id'] + end end end diff --git a/elasticsearch-persistence/test/integration/model/model_basic_test.rb b/elasticsearch-persistence/test/integration/model/model_basic_test.rb index e67c0b43d..dd7d3b344 100644 --- a/elasticsearch-persistence/test/integration/model/model_basic_test.rb +++ b/elasticsearch-persistence/test/integration/model/model_basic_test.rb @@ -26,6 +26,10 @@ class ::Person end context "A basic persistence model" do + setup do + Person.gateway.create_index! force: true + end + should "save and find the object" do person = Person.new name: 'John Smith', birthday: Date.parse('1970-01-01') person.save @@ -94,6 +98,20 @@ class ::Person assert people.map_with_hit { |o,h| h._score }.all? { |s| s > 0 } end + + should "find instances in batches" do + 100.times { |i| Person.create name: "John #{i+1}" } + Person.gateway.refresh_index! + + @results = [] + + Person.find_in_batches(_source_include: 'name') do |batch| + @results += batch.map(&:name) + end + + assert_equal 100, @results.size + assert_contains @results, 'John 1' + end end end diff --git a/elasticsearch-persistence/test/unit/model_find_test.rb b/elasticsearch-persistence/test/unit/model_find_test.rb index 8879e7bfb..a5fe0fc08 100644 --- a/elasticsearch-persistence/test/unit/model_find_test.rb +++ b/elasticsearch-persistence/test/unit/model_find_test.rb @@ -34,7 +34,7 @@ def set_id(id); self.id = id; end end setup do - @gateway = stub + @gateway = stub(client: stub(), index_name: 'foo', document_type: 'bar') DummyFindModel.stubs(:gateway).returns(@gateway) @response = MultiJson.load <<-JSON @@ -83,5 +83,35 @@ def set_id(id); self.id = id; end DummyFindModel.all( { query: { match: { title: 'test' } }, routing: 'abc123' } ) end + should "find all records in batches" do + @gateway + .expects(:deserialize) + .with('_source' => {'foo' => 'bar'}) + .returns('_source' => {'foo' => 'bar'}) + + @gateway.client + .expects(:search) + .with do |arguments| + assert_equal 'scan', arguments[:search_type] + assert_equal 'foo', arguments[:index] + assert_equal 'bar', arguments[:type] + end + .returns(MultiJson.load('{"_scroll_id":"abc123==", "hits":{"hits":[]}}')) + + @gateway.client + .expects(:scroll) + .twice + .returns(MultiJson.load('{"_scroll_id":"abc456==", "hits":{"hits":[{"_source":{"foo":"bar"}}]}}')) + .then + .returns(MultiJson.load('{"_scroll_id":"abc789==", "hits":{"hits":[]}}')) + + @doc = nil + + result = DummyFindModel.find_in_batches { |batch| @doc = batch.first['_source']['foo'] } + + assert_equal 'abc789==', result + assert_equal 'bar', @doc + end + end end From e5546e59d02caa3c9f6167b0731bc958f1e07779 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Wed, 28 May 2014 10:40:43 +0200 Subject: [PATCH 078/582] [STORE] Added, that `find_in_batches` returns an Enumerator when the block is not passed Example: Person .find_in_batches(size: 100) .each { |batch| puts batch.results.map(&:name) } # => Test 0 Test 1 Test 2 Test 3 Test 4 See: * http://ruby-doc.org/core-2.1.2/Object.html#method-i-to_enum * http://blog.arkency.com/2014/01/ruby-to-enum-for-enumerator/ --- .../lib/elasticsearch/persistence/model/find.rb | 9 ++++++++- elasticsearch-persistence/test/unit/model_find_test.rb | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb index 9c794bc6c..3389b67e8 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb @@ -45,9 +45,16 @@ def all(options={}) # # Person.find_in_batches( _source_include: 'name') { |_| puts _.response.hits.hits.map(&:to_hash) } # - # @return [String] The `scroll_id` for the request + # @example Leave out the block to return an Enumerator instance + # + # Person.find_in_batches(size: 100).map { |batch| batch.size } + # # => [100, 100, 100, ... ] + # + # @return [String,Enumerator] The `scroll_id` for the request or Enumerator when the block is not passed # def find_in_batches(options={}, &block) + return to_enum(:find_in_batches, options) unless block_given? + search_params = options.extract!( :index, :type, diff --git a/elasticsearch-persistence/test/unit/model_find_test.rb b/elasticsearch-persistence/test/unit/model_find_test.rb index a5fe0fc08..3bf97b8d1 100644 --- a/elasticsearch-persistence/test/unit/model_find_test.rb +++ b/elasticsearch-persistence/test/unit/model_find_test.rb @@ -113,5 +113,11 @@ def set_id(id); self.id = id; end assert_equal 'bar', @doc end + should "return an Enumerator for find in batches" do + assert_nothing_raised do + assert_instance_of Enumerator, DummyFindModel.find_in_batches + end + end + end end From 074930f53e83fccdfc62f6fc61806d01008f1633 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Wed, 28 May 2014 11:57:00 +0200 Subject: [PATCH 079/582] [STORE] Added the `find_each` method for models Person.find_each { |person| puts person.name } # # GET http://localhost:9200/people/person/_search?scroll=5m&search_type=scan&size=20 # # GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhbj... # Test 0 # Test 1 # Test 2 # ... # # GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhbj... # Test 20 # Test 21 # Test 22 See: http://api.rubyonrails.org/classes/ActiveRecord/Batches.html#method-i-find_each --- .../elasticsearch/persistence/model/find.rb | 42 ++++++++++- .../integration/model/model_basic_test.rb | 21 +++++- .../test/unit/model_find_test.rb | 75 +++++++++++++------ 3 files changed, 110 insertions(+), 28 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb index 3389b67e8..f16a54d5b 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb @@ -25,8 +25,12 @@ def all(options={}) # # You can restrict the models being returned with a query. # - # The full {Persistence::Repository::Response::Result} instance is yielded to the passed - # block in each batch, so you can access any of its properties. Calling `to_a` will + # The {http://rubydoc.info/gems/elasticsearch-api/Elasticsearch/API/Actions#search-instance_method Search API} + # options are passed to the search method as parameters, all remaining options are passed + # as the `:body` parameter. + # + # The full {Persistence::Repository::Response::Results} instance is yielded to the passed + # block in each batch, so you can access any of its properties; calling `to_a` will # convert the object to an Array of model instances. # # @example Return all models in batches of 20 x number of primary shards @@ -102,6 +106,40 @@ def find_in_batches(options={}, &block) return response['_scroll_id'] end + + # Iterate effectively over models using the `find_in_batches` method. + # + # All the options are passed to `find_in_batches` and each result is yielded to the passed block. + # + # @example Print out the people's names by scrolling through the index + # + # Person.find_each { |person| puts person.name } + # + # # # GET http://localhost:9200/people/person/_search?scroll=5m&search_type=scan&size=20 + # # # GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhbj... + # # Test 0 + # # Test 1 + # # Test 2 + # # ... + # # # GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhbj... + # # Test 20 + # # Test 21 + # # Test 22 + # + # @example Leave out the block to return an Enumerator instance + # + # Person.find_each.select { |person| person.name =~ /John/ } + # # => => [#] + # + # @return [String,Enumerator] The `scroll_id` for the request or Enumerator when the block is not passed + # + def find_each(options = {}) + return to_enum(:find_each, options) unless block_given? + + find_in_batches(options) do |batch| + batch.each { |result| yield result } + end + end end end diff --git a/elasticsearch-persistence/test/integration/model/model_basic_test.rb b/elasticsearch-persistence/test/integration/model/model_basic_test.rb index dd7d3b344..7fe99b959 100644 --- a/elasticsearch-persistence/test/integration/model/model_basic_test.rb +++ b/elasticsearch-persistence/test/integration/model/model_basic_test.rb @@ -100,16 +100,33 @@ class ::Person end should "find instances in batches" do - 100.times { |i| Person.create name: "John #{i+1}" } + 50.times { |i| Person.create name: "John #{i+1}" } Person.gateway.refresh_index! + @batches = 0 @results = [] Person.find_in_batches(_source_include: 'name') do |batch| + @batches += 1 @results += batch.map(&:name) end - assert_equal 100, @results.size + assert_equal 3, @batches + assert_equal 50, @results.size + assert_contains @results, 'John 1' + end + + should "find each instance" do + 50.times { |i| Person.create name: "John #{i+1}" } + Person.gateway.refresh_index! + + @results = [] + + Person.find_each(_source_include: 'name') do |person| + @results << person.name + end + + assert_equal 50, @results.size assert_contains @results, 'John 1' end end diff --git a/elasticsearch-persistence/test/unit/model_find_test.rb b/elasticsearch-persistence/test/unit/model_find_test.rb index 3bf97b8d1..649e96d2e 100644 --- a/elasticsearch-persistence/test/unit/model_find_test.rb +++ b/elasticsearch-persistence/test/unit/model_find_test.rb @@ -83,39 +83,66 @@ def set_id(id); self.id = id; end DummyFindModel.all( { query: { match: { title: 'test' } }, routing: 'abc123' } ) end - should "find all records in batches" do - @gateway + context "finding via scan/scroll" do + setup do + @gateway .expects(:deserialize) .with('_source' => {'foo' => 'bar'}) .returns('_source' => {'foo' => 'bar'}) - @gateway.client - .expects(:search) - .with do |arguments| - assert_equal 'scan', arguments[:search_type] - assert_equal 'foo', arguments[:index] - assert_equal 'bar', arguments[:type] - end - .returns(MultiJson.load('{"_scroll_id":"abc123==", "hits":{"hits":[]}}')) + @gateway.client + .expects(:search) + .with do |arguments| + assert_equal 'scan', arguments[:search_type] + assert_equal 'foo', arguments[:index] + assert_equal 'bar', arguments[:type] + end + .returns(MultiJson.load('{"_scroll_id":"abc123==", "hits":{"hits":[]}}')) + + @gateway.client + .expects(:scroll) + .twice + .returns(MultiJson.load('{"_scroll_id":"abc456==", "hits":{"hits":[{"_source":{"foo":"bar"}}]}}')) + .then + .returns(MultiJson.load('{"_scroll_id":"abc789==", "hits":{"hits":[]}}')) + end - @gateway.client - .expects(:scroll) - .twice - .returns(MultiJson.load('{"_scroll_id":"abc456==", "hits":{"hits":[{"_source":{"foo":"bar"}}]}}')) - .then - .returns(MultiJson.load('{"_scroll_id":"abc789==", "hits":{"hits":[]}}')) + should "find all records in batches" do + @doc = nil + result = DummyFindModel.find_in_batches { |batch| @doc = batch.first['_source']['foo'] } - @doc = nil + assert_equal 'abc789==', result + assert_equal 'bar', @doc + end - result = DummyFindModel.find_in_batches { |batch| @doc = batch.first['_source']['foo'] } + should "return an Enumerator for find in batches" do + @doc = nil + assert_nothing_raised do + e = DummyFindModel.find_in_batches + assert_instance_of Enumerator, e - assert_equal 'abc789==', result - assert_equal 'bar', @doc - end + e.each { |batch| @doc = batch.first['_source']['foo'] } + assert_equal 'bar', @doc + end + end + + should "find each" do + @doc = nil + result = DummyFindModel.find_each { |doc| @doc = doc['_source']['foo'] } + + assert_equal 'abc789==', result + assert_equal 'bar', @doc + end + + should "return an Enumerator for find each" do + @doc = nil + assert_nothing_raised do + e = DummyFindModel.find_each + assert_instance_of Enumerator, e - should "return an Enumerator for find in batches" do - assert_nothing_raised do - assert_instance_of Enumerator, DummyFindModel.find_in_batches + e.each { |doc| @doc = doc['_source']['foo'] } + assert_equal 'bar', @doc + end end end From 634fbecbd4c039754890a7fecc8b9d1d625aabf3 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Wed, 28 May 2014 12:02:27 +0200 Subject: [PATCH 080/582] [STORE] Added a custom `inspect` methods to model person.inspect # --- .../lib/elasticsearch/persistence/model.rb | 4 ++++ .../test/unit/model_base_test.rb | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 elasticsearch-persistence/test/unit/model_base_test.rb diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb index 9a38cfdcf..a17db7173 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb @@ -127,6 +127,10 @@ def deserialize(document) attribute :created_at, DateTime, default: lambda { |o,a| Time.now.utc } attribute :updated_at, DateTime, default: lambda { |o,a| Time.now.utc } + def to_s + "#<#{self.class} #{attributes.to_hash.inspect.gsub(/:(\w+)=>/, '\1: ')}>" + end + def set_id(id) self.id = id end diff --git a/elasticsearch-persistence/test/unit/model_base_test.rb b/elasticsearch-persistence/test/unit/model_base_test.rb new file mode 100644 index 000000000..9925bd192 --- /dev/null +++ b/elasticsearch-persistence/test/unit/model_base_test.rb @@ -0,0 +1,21 @@ +require 'test_helper' + +require 'elasticsearch/persistence/model' +require 'elasticsearch/persistence/model/rails' + +class Elasticsearch::Persistence::ModelBaseTest < Test::Unit::TestCase + context "The model" do + setup do + class DummyBaseModel + include Elasticsearch::Persistence::Model + + attribute :name, String + end + end + + should "have the customized inspect method" do + m = DummyBaseModel.new name: 'Test' + assert_match /name\: "Test"/, m.inspect + end + end +end From 4d6aadeb189dd3879727e047513d0456fcd6b279 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Sat, 31 May 2014 11:09:12 +0200 Subject: [PATCH 081/582] [STORE] Added, that model can be created with a custom ID, and _index/_type/_version are set per model instance Previously, it was not possible to set a custom ID for a model, when creating/saving it. This patch fixes this ridiculous oversight. Also, when the model is saved into a *different* index than the class-level `index_name`, the `_index` method returns it correctly. Also applies to `_type` and `_version`. --- .../lib/elasticsearch/persistence/model.rb | 44 ++--- .../elasticsearch/persistence/model/base.rb | 75 +++++++++ .../elasticsearch/persistence/model/store.rb | 53 +++++- .../elasticsearch/persistence/model/utils.rb | 0 .../test/unit/model_base_test.rb | 19 +++ .../test/unit/model_find_test.rb | 3 - .../test/unit/model_gateway_test.rb | 1 - .../test/unit/model_store_test.rb | 151 +++++++++++++++++- 8 files changed, 304 insertions(+), 42 deletions(-) create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/model/utils.rb diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb index a17db7173..1a6e98505 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb @@ -4,6 +4,7 @@ require 'virtus' require 'elasticsearch/persistence' +require 'elasticsearch/persistence/model/base' require 'elasticsearch/persistence/model/errors' require 'elasticsearch/persistence/model/store' require 'elasticsearch/persistence/model/find' @@ -22,29 +23,6 @@ module Persistence # end # module Model - - # Utility methods for {Elasticsearch::Persistence::Model} - # - module Utils - - # Return Elasticsearch type based on passed Ruby class (used in the `attribute` method) - # - def lookup_type(type) - case - when type == String - 'string' - when type == Integer - 'integer' - when type == Float - 'float' - when type == Date || type == Time || type == DateTime - 'date' - when type == Virtus::Attribute::Boolean - 'boolean' - end - end; module_function :lookup_type - end - def self.included(base) base.class_eval do include ActiveModel::Naming @@ -59,6 +37,8 @@ def self.included(base) define_model_callbacks :create, :save, :update, :destroy define_model_callbacks :find, :touch, only: :after + include Elasticsearch::Persistence::Model::Base::InstanceMethods + extend Elasticsearch::Persistence::Model::Store::ClassMethods include Elasticsearch::Persistence::Model::Store::InstanceMethods @@ -115,7 +95,14 @@ def serialize(document) def deserialize(document) object = klass.new document['_source'] - object.set_id document['_id'] + + # Set the meta attributes when fetching the document from Elasticsearch + # + object.instance_variable_set :@_id, document['_id'] + object.instance_variable_set :@_index, document['_index'] + object.instance_variable_set :@_type, document['_type'] + object.instance_variable_set :@_version, document['_version'] + object.instance_variable_set(:@persisted, true) object end @@ -123,17 +110,8 @@ def deserialize(document) # Set up common attributes # - attribute :id, String, writer: :private attribute :created_at, DateTime, default: lambda { |o,a| Time.now.utc } attribute :updated_at, DateTime, default: lambda { |o,a| Time.now.utc } - - def to_s - "#<#{self.class} #{attributes.to_hash.inspect.gsub(/:(\w+)=>/, '\1: ')}>" - end - - def set_id(id) - self.id = id - end end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb new file mode 100644 index 000000000..f1fcebed0 --- /dev/null +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb @@ -0,0 +1,75 @@ +module Elasticsearch + module Persistence + module Model + # This module contains the base interface for models + # + module Base + module InstanceMethods + + # Model initializer sets the `@id` variable if passed + # + def initialize(attributes={}) + @_id = attributes[:id] || attributes['id'] + super + end + + # Return model attributes as a Hash, merging in the `id` + # + def attributes + super.merge id: id + end + + # Return the document `_id` + # + def id + @_id + end; alias :_id :id + + # Return the document `_index` + # + def _index + @_index + end + + # Return the document `_type` + # + def _type + @_type + end + + # Return the document `_version` + # + def _version + @_version + end + + def to_s + "#<#{self.class} #{attributes.to_hash.inspect.gsub(/:(\w+)=>/, '\1: ')}>" + end + end + end + + # Utility methods for {Elasticsearch::Persistence::Model} + # + module Utils + + # Return Elasticsearch type based on passed Ruby class (used in the `attribute` method) + # + def lookup_type(type) + case + when type == String + 'string' + when type == Integer + 'integer' + when type == Float + 'float' + when type == Date || type == Time || type == DateTime + 'date' + when type == Virtus::Attribute::Boolean + 'boolean' + end + end; module_function :lookup_type + end + end + end +end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb index 85f1abb2c..4c435ed6e 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb @@ -46,10 +46,20 @@ module InstanceMethods def save(options={}) return false unless valid? run_callbacks :save do - response = self.class.gateway.save(self, options.merge(id: self.id)) + options.update id: self.id + options.update index: self._index if self._index + options.update type: self._type if self._type + + response = self.class.gateway.save(self, options) + self[:updated_at] = Time.now.utc + + @_id = response['_id'] + @_index = response['_index'] + @_type = response['_type'] + @_version = response['_version'] @persisted = true - set_id(response['_id']) if respond_to?(:set_id) + response end end @@ -67,7 +77,11 @@ def destroy(options={}) raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? run_callbacks :destroy do + options.update index: self._index if self._index + options.update type: self._type if self._type + response = self.class.gateway.delete(self.id, options) + @destroyed = true @persisted = false self.freeze @@ -88,9 +102,18 @@ def update(attributes={}, options={}) raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? run_callbacks :update do + options.update index: self._index if self._index + options.update type: self._type if self._type + attributes.update( { updated_at: Time.now.utc } ) + response = self.class.gateway.update(self.id, { doc: attributes}.merge(options)) + self.attributes = self.attributes.merge(attributes) + @_index = response['_index'] + @_type = response['_type'] + @_version = response['_version'] + response end end; alias :update_attributes :update @@ -110,8 +133,17 @@ def update(attributes={}, options={}) def increment(attribute, value=1, options={}) raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? + options.update index: self._index if self._index + options.update type: self._type if self._type + response = self.class.gateway.update(self.id, { script: "ctx._source.#{attribute} += #{value}"}.merge(options)) + self[attribute] += value + + @_index = response['_index'] + @_type = response['_type'] + @_version = response['_version'] + response end @@ -130,8 +162,16 @@ def increment(attribute, value=1, options={}) def decrement(attribute, value=1, options={}) raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? + options.update index: self._index if self._index + options.update type: self._type if self._type + response = self.class.gateway.update(self.id, { script: "ctx._source.#{attribute} = ctx._source.#{attribute} - #{value}"}.merge(options)) self[attribute] -= value + + @_index = response['_index'] + @_type = response['_type'] + @_version = response['_version'] + response end @@ -152,9 +192,18 @@ def touch(attribute=:updated_at, options={}) raise ArgumentError, "Object does not have '#{attribute}' attribute" unless respond_to?(attribute) run_callbacks :touch do + options.update index: self._index if self._index + options.update type: self._type if self._type + value = Time.now.utc response = self.class.gateway.update(self.id, { doc: { attribute => value.iso8601 }}.merge(options)) + self[attribute] = value + + @_index = response['_index'] + @_type = response['_type'] + @_version = response['_version'] + response end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/utils.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/utils.rb new file mode 100644 index 000000000..e69de29bb diff --git a/elasticsearch-persistence/test/unit/model_base_test.rb b/elasticsearch-persistence/test/unit/model_base_test.rb index 9925bd192..e9d14d27c 100644 --- a/elasticsearch-persistence/test/unit/model_base_test.rb +++ b/elasticsearch-persistence/test/unit/model_base_test.rb @@ -13,6 +13,25 @@ class DummyBaseModel end end + should "respond to id, _id, _index, _type and _version" do + model = DummyBaseModel.new + + [:id, :_id, :_index, :_type, :_version].each { |method| assert_respond_to model, method } + end + + should "set the ID from attributes during initialization" do + m = DummyBaseModel.new id: 1 + assert_equal 1, m.id + + m = DummyBaseModel.new 'id' => 2 + assert_equal 2, m.id + end + + should "have ID in attributes" do + m = DummyBaseModel.new id: 1, name: 'Test' + assert_equal 1, m.attributes[:id] + end + should "have the customized inspect method" do m = DummyBaseModel.new name: 'Test' assert_match /name\: "Test"/, m.inspect diff --git a/elasticsearch-persistence/test/unit/model_find_test.rb b/elasticsearch-persistence/test/unit/model_find_test.rb index 649e96d2e..546974b7c 100644 --- a/elasticsearch-persistence/test/unit/model_find_test.rb +++ b/elasticsearch-persistence/test/unit/model_find_test.rb @@ -24,13 +24,10 @@ class DummyFindModel define_model_callbacks :create, :save, :update, :destroy define_model_callbacks :find, :touch, only: :after - attribute :id, String, writer: :private attribute :title, String attribute :count, Integer, default: 0 attribute :created_at, DateTime, default: lambda { |o,a| Time.now.utc } attribute :updated_at, DateTime, default: lambda { |o,a| Time.now.utc } - - def set_id(id); self.id = id; end end setup do diff --git a/elasticsearch-persistence/test/unit/model_gateway_test.rb b/elasticsearch-persistence/test/unit/model_gateway_test.rb index d7bdb9086..daba814fc 100644 --- a/elasticsearch-persistence/test/unit/model_gateway_test.rb +++ b/elasticsearch-persistence/test/unit/model_gateway_test.rb @@ -34,7 +34,6 @@ def run!; DummyGatewayModel.gateway { |g| @b += 1 }; end should "define common attributes" do d = DummyGatewayModel.new - assert_respond_to d, :id assert_respond_to d, :updated_at assert_respond_to d, :created_at end diff --git a/elasticsearch-persistence/test/unit/model_store_test.rb b/elasticsearch-persistence/test/unit/model_store_test.rb index 2ed8af091..be561525f 100644 --- a/elasticsearch-persistence/test/unit/model_store_test.rb +++ b/elasticsearch-persistence/test/unit/model_store_test.rb @@ -3,6 +3,7 @@ require 'active_model' require 'virtus' +require 'elasticsearch/persistence/model/base' require 'elasticsearch/persistence/model/errors' require 'elasticsearch/persistence/model/store' @@ -18,6 +19,7 @@ class DummyStoreModel include Virtus.model + include Elasticsearch::Persistence::Model::Base::InstanceMethods extend Elasticsearch::Persistence::Model::Store::ClassMethods include Elasticsearch::Persistence::Model::Store::InstanceMethods @@ -25,13 +27,10 @@ class DummyStoreModel define_model_callbacks :create, :save, :update, :destroy define_model_callbacks :find, :touch, only: :after - attribute :id, String, writer: :private attribute :title, String attribute :count, Integer, default: 0 attribute :created_at, DateTime, default: lambda { |o,a| Time.now.utc } attribute :updated_at, DateTime, default: lambda { |o,a| Time.now.utc } - - def set_id(id); self.id = id; end end setup do @@ -136,6 +135,38 @@ def set_id(id); self.id = id; end d = DummyStoreModelWithCallback.new name: 'Test' d.save end + + should "save the model to its own index" do + @gateway.expects(:save) + .with do |model, options| + assert_equal 'my_custom_index', options[:index] + assert_equal 'my_custom_type', options[:type] + end + .returns({'_id' => 'abc'}) + + d = DummyStoreModel.new name: 'Test' + d.instance_variable_set(:@_index, 'my_custom_index') + d.instance_variable_set(:@_type, 'my_custom_type') + d.save + end + + should "set the meta attributes from response" do + @gateway.expects(:save) + .with do |model, options| + assert_equal 'my_custom_index', options[:index] + assert_equal 'my_custom_type', options[:type] + end + .returns({'_id' => 'abc', '_index' => 'foo', '_type' => 'bar', '_version' => '100'}) + + d = DummyStoreModel.new name: 'Test' + d.instance_variable_set(:@_index, 'my_custom_index') + d.instance_variable_set(:@_type, 'my_custom_type') + d.save + + assert_equal 'foo', d._index + assert_equal 'bar', d._type + assert_equal '100', d._version + end end context "when destroying," do @@ -188,6 +219,24 @@ def set_id(id); self.id = id; end d = DummyStoreModelWithCallback.new name: 'Test' d.expects(:persisted?).returns(true) d.expects(:freeze).returns(d) + + d.destroy + end + + should "remove the model from its own index" do + @gateway.expects(:delete) + .with do |model, options| + assert_equal 'my_custom_index', options[:index] + assert_equal 'my_custom_type', options[:type] + end + .returns({'_id' => 'abc'}) + + d = DummyStoreModel.new name: 'Test' + d.instance_variable_set(:@_index, 'my_custom_index') + d.instance_variable_set(:@_type, 'my_custom_type') + d.expects(:persisted?).returns(true) + d.expects(:freeze).returns(d) + d.destroy end end @@ -259,6 +308,42 @@ def set_id(id); self.id = id; end d.expects(:persisted?).returns(true) d.update name: 'Update' end + + should "update the model in its own index" do + @gateway.expects(:update) + .with do |model, options| + assert_equal 'my_custom_index', options[:index] + assert_equal 'my_custom_type', options[:type] + end + .returns({'_id' => 'abc'}) + + d = DummyStoreModel.new name: 'Test' + d.instance_variable_set(:@_index, 'my_custom_index') + d.instance_variable_set(:@_type, 'my_custom_type') + d.expects(:persisted?).returns(true) + + d.update name: 'Update' + end + + should "set the meta attributes from response" do + @gateway.expects(:update) + .with do |model, options| + assert_equal 'my_custom_index', options[:index] + assert_equal 'my_custom_type', options[:type] + end + .returns({'_id' => 'abc', '_index' => 'foo', '_type' => 'bar', '_version' => '100'}) + + d = DummyStoreModel.new name: 'Test' + d.instance_variable_set(:@_index, 'my_custom_index') + d.instance_variable_set(:@_type, 'my_custom_type') + d.expects(:persisted?).returns(true) + + d.update name: 'Update' + + assert_equal 'foo', d._index + assert_equal 'bar', d._type + assert_equal '100', d._version + end end context "when incrementing," do @@ -276,6 +361,26 @@ def set_id(id); self.id = id; end assert_equal 1, subject.count end + + should "set the meta attributes from response" do + subject.expects(:persisted?).returns(true) + + @gateway.expects(:update) + .with do |model, options| + assert_equal 'my_custom_index', options[:index] + assert_equal 'my_custom_type', options[:type] + end + .returns({'_id' => 'abc', '_index' => 'foo', '_type' => 'bar', '_version' => '100'}) + + subject.instance_variable_set(:@_index, 'my_custom_index') + subject.instance_variable_set(:@_type, 'my_custom_type') + + subject.increment :count + + assert_equal 'foo', subject._index + assert_equal 'bar', subject._type + assert_equal '100', subject._version + end end context "when decrement," do @@ -293,6 +398,26 @@ def set_id(id); self.id = id; end assert_equal -1, subject.count end + + should "set the meta attributes from response" do + subject.expects(:persisted?).returns(true) + + @gateway.expects(:update) + .with do |model, options| + assert_equal 'my_custom_index', options[:index] + assert_equal 'my_custom_type', options[:type] + end + .returns({'_id' => 'abc', '_index' => 'foo', '_type' => 'bar', '_version' => '100'}) + + subject.instance_variable_set(:@_index, 'my_custom_index') + subject.instance_variable_set(:@_type, 'my_custom_type') + + subject.decrement :count + + assert_equal 'foo', subject._index + assert_equal 'bar', subject._type + assert_equal '100', subject._version + end end context "when touching," do @@ -342,6 +467,26 @@ def set_id(id); self.id = id; end d.expects(:persisted?).returns(true) d.touch end + + should "set the meta attributes from response" do + subject.expects(:persisted?).returns(true) + + @gateway.expects(:update) + .with do |model, options| + assert_equal 'my_custom_index', options[:index] + assert_equal 'my_custom_type', options[:type] + end + .returns({'_id' => 'abc', '_index' => 'foo', '_type' => 'bar', '_version' => '100'}) + + subject.instance_variable_set(:@_index, 'my_custom_index') + subject.instance_variable_set(:@_type, 'my_custom_type') + + subject.touch + + assert_equal 'foo', subject._index + assert_equal 'bar', subject._type + assert_equal '100', subject._version + end end end From 1f74e7b16a33f310dd82dac383898defacbd5504 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 3 Jun 2014 16:17:37 +0200 Subject: [PATCH 082/582] [STORE] Added a `count` method to the repository and model repository.count Person.count --- .../elasticsearch/persistence/model/find.rb | 23 +++++++++++++++++ .../persistence/repository/search.rb | 25 +++++++++++++++++++ .../repository/default_class_test.rb | 6 +++++ .../test/unit/repository_search_test.rb | 17 +++++++++++++ 4 files changed, 71 insertions(+) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb index f16a54d5b..66a6d7180 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb @@ -21,6 +21,29 @@ def all(options={}) gateway.search( { query: { match_all: {} }, size: 10_000 }.merge(options) ) end + # Returns the number of models + # + # @example Return the count of all models + # + # Person.count + # # => 2 + # + # @example Return the count of models matching a simple query + # + # Person.count('fox or dog') + # # => 1 + # + # @example Return the count of models matching a query in the Elasticsearch DSL + # + # Person.search(query: { match: { title: 'fox dog' } }) + # # => 1 + # + # @return [Integer] + # + def count(query_or_definition=nil, options={}) + gateway.count( query_or_definition, options ) + end + # Returns all models efficiently via the Elasticsearch's scan/scroll API # # You can restrict the models being returned with a query. diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb index 5ed018dab..201d2979d 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb @@ -53,6 +53,31 @@ def search(query_or_definition, options={}) end Response::Results.new(self, response) end + + # Return the number of domain object in the index + # + # @example Return the number of all domain objects + # + # repository.count + # # => 2 + # + # @example Return the count of domain object matching a simple query + # + # repository.count('fox or dog') + # # => 1 + # + # @example Return the count of domain object matching a query in the Elasticsearch DSL + # + # repository.search(query: { match: { title: 'fox dog' } }) + # # => 1 + # + # @return [Integer] + # + def count(query_or_definition=nil, options={}) + query_or_definition ||= { query: { match_all: {} } } + response = search query_or_definition, options.update(search_type: 'count') + response.response.hits.total + end end end diff --git a/elasticsearch-persistence/test/integration/repository/default_class_test.rb b/elasticsearch-persistence/test/integration/repository/default_class_test.rb index efe926cfd..0335aa843 100644 --- a/elasticsearch-persistence/test/integration/repository/default_class_test.rb +++ b/elasticsearch-persistence/test/integration/repository/default_class_test.rb @@ -96,6 +96,12 @@ def to_hash assert_equal 2, results.size end + should "count notes" do + @repository.save Note.new(title: 'Test') + @repository.client.indices.refresh index: @repository.index_name + assert_equal 1, @repository.count + end + should "save and find a plain hash" do @repository.save id: 1, title: 'Hash' result = @repository.find(1) diff --git a/elasticsearch-persistence/test/unit/repository_search_test.rb b/elasticsearch-persistence/test/unit/repository_search_test.rb index df3f7dc61..3eec4bedc 100644 --- a/elasticsearch-persistence/test/unit/repository_search_test.rb +++ b/elasticsearch-persistence/test/unit/repository_search_test.rb @@ -92,6 +92,23 @@ class MyDocument; end subject.search 123 end end + + should "return the number of domain objects" do + subject.expects(:search) + .returns(Elasticsearch::Persistence::Repository::Response::Results.new( subject, {'hits' => { 'total' => 1 }})) + assert_equal 1, subject.count + end + + should "pass arguments from count to search" do + subject.expects(:search) + .with do |query_or_definition, options| + assert_equal 'bar', query_or_definition[:query][:match][:foo] + assert_equal true, options[:ignore_unavailable] + end + .returns(Elasticsearch::Persistence::Repository::Response::Results.new( subject, {'hits' => { 'total' => 1 }})) + + subject.count( { query: { match: { foo: 'bar' } } }, { ignore_unavailable: true } ) + end end end From 35aa57a9b41dc385437f1bedfe1c9b2d5def1fda Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Thu, 5 Jun 2014 19:07:28 +0200 Subject: [PATCH 083/582] [STORE] Allow accessing the raw hit directly from result people = Person.search query: { match: { name: 'smith' } }, highlight: { fields: { name: {} } } people.first.hit.highlight['name'].first # => ["John Smith"] --- .../lib/elasticsearch/persistence/model.rb | 16 +++++++++++----- .../test/integration/model/model_basic_test.rb | 6 +++++- .../test/unit/model_gateway_test.rb | 6 ++++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb index 1a6e98505..3b2680d0c 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb @@ -45,7 +45,6 @@ def self.included(base) extend Elasticsearch::Persistence::Model::Find::ClassMethods class << self - # Re-define the Virtus' `attribute` method, to configure Elasticsearch mapping as well # def attribute(name, type=nil, options={}, &block) @@ -98,10 +97,15 @@ def deserialize(document) # Set the meta attributes when fetching the document from Elasticsearch # - object.instance_variable_set :@_id, document['_id'] - object.instance_variable_set :@_index, document['_index'] - object.instance_variable_set :@_type, document['_type'] - object.instance_variable_set :@_version, document['_version'] + object.instance_variable_set :@_id, document['_id'] + object.instance_variable_set :@_index, document['_index'] + object.instance_variable_set :@_type, document['_type'] + object.instance_variable_set :@_version, document['_version'] + + # Store the "hit" information (highlighting, score, ...) + # + object.instance_variable_set :@hit, + Hashie::Mash.new(document.except('_index', '_type', '_id', '_version', '_source')) object.instance_variable_set(:@persisted, true) object @@ -112,6 +116,8 @@ def deserialize(document) # attribute :created_at, DateTime, default: lambda { |o,a| Time.now.utc } attribute :updated_at, DateTime, default: lambda { |o,a| Time.now.utc } + + attr_reader :hit end end diff --git a/elasticsearch-persistence/test/integration/model/model_basic_test.rb b/elasticsearch-persistence/test/integration/model/model_basic_test.rb index 7fe99b959..a89505354 100644 --- a/elasticsearch-persistence/test/integration/model/model_basic_test.rb +++ b/elasticsearch-persistence/test/integration/model/model_basic_test.rb @@ -91,12 +91,16 @@ class ::Person Person.create name: 'Mary Smith' Person.gateway.refresh_index! - people = Person.search query: { match: { name: 'smith' } } + people = Person.search query: { match: { name: 'smith' } }, + highlight: { fields: { name: {} } } assert_equal 2, people.total assert_equal 2, people.size assert people.map_with_hit { |o,h| h._score }.all? { |s| s > 0 } + + assert_not_nil people.first.hit + assert_match /smith/i, people.first.hit.highlight['name'].first end should "find instances in batches" do diff --git a/elasticsearch-persistence/test/unit/model_gateway_test.rb b/elasticsearch-persistence/test/unit/model_gateway_test.rb index daba814fc..b47de0613 100644 --- a/elasticsearch-persistence/test/unit/model_gateway_test.rb +++ b/elasticsearch-persistence/test/unit/model_gateway_test.rb @@ -89,5 +89,11 @@ def run!; DummyGatewayModel.gateway { |g| @b += 1 }; end assert DummyGatewayModel.gateway.deserialize('_id' => 'abc123', '_source' => {}).instance_variable_get(:@persisted) end + should "allow to access the raw hit from results as Hashie::Mash" do + assert_equal 0.42, DummyGatewayModel.gateway.deserialize('_score' => 0.42, '_source' => {}).hit._score + end + + + end end From 783cd1a3960e2e741e682f3e21a41e1a3c3d544f Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Thu, 12 Jun 2014 15:44:10 +0200 Subject: [PATCH 084/582] [STORE] Added an integration test for creating/saving models with custom IDs --- .../test/integration/model/model_basic_test.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/elasticsearch-persistence/test/integration/model/model_basic_test.rb b/elasticsearch-persistence/test/integration/model/model_basic_test.rb index a89505354..a35bae71d 100644 --- a/elasticsearch-persistence/test/integration/model/model_basic_test.rb +++ b/elasticsearch-persistence/test/integration/model/model_basic_test.rb @@ -30,6 +30,23 @@ class ::Person Person.gateway.create_index! force: true end + should "save the object with custom ID" do + person = Person.new id: 1, name: 'Number One' + person.save + + document = Person.find(1) + assert_not_nil document + assert_equal 'Number One', document.name + end + + should "create the object with custom ID" do + person = Person.create id: 1, name: 'Number One' + + document = Person.find(1) + assert_not_nil document + assert_equal 'Number One', document.name + end + should "save and find the object" do person = Person.new name: 'John Smith', birthday: Date.parse('1970-01-01') person.save From f01446ae081da1fe0cce0e32f1ef935b36c2089c Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 16 Jun 2014 10:38:49 +0200 Subject: [PATCH 085/582] [STORE] Added `Model.create_index!` and `Model.refresh_index!` methods (delegated to gateway) --- .../lib/elasticsearch/persistence/model.rb | 2 ++ .../test/integration/model/model_basic_test.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb index 3b2680d0c..015b60529 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb @@ -78,6 +78,8 @@ def gateway(&block) :search, :find, :exists?, + :create_index!, + :refresh_index!, to: :gateway end diff --git a/elasticsearch-persistence/test/integration/model/model_basic_test.rb b/elasticsearch-persistence/test/integration/model/model_basic_test.rb index a35bae71d..fe9ab2056 100644 --- a/elasticsearch-persistence/test/integration/model/model_basic_test.rb +++ b/elasticsearch-persistence/test/integration/model/model_basic_test.rb @@ -27,7 +27,7 @@ class ::Person context "A basic persistence model" do setup do - Person.gateway.create_index! force: true + Person.create_index! force: true end should "save the object with custom ID" do From 1fcc0a2927b1c4212383474516dbc0f4d9377ecf Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 16 Jun 2014 12:10:08 +0200 Subject: [PATCH 086/582] [STORE] Fixed incorrect handling of Rails' form values for `Date` (Previously, only `DateTime` objects have been working) --- .../elasticsearch/persistence/model/rails.rb | 4 ++-- .../test/unit/model_rails_test.rb | 21 ++++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb index 46f0e3074..118f52bc4 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb @@ -24,8 +24,8 @@ def self.included(base) def initialize(attributes={}) day = attributes.select { |p| p =~ /\([1-3]/ }.reduce({}) { |sum, item| (sum[item.first.gsub(/\(.+\)/, '')] ||= '' )<< item.last+'-'; sum } time = attributes.select { |p| p =~ /\([4-6]/ }.reduce({}) { |sum, item| (sum[item.first.gsub(/\(.+\)/, '')] ||= '' )<< item.last+':'; sum } - unless day.empty? && time.empty? - attributes.update day.reduce({}) { |sum, item| sum[item.first] = item.last + ' ' + time[item.first]; sum } + unless day.empty? + attributes.update day.reduce({}) { |sum, item| sum[item.first] = item.last; sum[item.first] += ' ' + time[item.first] unless time.empty?; sum } end super(attributes) diff --git a/elasticsearch-persistence/test/unit/model_rails_test.rb b/elasticsearch-persistence/test/unit/model_rails_test.rb index 260f21f8e..990dfbe6d 100644 --- a/elasticsearch-persistence/test/unit/model_rails_test.rb +++ b/elasticsearch-persistence/test/unit/model_rails_test.rb @@ -12,7 +12,8 @@ class ::MyRailsModel include Elasticsearch::Persistence::Model::Rails attribute :name, String, mapping: { analyzer: 'string' } - attribute :published_on, DateTime + attribute :published_at, DateTime + attribute :published_on, Date end class Application < Rails::Application @@ -62,15 +63,25 @@ class MyView; include ActionView::Helpers::UrlHelper; end end should "parse DateTime from Rails forms" do + params = { "published_at(1i)"=>"2014", + "published_at(2i)"=>"1", + "published_at(3i)"=>"1", + "published_at(4i)"=>"12", + "published_at(5i)"=>"00" + } + + m = MyRailsModel.new params + assert_equal "2014-01-01T12:00:00+00:00", m.published_at.iso8601 + end + + should "parse Date from Rails forms" do params = { "published_on(1i)"=>"2014", "published_on(2i)"=>"1", - "published_on(3i)"=>"1", - "published_on(4i)"=>"12", - "published_on(5i)"=>"00" + "published_on(3i)"=>"1" } m = MyRailsModel.new params - assert_equal "2014-01-01T12:00:00+00:00", m.published_on.iso8601 + assert_equal "2014-01-01", m.published_on.iso8601 end end From e3a441470c2380f324d5c3bd192211e95df933d0 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 16 Jun 2014 12:11:37 +0200 Subject: [PATCH 087/582] [STORE] Added full readme for the "active record" pattern --- elasticsearch-persistence/README.md | 180 +++++++++++++++++- .../lib/elasticsearch/persistence.rb | 19 ++ 2 files changed, 195 insertions(+), 4 deletions(-) diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index 5af250444..9e536561a 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -423,10 +423,182 @@ and demonstrates a rich set of features of the repository. ### The ActiveRecord Pattern -[_Work in progress_](https://github.com/elasticsearch/elasticsearch-rails/pull/91). -The ActiveRecord [pattern](http://www.martinfowler.com/eaaCatalog/activeRecord.html) will work -in a very similar way as `Tire::Model::Persistence`, allowing a drop-in replacement of -an Elasticsearch-backed model in Ruby on Rails applications. +The `Elasticsearch::Persistence::Model` module provides an implementation of the +active record [pattern](http://www.martinfowler.com/eaaCatalog/activeRecord.html), +with a familiar interface for using Elasticsearch as a persistence layer in +Ruby on Rails applications. + +All the methods are documented with comprehensive examples in the source code, +available also online at . + +#### Model Definition + +The integration is implemented by including the module in a Ruby class. +The model attribute definition support is implemented with the +[_Virtus_](https://github.com/solnic/virtus) Rubygem, and the +naming, validation, etc. features with the +[_ActiveModel_](https://github.com/rails/rails/tree/master/activemodel) Rubygem. + +```ruby +class Article + include Elasticsearch::Persistence::Model + + # Define a plain `title` attribute + # + attribute :title, String + + # Define an `author` attribute, with multiple analyzers for this field + # + attribute :author, String, mapping: { fields: { + author: { type: 'string'}, + raw: { type: 'string', analyzer: 'keyword' } + } } + + + # Define a `views` attribute, with default value + # + attribute :views, Integer, default: 0, mapping: { type: 'integer' } + + # Validate the presence of the `title` attribute + # + validates :title, presence: true + + # Execute code after saving the model. + # + after_save { puts "Successfuly saved: #{self}" } +end +``` + +Attribute validations works like for any other _ActiveModel_-compatible implementation: + +```ruby +article = Article.new # => #

+ +article.valid? +# => false + +article.errors.to_a +# => ["Title can't be blank"] +``` + +#### Persistence + +We can create a new article in the database... + +```ruby +Article.create id: 1, title: 'Test', author: 'John' +# PUT http://localhost:9200/articles/article/1 [status:201, request:0.015s, query:n/a] +``` + +... and find it: + +```ruby +article = Article.find(1) +# => #
+ +article._index +# => "articles" + +article.id +# => "1" + +article.title +# => "Test" +``` + +To update the model, either update the attribute and save the model: + +```ruby +article.title = 'Updated' + +article.save +=> {"_index"=>"articles", "_type"=>"article", "_id"=>"1", "_version"=>2, "created"=>false} +``` + +... or use the `update_attributes` method: + +```ruby +article.update_attributes title: 'Test', author: 'Mary' +# => {"_index"=>"articles", "_type"=>"article", "_id"=>"1", "_version"=>3} +``` + +The implementation supports the familiar interface for updating model timestamps: + +```ruby +article.touch +# => => { ... "_version"=>4} +``` + +... and numeric attributes: + +```ruby +article.views +# => 0 + +article.increment :views +article.views +# => 1 +``` + +Any callbacks defined in the model will be triggered during the persistence operations: + +```ruby +article.save +# Successfuly saved: #
+``` + +The model also supports familiar `find_in_batches` and `find_each` methods to efficiently +retrieve big collections of model instance, using the Elasticsearch's _Scan API_: + +```ruby +Article.find_each(_source_include: 'title') { |a| puts "===> #{a.title.upcase}" } +# GET http://localhost:9200/articles/article/_search?scroll=5m&search_type=scan&size=20 +# GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhb... +# ===> TEST +# GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhb... +# => "c2Nhb..." +``` + +#### Search + +The model class provides a `search` method to retrieve model instances with a regular +search definition, including highlighting, aggregations, etc: + +```ruby +results = Article.search query: { match: { title: 'test' } }, + aggregations: { authors: { terms: { field: 'author.raw' } } }, + highlight: { fields: { title: {} } } + +puts results.first.title +# Test + +puts results.first.hit.highlight['title'] +# Test + +puts results.response.aggregations.authors.buckets.each { |b| puts "#{b['key']} : #{b['doc_count']}" } +# John : 1 +``` + +#### Rails Compatibility + +The model instances are fully compatible with Rails' conventions and helpers: + +```ruby +url_for article +# => "http://localhost:3000/articles/1" + +div_for article +# => '
' +``` + +... as well as form values for dates and times: + +```ruby +article = Article.new "title" => "Date", "published(1i)"=>"2014", "published(2i)"=>"1", "published(3i)"=>"1" + +article.published.iso8601 +# => "2014-01-01" +``` ## License diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence.rb b/elasticsearch-persistence/lib/elasticsearch/persistence.rb index e6d215ad0..2d0118496 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence.rb @@ -56,6 +56,25 @@ module Elasticsearch # # 2014-04-04 22:15:25 +0200: > {"foo":"bar"} # # 2014-04-04 22:15:25 +0200: < {"_index":"my_notes","_type":"note","_id":"-d28yXLFSlusnTxb13WIZQ", ...} # + # == Model + # + # The active record pattern allows to use the interface familiar from ActiveRecord models: + # + # require 'elasticsearch/persistence' + # + # class Article + # attribute :title, String, mapping: { analyzer: 'snowball' } + # end + # + # article = Article.new id: 1, title: 'Test' + # article.save + # + # Article.find(1) + # + # article.update_attributes title: 'Update' + # + # article.destroy + # module Persistence # :nodoc: From 1ec85146635afc28d4862e8c754505b3094f188f Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 17 Jun 2014 09:14:15 +0200 Subject: [PATCH 088/582] [STORE] Added a Rails ORM generator for Elasticsearch::Persistence::Model Usage: $ bundle exec rails generate scaffold Person name:String email:String --orm=elasticsearch --force --- elasticsearch-persistence/README.md | 6 ++++++ .../elasticsearch/model/model_generator.rb | 21 +++++++++++++++++++ .../elasticsearch/model/templates/model.rb.tt | 9 ++++++++ .../generators/elasticsearch_generator.rb | 2 ++ 4 files changed, 38 insertions(+) create mode 100644 elasticsearch-persistence/lib/rails/generators/elasticsearch/model/model_generator.rb create mode 100644 elasticsearch-persistence/lib/rails/generators/elasticsearch/model/templates/model.rb.tt create mode 100644 elasticsearch-persistence/lib/rails/generators/elasticsearch_generator.rb diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index 9e536561a..b82a9166e 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -600,6 +600,12 @@ article.published.iso8601 # => "2014-01-01" ``` +The library provides a Rails ORM generator: + +```bash +rails generate scaffold Person name:String email:String birthday:Date --orm=elasticsearch +``` + ## License This software is licensed under the Apache 2 license, quoted below. diff --git a/elasticsearch-persistence/lib/rails/generators/elasticsearch/model/model_generator.rb b/elasticsearch-persistence/lib/rails/generators/elasticsearch/model/model_generator.rb new file mode 100644 index 000000000..84fb632cd --- /dev/null +++ b/elasticsearch-persistence/lib/rails/generators/elasticsearch/model/model_generator.rb @@ -0,0 +1,21 @@ +require "rails/generators/elasticsearch_generator" + +module Elasticsearch + module Generators + class ModelGenerator < ::Rails::Generators::NamedBase + source_root File.expand_path('../templates', __FILE__) + + desc "Creates an Elasticsearch::Persistence model" + argument :attributes, type: :array, default: [], banner: "attribute:type attribute:type" + + check_class_collision + + def create_model_file + @padding = attributes.map { |a| a.name.size }.max + template "model.rb.tt", File.join("app/models", class_path, "#{file_name}.rb") + end + + hook_for :test_framework + end + end +end diff --git a/elasticsearch-persistence/lib/rails/generators/elasticsearch/model/templates/model.rb.tt b/elasticsearch-persistence/lib/rails/generators/elasticsearch/model/templates/model.rb.tt new file mode 100644 index 000000000..0597174da --- /dev/null +++ b/elasticsearch-persistence/lib/rails/generators/elasticsearch/model/templates/model.rb.tt @@ -0,0 +1,9 @@ +<% module_namespacing do -%> +class <%= class_name %> + include Elasticsearch::Persistence::Model + +<% attributes.each do |attribute| -%> + <%= "attribute :#{attribute.name},".ljust(@padding+12) %> <%= attribute.type %> +<% end -%> +end +<% end -%> diff --git a/elasticsearch-persistence/lib/rails/generators/elasticsearch_generator.rb b/elasticsearch-persistence/lib/rails/generators/elasticsearch_generator.rb new file mode 100644 index 000000000..20072ed93 --- /dev/null +++ b/elasticsearch-persistence/lib/rails/generators/elasticsearch_generator.rb @@ -0,0 +1,2 @@ +require "rails/generators/named_base" +require "rails/generators/active_model" From 2a137e07fd2bae45ea1d20ca8fea97462539c8d7 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 17 Jun 2014 15:45:24 +0200 Subject: [PATCH 089/582] [STORE] Added the template for generating a full Rails application with persistence model Usage: rails new music --force --skip --skip-bundle --skip-active-record --template /path/to/template.rb rails new music --force --skip --skip-bundle --skip-active-record --template https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/template.rb --- elasticsearch-persistence/README.md | 19 + .../examples/music/album.rb | 34 ++ .../examples/music/artist.rb | 50 +++ .../examples/music/artists/_form.html.erb | 8 + .../music/artists/artists_controller.rb | 67 +++ .../music/artists/artists_controller_test.rb | 53 +++ .../examples/music/artists/index.html.erb | 57 +++ .../examples/music/artists/show.html.erb | 51 +++ .../examples/music/assets/application.css | 226 ++++++++++ .../examples/music/assets/autocomplete.css | 48 +++ .../examples/music/assets/blank_cover.png | Bin 0 -> 22778 bytes .../examples/music/assets/form.css | 113 +++++ .../examples/music/index_manager.rb | 60 +++ .../examples/music/search/index.html.erb | 93 +++++ .../music/search/search_controller.rb | 41 ++ .../music/search/search_controller_test.rb | 9 + .../examples/music/search/search_helper.rb | 15 + .../examples/music/suggester.rb | 45 ++ .../examples/music/template.rb | 392 ++++++++++++++++++ .../assets/jquery-ui-1.10.4.custom.min.css | 7 + .../assets/jquery-ui-1.10.4.custom.min.js | 6 + 21 files changed, 1394 insertions(+) create mode 100644 elasticsearch-persistence/examples/music/album.rb create mode 100644 elasticsearch-persistence/examples/music/artist.rb create mode 100644 elasticsearch-persistence/examples/music/artists/_form.html.erb create mode 100644 elasticsearch-persistence/examples/music/artists/artists_controller.rb create mode 100644 elasticsearch-persistence/examples/music/artists/artists_controller_test.rb create mode 100644 elasticsearch-persistence/examples/music/artists/index.html.erb create mode 100644 elasticsearch-persistence/examples/music/artists/show.html.erb create mode 100644 elasticsearch-persistence/examples/music/assets/application.css create mode 100644 elasticsearch-persistence/examples/music/assets/autocomplete.css create mode 100644 elasticsearch-persistence/examples/music/assets/blank_cover.png create mode 100644 elasticsearch-persistence/examples/music/assets/form.css create mode 100644 elasticsearch-persistence/examples/music/index_manager.rb create mode 100644 elasticsearch-persistence/examples/music/search/index.html.erb create mode 100644 elasticsearch-persistence/examples/music/search/search_controller.rb create mode 100644 elasticsearch-persistence/examples/music/search/search_controller_test.rb create mode 100644 elasticsearch-persistence/examples/music/search/search_helper.rb create mode 100644 elasticsearch-persistence/examples/music/suggester.rb create mode 100644 elasticsearch-persistence/examples/music/template.rb create mode 100755 elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.css create mode 100755 elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.js diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index b82a9166e..148b6ec8b 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -606,6 +606,25 @@ The library provides a Rails ORM generator: rails generate scaffold Person name:String email:String birthday:Date --orm=elasticsearch ``` +#### Example application + +A fully working Ruby on Rails application can be generated with the following command: + +```bash +rails new music --force --skip --skip-bundle --skip-active-record --template https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/template.rb +``` + +The application demonstrates: + +* How to set up model attributes with custom mappings +* How to configure model relationships with Elasticsearch's parent/child +* How to configure models to use a common index, and create the index with proper mappings +* How to use Elasticsearch's completion suggester to drive auto-complete functionality +* How to use Elasticsearch-persisted model in Rails' views and forms +* How to write controller tests + +The source files for the application are available in the [`examples/music`](examples/music) folder. + ## License This software is licensed under the Apache 2 license, quoted below. diff --git a/elasticsearch-persistence/examples/music/album.rb b/elasticsearch-persistence/examples/music/album.rb new file mode 100644 index 000000000..124e643ed --- /dev/null +++ b/elasticsearch-persistence/examples/music/album.rb @@ -0,0 +1,34 @@ +class Meta + include Virtus.model + + attribute :rating + attribute :have + attribute :want + attribute :formats +end + +class Album + include Elasticsearch::Persistence::Model + + index_name [Rails.application.engine_name, Rails.env].join('-') + + mapping _parent: { type: 'artist' } do + indexes :suggest_title, type: 'completion', payloads: true + indexes :suggest_track, type: 'completion', payloads: true + end + + attribute :artist + attribute :artist_id, String, mapping: { index: 'not_analyzed' } + attribute :label, Hash, mapping: { type: 'object' } + + attribute :title + attribute :suggest_title, String, default: {}, mapping: { type: 'completion', payloads: true } + attribute :released, Date + attribute :notes + attribute :uri + + attribute :tracklist, Array, mapping: { type: 'object' } + + attribute :styles + attribute :meta, Meta, mapping: { type: 'object' } +end diff --git a/elasticsearch-persistence/examples/music/artist.rb b/elasticsearch-persistence/examples/music/artist.rb new file mode 100644 index 000000000..bcf123dc3 --- /dev/null +++ b/elasticsearch-persistence/examples/music/artist.rb @@ -0,0 +1,50 @@ +class Artist + include Elasticsearch::Persistence::Model + + index_name [Rails.application.engine_name, Rails.env].join('-') + + analyzed_and_raw = { fields: { + name: { type: 'string', analyzer: 'snowball' }, + raw: { type: 'string', analyzer: 'keyword' } + } } + + attribute :name, String, mapping: analyzed_and_raw + attribute :suggest_name, String, default: {}, mapping: { type: 'completion', payloads: true } + + attribute :profile + attribute :date, Date + + attribute :members, String, default: [], mapping: analyzed_and_raw + attribute :members_combined, String, default: [], mapping: { analyzer: 'snowball' } + attribute :suggest_member, String, default: {}, mapping: { type: 'completion', payloads: true } + + attribute :urls, String, default: [] + attribute :album_count, Integer, default: 0 + + validates :name, presence: true + + def albums + Album.search( + { query: { + has_parent: { + type: 'artist', + query: { + filtered: { + filter: { + ids: { values: [ self.id ] } + } + } + } + } + }, + sort: 'released', + size: 100 + }, + { type: 'album' } + ) + end + + def to_param + [id, name.parameterize].join('-') + end +end diff --git a/elasticsearch-persistence/examples/music/artists/_form.html.erb b/elasticsearch-persistence/examples/music/artists/_form.html.erb new file mode 100644 index 000000000..55273679c --- /dev/null +++ b/elasticsearch-persistence/examples/music/artists/_form.html.erb @@ -0,0 +1,8 @@ +<%= simple_form_for @artist do |f| %> + <%= f.input :name %> + <%= f.input :profile, as: :text %> + <%= f.input :date, as: :date %> + <%= f.input :members, hint: 'Separate names by comma', input_html: { value: f.object.members.join(', ') } %> + + <%= f.button :submit %> +<% end %> diff --git a/elasticsearch-persistence/examples/music/artists/artists_controller.rb b/elasticsearch-persistence/examples/music/artists/artists_controller.rb new file mode 100644 index 000000000..458c243f7 --- /dev/null +++ b/elasticsearch-persistence/examples/music/artists/artists_controller.rb @@ -0,0 +1,67 @@ +class ArtistsController < ApplicationController + before_action :set_artist, only: [:show, :edit, :update, :destroy] + + rescue_from Elasticsearch::Persistence::Repository::DocumentNotFound do + render file: "public/404.html", status: 404, layout: false + end + + def index + @artists = Artist.all sort: 'name.raw', _source: ['name', 'album_count'] + end + + def show + @albums = @artist.albums + end + + def new + @artist = Artist.new + end + + def edit + end + + def create + @artist = Artist.new(artist_params) + + respond_to do |format| + if @artist.save refresh: true + format.html { redirect_to @artist, notice: 'Artist was successfully created.' } + format.json { render :show, status: :created, location: @artist } + else + format.html { render :new } + format.json { render json: @artist.errors, status: :unprocessable_entity } + end + end + end + + def update + respond_to do |format| + if @artist.update(artist_params, refresh: true) + format.html { redirect_to @artist, notice: 'Artist was successfully updated.' } + format.json { render :show, status: :ok, location: @artist } + else + format.html { render :edit } + format.json { render json: @artist.errors, status: :unprocessable_entity } + end + end + end + + def destroy + @artist.destroy refresh: true + respond_to do |format| + format.html { redirect_to artists_url, notice: 'Artist was successfully destroyed.' } + format.json { head :no_content } + end + end + + private + def set_artist + @artist = Artist.find(params[:id].split('-').first) + end + + def artist_params + a = params.require(:artist) + a[:members] = a[:members].split(/,\s?/) unless a[:members].is_a?(Array) || a[:members].blank? + return a + end +end diff --git a/elasticsearch-persistence/examples/music/artists/artists_controller_test.rb b/elasticsearch-persistence/examples/music/artists/artists_controller_test.rb new file mode 100644 index 000000000..3307f5e47 --- /dev/null +++ b/elasticsearch-persistence/examples/music/artists/artists_controller_test.rb @@ -0,0 +1,53 @@ +require 'test_helper' + +class ArtistsControllerTest < ActionController::TestCase + setup do + IndexManager.create_index force: true + @artist = Artist.create(id: 1, name: 'TEST') + Artist.gateway.refresh_index! + end + + test "should get index" do + get :index + assert_response :success + assert_not_nil assigns(:artists) + end + + test "should get new" do + get :new + assert_response :success + end + + test "should create artist" do + assert_difference('Artist.count') do + post :create, artist: { name: @artist.name } + Artist.gateway.refresh_index! + end + + assert_redirected_to artist_path(assigns(:artist)) + end + + test "should show artist" do + get :show, id: @artist + assert_response :success + end + + test "should get edit" do + get :edit, id: @artist + assert_response :success + end + + test "should update artist" do + patch :update, id: @artist, artist: { name: @artist.name } + assert_redirected_to artist_path(assigns(:artist)) + end + + test "should destroy artist" do + assert_difference('Artist.count', -1) do + delete :destroy, id: @artist + Artist.gateway.refresh_index! + end + + assert_redirected_to artists_path + end +end diff --git a/elasticsearch-persistence/examples/music/artists/index.html.erb b/elasticsearch-persistence/examples/music/artists/index.html.erb new file mode 100644 index 000000000..c0c0c7304 --- /dev/null +++ b/elasticsearch-persistence/examples/music/artists/index.html.erb @@ -0,0 +1,57 @@ +
+

+ Artists + <%= button_to 'New Artist', new_artist_path, method: 'get', tabindex: 5 %> +

+
+ + + +
+ <% @artists.each do |artist| %> + <%= div_for artist, class: 'result clearfix' do %> +

+ <%= link_to artist do %> + <%= artist.name %> + <%= pluralize artist.album_count, 'album' %> + <% end %> +

+
+ <%= button_to 'Edit', edit_artist_path(artist), method: 'get' %> + <%= button_to 'Destroy', artist, method: :delete, data: { confirm: 'Are you sure?' } %> +
+ <% end %> + <% end %> +
+ +<% if @artists.empty? %> +
+

The search hasn't returned any results...

+
+<% end %> + + diff --git a/elasticsearch-persistence/examples/music/artists/show.html.erb b/elasticsearch-persistence/examples/music/artists/show.html.erb new file mode 100644 index 000000000..984f1f726 --- /dev/null +++ b/elasticsearch-persistence/examples/music/artists/show.html.erb @@ -0,0 +1,51 @@ +
+

+ <%= link_to "〈".html_safe, artists_path, title: "Back" %> + <%= @artist.name %> + <%= button_to 'Edit', edit_artist_path(@artist), method: 'get' %> +

+
+ +

<%= notice %>

+ +
+ <%= @artist.members.to_sentence last_word_connector: ' and ' %> | + <%= pluralize @albums.size, 'album' %> +

<%= @artist.profile %>

+
+ +
+ <% @albums.each do |album| %> + <%= div_for album, class: 'clearfix' do %> +

+ <%= album.title %> +
+ <%= album.meta.formats.join(', ') %> + <%= album.released %> +
+

+ +
+ <%= image_tag "http://ruby-demo-assets.s3.amazonaws.com/discogs/covers/#{album.id}.jpeg", width: '100px', class: 'cover' %> +
+ +
+ <% album.tracklist.in_groups_of(album.tracklist.size/2+1).each_with_index do |half, g| %> +
    start="<%= g < 1 ? 1 : album.tracklist.size/2+2 %>"> + <% half.compact.each_with_index do |track, i| %> +
  • + <%= g < 1 ? i+1 : i+(g*album.tracklist.size/2+2) %> + <%= track['title'] %> + <%= track['duration'] %> +
  • + <% end %> +
+ <% end %> +
+ <% end %> + + <% end %> + + + +
diff --git a/elasticsearch-persistence/examples/music/assets/application.css b/elasticsearch-persistence/examples/music/assets/application.css new file mode 100644 index 000000000..7be6447f4 --- /dev/null +++ b/elasticsearch-persistence/examples/music/assets/application.css @@ -0,0 +1,226 @@ +/* + *= require_tree . + *= require_self + *= require ui-lightness/jquery-ui-1.10.4.custom.min.css + */ + +.clearfix { + *zoom: 1; +} + +.clearfix:before, +.clearfix:after { + display: table; + line-height: 0; + content: ""; +} + +.clearfix:after { + clear: both; +} + +body { + font-family: 'Helvetica Neue', Helvetica, sans-serif !important; + margin: 2em 4em; +} + +header { + margin: 0; + padding: 0 0 1em 0; + border-bottom: 1px solid #666; +} + +header h1 { + color: #999; + font-weight: 100; + text-transform: uppercase; + margin: 0; padding: 0; +} + +header a { + color: #0b6aff; + text-decoration: none; +} + +header .back { + font-size: 100%; + margin: 0 0.5em 0 -0.5em; +} + +h1 form { + float: right; +} + +#searchbox { + border-bottom: 1px solid #666; +} + +#searchbox input { + color: #444; + font-size: 100%; + font-weight: 100; + border: none; + padding: 1em 0 1em 0; + width: 100%; +} + +#searchbox input:focus { + outline-width: 0; +} + +.actions form { + float: right; + position: relative; + top: 0.2em; +} + +.no-results { + font-weight: 200; + font-size: 200%; +} + +.result, +.artist { + padding: 1em 0 1em 0; + margin: 0; + border-bottom: 1px solid #999; +} + +.result:hover, +.artist:hover { + background: #f9f9f9; +} + +.result h2, +.artist h2 { + color: #444; + margin: 0; + padding: 0; +} + +.artist h2 { + float: left; +} + +.result h2 a, +.artist h2 a { + color: #444; +} + +.result h2 small, +.artist h2 small { + font-size: 70%; + font-weight: 100; + margin-left: 0.5em; +} + +.result h2 a, +.artist h2 a { + text-decoration: none; +} + +.result h2 a:hover name, +.artist h2 a:hover .name { + text-decoration: underline; +} + +.result .small { + font-size: 90%; + font-weight: 200; + padding: 0; + margin: 0.25em 0 0.25em 0; +} + +.result .small .label { + color: #999; + font-size: 80%; + min-width: 5em; + display: inline-block; +} + +.artist-info { + color: #5f5f5f; + text-transform: uppercase; + font-weight: 200; + border-bottom: 1px solid #666; + padding: 0 0 1em 0; + margin: 0 0 1em 0; +} + +.artist-profile { + color: #999; + font-size: 95%; + font-weight: 100; + text-transform: none; + padding: 0; + margin: 0.25em 0 0 0; +} + +.album { + margin: 0 0 4em 0; +} + +.album .cover { + float: left; + width: 150px; +} + +.album .cover img { + border: 1px solid rgba(0,0,0,0.15); + box-shadow: 0px 0px 1px 0px rgba(0,0,0,0.05); +} + +.album .content { + float: left; + margin-left: 25px; +} + +.album .content ul { + float: left; + margin: 0 2em 0 0; + padding: 0; + min-width: 18em; +} + +.album .content ul li { + line-height: 1.5em; + padding: 0.5em 0 0.5em 0; + border-bottom:1px solid #f8f8f8; + list-style: none; +} + +.album .content ul li .counter { + color: #999; + font-style: normal; + font-size: 80%; + font-weight: 100; + margin-right: 0.5em; +} + +.album h3 { + margin: 0; padding: 0; + border-bottom: 2px solid #e0e0e0; + padding: 0 0 0.5em 0; + margin: 0 0 1em 0; +} + +.album h3 .title { + text-transform: uppercase; + font-weight: 200; +} + +.album small { + color: #a3a3a3; + font-weight: 200; +} + +.album .info { + float: right; +} + +em[class^=hl] { + font-style: normal; + background: #e6efff; + padding: 0.15em 0.35em; + border-radius: 5px; +} \ No newline at end of file diff --git a/elasticsearch-persistence/examples/music/assets/autocomplete.css b/elasticsearch-persistence/examples/music/assets/autocomplete.css new file mode 100644 index 000000000..7f2340969 --- /dev/null +++ b/elasticsearch-persistence/examples/music/assets/autocomplete.css @@ -0,0 +1,48 @@ +.ui-autocomplete { + font-family: 'Helvetica Neue', Helvetica, sans-serif !important; + border: none !important; + border-radius: 0 !important; + background-color: #fff !important; + margin: 0 !important; + padding: 0 !important; + box-shadow: 0px 3px 3px 0px rgba(0,0,0,0.75); +} + +.ui-autocomplete-category { + color: #fff; + background: #222; + font-size: 90%; + font-weight: 300; + text-transform: uppercase; + margin: 0 !important; + padding: 0.25em 0.5em 0.25em 0.5em; +} + +.ui-autocomplete-item { + border-bottom: 1px solid #000; + margin: 0 !important; + padding: 0 !important; +} + +.ui-autocomplete-item:hover, +.ui-autocomplete-item:focus { + color: #fff !important; + background: #0b6aff !important; +} + +.ui-state-focus, +.ui-state-focus a, +.ui-state-active, +.ui-state-active a, +.ui-autocomplete-item:hover a { + color: #fff !important; + background: #0b6aff !important; + outline: none !important; + border: none !important; + border-radius: 0 !important; +} + +a.ui-state-focus, +a.ui-state-active { + margin: 0px !important; +} diff --git a/elasticsearch-persistence/examples/music/assets/blank_cover.png b/elasticsearch-persistence/examples/music/assets/blank_cover.png new file mode 100644 index 0000000000000000000000000000000000000000..8c513407a0456537db0fd720756e7ba7a5da1737 GIT binary patch literal 22778 zcmZ6yby$<{8#lbs($XCQN=h>YX$k4>7$UVXK$H*!1_&sPF$pQ@p1?*)ODNqiN0eb=R^F+6WMg_msq((eJ=+X*m)W`cK;xk0_L^|Z0H0-^ zpT^6fBX^I_|ArU(_o-&61{hEM%`R88#H{#v0wkw?cqs~e@Ik>K5Iq|Bj{@N8|NT0f z)Z23z&3J41BMSKO_V@UU_r@$}5R{A@;WbzLaA0aBmmBT2Y=DuW}M#Qes<1|HilQD5y^-X;Z+h=W=J3@J>k<3Isk6vk(o z!R1jGuKfEND?2;%y-Zz3F`%Q6lYO{@9tcdOhS{de--J?_&cyHq2wBRe4QSkTrU;dN zQDv=hqR8^?K{+kJyKr^z)^77BwW8XT@~>as<$vJrFiMH3a^*+%n5X>{iT`?eoyOa5 zQx^KSMZPbweQTT

z`ME4z(ElJaqKWQ=$1Xu8IjmaVoZzMb#8%+PL0PJYKqBjBjE zI~Ci#LV87f)Jf#OQu~RInOw}mnyY?wl%QZqG{|}e1^48cCUR<~o847=2OZ3>(>UVf z_bcO)W?f>XRwJXZQB(2cKdizoFc2|Mgr$FpG})z$h){e{<$-wCd(yxTe)LjA1FBLV zyH#wW;&V* zCNb>|><4{Fpf7&<^wkn*LH`{{paOdjF;JuNYs*}S4|*Mqq#9HI8kdwy^!e=;==Rpz zee;*bY!SNhY8^MA;AHKRU5d{5x=Z9HH;p>}eQ%0CupkkOsIJb$0^(ebFJUTj-BbE= zF&!`D@)u8@3|Q==o{1?(RdXLIqTz>gQZA*S1NbW}4oj%XuUd4q>3bQ^2hE37V}A5u zKcIia_10jBQrn{Qhq5UUzwOFN0O_3gP#KT4L&6nPC0LMuZLT4HALkn`X}<&1&ZMvO zbUgz71M=TAUdRjV-?&k$jAC*yRlhW%;F%;^FE)mNl}-0ma96ZmD|zjggo!=T;MP z;KiNuQrH1t{d(HCpT%7MrZsj8B)YjZN`4;tixH5zRFOU6*&HfD&YPODLy7J5#d}R& zFrZe)=C3CKtnHW|isi%|cO6(yDG1CEb8cXzpCypk`ci^3HM|nEoqQS}K^sQy%N>Oz zO}xvb!OSg)6yzDDH3dfIf2)T&YM!^thTGAD+yl&inTNxJMaW3REe73s_ogcwB5evO z`DhwqRW|zaN5B*|!V){<&vPXr|tf!UlX$p4u zp!^>7$iq-TT@M|JkvHOJ62eD*-=L+i>5sHeT!wFmS&$+bPV|0F_~eVKb#&cxfSu3Z zxq37J2B>_+MG-bXqUlTy?0RiS;16%?#Us$@3_$d63vv+S!+IVP@j~qy$-3KMwRcH1 z5I22bq*PsW?_S7YmOQBZcDU78)#N!>Cc`@IT4At!NYb5Nw$bP zu!JoT^4K#*@{QqbTKCSP*Hz#34sns4nd zrjxLL4;R};Z#@~L1yhk04ef@z2J6#<4u0j2>eNY#0MGoB_I;}tw1M?}7FkZW;H7{b z5B6y7FNaVSrIeul)(VZ>Fmg-v4hybxy1=?Ci)^Qd(H5j&sy~RAlC%CofMARARW6*L`Q3!N zsL;+IjKp44f$|ao9xtW-?G)ICIB&9&C?^8xRWP5P`kFJ7h(DT`Z8HK^b9Zv?o` zSobCD+cf1tpukO~l_f9{%k5F!wg)Nq2k-q?M_MWri5}3NN~N{&v(U>o5)INlX-rb?MNC7 z65KoTOL3{-C9*2=Og_)`ZUO`gDqKhT)!60(g0)1?oDeJBwWbH9VmZ5&JtP4p$~77l z?^}XEGX5`9wK_!Wg6&d?6~Q3emL(}i-40QH37PXMmvu^zlxmzt|HBm}aE088p07W8 z19;>MVOzQ_2z`e<;`^&(?^6tTC3hb`X@q<-W&u~E)+baegt&^4fz*~37p=k)kYeEA;MxClRz6St-Ldvs-YBu7G0mk(9N3E#(ckOD4?>ooG)n}T(vVcjc}1k|{i7y+ zEF)%cfgWv^Pq0#jYzL5nN_nPv7=iEBR)pT~lmKi?OHX$G>X%UxSuw~xB+$j~uz^6M zt*JYl6gIC;awH&TG$7C&2-~y(Big-i#=QN$JP3sDe&40cKvMb2i+|a;6!2PyH*dvi z0mNSO;^!s5Lk#i@-|?$iAJib4uJ%$QGh!eIeebHKJT{<5-`>i?F6jMRL#!b-^cIE9 z&j2dcYakHgnR{a#1G+To_zrZoj2ndXn(S6S1Yo+h8WVB`oLW%v$IxHC20hO70??g9 zP5bI1Kt1UXel*;<0Y$V7pt>!HL7-QSvJ3XtY?zCiR{v=OB)COh{1N|&!e*Z8V3rRM z$fF&3=CH?!NVmZ-EZ0Y11>K%}zZxN^<+i>@yBG*GPB1k8)R`zsUdh}YZVduG;&)1} z)94UYMSS%`!g)YQ11^eVCnBp?i!P$)Tf!jF6MySQaz?b?uWzD4fD`P$(YNAc&>?z# z2z=GLBFqH>ZMgY1vc3bn)sTPB>{gZ>zLj;AlT=F1`76)JfIvZbk^0Hg?#7Qfb+jK1Dur@e~-pEMzp_f!jC&}dJyuVXwQI`8?_Bx z1|`>xYaq1;n>?2YBC9s8+8+rHSpddy$}2fW9pAtR%$273!9)`PDrDF4!~hd|S3yWc zjsd`7lfuIMHJc}e)=Ipqb|j!Y3mzHLMSqc1Dj*2BYdH&ki}Snl`Fn)l>bm{2D_Kv9 zeYb9$7~k<0p}9;a?bTM=MP*k1MdOAl9D85hye?P%jD70R)!D&=S!IOf*yYrM{nwK` zUb;bOUi(am#6|wuHr?q_-)Vi(@=ty6fiVrC8~Zu)=khx9ndI{RaT0^YIUR+yTDTlX zz(gs;U@>gEK^32$Au2aV7sJgPzo1&1FY2E!dUcd$Zf;|jf%T=DhUvEQxzLymfdg07eE-4;d1n-;SY&=44i&V;G z(M)*D8FYs$a9sAycb0Bwr#S3+Y7U)vzNOMIMaX@VON=%-zY5^5P@vRWQ{VDd(m^M# zzB?nuMsSf=NGTTc&S02l0G(Fy*Gvy&zq0d7R^6V-NlV0B%KoL_Ry^4 zTv@oB_7fRjhrORsovqEq)AoP&G63sEej`gendlRir4$T*D@v;ou4nY)gMf|??zX{P zlHm%Cc0T-j@hO?hmhU~?B=YJzjZGpw^dE0Pdf0Z-qn?_-_JPU7Gp;75$sI2@M>JfV z9h<*Y`PHNB)-WHSE_P`FB2Inv27AtHKYehLB{spd!g|eGEiA>Q zqjl_zK!|t;EO^}OB8S4n_=%tMAJbKla&eciqo-_2Ihm+VYb%o2|NK!Y9RT3Ug50^37#Jy z@)@5EIv+L`Qyw!mxg1;?&sx~hTDa|Tuct^&i*5En08?DioqBB7WO=booU5;)wyO*L z@fi8MayRqY)ncuB#hOZnHp$J8)V~q-Dkyw-1605Uf;%9ZVL>Z3Z?=qyhPocN@ZK-z z+4A9x9{Uxadz4U8E4LH3>BS+P6Eao*G1n+Dfn6dw$X5>+(Sm9Ep^ag32XEta5O-g{ zUmlzju+gHjX|MZ1Kf~k~?nCMKxRX?@!6d9+?DUAoc(>O_wWW2}Q!sXiY&?IDFDDtH zkm+(Lh08IU#4@=YKMNzLw9Cpe;3sPFn?$9o%R`yH;(sf2G5dvch}b$f$RRkZpRNUl zs>hUx2HiJkhv4S!7xu=>Ms*n+=zkVMM>t*2XE=Bu!hznEXhNe+AjDA94^7$c!JC zeXt+=9Q>}VL}^x{-GHRH&+ktCaVf=Wkqr)&mHi~&RGSQ32#wMbH5OS-h$Xo zIXx}lZIt|Wk;+muJml-1F3(<{97k0x#KllLfUQcDsWqll z#3|!X}&Hyt$tf9HaP{~2pae#rco1b@4W8Ni%b%TBK1f~n+~r^mii z#P_4dNsHHA%zYT9H_I#quG1qQuHiRX7M*Hv*7`bsC*#sW&rD__zhxxRM6rCA?Q*dX z&n#KCSkeKABIwxD`d#GJcIrWJMOqE}KZR{FcDi4`t6X?VHi0ZEApOlTcF@u_6Ub#% zl~-gcf$(mA>^>0#&+A%3{~&fxF0j$!>}*c(izy$+aVD9aWNq1?DIr$-ov?1SvD;k{ zshZq5KLN{~9xRnz*cp)juv~aK?LS|&HXhOUFxX-;GL=22f52CGM8Kx9RYS;2{m;1B zJa_kMgWYd()X|tETzR(UwZ!3BlV0~vrFl)&J)+|jEbtl= zkxW`UY^UA|?EEEj(nC+fDJcU|QHT8IRnlEb+f2o2xlcj~&eLo~ui!e4%fiqiUk_q1T-lvWyWk{6 zxlU={9Job@kAVbt#b{`EAJt9eAZ2CBM)9 zfnVsC@?{SuqP-&ceWfq>A=6{`d>Z@ieBO&mOc7r9$^rj(dL~2RzL2oe`a|c+&&kk> zEQy&X*?EHkbeXgcPEH(#6P{7U#F+LppQvy}Kd2%%Z?RlaLH11K1Ci94>cBo<{UDFeC#2_zCKk1!%N`aP4DnT zZUv^%%$(JQyTi1J3vdDlhT}yUXEAY_AZZDP5D>JKAJv*{bx>eS!+AvR!f>_-*+Iimd$z>9L%$4j zapB)?=GLwvBZm0rWpSqx9fmgMfZTJ4x8 zC?{~#^Xy{v8GEN0di>W^kprD@^SgI$4e2%>6bfDQ_IBj1-sbkMRm}bxk>9GQD%tBs zVNIqIt$xxJ<}+|O-h$y9bOi&Jsr;$-2bF*?NqP+x zX~ffZ{HhB$@gTz^?U~IJOa=UI_yn`?fozRL(8wC=a5n`@oMOT6L)7;iVD}}xnjSdw zCtCO9WdMC*>lwwQ)~E|Y1+oaC8M1O37KF%dpf-K#i#upH{MPq#9k4=HUCi+YKPk2h z#U=QryIdCEDnJyvDLR#2h^^;Tn>toBy9exSaZF<>;dV3F>c@E<)pC~4)zEq1WYy87Bcd1?2Psc81y%OGP zgyY(-UBk@h>M%$=d_D?CV!|iJANuHF4j9kV24{*%Zk`MM$`iDZr2T2t5gmF|{AaiY zzVT3?Q_XbqKk+E5B&i|P5b6jG1hH`HU~S)LqG=keL-%pwjZq%;_uICC69W5lh<)`T zTn2Y3kt2RTa!eFP1!IJ9a@9l=HQjuaI!-38U1LJA)1%b@E|`z)jHw8}Q~GT2!`(V{ z(tNY0LwH<}FB=Z~UDIkE_Yy~GLZ9&{wU_G>YPWFtxXpQ8#R0=Q1eoh=5gWR`CF(e0 zh+{3LXL!2wCPJ^=t6Tr5^4B75LrnC|{`aW)T?fsjk6K{i=7#z7y~?I1IFPq_mdX6fl$Y`INjQ3{<+LX=S#O=O1B%cK=Qi{8cTSL*;`k7T znNNJJ;Um;tnPPyu5}E2Y{}LCEGZ08TFPHcfL+pQh%HXwI$M_s&&_URZlzNwsEI@vT zs+n{rnqR=W!$%W^5QE9?>!NV4aZFihH~Mxd=L&y=@mBV?*Us)Qc~0Ett%em;v7OIH z?J-m%A84QuvM{B2`#X~k^JbjH{=ddk4JmoiyWd;xFP+p)TP?U?_iuhwq`hy>BkoCl zvp`|3m^QOc^v)%j?n{8(w>T6o z9hZswfa6J(EqnA-9~oZ^#6NCORIo~ zk3x87=n4^vrJ=6*dQ+ZtFGe+G2^QbrIvNSvtFAJ?t48TC&*9^{oQEw@=s%+AzwY*S zK7kFL&U0VIE;ZEEZ*CHn_DXwR`oyou81$9X*A@08EwoOGGVAnUJ841pUPU1{T5dh? zDM*oENxoeMow z@U*i6z6YAqa1vP|8aQkc@7vWmRhBJ$|{0=amSleF^7COcavf4DNoK6tpB ze_k5&ht?KLL))DdljQdy87C^`5%A{wpnWD7AJ`@~IN?uWP0(JXCL^|2I<$Vl)2QqzM05=1Xk~ZdMQ7`C>1Vo-d8ZSGEux ze8D`JS7>PPd8s{d+VMe9LG7(oNAtTqia!j-vZdY05Jm!ArIeG;c=F~N!RCU#ws&4i z>ej1AY(PR;%Oe}Z%DTvuuZY-bEuj58@76BHmooXEIHugE0_T0cr4~F@>rk9mx36A5 zQ=eYAF;^rMRO($Q!G0PJ-pP>Vw4hCOg%!XmV0Ew-ST}6Ifcc?OHMQ-{AaKH%*kB9` zCM#3*iJufypDa&zwM+x!7^LkYQSSv+aaYNbN<#J=QTIF4%{|2{u-Jv$sZY@0J%-Uj z#3uCrR@~pWgblzZVDlYZgZFISKJ6H%EuA@N<%16lARyV_i^(tz$zcrcFnVDYh-E41 z9%mSj*$gBYPA@Esam2V>S>-=Q%(s7LFumK4 zGx#Z@^Xzm8^k)3Vtewqlb=cDd#^3DPHOLzY^XO&Hie20tswHW1SsD%@cJyoxIAeTu zMreL>tC42xZIt2$zfwF=OtC^GcDwjI=`q3qkxjbSKWi{AFUXGe`QD<2LZPs%*b0H& zBOv=EOoeg7Y50>lG4=v6@nZFE(h~oV4o#E%2zvPY&ESaLGokT1! zc7~AhBIVjk{9%N-bbpuEV)*SN-nnq`ePj_Wez`27&@j6~(N=L*BV-X(qM4`oQ0XJ) z-g&Y2RbcCW*0m*1_j@eFbE?qIm3UL}N9!Yr&c$dv^4%9E{xfI(I%LtXB~vw9ljs-o zWpnI3FQuIK2Pd4=?UL_wyl0yS!|c0~ zASWfZhC1y&AAOVbEO8wYlF*pZom!qfP(>h{AT=vnTrPNoeH%O>$3pedKmqo{s@PCU z<4^^iiF2NcOB#eSE$LOAkt#P~%ZyEy(O)@Z`dvCQ14!^@Ys;fxvx4j_PM<}s zMe4<0Q={JAGRoOJift5S`bDrFen!4b(S@a#)l(}-*$*u7nV01bC z&FCJl{5X{w6Gi}UQBB?|^@XmPK3YE&CuW(5*0rpgLmQV+g&)Fx@i^=-ijymj%rRo0 zcf=;eA#AH~yi1oE5r~9o&qdCtn=%`x$b35c07w+(`za+*fZl%1g5DCrxL;iXK|Lx&)(sm>mPx=7pG=V&L7npk&y=EI z`Pyf7eL^9@HpPT<&cqqbKFn{)o8Q)T%&rDob!{}Gv-yCJc}hIp6=mN6Tlh7F#8}Un z+nkir(D|A%w@}*JQPd!~RorJ)#Td63Cn2RNCjU_|S6xR3rfMo&^W#X0`PeY-jN_8%JZR5Ze3PN@mpYGcGK_HumkAq-Von6cHP zAqnjonU5yF^XVC+!ul1xR8?T-JTAIvr?F!Xg8(De=I8+w(NdMRgjwsMmMvI1GMC3n zU@_tl&Rt2(J008n`sT4Xv2F8SUj7$yM5wHrhrTS@`Fp?NwMdIXl_<=zL1Tdk+8#I2 zD`8uuRXs7uC~7ZTXp}IkT97?Z7XKxBS~AJ`Jw_WL==(XvB-fNGj3Sl6Emv%U+9uJ7 zUp|3KV#XA9t1~i8Zt>xvD8hL{8e*YY&X0e#Rzkg8tnj)-;LTJD*RD$`W=R)rOkw`j(FvYMrFR+r^i@87M;MR35US7)$qTY@o*C zg)nP&Pn_6qjxAO%zl8a|K%Zs6H!EVRRw?^KD2+GJoPK<^{}anqqVT$??P1^Wt^wl> z<(6-1B;xDeqOy1@W4JSS-iY|p75{L`F4K`86nf;9nc7;*fW=_sw;&4%G6swvk>CoW z4h@d;Lns$Iws;<&>C>RoBWQePfAJEQ4)0gw(O`+wx^@4bmV%xdQP==m^dJMq1fCQ( zozl*B6@VF7RUh|);R&0aa_9X^3wMWwc=`3=wJI*NUHsDaKAC=ZaK(|P;)VBFr?pZB zGj2kqLt>s1SyjkmBWS>4q{egpl2fS_sYMM`s_^s7JA(A+$)GQEuq|`sTZe4z4n^J8 z8(#aV;S%1r%U3R)QZ_qDT4Cp$(F^^5SZkS{&+RLNw`3=SPLw}k&zSf~S%)+xNGdDJ ztOcv46yYJMRqU?|X>P0b-2B*dlqWG_PE+^9ULnzt5zE%2p=ie5*&H9DHO^u;WE$6h zTJa@pIJRP@EcTpIAR$-bv+bSG4oW-M|X ziGAIs68#bVAxbaAiywRBwP}XwE_2sdx{^?NJ$Oz6uaP(_wH7QJE1e(rQ<9q5cDCp4 zveW=_nQrCzCj9ujXPx#rvSz#=+wO4 z)6PQb1$>I1pGB7oe;`kc5CNMXg!PXSM7HcF`#>U%GGCEBhkOh(TvhEH&5r3xiBO>x zQ_kwf2dpMGDFHPu<$kfqDfw`Al=Jq|BW{-Q#{qx`K)S2zP%-y3!R+iJa`UN@&jpFQ z|M*G6xXD5);p0W{hs^ZkQ+k5Xmic5uX$siPj~O`H%r&j!X)GLHwq!!yy{MVvJmi=% z2|k|p&x8Sk8UE3;im_44A)bCWdF%D0w?fxpp6d6e?)m7t5Cw0CS5bA#1zl5+(pQ_M zlKT@cT*s>~CRtX)Ra3u&<+E z5*D)n{PltL_nLM~RMNaVV6!EIk(2p;AK%%TRxB&(H z{YS)B1uNFhbF_YJISSIuJMRyLr%aq&=E&d5CiPMG_6&XDEn&N(pqI+X|3nqg^OFZdF-< z>9rS9if*#oE0o4w(?IRGPR^8$m@X5qi$Hhew^x%%dW_Sy4o{U)Zw=HTvn6G*TTKtI zepJL}FW&m7;{JU}P%aV5?^TtWR!sZPIGcvVSL&qAyJTF{6d&j{j(yC`$)^l74of`c zAlwB;lD;2iKp&Y!=)P8p7!3?g+xgAbxk^UWb|qJq@~Vs2F&hbp9bg->qBl85(>)>7 z*!l3dtLaT&>8nLkURmm};b2}8!AHd+U15{F3{*Bp5B#5}#HxVdW+gi`s4TH$6VjHo z)AUcR91g&^xa;EUnieTt{3EW}yf^o6Sc1A&M&6KCj{cm~0Dkr?z7BNEF_@=p?I!VX zci`xJj{r}iI@&@LK+5ECDIL)P13LZ2{fG6lles>sezz7~AJ&+VQsB`>ri?8GZQ5bU zr#V2wpw{94Gk#{7j5P~z?`Li1Z5b%xf-;DpycLZk z!FXJ=L2S{zBCRZSS8B{0b4~>M1f||UN~0Xf#ODFcy;>maGq`@kw91y~KP*&j9CwbJ zw9-10LM-ST&FO5gHIL`_n%V%#2*&?MwU&GXYm%a(gd)%iWUln!-+pB^Wq;dBdMQ|5 z%TN3dQtP||i_V(b=$k$8zij^izj zEE{JQ4}jK27pv_AZRhB}4@s#0N?kHNC?OwAT%!+UM+1UF1zR%ieAw85A_UU*SPv0L2^yfY_R{W48U)%9~sGg36hOG=L@#V|x8`^uI ziJ3gmg^c+N5XAiL?@jiO=Ky_8!4h9i?3HIRVHV)wF56EnQkT;*_)lsb8bXT7^ym(G zb?4}EGI1vwork@-H|{bfW{J25vRk4PvbY6obVqu{!3oygX`UTsSH)drM+*khtpEk% zt9DKrI8uWA<95vsjV{GH5^+B1%k$3svkI#khc|Z!W>E1bCEIZ4qD9H2ks@; zEAy{~jUXx2eSlQdLRxxf2i+LHM)(HQ@E6B_J1uPAP}M4J^XLFni&50?AbJg)pv}j3 zMukzWAiTkKBhTemZ+t(Ea({xC{X-N%-tN24Sq@mhrhhdvODG#`PAWd%*#x10%WI>H zpKx0uzw=q52ePtv;OFiIZFEEoasXgTFaVUNX6B>A$GntR*WZgc7&CwW^0h%uxu1pR z|2`A$IPwb33;s_?w_F~AVA)J@fuewod1Is~GT^1?tTI7<3pj;oR~84VBh28lKgHQlm@rBPo?Q%s!;RQ?%3iGMuY|68KlU>m&$ z;(Sr*#WRSwYankCM>A>7iT#x>?vQlK(lD3}T-T9+SELaLHTlL5HH2@{I=^)yHuvGYeP{YpnIKcr-pgLPX=1DMCi~i7C8LPkuYJpvoI{ha2-iyG2VWGC}@7> zi#nAqJ&@igskV<}&IoDU%~62w$I7Qq)6KG0SnSGHwr1){B*s?&zC!meX0t8|mIDrS zvwNt{ai$lBttT^W+U3*tRuXR$EA*+NyfpXn`$>b((Jgy$2imLla z^B85E;MFC#;Nd%bRuf}I^vy30OGk_BSjC`EfX(libLLTKR+V4(= z4sIKTzS$si9Q4D?oQ#PXJP3Pv`Z{EmxT5=z&z}cfpPI0^kk;sPE4-!8m#km0^2_;` zrAo;n4$7Z>y}8n_M9S#)C!)g>$N(RlQo%qYVIe@{=EMtKhmv{e%N;M?T003;w&1#q zeR!vROpi-@MN881F{LbNCF&Jx!#`000E)`+9B)d8lo|^fmdy9TKY0gvvaogPjT^>7 z#^M>O56OPfmA;xQqce48sA)C(H)BYUm<5kcjW;S;%TWjru9!APZ+d=gbMsw-n9?-S zT8Bulu*No!c@zV2#Y@%i&)7~`_^!bCJA_wq-T~Mk+v$&!F{Hb)*T$JvMr>2w!Bs!~ zfR(!hZYizOButfff4ZdHybDBkyFJDkTl9_{54o+z>v`}ED>U-!FE5PR5H>;Y96Dfs zk<6-S>8O~GB$YE|#)X~AUOoW}CAl}e)3u|!a~T6$C9c?p8#GnArj%pl{a=~p<^$yP z2j>zMt`h(T;ONxMs^MUdemNJtdKSFLXWR4yzampH7R;^wWA?R)Xuv141U!62Zpzf> z9XLgJW-9U>N~!77h_Zvouglhb{8}osgCmzG@qYoF=7e{~kQ~2RzJSCke^|ilA?*p8 zV*%#l*{t)1(%7aKVh~Gsu&b2mme{1n7qEWVt5YUEq5tAOah7HQ`FZd_V=Wr3R#f;i z>&SgX*9yC`<$kswCGpuiRf@p;xcxf0IG+|U9&iP%6&kP$!viXhwA|BxQbO3+(qTkr z*Res@yJGhUxPBH+cj;pm&tby+R9vYf*zD~$ifQRBW-lOevG*Z&KbGUr@}5gok-%Oe zX15tOUsOoWrQ~uJlbWw!-Cy3A@hIIh;q5x)GYcFOmow}LNea=*R9KYDSM1AtRX-aB z>`3jowdL$h-`HCBEc^1>q%}E0w zU&7I#7Vu<%x~sS#M9T3IlbF~U;#ntI^jT?qM%O!ytvy6j3{Lm`DH-AVmKa(79`uQ6 z;Wj2UhJnr|78z6D)UzACGW#%y$SSrTGNGv;crN>s)rh&8$;g?)h1rW-{P+lJv*2?^ z&C(!HfDAAN5)fu36Y{N2BN7%#A2Yq=wu*Zm9#?ufPVaS36Ey~-n+@_URwmJro;Uwx z;7owjlSY_!Tz9tVZzMsKkWi0khD65txN<7zV>51kAW!P@6vY1&N2>aw%Yhp66`O{L z%(&BGf%TRE8GtUO7Mvl!-k)3K?vyrJTjCdTF0DKnJv~~-_3=%kJVp^Fso%M}xmUJQ z_u*BCD86}zN4o#ZABGQ2NgcAQZ3W8r%ON_(!Eq&}u7wXYFFEbdifoN*lIIVPwM5;+ zk7D`u{V7;1X|2w@|7KjQ?0MhqFG^D;kcR&}%ESvyTBDDdW&FN5tf&68C7!NkUp<%o790*{hwz6|2TzTIeERy2gBF5c@ChY)nqvRuQ8CI!V zD09lsNyGXyum%S1Wua5};GYiG)_POiFU;q3>bG-!BknZpt0Kniqdo}T(W$1R_(@y- zpYVwKJF4qO0RJz*rvFQ9m<%`dxjD?yxEi4n%n0z0}gnM64%n@rMzO%AND=!h@0y#ac!rdY~|TKg5gLK^l>AFOdWpP zyk$1I0b6%>xqUo)x1Cz^M?aFKzFNG&ux?K7qO&8Q@+#o8y)6A)sXULlYkHF_PL$xm z@k+^i01>O1@dO{S9oil`I!T}k}9icivfeMd&Pkq+L zN4%x>9>rqVuIg9gcD&=9d2@C)&vmFtGynN=Y`VbFA1(XCXI}!fFP>fY%3ZK(yepQM zH^ZdVh%HZUEKam)*LXYU6+EV}?jO~6XVqC3ypCQiF(+b?$(O=R`m`V|Yi1TxuGz$x z(y8BcD!^PPvgT@bYKRoWR5@?g6kRWi&j0s57-tDfqVH8Wr-Fn}$%QK|7(U}pV^i6! zu>pq5#k(olTqwgo#ouMS9yKiC%I|5YK5#OVw=8v8f4a|zU%oRV=bFCM61dH~dH#iX zCD)NV{JD^&)4AV|*CC53PCa2nCez4(x2M`Dref^^o1|thNr`62%iCBKeM}zYu$t<} zhui3zOhU!14wKV;IyJ%QE-r(&&)gS3?N8bor>m78eo?HCXfmxVtqyuMIBu2|57sjP zDo2h8CxmktsKB~^_Oa~BHH8P>uZDzKYH4ge6bvK2&#-UZs|~(SrS`WJQIXX5GqwD` z@{PjdX?|#aFj${6WsBy<{Mc4MHR2vy%?QZkHf$cY2E)Vtz|OqIn5M574FP8w<6y~*M!>{PE7A_rrg{syQjFo5;vdvl9-(}Z>w7B&MBaY|@otb79n z3dklkHDau>z~&WI6+?A!rH$|P8LPn^g)G%v`Il)JSr4EqIvOzH#dDtNTC`DQdg=&U z1kRX-*1_I7i+lrGo(Kd!E}QAp0gZq`avsDAI62w*8-E7H>mzJ{p>~tKQ`1u$k$2N! zA{KHZ&RpqJ&73_4XRBXA*GpZ=OdK9_T4hJB6D1kH4wu$h$T)>>fT~{VWFQ_?V{A*k~N*EvJMc zr)SSHOvUqFI?M-9&0pZe8upm8;va@P$uV;M{-E9Q+T(;rN)nHsr&_{eU6X?==!Lm3 zwwT4!*{(ng0-BtYeRMUe`y0{;gsj>L&NDM3>C+km;cH%Y6Oe?4t3(E1HJs2KAE zM7Li?ZJ(XI=^bD*oBS;6RH#i*2VJl8CF?nBI%@-kf_QMsQ=|Ma1}B$tdSMcbW&6)} z&a*zi*uS9@2A{4hIl%fkKhHWoH*S(NuqhZY<8HZBtwGD_`ymi{&-rp(C9WEW#?|8F zq;zE6GJ$gGljpqYw{{LHpL|oyP7dXmw#d&(d{#l1LEJ^fRu^PZOk>hO%XfmSR>^wE_mpEFQtE>my+IR(|>B-_}Yv%MU7KrWs=^M_;k z+Qx$kN;p2#iLH@VX`HN3zV))z;0NgLDy|TbWlCWUt83elk$>5g_4mUM;h5#giRY5( z5JOqd$nEKA6uy$x?s7+CSd`>I9bt+vLzp8hfL8&n5Ul-?84Wpl0?>=sd!*0MJUK7U zTHEZ^Drz6*rV|wJApe^FpHlqtpZGF$;2n$?I~=K;`%g7%mK(Aa?IECN^ffJJS=)rO z0TW?+5m*!>lHX(i0yZxjHnPzO84_O#@le82eR+T=)Erwvz;m~F28462)y@6%H z3cM*2xgH311Mhc*WM$dAKYv0`xDoK)!coBE?AaH|?L3e~EqIIdLew z*RIYdvWH7vkO-l(hj`NT18r0@bHutS|N26dC_U;Z)xYq z;b(P2xPtnr`+(#aUSB z85y4~%p7J9bA`eGHDth|)o7J{-4|MwE0Au~z#I{YN^a`7Bd=!o$I=ZSP!-U#B7p(g zH^4A`nE5Wa7c2dbaTbg~AH$HiEr~c#1l8YbYjJRU zxO6z2Yc=s)kwZq`g$U)Rz<+Sozq%yuKs66JT%5ob%if-=>*BHPcrocd88{g`i8@_j z?Px#!%TDFfH>Q_`u;}-r;wolw&GGy6^i4CZv)&NI7`fjbfQG>=zM>&2|L(yK_3$(kg}H*}f7DvsT}>eT)#~X)-hATm^YB0a)_VFRLU% zEe(Zb?p=@ZL}X=c0F6n0b!@YoI^BzEM^3eIF?(ux*gS=mZp&`W|G!rrUk)vM5gM$Y ze)IlgjpXG^f>aeHtwGck;F-$yWV}u-6+HG3+W2)dts@#;~9*2Z6XRL026y5#xz*=UsKj0$NHP+xxyCxxCJ>l9$(Nv zML2Ep5f>X6;HrrJHYQ&KcSb-%#bP7j0tQqckvVcAJ_5{c=W)-X_87GadYbn>x2Rx^ zt=7M-b`g=^FOYd!h0jc>x!MV>9O%{ zw$BVV`FJfPyVNO&DqP-;=eLCtK$-{kh(K8oq`2VUlqd{n!eVFd08Ug`zL&r$Duw41L1UA#iY zfX#Vr;@KP=uq=fr-}awWkx>YlPz5yNaicwA4(fZ+yTe3bi5B> zzYx{Gu#eG@{9xZa?N%Ob5C_l-syMv>}WNp=tIO6gtV46Ii@h>aI2Z>Y8S_W=HdWEU8Jy{VBNNK ziq@{QM4+n60Oc~|8_>^4XC6&;6llpxL>niMV-r zc?|Z>XL}3&Q$BC5Zs{)_0|x0U#XFvK?S&xgyR(;#*$%s6R;x2#?$eAv9_j3V7BbeU zfh6Mb@h(J*>4TNwc4wYT6S{?4m1x6$J2{K9vxXNudxk?L;`$x#9zFeQH7V4ya|V># zBWi-<-eO$W>brJ7VKB>0eV@kLN%fnXmoW)$A}$taVu5kK#diZH9plkzKMrB9Ok;`Y zDvj1^m-Zvl`Ce`OapE@lg32zfnrl0I#R(&`%8-LHR-;Oai-jY|qcE%%DlQ}OEJk?* z29gUtD3EocuTReno*+zRoyUGBj>09Xd*>BLf_?~ML1cPt5fW85mL*u!Ie5ZL=IQyQ ze|HQmFHR|{54t?jL8=Nmmdn7u!(VpFfIGy=;#S%oBhSbEiL&-T+iV>y;d$^~v>Q6o zSOTR1VwZf$618=1|>K>+4!z3Ku3-bG}V9D7rb?3al z&kj-KKca8+#}!FgJ6voGN5^>Lnf=@Dm&=pOt6ti@&?%OS_(rfr&4=ZRr#zXYigp_q zdtXKkD|%I2kqPk8e;|;Upm>jH#FW*C;|S0^{+wJ zPb(3Jps=nO20wA)%GjVXn=xBhyd1$uCk3tq*IItf;{Uf~g6Zx;!6GpoeXT#Dh_ANE zW47qLwMiw(JBp*oNW(B$Ar+gF!y#GG6o2$PNuAr}r@jANb>=Df_inMqu0tZIBpH=~ zMVS*5cnrQDatn6~*h;U>&g}99pHeU~7M2Y<%GaMpm#d&pC zr+nxlC%kZfpZD8q6NhFCWer60#L?k?ff&p#hg7t>IM0%07Ka30A~1(&A-9V;faljJ zS+qu(a~9yd{K@g`i3*D&HgO!1rX6I`{!29dfOt(sB?lXS=|=3wZ>wv4Y`fS4rNZ&< z>wVLG@xy$nbL4<-xinuJTPt(Vvv-(H;qQMYf7C+Wo8m2|F6Puw4YFxMSo}SO#^Ffq-N*Y@)XnZ%9l|j#KDs(Chm{t+QfJ~VU=qHx#Xx4j8lC%J>}Jy zUcMQ+oFZ}Hg_j*IM~)sjwR?Y6LC9$BMstPoE>Y7&1n>u)sIZHt*sa8Tr7yZ+DkzAW zvv&PLR}}cFUn|GhF=d+9h5Fu7kY9|dn#N4RMJZ9(fI_pfenI+Dn;3q7_=cd^06~ub zyC6EBD|FNPpmx$(S<3liCU8LjVuym5F#IY1QCMFdwU0oL|vb5bFBKQ$}TW)=-lLWf=hm9@F3RzSh49PehEY zE7W`4lCt;Q+zodIYv&!(5o|@AhRv>}5nasZrbGP0^S!qIp+8;ES@>Fm!{6zCtp@+!wB-Ih!#H1W#r`>g}v<*B$yaYkpA*a&vG4|22jq>26J zuBTk(`Q`78CDpj2hu;g=*3}Y@6tD-WbJSf-D0+jhO?f9XY#wu;@ay!^hV?!3_~~EE z`7wn#k0N`lWqdw8DmAw@$q8a6l4Z5keWSzNVt<4apPEhsEhz-eeEq{y&Em53!U?V4 zu8S#WSj0c_(7aVG)2*=Tc!c#7Pm}y<5mcP>X@gtXY_xw(db!zNICtC*`wX?GaN1Hp zh(Sa6scWWTS&(FCl^?k?cbb|IF(2f9SB0>0Rq}4_QPQBzi13Q8{NbK(nDtLIG5tkz zx|||XRl9AAr*35A!_1f!g{F_gGWE-pW08c&LfsVkTEK}^O@qt&=`IKZnB=(x%F~q) zJsTp$(#M!l`%rkej+<3K@OKQ=xG3-2ku7hKYo>VHnz|2Z?f2By_9ZPfABEjdOf1`I z`=h)e=THQj*~P`VBN_O+b(loz{Li0G0tJcdw2w6I{`RLa`&{GL-rgh~z##K4GyMH6 zstOh<-+&t4{y8k8--`cO>M$vGzl72`y;eKM=E{3TsP2y)S4-oZ;)r+`g?W$ed4c#k}(I;Dk)rih}wT09}rLwJ7gJHb**H7YA zV){!J#@UqRzeFb8lCVr`O1M3NVyJ*I)Tk7{fs|KB+_R8@O#IK>53!PHyTa-;?6E(c zDPiRVL8zvrwd9D@j$x0*MR$0{Q?nAp7Dv%#2trJ~QVz@adckbf8pECS8V1#M+zP`6 zi*hO0xg@Tltvo>A24q9^2i)6`95?@0Xf_CUTM53rgXegg*1aOHFnU24f;#l6+tOT1 zHnw57tUL0;GrP1`p~6F14t87rL8-!u0CKl#~;u@P@O-wFLWM3 zNib>=y#_&^x9~PJsBD~Ri?$~O{Z657Xx29Jyj^6Wpi|WTRD$CEE^le+jVt=bOtoC@ z*P7T?01?pB>M1Qo#eOoPf-xeVq2mn?Hh z20#D%EtmrRdk1?AJr-?O+(7u)xXq;$)(VNjyPXr!?CU*Qs0m>xOy zgRGl6GgMbq5QcqN+Y#K7`_y(ZnG_JRaI8CQ2LSQG0!5LgCS>2|PXZHx48yRVbK2a8 zujL~)PxLE7&^tde%K1cI8%$5BIcO*@fbBGd6POLhVjkQwdx1E_3(FtM8v|1wvaBp0lNi^3R%d5O$8nU9 zR9prsVG!=4vgyj6iJ?@-CqKVg|5YGR*jc>)G7L$|!X zEymD8b(7vJ`*Bju`8FJM1zy;`5;YzOdh~VKNAr|WO zQzhA3@|HFqL3dA^lT@8kC63*nrrwZJJ_3n?`|+ibf0O_)Bf;b`ye>=DRE5-i3d6AD z!^dqmELx~cx0Rr@r;6i3Eg8NvW{A|XDH{;lNY<8(st(wbzvWUf|2ZgKDl57uT9^dO zm`qhFGbC1<)1bmmSI`mACu0R(7M~rrVW2U~F8#e?5_Xvd22EvR`G;jsl}t~^trZuf zW`Mzr46E1OyfSq}9Jh0Z)YsfYvG6aK=?bj;TDRvkAt++8XFT-;jTr_gWW2ycQP*(! z&}?f0G#2G4CLMu?Rv z3HX@_k0>=W1_-D8M0q4%9r!dqhS$%5wQG6zFIk0|uAre?mbUhoFHu-SlR2)^6>!e- z*A};Y0XC9dr?>PjsvH@DGxbw!pZljtIE4wSQx;&Np6TQTD9+7*eMo7N2Kv19dfXfd zB$L9y^*bbiEHFbp1^BWG`q=a7FeBqTy+t7rcijdRr?&(VJJXQVGpdf}+icMFA7938 z$SvHk{M&NuhsdyJ@-V1%&!@b(WI6(8dgIBPi8mx=ZLl>t*HPtVt0WOve)pfi&sV{G zWb&KID)_)6Msr5*`!I%Uv{3eD;`lZ{4NvEdaEz+H{QvQxguD}=WhKb6MhCR`&Y}O$ z6aD!LE--M`tQxDwQ-N3HJEz-JOWz6-vrPP)mhx2f;)c} zFkI@*LIG{@x`3a_J-N9f5C|T}pQy2KcXDxqjCRB4e}t(D1IB5R$?|Xrm1n1X9A*n_`zZp7iZ zvCko0Iyf1;Q&g?=^zDE1f`>e-*dVS}o!PIoZhrnRaQKlYZ6zFn5SMnn8!_Q?aQ(n}63syG}Et0Dcs)LJLApcG9ND*kl21NGzMdeJ63;j~;4y){|}+ z6blZMzMU!`bb8km41M~7P0gC*F;(SXWxwC#Bl(dIxA;w3q*?=v(MWYIACy5wob`o7 z0znqZ0~SGm<^5(N$B9B8mwLPURsVaJU@^a%)%WEUD7LCuQ+QADINE08*V+ZsaT8#h zbu6N0O$Oh-$I+L6Usk>E_XRj&Yi_f8gOl%o6PF?vRCSe^&2KMMBT=S&2lRiT3}-Jr&jSV6GR1OCbasU26J zb3vqUN}ts_w8VgsMLbJ+1NUfzazEdiWf{~x1y;K+Pua~pnLe%3=F)JcnX(%IvaY$v z=`FC<*k4_Tsmr^Cs;-<3o8km3p>5%@`dlj5vE2(0aGcnS8$L0o`z%6m_?H4@Q^la7 z!JgL-ntFuTa1Z9*Rm9=&9V#yE3@5|i(TIhA2vc3UaSXF{3=^b25T~Tw{G;X}BlTju z>Y`V=AppEuqIjchi&x9SCVU|-vc>F|L&*($kegTW5uX+VS8F`v>V=6kr!oijW`=1m zf)x`14|#h?`xW+Ds?g55>uj>B;4T6jwc!Q3=7E49A5K7dJMiMh4Y#C|pu|ly=<*DV zyekO}TCfEEV9>NZ#sVvEEo&$$Kwf_3*w}C-(BL~k-dH@rF2eeQFaBQ;E23M|WGSxT za!s_6AjIi;ZbLlyfnWWx(KljjsS0m12KryzL5pp749NIQIcojuK&sO8fd=q*G(m>N zgg=3nPV)DDDsc*Y3yiXVE?iep4c~pv9eBN)#8W%%^xq(4!tMl?JzAazA{Q&JH9^b!Kmo zd^YFa%E*`@u>Wb|x-X|k1z#=oXLA6iT+8!ciu((z_wz@Slu%4Y&hNY8)lLsx*_#N# z(RaM!sdg=!mUyYgHX&z9!__v|Y{mknowfRU4Xt#sY^ps>UP#@{hQ)n(z3YT+#&O3sr{4JHm%(~-nWX*h)h*H3 z(p%1!!mRE~pSrei-N0ePVd2avS_mtZyEaOqpK(GRgB~g7e-qO9|Y`}8XO&o^-buR6J z6skXZVevB?jaeO+sHgpbC#|u|;G@X3p6AS#5-4_rvs9?K!I28u{3~P1KQNYo>Q3_k z*YFabJKJt1Dq^3aPGzT_{NUzAV?}2YTDK_oI*X%FNo&B>_`FyvExsX6hah+FnEae+ zhD(ZATV(nquc1kp3vTJS^m&DeGPADhQkDn+f{HwrbN4f0v|zSo$mN{u;uqByH&^jU zJOmerdc6Rn#=`WeD)TbD{{W-Le8s7PU;ojdMm&Y5KBF+mml#p&X7?Q*Ml(d!4zLn& zH%|+XR@Rq{@i=A@O{C#EdBA{zN@f7g zn$67~#(A`jAP|g7Ph0n3o14H_1$CJ0Q~EedCAvbi)(hIxnbv=aiacpHtW$~h_nE~& z20hM|!n&7b)MTx|Ln>k(*ITH%nAyg;Nm^UT1Vf9#6OeiEv=K0O`D(tl%QM@LnJp9G zN7H|L$D8{@YsYkZmpq5Ik=r)x_Vb&o)Ev!R8nB6=8O{1MA(}*;O1VE-95U`nm7EQU zR(zUwU%dzSIDKXV#aZ)YFV~H^`-~=p9MuGtZl;{%Jo+|uQ0{nyR+ z$KLBn&{eP~ICj=5`5y_S%L@5(8jKdE&eYN#_Zi`1m;3>4yo5g)9vt9vyWJ%Y@ZTOf j9>V{%UsUT6uN`!2(~)rDUCR#m6LdrS*0mx{yQlvH2d6cR literal 0 HcmV?d00001 diff --git a/elasticsearch-persistence/examples/music/assets/form.css b/elasticsearch-persistence/examples/music/assets/form.css new file mode 100644 index 000000000..3a937e310 --- /dev/null +++ b/elasticsearch-persistence/examples/music/assets/form.css @@ -0,0 +1,113 @@ +/* Based on https://github.com/plataformatec/simple_form/wiki/CSS-for-simple_form */ + +body.edit h1, +body.new h1 { + color: #999; + font-size: 100%; + text-transform: uppercase; + margin: 0 0 1em 5.5em; +} + +body.edit a[href^="/artists"], +body.new a[href^="/artists"], +body.edit a[href^="/music/artists"], +body.new a[href^="/music/artists"] { + color: #222; + background: #ccc; + text-decoration: none; + border-radius: 0.3em; + padding: 0.25em 0.5em; + margin: 2em 0 0 5.5em; + display: inline-block; +} + +body.edit a[href^="/artists"]:hover, +body.new a[href^="/artists"]:hover, +body.edit a[href^="/music/artists"]:hover, +body.new a[href^="/music/artists"]:hover { + color: #fff; + background: #333; +} + +body.edit a[href^="/artists"]:last-child, +body.new a[href^="/artists"]:last-child, +body.edit a[href^="/music/artists"]:last-child, +body.new a[href^="/music/artists"]:last-child { + margin-left: 0; +} + +.simple_form div.input { + margin-bottom: 1em; + clear: both; +} + +.simple_form label { + color: #878787; + font-size: 80%; + text-transform: uppercase; + font-weight: 200; + float: left; + width: 5em; + text-align: right; + margin: 0.25em 1em; +} + +div.boolean, .simple_form input[type='submit'] { + margin-left: 8.5em; +} + +.field_with_errors input { + border: 2px solid #c70008 !important; +} + +.simple_form .error { + color: #fff !important; + background: #c70008; + font-weight: bold; + clear: left; + display: block; + padding: 0.25em 0.5em; + margin-left: 5.6em; + width: 27.45em; +} + +.simple_form .hint { + color: #878787; + font-size: 80%; + font-style: italic; + display: block; + margin: 0.25em 0 0 7em; + clear: left; +} + +input { + margin: 0; +} + +input.radio { + margin-right: 5px; + vertical-align: -3px; +} + +input.check_boxes { + margin-left: 3px; + vertical-align: -3px; +} + +label.collection_check_boxes { + float: none; + margin: 0; + vertical-align: -2px; + margin-left: 2px; +} + +input.string, +textarea.text { + padding: 0.5em; + min-width: 40em; + border: 1px solid #ccc; +} + +textarea.text { + min-height: 5em; +} diff --git a/elasticsearch-persistence/examples/music/index_manager.rb b/elasticsearch-persistence/examples/music/index_manager.rb new file mode 100644 index 000000000..fae8a93d1 --- /dev/null +++ b/elasticsearch-persistence/examples/music/index_manager.rb @@ -0,0 +1,60 @@ +require 'open-uri' + +class IndexManager + def self.create_index(options={}) + client = Artist.gateway.client + index_name = Artist.index_name + + client.indices.delete index: index_name rescue nil if options[:force] + + settings = Artist.settings.to_hash.merge(Album.settings.to_hash) + mappings = Artist.mappings.to_hash.merge(Album.mappings.to_hash) + + client.indices.create index: index_name, + body: { + settings: settings.to_hash, + mappings: mappings.to_hash } + end + + def self.import_from_yaml(source, options={}) + create_index force: true if options[:force] + + input = open(source) + artists = YAML.load_documents input + + artists.each do |artist| + Artist.create artist.update( + 'album_count' => artist['releases'].size, + 'members_combined' => artist['members'].join(', '), + 'suggest_name' => { + 'input' => artist['namevariations'].unshift(artist['name']), + 'output' => artist['name'], + 'payload' => { 'url' => "/artists/#{artist['id']}" } + }, + 'suggest_member' => { + 'input' => artist['members'], + 'output' => artist['name'], + 'payload' => { 'url' => "/artists/#{artist['id']}" } + } + ) + + artist['releases'].each do |album| + album.update( + 'suggest_title' => { + 'input' => album['title'], + 'output' => album['title'], + 'payload' => { 'url' => "/artists/#{artist['id']}#album_#{album['id']}" } + }, + 'suggest_track' => { + 'input' => album['tracklist'].map { |d| d['title'] }, + 'output' => album['title'], + 'payload' => { 'url' => "/artists/#{artist['id']}#album_#{album['id']}" } + } + ) + album['notes'] = album['notes'].to_s.gsub(/<.+?>/, '').gsub(/ {2,}/, '') + album['released'] = nil if album['released'] < 1 + Album.create album, id: album['id'], parent: artist['id'] + end + end + end +end diff --git a/elasticsearch-persistence/examples/music/search/index.html.erb b/elasticsearch-persistence/examples/music/search/index.html.erb new file mode 100644 index 000000000..aed20f590 --- /dev/null +++ b/elasticsearch-persistence/examples/music/search/index.html.erb @@ -0,0 +1,93 @@ +

+

+ <%= link_to "〈".html_safe, :back, title: "Back" %> + Artists & Albums +

+
+ + + +
+ <% @artists.each do |artist| %> + <%= content_tag :div, class: 'result clearfix' do %> +

+ <%= link_to artist do %> + <%= highlighted(artist, :name) %> + <%= pluralize artist.album_count, 'album' %> + <% end %> +

+ <% if highlight = highlight(artist, :members_combined) %> +

+ Members + <%= highlight.first.html_safe %> +

+ <% end %> + <% if highlight = highlight(artist, :profile) %> +

+ Profile + <%= highlight.join('…').html_safe %> +

+ <% end %> + <% end %> + <% end %> +
+ +
+ <% @albums.each do |album| %> + <%= content_tag :div, class: 'result clearfix' do %> +

+ <%= link_to artist_path(album.artist_id, anchor: "album_#{album.id}") do %> + <%= highlighted(album, :title) %> + <%= album.artist %> + (<%= [album.meta.formats.first, album.released].compact.join(' ') %>) + <% end %> +

+ + <% if highlight = highlight(album, 'tracklist.title') %> +

+ Tracks + <%= highlight.join('…').html_safe %> +

+ <% end %> + + <% if highlight = highlight(album, :notes) %> +

+ Notes + <%= highlight.map { |d| d.gsub(/^\.\s?/, '') }.join('…').html_safe %> +

+ <% end %> + <% end %> + <% end %> +
+ +<% if @artists.empty? && @albums.empty? %> +
+

The search hasn't returned any results...

+
+<% end %> + + diff --git a/elasticsearch-persistence/examples/music/search/search_controller.rb b/elasticsearch-persistence/examples/music/search/search_controller.rb new file mode 100644 index 000000000..bb845c5b6 --- /dev/null +++ b/elasticsearch-persistence/examples/music/search/search_controller.rb @@ -0,0 +1,41 @@ +class SearchController < ApplicationController + + def index + tags = { pre_tags: '', post_tags: '' } + @artists = Artist.search \ + query: { + multi_match: { + query: params[:q], + fields: ['name^10','members^2','profile'] + } + }, + highlight: { + tags_schema: 'styled', + fields: { + name: { number_of_fragments: 0 }, + members_combined: { number_of_fragments: 0 }, + profile: { fragment_size: 50 } + } + } + + @albums = Album.search \ + query: { + multi_match: { + query: params[:q], + fields: ['title^100','tracklist.title^10','notes^1'] + } + }, + highlight: { + tags_schema: 'styled', + fields: { + title: { number_of_fragments: 0 }, + 'tracklist.title' => { number_of_fragments: 0 }, + notes: { fragment_size: 50 } + } + } + end + + def suggest + render json: Suggester.new(params) + end +end diff --git a/elasticsearch-persistence/examples/music/search/search_controller_test.rb b/elasticsearch-persistence/examples/music/search/search_controller_test.rb new file mode 100644 index 000000000..308bad200 --- /dev/null +++ b/elasticsearch-persistence/examples/music/search/search_controller_test.rb @@ -0,0 +1,9 @@ +require 'test_helper' + +class SearchControllerTest < ActionController::TestCase + test "should get suggest" do + get :suggest + assert_response :success + end + +end diff --git a/elasticsearch-persistence/examples/music/search/search_helper.rb b/elasticsearch-persistence/examples/music/search/search_helper.rb new file mode 100644 index 000000000..65a57c322 --- /dev/null +++ b/elasticsearch-persistence/examples/music/search/search_helper.rb @@ -0,0 +1,15 @@ +module SearchHelper + + def highlight(object, field) + object.try(:hit).try(:highlight).try(field) + end + + def highlighted(object, field) + if h = object.try(:hit).try(:highlight).try(field).try(:first) + h.html_safe + else + field.to_s.split('.').reduce(object) { |result,item| result.try(item) } + end + end + +end diff --git a/elasticsearch-persistence/examples/music/suggester.rb b/elasticsearch-persistence/examples/music/suggester.rb new file mode 100644 index 000000000..7f14a33ef --- /dev/null +++ b/elasticsearch-persistence/examples/music/suggester.rb @@ -0,0 +1,45 @@ +class Suggester + attr_reader :response + + def initialize(params={}) + @term = params[:term] + end + + def response + @response ||= begin + Elasticsearch::Persistence.client.suggest \ + index: Artist.index_name, + body: { + artists: { + text: @term, + completion: { field: 'suggest_name' } + }, + members: { + text: @term, + completion: { field: 'suggest_member' } + }, + albums: { + text: @term, + completion: { field: 'suggest_title' } + }, + tracks: { + text: @term, + completion: { field: 'suggest_track' } + } + } + end + end + + def as_json(options={}) + response + .except('_shards') + .reduce([]) do |sum,d| + # category = { d.first => d.second.first['options'] } + item = { :label => d.first.titleize, :value => d.second.first['options'] } + sum << item + end + .reject do |d| + d[:value].empty? + end + end +end diff --git a/elasticsearch-persistence/examples/music/template.rb b/elasticsearch-persistence/examples/music/template.rb new file mode 100644 index 000000000..a6d80641e --- /dev/null +++ b/elasticsearch-persistence/examples/music/template.rb @@ -0,0 +1,392 @@ +# ====================================================================================== +# Template for generating a Rails application with support for Elasticsearch persistence +# ====================================================================================== +# +# This file creates a fully working Rails application with support for storing and retrieving models +# in Elasticsearch, using the `elasticsearch-persistence` gem +# (https://github.com/elasticsearch/elasticsearch-rails/tree/persistence-model/elasticsearch-persistence). +# +# Requirements: +# ------------- +# +# * Git +# * Ruby >= 1.9.3 +# * Rails >= 4 +# * Java >= 7 (for Elasticsearch) +# +# Usage: +# ------ +# +# $ time rails new music --force --skip --skip-bundle --skip-active-record --template /Users/karmi/Contracts/Elasticsearch/Projects/Clients/Ruby/elasticsearch-rails/elasticsearch-persistence/examples/music/template.rb +# +# ===================================================================================================== + +STDOUT.sync = true +STDERR.sync = true + +require 'uri' +require 'net/http' + +at_exit do + pid = File.read("#{destination_root}/tmp/pids/elasticsearch.pid") rescue nil + if pid + say_status "Stop", "Elasticsearch", :yellow + run "kill #{pid}" + end +end + +run "touch tmp/.gitignore" + +append_to_file ".gitignore", "vendor/elasticsearch-1.2.1/\n" + +git :init +git add: "." +git commit: "-m 'Initial commit: Clean application'" + +# ----- Download Elasticsearch -------------------------------------------------------------------- + +unless (Net::HTTP.get(URI.parse('http://localhost:9200')) rescue false) + COMMAND = <<-COMMAND.gsub(/^ /, '') + curl -# -O "http://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.2.1.tar.gz" + tar -zxf elasticsearch-1.2.1.tar.gz + rm -f elasticsearch-1.2.1.tar.gz + ./elasticsearch-1.2.1/bin/elasticsearch -d -p #{destination_root}/tmp/pids/elasticsearch.pid + COMMAND + + puts "\n" + say_status "ERROR", "Elasticsearch not running!\n", :red + puts '-'*80 + say_status '', "It appears that Elasticsearch is not running on this machine." + say_status '', "Is it installed? Do you want me to install it for you with this command?\n\n" + COMMAND.each_line { |l| say_status '', "$ #{l}" } + puts + say_status '', "(To uninstall, just remove the generated application directory.)" + puts '-'*80, '' + + if yes?("Install Elasticsearch?", :bold) + puts + say_status "Install", "Elasticsearch", :yellow + + java_info = `java -version 2>&1` + + unless java_info.match /1\.[7-9]/ + puts + say_status "ERROR", "Required Java version (1.7) not found, exiting...", :red + exit(1) + end + + commands = COMMAND.split("\n") + exec = commands.pop + inside("vendor") do + commands.each { |command| run command } + run "(#{exec})" # Launch Elasticsearch in subshell + end + end +end unless ENV['RAILS_NO_ES_INSTALL'] + +# ----- Add README -------------------------------------------------------------------------------- + +puts +say_status "README", "Adding Readme...\n", :yellow +puts '-'*80, ''; sleep 0.25 + +remove_file 'README.rdoc' + +create_file 'README.rdoc', <<-README += Ruby on Rails and Elasticsearch persistence: Example application + +README + + +git add: "." +git commit: "-m 'Added README for the application'" + +# ----- Use Thin ---------------------------------------------------------------------------------- + +begin + require 'thin' + puts + say_status "Rubygems", "Adding Thin into Gemfile...\n", :yellow + puts '-'*80, ''; + + gem 'thin' +rescue LoadError +end + +# ----- Auxiliary gems ---------------------------------------------------------------------------- + +# ----- Remove CoffeeScript, Sass and "all that jazz" --------------------------------------------- + +comment_lines 'Gemfile', /gem 'coffee/ +comment_lines 'Gemfile', /gem 'sass/ +comment_lines 'Gemfile', /gem 'uglifier/ +uncomment_lines 'Gemfile', /gem 'therubyracer/ + +# ----- Add gems into Gemfile --------------------------------------------------------------------- + +puts +say_status "Rubygems", "Adding Elasticsearch libraries into Gemfile...\n", :yellow +puts '-'*80, ''; sleep 0.75 + +gem "quiet_assets" +gem "simple_form" + +gem 'elasticsearch', git: 'git://github.com/elasticsearch/elasticsearch-ruby.git' +gem 'elasticsearch-persistence', git: 'git://github.com/elasticsearch/elasticsearch-rails.git', branch: 'persistence-model', require: 'elasticsearch/persistence/model' +gem 'elasticsearch-rails', git: 'git://github.com/elasticsearch/elasticsearch-rails.git' + +git add: "Gemfile*" +git commit: "-m 'Added libraries into Gemfile'" + +# ----- Install gems ------------------------------------------------------------------------------ + +puts +say_status "Rubygems", "Installing Rubygems...", :yellow +puts '-'*80, '' + +run "bundle install" + +# ----- Autoload ./lib ---------------------------------------------------------------------------- + +puts +say_status "Application", "Adding autoloading of ./lib...", :yellow +puts '-'*80, '' + +insert_into_file 'config/application.rb', + ' + config.autoload_paths += %W(#{config.root}/lib) + +', + after: 'class Application < Rails::Application' + +git commit: "-a -m 'Added autoloading of the ./lib folder'" + +# ----- Add jQuery UI ---------------------------------------------------------------------------- + +puts +say_status "Assets", "Adding jQuery UI...", :yellow +puts '-'*80, ''; sleep 0.25 + +if ENV['LOCAL'] + copy_file File.expand_path('../vendor/assets/jquery-ui-1.10.4.custom.min.js', __FILE__), + 'vendor/assets/javascripts/jquery-ui-1.10.4.custom.min.js' + copy_file File.expand_path('../vendor/assets/jquery-ui-1.10.4.custom.min.css', __FILE__), + 'vendor/assets/stylesheets/ui-lightness/jquery-ui-1.10.4.custom.min.css' +else + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.js', + 'vendor/assets/javascripts/jquery-ui-1.10.4.custom.min.js' + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.css', + 'vendor/assets/stylesheets/ui-lightness/jquery-ui-1.10.4.custom.min.css' +end + +append_to_file 'app/assets/javascripts/application.js', "//= require jquery-ui-1.10.4.custom.min.js" + +git commit: "-a -m 'Added jQuery UI'" + +# ----- Generate Artist scaffold ------------------------------------------------------------------ + +puts +say_status "Model", "Generating the Artist scaffold...", :yellow +puts '-'*80, ''; sleep 0.25 + +generate :scaffold, "Artist name:String --orm=elasticsearch" +route "root to: 'artists#index'" + +git add: "." +git commit: "-m 'Added the generated Artist scaffold'" + +# ----- Generate Album model ---------------------------------------------------------------------- + +puts +say_status "Model", "Generating the Album model...", :yellow +puts '-'*80, ''; sleep 0.25 + +generate :model, "Album --orm=elasticsearch" + +git add: "." +git commit: "-m 'Added the generated Album model'" + +# ----- Add proper model classes ------------------------------------------------------------------ + +puts +say_status "Model", "Adding Album, Artist and Suggester models implementation...", :yellow +puts '-'*80, ''; sleep 0.25 + +if ENV['LOCAL'] + copy_file File.expand_path('../artist.rb', __FILE__), 'app/models/artist.rb' + copy_file File.expand_path('../album.rb', __FILE__), 'app/models/album.rb' + copy_file File.expand_path('../suggester.rb', __FILE__), 'app/models/suggester.rb' +else + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/artist.rb', + 'app/models/artist.rb' + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/album.rb', + 'app/models/album.rb' + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/suggester.rb', + 'app/models/suggester.rb' +end + +git add: "./app/models" +git commit: "-m 'Added Album, Artist and Suggester models implementation'" + +# ----- Add controllers and views ----------------------------------------------------------------- + +puts +say_status "Views", "Adding ArtistsController and views...", :yellow +puts '-'*80, ''; sleep 0.25 + +if ENV['LOCAL'] + copy_file File.expand_path('../artists/artists_controller.rb', __FILE__), 'app/controllers/artists_controller.rb' + copy_file File.expand_path('../artists/index.html.erb', __FILE__), 'app/views/artists/index.html.erb' + copy_file File.expand_path('../artists/show.html.erb', __FILE__), 'app/views/artists/show.html.erb' + copy_file File.expand_path('../artists/_form.html.erb', __FILE__), 'app/views/artists/_form.html.erb' + copy_file File.expand_path('../artists/artists_controller_test.rb', __FILE__), + 'test/controllers/artists_controller_test.rb' +else + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/artists/artists_controller.rb', + 'app/controllers/artists_controller.rb' + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/artists/index.html.erb', + 'app/views/artists/index.html.erb' + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/artists/show.html.erb', + 'app/views/artists/show.html.erb' + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/artists/_form.html.erb', + 'app/views/artists/_form.html.erb' + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/artists/artists_controller_test.rb', + 'test/controllers/artists_controller_test.rb' +end + +git commit: "-a -m 'Added ArtistsController and related views'" + +puts +say_status "Views", "Adding SearchController and views...", :yellow +puts '-'*80, ''; sleep 0.25 + +if ENV['LOCAL'] + copy_file File.expand_path('../search/search_controller.rb', __FILE__), 'app/controllers/search_controller.rb' + copy_file File.expand_path('../search/search_helper.rb', __FILE__), 'app/helpers/search_helper.rb' + copy_file File.expand_path('../search/index.html.erb', __FILE__), 'app/views/search/index.html.erb' + copy_file File.expand_path('../search/search_controller_test.rb', __FILE__), + 'test/controllers/search_controller_test.rb' +else + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/search/search_controller.rb', + 'app/controllers/search_controller.rb' + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/search/search_helper.rb', + 'app/helpers/search_helper.rb' + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/search/index.html.erb', + 'app/views/search/index.html.erb' + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/search/search_controller_test.rb', + 'test/controllers/search_controller_test.rb' +end + +route "get 'search', to: 'search#index'" +route "get 'suggest', to: 'search#suggest'" + +comment_lines 'test/test_helper.rb', /fixtures \:all/ + +git add: "." +git commit: "-m 'Added SearchController and related views'" + +# ----- Add assets ----------------------------------------------------------------- + +puts +say_status "Views", "Adding application assets...", :yellow +puts '-'*80, ''; sleep 0.25 + +git rm: 'app/assets/stylesheets/scaffold.css' + +gsub_file 'app/views/layouts/application.html.erb', //, '' + +if ENV['LOCAL'] + copy_file File.expand_path('../assets/application.css', __FILE__), 'app/assets/stylesheets/application.css' + copy_file File.expand_path('../assets/autocomplete.css', __FILE__), 'app/assets/stylesheets/autocomplete.css' + copy_file File.expand_path('../assets/form.css', __FILE__), 'app/assets/stylesheets/form.css' + copy_file File.expand_path('../assets/blank_cover.png', __FILE__), 'public/images/blank_cover.png' +else + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/assets/application.css', + 'app/assets/stylesheets/application.css' + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/assets/autocomplete.css', + 'app/assets/stylesheets/autocomplete.css' + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/assets/form.css', + 'app/assets/stylesheets/form.css' + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/assets/blank_cover.png', + 'public/images/blank_cover.png' +end + +git add: "." +git commit: "-m 'Added application assets'" + +# ----- Add an Elasticsearch initializer ---------------------------------------------------------- + +puts +say_status "Initializer", "Adding an Elasticsearch initializer...", :yellow +puts '-'*80, ''; sleep 0.25 + +initializer 'elasticsearch.rb', %q{ + Elasticsearch::Persistence.client = Elasticsearch::Client.new host: ENV['ELASTICSEARCH_URL'] || 'localhost:9200' + + if Rails.env.development? + logger = ActiveSupport::Logger.new(STDERR) + logger.level = Logger::INFO + logger.formatter = proc { |s, d, p, m| "\e[2m#{m}\n\e[0m" } + Elasticsearch::Persistence.client.transport.logger = logger + end +}.gsub(/^ /, '') + +git add: "./config" +git commit: "-m 'Added an Elasticsearch initializer'" + +# ----- Add IndexManager ----------------------------------------------------------------- + +puts +say_status "Application", "Adding the IndexManager class...", :yellow +puts '-'*80, ''; sleep 0.25 + +if ENV['LOCAL'] + copy_file File.expand_path('../index_manager.rb', __FILE__), 'lib/index_manager.rb' +else + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/index_manager.rb', + 'lib/index_manager.rb' +end + +# TODO: get 'https://raw.github.com/...', '...' + +git add: "." +git commit: "-m 'Added the IndexManager class'" + +# ----- Import the data --------------------------------------------------------------------------- + +puts +say_status "Data", "Import the data...", :yellow +puts '-'*80, ''; sleep 0.25 + +source = ENV.fetch('DATA_SOURCE', 'http://ruby-demo-assets.s3.amazonaws.com/dischord.yml') + +run "rails runner 'IndexManager.import_from_yaml(\"#{source}\", force: true)'" + +# ----- Print Git log ----------------------------------------------------------------------------- + +puts +say_status "Git", "Details about the application:", :yellow +puts '-'*80, '' + +run "git --no-pager log --reverse --oneline" + +# ----- Start the application --------------------------------------------------------------------- + +unless ENV['RAILS_NO_SERVER_START'] + require 'net/http' + if (begin; Net::HTTP.get(URI('http://localhost:3000')); rescue Errno::ECONNREFUSED; false; rescue Exception; true; end) + puts "\n" + say_status "ERROR", "Some other application is running on port 3000!\n", :red + puts '-'*80 + + port = ask("Please provide free port:", :bold) + else + port = '3000' + end + + puts "", "="*80 + say_status "DONE", "\e[1mStarting the application.\e[0m", :yellow + puts "="*80, "" + + run "rails server --port=#{port}" +end diff --git a/elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.css b/elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.css new file mode 100755 index 000000000..672cea658 --- /dev/null +++ b/elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.css @@ -0,0 +1,7 @@ +/*! jQuery UI - v1.10.4 - 2014-06-04 +* http://jqueryui.com +* Includes: jquery.ui.core.css, jquery.ui.autocomplete.css, jquery.ui.menu.css, jquery.ui.theme.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS%2CTahoma%2CVerdana%2CArial%2Csans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=gloss_wave&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=highlight_soft&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=glass&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=glass&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=highlight_soft&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=diagonals_thick&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=diagonals_thick&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=flat&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px +* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ + +.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-menu{list-style:none;padding:2px;margin:0;display:block;outline:none}.ui-menu .ui-menu{margin-top:-3px;position:absolute}.ui-menu .ui-menu-item{margin:0;padding:0;width:100%;list-style-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)}.ui-menu .ui-menu-divider{margin:5px -2px 5px -2px;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:2px .4em;line-height:1.5;min-height:0;font-weight:normal}.ui-menu .ui-menu-item a.ui-state-focus,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px}.ui-menu .ui-state-disabled{font-weight:normal;margin:.4em 0 .2em;line-height:1.5}.ui-menu .ui-state-disabled a{cursor:default}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item a{position:relative;padding-left:2em}.ui-menu .ui-icon{position:absolute;top:.2em;left:.2em}.ui-menu .ui-menu-icon{position:static;float:right}.ui-widget{font-family:Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #ddd;background:#eee url("images/ui-bg_highlight-soft_100_eeeeee_1x100.png") 50% top repeat-x;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #e78f08;background:#f6a828 url("images/ui-bg_gloss-wave_35_f6a828_500x100.png") 50% 50% repeat-x;color:#fff;font-weight:bold}.ui-widget-header a{color:#fff}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #ccc;background:#f6f6f6 url("images/ui-bg_glass_100_f6f6f6_1x400.png") 50% 50% repeat-x;font-weight:bold;color:#1c94c4}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#1c94c4;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #fbcb09;background:#fdf5ce url("images/ui-bg_glass_100_fdf5ce_1x400.png") 50% 50% repeat-x;font-weight:bold;color:#c77405}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited{color:#c77405;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #fbd850;background:#fff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;font-weight:bold;color:#eb8f00}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#eb8f00;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fed22f;background:#ffe45c url("images/ui-bg_highlight-soft_75_ffe45c_1x100.png") 50% top repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#b81900 url("images/ui-bg_diagonals-thick_18_b81900_40x40.png") 50% 50% repeat;color:#fff}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#fff}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#fff}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_222222_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_ffffff_256x240.png")}.ui-state-default .ui-icon{background-image:url("images/ui-icons_ef8c08_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url("images/ui-icons_ef8c08_256x240.png")}.ui-state-active .ui-icon{background-image:url("images/ui-icons_ef8c08_256x240.png")}.ui-state-highlight .ui-icon{background-image:url("images/ui-icons_228ef1_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_ffd27a_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:4px}.ui-widget-overlay{background:#666 url("images/ui-bg_diagonals-thick_20_666666_40x40.png") 50% 50% repeat;opacity:.5;filter:Alpha(Opacity=50)}.ui-widget-shadow{margin:-5px 0 0 -5px;padding:5px;background:#000 url("images/ui-bg_flat_10_000000_40x100.png") 50% 50% repeat-x;opacity:.2;filter:Alpha(Opacity=20);border-radius:5px} \ No newline at end of file diff --git a/elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.js b/elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.js new file mode 100755 index 000000000..8af84cb1e --- /dev/null +++ b/elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.js @@ -0,0 +1,6 @@ +/*! jQuery UI - v1.10.4 - 2014-06-05 +* http://jqueryui.com +* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.position.js, jquery.ui.autocomplete.js, jquery.ui.menu.js, jquery.ui.effect.js, jquery.ui.effect-highlight.js +* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ + +(function(e,t){function i(t,i){var s,a,o,r=t.nodeName.toLowerCase();return"area"===r?(s=t.parentNode,a=s.name,t.href&&a&&"map"===s.nodeName.toLowerCase()?(o=e("img[usemap=#"+a+"]")[0],!!o&&n(o)):!1):(/input|select|textarea|button|object/.test(r)?!t.disabled:"a"===r?t.href||i:i)&&n(t)}function n(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}var s=0,a=/^ui-id-\d+$/;e.ui=e.ui||{},e.extend(e.ui,{version:"1.10.4",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({focus:function(t){return function(i,n){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),n&&n.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),scrollParent:function(){var t;return t=e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(i){if(i!==t)return this.css("zIndex",i);if(this.length)for(var n,s,a=e(this[0]);a.length&&a[0]!==document;){if(n=a.css("position"),("absolute"===n||"relative"===n||"fixed"===n)&&(s=parseInt(a.css("zIndex"),10),!isNaN(s)&&0!==s))return s;a=a.parent()}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++s)})},removeUniqueId:function(){return this.each(function(){a.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,n){return!!e.data(t,n[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var n=e.attr(t,"tabindex"),s=isNaN(n);return(s||n>=0)&&i(t,!s)}}),e("").outerWidth(1).jquery||e.each(["Width","Height"],function(i,n){function s(t,i,n,s){return e.each(a,function(){i-=parseFloat(e.css(t,"padding"+this))||0,n&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var a="Width"===n?["Left","Right"]:["Top","Bottom"],o=n.toLowerCase(),r={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+n]=function(i){return i===t?r["inner"+n].call(this):this.each(function(){e(this).css(o,s(this,i)+"px")})},e.fn["outer"+n]=function(t,i){return"number"!=typeof t?r["outer"+n].call(this,t):this.each(function(){e(this).css(o,s(this,t,!0,i)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart="onselectstart"in document.createElement("div"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,i,n){var s,a=e.ui[t].prototype;for(s in n)a.plugins[s]=a.plugins[s]||[],a.plugins[s].push([i,n[s]])},call:function(e,t,i){var n,s=e.plugins[t];if(s&&e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType)for(n=0;s.length>n;n++)e.options[s[n][0]]&&s[n][1].apply(e.element,i)}},hasScroll:function(t,i){if("hidden"===e(t).css("overflow"))return!1;var n=i&&"left"===i?"scrollLeft":"scrollTop",s=!1;return t[n]>0?!0:(t[n]=1,s=t[n]>0,t[n]=0,s)}})})(jQuery);(function(t,e){var i=0,s=Array.prototype.slice,n=t.cleanData;t.cleanData=function(e){for(var i,s=0;null!=(i=e[s]);s++)try{t(i).triggerHandler("remove")}catch(o){}n(e)},t.widget=function(i,s,n){var o,a,r,h,l={},c=i.split(".")[0];i=i.split(".")[1],o=c+"-"+i,n||(n=s,s=t.Widget),t.expr[":"][o.toLowerCase()]=function(e){return!!t.data(e,o)},t[c]=t[c]||{},a=t[c][i],r=t[c][i]=function(t,i){return this._createWidget?(arguments.length&&this._createWidget(t,i),e):new r(t,i)},t.extend(r,a,{version:n.version,_proto:t.extend({},n),_childConstructors:[]}),h=new s,h.options=t.widget.extend({},h.options),t.each(n,function(i,n){return t.isFunction(n)?(l[i]=function(){var t=function(){return s.prototype[i].apply(this,arguments)},e=function(t){return s.prototype[i].apply(this,t)};return function(){var i,s=this._super,o=this._superApply;return this._super=t,this._superApply=e,i=n.apply(this,arguments),this._super=s,this._superApply=o,i}}(),e):(l[i]=n,e)}),r.prototype=t.widget.extend(h,{widgetEventPrefix:a?h.widgetEventPrefix||i:i},l,{constructor:r,namespace:c,widgetName:i,widgetFullName:o}),a?(t.each(a._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,r,i._proto)}),delete a._childConstructors):s._childConstructors.push(r),t.widget.bridge(i,r)},t.widget.extend=function(i){for(var n,o,a=s.call(arguments,1),r=0,h=a.length;h>r;r++)for(n in a[r])o=a[r][n],a[r].hasOwnProperty(n)&&o!==e&&(i[n]=t.isPlainObject(o)?t.isPlainObject(i[n])?t.widget.extend({},i[n],o):t.widget.extend({},o):o);return i},t.widget.bridge=function(i,n){var o=n.prototype.widgetFullName||i;t.fn[i]=function(a){var r="string"==typeof a,h=s.call(arguments,1),l=this;return a=!r&&h.length?t.widget.extend.apply(null,[a].concat(h)):a,r?this.each(function(){var s,n=t.data(this,o);return n?t.isFunction(n[a])&&"_"!==a.charAt(0)?(s=n[a].apply(n,h),s!==n&&s!==e?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):e):t.error("no such method '"+a+"' for "+i+" widget instance"):t.error("cannot call methods on "+i+" prior to initialization; "+"attempted to call method '"+a+"'")}):this.each(function(){var e=t.data(this,o);e?e.option(a||{})._init():t.data(this,o,new n(a,this))}),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{disabled:!1,create:null},_createWidget:function(e,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this.bindings=t(),this.hoverable=t(),this.focusable=t(),s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:t.noop,_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(t.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:t.noop,widget:function(){return this.element},option:function(i,s){var n,o,a,r=i;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof i)if(r={},n=i.split("."),i=n.shift(),n.length){for(o=r[i]=t.widget.extend({},this.options[i]),a=0;n.length-1>a;a++)o[n[a]]=o[n[a]]||{},o=o[n[a]];if(i=n.pop(),1===arguments.length)return o[i]===e?null:o[i];o[i]=s}else{if(1===arguments.length)return this.options[i]===e?null:this.options[i];r[i]=s}return this._setOptions(r),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return this.options[t]=e,"disabled"===t&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!e).attr("aria-disabled",e),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,s,n){var o,a=this;"boolean"!=typeof i&&(n=s,s=i,i=!1),n?(s=o=t(s),this.bindings=this.bindings.add(s)):(n=s,s=this.element,o=this.widget()),t.each(n,function(n,r){function h(){return i||a.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof r?a[r]:r).apply(a,arguments):e}"string"!=typeof r&&(h.guid=r.guid=r.guid||h.guid||t.guid++);var l=n.match(/^(\w+)\s*(.*)$/),c=l[1]+a.eventNamespace,u=l[2];u?o.delegate(u,c,h):s.bind(c,h)})},_off:function(t,e){e=(e||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(e).undelegate(e)},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){t(e.currentTarget).addClass("ui-state-hover")},mouseleave:function(e){t(e.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){t(e.currentTarget).addClass("ui-state-focus")},focusout:function(e){t(e.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}})})(jQuery);(function(t,e){function i(t,e,i){return[parseFloat(t[0])*(p.test(t[0])?e/100:1),parseFloat(t[1])*(p.test(t[1])?i/100:1)]}function s(e,i){return parseInt(t.css(e,i),10)||0}function n(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}t.ui=t.ui||{};var a,o=Math.max,r=Math.abs,l=Math.round,h=/left|center|right/,c=/top|center|bottom/,u=/[\+\-]\d+(\.[\d]+)?%?/,d=/^\w+/,p=/%$/,f=t.fn.position;t.position={scrollbarWidth:function(){if(a!==e)return a;var i,s,n=t("
"),o=n.children()[0];return t("body").append(n),i=o.offsetWidth,n.css("overflow","scroll"),s=o.offsetWidth,i===s&&(s=n[0].clientWidth),n.remove(),a=i-s},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.widths?"left":i>0?"right":"center",vertical:0>a?"top":n>0?"bottom":"middle"};u>p&&p>r(i+s)&&(l.horizontal="center"),d>g&&g>r(n+a)&&(l.vertical="middle"),l.important=o(r(i),r(s))>o(r(n),r(a))?"horizontal":"vertical",e.using.call(this,t,l)}),c.offset(t.extend(M,{using:h}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,l=n-r,h=r+e.collisionWidth-a-n;e.collisionWidth>a?l>0&&0>=h?(i=t.left+l+e.collisionWidth-a-n,t.left+=l-i):t.left=h>0&&0>=l?n:l>h?n+a-e.collisionWidth:n:l>0?t.left+=l:h>0?t.left-=h:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,l=n-r,h=r+e.collisionHeight-a-n;e.collisionHeight>a?l>0&&0>=h?(i=t.top+l+e.collisionHeight-a-n,t.top+=l-i):t.top=h>0&&0>=l?n:l>h?n+a-e.collisionHeight:n:l>0?t.top+=l:h>0?t.top-=h:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,a=n.offset.left+n.scrollLeft,o=n.width,l=n.isWindow?n.scrollLeft:n.offset.left,h=t.left-e.collisionPosition.marginLeft,c=h-l,u=h+e.collisionWidth-o-l,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-o-a,(0>i||r(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-l,(s>0||u>r(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,a=n.offset.top+n.scrollTop,o=n.height,l=n.isWindow?n.scrollTop:n.offset.top,h=t.top-e.collisionPosition.marginTop,c=h-l,u=h+e.collisionHeight-o-l,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-o-a,t.top+p+f+g>c&&(0>s||r(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-l,t.top+p+f+g>u&&(i>0||u>r(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}},function(){var e,i,s,n,a,o=document.getElementsByTagName("body")[0],r=document.createElement("div");e=document.createElement(o?"div":"body"),s={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},o&&t.extend(s,{position:"absolute",left:"-1000px",top:"-1000px"});for(a in s)e.style[a]=s[a];e.appendChild(r),i=o||document.documentElement,i.insertBefore(e,i.firstChild),r.style.cssText="position: absolute; left: 10.7432222px;",n=t(r).offset().left,t.support.offsetFractions=n>10&&11>n,e.innerHTML="",i.removeChild(e)}()})(jQuery);(function(e){e.widget("ui.autocomplete",{version:"1.10.4",defaultElement:"",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var t,i,s,n=this.element[0].nodeName.toLowerCase(),a="textarea"===n,o="input"===n;this.isMultiLine=a?!0:o?!1:this.element.prop("isContentEditable"),this.valueMethod=this.element[a||o?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return t=!0,s=!0,i=!0,undefined;t=!1,s=!1,i=!1;var a=e.ui.keyCode;switch(n.keyCode){case a.PAGE_UP:t=!0,this._move("previousPage",n);break;case a.PAGE_DOWN:t=!0,this._move("nextPage",n);break;case a.UP:t=!0,this._keyEvent("previous",n);break;case a.DOWN:t=!0,this._keyEvent("next",n);break;case a.ENTER:case a.NUMPAD_ENTER:this.menu.active&&(t=!0,n.preventDefault(),this.menu.select(n));break;case a.TAB:this.menu.active&&this.menu.select(n);break;case a.ESCAPE:this.menu.element.is(":visible")&&(this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(t)return t=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),undefined;if(!i){var n=e.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(e){return s?(s=!1,e.preventDefault(),undefined):(this._searchTimeout(e),undefined)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){return this.cancelBlur?(delete this.cancelBlur,undefined):(clearTimeout(this.searching),this.close(e),this._change(e),undefined)}}),this._initSource(),this.menu=e("
@@ -54,7 +54,7 @@ <% if suggestions['suggest_title'].present? || suggestions['suggest_body'].present? %> Maybe you mean <%= suggestions.map { |k,v| v.first['options'] }.flatten.map {|v| v['text']}.uniq.map do |term| - link_to term, search_path(params.merge q: term) + link_to term, search_path(params.except(:controller, :action).merge q: term) end.to_sentence(last_word_connector: ' or ').html_safe %>? <% end %>

@@ -65,12 +65,12 @@ <% unless @articles.size < 1 %>
-

<%= link_to 'All Sections →'.html_safe, search_path(params.merge(c: nil))%>

+

<%= link_to 'All Sections →'.html_safe, search_path(params.except(:controller, :action).merge(c: nil))%>

<% @articles.response.response['facets']['categories']['terms'].each do |c| %> <%= - link_to search_path(params.merge(c: c['term'])), + link_to search_path(params.except(:controller, :action).merge(c: c['term'])), class: "list-group-item#{' active' if params[:c] == c['term']}" do c['term'].titleize.html_safe + content_tag(:small, c['count'], class: 'badge').html_safe end @@ -80,12 +80,12 @@
-

<%= link_to 'All Authors →'.html_safe, search_path(params.merge(a: nil))%>

+

<%= link_to 'All Authors →'.html_safe, search_path(params.except(:controller, :action).merge(a: nil))%>

<% @articles.response.response['facets']['authors']['terms'].each do |a| %> <%= - link_to search_path(params.merge(a: a['term'])), + link_to search_path(params.except(:controller, :action).merge(a: a['term'])), class: "list-group-item#{' active' if params[:a] == a['term']}" do a['term'].titleize.html_safe + content_tag(:small, a['count'], class: 'badge').html_safe end @@ -95,7 +95,7 @@
-

<%= link_to 'Any Date →'.html_safe, search_path(params.merge(w: nil))%>

+

<%= link_to 'Any Date →'.html_safe, search_path(params.except(:controller, :action).merge(w: nil))%>

<% @articles.response.response['facets']['published']['entries'].each do |w| %> @@ -104,7 +104,7 @@ __end = __start.end_of_week __date = __start.to_date.to_s(:iso) - link_to search_path(params.merge(w: __date)), + link_to search_path(params.except(:controller, :action).merge(w: __date)), class: "list-group-item#{' active' if params[:w] == __date}" do "#{__start.to_date.to_s(:short)} — #{__end.to_date.to_s(:short)}".html_safe + \ content_tag(:small, w['count'], class: 'badge').html_safe From 2f2c0fb7a25ece68fd3a9eec7b9026848e66fa4f Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 16 Mar 2015 18:05:05 -0700 Subject: [PATCH 186/582] [RAILS] Added the `04-dsl` template Still a work in progress... --- .../lib/rails/templates/04-dsl.rb | 125 ++++++++++ .../lib/rails/templates/index.html.dsl.erb | 160 +++++++++++++ .../templates/search_controller_test.dsl.rb | 130 +++++++++++ .../lib/rails/templates/searchable.dsl.rb | 217 ++++++++++++++++++ 4 files changed, 632 insertions(+) create mode 100644 elasticsearch-rails/lib/rails/templates/04-dsl.rb create mode 100644 elasticsearch-rails/lib/rails/templates/index.html.dsl.erb create mode 100644 elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb create mode 100644 elasticsearch-rails/lib/rails/templates/searchable.dsl.rb diff --git a/elasticsearch-rails/lib/rails/templates/04-dsl.rb b/elasticsearch-rails/lib/rails/templates/04-dsl.rb new file mode 100644 index 000000000..0c3eb9e49 --- /dev/null +++ b/elasticsearch-rails/lib/rails/templates/04-dsl.rb @@ -0,0 +1,125 @@ +# $ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/04-dsl.rb + +# (See: 01-basic.rb, 02-pretty.rb, 03-expert.rb) + +append_to_file 'README.rdoc', <<-README + +== [4] DSL + +The `dsl` template refactors the search definition in SearchController#index +to use the [`elasticsearch-dsl`](https://github.com/elastic/elasticsearch-ruby/tree/dsl/elasticsearch-dsl) +Rubygem for better expresivity and readability of the code. + +README + +git add: "README.rdoc" +git commit: "-m '[03] Updated the application README'" + +# ----- Add gems into Gemfile --------------------------------------------------------------------- + +puts +say_status "Rubygems", "Adding Rubygems into Gemfile...\n", :yellow +puts '-'*80, ''; sleep 0.25 + +gem "elasticsearch-dsl", git: "git://github.com/elasticsearch/elasticsearch-ruby.git", branch: 'dsl' + +git add: "Gemfile*" +git commit: "-m 'Added the `elasticsearch-dsl` gem'" + +# ----- Run bundle install ------------------------------------------------------------------------ + +run "bundle install" + +# ----- Change the search definition implementation and associated views and tests ---------------- + +copy_file File.expand_path('../searchable.dsl.rb', __FILE__), 'app/models/concerns/searchable.rb', force: true +# get 'https://raw.github.com/elasticsearch/elasticsearch-rails/templates/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb', +# 'app/models/concerns/searchable.rb' + +copy_file File.expand_path('../index.html.dsl.erb', __FILE__), 'app/views/search/index.html.erb', force: true +# get 'https://raw.github.com/elasticsearch/elasticsearch-rails/templates/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb', +# 'app/views/search/index.html.erb' + +gsub_file "test/controllers/search_controller_test.rb", %r{test "should return facets" do.*?end}m, <<-CODE +test "should return aggregations" do + get :index, q: 'one' + assert_response :success + + aggregations = assigns(:articles).response.response['aggregations'] + + assert_equal 2, aggregations['categories']['categories']['buckets'].size + assert_equal 2, aggregations['authors']['authors']['buckets'].size + assert_equal 2, aggregations['published']['published']['buckets'].size + + assert_equal 'John Smith', aggregations['authors']['authors']['buckets'][0]['key'] + assert_equal 'One', aggregations['categories']['categories']['buckets'][0]['key'] + assert_equal '2015-03-02T00:00:00.000Z', aggregations['published']['published']['buckets'][0]['key_as_string'] + end +CODE + +gsub_file "test/controllers/search_controller_test.rb", %r{test "should filter search results and the author and published date facets when user selects a category" do.*?end}m, <<-CODE +test "should filter search results and the author and published date facets when user selects a category" do + get :index, q: 'one', c: 'One' + assert_response :success + + assert_equal 2, assigns(:articles).size + + aggregations = assigns(:articles).response.response['aggregations'] + + assert_equal 1, aggregations['authors']['authors']['buckets'].size + assert_equal 1, aggregations['published']['published']['buckets'].size + + # Do NOT filter the category facet + assert_equal 2, aggregations['categories']['categories']['buckets'].size + end +CODE + +gsub_file "test/controllers/search_controller_test.rb", %r{test "should filter search results and the category and published date facets when user selects a category" do.*?end}m, <<-CODE +test "should filter search results and the category and published date facets when user selects a category" do + get :index, q: 'one', a: 'Mary Smith' + assert_response :success + + assert_equal 1, assigns(:articles).size + + aggregations = assigns(:articles).response.response['aggregations'] + + assert_equal 1, aggregations['categories']['categories']['buckets'].size + assert_equal 1, aggregations['published']['published']['buckets'].size + + # Do NOT filter the authors facet + assert_equal 2, aggregations['authors']['authors']['buckets'].size + end +CODE + +git add: "app/models/concerns/ app/views/search/ test/controllers/search_controller_test.rb" +git commit: "-m 'Updated the Article.search method to use the Ruby DSL; Updated the views and tests'" + +# ----- Print Git log ----------------------------------------------------------------------------- + +puts +say_status "Git", "Details about the application:", :yellow +puts '-'*80, '' + +git tag: "dsl" +git log: "--reverse --oneline HEAD...expert" + +# ----- Start the application --------------------------------------------------------------------- + +unless ENV['RAILS_NO_SERVER_START'] + require 'net/http' + if (begin; Net::HTTP.get(URI('http://localhost:3000')); rescue Errno::ECONNREFUSED; false; rescue Exception; true; end) + puts "\n" + say_status "ERROR", "Some other application is running on port 3000!\n", :red + puts '-'*80 + + port = ask("Please provide free port:", :bold) + else + port = '3000' + end + + puts "", "="*80 + say_status "DONE", "\e[1mStarting the application. Open http://localhost:#{port}\e[0m", :yellow + puts "="*80, "" + + run "rails server --port=#{port}" +end diff --git a/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb b/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb new file mode 100644 index 000000000..2e7ee6c67 --- /dev/null +++ b/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb @@ -0,0 +1,160 @@ +
+

<%= link_to 'Search New York Times articles', root_path %>

+ + <%= form_tag search_path, method: 'get', role: 'search' do %> +
+ <%= text_field_tag :q, params[:q], class: 'form-control', placeholder: 'Search...' %> + + + + +
+ +
+
+ + <% params.slice(:a, :c, :s).each do |name, value| %> + <%= hidden_field_tag name, value %> + <% end %> +
+ +
+

Displaying <%= (params[:page] || 1).to_i.ordinalize %> page with <%= @articles.size %> articles + of total <%= @articles.total %>

+ + + +
+
+ <% end %> + +
+
+ +<% if @articles.size < 1 && (suggestions = @articles.response.response['suggest']) && suggestions.present? %> +
+

+ No documents have been found. + <% if suggestions['suggest_title'].present? || suggestions['suggest_body'].present? %> + Maybe you mean + <%= suggestions.map { |k,v| v.first['options'] }.flatten.map {|v| v['text']}.uniq.map do |term| + link_to term, search_path(params.except(:controller, :action).merge q: term) + end.to_sentence(last_word_connector: ' or ').html_safe %>? + <% end %> +

+
+<% end %> + +
+ <% unless @articles.size < 1 %> + +
+

<%= link_to 'All Sections →'.html_safe, search_path(params.except(:controller, :action).merge(c: nil))%>

+ +
+ <% @articles.response.response['aggregations']['categories']['categories']['buckets'].each do |c| %> + <%= + link_to search_path(params.except(:controller, :action).merge(c: c['key'])), + class: "list-group-item#{' active' if params[:c] == c['key']}" do + c['key'].titleize.html_safe + content_tag(:small, c['doc_count'], class: 'badge').html_safe + end + %> + <% end %> +
+
+ +
+

<%= link_to 'All Authors →'.html_safe, search_path(params.except(:controller, :action).merge(a: nil))%>

+ +
+ <% @articles.response.response['aggregations']['authors']['authors']['buckets'].each do |a| %> + <%= + link_to search_path(params.except(:controller, :action).merge(a: a['key'])), + class: "list-group-item#{' active' if params[:a] == a['key']}" do + a['key'].titleize.html_safe + content_tag(:small, a['doc_count'], class: 'badge').html_safe + end + %> + <% end %> +
+
+ +
+

<%= link_to 'Any Date →'.html_safe, search_path(params.except(:controller, :action).merge(w: nil))%>

+ +
+ <% @articles.response.response['aggregations']['published']['published']['buckets'].each do |w| %> + <%= + __start = Time.at(w['key']/1000) + __end = __start.end_of_week + __date = __start.to_date.to_s(:iso) + + link_to search_path(params.except(:controller, :action).merge(w: __date)), + class: "list-group-item#{' active' if params[:w] == __date}" do + "#{__start.to_date.to_s(:short)} — #{__end.to_date.to_s(:short)}".html_safe + \ + content_tag(:small, w['doc_count'], class: 'badge').html_safe + end + %> + <% end %> +
+
+ <% end %> +
+ +
+
+ <% @articles.each do |article| %> +
+

+ <%= (article.try(:highlight).try(:title) ? article.highlight.title.join.html_safe : article.title) %> + <%= article.categories.to_sentence %> +

+ +

+ <% if article.try(:highlight).try(:abstract) %> + <%= article.highlight.abstract.join.html_safe %> + <% else %> + <%= article.try(:highlight).try(:content) ? article.highlight.content.join('…').html_safe : article.abstract %> + <% end %> +

+ + <% if comments = article.try(:highlight) && article.highlight['comments.body'] %> +

+ Comments: <%= comments.join('…').html_safe %> +

+ <% end %> + +

+ Authors: <%= article.authors.map(&:full_name).to_sentence %> | + Published: <%= article.published_on %> | + Score: <%= article._score %> +

+
+ <% end %> +
+ +
    + + +
+ +
+ +
diff --git a/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb b/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb new file mode 100644 index 000000000..aa2fae65a --- /dev/null +++ b/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb @@ -0,0 +1,130 @@ +require 'test_helper' + +class SearchControllerTest < ActionController::TestCase + setup do + Time.stubs(:now).returns(Time.parse('2015-03-16 10:00:00 UTC')) + + Article.delete_all + + articles = [ + { title: 'Article One', abstract: 'One', content: 'One', published_on: 1.day.ago, category_title: 'One', author_first_name: 'John', author_last_name: 'Smith' }, + { title: 'Article One Another', abstract: '', content: '', published_on: 2.days.ago, category_title: 'One', author_first_name: 'John', author_last_name: 'Smith' }, + { title: 'Article One Two', abstract: '', content: '', published_on: 10.days.ago, category_title: 'Two', author_first_name: 'Mary', author_last_name: 'Smith' }, + { title: 'Article Two', abstract: '', content: '', published_on: 12.days.ago, category_title: 'Two', author_first_name: 'Mary', author_last_name: 'Smith' }, + { title: 'Article Three', abstract: '', content: '', published_on: 12.days.ago, category_title: 'Three', author_first_name: 'Alice', author_last_name: 'Smith' } + ] + + articles.each do |a| + article = Article.create! \ + title: a[:title], + abstract: a[:abstract], + content: a[:content], + published_on: a[:published_on] + + article.categories << Category.find_or_create_by!(title: a[:category_title]) + + article.authors << Author.find_or_create_by!(first_name: a[:author_first_name], last_name: a[:author_last_name]) + + article.save! + end + + Article.find_by_title('Article Three').comments.create body: 'One' + + Sidekiq::Queue.new("elasticsearch").clear + + Article.__elasticsearch__.import force: true + Article.__elasticsearch__.refresh_index! + end + + test "should return search results" do + get :index, q: 'one' + assert_response :success + assert_equal 3, assigns(:articles).size + end + + test "should return search results in comments" do + get :index, q: 'one', comments: 'y' + assert_response :success + assert_equal 4, assigns(:articles).size + end + + test "should return highlighted snippets" do + get :index, q: 'one' + assert_response :success + assert_match %r{One}, assigns(:articles).first.highlight.title.first + end + + test "should return suggestions" do + get :index, q: 'one' + assert_response :success + + suggestions = assigns(:articles).response.response['suggest'] + + assert_equal 'one', suggestions['suggest_title'][0]['text'] + end + + test "should return aggregations" do + get :index, q: 'one' + assert_response :success + + aggregations = assigns(:articles).response.response['aggregations'] + + assert_equal 2, aggregations['categories']['categories']['buckets'].size + assert_equal 2, aggregations['authors']['authors']['buckets'].size + assert_equal 2, aggregations['published']['published']['buckets'].size + + assert_equal 'John Smith', aggregations['authors']['authors']['buckets'][0]['key'] + assert_equal 'One', aggregations['categories']['categories']['buckets'][0]['key'] + assert_equal '2015-03-02T00:00:00.000Z', aggregations['published']['published']['buckets'][0]['key_as_string'] + end + + test "should sort on the published date" do + get :index, q: 'one', s: 'published_on' + assert_response :success + + assert_equal 3, assigns(:articles).size + assert_equal '2015-03-15', assigns(:articles)[0].published_on + assert_equal '2015-03-14', assigns(:articles)[1].published_on + assert_equal '2015-03-06', assigns(:articles)[2].published_on + end + + test "should sort on the published date when no query is provided" do + get :index, q: '' + assert_response :success + + assert_equal 5, assigns(:articles).size + assert_equal '2015-03-15', assigns(:articles)[0].published_on + assert_equal '2015-03-14', assigns(:articles)[1].published_on + assert_equal '2015-03-06', assigns(:articles)[2].published_on + end + + test "should filter search results and the author and published date facets when user selects a category" do + get :index, q: 'one', c: 'One' + assert_response :success + + assert_equal 2, assigns(:articles).size + + aggregations = assigns(:articles).response.response['aggregations'] + + assert_equal 1, aggregations['authors']['authors']['buckets'].size + assert_equal 1, aggregations['published']['published']['buckets'].size + + # Do NOT filter the category facet + assert_equal 2, aggregations['categories']['categories']['buckets'].size + end + + test "should filter search results and the category and published date facets when user selects a category" do + get :index, q: 'one', a: 'Mary Smith' + assert_response :success + + assert_equal 1, assigns(:articles).size + + aggregations = assigns(:articles).response.response['aggregations'] + + assert_equal 1, aggregations['categories']['categories']['buckets'].size + assert_equal 1, aggregations['published']['published']['buckets'].size + + # Do NOT filter the authors facet + assert_equal 2, aggregations['authors']['authors']['buckets'].size + end +end diff --git a/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb b/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb new file mode 100644 index 000000000..e58510469 --- /dev/null +++ b/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb @@ -0,0 +1,217 @@ +module Searchable + extend ActiveSupport::Concern + + included do + include Elasticsearch::Model + + # Customize the index name + # + index_name [Rails.application.engine_name, Rails.env].join('_') + + # Set up index configuration and mapping + # + settings index: { number_of_shards: 1, number_of_replicas: 0 } do + mapping do + indexes :title, type: 'multi_field' do + indexes :title, analyzer: 'snowball' + indexes :tokenized, analyzer: 'simple' + end + + indexes :content, type: 'multi_field' do + indexes :content, analyzer: 'snowball' + indexes :tokenized, analyzer: 'simple' + end + + indexes :published_on, type: 'date' + + indexes :authors do + indexes :full_name, type: 'multi_field' do + indexes :full_name + indexes :raw, analyzer: 'keyword' + end + end + + indexes :categories, analyzer: 'keyword' + + indexes :comments, type: 'nested' do + indexes :body, analyzer: 'snowball' + indexes :stars + indexes :pick + indexes :user, analyzer: 'keyword' + indexes :user_location, type: 'multi_field' do + indexes :user_location + indexes :raw, analyzer: 'keyword' + end + end + end + end + + # Set up callbacks for updating the index on model changes + # + after_commit lambda { Indexer.perform_async(:index, self.class.to_s, self.id) }, on: :create + after_commit lambda { Indexer.perform_async(:update, self.class.to_s, self.id) }, on: :update + after_commit lambda { Indexer.perform_async(:delete, self.class.to_s, self.id) }, on: :destroy + after_touch lambda { Indexer.perform_async(:update, self.class.to_s, self.id) } + + # Customize the JSON serialization for Elasticsearch + # + def as_indexed_json(options={}) + hash = self.as_json( + include: { authors: { methods: [:full_name], only: [:full_name] }, + comments: { only: [:body, :stars, :pick, :user, :user_location] } + }) + hash['categories'] = self.categories.map(&:title) + hash + end + + # Return documents matching the user's query, include highlights and aggregations in response, + # and implement a "cross" faceted navigation + # + # @param q [String] The user query + # @return [Elasticsearch::Model::Response::Response] + # + def self.search(q, options={}) + @search_definition = Elasticsearch::DSL::Search.search do + query do + + # If a user query is present... + # + unless q.blank? + bool do + + # ... search in `title`, `abstract` and `content`, boosting `title` + # + should do + multi_match do + query q + fields ['title^10', 'abstract^2', 'content'] + operator 'and' + end + end + + # ... search in comment body if user checked the comments checkbox + # + if q.present? && options[:comments] + should do + nested do + path :comments + query do + multi_match do + query q + fields 'body' + operator 'and' + end + end + end + end + end + end + + # ... otherwise, just return all articles + else + match_all + end + end + + # Filter the search results based on user selection + # + post_filter do + bool do + must { term categories: options[:category] } if options[:category] + must { match_all } if options.keys.none? { |k| [:c, :a, :w].include? k } + must { term 'authors.full_name.raw' => options[:author] } if options[:author] + must { range published_on: { gte: options[:published_week], lte: "#{options[:published_week]}||+1w" } } if options[:published_week] + end + end + + # Return top categories for faceted navigation + # + aggregation :categories do + # Filter the aggregation with any selected `author` and `published_week` + # + f = Elasticsearch::DSL::Search::Filters::Bool.new + f.must { match_all } + f.must { term 'authors.full_name.raw' => options[:author] } if options[:author] + f.must { range published_on: { gte: options[:published_week], lte: "#{options[:published_week]}||+1w" } } if options[:published_week] + + filter f.to_hash do + aggregation :categories do + terms field: 'categories' + end + end + end + + # Return top authors for faceted navigation + # + aggregation :authors do + # Filter the aggregation with any selected `category` and `published_week` + # + f = Elasticsearch::DSL::Search::Filters::Bool.new + f.must { match_all } + f.must { term categories: options[:category] } if options[:category] + f.must { range published_on: { gte: options[:published_week], lte: "#{options[:published_week]}||+1w" } } if options[:published_week] + + filter f do + aggregation :authors do + terms field: 'authors.full_name.raw' + end + end + end + + # Return the published date ranges for faceted navigation + # + aggregation :published do + # Filter the aggregation with any selected `author` and `category` + # + f = Elasticsearch::DSL::Search::Filters::Bool.new + f.must { match_all } + f.must { term 'authors.full_name.raw' => options[:author] } if options[:author] + f.must { term categories: options[:category] } if options[:category] + + filter f do + aggregation :published do + date_histogram do + field 'published_on' + interval 'week' + end + end + end + end + + # Highlight the snippets in results + # + highlight do + fields title: { number_of_fragments: 0 }, + abstract: { number_of_fragments: 0 }, + content: { fragment_size: 50 } + + field 'comments.body', fragment_size: 50 if q.present? && options[:comments] + + pre_tags '' + post_tags '' + end + + case + # By default, sort by relevance, but when a specific sort option is present, use it ... + # + when options[:sort] + sort options[:sort].to_sym => 'desc' + track_scores true + # + # ... when there's no user query, sort on published date + # + when q.blank? + sort published_on: 'desc' + end + + # Return suggestions unless there's no query from the user + unless q.blank? + suggest :suggest_title, text: q, term: { field: 'title.tokenized', suggest_mode: 'always' } + suggest :suggest_body, text: q, term: { field: 'content.tokenized', suggest_mode: 'always' } + end + end + + __elasticsearch__.search(@search_definition) + end + end +end From 7275b1c493a1d28c24d645f79fdf4907c92dc5d5 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Tue, 7 Apr 2015 15:50:41 +0200 Subject: [PATCH 187/582] [RAILS] Updated the `04-dsl` template * Remove Sass and Coffeescript assets * Copy files from Github rather than locally --- .../lib/rails/templates/04-dsl.rb | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/elasticsearch-rails/lib/rails/templates/04-dsl.rb b/elasticsearch-rails/lib/rails/templates/04-dsl.rb index 0c3eb9e49..d5c5d7aa4 100644 --- a/elasticsearch-rails/lib/rails/templates/04-dsl.rb +++ b/elasticsearch-rails/lib/rails/templates/04-dsl.rb @@ -1,4 +1,4 @@ -# $ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/04-dsl.rb +# $ rails new searchapp --skip --skip-bundle --template https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/04-dsl.rb # (See: 01-basic.rb, 02-pretty.rb, 03-expert.rb) @@ -15,13 +15,16 @@ git add: "README.rdoc" git commit: "-m '[03] Updated the application README'" +run 'rm -f app/assets/stylesheets/*.scss' +run 'rm -f app/assets/javascripts/*.coffee' + # ----- Add gems into Gemfile --------------------------------------------------------------------- puts say_status "Rubygems", "Adding Rubygems into Gemfile...\n", :yellow puts '-'*80, ''; sleep 0.25 -gem "elasticsearch-dsl", git: "git://github.com/elasticsearch/elasticsearch-ruby.git", branch: 'dsl' +gem "elasticsearch-dsl", git: "git://github.com/elastic/elasticsearch-ruby.git" git add: "Gemfile*" git commit: "-m 'Added the `elasticsearch-dsl` gem'" @@ -32,13 +35,13 @@ # ----- Change the search definition implementation and associated views and tests ---------------- -copy_file File.expand_path('../searchable.dsl.rb', __FILE__), 'app/models/concerns/searchable.rb', force: true -# get 'https://raw.github.com/elasticsearch/elasticsearch-rails/templates/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb', -# 'app/models/concerns/searchable.rb' +# copy_file File.expand_path('../searchable.dsl.rb', __FILE__), 'app/models/concerns/searchable.rb', force: true +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb', + 'app/models/concerns/searchable.rb' -copy_file File.expand_path('../index.html.dsl.erb', __FILE__), 'app/views/search/index.html.erb', force: true -# get 'https://raw.github.com/elasticsearch/elasticsearch-rails/templates/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb', -# 'app/views/search/index.html.erb' +# copy_file File.expand_path('../index.html.dsl.erb', __FILE__), 'app/views/search/index.html.erb', force: true +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb', + 'app/views/search/index.html.erb' gsub_file "test/controllers/search_controller_test.rb", %r{test "should return facets" do.*?end}m, <<-CODE test "should return aggregations" do @@ -92,7 +95,7 @@ CODE git add: "app/models/concerns/ app/views/search/ test/controllers/search_controller_test.rb" -git commit: "-m 'Updated the Article.search method to use the Ruby DSL; Updated the views and tests'" +git commit: "-m 'Updated the Article.search method to use the Ruby DSL and updated the associated views and tests'" # ----- Print Git log ----------------------------------------------------------------------------- From 9ae94f2d849aab697aff11fc44a11422044aa4fa Mon Sep 17 00:00:00 2001 From: Miguel Fernandez Date: Tue, 17 Mar 2015 17:52:28 +0100 Subject: [PATCH 188/582] [RAILS] Fix lograge test for versions > 0.3.0 --- elasticsearch-rails/test/unit/instrumentation/lograge_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/elasticsearch-rails/test/unit/instrumentation/lograge_test.rb b/elasticsearch-rails/test/unit/instrumentation/lograge_test.rb index 21b5d49ed..f7984daef 100644 --- a/elasticsearch-rails/test/unit/instrumentation/lograge_test.rb +++ b/elasticsearch-rails/test/unit/instrumentation/lograge_test.rb @@ -1,6 +1,7 @@ require 'test_helper' require 'rails/railtie' +require 'action_pack' require 'lograge' require 'elasticsearch/rails/lograge' From 9dd5ba32167ef8bdbad1298972ad5d28d9eee76b Mon Sep 17 00:00:00 2001 From: Miguel Fernandez Date: Tue, 17 Mar 2015 17:51:36 +0100 Subject: [PATCH 189/582] [MODEL] Do not require models to respond to `ancestors` Related: #10, #30, #50, #129, #346 --- .../lib/elasticsearch/model/adapters/active_record.rb | 2 +- elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb index 0dd9e4e99..b0f61bc8a 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb @@ -7,7 +7,7 @@ module Adapter module ActiveRecord Adapter.register self, - lambda { |klass| !!defined?(::ActiveRecord::Base) && klass.ancestors.include?(::ActiveRecord::Base) } + lambda { |klass| !!defined?(::ActiveRecord::Base) && klass.respond_to?(:ancestors) && klass.ancestors.include?(::ActiveRecord::Base) } module Records # Returns an `ActiveRecord::Relation` instance diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb index 51c9e1be3..f59ed9966 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb @@ -9,7 +9,7 @@ module Adapter module Mongoid Adapter.register self, - lambda { |klass| !!defined?(::Mongoid::Document) && klass.ancestors.include?(::Mongoid::Document) } + lambda { |klass| !!defined?(::Mongoid::Document) && klass.respond_to?(:ancestors) && klass.ancestors.include?(::Mongoid::Document) } module Records From 0a811a9cc127f755b44391f22f6ace7450eaedde Mon Sep 17 00:00:00 2001 From: Miguel Fernandez Date: Tue, 17 Mar 2015 17:52:02 +0100 Subject: [PATCH 190/582] [MODEL] Update README.md with multi-model usage examples Related: #10, #30, #50, #129, #346 --- elasticsearch-model/README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index c8d828820..d2404e602 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -172,6 +172,12 @@ response.results.first._source.title # => "Quick brown fox" ``` +Or if we want to perform the search across different models: + +```ruby +Elasticsearch::Model.search 'fox dogs', [Article, Comment] +``` + #### Search results The returned `response` object is a rich wrapper around the JSON returned from Elasticsearch, @@ -216,7 +222,17 @@ response.records.to_a ``` The returned object is the genuine collection of model instances returned by your database, -i.e. `ActiveRecord::Relation` for ActiveRecord, or `Mongoid::Criteria` in case of MongoDB. This allows you to +i.e. `ActiveRecord::Relation` for ActiveRecord, or `Mongoid::Criteria` in case of MongoDB. +When the search is performed across different models, an Array of model instances is returned. + +```ruby +Elasticsearch::Model.search('fox', [Article, Comment]).records +# Article Load (0.3ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" IN (1) +# Comment Load (0.2ms) SELECT "comments".* FROM "comments" WHERE "comments"."id" IN (1,5) +# => [#
, #, #] +``` + +In the case of searching a single model, this allows you to chain other methods on top of search results, as you would normally do: ```ruby From d6cce611c487ecee934b36809850f4cba532b581 Mon Sep 17 00:00:00 2001 From: Miguel Fernandez Date: Tue, 17 Mar 2015 17:55:47 +0100 Subject: [PATCH 191/582] [MODEL] Extract test helpers for mongo Related: #10, #30, #50, #129, #346 --- .../test/integration/mongoid_basic_test.rb | 24 +++------------ elasticsearch-model/test/test_helper.rb | 30 +++++++++++++++++++ 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/elasticsearch-model/test/integration/mongoid_basic_test.rb b/elasticsearch-model/test/integration/mongoid_basic_test.rb index 749446369..e370bd82a 100644 --- a/elasticsearch-model/test/integration/mongoid_basic_test.rb +++ b/elasticsearch-model/test/integration/mongoid_basic_test.rb @@ -1,25 +1,9 @@ require 'test_helper' -begin - require 'mongoid' - session = Moped::Connection.new("localhost", 27017, 0.5) - session.connect - ENV["MONGODB_AVAILABLE"] = 'yes' -rescue LoadError, Moped::Errors::ConnectionFailure => e - $stderr.puts "MongoDB not installed or running: #{e}" -end - -if ENV["MONGODB_AVAILABLE"] - $stderr.puts "Mongoid #{Mongoid::VERSION}", '-'*80 - - logger = ::Logger.new($stderr) - logger.formatter = lambda { |s, d, p, m| " #{m.ansi(:faint, :cyan)}\n" } - logger.level = ::Logger::DEBUG - - Mongoid.logger = logger unless ENV['QUIET'] - Moped.logger = logger unless ENV['QUIET'] +Mongo.setup! - Mongoid.connect_to 'mongoid_articles' +if Mongo.available? + Mongo.connect_to 'mongoid_articles' module Elasticsearch module Model @@ -50,7 +34,7 @@ def as_indexed_json(options={}) setup do Elasticsearch::Model::Adapter.register \ Elasticsearch::Model::Adapter::Mongoid, - lambda { |klass| !!defined?(::Mongoid::Document) && klass.ancestors.include?(::Mongoid::Document) } + lambda { |klass| !!defined?(::Mongoid::Document) && klass.respond_to?(:ancestors) && klass.ancestors.include?(::Mongoid::Document) } MongoidArticle.__elasticsearch__.create_index! force: true diff --git a/elasticsearch-model/test/test_helper.rb b/elasticsearch-model/test/test_helper.rb index dc8b175db..ff3a6d935 100644 --- a/elasticsearch-model/test/test_helper.rb +++ b/elasticsearch-model/test/test_helper.rb @@ -61,3 +61,33 @@ def setup end end end + +class Mongo + def self.setup! + begin + require 'mongoid' + session = Moped::Connection.new("localhost", 27017, 0.5) + session.connect + ENV['MONGODB_AVAILABLE'] = 'yes' + rescue LoadError, Moped::Errors::ConnectionFailure => e + $stderr.puts "MongoDB not installed or running: #{e}" + end + end + + def self.available? + !!ENV['MONGODB_AVAILABLE'] + end + + def self.connect_to(source) + $stderr.puts "Mongoid #{Mongoid::VERSION}", '-'*80 + + logger = ::Logger.new($stderr) + logger.formatter = lambda { |s, d, p, m| " #{m.ansi(:faint, :cyan)}\n" } + logger.level = ::Logger::DEBUG + + Mongoid.logger = logger unless ENV['QUIET'] + Moped.logger = logger unless ENV['QUIET'] + + Mongoid.connect_to source + end +end From 6dd4972d6bf4e654b78d3ec768a0f6e5124e7b03 Mon Sep 17 00:00:00 2001 From: Miguel Fernandez Date: Tue, 17 Mar 2015 17:56:10 +0100 Subject: [PATCH 192/582] [MODEL] Implement multi-model search Related: #10, #30, #50, #129, #346 --- .../lib/elasticsearch/model.rb | 72 ++++++++ .../elasticsearch/model/adapters/multiple.rb | 125 ++++++++++++++ .../lib/elasticsearch/model/multimodel.rb | 43 +++++ .../test/integration/multiple_models_test.rb | 156 ++++++++++++++++++ .../test/unit/adapter_multiple_test.rb | 106 ++++++++++++ .../test/unit/multimodel_test.rb | 42 +++++ 6 files changed, 544 insertions(+) create mode 100644 elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb create mode 100644 elasticsearch-model/lib/elasticsearch/model/multimodel.rb create mode 100644 elasticsearch-model/test/integration/multiple_models_test.rb create mode 100644 elasticsearch-model/test/unit/adapter_multiple_test.rb create mode 100644 elasticsearch-model/test/unit/multimodel_test.rb diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index f73c9a5d9..15c2b2bda 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -8,10 +8,13 @@ require 'elasticsearch/model/client' +require 'elasticsearch/model/multimodel' + require 'elasticsearch/model/adapter' require 'elasticsearch/model/adapters/default' require 'elasticsearch/model/adapters/active_record' require 'elasticsearch/model/adapters/mongoid' +require 'elasticsearch/model/adapters/multiple' require 'elasticsearch/model/importing' require 'elasticsearch/model/indexing' @@ -64,6 +67,50 @@ module Elasticsearch module Model METHODS = [:search, :mapping, :mappings, :settings, :index_name, :document_type, :import] + + # Keeps a registry of the classes that include `Elasticsearch::Model` + # + class Registry < Array + + # Add the class of a model to the registry + # + def self.add(klass) + __instance.add(klass) + end + + # List all the registered models + # + # @return [Class] + # + def self.all + __instance.models + end + + # Returns the unique instance of the registry + # + # @api private + # + def self.__instance + @instance ||= new + end + + def initialize + @models = [] + end + + # Adds a model to the registry + # + def add(klass) + @models << klass + end + + # Gets a copy of the registered models + # + def models + @models.dup + end + end + # Adds the `Elasticsearch::Model` functionality to the including class. # # * Creates the `__elasticsearch__` class and instance methods, pointing to the proxy object @@ -119,6 +166,9 @@ class << self include Elasticsearch::Model::Importing::ClassMethods include Adapter.from_class(base).importing_mixin end + + # Add to the registry if it's a class (and not in intermediate module) + Registry.add(base) if base.is_a?(Class) end end @@ -149,6 +199,28 @@ def client=(client) @client = client end + # Search across models which include Elasticsearch::Model + # + # @param query_or_payload [String,Hash,Object] The search request definition + # (string, JSON, Hash, or object responding to `to_hash`) + # @param models [Array] The list of Model objects to search + # @param options [Hash] Optional parameters to be passed to the Elasticsearch client + # + # @return [Elasticsearch::Model::Response::Response] + # + # @example Search across specified models + # + # Elasticsearch::Model.search('foo', [Author, Article]) + # + # @example Search across all models + # + # Elasticsearch::Model.search('foo') + # + def search(query_or_payload, models=[], options={}) + models = Multimodel.new(models) + request = Searching::SearchRequest.new(models, query_or_payload, options) + Response::Response.new(models, request) + end end extend ClassMethods diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb new file mode 100644 index 000000000..2b3eb8228 --- /dev/null +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb @@ -0,0 +1,125 @@ +module Elasticsearch + module Model + module Adapter + + # An adapter to be used for deserializing results from multiple models, retrieved through + # Elasticsearch::Model.search + # + # @see Elasticsearch::Model.search + # + module Multiple + + Adapter.register self, lambda { |klass| klass.is_a? Multimodel } + + module Records + + # Returns an Array, which elements are the model instances represented + # by the search results. + # + # This means that if the models queried are a Mixture of ActiveRecord, Mongoid, or + # POROs, the elements contained in this array will also be instances of those models + # + # Ranking of results across multiple indexes is preserved, and queries made to the different + # model's datasources are minimal. + # + # Internally, it gets the results, as ranked by elasticsearch. + # Then results are grouped by _type + # Then the model corresponding to each _type is queried to retrieve the records + # Finally records are rearranged in the same way results were ranked. + # + # @return [ElasticSearch::Model] + # + def records + @_records ||= begin + result = [] + by_type = __records_by_type + __hits.each do |hit| + result << by_type[__type(hit)][hit[:_id]] + end + result.compact + end + end + + # Returns the record representation of the results retrieved from Elasticsearch, grouped + # by model type + # + # @example + # {Series => + # {"1"=> #}, + # + # "Title => + # {"1"=> #}} + # + # @api private + # + def __records_by_type + array = __ids_by_type.map do |klass, ids| + records = __type_records(klass, ids) + ids = records.map(&:id).map(&:to_s) + [klass, Hash[ids.zip(records)]] + end + Hash[array] + end + + # Returns the records for a specific type + # + # @api private + # + def __type_records(klass, ids) + if (adapter = Adapter.adapters[ActiveRecord]) && adapter.call(klass) + klass.where(klass.primary_key => ids) + elsif (adapter = Adapter.adapters[Mongoid]) && adapter.call(klass) + klass.where(:id.in => ids) + else + klass.find(ids) + end + end + + + # @return A Hash containing for each type, the ids to retrieve + # + # @example {Series =>["1"], Title =>["1", "5"]} + # + # @api private + # + def __ids_by_type + ids_by_type = {} + __hits.each do |hit| + type = __type(hit) + ids_by_type[type] ||= [] + ids_by_type[type] << hit[:_id] + end + ids_by_type + end + + # Returns the class of the model associated to a certain hit + # + # A simple class-level memoization over the `_index` and `_type` properties of the hit is applied. + # Hence querying the Model Registry is done the minimal amount of times. + # + # @see Elasticsearch::Model::Registry + # + # @return Class + # + # @api private + # + def __type(hit) + @@__types ||= {} + @@__types[[hit[:_index], hit[:_type]].join("::")] ||= begin + Registry.all.detect { |model| model.index_name == hit[:_index] && model.document_type == hit[:_type] } + end + end + + + # Memoizes and returns the hits from the response + # + # @api private + # + def __hits + @__hits ||= response.response["hits"]["hits"] + end + end + end + end + end +end diff --git a/elasticsearch-model/lib/elasticsearch/model/multimodel.rb b/elasticsearch-model/lib/elasticsearch/model/multimodel.rb new file mode 100644 index 000000000..0995d2d33 --- /dev/null +++ b/elasticsearch-model/lib/elasticsearch/model/multimodel.rb @@ -0,0 +1,43 @@ +module Elasticsearch + module Model + + # Wraps a series of models to be used when querying multiple indexes via Elasticsearch::Model.search + # + # @see Elasticsearch::Model.search + # + class Multimodel + + attr_reader :models + + # @param models [Class] The list of models across which the search will be performed. + def initialize(*models) + @models = models.flatten + @models = Model::Registry.all if @models.empty? + end + + # Get the list of index names used for retrieving documents when doing a search across multiple models + # + # @return [String] the list of index names used for retrieving documents + # + def index_name + models.map { |m| m.index_name } + end + + # Get the list of document types used for retrieving documents when doing a search across multiple models + # + # @return [String] the list of document types used for retrieving documents + # + def document_type + models.map { |m| m.document_type } + end + + # Get the client common for all models + # + # @return Elasticsearch::Transport::Client + # + def client + Elasticsearch::Model.client + end + end + end +end diff --git a/elasticsearch-model/test/integration/multiple_models_test.rb b/elasticsearch-model/test/integration/multiple_models_test.rb new file mode 100644 index 000000000..dc5ffe966 --- /dev/null +++ b/elasticsearch-model/test/integration/multiple_models_test.rb @@ -0,0 +1,156 @@ +require 'test_helper' +require 'active_record' + +Mongo.setup! + +module Elasticsearch + module Model + class MultipleModelsIntegraton < Elasticsearch::Test::IntegrationTestCase + context "Multiple models" do + setup do + ActiveRecord::Schema.define(:version => 1) do + create_table :episodes do |t| + t.string :name + t.datetime :created_at, :default => 'NOW()' + end + + create_table :series do |t| + t.string :name + t.datetime :created_at, :default => 'NOW()' + end + end + + class ::Episode < ActiveRecord::Base + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + settings index: {number_of_shards: 1, number_of_replicas: 0} do + mapping do + indexes :name, type: 'string', analyzer: 'snowball' + indexes :created_at, type: 'date' + end + end + end + + class ::Series < ActiveRecord::Base + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + settings index: {number_of_shards: 1, number_of_replicas: 0} do + mapping do + indexes :name, type: 'string', analyzer: 'snowball' + indexes :created_at, type: 'date' + end + end + end + + [::Episode, ::Series].each do |model| + model.delete_all + model.__elasticsearch__.create_index! force: true + model.create name: "The #{model.name}" + model.create name: "A great #{model.name}" + model.create name: "The greatest #{model.name}" + model.__elasticsearch__.refresh_index! + end + + end + + should "find matching documents across multiple models" do + response = Elasticsearch::Model.search("greatest", [Series, Episode]) + + assert response.any?, "Response should not be empty: #{response.to_a.inspect}" + + assert_equal 2, response.results.size + assert_equal 2, response.records.size + + assert_instance_of Elasticsearch::Model::Response::Result, response.results.first + assert_instance_of Episode, response.records.first + assert_instance_of Series, response.records.last + + assert_equal 'The greatest Episode', response.results[0].name + assert_equal 'The greatest Episode', response.records[0].name + + assert_equal 'The greatest Series', response.results[1].name + assert_equal 'The greatest Series', response.records[1].name + end + + should "provide access to result" do + q = {query: {query_string: {query: 'A great *'}}, highlight: {fields: {name: {}}}} + response = Elasticsearch::Model.search(q, [Series, Episode]) + + first_result, second_result = *response.results + + assert_equal 'A great Episode', first_result.name + assert_equal true, first_result.name? + assert_equal false, first_result.boo? + assert_equal true, first_result.highlight? + assert_equal true, first_result.highlight.name? + assert_equal false, first_result.highlight.boo? + + assert_equal 'A great Series', second_result.name + assert_equal true, second_result.name? + assert_equal false, second_result.boo? + assert_equal true, second_result.highlight? + assert_equal true, second_result.highlight.name? + assert_equal false, second_result.highlight.boo? + end + + if Mongo.available? + Mongo.connect_to 'mongoid_collections' + + context "Across mongoid models" do + setup do + class ::Image + include Mongoid::Document + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + field :name, type: String + attr_accessible :name if respond_to? :attr_accessible + + settings index: {number_of_shards: 1, number_of_replicas: 0} do + mapping do + indexes :name, type: 'string', analyzer: 'snowball' + indexes :created_at, type: 'date' + end + end + + def as_indexed_json(options={}) + as_json(except: [:_id]) + end + end + + Image.delete_all + Image.__elasticsearch__.create_index! force: true + Image.create! name: "The Image" + Image.create! name: "A great Image" + Image.create! name: "The greatest Image" + Image.__elasticsearch__.refresh_index! + Image.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' + end + + should "find matching documents across multiple models" do + response = Elasticsearch::Model.search("greatest", [Episode, Image]) + + assert response.any?, "Response should not be empty: #{response.to_a.inspect}" + + assert_equal 2, response.results.size + assert_equal 2, response.records.size + + assert_instance_of Elasticsearch::Model::Response::Result, response.results.first + assert_instance_of Image, response.records.first + assert_instance_of Episode, response.records.last + + assert_equal 'The greatest Image', response.results[0].name + assert_equal 'The greatest Image', response.records[0].name + + assert_equal 'The greatest Episode', response.results[1].name + assert_equal 'The greatest Episode', response.records[1].name + end + end + end + + end + end + end +end diff --git a/elasticsearch-model/test/unit/adapter_multiple_test.rb b/elasticsearch-model/test/unit/adapter_multiple_test.rb new file mode 100644 index 000000000..2fb9d88a1 --- /dev/null +++ b/elasticsearch-model/test/unit/adapter_multiple_test.rb @@ -0,0 +1,106 @@ +require 'test_helper' + +class Elasticsearch::Model::MultipleTest < Test::Unit::TestCase + + context "Adapter multiple module" do + + class ::DummyOne + include Elasticsearch::Model + + index_name 'dummy' + document_type 'dummy_one' + + def self.find(ids) + ids.map { |id| new(id) } + end + + attr_reader :id + + def initialize(id) + @id = id.to_i + end + end + + module ::Namespace + class DummyTwo + include Elasticsearch::Model + + index_name 'dummy' + document_type 'dummy_two' + + def self.find(ids) + ids.map { |id| new(id) } + end + + attr_reader :id + + def initialize(id) + @id = id.to_i + end + end + end + + class ::DummyTwo + include Elasticsearch::Model + + index_name 'other_index' + document_type 'dummy_two' + + def self.find(ids) + ids.map { |id| new(id) } + end + + attr_reader :id + + def initialize(id) + @id = id.to_i + end + end + + HITS = [{_index: 'dummy', + _type: 'dummy_two', + _id: '2', + }, { + _index: 'dummy', + _type: 'dummy_one', + _id: '2', + }, { + _index: 'other_index', + _type: 'dummy_two', + _id: '1', + }, { + _index: 'dummy', + _type: 'dummy_two', + _id: '1', + }, { + _index: 'dummy', + _type: 'dummy_one', + _id: '3'}] + + setup do + @multimodel = Elasticsearch::Model::Multimodel.new(DummyOne, DummyTwo, Namespace::DummyTwo) + end + + context "Records" do + setup do + @multimodel.class.send :include, Elasticsearch::Model::Adapter::Multiple::Records + @multimodel.expects(:__hits).at_least_once.returns(HITS) + end + + should "keep global order among models" do + assert_instance_of Module, Elasticsearch::Model::Adapter::Multiple::Records + records = @multimodel.records + + assert_equal 5, records.count + + assert_kind_of ::Namespace::DummyTwo, records[0] + assert_kind_of ::DummyOne, records[1] + assert_kind_of ::DummyTwo, records[2] + assert_kind_of ::Namespace::DummyTwo, records[3] + assert_kind_of ::DummyOne, records[4] + + assert_equal [2, 2, 1, 1, 3], records.map(&:id) + end + end + end +end diff --git a/elasticsearch-model/test/unit/multimodel_test.rb b/elasticsearch-model/test/unit/multimodel_test.rb new file mode 100644 index 000000000..cd7809fd3 --- /dev/null +++ b/elasticsearch-model/test/unit/multimodel_test.rb @@ -0,0 +1,42 @@ +require 'test_helper' + +class Elasticsearch::Model::MultimodelTest < Test::Unit::TestCase + + context "Multimodel class" do + setup do + title = stub('Title', index_name: 'titles_index', document_type: 'title') + series = stub('Series', index_name: 'series_index', document_type: 'series') + @multimodel = Elasticsearch::Model::Multimodel.new(title, series) + end + + should "#index_name" do + assert_equal ['titles_index', 'series_index'], @multimodel.index_name + end + + should "#document_type" do + assert_equal ['title', 'series'], @multimodel.document_type + end + + should "#client" do + assert_equal Elasticsearch::Model.client, @multimodel.client + end + + should "default intialization" do + class ::JustAModel + include Elasticsearch::Model + + document_type "just_a_model" + end + + class ::JustAnotherModel + include Elasticsearch::Model + + document_type "just_another_model" + end + + multimodel = Elasticsearch::Model::Multimodel.new + assert multimodel.models.include?(::JustAModel) + assert multimodel.models.include?(::JustAnotherModel) + end + end +end From 1d71fbabb7c222fda32af590ad527d7c113df305 Mon Sep 17 00:00:00 2001 From: Miguel Fernandez <miguelfernandezfernandez@gmail.com> Date: Mon, 23 Mar 2015 15:45:08 +0100 Subject: [PATCH 193/582] [MODEL] Minor improvements to the multimodel search Registry is not an array Modify _types to only iterate to necessary models Related: #10, #30, #50, #129, #346 --- elasticsearch-model/lib/elasticsearch/model.rb | 2 +- .../lib/elasticsearch/model/adapters/multiple.rb | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index 15c2b2bda..71fc2548f 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -70,7 +70,7 @@ module Model # Keeps a registry of the classes that include `Elasticsearch::Model` # - class Registry < Array + class Registry # Add the class of a model to the registry # diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb index 2b3eb8228..486f717d6 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb @@ -97,6 +97,10 @@ def __ids_by_type # A simple class-level memoization over the `_index` and `_type` properties of the hit is applied. # Hence querying the Model Registry is done the minimal amount of times. # + # Event though memoization happens at the class level, the side effect of a race condition will only be + # to iterate over models one extra time, so we can consider the method thread-safe, and don't include + # any Mutex.synchronize around the method implementaion + # # @see Elasticsearch::Model::Registry # # @return Class @@ -106,7 +110,7 @@ def __ids_by_type def __type(hit) @@__types ||= {} @@__types[[hit[:_index], hit[:_type]].join("::")] ||= begin - Registry.all.detect { |model| model.index_name == hit[:_index] && model.document_type == hit[:_type] } + models.detect { |model| model.index_name == hit[:_index] && model.document_type == hit[:_type] } end end From 3c8e052c34c6036b0c55c80eecff5d5a7c2a701e Mon Sep 17 00:00:00 2001 From: Miguel Fernandez <miguelfernandezfernandez@gmail.com> Date: Tue, 7 Apr 2015 17:30:02 +0200 Subject: [PATCH 194/582] [MODEL] Revert multiple_adapter to lookup over Registry instead of models reader Related: #10, #30, #50, #129, #346 --- .../lib/elasticsearch/model/adapters/multiple.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb index 486f717d6..3277597b9 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb @@ -110,7 +110,7 @@ def __ids_by_type def __type(hit) @@__types ||= {} @@__types[[hit[:_index], hit[:_type]].join("::")] ||= begin - models.detect { |model| model.index_name == hit[:_index] && model.document_type == hit[:_type] } + Registry.all.detect { |model| model.index_name == hit[:_index] && model.document_type == hit[:_type] } end end From b26b67a9b6719177ac6b0265aef580448048a360 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Wed, 8 Apr 2015 14:41:22 +0200 Subject: [PATCH 195/582] [MODEL] Refactorings and cleanups for the multi-model search Many thanks to @miguelff for the implementation! * Rephrase documentation in README and code annotations * Move the `Registry` class to the multimodel.rb file * Clean up adapters/multiple.rb * Tweak test descriptions and values Closes #345 Closes #10 Closes #30 Closes #50 Closes #129 --- elasticsearch-model/README.md | 33 +++-- .../lib/elasticsearch/model.rb | 54 +------- .../elasticsearch/model/adapters/multiple.rb | 115 ++++++++---------- .../lib/elasticsearch/model/multimodel.rb | 54 ++++++-- .../test/integration/multiple_models_test.rb | 32 +++-- .../test/unit/adapter_multiple_test.rb | 8 +- .../test/unit/multimodel_test.rb | 20 ++- 7 files changed, 150 insertions(+), 166 deletions(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index d2404e602..c0cd946e4 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -172,7 +172,7 @@ response.results.first._source.title # => "Quick brown fox" ``` -Or if we want to perform the search across different models: +To search across multiple models, use the module method: ```ruby Elasticsearch::Model.search 'fox dogs', [Article, Comment] @@ -223,17 +223,8 @@ response.records.to_a The returned object is the genuine collection of model instances returned by your database, i.e. `ActiveRecord::Relation` for ActiveRecord, or `Mongoid::Criteria` in case of MongoDB. -When the search is performed across different models, an Array of model instances is returned. -```ruby -Elasticsearch::Model.search('fox', [Article, Comment]).records -# Article Load (0.3ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" IN (1) -# Comment Load (0.2ms) SELECT "comments".* FROM "comments" WHERE "comments"."id" IN (1,5) -# => [#<Article id: 1, title: "Quick brown fox">, #<Comment id: 1, body: "I like foxes">, #<Comment id: 5, body: "Michael J.Fox is my favorite actor">] -``` - -In the case of searching a single model, this allows you to -chain other methods on top of search results, as you would normally do: +This allows you to chain other methods on top of search results, as you would normally do: ```ruby response.records.where(title: 'Quick brown fox').to_a @@ -268,6 +259,26 @@ response.records.each_with_hit { |record, hit| puts "* #{record.title}: #{hit._s # * Fast black dogs: 0.02250402 ``` +It is possible to search across multiple models with the module method: + +```ruby +Elasticsearch::Model.search('fox', [Article, Comment]).results.to_a.map(&:to_hash) +# => [ +# {"_index"=>"articles", "_type"=>"article", "_id"=>"1", "_score"=>0.35136628, "_source"=>...}, +# {"_index"=>"comments", "_type"=>"comment", "_id"=>"1", "_score"=>0.35136628, "_source"=>...} +# ] + +Elasticsearch::Model.search('fox', [Article, Comment]).records.to_a +# Article Load (0.3ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" IN (1) +# Comment Load (0.2ms) SELECT "comments".* FROM "comments" WHERE "comments"."id" IN (1,5) +# => [#<Article id: 1, title: "Quick brown fox">, #<Comment id: 1, body: "Fox News">, ...] +``` + +By default, all models which include the `Elasticsearch::Model` module are searched. + +NOTE: It is _not_ possible to chain other methods on top of the `records` object, since it + is a heterogenous collection, with models potentially backed by different databases. + #### Pagination You can implement pagination with the `from` and `size` search parameters. However, search results diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index 71fc2548f..5cb9ff2b1 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -67,50 +67,6 @@ module Elasticsearch module Model METHODS = [:search, :mapping, :mappings, :settings, :index_name, :document_type, :import] - - # Keeps a registry of the classes that include `Elasticsearch::Model` - # - class Registry - - # Add the class of a model to the registry - # - def self.add(klass) - __instance.add(klass) - end - - # List all the registered models - # - # @return [Class] - # - def self.all - __instance.models - end - - # Returns the unique instance of the registry - # - # @api private - # - def self.__instance - @instance ||= new - end - - def initialize - @models = [] - end - - # Adds a model to the registry - # - def add(klass) - @models << klass - end - - # Gets a copy of the registered models - # - def models - @models.dup - end - end - # Adds the `Elasticsearch::Model` functionality to the including class. # # * Creates the `__elasticsearch__` class and instance methods, pointing to the proxy object @@ -199,20 +155,22 @@ def client=(client) @client = client end - # Search across models which include Elasticsearch::Model + # Search across multiple models + # + # By default, all models which include the `Elasticsearch::Model` module are searched # # @param query_or_payload [String,Hash,Object] The search request definition # (string, JSON, Hash, or object responding to `to_hash`) - # @param models [Array] The list of Model objects to search + # @param models [Array] The Array of Model objects to search # @param options [Hash] Optional parameters to be passed to the Elasticsearch client # # @return [Elasticsearch::Model::Response::Response] # - # @example Search across specified models + # @example Search across specific models # # Elasticsearch::Model.search('foo', [Author, Article]) # - # @example Search across all models + # @example Search across all models which include the `Elasticsearch::Model` module # # Elasticsearch::Model.search('foo') # diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb index 3277597b9..72e31306b 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb @@ -2,125 +2,106 @@ module Elasticsearch module Model module Adapter - # An adapter to be used for deserializing results from multiple models, retrieved through - # Elasticsearch::Model.search + # An adapter to be used for deserializing results from multiple models, + # retrieved through `Elasticsearch::Model.search` # # @see Elasticsearch::Model.search # module Multiple - Adapter.register self, lambda { |klass| klass.is_a? Multimodel } module Records - - # Returns an Array, which elements are the model instances represented - # by the search results. - # - # This means that if the models queried are a Mixture of ActiveRecord, Mongoid, or - # POROs, the elements contained in this array will also be instances of those models - # - # Ranking of results across multiple indexes is preserved, and queries made to the different - # model's datasources are minimal. - # - # Internally, it gets the results, as ranked by elasticsearch. - # Then results are grouped by _type - # Then the model corresponding to each _type is queried to retrieve the records - # Finally records are rearranged in the same way results were ranked. + # Returns a collection of model instances, possibly of different classes (ActiveRecord, Mongoid, ...) # - # @return [ElasticSearch::Model] + # @note The order of results in the Elasticsearch response is preserved # def records - @_records ||= begin - result = [] - by_type = __records_by_type - __hits.each do |hit| - result << by_type[__type(hit)][hit[:_id]] - end - result.compact + records_by_type = __records_by_type + + response.response["hits"]["hits"].map do |hit| + records_by_type[ __type_for_hit(hit) ][ hit[:_id] ] end end - # Returns the record representation of the results retrieved from Elasticsearch, grouped - # by model type + # Returns the collection of records grouped by class based on `_type` # - # @example - # {Series => - # {"1"=> #<Series id: 1, series_name: "The Who S01", created_at: "2015-02-23 17:18:28">}, + # Example: # - # "Title => - # {"1"=> #<Title id: 1, name: "Who Strikes Back", created_at: "2015-02-23 17:18:28">}} + # { + # Foo => {"1"=> #<Foo id: 1, title: "ABC"}, ...}, + # Bar => {"1"=> #<Bar id: 1, name: "XYZ"}, ...} + # } # # @api private # def __records_by_type - array = __ids_by_type.map do |klass, ids| - records = __type_records(klass, ids) - ids = records.map(&:id).map(&:to_s) - [klass, Hash[ids.zip(records)]] + result = __ids_by_type.map do |klass, ids| + records = __records_for_klass(klass, ids) + ids = records.map(&:id).map(&:to_s) + [ klass, Hash[ids.zip(records)] ] end - Hash[array] + + Hash[result] end - # Returns the records for a specific type + # Returns the collection of records for a specific type based on passed `klass` # # @api private # - def __type_records(klass, ids) - if (adapter = Adapter.adapters[ActiveRecord]) && adapter.call(klass) - klass.where(klass.primary_key => ids) - elsif (adapter = Adapter.adapters[Mongoid]) && adapter.call(klass) - klass.where(:id.in => ids) - else - klass.find(ids) + def __records_for_klass(klass, ids) + adapter = __adapter_name_for_klass(klass) + + case adapter + when Elasticsearch::Model::Adapter::ActiveRecord + klass.where(klass.primary_key => ids) + when Elasticsearch::Model::Adapter::Mongoid + klass.where(:id.in => ids) + else + klass.find(ids) end end - - # @return A Hash containing for each type, the ids to retrieve + # Returns the record IDs grouped by class based on type `_type` + # + # Example: # - # @example {Series =>["1"], Title =>["1", "5"]} + # { Foo => ["1"], Bar => ["1", "5"] } # # @api private # def __ids_by_type ids_by_type = {} - __hits.each do |hit| - type = __type(hit) + + response.response["hits"]["hits"].each do |hit| + type = __type_for_hit(hit) ids_by_type[type] ||= [] ids_by_type[type] << hit[:_id] end ids_by_type end - # Returns the class of the model associated to a certain hit - # - # A simple class-level memoization over the `_index` and `_type` properties of the hit is applied. - # Hence querying the Model Registry is done the minimal amount of times. - # - # Event though memoization happens at the class level, the side effect of a race condition will only be - # to iterate over models one extra time, so we can consider the method thread-safe, and don't include - # any Mutex.synchronize around the method implementaion + # Returns the class of the model corresponding to a specific `hit` in Elasticsearch results # # @see Elasticsearch::Model::Registry # - # @return Class - # # @api private # - def __type(hit) + def __type_for_hit(hit) @@__types ||= {} - @@__types[[hit[:_index], hit[:_type]].join("::")] ||= begin - Registry.all.detect { |model| model.index_name == hit[:_index] && model.document_type == hit[:_type] } + + @@__types[ "#{hit[:_index]}::#{hit[:_type]}" ] ||= begin + Registry.all.detect do |model| + model.index_name == hit[:_index] && model.document_type == hit[:_type] + end end end - - # Memoizes and returns the hits from the response + # Returns the adapter registered for a particular `klass` or `nil` if not available # # @api private # - def __hits - @__hits ||= response.response["hits"]["hits"] + def __adapter_name_for_klass(klass) + Adapter.adapters.select { |name, checker| checker.call(klass) }.keys.first end end end diff --git a/elasticsearch-model/lib/elasticsearch/model/multimodel.rb b/elasticsearch-model/lib/elasticsearch/model/multimodel.rb index 0995d2d33..8831d4fd0 100644 --- a/elasticsearch-model/lib/elasticsearch/model/multimodel.rb +++ b/elasticsearch-model/lib/elasticsearch/model/multimodel.rb @@ -1,31 +1,71 @@ module Elasticsearch module Model - # Wraps a series of models to be used when querying multiple indexes via Elasticsearch::Model.search + # Keeps a global registry of classes that include `Elasticsearch::Model` + # + class Registry + def initialize + @models = [] + end + + # Returns the unique instance of the registry (Singleton) + # + # @api private + # + def self.__instance + @instance ||= new + end + + # Adds a model to the registry + # + def self.add(klass) + __instance.add(klass) + end + + # Returns an Array of registered models + # + def self.all + __instance.models + end + + # Adds a model to the registry + # + def add(klass) + @models << klass + end + + # Returns a copy of the registered models + # + def models + @models.dup + end + end + + # Wraps a collection of models when querying multiple indices # # @see Elasticsearch::Model.search # class Multimodel - attr_reader :models - # @param models [Class] The list of models across which the search will be performed. + # @param models [Class] The list of models across which the search will be performed + # def initialize(*models) @models = models.flatten @models = Model::Registry.all if @models.empty? end - # Get the list of index names used for retrieving documents when doing a search across multiple models + # Get an Array of index names used for retrieving documents when doing a search across multiple models # - # @return [String] the list of index names used for retrieving documents + # @return [Array] the list of index names used for retrieving documents # def index_name models.map { |m| m.index_name } end - # Get the list of document types used for retrieving documents when doing a search across multiple models + # Get an Array of document types used for retrieving documents when doing a search across multiple models # - # @return [String] the list of document types used for retrieving documents + # @return [Array] the list of document types used for retrieving documents # def document_type models.map { |m| m.document_type } diff --git a/elasticsearch-model/test/integration/multiple_models_test.rb b/elasticsearch-model/test/integration/multiple_models_test.rb index dc5ffe966..099ab4504 100644 --- a/elasticsearch-model/test/integration/multiple_models_test.rb +++ b/elasticsearch-model/test/integration/multiple_models_test.rb @@ -5,7 +5,7 @@ module Elasticsearch module Model - class MultipleModelsIntegraton < Elasticsearch::Test::IntegrationTestCase + class MultipleModelsIntegration < Elasticsearch::Test::IntegrationTestCase context "Multiple models" do setup do ActiveRecord::Schema.define(:version => 1) do @@ -74,25 +74,23 @@ class ::Series < ActiveRecord::Base assert_equal 'The greatest Series', response.records[1].name end - should "provide access to result" do + should "provide access to results" do q = {query: {query_string: {query: 'A great *'}}, highlight: {fields: {name: {}}}} response = Elasticsearch::Model.search(q, [Series, Episode]) - first_result, second_result = *response.results - - assert_equal 'A great Episode', first_result.name - assert_equal true, first_result.name? - assert_equal false, first_result.boo? - assert_equal true, first_result.highlight? - assert_equal true, first_result.highlight.name? - assert_equal false, first_result.highlight.boo? - - assert_equal 'A great Series', second_result.name - assert_equal true, second_result.name? - assert_equal false, second_result.boo? - assert_equal true, second_result.highlight? - assert_equal true, second_result.highlight.name? - assert_equal false, second_result.highlight.boo? + assert_equal 'A great Episode', response.results[0].name + assert_equal true, response.results[0].name? + assert_equal false, response.results[0].boo? + assert_equal true, response.results[0].highlight? + assert_equal true, response.results[0].highlight.name? + assert_equal false, response.results[0].highlight.boo? + + assert_equal 'A great Series', response.results[1].name + assert_equal true, response.results[1].name? + assert_equal false, response.results[1].boo? + assert_equal true, response.results[1].highlight? + assert_equal true, response.results[1].highlight.name? + assert_equal false, response.results[1].highlight.boo? end if Mongo.available? diff --git a/elasticsearch-model/test/unit/adapter_multiple_test.rb b/elasticsearch-model/test/unit/adapter_multiple_test.rb index 2fb9d88a1..b848286fb 100644 --- a/elasticsearch-model/test/unit/adapter_multiple_test.rb +++ b/elasticsearch-model/test/unit/adapter_multiple_test.rb @@ -2,7 +2,7 @@ class Elasticsearch::Model::MultipleTest < Test::Unit::TestCase - context "Adapter multiple module" do + context "Adapter for multiple models" do class ::DummyOne include Elasticsearch::Model @@ -81,13 +81,13 @@ def initialize(id) @multimodel = Elasticsearch::Model::Multimodel.new(DummyOne, DummyTwo, Namespace::DummyTwo) end - context "Records" do + context "when returning records" do setup do @multimodel.class.send :include, Elasticsearch::Model::Adapter::Multiple::Records - @multimodel.expects(:__hits).at_least_once.returns(HITS) + @multimodel.expects(:response).at_least_once.returns(stub(response: { 'hits' => { 'hits' => HITS } })) end - should "keep global order among models" do + should "keep the order from response" do assert_instance_of Module, Elasticsearch::Model::Adapter::Multiple::Records records = @multimodel.records diff --git a/elasticsearch-model/test/unit/multimodel_test.rb b/elasticsearch-model/test/unit/multimodel_test.rb index cd7809fd3..89e88f7a1 100644 --- a/elasticsearch-model/test/unit/multimodel_test.rb +++ b/elasticsearch-model/test/unit/multimodel_test.rb @@ -4,34 +4,30 @@ class Elasticsearch::Model::MultimodelTest < Test::Unit::TestCase context "Multimodel class" do setup do - title = stub('Title', index_name: 'titles_index', document_type: 'title') - series = stub('Series', index_name: 'series_index', document_type: 'series') + title = stub('Foo', index_name: 'foo_index', document_type: 'foo') + series = stub('Bar', index_name: 'bar_index', document_type: 'bar') @multimodel = Elasticsearch::Model::Multimodel.new(title, series) end - should "#index_name" do - assert_equal ['titles_index', 'series_index'], @multimodel.index_name + should "have an index_name" do + assert_equal ['foo_index', 'bar_index'], @multimodel.index_name end - should "#document_type" do - assert_equal ['title', 'series'], @multimodel.document_type + should "have a document_type" do + assert_equal ['foo', 'bar'], @multimodel.document_type end - should "#client" do + should "have a client" do assert_equal Elasticsearch::Model.client, @multimodel.client end - should "default intialization" do + should "include models in the registry" do class ::JustAModel include Elasticsearch::Model - - document_type "just_a_model" end class ::JustAnotherModel include Elasticsearch::Model - - document_type "just_another_model" end multimodel = Elasticsearch::Model::Multimodel.new From e78d9aa40a5fd19873aa18cef60b623900b53ae8 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Wed, 8 Apr 2015 14:54:56 +0200 Subject: [PATCH 196/582] [MODEL] Added a separate header for the multi-model search to the README Related: #345 --- elasticsearch-model/README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index c0cd946e4..4bdaca182 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -172,12 +172,6 @@ response.results.first._source.title # => "Quick brown fox" ``` -To search across multiple models, use the module method: - -```ruby -Elasticsearch::Model.search 'fox dogs', [Article, Comment] -``` - #### Search results The returned `response` object is a rich wrapper around the JSON returned from Elasticsearch, @@ -259,6 +253,8 @@ response.records.each_with_hit { |record, hit| puts "* #{record.title}: #{hit._s # * Fast black dogs: 0.02250402 ``` +#### Searching multiple models + It is possible to search across multiple models with the module method: ```ruby From 5b00030e66d492cb98ab51e7470d75ee4782a901 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Wed, 8 Apr 2015 17:49:56 +0200 Subject: [PATCH 197/582] [MODEL] Added example of the multi-model search to `examples/activerecord_associations.rb` Related: #345 --- .../examples/activerecord_associations.rb | 87 +++++++++++-------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/elasticsearch-model/examples/activerecord_associations.rb b/elasticsearch-model/examples/activerecord_associations.rb index 74713c649..6143a0356 100644 --- a/elasticsearch-model/examples/activerecord_associations.rb +++ b/elasticsearch-model/examples/activerecord_associations.rb @@ -59,9 +59,43 @@ add_index(:comments, :article_id) end +# ----- Elasticsearch client setup ---------------------------------------------------------------- + +Elasticsearch::Model.client = Elasticsearch::Client.new log: true +Elasticsearch::Model.client.transport.logger.formatter = proc { |s, d, p, m| "\e[32m#{m}\n\e[0m" } + +# ----- Search integration ------------------------------------------------------------------------ + +module Searchable + extend ActiveSupport::Concern + + included do + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + include Indexing + after_touch() { __elasticsearch__.index_document } + end + + module Indexing + + # Customize the JSON serialization for Elasticsearch + def as_indexed_json(options={}) + self.as_json( + include: { categories: { only: :title}, + authors: { methods: [:full_name], only: [:full_name] }, + comments: { only: :text } + }) + end + end +end + # ----- Model definitions ------------------------------------------------------------------------- class Category < ActiveRecord::Base + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + has_and_belongs_to_many :articles end @@ -81,6 +115,8 @@ class Authorship < ActiveRecord::Base end class Article < ActiveRecord::Base + include Searchable + has_and_belongs_to_many :categories, after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ], after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ] has_many :authorships @@ -88,43 +124,13 @@ class Article < ActiveRecord::Base has_many :comments end -class Article < ActiveRecord::Base; delegate :size, to: :comments, prefix: true; end - class Comment < ActiveRecord::Base - belongs_to :article, touch: true -end - -# ----- Search integration ------------------------------------------------------------------------ - -module Searchable - extend ActiveSupport::Concern - - included do - include Elasticsearch::Model - include Elasticsearch::Model::Callbacks - - __elasticsearch__.client = Elasticsearch::Client.new log: true - __elasticsearch__.client.transport.logger.formatter = proc { |s, d, p, m| "\e[32m#{m}\n\e[0m" } - - include Indexing - after_touch() { __elasticsearch__.index_document } - end - - module Indexing + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks - # Customize the JSON serialization for Elasticsearch - def as_indexed_json(options={}) - self.as_json( - include: { categories: { only: :title}, - authors: { methods: [:full_name], only: [:full_name] }, - comments: { only: :text } - }) - end - end + belongs_to :article, touch: true end -Article.__send__ :include, Searchable - # ----- Insert data ------------------------------------------------------------------------------- # Create category @@ -149,14 +155,23 @@ def as_indexed_json(options={}) # Add comment # -article.comments.create text: 'First comment' +article.comments.create text: 'First comment for article One' +article.comments.create text: 'Second comment for article One' -# Load +Elasticsearch::Model.client.indices.refresh index: Elasticsearch::Model::Registry.all.map(&:index_name) + +puts "\n\e[1mArticles containing 'one':\e[0m", Article.search('one').records.to_a.map(&:inspect), "" + +puts "\n\e[1mModels containing 'one':\e[0m", Elasticsearch::Model.search('one').records.to_a.map(&:inspect), "" + +# Load model # article = Article.all.includes(:categories, :authors, :comments).first # ----- Pry --------------------------------------------------------------------------------------- +puts '', '-'*Pry::Terminal.width! + Pry.start(binding, prompt: lambda { |obj, nest_level, _| '> ' }, - input: StringIO.new('puts "\n\narticle.as_indexed_json\n"; article.as_indexed_json'), + input: StringIO.new("article.as_indexed_json\n"), quiet: true) From 85be8ca203e0ca87a562c40e94f380b0a6db4d5f Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Wed, 8 Apr 2015 17:51:10 +0200 Subject: [PATCH 198/582] Release 0.1.7 --- elasticsearch-model/CHANGELOG.md | 8 ++++++++ elasticsearch-model/lib/elasticsearch/model/version.rb | 2 +- elasticsearch-persistence/CHANGELOG.md | 5 +++++ .../lib/elasticsearch/persistence/version.rb | 2 +- elasticsearch-rails/CHANGELOG.md | 10 ++++++++++ elasticsearch-rails/lib/elasticsearch/rails/version.rb | 2 +- 6 files changed, 26 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/CHANGELOG.md b/elasticsearch-model/CHANGELOG.md index a8b1f416e..e9639b114 100644 --- a/elasticsearch-model/CHANGELOG.md +++ b/elasticsearch-model/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.1.7 + +* Improved examples and instructions in README and code annotations +* Prevented index methods to swallow all exceptions +* Added the `:validate` option to the `save` method for models +* Added support for searching across multiple models (elastic/elasticsearch-rails#345), + including documentation, examples and tests + ## 0.1.6 * Improved documentation diff --git a/elasticsearch-model/lib/elasticsearch/model/version.rb b/elasticsearch-model/lib/elasticsearch/model/version.rb index 90d22a671..c8a197920 100644 --- a/elasticsearch-model/lib/elasticsearch/model/version.rb +++ b/elasticsearch-model/lib/elasticsearch/model/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Model - VERSION = "0.1.6" + VERSION = "0.1.7" end end diff --git a/elasticsearch-persistence/CHANGELOG.md b/elasticsearch-persistence/CHANGELOG.md index dd5f6026e..17e0a08ea 100644 --- a/elasticsearch-persistence/CHANGELOG.md +++ b/elasticsearch-persistence/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.1.7 + +* Added an integration test for the `MyModel.all` method +* Improved the "music" example application + ## 0.1.6 * Improved documentation diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb index eb0d22a3f..f94cea570 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Persistence - VERSION = "0.1.6" + VERSION = "0.1.7" end end diff --git a/elasticsearch-rails/CHANGELOG.md b/elasticsearch-rails/CHANGELOG.md index 29f76fa0a..05767473b 100644 --- a/elasticsearch-rails/CHANGELOG.md +++ b/elasticsearch-rails/CHANGELOG.md @@ -1,3 +1,13 @@ +# 0.1.7 + +* Updated dependencies for the gem and example applications +* Fixed various small errors in the `01-basic.rb` template +* Fixed error when inserting the Kaminari gem into Gemfile in the 02-pretty.rb template +* Fixed incorrect regex for adding Rails instrumentation into the application.rb in the `02-pretty.rb` template +* Fixed other small errors in the `02-pretty.rb` template +* Improved and added tests for the generated application from the `02-pretty.rb` template +* Added the `04-dsl.rb` template which uses the `elasticsearch-dsl` gem to build the search definition + ## 0.1.6 * Fixed errors in templates for the Rails example applications diff --git a/elasticsearch-rails/lib/elasticsearch/rails/version.rb b/elasticsearch-rails/lib/elasticsearch/rails/version.rb index c8388f308..f114090e7 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/version.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Rails - VERSION = "0.1.6" + VERSION = "0.1.7" end end From 2352c2f511f1313c1ce7688e142cc37adc40704d Mon Sep 17 00:00:00 2001 From: Jonathan Simmons <jon@jsdev.co> Date: Fri, 27 Mar 2015 16:21:50 -0500 Subject: [PATCH 199/582] [RAILS] Fixed the error when getting the search_controller_test.rb asset in `03-expert.rb` template Closes #357 Closes #358 --- elasticsearch-rails/lib/rails/templates/03-expert.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-rails/lib/rails/templates/03-expert.rb b/elasticsearch-rails/lib/rails/templates/03-expert.rb index b295a6c27..f175bd21e 100644 --- a/elasticsearch-rails/lib/rails/templates/03-expert.rb +++ b/elasticsearch-rails/lib/rails/templates/03-expert.rb @@ -217,8 +217,8 @@ def index CODE end -copy_file File.expand_path('../search_controller_test.rb', __FILE__), 'test/controllers/search_controller_test.rb' -# get 'https://raw.github.com/elasticsearch/elasticsearch-rails/templates/elasticsearch-rails/lib/rails/templates/search_controller_test.rb', +# copy_file File.expand_path('../search_controller_test.rb', __FILE__), 'test/controllers/search_controller_test.rb' +get 'https://raw.github.com/elasticsearch/elasticsearch-rails/templates/elasticsearch-rails/lib/rails/templates/search_controller_test.rb', 'test/controllers/search_controller_test.rb' route "get '/search', to: 'search#index', as: 'search'" From 72f3c1397da439365b7f91080586b29f1f7afc12 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Wed, 8 Apr 2015 20:25:41 +0200 Subject: [PATCH 200/582] [RAILS] Updated URLs for getting raw assets from Github in the `03-expert.rb` template Related: 358 --- .../lib/rails/templates/03-expert.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/elasticsearch-rails/lib/rails/templates/03-expert.rb b/elasticsearch-rails/lib/rails/templates/03-expert.rb index f175bd21e..23aaca6f7 100644 --- a/elasticsearch-rails/lib/rails/templates/03-expert.rb +++ b/elasticsearch-rails/lib/rails/templates/03-expert.rb @@ -157,7 +157,7 @@ class Article < ActiveRecord::Base gsub_file "#{Rails::VERSION::STRING > '4' ? 'test/models' : 'test/unit' }/article_test.rb", %r{assert_equal 'foo', definition\[:query\]\[:multi_match\]\[:query\]}, "assert_equal 'foo', definition.to_hash[:query][:bool][:should][0][:multi_match][:query]" # copy_file File.expand_path('../searchable.rb', __FILE__), 'app/models/concerns/searchable.rb' -get 'https://raw.github.com/elasticsearch/elasticsearch-rails/templates/elasticsearch-rails/lib/rails/templates/searchable.rb', +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/searchable.rb', 'app/models/concerns/searchable.rb' insert_into_file "app/models/article.rb", after: "ActiveRecord::Base" do @@ -186,7 +186,7 @@ class Article < ActiveRecord::Base run "bundle install" # copy_file File.expand_path('../indexer.rb', __FILE__), 'app/workers/indexer.rb' -get 'https://raw.github.com/elasticsearch/elasticsearch-rails/templates/elasticsearch-rails/lib/rails/templates/indexer.rb', +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/indexer.rb', 'app/workers/indexer.rb' git add: "Gemfile* app/workers/" @@ -218,18 +218,18 @@ def index end # copy_file File.expand_path('../search_controller_test.rb', __FILE__), 'test/controllers/search_controller_test.rb' -get 'https://raw.github.com/elasticsearch/elasticsearch-rails/templates/elasticsearch-rails/lib/rails/templates/search_controller_test.rb', +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/search_controller_test.rb', 'test/controllers/search_controller_test.rb' route "get '/search', to: 'search#index', as: 'search'" gsub_file 'config/routes.rb', %r{root to: 'articles#index'$}, "root to: 'search#index'" # copy_file File.expand_path('../index.html.erb', __FILE__), 'app/views/search/index.html.erb' -get 'https://raw.github.com/elasticsearch/elasticsearch-rails/templates/elasticsearch-rails/lib/rails/templates/index.html.erb', +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/index.html.erb', 'app/views/search/index.html.erb' # copy_file File.expand_path('../search.css', __FILE__), 'app/assets/stylesheets/search.css' -get 'https://raw.github.com/elasticsearch/elasticsearch-rails/templates/elasticsearch-rails/lib/rails/templates/search.css', +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/search.css', 'app/assets/stylesheets/search.css' git add: "app/controllers/ test/controllers/ config/routes.rb" @@ -281,12 +281,12 @@ def index puts '-'*80, ''; sleep 0.25 # copy_file File.expand_path('../articles.yml.gz', __FILE__), 'db/articles.yml.gz' -get 'https://raw.github.com/elasticsearch/elasticsearch-rails/templates/elasticsearch-rails/lib/rails/templates/articles.yml.gz', +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/articles.yml.gz', 'db/articles.yml.gz' remove_file 'db/seeds.rb' # copy_file File.expand_path('../seeds.rb', __FILE__), 'db/seeds.rb' -get 'https://raw.github.com/elasticsearch/elasticsearch-rails/templates/elasticsearch-rails/lib/rails/templates/seeds.rb', +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/seeds.rb', 'db/seeds.rb' rake "db:reset" From c9484b00e58cfd096b0cf7c0e03a9b0942f96851 Mon Sep 17 00:00:00 2001 From: David Padilla <david@easybroker.com> Date: Tue, 24 Mar 2015 18:34:35 -0600 Subject: [PATCH 201/582] [MODEL] Fixed incorrect example for setting the client Closes: #353 --- elasticsearch-model/lib/elasticsearch/model.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index 5cb9ff2b1..ce05d215d 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -145,7 +145,7 @@ def client # # @example Configure (set) the client for all models # - # Elasticsearch::Model.client Elasticsearch::Client.new host: 'http://localhost:9200', tracer: true + # Elasticsearch::Model.client = Elasticsearch::Client.new host: 'http://localhost:9200', tracer: true # => #<Elasticsearch::Transport::Client:0x007f96a6dd0d80 @transport=... > # # @note You have to set the client before you call Elasticsearch methods on the model, From 0a1d1dac9bf09fd9a5c9740c4cab0b026a9d3569 Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt <andrewnez@gmail.com> Date: Wed, 8 Apr 2015 17:33:53 +0100 Subject: [PATCH 202/582] [MODEL] Fixed typo in CHANGELOG Closes #364 --- elasticsearch-model/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/CHANGELOG.md b/elasticsearch-model/CHANGELOG.md index e9639b114..898e4b4c1 100644 --- a/elasticsearch-model/CHANGELOG.md +++ b/elasticsearch-model/CHANGELOG.md @@ -3,7 +3,7 @@ * Improved examples and instructions in README and code annotations * Prevented index methods to swallow all exceptions * Added the `:validate` option to the `save` method for models -* Added support for searching across multiple models (elastic/elasticsearch-rails#345), +* Added support for searching across multiple models (elastic/elasticsearch-rails#345), including documentation, examples and tests ## 0.1.6 From 92d40754f59e78b93ebd52f89d2d81b6a4dcb448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rath=C3=A9?= <prathe@gmail.com> Date: Mon, 23 Feb 2015 13:47:58 -0500 Subject: [PATCH 203/582] [MODEL] Added the `no_timeout` option for `__find_in_batches` in the Mongoid adapter See: http://mongoid.org/en/origin/docs/options.html Closes #335 --- elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb index f59ed9966..e5a907235 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb @@ -66,7 +66,7 @@ def __find_in_batches(options={}, &block) options[:batch_size] ||= 1_000 items = [] - all.each do |item| + all.no_timeout.each do |item| items << item if items.length % options[:batch_size] == 0 From 2b0999ba868d0ed8dd6868170442f8e5493a6e92 Mon Sep 17 00:00:00 2001 From: Eric Bouchut <ebouchut@gmail.com> Date: Thu, 13 Nov 2014 17:58:43 +0100 Subject: [PATCH 204/582] [STORE] Added shortcut links to both main chapters on patterns in the README Closes #287 --- elasticsearch-persistence/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index 3d209d8e6..62890e0f9 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -23,12 +23,18 @@ or install it from a source code checkout: ## Usage +The library provides two different patterns for adding persistence to your Ruby objects: + +* [Repository Pattern](#the-repository-pattern) +* [ActiveRecord Pattern](#the-activerecord-pattern) + ### The Repository Pattern The `Elasticsearch::Persistence::Repository` module provides an implementation of the [repository pattern](http://martinfowler.com/eaaCatalog/repository.html) and allows to save, delete, find and search objects stored in Elasticsearch, as well as configure -mappings and settings for the index. +mappings and settings for the index. It's an unobtrusive and decoupled way of adding +persistence to your Ruby objects. Let's have a simple plain old Ruby object (PORO): From 538ed46f9c10f1b9ff01b9a40988a35be592d762 Mon Sep 17 00:00:00 2001 From: Eric Bouchut <ebouchut@gmail.com> Date: Wed, 5 Nov 2014 09:50:43 +0100 Subject: [PATCH 205/582] [MODEL] Fixed typo in the README file --- elasticsearch-model/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 4bdaca182..b7b7b7baa 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -129,7 +129,7 @@ Or configure the client for all models: Elasticsearch::Model.client = Elasticsearch::Client.new log: true ``` -You might want to do this during you application bootstrap process, e.g. in a Rails initializer. +You might want to do this during your application bootstrap process, e.g. in a Rails initializer. Please refer to the [`elasticsearch-transport`](https://github.com/elasticsearch/elasticsearch-ruby/tree/master/elasticsearch-transport) From 639e3daed609b236175fc75a3bf2aa3ced3cd4b4 Mon Sep 17 00:00:00 2001 From: Miguel Fernandez <miguelfernandezfernandez@gmail.com> Date: Sat, 11 Apr 2015 18:30:43 +0200 Subject: [PATCH 206/582] [MODEL] Fixed a buggy test introduced in #335 Fixes 04e860861fd709e21fd916a68924e89caee9ac53 Related: #370 --- elasticsearch-model/test/unit/adapter_mongoid_test.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/elasticsearch-model/test/unit/adapter_mongoid_test.rb b/elasticsearch-model/test/unit/adapter_mongoid_test.rb index fbec800d0..ca9b0d20b 100644 --- a/elasticsearch-model/test/unit/adapter_mongoid_test.rb +++ b/elasticsearch-model/test/unit/adapter_mongoid_test.rb @@ -76,7 +76,9 @@ def ids context "Importing" do should "implement the __find_in_batches method" do - DummyClassForMongoid.expects(:all).returns([]) + relation = mock() + relation.stubs(:no_timeout).returns([]) + DummyClassForMongoid.expects(:all).returns(relation) DummyClassForMongoid.__send__ :extend, Elasticsearch::Model::Adapter::Mongoid::Importing DummyClassForMongoid.__find_in_batches do; end From 5fb9c563038a5599e551b1d2186f260457ad630b Mon Sep 17 00:00:00 2001 From: Miguel Fernandez <miguelfernandezfernandez@gmail.com> Date: Fri, 17 Apr 2015 20:23:30 +0200 Subject: [PATCH 207/582] [MODEL] Fixed incorrect deserialization of records in the Multiple adapter During refactoring of the `__type_records` methods of the Multiple adapter, a bug has been introduced: * A user searches across several models. Along the search execution the code reaches the point above. * A certain klass (say for instance an ActiveRecord model) wont' match any of the cases on the case/when statement, entering in the default clause. * Then find will be invoked over klass which is an ActiveRecord, but unluckily ActiveRecord will respond to the message. * Due to eventual consistency, some of the records returned by ES, are no longer present in the DB. * So in this case, klass.find(ids) will raise an exception as no all the ids where found in the DB. Closes #369 Closes #370 --- .../elasticsearch/model/adapters/multiple.rb | 15 +++--- .../test/integration/multiple_models_test.rb | 48 ++++++++++++------- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb index 72e31306b..4c2526586 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb @@ -18,9 +18,11 @@ module Records def records records_by_type = __records_by_type - response.response["hits"]["hits"].map do |hit| + records = response.response["hits"]["hits"].map do |hit| records_by_type[ __type_for_hit(hit) ][ hit[:_id] ] end + + records.compact end # Returns the collection of records grouped by class based on `_type` @@ -49,14 +51,13 @@ def __records_by_type # @api private # def __records_for_klass(klass, ids) - adapter = __adapter_name_for_klass(klass) + adapter = __adapter_for_klass(klass) - case adapter - when Elasticsearch::Model::Adapter::ActiveRecord + if Elasticsearch::Model::Adapter::ActiveRecord.equal?(adapter) klass.where(klass.primary_key => ids) - when Elasticsearch::Model::Adapter::Mongoid + elsif Elasticsearch::Model::Adapter::Mongoid.equal?(adapter) klass.where(:id.in => ids) - else + else klass.find(ids) end end @@ -100,7 +101,7 @@ def __type_for_hit(hit) # # @api private # - def __adapter_name_for_klass(klass) + def __adapter_for_klass(klass) Adapter.adapters.select { |name, checker| checker.call(klass) }.keys.first end end diff --git a/elasticsearch-model/test/integration/multiple_models_test.rb b/elasticsearch-model/test/integration/multiple_models_test.rb index 099ab4504..1eb91e6a9 100644 --- a/elasticsearch-model/test/integration/multiple_models_test.rb +++ b/elasticsearch-model/test/integration/multiple_models_test.rb @@ -20,28 +20,28 @@ class MultipleModelsIntegration < Elasticsearch::Test::IntegrationTestCase end end - class ::Episode < ActiveRecord::Base - include Elasticsearch::Model - include Elasticsearch::Model::Callbacks + module ::NameSearch + extend ActiveSupport::Concern + + included do + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks - settings index: {number_of_shards: 1, number_of_replicas: 0} do - mapping do - indexes :name, type: 'string', analyzer: 'snowball' - indexes :created_at, type: 'date' + settings index: {number_of_shards: 1, number_of_replicas: 0} do + mapping do + indexes :name, type: 'string', analyzer: 'snowball' + indexes :created_at, type: 'date' + end end end end - class ::Series < ActiveRecord::Base - include Elasticsearch::Model - include Elasticsearch::Model::Callbacks + class ::Episode < ActiveRecord::Base + include NameSearch + end - settings index: {number_of_shards: 1, number_of_replicas: 0} do - mapping do - indexes :name, type: 'string', analyzer: 'snowball' - indexes :created_at, type: 'date' - end - end + class ::Series < ActiveRecord::Base + include NameSearch end [::Episode, ::Series].each do |model| @@ -93,6 +93,22 @@ class ::Series < ActiveRecord::Base assert_equal false, response.results[1].highlight.boo? end + should "only retrieve records for existing results" do + ::Series.find_by_name("The greatest Series").delete + response = Elasticsearch::Model.search("\"The greatest Episode\"^2 OR \"The greatest Series\"", [Series, Episode]) + + assert response.any?, "Response should not be empty: #{response.to_a.inspect}" + + assert_equal 2, response.results.size + assert_equal 1, response.records.size + + assert_instance_of Elasticsearch::Model::Response::Result, response.results.first + assert_instance_of Episode, response.records.first + + assert_equal 'The greatest Episode', response.results[0].name + assert_equal 'The greatest Episode', response.records[0].name + end + if Mongo.available? Mongo.connect_to 'mongoid_collections' From 61759f1f7fee07b5f0ddc5ee38bcc14d7b112dca Mon Sep 17 00:00:00 2001 From: Miguel Fernandez <miguelfernandezfernandez@gmail.com> Date: Fri, 17 Apr 2015 20:38:02 +0200 Subject: [PATCH 208/582] [MODEL] Fixed unreliable order of returned results/records in the integration test for the multiple adapter Related: #370 --- .../test/integration/multiple_models_test.rb | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/elasticsearch-model/test/integration/multiple_models_test.rb b/elasticsearch-model/test/integration/multiple_models_test.rb index 1eb91e6a9..b6cc5390b 100644 --- a/elasticsearch-model/test/integration/multiple_models_test.rb +++ b/elasticsearch-model/test/integration/multiple_models_test.rb @@ -56,7 +56,7 @@ class ::Series < ActiveRecord::Base end should "find matching documents across multiple models" do - response = Elasticsearch::Model.search("greatest", [Series, Episode]) + response = Elasticsearch::Model.search("\"The greatest Episode\"^2 OR \"The greatest Series\"", [Series, Episode]) assert response.any?, "Response should not be empty: #{response.to_a.inspect}" @@ -75,22 +75,15 @@ class ::Series < ActiveRecord::Base end should "provide access to results" do - q = {query: {query_string: {query: 'A great *'}}, highlight: {fields: {name: {}}}} - response = Elasticsearch::Model.search(q, [Series, Episode]) + response = Elasticsearch::Model.search("\"A great Episode\"^2 OR \"A great Series\"", [Series, Episode]) assert_equal 'A great Episode', response.results[0].name assert_equal true, response.results[0].name? assert_equal false, response.results[0].boo? - assert_equal true, response.results[0].highlight? - assert_equal true, response.results[0].highlight.name? - assert_equal false, response.results[0].highlight.boo? assert_equal 'A great Series', response.results[1].name assert_equal true, response.results[1].name? assert_equal false, response.results[1].boo? - assert_equal true, response.results[1].highlight? - assert_equal true, response.results[1].highlight.name? - assert_equal false, response.results[1].highlight.boo? end should "only retrieve records for existing results" do @@ -144,7 +137,7 @@ def as_indexed_json(options={}) end should "find matching documents across multiple models" do - response = Elasticsearch::Model.search("greatest", [Episode, Image]) + response = Elasticsearch::Model.search("\"greatest Episode\" OR \"greatest Image\"^2", [Episode, Image]) assert response.any?, "Response should not be empty: #{response.to_a.inspect}" From d6a1950c4fd3dda3f4d6ceec2ee71228303f487f Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Wed, 22 Apr 2015 10:21:15 +0200 Subject: [PATCH 209/582] [MODEL] Refactored the `if/elsif` statement from 5fb9c56 into `case` Related: #369, #370 --- .../lib/elasticsearch/model/adapters/multiple.rb | 7 ++++--- .../test/integration/multiple_models_test.rb | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb index 4c2526586..9a0bc4e8e 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb @@ -53,11 +53,12 @@ def __records_by_type def __records_for_klass(klass, ids) adapter = __adapter_for_klass(klass) - if Elasticsearch::Model::Adapter::ActiveRecord.equal?(adapter) + case + when Elasticsearch::Model::Adapter::ActiveRecord.equal?(adapter) klass.where(klass.primary_key => ids) - elsif Elasticsearch::Model::Adapter::Mongoid.equal?(adapter) + when Elasticsearch::Model::Adapter::Mongoid.equal?(adapter) klass.where(:id.in => ids) - else + else klass.find(ids) end end diff --git a/elasticsearch-model/test/integration/multiple_models_test.rb b/elasticsearch-model/test/integration/multiple_models_test.rb index b6cc5390b..69ccd399d 100644 --- a/elasticsearch-model/test/integration/multiple_models_test.rb +++ b/elasticsearch-model/test/integration/multiple_models_test.rb @@ -88,6 +88,7 @@ class ::Series < ActiveRecord::Base should "only retrieve records for existing results" do ::Series.find_by_name("The greatest Series").delete + ::Series.__elasticsearch__.refresh_index! response = Elasticsearch::Model.search("\"The greatest Episode\"^2 OR \"The greatest Series\"", [Series, Episode]) assert response.any?, "Response should not be empty: #{response.to_a.inspect}" From f493f56238348d62e0602ef931a2716b2f7dc2df Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Wed, 22 Apr 2015 10:37:50 +0200 Subject: [PATCH 210/582] [MODEL] Refactored the string queries in multiple_models_test.rb to avoid quote escaping Related: #370 --- .../test/integration/multiple_models_test.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/elasticsearch-model/test/integration/multiple_models_test.rb b/elasticsearch-model/test/integration/multiple_models_test.rb index 69ccd399d..214217ae3 100644 --- a/elasticsearch-model/test/integration/multiple_models_test.rb +++ b/elasticsearch-model/test/integration/multiple_models_test.rb @@ -56,7 +56,7 @@ class ::Series < ActiveRecord::Base end should "find matching documents across multiple models" do - response = Elasticsearch::Model.search("\"The greatest Episode\"^2 OR \"The greatest Series\"", [Series, Episode]) + response = Elasticsearch::Model.search(%q<"The greatest Episode"^2 OR "The greatest Series">, [Series, Episode]) assert response.any?, "Response should not be empty: #{response.to_a.inspect}" @@ -75,7 +75,7 @@ class ::Series < ActiveRecord::Base end should "provide access to results" do - response = Elasticsearch::Model.search("\"A great Episode\"^2 OR \"A great Series\"", [Series, Episode]) + response = Elasticsearch::Model.search(%q<"A great Episode"^2 OR "A great Series">, [Series, Episode]) assert_equal 'A great Episode', response.results[0].name assert_equal true, response.results[0].name? @@ -89,7 +89,7 @@ class ::Series < ActiveRecord::Base should "only retrieve records for existing results" do ::Series.find_by_name("The greatest Series").delete ::Series.__elasticsearch__.refresh_index! - response = Elasticsearch::Model.search("\"The greatest Episode\"^2 OR \"The greatest Series\"", [Series, Episode]) + response = Elasticsearch::Model.search(%q<"The greatest Episode"^2 OR "The greatest Series">, [Series, Episode]) assert response.any?, "Response should not be empty: #{response.to_a.inspect}" @@ -138,7 +138,7 @@ def as_indexed_json(options={}) end should "find matching documents across multiple models" do - response = Elasticsearch::Model.search("\"greatest Episode\" OR \"greatest Image\"^2", [Episode, Image]) + response = Elasticsearch::Model.search(%q<"greatest Episode" OR "greatest Image"^2>, [Episode, Image]) assert response.any?, "Response should not be empty: #{response.to_a.inspect}" From d5eed56d0d720a7bd87feda45d28d4ce0daa0e4b Mon Sep 17 00:00:00 2001 From: treby <treby@atelier-nodoka.net> Date: Sat, 18 Apr 2015 11:10:35 +0900 Subject: [PATCH 211/582] [MODEL] Fixed incorrect indentation in integration test :scissors: Closes #372 --- .../test/integration/active_record_basic_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index d5b51642e..63dce1bc1 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -128,7 +128,7 @@ class ::Article < ActiveRecord::Base assert_equal 1, response.records.size end - should "update specific attributes" do + should "update specific attributes" do article = Article.first response = Article.search 'title:special' From b1d44fb22c29eb72bda7a7b65dc660745f3ebd30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= <deivid.rodriguez@gmail.com> Date: Tue, 24 Mar 2015 14:32:56 -0300 Subject: [PATCH 212/582] [RAILS] Added missing require in the seeds.rb file for the expert template Otherwise a `uninitialized constant Sidekiq::Queue (NameError)` exception is raised. Closes #352 --- elasticsearch-rails/lib/rails/templates/seeds.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/elasticsearch-rails/lib/rails/templates/seeds.rb b/elasticsearch-rails/lib/rails/templates/seeds.rb index 6b7bacac8..85bcd509b 100644 --- a/elasticsearch-rails/lib/rails/templates/seeds.rb +++ b/elasticsearch-rails/lib/rails/templates/seeds.rb @@ -53,4 +53,5 @@ # Remove any jobs from the "elasticsearch" Sidekiq queue # +require 'sidekiq/api' Sidekiq::Queue.new("elasticsearch").clear From ee0af92450ec2bddfafc208f9f10e16e6e293623 Mon Sep 17 00:00:00 2001 From: treby <treby@atelier-nodoka.net> Date: Tue, 3 Mar 2015 16:58:17 +0900 Subject: [PATCH 213/582] [MODEL] Fixed a bug where continuous `#save` calls emptied the `@__changed_attributes` variable Closes #339 --- .../lib/elasticsearch/model/proxy.rb | 3 +- .../integration/active_record_basic_test.rb | 31 +++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/proxy.rb b/elasticsearch-model/lib/elasticsearch/model/proxy.rb index 90e1a9a44..64dd4da1f 100644 --- a/elasticsearch-model/lib/elasticsearch/model/proxy.rb +++ b/elasticsearch-model/lib/elasticsearch/model/proxy.rb @@ -59,8 +59,9 @@ def __elasticsearch__ &block # @see http://api.rubyonrails.org/classes/ActiveModel/Dirty.html # before_save do |i| + changed_attr = i.__elasticsearch__.instance_variable_get(:@__changed_attributes) || {} i.__elasticsearch__.instance_variable_set(:@__changed_attributes, - Hash[ i.changes.map { |key, value| [key, value.last] } ]) + changed_attr.merge(Hash[ i.changes.map { |key, value| [key, value.last] } ])) end if respond_to?(:before_save) && instance_methods.include?(:changed_attributes) end end diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index 63dce1bc1..6fd5467df 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -11,6 +11,7 @@ class ActiveRecordBasicIntegrationTest < Elasticsearch::Test::IntegrationTestCas ActiveRecord::Schema.define(:version => 1) do create_table :articles do |t| t.string :title + t.string :body t.datetime :created_at, :default => 'NOW()' end end @@ -22,6 +23,7 @@ class ::Article < ActiveRecord::Base settings index: { number_of_shards: 1, number_of_replicas: 0 } do mapping do indexes :title, type: 'string', analyzer: 'snowball' + indexes :body, type: 'string' indexes :created_at, type: 'date' end end @@ -30,9 +32,9 @@ class ::Article < ActiveRecord::Base Article.delete_all Article.__elasticsearch__.create_index! force: true - ::Article.create! title: 'Test' - ::Article.create! title: 'Testing Coding' - ::Article.create! title: 'Coding' + ::Article.create! title: 'Test', body: '' + ::Article.create! title: 'Testing Coding', body: '' + ::Article.create! title: 'Coding', body: '' Article.__elasticsearch__.refresh_index! end @@ -146,6 +148,29 @@ class ::Article < ActiveRecord::Base assert_equal 1, response.records.size end + should "update document when save is called multiple times in a transaction" do + article = Article.first + response = Article.search 'body:dummy' + + assert_equal 0, response.results.size + assert_equal 0, response.records.size + + ActiveRecord::Base.transaction do + article.body = 'dummy' + article.save + + article.title = 'special' + article.save + end + + article.__elasticsearch__.update_document + Article.__elasticsearch__.refresh_index! + + response = Article.search 'body:dummy' + assert_equal 1, response.results.size + assert_equal 1, response.records.size + end + should "return results for a DSL search" do response = Article.search query: { match: { title: { query: 'test' } } } From 50fee81a0d841ce79e80d25d21da5d25f203ff66 Mon Sep 17 00:00:00 2001 From: Kevin Kirsche <Kev.Kirsche+GitHub@gmail.com> Date: Mon, 17 Nov 2014 12:25:02 -0500 Subject: [PATCH 214/582] [STORE] Short-circuit the operation and return `false` when the model is not valid Closes #289 --- .../elasticsearch/persistence/model/store.rb | 3 ++ .../test/unit/model_store_test.rb | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb index 72326128a..313d4e75f 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb @@ -102,6 +102,9 @@ def destroy(options={}) # @return [Hash] The Elasticsearch response as a Hash # def update(attributes={}, options={}) + unless options.delete(:validate) == false + return false unless valid? + end raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? run_callbacks :update do diff --git a/elasticsearch-persistence/test/unit/model_store_test.rb b/elasticsearch-persistence/test/unit/model_store_test.rb index 835f49428..f4163d975 100644 --- a/elasticsearch-persistence/test/unit/model_store_test.rb +++ b/elasticsearch-persistence/test/unit/model_store_test.rb @@ -311,6 +311,43 @@ def valid?; false; end; assert subject.update( {}, { script: 'EXEC' } ) end + should "not update an invalid model" do + subject.expects(:persisted?).returns(true) + subject.expects(:id).returns('abc123').at_least_once + + @gateway + .expects(:update) + .never + + subject.instance_eval do + def valid?; false; end; + end + + assert ! subject.update + assert ! subject.persisted? + end + + should "skip the validation with the :validate option" do + subject.expects(:persisted?).returns(true) + subject.expects(:id).returns('abc123').at_least_once + + @gateway + .expects(:update) + .with do |object, options| + assert_equal subject, object + assert_equal nil, options[:id] + true + end + .returns({'_id' => 'abc123'}) + + subject.instance_eval do + def valid?; false; end; + end + + assert subject.update validate: false + assert subject.persisted? + end + should "pass the options to gateway" do subject.expects(:persisted?).returns(true) From b660d6a2398d3db7544297a9f17609122bef8c26 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Thu, 23 Apr 2015 16:39:20 +0200 Subject: [PATCH 215/582] [STORE] Fixed tests for the updates to the `update` method for Persistence::Model This fixes 50fee81a0d841ce79e80d25d21da5d25f203ff66 Related: #289 --- .../test/unit/model_store_test.rb | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/elasticsearch-persistence/test/unit/model_store_test.rb b/elasticsearch-persistence/test/unit/model_store_test.rb index f4163d975..442ab56ee 100644 --- a/elasticsearch-persistence/test/unit/model_store_test.rb +++ b/elasticsearch-persistence/test/unit/model_store_test.rb @@ -312,9 +312,6 @@ def valid?; false; end; end should "not update an invalid model" do - subject.expects(:persisted?).returns(true) - subject.expects(:id).returns('abc123').at_least_once - @gateway .expects(:update) .never @@ -323,19 +320,19 @@ def valid?; false; end; def valid?; false; end; end - assert ! subject.update - assert ! subject.persisted? + assert ! subject.update(title: 'INVALID') end should "skip the validation with the :validate option" do - subject.expects(:persisted?).returns(true) + subject.expects(:persisted?).returns(true).at_least_once subject.expects(:id).returns('abc123').at_least_once @gateway .expects(:update) .with do |object, options| - assert_equal subject, object + assert_equal 'abc123', object assert_equal nil, options[:id] + assert_equal 'INVALID', options[:doc][:title] true end .returns({'_id' => 'abc123'}) @@ -344,7 +341,7 @@ def valid?; false; end; def valid?; false; end; end - assert subject.update validate: false + assert subject.update( { title: 'INVALID' }, { validate: false } ) assert subject.persisted? end From ed0d087de434db66aa3456ea8cf46a511fe3b15c Mon Sep 17 00:00:00 2001 From: Konstantin Zub <Zubkonst@gmail.com> Date: Thu, 30 Oct 2014 04:36:06 -0700 Subject: [PATCH 216/582] [RAILS] Fixed double include of the aliased method (execute_without_instrumentation) Fixes #277 Closes #278 --- .../rails/instrumentation/publishers.rb | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/publishers.rb b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/publishers.rb index 679a6f698..e054d5371 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/publishers.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/publishers.rb @@ -12,25 +12,23 @@ module SearchRequest def self.included(base) base.class_eval do - alias_method :execute_without_instrumentation!, :execute! - - def execute! - execute_with_instrumentation! - end - - # Wrap `Search#execute!` and perform instrumentation - # - def execute_with_instrumentation! - ActiveSupport::Notifications.instrument "search.elasticsearch", - name: 'Search', - klass: (self.klass.is_a?(Elasticsearch::Model::Proxy::ClassMethodsProxy) ? self.klass.target.to_s : self.klass.to_s), - search: self.definition do - execute_without_instrumentation! - end + unless method_defined?(:execute_without_instrumentation!) + alias_method :execute_without_instrumentation!, :execute! + alias_method :execute!, :execute_with_instrumentation! end end end + # Wrap `Search#execute!` and perform instrumentation + # + def execute_with_instrumentation! + ActiveSupport::Notifications.instrument "search.elasticsearch", + name: 'Search', + klass: (self.klass.is_a?(Elasticsearch::Model::Proxy::ClassMethodsProxy) ? self.klass.target.to_s : self.klass.to_s), + search: self.definition do + execute_without_instrumentation! + end + end end end end From 67ddb4ec2022a09797e6418747c4ead2df5029a5 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Mon, 27 Apr 2015 10:18:30 +0200 Subject: [PATCH 217/582] [STORE] Added `cluster.health wait_for_status: 'yellow'` to Repository integration test Trying to prevent errors on the Jenkins machine such as: 1) Failure: test_: The default repository class should pass options to save and find. (Elasticsearch::Persistence::RepositoryDefaultClassIntegrationTest) [/home/jenkins/workspace/es-rails_core/ES_V/1.x_nightly/GEMFILE/4.0.gemfile/RUBY_V/1.9.3-p551/jdk/JDK7/label/metal-pool/elasticsearch-persistence/test/integration/repository/default_class_test.rb:76]: [Elasticsearch::Persistence::Repository::DocumentNotFound] exception expected, not Class: <Elasticsearch::Transport::Transport::Errors::ServiceUnavailable> Message: <"[503] {\"error\":\"NoShardAvailableActionException[[repository][1] null]; nested: IllegalIndexShardStateException[[repository][1] CurrentState[POST_RECOVERY] operations only allowed when started/relocated]; \",\"status\":503}"> http://build-eu-00.elasticsearch.org/view/Clients/job/es-rails_core/ES_V=1.x_nightly,GEMFILE=4.0.gemfile,RUBY_V=1.9.3-p551,jdk=JDK7,label=metal-pool/lastBuild/console --- .../test/integration/repository/default_class_test.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/elasticsearch-persistence/test/integration/repository/default_class_test.rb b/elasticsearch-persistence/test/integration/repository/default_class_test.rb index 0335aa843..c27d587be 100644 --- a/elasticsearch-persistence/test/integration/repository/default_class_test.rb +++ b/elasticsearch-persistence/test/integration/repository/default_class_test.rb @@ -73,6 +73,8 @@ def to_hash note = Note.new(id: '1', title: 'Test') @repository.save note, routing: 'ABC' + @repository.client.cluster.health level: 'indices', wait_for_status: 'yellow' + assert_raise Elasticsearch::Persistence::Repository::DocumentNotFound do @repository.find(1, routing: 'DEF') end From f5fc4ea26974fec53c7bbe1a335f5a6a371fffb4 Mon Sep 17 00:00:00 2001 From: David Padilla <david@easybroker.com> Date: Thu, 23 Apr 2015 12:48:10 -0500 Subject: [PATCH 218/582] [STORE] Removed duplicate property declaration in the "music" example application Fixes #374 Closes #377 --- elasticsearch-persistence/examples/music/album.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/elasticsearch-persistence/examples/music/album.rb b/elasticsearch-persistence/examples/music/album.rb index 124e643ed..dc3dfdf07 100644 --- a/elasticsearch-persistence/examples/music/album.rb +++ b/elasticsearch-persistence/examples/music/album.rb @@ -22,7 +22,6 @@ class Album attribute :label, Hash, mapping: { type: 'object' } attribute :title - attribute :suggest_title, String, default: {}, mapping: { type: 'completion', payloads: true } attribute :released, Date attribute :notes attribute :uri From 2b338c1a4454bfb8b3915a297b2d2a7f2148efbe Mon Sep 17 00:00:00 2001 From: Miguel Fernandez <miguelfernandezfernandez@gmail.com> Date: Sat, 11 Apr 2015 13:58:17 +0200 Subject: [PATCH 219/582] [MODEL] [STORE] Fixed the problem where `document_type` configuration was not propagated to mapping This PR tries to fix the bug described in #270 The problem was the following: * Once a model extends Persistence::Model it sets up two attributes. As a consequence, the attribute macro instantiates a Elasticsearch::Model::Indexing::Mappings in the model. This instance contains an attribute @type that defaults to the model name in lowercase. * Any other definition of document_type will override the document_type in the gateway delegate, but the mappings instance originally created in the previous step remains untouched, and hence in an inconsistent state. What I did here is: Instead of simply forward document_type to the gateway, also change the mappings' document_type accordingly, leaving it in a correct state. An integration test was modified, in order to have a regression of the fix. Unit tests were added. Fixes #270 Closes #366 --- .../lib/elasticsearch/model/indexing.rb | 2 +- .../lib/elasticsearch/persistence/model.rb | 8 +++- .../integration/model/model_basic_test.rb | 3 +- .../test/unit/model_base_test.rb | 48 ++++++++++++++----- 4 files changed, 46 insertions(+), 15 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index 610d6444e..05dfbb8a7 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -34,7 +34,7 @@ def as_json(options={}) # Wraps the [index mappings](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping.html) # class Mappings - attr_accessor :options + attr_accessor :options, :type def initialize(type, options={}) raise ArgumentError, "`type` is missing" if type.nil? diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb index 9dcbd8355..3eb3c3a0d 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb @@ -71,7 +71,6 @@ def gateway(&block) delegate :settings, :mappings, :mapping, - :document_type, :document_type=, :index_name, :index_name=, @@ -80,6 +79,13 @@ def gateway(&block) :create_index!, :refresh_index!, to: :gateway + + # forward document type to mappings when set + def document_type(type = nil) + return gateway.document_type unless type + gateway.document_type type + mapping.type = type + end end # Configure the repository based on the model (set up index_name, etc) diff --git a/elasticsearch-persistence/test/integration/model/model_basic_test.rb b/elasticsearch-persistence/test/integration/model/model_basic_test.rb index 172d0bb2f..b43571e6e 100644 --- a/elasticsearch-persistence/test/integration/model/model_basic_test.rb +++ b/elasticsearch-persistence/test/integration/model/model_basic_test.rb @@ -12,6 +12,7 @@ class ::Person include Elasticsearch::Persistence::Model::Rails settings index: { number_of_shards: 1 } + document_type 'human_being' attribute :name, String, mapping: { fields: { @@ -60,7 +61,7 @@ class ::Person assert_equal 'John Smith', document.name assert_equal 'John Smith', Person.find(person.id).name - assert_not_nil Elasticsearch::Persistence.client.get index: 'people', type: 'person', id: person.id + assert_not_nil Elasticsearch::Persistence.client.get index: 'people', type: 'human_being', id: person.id end should "not save an invalid object" do diff --git a/elasticsearch-persistence/test/unit/model_base_test.rb b/elasticsearch-persistence/test/unit/model_base_test.rb index 10141006e..2d55e1620 100644 --- a/elasticsearch-persistence/test/unit/model_base_test.rb +++ b/elasticsearch-persistence/test/unit/model_base_test.rb @@ -20,29 +20,53 @@ class DummyBaseModel end should "set the ID from attributes during initialization" do - m = DummyBaseModel.new id: 1 - assert_equal 1, m.id + model = DummyBaseModel.new id: 1 + assert_equal 1, model.id - m = DummyBaseModel.new 'id' => 2 - assert_equal 2, m.id + model = DummyBaseModel.new 'id' => 2 + assert_equal 2, model.id end should "set the ID using setter method" do - m = DummyBaseModel.new id: 1 - assert_equal 1, m.id + model = DummyBaseModel.new id: 1 + assert_equal 1, model.id - m.id = 2 - assert_equal 2, m.id + model.id = 2 + assert_equal 2, model.id end should "have ID in attributes" do - m = DummyBaseModel.new id: 1, name: 'Test' - assert_equal 1, m.attributes[:id] + model = DummyBaseModel.new id: 1, name: 'Test' + assert_equal 1, model.attributes[:id] end should "have the customized inspect method" do - m = DummyBaseModel.new name: 'Test' - assert_match /name\: "Test"/, m.inspect + model = DummyBaseModel.new name: 'Test' + assert_match /name\: "Test"/, model.inspect + end + + context "with custom document_type" do + setup do + @model = DummyBaseModel + @gateway = mock() + @mapping = mock() + @model.stubs(:gateway).returns(@gateway) + @gateway.stubs(:mapping).returns(@mapping) + @document_type = 'dummybase' + end + + should "forward the argument to mapping" do + @gateway.expects(:document_type).with(@document_type).once + @mapping.expects(:type=).with(@document_type).once + @model.document_type @document_type + end + + should "return the value from the gateway" do + @gateway.expects(:document_type).once.returns(@document_type) + @mapping.expects(:type=).never + returned_type = @model.document_type + assert_equal @document_type, returned_type + end end end end From 8508577ab0fa33934cc678af426f498e56aa7e3d Mon Sep 17 00:00:00 2001 From: Roman Charushin <asroman@mail.ru> Date: Thu, 23 Apr 2015 21:30:23 +0300 Subject: [PATCH 220/582] [MODEL] Refactored the code in `__find_in_batches` to use Enumerable#each_slice Closes #378 --- .../lib/elasticsearch/model/adapters/mongoid.rb | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb index e5a907235..5117dbf58 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb @@ -64,18 +64,8 @@ module Importing # def __find_in_batches(options={}, &block) options[:batch_size] ||= 1_000 - items = [] - - all.no_timeout.each do |item| - items << item - - if items.length % options[:batch_size] == 0 - yield items - items = [] - end - end - - unless items.empty? + + all.no_timeout.each_slice(options[:batch_size]) do |items| yield items end end From 1c43a5331b642a542ac17a11484cb8a382b3f603 Mon Sep 17 00:00:00 2001 From: Orion Delwaterman <delwaterman@gmail.com> Date: Thu, 23 Apr 2015 13:05:20 -0400 Subject: [PATCH 221/582] [MODEL] Added a convenience accessor for the `aggregations` part of response Closes #376 --- .../lib/elasticsearch/model/response.rb | 6 ++++++ elasticsearch-model/test/unit/response_test.rb | 12 +++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response.rb b/elasticsearch-model/lib/elasticsearch/model/response.rb index b62ba822c..efece7143 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response.rb @@ -65,6 +65,12 @@ def timed_out def shards Hashie::Mash.new(response['_shards']) end + + # Returns a Hashie::Mash of the aggregations + # + def aggregations + response['aggregations'] ? Hashie::Mash.new(response['aggregations']) : nil + end end end end diff --git a/elasticsearch-model/test/unit/response_test.rb b/elasticsearch-model/test/unit/response_test.rb index ff14e5b22..0bd877af0 100644 --- a/elasticsearch-model/test/unit/response_test.rb +++ b/elasticsearch-model/test/unit/response_test.rb @@ -7,7 +7,8 @@ def self.index_name; 'foo'; end def self.document_type; 'bar'; end end - RESPONSE = { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, 'hits' => { 'hits' => [] } } + RESPONSE = { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, 'hits' => { 'hits' => [] }, + 'aggregations' => {'foo' => {'bar' => 10}}} setup do @search = Elasticsearch::Model::Searching::SearchRequest.new OriginClass, '*' @@ -63,5 +64,14 @@ def self.document_type; 'bar'; end Elasticsearch::Model::Response::Response.new OriginClass, @search end + + should "access the aggregations" do + @search.expects(:execute!).returns(RESPONSE) + + response = Elasticsearch::Model::Response::Response.new OriginClass, @search + assert_respond_to response, :aggregations + assert_kind_of Hashie::Mash, response.aggregations.foo + assert_equal 10, response.aggregations.foo.bar + end end end From 32ae874df3742008e6dc8c23892cc8777cd86b6c Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Wed, 20 May 2015 13:34:51 +0200 Subject: [PATCH 222/582] [STORE] Fixed, that `options` have not been passed to gateway in MyModel#update In the `Rails` module, the `update` method has been redefined to allow converting dates coming in from the Rails' forms, but the `options` argument has not been passed down to the `update` method in the `Store` module. This patch fixes that and adds unit and integration tests to cover the beahviour. Closes #347 --- .../elasticsearch/persistence/model/rails.rb | 2 +- .../elasticsearch/persistence/model/store.rb | 11 ++++++++++- .../test/integration/model/model_basic_test.rb | 10 ++++++++++ .../test/unit/model_rails_test.rb | 17 +++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb index 76c5b927b..5ed510e21 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb @@ -13,7 +13,7 @@ def initialize(attributes={}) end def update(attributes={}, options={}) - super(__convert_rails_dates(attributes)) + super(__convert_rails_dates(attributes), options) end end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb index 313d4e75f..2963bc0aa 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb @@ -99,6 +99,16 @@ def destroy(options={}) # p.update name: 'UPDATED' # => {"_index"=>"people", ... "_version"=>2} # + # @example Pass a version for concurrency control + # + # p.update( { name: 'UPDATED' }, { version: 2 } ) + # => {"_index"=>"people", ... "_version"=>3} + # + # @example An exception is raised when the version doesn't match + # + # p.update( { name: 'UPDATED' }, { version: 2 } ) + # => Elasticsearch::Transport::Transport::Errors::Conflict: [409] {"error" ... } + # # @return [Hash] The Elasticsearch response as a Hash # def update(attributes={}, options={}) @@ -112,7 +122,6 @@ def update(attributes={}, options={}) options.update type: self._type if self._type attributes.update( { updated_at: Time.now.utc } ) - response = self.class.gateway.update(self.id, { doc: attributes}.merge(options)) self.attributes = self.attributes.merge(attributes) diff --git a/elasticsearch-persistence/test/integration/model/model_basic_test.rb b/elasticsearch-persistence/test/integration/model/model_basic_test.rb index b43571e6e..f2d09a538 100644 --- a/elasticsearch-persistence/test/integration/model/model_basic_test.rb +++ b/elasticsearch-persistence/test/integration/model/model_basic_test.rb @@ -144,6 +144,16 @@ class ::Person assert found.updated_at > updated_at, [found.updated_at, updated_at].inspect end + should "respect the version" do + person = Person.create name: 'John Smith' + + person.update( { name: 'UPDATE 1' }) + + assert_raise Elasticsearch::Transport::Transport::Errors::Conflict do + person.update( { name: 'UPDATE 2' }, { version: 1 } ) + end + end + should "find all instances" do Person.create name: 'John Smith' Person.create name: 'Mary Smith' diff --git a/elasticsearch-persistence/test/unit/model_rails_test.rb b/elasticsearch-persistence/test/unit/model_rails_test.rb index dfd8edf71..9ead42896 100644 --- a/elasticsearch-persistence/test/unit/model_rails_test.rb +++ b/elasticsearch-persistence/test/unit/model_rails_test.rb @@ -91,5 +91,22 @@ class MyView; include ActionView::Helpers::UrlHelper; end assert_equal "2014-01-01", m.published_on.iso8601 end + context "when updating," do + should "pass the options to gateway" do + model = MyRailsModel.new name: 'Test' + model.stubs(:persisted?).returns(true) + + model.class.gateway + .expects(:update) + .with do |object, options| + assert_equal 'ABC', options[:routing] + true + end + .returns({'_id' => 'abc123'}) + + assert model.update( { title: 'UPDATED' }, { routing: 'ABC' } ) + end + end + end end From a78f4926a5633e1199c6c0b3e14122982242e959 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Wed, 20 May 2015 18:21:15 +0200 Subject: [PATCH 223/582] [MODEL] Fixed, that `param_name` is used when paginating with WillPaginate Closes #307 --- .../lib/elasticsearch/model/response/pagination.rb | 5 +++-- .../unit/response_pagination_will_paginate_test.rb | 12 ++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb index 6aa7f9f08..92d9c239c 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb @@ -122,8 +122,9 @@ def length # Article.search('foo').paginate(page: 1, per_page: 30) # def paginate(options) - page = [options[:page].to_i, 1].max - per_page = (options[:per_page] || klass.per_page).to_i + param_name = options[:param_name] || :page + page = [options[param_name].to_i, 1].max + per_page = (options[:per_page] || klass.per_page).to_i search.definition.update size: per_page, from: (page - 1) * per_page diff --git a/elasticsearch-model/test/unit/response_pagination_will_paginate_test.rb b/elasticsearch-model/test/unit/response_pagination_will_paginate_test.rb index 5722494b8..69af2ea07 100644 --- a/elasticsearch-model/test/unit/response_pagination_will_paginate_test.rb +++ b/elasticsearch-model/test/unit/response_pagination_will_paginate_test.rb @@ -153,6 +153,18 @@ class WillPaginateResponse < Elasticsearch::Model::Response::Response assert_equal 0, @response.search.definition[:from] assert_equal 33, @response.search.definition[:size] end + + should "use the param_name" do + @response.klass.client + .expects(:search) + .with do |definition| + assert_equal 10, definition[:from] + true + end + .returns(RESPONSE) + + @response.paginate(my_page: 2, per_page: 10, param_name: :my_page).to_a + end end context "#page and #per_page shorthand methods" do From 6b81a77771b420f8c010adca6dc5241e1a0cd046 Mon Sep 17 00:00:00 2001 From: Ryan Schlesinger <ryan@instanceinc.com> Date: Thu, 8 May 2014 10:20:15 -0700 Subject: [PATCH 224/582] [MODEL] Added, that String pagination parameters are converted to numbers Closes #110 --- .../model/response/pagination.rb | 6 +++-- .../unit/response_pagination_kaminari_test.rb | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb index 92d9c239c..da39a2997 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb @@ -66,10 +66,11 @@ def offset_value # Set the "limit" (`size`) value # def limit(value) + return self if value.to_i <= 0 @results = nil @records = nil @response = nil - @per_page = value + @per_page = value.to_i search.definition.update :size => @per_page search.definition.update :from => @per_page * (@page - 1) if @page @@ -79,11 +80,12 @@ def limit(value) # Set the "offset" (`from`) value # def offset(value) + return self if value.to_i < 0 @results = nil @records = nil @response = nil @page = nil - search.definition.update :from => value + search.definition.update :from => value.to_i self end diff --git a/elasticsearch-model/test/unit/response_pagination_kaminari_test.rb b/elasticsearch-model/test/unit/response_pagination_kaminari_test.rb index 1efdb38c2..ae9509fb5 100644 --- a/elasticsearch-model/test/unit/response_pagination_kaminari_test.rb +++ b/elasticsearch-model/test/unit/response_pagination_kaminari_test.rb @@ -104,6 +104,17 @@ def self.document_type; 'bar'; end assert_nil @response.instance_variable_get(:@records) assert_nil @response.instance_variable_get(:@results) end + + should 'coerce string parameters' do + @response.limit("35") + assert_equal 35, @response.search.definition[:size] + end + + should 'ignore invalid string parameters' do + @response.limit(35) + @response.limit("asdf") + assert_equal 35, @response.search.definition[:size] + end end context "with the page() and limit() methods" do @@ -151,6 +162,17 @@ def self.document_type; 'bar'; end assert_nil @response.instance_variable_get(:@records) assert_nil @response.instance_variable_get(:@results) end + + should 'coerce string parameters' do + @response.offset("35") + assert_equal 35, @response.search.definition[:from] + end + + should 'coerce invalid string parameters' do + @response.offset(35) + @response.offset("asdf") + assert_equal 0, @response.search.definition[:from] + end end context "total" do From 36bb08e5e23c05ca39dc771a9830ee3b2618a60c Mon Sep 17 00:00:00 2001 From: Ryan Schlesinger <ryan@instanceinc.com> Date: Mon, 11 May 2015 15:27:24 -0700 Subject: [PATCH 225/582] [MODEL] Stop requiring empty block for setting mapping options This just bit me when trying to configure a class that includes Elasticsearch::Persistence::Model. Since the attribute definitions handle setting up the mapping, I just wanted to set dynamic and _routing. I had to resort to debugging to find out that I had to pass an empty block to get the #mapping method to actually do something with my options. Closes #393 --- elasticsearch-model/lib/elasticsearch/model/indexing.rb | 4 ++-- elasticsearch-model/test/unit/indexing_test.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index 05dfbb8a7..c3cca9be9 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -133,9 +133,9 @@ module ClassMethods def mapping(options={}, &block) @mapping ||= Mappings.new(document_type, options) - if block_given? - @mapping.options.update(options) + @mapping.options.update(options) unless options.empty? + if block_given? @mapping.instance_eval(&block) return self else diff --git a/elasticsearch-model/test/unit/indexing_test.rb b/elasticsearch-model/test/unit/indexing_test.rb index f2eb121bb..73fc7af33 100644 --- a/elasticsearch-model/test/unit/indexing_test.rb +++ b/elasticsearch-model/test/unit/indexing_test.rb @@ -106,8 +106,8 @@ class NotFound < Exception; end end should "update and return the index mappings" do - DummyIndexingModel.mappings foo: 'boo' do; end - DummyIndexingModel.mappings bar: 'bam' do; end + DummyIndexingModel.mappings foo: 'boo' + DummyIndexingModel.mappings bar: 'bam' assert_equal( { dummy_indexing_model: { foo: "boo", bar: "bam", properties: {} } }, DummyIndexingModel.mappings.to_hash ) end From 373885081c76df3569ffbdf2dd7ff56c04d963ae Mon Sep 17 00:00:00 2001 From: Ryan Schlesinger <ryan@instanceinc.com> Date: Thu, 7 May 2015 16:53:16 -0700 Subject: [PATCH 226/582] [STORE] Fixed, that `MyModel#save` does in fact persist `updated_at` attribute Without this patch, a persistence model will not actually save the updated_at timestamp to Elasticsearch. Closes #389 Closes #342 --- .../lib/elasticsearch/persistence/model.rb | 4 ++-- .../lib/elasticsearch/persistence/model/store.rb | 4 ++-- .../test/integration/model/model_basic_test.rb | 16 +++++++++++++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb index 3eb3c3a0d..8db15bd82 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb @@ -121,8 +121,8 @@ def deserialize(document) # Set up common attributes # - attribute :created_at, DateTime, default: lambda { |o,a| Time.now.utc } - attribute :updated_at, DateTime, default: lambda { |o,a| Time.now.utc } + attribute :created_at, Time, default: lambda { |o,a| Time.now.utc } + attribute :updated_at, Time, default: lambda { |o,a| Time.now.utc } attr_reader :hit end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb index 2963bc0aa..a6c44c998 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb @@ -53,10 +53,10 @@ def save(options={}) options.update index: self._index if self._index options.update type: self._type if self._type - response = self.class.gateway.save(self, options) - self[:updated_at] = Time.now.utc + response = self.class.gateway.save(self, options) + @_id = response['_id'] @_index = response['_index'] @_type = response['_type'] diff --git a/elasticsearch-persistence/test/integration/model/model_basic_test.rb b/elasticsearch-persistence/test/integration/model/model_basic_test.rb index f2d09a538..e2db24d2e 100644 --- a/elasticsearch-persistence/test/integration/model/model_basic_test.rb +++ b/elasticsearch-persistence/test/integration/model/model_basic_test.rb @@ -135,7 +135,7 @@ class ::Person person = Person.create name: 'John Smith' updated_at = person.updated_at - sleep 1 + sleep 0.25 person.touch assert person.updated_at > updated_at, [person.updated_at, updated_at].inspect @@ -144,6 +144,20 @@ class ::Person assert found.updated_at > updated_at, [found.updated_at, updated_at].inspect end + should 'update the object timestamp on save' do + person = Person.create name: 'John Smith' + person.admin = true + sleep 0.25 + person.save + + Person.gateway.refresh_index! + + found = Person.find(person.id) + + # Compare without milliseconds + assert_equal person.updated_at.to_i, found.updated_at.to_i + end + should "respect the version" do person = Person.create name: 'John Smith' From 917c2dfb5ae18614882cec60d2526246f5bdbd27 Mon Sep 17 00:00:00 2001 From: Paul Geraghty <pgeraghty07@gmail.com> Date: Fri, 1 May 2015 00:44:06 +0200 Subject: [PATCH 227/582] [MODEL] Minor grammar fix (singular vs. plural) Closes #383 --- elasticsearch-model/lib/elasticsearch/model/indexing.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index c3cca9be9..fc18aaa0a 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -128,7 +128,7 @@ module ClassMethods # # => {:article=>{:dynamic=>"strict", :properties=>{:foo=>{:type=>"long"}}}} # # The `mappings` and `settings` methods are accessible directly on the model class, - # when it doesn't already defines them. Use the `__elasticsearch__` proxy otherwise. + # when it doesn't already define them. Use the `__elasticsearch__` proxy otherwise. # def mapping(options={}, &block) @mapping ||= Mappings.new(document_type, options) From 5c7cd1239976d2e9d2c51a852800412f3454d9fe Mon Sep 17 00:00:00 2001 From: David Padilla <david@easybroker.com> Date: Sat, 14 Mar 2015 18:45:31 -0600 Subject: [PATCH 228/582] [MODEL] Added, that index settings/mappings can be loaded from a YAML or JSON file This change allows you to specify a YAML file with settings for the indices. It would be helpful if you want to reuse analyzers in many different indexes. Also, YAML is much more readable than hashes in ruby code. Example: # config/elasticsearch/custom_analyzers.yml # # number_of_shards: 5 # analysis: # analyzer: # my_custom_analyzer: # tokenizer: "whitespace" # filter: ["lowercase", "asciifolding"] # class Article include Elasticsearch::Model settings "config/elasticsearch/custom_analyzers.yml" end class Author include Elasticsearch::Model settings "config/elasticsearch/custom_analyzers.yml" end Closes: #346 Closes: #351 --- .../lib/elasticsearch/model/indexing.rb | 17 +++++++++++++++++ elasticsearch-model/test/support/model.yml | 2 ++ elasticsearch-model/test/unit/indexing_test.rb | 9 ++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 elasticsearch-model/test/support/model.yml diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index fc18aaa0a..030fe2bd1 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -153,7 +153,24 @@ def mapping(options={}, &block) # # # => {:index=>{:number_of_shards=>1}} # + # You can specify a YAML file with settings + # + # @example Define index settings from YAML file + # + # # config/elasticsearch/articles.yml: + # # + # # index: + # # number_of_shards: 1 + # # + # + # Article.settings "config/elasticsearch/articles.yml" + # + # Article.settings.to_hash + # + # # => { "index" => { "number_of_shards" => 1 } } + # def settings(settings={}, &block) + settings = YAML.load_file(settings) if settings.is_a?(String) @settings ||= Settings.new(settings) @settings.settings.update(settings) unless settings.empty? diff --git a/elasticsearch-model/test/support/model.yml b/elasticsearch-model/test/support/model.yml new file mode 100644 index 000000000..ba8ca60f3 --- /dev/null +++ b/elasticsearch-model/test/support/model.yml @@ -0,0 +1,2 @@ +baz: + 'qux' diff --git a/elasticsearch-model/test/unit/indexing_test.rb b/elasticsearch-model/test/unit/indexing_test.rb index 73fc7af33..ceed55d7b 100644 --- a/elasticsearch-model/test/unit/indexing_test.rb +++ b/elasticsearch-model/test/unit/indexing_test.rb @@ -28,13 +28,20 @@ class NotFound < Exception; end assert_instance_of Elasticsearch::Model::Indexing::Settings, DummyIndexingModel.settings end - should "update and return the index settings" do + should "update and return the index settings from a hash" do DummyIndexingModel.settings foo: 'boo' DummyIndexingModel.settings bar: 'bam' assert_equal( {foo: 'boo', bar: 'bam'}, DummyIndexingModel.settings.to_hash) end + should "update and return the index settings from a yml file" do + DummyIndexingModel.settings "test/support/model.yml" + DummyIndexingModel.settings bar: 'bam' + + assert_equal( {foo: 'boo', bar: 'bam', 'baz' => 'qux'}, DummyIndexingModel.settings.to_hash) + end + should "evaluate the block" do DummyIndexingModel.expects(:foo) From b96978348caa047fafce07c1efe33a5849ab1233 Mon Sep 17 00:00:00 2001 From: David Padilla <david@easybroker.com> Date: Wed, 22 Apr 2015 12:43:43 -0500 Subject: [PATCH 229/582] [MODEL] Added, that index settings can be loaded from any object that responds to `:read` Related: #346 Related: #351 --- .../lib/elasticsearch/model/indexing.rb | 25 ++++++++++++++++--- elasticsearch-model/test/support/model.json | 1 + .../test/unit/indexing_test.rb | 9 ++++++- 3 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 elasticsearch-model/test/support/model.json diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index 030fe2bd1..60e337eaa 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -153,7 +153,8 @@ def mapping(options={}, &block) # # # => {:index=>{:number_of_shards=>1}} # - # You can specify a YAML file with settings + # You can read settings from any object that responds to :read + # as long as its return value can be parsed as either YAML or JSON. # # @example Define index settings from YAML file # @@ -163,14 +164,28 @@ def mapping(options={}, &block) # # number_of_shards: 1 # # # - # Article.settings "config/elasticsearch/articles.yml" + # Article.settings File.open("config/elasticsearch/articles.yml") + # + # Article.settings.to_hash + # + # # => { "index" => { "number_of_shards" => 1 } } + # + # + # @example Define index settings from JSON file + # + # # config/elasticsearch/articles.json: + # # + # # { "index": { "number_of_shards": 1 } } + # # + # + # Article.settings File.open("config/elasticsearch/articles.json") # # Article.settings.to_hash # # # => { "index" => { "number_of_shards" => 1 } } # def settings(settings={}, &block) - settings = YAML.load_file(settings) if settings.is_a?(String) + settings = YAML.load(settings.read) if settings.respond_to?(:read) @settings ||= Settings.new(settings) @settings.settings.update(settings) unless settings.empty? @@ -183,6 +198,10 @@ def settings(settings={}, &block) end end + def load_settings_from_io(settings) + YAML.load(settings.read) + end + # Creates an index with correct name, automatically passing # `settings` and `mappings` defined in the model # diff --git a/elasticsearch-model/test/support/model.json b/elasticsearch-model/test/support/model.json new file mode 100644 index 000000000..fcf3a6473 --- /dev/null +++ b/elasticsearch-model/test/support/model.json @@ -0,0 +1 @@ +{ "baz": "qux" } diff --git a/elasticsearch-model/test/unit/indexing_test.rb b/elasticsearch-model/test/unit/indexing_test.rb index ceed55d7b..ee1fdc6aa 100644 --- a/elasticsearch-model/test/unit/indexing_test.rb +++ b/elasticsearch-model/test/unit/indexing_test.rb @@ -36,7 +36,14 @@ class NotFound < Exception; end end should "update and return the index settings from a yml file" do - DummyIndexingModel.settings "test/support/model.yml" + DummyIndexingModel.settings File.open("test/support/model.yml") + DummyIndexingModel.settings bar: 'bam' + + assert_equal( {foo: 'boo', bar: 'bam', 'baz' => 'qux'}, DummyIndexingModel.settings.to_hash) + end + + should "update and return the index settings from a json file" do + DummyIndexingModel.settings File.open("test/support/model.json") DummyIndexingModel.settings bar: 'bam' assert_equal( {foo: 'boo', bar: 'bam', 'baz' => 'qux'}, DummyIndexingModel.settings.to_hash) From 14d636e2b8b0d78f234f764b2623c68268edda30 Mon Sep 17 00:00:00 2001 From: David Padilla <david@easybroker.com> Date: Wed, 22 Apr 2015 18:06:24 -0500 Subject: [PATCH 230/582] [RAILS] Added an example application template that loads settings from a file Related: #346 Related: #351 --- .../lib/rails/templates/05-settings-files.rb | 75 +++++++++++++++++++ .../rails/templates/articles_settings.json | 1 + 2 files changed, 76 insertions(+) create mode 100644 elasticsearch-rails/lib/rails/templates/05-settings-files.rb create mode 100644 elasticsearch-rails/lib/rails/templates/articles_settings.json diff --git a/elasticsearch-rails/lib/rails/templates/05-settings-files.rb b/elasticsearch-rails/lib/rails/templates/05-settings-files.rb new file mode 100644 index 000000000..3ce868b6e --- /dev/null +++ b/elasticsearch-rails/lib/rails/templates/05-settings-files.rb @@ -0,0 +1,75 @@ +# $ rails new searchapp --skip --skip-bundle --template https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/05-settings-files.rb + +# (See: 01-basic.rb, 02-pretty.rb, 03-expert.rb, 04-dsl.rb) + +append_to_file 'README.rdoc', <<-README + +== [5] Settings Files + +The `settings-files` template refactors the `Searchable` module to load the index settings +from an external file. + +README + +git add: "README.rdoc" +git commit: "-m '[05] Updated the application README'" + +# ----- Setup the Searchable module to load settings from config/elasticsearch/articles_settings.json + +gsub_file "app/models/concerns/searchable.rb", + /index: { number_of_shards: 1, number_of_replicas: 0 }/, + "File.open('config/elasticsearch/articles_settings.json')" + +git add: "app/models/concerns/searchable.rb" +git commit: "-m 'Setup the Searchable module to load settings from file'" + +# ----- Copy the articles_settings.json file ------------------------------------------------------- + +copy_file File.expand_path('../articles_settings.json', __FILE__), 'config/elasticsearch/articles_settings.json' + +git add: "config/elasticsearch/articles_settings.json" +git commit: "-m 'Create the articles settings file'" + +# ----- Temporarily set local repo for testing ---------------------------------------------------- + +gsub_file "Gemfile", + %r{gem 'elasticsearch-model', git: 'git://github.com/elasticsearch/elasticsearch-rails.git'}, + "gem 'elasticsearch-model', path: File.expand_path('../../../../../../elasticsearch-model', __FILE__)" + +# ----- Run bundle install ------------------------------------------------------------------------ + +run "bundle install" + +# ----- Recreate the index ------------------------------------------------------------------------ + +rake "environment elasticsearch:import:model CLASS='Article' BATCH=100 FORCE=y" + +# ----- Print Git log ----------------------------------------------------------------------------- + +puts +say_status "Git", "Details about the application:", :yellow +puts '-'*80, '' + +git tag: "settings-files" +git log: "--reverse --oneline HEAD...dsl" + +# ----- Start the application --------------------------------------------------------------------- + +unless ENV['RAILS_NO_SERVER_START'] + require 'net/http' + if (begin; Net::HTTP.get(URI('http://localhost:3000')); rescue Errno::ECONNREFUSED; false; rescue Exception; true; end) + puts "\n" + say_status "ERROR", "Some other application is running on port 3000!\n", :red + puts '-'*80 + + port = ask("Please provide free port:", :bold) + else + port = '3000' + end + + puts "", "="*80 + say_status "DONE", "\e[1mStarting the application. Open http://localhost:#{port}\e[0m", :yellow + puts "="*80, "" + + run "rails server --port=#{port}" +end diff --git a/elasticsearch-rails/lib/rails/templates/articles_settings.json b/elasticsearch-rails/lib/rails/templates/articles_settings.json new file mode 100644 index 000000000..c8fd35ea6 --- /dev/null +++ b/elasticsearch-rails/lib/rails/templates/articles_settings.json @@ -0,0 +1 @@ +{ "number_of_shards": 1, "number_of_replicas": 0 } From f5162fac0197136264851f418997613915d1d3e9 Mon Sep 17 00:00:00 2001 From: Ryan Schlesinger <ryan@instanceinc.com> Date: Thu, 21 May 2015 17:08:09 -0700 Subject: [PATCH 231/582] [STORE] Fixed timestamp tests Until we have a version of rails that uses millisecond precision (>=4.1) we have to wait an entire second to avoid race conditions. Reverts decreased sleep values in 3738850 Closes #407 --- .../test/integration/model/model_basic_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-persistence/test/integration/model/model_basic_test.rb b/elasticsearch-persistence/test/integration/model/model_basic_test.rb index e2db24d2e..b052114f8 100644 --- a/elasticsearch-persistence/test/integration/model/model_basic_test.rb +++ b/elasticsearch-persistence/test/integration/model/model_basic_test.rb @@ -135,7 +135,7 @@ class ::Person person = Person.create name: 'John Smith' updated_at = person.updated_at - sleep 0.25 + sleep 1 person.touch assert person.updated_at > updated_at, [person.updated_at, updated_at].inspect @@ -147,7 +147,7 @@ class ::Person should 'update the object timestamp on save' do person = Person.create name: 'John Smith' person.admin = true - sleep 0.25 + sleep 1 person.save Person.gateway.refresh_index! From 3792616cfc98aea7bd37702492a211e9665cb02f Mon Sep 17 00:00:00 2001 From: Rhett Sutphin <rhett@detailedbalance.net> Date: Thu, 5 Jun 2014 12:34:52 -0500 Subject: [PATCH 232/582] [MODEL] Added proper support for the new "multi_fields" in the mapping DSL This branch updates the `mapping` DSL to have nested `indexes` calls produce embedded fields (instead of properties) for the core types where this is appropriate. This is allowed starting with [elasticsearch 1.0][es10mf] as a replacement for `multi_field`. [es10mf]: http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/_multi_fields.html `type: "multi_field"` will still work as before; this change should not break backwards compatibility. Closes #138 --- .../lib/elasticsearch/model/indexing.rb | 5 ++- .../test/unit/indexing_test.rb | 39 ++++++++++++------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index 60e337eaa..eb547e57f 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -36,6 +36,9 @@ def as_json(options={}) class Mappings attr_accessor :options, :type + # @private + TYPES_WITH_EMBEDDED_PROPERTIES = %w(object nested) + def initialize(type, options={}) raise ArgumentError, "`type` is missing" if type.nil? @@ -49,7 +52,7 @@ def indexes(name, options = {}, &block) if block_given? @mapping[name][:type] ||= 'object' - properties = @mapping[name][:type] == 'multi_field' ? :fields : :properties + properties = TYPES_WITH_EMBEDDED_PROPERTIES.include?(@mapping[name][:type]) ? :properties : :fields @mapping[name][properties] ||= {} diff --git a/elasticsearch-model/test/unit/indexing_test.rb b/elasticsearch-model/test/unit/indexing_test.rb index ee1fdc6aa..cd9941f76 100644 --- a/elasticsearch-model/test/unit/indexing_test.rb +++ b/elasticsearch-model/test/unit/indexing_test.rb @@ -90,27 +90,36 @@ class NotFound < Exception; end assert_equal 'string', mappings.to_hash[:mytype][:properties][:bar][:type] end - should "define embedded properties" do - mappings = Elasticsearch::Model::Indexing::Mappings.new :mytype + %w(string long double boolean multi_field).each do |outer_type| + should "define embedded fields for #{outer_type}" do + mappings = Elasticsearch::Model::Indexing::Mappings.new :mytype - mappings.indexes :foo do - indexes :bar - end + mappings.indexes :foo, type: outer_type do + indexes :raw, analyzer: 'keyword' + end - mappings.indexes :multi, type: 'multi_field' do - indexes :multi, analyzer: 'snowball' - indexes :raw, analyzer: 'keyword' + foo_mapping = mappings.to_hash[:mytype][:properties][:foo] + + assert_equal outer_type, foo_mapping[:type] + assert_nil foo_mapping[:properties] + assert_equal 'string', foo_mapping[:fields][:raw][:type] end + end - assert_equal 'object', mappings.to_hash[:mytype][:properties][:foo][:type] - assert_equal 'string', mappings.to_hash[:mytype][:properties][:foo][:properties][:bar][:type] + %w(object nested).each do |outer_type| + should "define embedded properties for #{outer_type}" do + mappings = Elasticsearch::Model::Indexing::Mappings.new :mytype - assert_equal 'multi_field', mappings.to_hash[:mytype][:properties][:multi][:type] - assert_equal 'snowball', mappings.to_hash[:mytype][:properties][:multi][:fields][:multi][:analyzer] - assert_equal 'keyword', mappings.to_hash[:mytype][:properties][:multi][:fields][:raw][:analyzer] - end + mappings.indexes :foo, type: outer_type do + indexes :bar + end - should "define multi_field properties" do + foo_mapping = mappings.to_hash[:mytype][:properties][:foo] + + assert_equal outer_type, foo_mapping[:type] + assert_nil foo_mapping[:fields] + assert_equal 'string', foo_mapping[:properties][:bar][:type] + end end end From 2f9f7a58807d88b907c9c1dba63877ca0f881e27 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 22 May 2015 16:54:36 +0200 Subject: [PATCH 233/582] [MODEL] Cleaned up the tests for multiple fields/properties in mapping DSL This removes the dynamic definitions of tests from 3792616 and makes the tests more physical. Related: #138 --- .../lib/elasticsearch/model/indexing.rb | 3 +- .../test/unit/indexing_test.rb | 62 ++++++++++++------- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index eb547e57f..5b1b05791 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -47,7 +47,7 @@ def initialize(type, options={}) @mapping = {} end - def indexes(name, options = {}, &block) + def indexes(name, options={}, &block) @mapping[name] = options if block_given? @@ -66,7 +66,6 @@ def indexes(name, options = {}, &block) end # Set the type to `string` by default - # @mapping[name][:type] ||= 'string' self diff --git a/elasticsearch-model/test/unit/indexing_test.rb b/elasticsearch-model/test/unit/indexing_test.rb index cd9941f76..b1ccd8635 100644 --- a/elasticsearch-model/test/unit/indexing_test.rb +++ b/elasticsearch-model/test/unit/indexing_test.rb @@ -90,36 +90,56 @@ class NotFound < Exception; end assert_equal 'string', mappings.to_hash[:mytype][:properties][:bar][:type] end - %w(string long double boolean multi_field).each do |outer_type| - should "define embedded fields for #{outer_type}" do - mappings = Elasticsearch::Model::Indexing::Mappings.new :mytype - - mappings.indexes :foo, type: outer_type do - indexes :raw, analyzer: 'keyword' - end + should "define multiple fields" do + mappings = Elasticsearch::Model::Indexing::Mappings.new :mytype - foo_mapping = mappings.to_hash[:mytype][:properties][:foo] + mappings.indexes :foo_1, type: 'string' do + indexes :raw, analyzer: 'keyword' + end - assert_equal outer_type, foo_mapping[:type] - assert_nil foo_mapping[:properties] - assert_equal 'string', foo_mapping[:fields][:raw][:type] + mappings.indexes :foo_2, type: 'multi_field' do + indexes :raw, analyzer: 'keyword' end + + assert_equal 'string', mappings.to_hash[:mytype][:properties][:foo_1][:type] + assert_equal 'string', mappings.to_hash[:mytype][:properties][:foo_1][:fields][:raw][:type] + assert_equal 'keyword', mappings.to_hash[:mytype][:properties][:foo_1][:fields][:raw][:analyzer] + assert_nil mappings.to_hash[:mytype][:properties][:foo_1][:properties] + + assert_equal 'multi_field', mappings.to_hash[:mytype][:properties][:foo_2][:type] + assert_equal 'string', mappings.to_hash[:mytype][:properties][:foo_2][:fields][:raw][:type] + assert_equal 'keyword', mappings.to_hash[:mytype][:properties][:foo_2][:fields][:raw][:analyzer] + assert_nil mappings.to_hash[:mytype][:properties][:foo_2][:properties] end - %w(object nested).each do |outer_type| - should "define embedded properties for #{outer_type}" do - mappings = Elasticsearch::Model::Indexing::Mappings.new :mytype + should "define embedded properties" do + mappings = Elasticsearch::Model::Indexing::Mappings.new :mytype - mappings.indexes :foo, type: outer_type do - indexes :bar - end + mappings.indexes :foo do + indexes :bar + end - foo_mapping = mappings.to_hash[:mytype][:properties][:foo] + mappings.indexes :foo_object, type: 'object' do + indexes :bar + end - assert_equal outer_type, foo_mapping[:type] - assert_nil foo_mapping[:fields] - assert_equal 'string', foo_mapping[:properties][:bar][:type] + mappings.indexes :foo_nested, type: 'nested' do + indexes :bar end + + # Object is the default when `type` is missing and there's a block passed + # + assert_equal 'object', mappings.to_hash[:mytype][:properties][:foo][:type] + assert_equal 'string', mappings.to_hash[:mytype][:properties][:foo][:properties][:bar][:type] + assert_nil mappings.to_hash[:mytype][:properties][:foo][:fields] + + assert_equal 'object', mappings.to_hash[:mytype][:properties][:foo_object][:type] + assert_equal 'string', mappings.to_hash[:mytype][:properties][:foo_object][:properties][:bar][:type] + assert_nil mappings.to_hash[:mytype][:properties][:foo_object][:fields] + + assert_equal 'nested', mappings.to_hash[:mytype][:properties][:foo_nested][:type] + assert_equal 'string', mappings.to_hash[:mytype][:properties][:foo_nested][:properties][:bar][:type] + assert_nil mappings.to_hash[:mytype][:properties][:foo_nested][:fields] end end From 55e50c6b362b0a7846cf73a800fe2047a9ea0c3c Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 22 May 2015 17:58:11 +0200 Subject: [PATCH 234/582] [MODEL] Removed useless variable assignment in Elasticsearch::Model::Response::Results#results Closes #325 --- elasticsearch-model/lib/elasticsearch/model/response/results.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/results.rb b/elasticsearch-model/lib/elasticsearch/model/response/results.rb index d185eae6f..006e66a46 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/results.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/results.rb @@ -22,7 +22,7 @@ def initialize(klass, response, options={}) # def results # TODO: Configurable custom wrapper - @results = response.response['hits']['hits'].map { |hit| Result.new(hit) } + response.response['hits']['hits'].map { |hit| Result.new(hit) } end end From 55af45b309e6e15eff4352f0bc206694fa0b56b6 Mon Sep 17 00:00:00 2001 From: Ben Woosley <ben.woosley@gmail.com> Date: Tue, 23 Sep 2014 10:38:17 -0700 Subject: [PATCH 235/582] [MODEL] On #import, raise an exception if the index does not exists This is to avoid the index being created based on the structure of the bulk import json. Extract #index_exists? to serve. Closes #253 --- .../lib/elasticsearch/model/importing.rb | 3 + .../lib/elasticsearch/model/indexing.rb | 18 ++++- .../test/unit/importing_test.rb | 51 ++++++++++---- .../test/unit/indexing_test.rb | 68 ++++++++++++++++--- 4 files changed, 116 insertions(+), 24 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/importing.rb b/elasticsearch-model/lib/elasticsearch/model/importing.rb index a92e9b07c..3bf96472f 100644 --- a/elasticsearch-model/lib/elasticsearch/model/importing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/importing.rb @@ -114,6 +114,9 @@ def import(options={}, &block) if options.delete(:force) self.create_index! force: true, index: target_index + elsif !self.index_exists? index: target_index + raise ArgumentError, + "#{target_index} does not exist to be imported into. Use create_index! or the :force option to create it." end __find_in_batches(options) do |batch| diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index 5b1b05791..d57c756ba 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -224,7 +224,7 @@ def create_index!(options={}) delete_index!(options.merge index: target_index) if options[:force] - unless ( self.client.indices.exists(index: target_index) rescue false ) + unless index_exists?(index: target_index) self.client.indices.create index: target_index, body: { settings: self.settings.to_hash, @@ -232,6 +232,22 @@ def create_index!(options={}) end end + # Returns true if the index exists + # + # @example Check whether the model's index exists + # + # Article.__elasticsearch__.index_exists? + # + # @example Check whether a specific index exists + # + # Article.__elasticsearch__.index_exists? index: 'my-index' + # + def index_exists?(options={}) + target_index = options[:index] || self.index_name + + self.client.indices.exists(index: target_index) rescue false + end + # Deletes the index with corresponding name # # @example Delete the index for the `Article` model diff --git a/elasticsearch-model/test/unit/importing_test.rb b/elasticsearch-model/test/unit/importing_test.rb index 6d703effe..6f739acec 100644 --- a/elasticsearch-model/test/unit/importing_test.rb +++ b/elasticsearch-model/test/unit/importing_test.rb @@ -44,6 +44,7 @@ def importing_mixin DummyImportingModel.expects(:client).returns(client) DummyImportingModel.expects(:index_name).returns('foo') DummyImportingModel.expects(:document_type).returns('foo') + DummyImportingModel.stubs(:index_exists?).returns(true) DummyImportingModel.stubs(:__batch_to_bulk) assert_equal 0, DummyImportingModel.import end @@ -61,6 +62,7 @@ def importing_mixin DummyImportingModel.stubs(:client).returns(client) DummyImportingModel.stubs(:index_name).returns('foo') DummyImportingModel.stubs(:document_type).returns('foo') + DummyImportingModel.stubs(:index_exists?).returns(true) DummyImportingModel.stubs(:__batch_to_bulk) assert_equal 1, DummyImportingModel.import @@ -79,6 +81,7 @@ def importing_mixin DummyImportingModel.stubs(:client).returns(client) DummyImportingModel.stubs(:index_name).returns('foo') DummyImportingModel.stubs(:document_type).returns('foo') + DummyImportingModel.stubs(:index_exists?).returns(true) DummyImportingModel.stubs(:__batch_to_bulk) assert_equal [{'index' => {'error' => 'FAILED'}}], DummyImportingModel.import(return: 'errors') @@ -97,6 +100,7 @@ def importing_mixin DummyImportingModel.stubs(:client).returns(client) DummyImportingModel.stubs(:index_name).returns('foo') DummyImportingModel.stubs(:document_type).returns('foo') + DummyImportingModel.stubs(:index_exists?).returns(true) DummyImportingModel.stubs(:__batch_to_bulk) DummyImportingModel.import do |response| @@ -104,22 +108,42 @@ def importing_mixin end end - should "delete and create the index with the force option" do - DummyImportingModel.expects(:__find_in_batches).with do |options| - assert_equal 'bar', options[:foo] - assert_nil options[:force] - true - end + context "when the index does not exist" do + should "raise an exception" do + Elasticsearch::Model::Adapter.expects(:from_class) + .with(DummyImportingModel) + .returns(DummyImportingAdapter) + + DummyImportingModel.__send__ :include, Elasticsearch::Model::Importing + + DummyImportingModel.expects(:index_name).returns('foo') + DummyImportingModel.expects(:document_type).returns('foo') + DummyImportingModel.expects(:index_exists?).returns(false) - DummyImportingModel.expects(:create_index!).with do |options| - assert_equal true, options[:force] - true + assert_raise ArgumentError do + DummyImportingModel.import + end end + end - DummyImportingModel.expects(:index_name).returns('foo') - DummyImportingModel.expects(:document_type).returns('foo') + context "with the force option" do + should "delete and create the index" do + DummyImportingModel.expects(:__find_in_batches).with do |options| + assert_equal 'bar', options[:foo] + assert_nil options[:force] + true + end + + DummyImportingModel.expects(:create_index!).with do |options| + assert_equal true, options[:force] + true + end + + DummyImportingModel.expects(:index_name).returns('foo') + DummyImportingModel.expects(:document_type).returns('foo') - DummyImportingModel.import force: true, foo: 'bar' + DummyImportingModel.import force: true, foo: 'bar' + end end should "allow passing a different index / type" do @@ -141,6 +165,7 @@ def importing_mixin .returns({'items' => [ {'index' => {} }]}) DummyImportingModel.stubs(:client).returns(client) + DummyImportingModel.stubs(:index_exists?).returns(true) DummyImportingModel.stubs(:__batch_to_bulk) DummyImportingModel.import index: 'my-new-index', type: 'my-other-type' @@ -151,6 +176,7 @@ def importing_mixin transform = lambda {|a|} DummyImportingModel.stubs(:client).returns(client) + DummyImportingModel.stubs(:index_exists?).returns(true) DummyImportingModel.expects(:__transform).returns(transform) DummyImportingModel.expects(:__batch_to_bulk).with(anything, transform) @@ -162,6 +188,7 @@ def importing_mixin transform = lambda {|a|} DummyImportingModel.stubs(:client).returns(client) + DummyImportingModel.stubs(:index_exists?).returns(true) DummyImportingModel.expects(:__batch_to_bulk).with(anything, transform) DummyImportingModel.import index: 'foo', type: 'bar', transform: transform diff --git a/elasticsearch-model/test/unit/indexing_test.rb b/elasticsearch-model/test/unit/indexing_test.rb index b1ccd8635..19a8c0dbf 100644 --- a/elasticsearch-model/test/unit/indexing_test.rb +++ b/elasticsearch-model/test/unit/indexing_test.rb @@ -414,6 +414,55 @@ def as_indexed_json(options={}) end end + context "Checking for index existence" do + context "the index exists" do + should "return true" do + indices = mock('indices', exists: true) + client = stub('client', indices: indices) + + DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once + + assert_equal true, DummyIndexingModelForRecreate.index_exists? + end + end + + context "the index does not exists" do + should "return false" do + indices = mock('indices', exists: false) + client = stub('client', indices: indices) + + DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once + + assert_equal false, DummyIndexingModelForRecreate.index_exists? + end + end + + context "the indices raises" do + should "return false" do + client = stub('client') + client.expects(:indices).raises(StandardError) + + DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once + + assert_equal false, DummyIndexingModelForRecreate.index_exists? + end + end + + context "the indices raises" do + should "return false" do + indices = stub('indices') + client = stub('client') + client.expects(:indices).returns(indices) + + indices.expects(:exists).raises(StandardError) + + DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once + + assert_equal false, DummyIndexingModelForRecreate.index_exists? + end + end + end + context "Re-creating the index" do class ::DummyIndexingModelForRecreate extend ActiveModel::Naming @@ -468,8 +517,6 @@ class ::DummyIndexingModelForRecreate indices = stub('indices') client.stubs(:indices).returns(indices) - indices.expects(:exists).returns(false) - indices.expects(:create).with do |payload| assert_equal 'dummy_indexing_model_for_recreates', payload[:index] assert_equal 1, payload[:body][:settings][:index][:number_of_shards] @@ -477,6 +524,7 @@ class ::DummyIndexingModelForRecreate true end.returns({}) + DummyIndexingModelForRecreate.expects(:index_exists?).returns(false) DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once assert_nothing_raised { DummyIndexingModelForRecreate.create_index! } @@ -487,11 +535,10 @@ class ::DummyIndexingModelForRecreate indices = stub('indices') client.stubs(:indices).returns(indices) - indices.expects(:exists).returns(true) - indices.expects(:create).never - DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once + DummyIndexingModelForRecreate.expects(:index_exists?).returns(true) + DummyIndexingModelForRecreate.expects(:client).returns(client).never assert_nothing_raised { DummyIndexingModelForRecreate.create_index! } end @@ -502,9 +549,9 @@ class ::DummyIndexingModelForRecreate client.stubs(:indices).returns(indices) indices.expects(:delete).returns({}) - indices.expects(:exists).returns(false) - indices.expects(:create).raises(Exception) + indices.expects(:create).raises(Exception).at_least_once + DummyIndexingModelForRecreate.expects(:index_exists?).returns(false) DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once assert_raise(Exception) { DummyIndexingModelForRecreate.create_index! force: true } @@ -516,9 +563,9 @@ class ::DummyIndexingModelForRecreate client.stubs(:indices).returns(indices) indices.expects(:delete).returns({}) - indices.expects(:exists).returns(false) indices.expects(:create).returns({}).at_least_once + DummyIndexingModelForRecreate.expects(:index_exists?).returns(false) DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once assert_nothing_raised do @@ -559,12 +606,11 @@ class ::DummyIndexingModelForRecreate end should "create the custom index" do - @indices.expects(:exists).with do |arguments| + @indices.expects(:create).with do |arguments| assert_equal 'custom-foo', arguments[:index] true end - - @indices.expects(:create).with do |arguments| + DummyIndexingModelForRecreate.expects(:index_exists?).with do |arguments| assert_equal 'custom-foo', arguments[:index] true end From f6ebf4fb3421960f14dd5fff0e3dab87427e7f93 Mon Sep 17 00:00:00 2001 From: Miguel Fernandez <miguelfernandezfernandez@gmail.com> Date: Sun, 24 May 2015 12:08:39 +0200 Subject: [PATCH 236/582] [MODEL] Added "default per page" methods for pagination with multi model searches Due to the fact that different models aggregated on a multimodel search could have different default values for the page size. I have opted for defaulting to Kaminari's and WillPaginate's respective default values. Problem: Elasticsearch::Model.search(query, [AAA, BBB]).page(1).results NoMethodError: undefined method `default_per_page' for #Elasticsearch::Model::Multimodel:0x007ff9dbe4f520 Fixes #382 Closes #408 --- .../model/response/pagination.rb | 22 +- .../unit/response_pagination_kaminari_test.rb | 194 ++++++++++++++++- .../response_pagination_will_paginate_test.rb | 206 ++++++++++++++++-- 3 files changed, 397 insertions(+), 25 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb index da39a2997..80f84b7bc 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb @@ -31,7 +31,7 @@ def #{::Kaminari.config.page_method_name}(num=nil) @records = nil @response = nil @page = [num.to_i, 1].max - @per_page ||= klass.default_per_page + @per_page ||= __default_per_page self.search.definition.update size: @per_page, from: @per_page * (@page - 1) @@ -48,7 +48,7 @@ def limit_value when search.definition[:size] search.definition[:size] else - search.klass.default_per_page + __default_per_page end end @@ -94,6 +94,14 @@ def offset(value) def total_count results.total end + + # Returns the models's `per_page` value or the default + # + # @api private + # + def __default_per_page + klass.respond_to?(:default_per_page) && klass.default_per_page || ::Kaminari.config.default_per_page + end end # Allow models to be paginated with the "will_paginate" gem [https://github.com/mislav/will_paginate] @@ -126,7 +134,7 @@ def length def paginate(options) param_name = options[:param_name] || :page page = [options[param_name].to_i, 1].max - per_page = (options[:per_page] || klass.per_page).to_i + per_page = (options[:per_page] || __default_per_page).to_i search.definition.update size: per_page, from: (page - 1) * per_page @@ -168,6 +176,14 @@ def per_page(num = nil) def total_entries results.total end + + # Returns the models's `per_page` value or the default + # + # @api private + # + def __default_per_page + klass.respond_to?(:per_page) && klass.per_page || ::WillPaginate.per_page + end end end diff --git a/elasticsearch-model/test/unit/response_pagination_kaminari_test.rb b/elasticsearch-model/test/unit/response_pagination_kaminari_test.rb index ae9509fb5..629db7400 100644 --- a/elasticsearch-model/test/unit/response_pagination_kaminari_test.rb +++ b/elasticsearch-model/test/unit/response_pagination_kaminari_test.rb @@ -1,16 +1,17 @@ require 'test_helper' class Elasticsearch::Model::ResponsePaginationKaminariTest < Test::Unit::TestCase - context "Response pagination" do - class ModelClass - include ::Kaminari::ConfigurationMethods + class ModelClass + include ::Kaminari::ConfigurationMethods - def self.index_name; 'foo'; end - def self.document_type; 'bar'; end - end + def self.index_name; 'foo'; end + def self.document_type; 'bar'; end + end - RESPONSE = { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, - 'hits' => { 'total' => 100, 'hits' => (1..100).to_a.map { |i| { _id: i } } } } + RESPONSE = { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, + 'hits' => { 'total' => 100, 'hits' => (1..100).to_a.map { |i| { _id: i } } } } + + context "Response pagination" do setup do @search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, '*' @@ -208,4 +209,181 @@ def self.document_type; 'bar'; end end end end + + context "Multimodel response pagination" do + setup do + @multimodel = Elasticsearch::Model::Multimodel.new(ModelClass) + @search = Elasticsearch::Model::Searching::SearchRequest.new @multimodel, '*' + @response = Elasticsearch::Model::Response::Response.new @multimodel, @search, RESPONSE + @response.klass.stubs(:client).returns mock('client') + end + + should "have pagination methods" do + assert_respond_to @response, :page + assert_respond_to @response, :limit_value + assert_respond_to @response, :offset_value + assert_respond_to @response, :limit + assert_respond_to @response, :offset + assert_respond_to @response, :total_count + end + + context "#page method" do + should "advance the from/size" do + @response.klass.client + .expects(:search) + .with do |definition| + assert_equal 25, definition[:from] + assert_equal 25, definition[:size] + true + end + .returns(RESPONSE) + + assert_nil @response.search.definition[:from] + assert_nil @response.search.definition[:size] + + @response.page(2).to_a + assert_equal 25, @response.search.definition[:from] + assert_equal 25, @response.search.definition[:size] + end + + should "advance the from/size further" do + @response.klass.client + .expects(:search) + .with do |definition| + assert_equal 75, definition[:from] + assert_equal 25, definition[:size] + true + end + .returns(RESPONSE) + + @response.page(4).to_a + assert_equal 75, @response.search.definition[:from] + assert_equal 25, @response.search.definition[:size] + end + end + + context "limit/offset readers" do + should "return the default" do + assert_equal Kaminari.config.default_per_page, @response.limit_value + assert_equal 0, @response.offset_value + end + + should "return the value from URL parameters" do + search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, '*', size: 10, from: 50 + @response = Elasticsearch::Model::Response::Response.new ModelClass, search, RESPONSE + + assert_equal 10, @response.limit_value + assert_equal 50, @response.offset_value + end + + should "ignore the value from request body" do + search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, + { query: { match_all: {} }, from: 333, size: 999 } + @response = Elasticsearch::Model::Response::Response.new ModelClass, search, RESPONSE + + assert_equal Kaminari.config.default_per_page, @response.limit_value + assert_equal 0, @response.offset_value + end + end + + context "limit setter" do + setup do + @response.records + @response.results + end + + should "set the values" do + @response.limit(35) + assert_equal 35, @response.search.definition[:size] + end + + should "reset the variables" do + @response.limit(35) + + assert_nil @response.instance_variable_get(:@response) + assert_nil @response.instance_variable_get(:@records) + assert_nil @response.instance_variable_get(:@results) + end + end + + context "with the page() and limit() methods" do + setup do + @response.records + @response.results + end + + should "set the values" do + @response.page(3).limit(35) + assert_equal 35, @response.search.definition[:size] + assert_equal 70, @response.search.definition[:from] + end + + should "set the values when limit is called first" do + @response.limit(35).page(3) + assert_equal 35, @response.search.definition[:size] + assert_equal 70, @response.search.definition[:from] + end + + should "reset the instance variables" do + @response.page(3).limit(35) + + assert_nil @response.instance_variable_get(:@response) + assert_nil @response.instance_variable_get(:@records) + assert_nil @response.instance_variable_get(:@results) + end + end + + context "offset setter" do + setup do + @response.records + @response.results + end + + should "set the values" do + @response.offset(15) + assert_equal 15, @response.search.definition[:from] + end + + should "reset the variables" do + @response.offset(35) + + assert_nil @response.instance_variable_get(:@response) + assert_nil @response.instance_variable_get(:@records) + assert_nil @response.instance_variable_get(:@results) + end + end + + context "total" do + should "return the number of hits" do + @response.expects(:results).returns(mock('results', total: 100)) + assert_equal 100, @response.total_count + end + end + + context "results" do + setup do + @search.stubs(:execute!).returns RESPONSE + end + + should "return current page and total count" do + assert_equal 1, @response.page(1).results.current_page + assert_equal 100, @response.results.total_count + + assert_equal 5, @response.page(5).results.current_page + end + end + + context "records" do + setup do + @search.stubs(:execute!).returns RESPONSE + end + + should "return current page and total count" do + assert_equal 1, @response.page(1).records.current_page + assert_equal 100, @response.records.total_count + + assert_equal 5, @response.page(5).records.current_page + end + end + end end diff --git a/elasticsearch-model/test/unit/response_pagination_will_paginate_test.rb b/elasticsearch-model/test/unit/response_pagination_will_paginate_test.rb index 69af2ea07..6c9383525 100644 --- a/elasticsearch-model/test/unit/response_pagination_will_paginate_test.rb +++ b/elasticsearch-model/test/unit/response_pagination_will_paginate_test.rb @@ -3,24 +3,25 @@ require 'will_paginate/collection' class Elasticsearch::Model::ResponsePaginationWillPaginateTest < Test::Unit::TestCase - context "Response pagination" do - class ModelClass - def self.index_name; 'foo'; end - def self.document_type; 'bar'; end + class ModelClass + def self.index_name; 'foo'; end + def self.document_type; 'bar'; end - # WillPaginate adds this method to models (see WillPaginate::PerPage module) - def self.per_page - 33 - end + # WillPaginate adds this method to models (see WillPaginate::PerPage module) + def self.per_page + 33 end + end - # Subsclass Response so we can include WillPaginate module without conflicts with Kaminari. - class WillPaginateResponse < Elasticsearch::Model::Response::Response - include Elasticsearch::Model::Response::Pagination::WillPaginate - end + # Subsclass Response so we can include WillPaginate module without conflicts with Kaminari. + class WillPaginateResponse < Elasticsearch::Model::Response::Response + include Elasticsearch::Model::Response::Pagination::WillPaginate + end - RESPONSE = { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, - 'hits' => { 'total' => 100, 'hits' => (1..100).to_a.map { |i| { _id: i } } } } + RESPONSE = { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, + 'hits' => { 'total' => 100, 'hits' => (1..100).to_a.map { |i| { _id: i } } } } + + context "Response pagination" do setup do @search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, '*' @@ -217,4 +218,181 @@ class WillPaginateResponse < Elasticsearch::Model::Response::Response end end end + + context "Multimodel response pagination" do + setup do + @multimodel = Elasticsearch::Model::Multimodel.new ModelClass + @search = Elasticsearch::Model::Searching::SearchRequest.new @multimodel, '*' + @response = WillPaginateResponse.new @multimodel, @search, RESPONSE + @response.klass.stubs(:client).returns mock('client') + + @expected_methods = [ + # methods needed by WillPaginate::CollectionMethods + :current_page, + :offset, + :per_page, + :total_entries, + :length, + + # methods defined by WillPaginate::CollectionMethods + :total_pages, + :previous_page, + :next_page, + :out_of_bounds?, + ] + end + + should "have pagination methods" do + assert_respond_to @response, :paginate + + @expected_methods.each do |method| + assert_respond_to @response, method + end + end + + context "response.results" do + should "have pagination methods" do + @expected_methods.each do |method| + assert_respond_to @response.results, method + end + end + end + + context "#offset method" do + should "calculate offset using current_page and per_page" do + @response.per_page(3).page(3) + assert_equal 6, @response.offset + end + end + context "#length method" do + should "return count of paginated results" do + @response.per_page(3).page(3) + assert_equal 3, @response.length + end + end + + context "#paginate method" do + should "set from/size using WillPaginate defaults, ignoring aggregated models configuration" do + @response.klass.client + .expects(:search) + .with do |definition| + assert_equal 0, definition[:from] + assert_equal ::WillPaginate.per_page, definition[:size] + true + end + .returns(RESPONSE) + + assert_nil @response.search.definition[:from] + assert_nil @response.search.definition[:size] + + @response.paginate(page: nil).to_a + assert_equal 0, @response.search.definition[:from] + assert_equal ::WillPaginate.per_page, @response.search.definition[:size] + end + + should "set from/size using default per_page, ignoring aggregated models' configuration" do + @response.klass.client + .expects(:search) + .with do |definition| + assert_equal ::WillPaginate.per_page, definition[:from] + assert_equal ::WillPaginate.per_page, definition[:size] + true + end + .returns(RESPONSE) + + assert_nil @response.search.definition[:from] + assert_nil @response.search.definition[:size] + + @response.paginate(page: 2).to_a + assert_equal ::WillPaginate.per_page, @response.search.definition[:from] + assert_equal ::WillPaginate.per_page, @response.search.definition[:size] + end + + should "set from/size using custom page and per_page" do + @response.klass.client + .expects(:search) + .with do |definition| + assert_equal 18, definition[:from] + assert_equal 9, definition[:size] + true + end + .returns(RESPONSE) + + assert_nil @response.search.definition[:from] + assert_nil @response.search.definition[:size] + + @response.paginate(page: 3, per_page: 9).to_a + assert_equal 18, @response.search.definition[:from] + assert_equal 9, @response.search.definition[:size] + end + + should "search for first page if specified page is < 1" do + @response.klass.client + .expects(:search) + .with do |definition| + assert_equal 0, definition[:from] + assert_equal ::WillPaginate.per_page, definition[:size] + true + end + .returns(RESPONSE) + + assert_nil @response.search.definition[:from] + assert_nil @response.search.definition[:size] + + @response.paginate(page: "-1").to_a + assert_equal 0, @response.search.definition[:from] + assert_equal ::WillPaginate.per_page, @response.search.definition[:size] + end + end + + context "#page and #per_page shorthand methods" do + should "set from/size using default per_page" do + @response.page(5) + assert_equal 120, @response.search.definition[:from] + assert_equal ::WillPaginate.per_page, @response.search.definition[:size] + end + + should "set from/size when calling #page then #per_page" do + @response.page(5).per_page(3) + assert_equal 12, @response.search.definition[:from] + assert_equal 3, @response.search.definition[:size] + end + + should "set from/size when calling #per_page then #page" do + @response.per_page(3).page(5) + assert_equal 12, @response.search.definition[:from] + assert_equal 3, @response.search.definition[:size] + end + end + + context "#current_page method" do + should "return 1 by default" do + @response.paginate({}) + assert_equal 1, @response.current_page + end + + should "return current page number" do + @response.paginate(page: 3, per_page: 9) + assert_equal 3, @response.current_page + end + + should "return nil if not pagination set" do + assert_equal nil, @response.current_page + end + end + + context "#per_page method" do + should "return value set in paginate call" do + @response.paginate(per_page: 8) + assert_equal 8, @response.per_page + end + end + + context "#total_entries method" do + should "return total from response" do + @response.expects(:results).returns(mock('results', total: 100)) + assert_equal 100, @response.total_entries + end + end + end end From 2ed221c6acd9b53eff43606d26f9f189b2cab624 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Tue, 26 May 2015 12:18:56 +0200 Subject: [PATCH 237/582] [MODEL] Added an integration test for paginating multiple models Related: #408 --- .../test/integration/multiple_models_test.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/elasticsearch-model/test/integration/multiple_models_test.rb b/elasticsearch-model/test/integration/multiple_models_test.rb index 214217ae3..7d0bf7b6b 100644 --- a/elasticsearch-model/test/integration/multiple_models_test.rb +++ b/elasticsearch-model/test/integration/multiple_models_test.rb @@ -103,6 +103,14 @@ class ::Series < ActiveRecord::Base assert_equal 'The greatest Episode', response.records[0].name end + should "paginate the results" do + response = Elasticsearch::Model.search('series OR episode', [Series, Episode]) + + assert_equal 3, response.page(1).per(3).results.size + assert_equal 3, response.page(2).per(3).results.size + assert_equal 0, response.page(3).per(3).results.size + end + if Mongo.available? Mongo.connect_to 'mongoid_collections' From 738c63efacc167b6e8faae3b01a1a0135cfc8bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C3=96zg=C3=BCr?= <halil.ozgur@gmail.com> Date: Fri, 1 Aug 2014 20:02:00 +0300 Subject: [PATCH 238/582] [STORE] Fixed typos in the README Closes #186 --- elasticsearch-persistence/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index 62890e0f9..053366813 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -98,7 +98,7 @@ repository.delete(note) => {"found"=>true, "_index"=>"repository", "_type"=>"note", "_id"=>"1", "_version"=>2} ``` -The repository module provides a number of features and facilities to configure and customize the behaviour: +The repository module provides a number of features and facilities to configure and customize the behavior: * Configuring the Elasticsearch [client](https://github.com/elasticsearch/elasticsearch-ruby#usage) being used * Setting the index name, document type, and object class for deserialization @@ -126,7 +126,7 @@ repository = Elasticsearch::Persistence::Repository.new do # Set a custom document type type :my_note - # Specify the class to inicialize when deserializing documents + # Specify the class to initialize when deserializing documents klass Note # Configure the settings and mappings for the Elasticsearch index @@ -494,7 +494,7 @@ class Article # Execute code after saving the model. # - after_save { puts "Successfuly saved: #{self}" } + after_save { puts "Successfully saved: #{self}" } end ``` @@ -573,7 +573,7 @@ Any callbacks defined in the model will be triggered during the persistence oper ```ruby article.save -# Successfuly saved: #<Article {...}> +# Successfully saved: #<Article {...}> ``` The model also supports familiar `find_in_batches` and `find_each` methods to efficiently From 139e2429977e4d343f67f0f6d47c9cfb12711935 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 20 Jun 2015 09:06:56 +0200 Subject: [PATCH 239/582] [MODEL] Changed the Elasticsearch port in the Mongoid example to 9200 --- elasticsearch-model/examples/mongoid_article.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/examples/mongoid_article.rb b/elasticsearch-model/examples/mongoid_article.rb index fac9fa849..5cd12ca4f 100644 --- a/elasticsearch-model/examples/mongoid_article.rb +++ b/elasticsearch-model/examples/mongoid_article.rb @@ -21,7 +21,7 @@ Mongoid.connect_to 'articles' -Elasticsearch::Model.client = Elasticsearch::Client.new host: 'localhost:9250', log: true +Elasticsearch::Model.client = Elasticsearch::Client.new host: 'localhost:9200', log: true class Article include Mongoid::Document @@ -49,7 +49,7 @@ def as_indexed_json(options={}) # Index data # -client = Elasticsearch::Client.new host:'localhost:9250', log:true +client = Elasticsearch::Client.new host:'localhost:9200', log:true client.indices.delete index: 'articles' rescue nil client.bulk index: 'articles', From ded20356920802c35d258756113acfd95b25ade6 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 20 Jun 2015 09:13:54 +0200 Subject: [PATCH 240/582] [MODEL] Added a full example with mapping for the completion suggester Related: #429 --- .../activerecord_mapping_completion.rb | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 elasticsearch-model/examples/activerecord_mapping_completion.rb diff --git a/elasticsearch-model/examples/activerecord_mapping_completion.rb b/elasticsearch-model/examples/activerecord_mapping_completion.rb new file mode 100644 index 000000000..46d986011 --- /dev/null +++ b/elasticsearch-model/examples/activerecord_mapping_completion.rb @@ -0,0 +1,69 @@ +require 'ansi' +require 'active_record' +require 'elasticsearch/model' + +ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT) +ActiveRecord::Base.establish_connection( adapter: 'sqlite3', database: ":memory:" ) + +ActiveRecord::Schema.define(version: 1) do + create_table :articles do |t| + t.string :title + t.date :published_at + t.timestamps + end +end + +class Article < ActiveRecord::Base + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + mapping do + indexes :title + indexes :title_suggest, type: 'completion', payloads: true + end + + def as_indexed_json(options={}) + as_json.merge \ + title_suggest: { + input: title, + output: title, + payload: { url: "/articles/#{id}" } + } + end +end + +Article.__elasticsearch__.client = Elasticsearch::Client.new log: true + +# Create index + +Article.__elasticsearch__.create_index! force: true + +# Store data + +Article.delete_all +Article.create title: 'Foo' +Article.create title: 'Bar' +Article.create title: 'Foo Foo' +Article.__elasticsearch__.refresh_index! + +# Search and suggest + +response_1 = Article.search 'foo'; + +puts "Article search:".ansi(:bold), + response_1.to_a.map { |d| "Title: #{d.title}" }.inspect.ansi(:bold, :yellow) + +response_2 = Article.__elasticsearch__.client.suggest \ + index: Article.index_name, + body: { + articles: { + text: 'foo', + completion: { field: 'title_suggest', size: 25 } + } + }; + +puts "Article suggest:".ansi(:bold), + response_2['articles'].first['options'].map { |d| "#{d['text']} -> #{d['payload']['url']}" }. + inspect.ansi(:bold, :green) + +require 'pry'; binding.pry; From ca664ed13adacd4ce00214a75fd500003e2253a3 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Mon, 12 Oct 2015 13:04:41 +0200 Subject: [PATCH 241/582] Release 0.1.8 --- elasticsearch-model/CHANGELOG.md | 25 +++++++++++++++++++ .../lib/elasticsearch/model/version.rb | 2 +- elasticsearch-persistence/CHANGELOG.md | 14 ++++++++++- .../lib/elasticsearch/persistence/version.rb | 2 +- elasticsearch-rails/CHANGELOG.md | 10 +++++++- .../lib/elasticsearch/rails/version.rb | 2 +- 6 files changed, 50 insertions(+), 5 deletions(-) diff --git a/elasticsearch-model/CHANGELOG.md b/elasticsearch-model/CHANGELOG.md index 898e4b4c1..55db448ef 100644 --- a/elasticsearch-model/CHANGELOG.md +++ b/elasticsearch-model/CHANGELOG.md @@ -1,3 +1,28 @@ +## 0.1.8 + +* Added "default per page" methods for pagination with multi model searches +* Added a convenience accessor for the `aggregations` part of response +* Added a full example with mapping for the completion suggester +* Added an integration test for paginating multiple models +* Added proper support for the new "multi_fields" in the mapping DSL +* Added the `no_timeout` option for `__find_in_batches` in the Mongoid adapter +* Added, that index settings can be loaded from any object that responds to `:read` +* Added, that index settings/mappings can be loaded from a YAML or JSON file +* Added, that String pagination parameters are converted to numbers +* Added, that empty block is not required for setting mapping options +* Added, that on MyModel#import, an exception is raised if the index does not exists +* Changed the Elasticsearch port in the Mongoid example to 9200 +* Cleaned up the tests for multiple fields/properties in mapping DSL +* Fixed a bug where continuous `#save` calls emptied the `@__changed_attributes` variable +* Fixed a buggy test introduced in #335 +* Fixed incorrect deserialization of records in the Multiple adapter +* Fixed incorrect examples and documentation +* Fixed unreliable order of returned results/records in the integration test for the multiple adapter +* Fixed, that `param_name` is used when paginating with WillPaginate +* Fixed the problem where `document_type` configuration was not propagated to mapping [6 months ago by Miguel Ferna +* Refactored the code in `__find_in_batches` to use Enumerable#each_slice +* Refactored the string queries in multiple_models_test.rb to avoid quote escaping + ## 0.1.7 * Improved examples and instructions in README and code annotations diff --git a/elasticsearch-model/lib/elasticsearch/model/version.rb b/elasticsearch-model/lib/elasticsearch/model/version.rb index c8a197920..2b34a8b27 100644 --- a/elasticsearch-model/lib/elasticsearch/model/version.rb +++ b/elasticsearch-model/lib/elasticsearch/model/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Model - VERSION = "0.1.7" + VERSION = "0.1.8" end end diff --git a/elasticsearch-persistence/CHANGELOG.md b/elasticsearch-persistence/CHANGELOG.md index 17e0a08ea..cde1f6211 100644 --- a/elasticsearch-persistence/CHANGELOG.md +++ b/elasticsearch-persistence/CHANGELOG.md @@ -1,4 +1,16 @@ -# 0.1.7 +## 0.1.8 + +* Added `cluster.health wait_for_status: 'yellow'` to Repository integration test +* Fixed tests for the updates to the `update` method for Persistence::Model +* Fixed timestamp tests +* Fixed typos and broken links in documentation, fixed examples +* Fixed, that `MyModel#save` does in fact persist `updated_at` attribute +* Fixed, that `options` have not been passed to gateway in MyModel#update +* Short-circuit the operation and return `false` when the model is not valid +* Fixed the problem where `document_type` configuration was not propagated to mapping + + +## 0.1.7 * Added an integration test for the `MyModel.all` method * Improved the "music" example application diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb index f94cea570..4c103e53e 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Persistence - VERSION = "0.1.7" + VERSION = "0.1.8" end end diff --git a/elasticsearch-rails/CHANGELOG.md b/elasticsearch-rails/CHANGELOG.md index 05767473b..a32bfb7cc 100644 --- a/elasticsearch-rails/CHANGELOG.md +++ b/elasticsearch-rails/CHANGELOG.md @@ -1,4 +1,12 @@ -# 0.1.7 +## 0.1.8 + +* Added an example application template that loads settings from a file +* Added missing require in the seeds.rb file for the expert template +* Fixed double include of the aliased method (execute_without_instrumentation) +* Fixed the error when getting the search_controller_test.rb asset in `03-expert.rb` template +* Updated URLs for getting raw assets from Github in the `03-expert.rb` template + +## 0.1.7 * Updated dependencies for the gem and example applications * Fixed various small errors in the `01-basic.rb` template diff --git a/elasticsearch-rails/lib/elasticsearch/rails/version.rb b/elasticsearch-rails/lib/elasticsearch/rails/version.rb index f114090e7..5ae9014f7 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/version.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Rails - VERSION = "0.1.7" + VERSION = "0.1.8" end end From ddc472623ae7cc2e262007cdc04e79c3d075962b Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sun, 18 Oct 2015 11:41:50 +0200 Subject: [PATCH 242/582] [STORE] Added, that _source is accessible from a model instance --- .../lib/elasticsearch/persistence/model.rb | 1 + .../lib/elasticsearch/persistence/model/base.rb | 6 ++++++ elasticsearch-persistence/test/unit/model_gateway_test.rb | 6 ++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb index 8db15bd82..70b2ccb6b 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb @@ -108,6 +108,7 @@ def deserialize(document) object.instance_variable_set :@_index, document['_index'] object.instance_variable_set :@_type, document['_type'] object.instance_variable_set :@_version, document['_version'] + object.instance_variable_set :@_source, document['_source'] # Store the "hit" information (highlighting, score, ...) # diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb index 52c80f0fa..bc55c583c 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb @@ -49,6 +49,12 @@ def _version @_version end + # Return the raw document `_source` + # + def _source + @_source + end + def to_s "#<#{self.class} #{attributes.to_hash.inspect.gsub(/:(\w+)=>/, '\1: ')}>" end; alias :inspect :to_s diff --git a/elasticsearch-persistence/test/unit/model_gateway_test.rb b/elasticsearch-persistence/test/unit/model_gateway_test.rb index b47de0613..e3962ed40 100644 --- a/elasticsearch-persistence/test/unit/model_gateway_test.rb +++ b/elasticsearch-persistence/test/unit/model_gateway_test.rb @@ -89,11 +89,13 @@ def run!; DummyGatewayModel.gateway { |g| @b += 1 }; end assert DummyGatewayModel.gateway.deserialize('_id' => 'abc123', '_source' => {}).instance_variable_get(:@persisted) end + should "allow accessing the raw _source" do + assert_equal 'bar', DummyGatewayModel.gateway.deserialize('_source' => { 'foo' => 'bar' })._source['foo'] + end + should "allow to access the raw hit from results as Hashie::Mash" do assert_equal 0.42, DummyGatewayModel.gateway.deserialize('_score' => 0.42, '_source' => {}).hit._score end - - end end From 92f0148af8849dac154ee41bd1c3dd868b421b86 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sun, 8 Nov 2015 08:17:59 +0100 Subject: [PATCH 243/582] [MODEL] Changed, that TYPES_WITH_EMBEDDED_PROPERTIES is evaluated against a string version of the passed field type Related: #480 --- elasticsearch-model/lib/elasticsearch/model/indexing.rb | 2 +- elasticsearch-model/test/unit/indexing_test.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index d57c756ba..9c90e9d82 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -52,7 +52,7 @@ def indexes(name, options={}, &block) if block_given? @mapping[name][:type] ||= 'object' - properties = TYPES_WITH_EMBEDDED_PROPERTIES.include?(@mapping[name][:type]) ? :properties : :fields + properties = TYPES_WITH_EMBEDDED_PROPERTIES.include?(@mapping[name][:type].to_s) ? :properties : :fields @mapping[name][properties] ||= {} diff --git a/elasticsearch-model/test/unit/indexing_test.rb b/elasticsearch-model/test/unit/indexing_test.rb index 19a8c0dbf..a52603a1e 100644 --- a/elasticsearch-model/test/unit/indexing_test.rb +++ b/elasticsearch-model/test/unit/indexing_test.rb @@ -127,6 +127,10 @@ class NotFound < Exception; end indexes :bar end + mappings.indexes :foo_nested_as_symbol, type: :nested do + indexes :bar + end + # Object is the default when `type` is missing and there's a block passed # assert_equal 'object', mappings.to_hash[:mytype][:properties][:foo][:type] @@ -140,6 +144,10 @@ class NotFound < Exception; end assert_equal 'nested', mappings.to_hash[:mytype][:properties][:foo_nested][:type] assert_equal 'string', mappings.to_hash[:mytype][:properties][:foo_nested][:properties][:bar][:type] assert_nil mappings.to_hash[:mytype][:properties][:foo_nested][:fields] + + assert_equal :nested, mappings.to_hash[:mytype][:properties][:foo_nested_as_symbol][:type] + assert_not_nil mappings.to_hash[:mytype][:properties][:foo_nested_as_symbol][:properties] + assert_nil mappings.to_hash[:mytype][:properties][:foo_nested_as_symbol][:fields] end end From 0d5327acc198fd1baf01c8dc94567c41347fa3bd Mon Sep 17 00:00:00 2001 From: Adrien Montfort <adrien.montfort@gmail.com> Date: Sat, 12 Sep 2015 13:53:03 +0200 Subject: [PATCH 244/582] [MODEL] Fixed typos in examples Closes #468 --- elasticsearch-model/lib/elasticsearch/model/importing.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/importing.rb b/elasticsearch-model/lib/elasticsearch/model/importing.rb index 3bf96472f..7c42545d2 100644 --- a/elasticsearch-model/lib/elasticsearch/model/importing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/importing.rb @@ -84,7 +84,7 @@ module ClassMethods # # class Article # # ... - # def enrich(batch) + # def self.enrich(batch) # batch.each do |item| # item.metadata = MyAPI.get_metadata(item.id) # end @@ -92,7 +92,7 @@ module ClassMethods # end # end # - # Article.import preprocess: enrich + # Article.import preprocess: :enrich # # @example Return an array of error elements instead of the number of errors, eg. # to try importing these records again From 8f905c5d6365c079f45a65e17fcb35240e8a755e Mon Sep 17 00:00:00 2001 From: Ryan Bigg <radarlistener@fastmail.fm> Date: Tue, 18 Aug 2015 10:06:28 +1000 Subject: [PATCH 245/582] [MODEL] Fixed a typo in the README Closes #455 --- elasticsearch-model/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index b7b7b7baa..c219a4600 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -229,7 +229,7 @@ response.records.records.class # => ActiveRecord::Relation::ActiveRecord_Relation_Article ``` -The ordering of the records by score will be preserved, unless you explicitely specify a different +The ordering of the records by score will be preserved, unless you explicitly specify a different order in your model query language: ```ruby From 5f32e484a6d0458f26dc9acfa9dd5fed4e5d6453 Mon Sep 17 00:00:00 2001 From: Glauber Campinho <glauber@lovemondays.com.br> Date: Mon, 10 Aug 2015 16:15:55 -0300 Subject: [PATCH 246/582] [MODEL] Delegate `max_pages` method properly for Kaminari's `next_page` Delegate `max_pages` to `Elasticsearch::Model::Response::Results` and `Elasticsearch::Model::Response::Response`, in order to Kaminari method `next_page` work Closes #452 --- .../model/response/pagination.rb | 4 +- .../unit/response_pagination_kaminari_test.rb | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb index 80f84b7bc..c8e74b793 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb @@ -20,8 +20,8 @@ def self.included(base) Elasticsearch::Model::Response::Results.__send__ :include, ::Kaminari::PageScopeMethods Elasticsearch::Model::Response::Records.__send__ :include, ::Kaminari::PageScopeMethods - Elasticsearch::Model::Response::Results.__send__ :delegate, :limit_value, :offset_value, :total_count, to: :response - Elasticsearch::Model::Response::Records.__send__ :delegate, :limit_value, :offset_value, :total_count, to: :response + Elasticsearch::Model::Response::Results.__send__ :delegate, :limit_value, :offset_value, :total_count, :max_pages, to: :response + Elasticsearch::Model::Response::Records.__send__ :delegate, :limit_value, :offset_value, :total_count, :max_pages, to: :response base.class_eval <<-RUBY, __FILE__, __LINE__ + 1 # Define the `page` Kaminari method diff --git a/elasticsearch-model/test/unit/response_pagination_kaminari_test.rb b/elasticsearch-model/test/unit/response_pagination_kaminari_test.rb index 629db7400..1fc9b2f3c 100644 --- a/elasticsearch-model/test/unit/response_pagination_kaminari_test.rb +++ b/elasticsearch-model/test/unit/response_pagination_kaminari_test.rb @@ -194,6 +194,17 @@ def self.document_type; 'bar'; end assert_equal 5, @response.page(5).results.current_page end + + should "return previous page and next page" do + assert_equal nil, @response.page(1).results.prev_page + assert_equal 2, @response.page(1).results.next_page + + assert_equal 3, @response.page(4).results.prev_page + assert_equal nil, @response.page(4).results.next_page + + assert_equal 2, @response.page(3).results.prev_page + assert_equal 4, @response.page(3).results.next_page + end end context "records" do @@ -207,6 +218,17 @@ def self.document_type; 'bar'; end assert_equal 5, @response.page(5).records.current_page end + + should "return previous page and next page" do + assert_equal nil, @response.page(1).records.prev_page + assert_equal 2, @response.page(1).records.next_page + + assert_equal 3, @response.page(4).records.prev_page + assert_equal nil, @response.page(4).records.next_page + + assert_equal 2, @response.page(3).records.prev_page + assert_equal 4, @response.page(3).records.next_page + end end end @@ -371,6 +393,17 @@ def self.document_type; 'bar'; end assert_equal 5, @response.page(5).results.current_page end + + should "return previous page and next page" do + assert_equal nil, @response.page(1).results.prev_page + assert_equal 2, @response.page(1).results.next_page + + assert_equal 3, @response.page(4).results.prev_page + assert_equal nil, @response.page(4).results.next_page + + assert_equal 2, @response.page(3).results.prev_page + assert_equal 4, @response.page(3).results.next_page + end end context "records" do @@ -384,6 +417,17 @@ def self.document_type; 'bar'; end assert_equal 5, @response.page(5).records.current_page end + + should "return previous page and next page" do + assert_equal nil, @response.page(1).records.prev_page + assert_equal 2, @response.page(1).records.next_page + + assert_equal 3, @response.page(4).records.prev_page + assert_equal nil, @response.page(4).records.next_page + + assert_equal 2, @response.page(3).records.prev_page + assert_equal 4, @response.page(3).records.next_page + end end end end From 17016b38c0d53c98a86fe9ade940366ca94e29e5 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 16 Jan 2016 19:38:04 +0100 Subject: [PATCH 247/582] [RAILS] Added the `force` option to the 04-dsl.rb application template Without force, and with the `rails generate` example in README, searchable.rb and index.html.erb files have been skipped. --- elasticsearch-rails/lib/rails/templates/04-dsl.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-rails/lib/rails/templates/04-dsl.rb b/elasticsearch-rails/lib/rails/templates/04-dsl.rb index d5c5d7aa4..7e3609df1 100644 --- a/elasticsearch-rails/lib/rails/templates/04-dsl.rb +++ b/elasticsearch-rails/lib/rails/templates/04-dsl.rb @@ -37,11 +37,11 @@ # copy_file File.expand_path('../searchable.dsl.rb', __FILE__), 'app/models/concerns/searchable.rb', force: true get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb', - 'app/models/concerns/searchable.rb' + 'app/models/concerns/searchable.rb', force: true # copy_file File.expand_path('../index.html.dsl.erb', __FILE__), 'app/views/search/index.html.erb', force: true get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb', - 'app/views/search/index.html.erb' + 'app/views/search/index.html.erb', force: true gsub_file "test/controllers/search_controller_test.rb", %r{test "should return facets" do.*?end}m, <<-CODE test "should return aggregations" do From 77ce5000d17cc624abf9620de9b5086f9b6f6bdd Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Thu, 21 Jan 2016 22:17:32 +0100 Subject: [PATCH 248/582] [RAILS] Updated the example application to work with Elasticsearch 2.x Closes #521 --- .../lib/rails/templates/03-expert.rb | 4 ++ .../lib/rails/templates/index.html.erb | 22 +++++------ .../rails/templates/search_controller_test.rb | 33 ++++++++-------- .../lib/rails/templates/searchable.rb | 38 ++++++++----------- 4 files changed, 48 insertions(+), 49 deletions(-) diff --git a/elasticsearch-rails/lib/rails/templates/03-expert.rb b/elasticsearch-rails/lib/rails/templates/03-expert.rb index 23aaca6f7..6598f9319 100644 --- a/elasticsearch-rails/lib/rails/templates/03-expert.rb +++ b/elasticsearch-rails/lib/rails/templates/03-expert.rb @@ -189,6 +189,10 @@ class Article < ActiveRecord::Base get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/indexer.rb', 'app/workers/indexer.rb' +insert_into_file "test/test_helper.rb", + "require 'sidekiq/testing'\n\n", + before: "class ActiveSupport::TestCase\n" + git add: "Gemfile* app/workers/" git commit: "-m 'Added a Sidekiq indexer\n\nRun:\n\n $ bundle exec sidekiq --queue elasticsearch --verbose\n\nSee http://sidekiq.org'" diff --git a/elasticsearch-rails/lib/rails/templates/index.html.erb b/elasticsearch-rails/lib/rails/templates/index.html.erb index 07d9c8549..2e7ee6c67 100644 --- a/elasticsearch-rails/lib/rails/templates/index.html.erb +++ b/elasticsearch-rails/lib/rails/templates/index.html.erb @@ -68,11 +68,11 @@ <p class="panel-heading"><%= link_to 'All Sections →'.html_safe, search_path(params.except(:controller, :action).merge(c: nil))%></p> <div class="list-group"> - <% @articles.response.response['facets']['categories']['terms'].each do |c| %> + <% @articles.response.response['aggregations']['categories']['categories']['buckets'].each do |c| %> <%= - link_to search_path(params.except(:controller, :action).merge(c: c['term'])), - class: "list-group-item#{' active' if params[:c] == c['term']}" do - c['term'].titleize.html_safe + content_tag(:small, c['count'], class: 'badge').html_safe + link_to search_path(params.except(:controller, :action).merge(c: c['key'])), + class: "list-group-item#{' active' if params[:c] == c['key']}" do + c['key'].titleize.html_safe + content_tag(:small, c['doc_count'], class: 'badge').html_safe end %> <% end %> @@ -83,11 +83,11 @@ <p class="panel-heading"><%= link_to 'All Authors →'.html_safe, search_path(params.except(:controller, :action).merge(a: nil))%></p> <div class="list-group"> - <% @articles.response.response['facets']['authors']['terms'].each do |a| %> + <% @articles.response.response['aggregations']['authors']['authors']['buckets'].each do |a| %> <%= - link_to search_path(params.except(:controller, :action).merge(a: a['term'])), - class: "list-group-item#{' active' if params[:a] == a['term']}" do - a['term'].titleize.html_safe + content_tag(:small, a['count'], class: 'badge').html_safe + link_to search_path(params.except(:controller, :action).merge(a: a['key'])), + class: "list-group-item#{' active' if params[:a] == a['key']}" do + a['key'].titleize.html_safe + content_tag(:small, a['doc_count'], class: 'badge').html_safe end %> <% end %> @@ -98,16 +98,16 @@ <p class="panel-heading"><%= link_to 'Any Date →'.html_safe, search_path(params.except(:controller, :action).merge(w: nil))%></p> <div class="list-group"> - <% @articles.response.response['facets']['published']['entries'].each do |w| %> + <% @articles.response.response['aggregations']['published']['published']['buckets'].each do |w| %> <%= - __start = Time.at(w['time']/1000) + __start = Time.at(w['key']/1000) __end = __start.end_of_week __date = __start.to_date.to_s(:iso) link_to search_path(params.except(:controller, :action).merge(w: __date)), class: "list-group-item#{' active' if params[:w] == __date}" do "#{__start.to_date.to_s(:short)} — #{__end.to_date.to_s(:short)}".html_safe + \ - content_tag(:small, w['count'], class: 'badge').html_safe + content_tag(:small, w['doc_count'], class: 'badge').html_safe end %> <% end %> diff --git a/elasticsearch-rails/lib/rails/templates/search_controller_test.rb b/elasticsearch-rails/lib/rails/templates/search_controller_test.rb index 7e2248219..5f041befb 100644 --- a/elasticsearch-rails/lib/rails/templates/search_controller_test.rb +++ b/elasticsearch-rails/lib/rails/templates/search_controller_test.rb @@ -30,7 +30,7 @@ class SearchControllerTest < ActionController::TestCase Article.find_by_title('Article Three').comments.create body: 'One' - Sidekiq::Queue.new("elasticsearch").clear + Sidekiq::Worker.clear_all Article.__elasticsearch__.import force: true Article.__elasticsearch__.refresh_index! @@ -45,6 +45,7 @@ class SearchControllerTest < ActionController::TestCase test "should return search results in comments" do get :index, q: 'one', comments: 'y' assert_response :success + assert_equal 4, assigns(:articles).size end @@ -67,15 +68,15 @@ class SearchControllerTest < ActionController::TestCase get :index, q: 'one' assert_response :success - facets = assigns(:articles).response.response['facets'] + aggregations = assigns(:articles).response.response['aggregations'] - assert_equal 2, facets['categories']['terms'].size - assert_equal 2, facets['authors']['terms'].size - assert_equal 2, facets['published']['entries'].size + assert_equal 2, aggregations['categories']['categories']['buckets'].size + assert_equal 2, aggregations['authors']['authors']['buckets'].size + assert_equal 2, aggregations['published']['published']['buckets'].size - assert_equal 'One', facets['categories']['terms'][0]['term'] - assert_equal 'John Smith', facets['authors']['terms'][0]['term'] - assert_equal 1425254400000, facets['published']['entries'][0]['time'] + assert_equal 'One', aggregations['categories']['categories']['buckets'][0]['key'] + assert_equal 'John Smith', aggregations['authors']['authors']['buckets'][0]['key'] + assert_equal 1425254400000, aggregations['published']['published']['buckets'][0]['key'] end test "should sort on the published date" do @@ -104,13 +105,13 @@ class SearchControllerTest < ActionController::TestCase assert_equal 2, assigns(:articles).size - facets = assigns(:articles).response.response['facets'] + aggregations = assigns(:articles).response.response['aggregations'] - assert_equal 1, facets['authors']['terms'].size - assert_equal 1, facets['published']['entries'].size + assert_equal 1, aggregations['authors']['authors']['buckets'].size + assert_equal 1, aggregations['published']['published']['buckets'].size # Do NOT filter the category facet - assert_equal 2, facets['categories']['terms'].size + assert_equal 2, aggregations['categories']['categories']['buckets'].size end test "should filter search results and the category and published date facets when user selects a category" do @@ -119,12 +120,12 @@ class SearchControllerTest < ActionController::TestCase assert_equal 1, assigns(:articles).size - facets = assigns(:articles).response.response['facets'] + aggregations = assigns(:articles).response.response['aggregations'] - assert_equal 1, facets['categories']['terms'].size - assert_equal 1, facets['published']['entries'].size + assert_equal 1, aggregations['categories']['categories']['buckets'].size + assert_equal 1, aggregations['published']['published']['buckets'].size # Do NOT filter the authors facet - assert_equal 2, facets['authors']['terms'].size + assert_equal 2, aggregations['authors']['authors']['buckets'].size end end diff --git a/elasticsearch-rails/lib/rails/templates/searchable.rb b/elasticsearch-rails/lib/rails/templates/searchable.rb index 0ff826150..55b031532 100644 --- a/elasticsearch-rails/lib/rails/templates/searchable.rb +++ b/elasticsearch-rails/lib/rails/templates/searchable.rb @@ -71,15 +71,14 @@ def as_indexed_json(options={}) # def self.search(query, options={}) - # Prefill and set the filters (top-level `filter` and `facet_filter` elements) + # Prefill and set the filters (top-level `post_filter` and aggregation `filter` elements) # __set_filters = lambda do |key, f| + @search_definition[:post_filter][:and] ||= [] + @search_definition[:post_filter][:and] |= [f] - @search_definition[:filter][:and] ||= [] - @search_definition[:filter][:and] |= [f] - - @search_definition[:facets][key.to_sym][:facet_filter][:and] ||= [] - @search_definition[:facets][key.to_sym][:facet_filter][:and] |= [f] + @search_definition[:aggregations][key.to_sym][:filter][:bool][:must] ||= [] + @search_definition[:aggregations][key.to_sym][:filter][:bool][:must] |= [f] end @search_definition = { @@ -95,27 +94,22 @@ def self.search(query, options={}) } }, - filter: {}, + post_filter: {}, - facets: { + aggregations: { categories: { - terms: { - field: 'categories' - }, - facet_filter: {} + filter: { bool: { must: [ match_all: {} ] } }, + aggregations: { categories: { terms: { field: 'categories' } } } }, authors: { - terms: { - field: 'authors.full_name.raw' - }, - facet_filter: {} + filter: { bool: { must: [ match_all: {} ] } }, + aggregations: { authors: { terms: { field: 'authors.full_name.raw' } } } }, published: { - date_histogram: { - field: 'published_on', - interval: 'week' - }, - facet_filter: {} + filter: { bool: { must: [ match_all: {} ] } }, + aggregations: { + published: { date_histogram: { field: 'published_on', interval: 'week' } } + } } } } @@ -174,7 +168,7 @@ def self.search(query, options={}) query: { multi_match: { query: query, - fields: ['body'], + fields: ['comments.body'], operator: 'and' } } From eab4058eb559a6f03b75e50699261e8e3edd8ccb Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Thu, 21 Jan 2016 22:36:41 +0100 Subject: [PATCH 249/582] [RAILS] Added checks for proper launch order to the example application templates In order to prevent confusing errors when users try to run templates not sequentially, added checks which ensure the templates are run properly one after another. --- elasticsearch-rails/lib/rails/templates/02-pretty.rb | 5 ++++- elasticsearch-rails/lib/rails/templates/03-expert.rb | 5 ++++- elasticsearch-rails/lib/rails/templates/04-dsl.rb | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/elasticsearch-rails/lib/rails/templates/02-pretty.rb b/elasticsearch-rails/lib/rails/templates/02-pretty.rb index 544d328a0..7fd6e5048 100644 --- a/elasticsearch-rails/lib/rails/templates/02-pretty.rb +++ b/elasticsearch-rails/lib/rails/templates/02-pretty.rb @@ -1,6 +1,9 @@ # $ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/02-pretty.rb -# (See: 01-basic.rb) +unless File.read('README.rdoc').include? '== [1] Basic' + say_status "ERROR", "You have to run the 01-basic.rb template first.", :red + exit(1) +end puts say_status "README", "Updating Readme...\n", :yellow diff --git a/elasticsearch-rails/lib/rails/templates/03-expert.rb b/elasticsearch-rails/lib/rails/templates/03-expert.rb index 6598f9319..eff2f4725 100644 --- a/elasticsearch-rails/lib/rails/templates/03-expert.rb +++ b/elasticsearch-rails/lib/rails/templates/03-expert.rb @@ -1,6 +1,9 @@ # $ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/03-expert.rb -# (See: 01-basic.rb, 02-pretty.rb) +unless File.read('README.rdoc').include? '== [2] Pretty' + say_status "ERROR", "You have to run the 01-basic.rb and 02-pretty.rb templates first.", :red + exit(1) +end append_to_file 'README.rdoc', <<-README diff --git a/elasticsearch-rails/lib/rails/templates/04-dsl.rb b/elasticsearch-rails/lib/rails/templates/04-dsl.rb index 7e3609df1..232903e33 100644 --- a/elasticsearch-rails/lib/rails/templates/04-dsl.rb +++ b/elasticsearch-rails/lib/rails/templates/04-dsl.rb @@ -1,6 +1,9 @@ # $ rails new searchapp --skip --skip-bundle --template https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/04-dsl.rb -# (See: 01-basic.rb, 02-pretty.rb, 03-expert.rb) +unless File.read('README.rdoc').include? '== [3] Expert' + say_status "ERROR", "You have to run the 01-basic.rb, 02-pretty.rb and 03-expert.rb templates first.", :red + exit(1) +end append_to_file 'README.rdoc', <<-README From e9052ae077beb13296475bb0d29d8326f2c05730 Mon Sep 17 00:00:00 2001 From: nipe <nipe0324@gmail.com> Date: Sun, 25 Oct 2015 17:56:41 +0900 Subject: [PATCH 250/582] [MODEL] Added a `suggest` method to wrap the suggestions in response Instead of: @articles.response.response['suggest'] let's have: @articles.response.suggest Closes #483 --- .../lib/elasticsearch/model/response.rb | 6 ++++++ .../integration/active_record_basic_test.rb | 20 +++++++++++++++---- .../test/unit/response_test.rb | 12 ++++++++++- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response.rb b/elasticsearch-model/lib/elasticsearch/model/response.rb index efece7143..ec043b8b6 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response.rb @@ -71,6 +71,12 @@ def shards def aggregations response['aggregations'] ? Hashie::Mash.new(response['aggregations']) : nil end + + # Returns a Hashie::Mash of the suggest + # + def suggest + response['suggest'] ? Hashie::Mash.new(response['suggest']) : nil + end end end end diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index 6fd5467df..4ad62cff7 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -22,11 +22,19 @@ class ::Article < ActiveRecord::Base settings index: { number_of_shards: 1, number_of_replicas: 0 } do mapping do - indexes :title, type: 'string', analyzer: 'snowball' - indexes :body, type: 'string' - indexes :created_at, type: 'date' + indexes :title, type: 'string', analyzer: 'snowball' + indexes :suggest_title, type: 'completion' + indexes :body, type: 'string' + indexes :created_at, type: 'date' end end + + def as_indexed_json(options = {}) + attributes + .symbolize_keys + .slice(:title, :body, :created_at) + .merge(suggest_title: title) + end end Article.delete_all @@ -210,10 +218,14 @@ class ::Article < ActiveRecord::Base should "allow dot access to response" do response = Article.search query: { match: { title: { query: 'test' } } }, - aggregations: { dates: { date_histogram: { field: 'created_at', interval: 'hour' } } } + aggregations: { dates: { date_histogram: { field: 'created_at', interval: 'hour' } } }, + suggest: { title_suggest: { text: 'test', completion: { field: 'suggest_title' } } } response.response.respond_to?(:aggregations) assert_equal 2, response.response.aggregations.dates.buckets.first.doc_count + + response.response.respond_to?(:suggest) + assert_equal 2, response.response.suggest.title_suggest.first.options.size end end diff --git a/elasticsearch-model/test/unit/response_test.rb b/elasticsearch-model/test/unit/response_test.rb index 0bd877af0..8e04bb886 100644 --- a/elasticsearch-model/test/unit/response_test.rb +++ b/elasticsearch-model/test/unit/response_test.rb @@ -8,7 +8,8 @@ def self.document_type; 'bar'; end end RESPONSE = { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, 'hits' => { 'hits' => [] }, - 'aggregations' => {'foo' => {'bar' => 10}}} + 'aggregations' => {'foo' => {'bar' => 10}}, + 'suggest' => {'my_suggest' => []}} setup do @search = Elasticsearch::Model::Searching::SearchRequest.new OriginClass, '*' @@ -73,5 +74,14 @@ def self.document_type; 'bar'; end assert_kind_of Hashie::Mash, response.aggregations.foo assert_equal 10, response.aggregations.foo.bar end + + should "access the suggest" do + @search.expects(:execute!).returns(RESPONSE) + + response = Elasticsearch::Model::Response::Response.new OriginClass, @search + assert_respond_to response, :suggest + assert_kind_of Hashie::Mash, response.suggest + assert_equal [], response.suggest.my_suggest + end end end From 65d64c1aabb3b00d39c2d582ec4f2e11efbeca22 Mon Sep 17 00:00:00 2001 From: nipe <nipe0324@gmail.com> Date: Sun, 25 Oct 2015 20:40:11 +0900 Subject: [PATCH 251/582] [RAILS] Used the `suggest` method instead of `response['suggest']` in the application template Related: #483 --- elasticsearch-rails/lib/rails/templates/index.html.dsl.erb | 2 +- elasticsearch-rails/lib/rails/templates/index.html.erb | 2 +- .../lib/rails/templates/search_controller_test.dsl.rb | 2 +- .../lib/rails/templates/search_controller_test.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb b/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb index 2e7ee6c67..ea9a76e68 100644 --- a/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb +++ b/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb @@ -47,7 +47,7 @@ <hr> </div> -<% if @articles.size < 1 && (suggestions = @articles.response.response['suggest']) && suggestions.present? %> +<% if @articles.size < 1 && @articles.response.suggest.present? %> <div class="col-md-12"> <p class="alert alert-warning"> No documents have been found. diff --git a/elasticsearch-rails/lib/rails/templates/index.html.erb b/elasticsearch-rails/lib/rails/templates/index.html.erb index 2e7ee6c67..ea9a76e68 100644 --- a/elasticsearch-rails/lib/rails/templates/index.html.erb +++ b/elasticsearch-rails/lib/rails/templates/index.html.erb @@ -47,7 +47,7 @@ <hr> </div> -<% if @articles.size < 1 && (suggestions = @articles.response.response['suggest']) && suggestions.present? %> +<% if @articles.size < 1 && @articles.response.suggest.present? %> <div class="col-md-12"> <p class="alert alert-warning"> No documents have been found. diff --git a/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb b/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb index aa2fae65a..7fe7dd955 100644 --- a/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb +++ b/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb @@ -58,7 +58,7 @@ class SearchControllerTest < ActionController::TestCase get :index, q: 'one' assert_response :success - suggestions = assigns(:articles).response.response['suggest'] + suggestions = assigns(:articles).response.suggest assert_equal 'one', suggestions['suggest_title'][0]['text'] end diff --git a/elasticsearch-rails/lib/rails/templates/search_controller_test.rb b/elasticsearch-rails/lib/rails/templates/search_controller_test.rb index 5f041befb..472d35d75 100644 --- a/elasticsearch-rails/lib/rails/templates/search_controller_test.rb +++ b/elasticsearch-rails/lib/rails/templates/search_controller_test.rb @@ -59,7 +59,7 @@ class SearchControllerTest < ActionController::TestCase get :index, q: 'one' assert_response :success - suggestions = assigns(:articles).response.response['suggest'] + suggestions = assigns(:articles).response.suggest assert_equal 'one', suggestions['suggest_title'][0]['text'] end From 23d1ab708a473f7ee1ce89fb231a47795c11a7af Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 22 Jan 2016 09:43:33 +0100 Subject: [PATCH 252/582] [RAILS] Fixed incorrect suggestions handling in the view Fixes 65d64c1 Related #483 --- elasticsearch-rails/lib/rails/templates/index.html.erb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/elasticsearch-rails/lib/rails/templates/index.html.erb b/elasticsearch-rails/lib/rails/templates/index.html.erb index ea9a76e68..9d849ef04 100644 --- a/elasticsearch-rails/lib/rails/templates/index.html.erb +++ b/elasticsearch-rails/lib/rails/templates/index.html.erb @@ -47,13 +47,13 @@ <hr> </div> -<% if @articles.size < 1 && @articles.response.suggest.present? %> +<% if @articles.size < 1 && @articles.response.suggest.present? %> <div class="col-md-12"> <p class="alert alert-warning"> No documents have been found. - <% if suggestions['suggest_title'].present? || suggestions['suggest_body'].present? %> + <% if @articles.response.suggest['suggest_title'].present? || @articles.response.suggest['suggest_body'].present? %> Maybe you mean - <%= suggestions.map { |k,v| v.first['options'] }.flatten.map {|v| v['text']}.uniq.map do |term| + <%= @articles.response.suggest.map { |k,v| v.first['options'] }.flatten.map {|v| v['text']}.uniq.map do |term| link_to term, search_path(params.except(:controller, :action).merge q: term) end.to_sentence(last_word_connector: ' or ').html_safe %>? <% end %> From 4556f14108d689f675ec8989083ee8548db730b3 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 22 Jan 2016 10:56:13 +0100 Subject: [PATCH 253/582] [MODEL] Added the "sqlite3" gem into the Gemfiles --- elasticsearch-model/gemfiles/3.0.gemfile | 1 + elasticsearch-model/gemfiles/4.0.gemfile | 1 + 2 files changed, 2 insertions(+) diff --git a/elasticsearch-model/gemfiles/3.0.gemfile b/elasticsearch-model/gemfiles/3.0.gemfile index e2bc44b02..23cbdf53d 100644 --- a/elasticsearch-model/gemfiles/3.0.gemfile +++ b/elasticsearch-model/gemfiles/3.0.gemfile @@ -10,3 +10,4 @@ gemspec path: '../' gem 'activemodel', '>= 3.0' gem 'activerecord', '~> 3.2' gem 'mongoid', '>= 3.0' +gem 'sqlite3' diff --git a/elasticsearch-model/gemfiles/4.0.gemfile b/elasticsearch-model/gemfiles/4.0.gemfile index 069520d23..89044bb19 100644 --- a/elasticsearch-model/gemfiles/4.0.gemfile +++ b/elasticsearch-model/gemfiles/4.0.gemfile @@ -9,3 +9,4 @@ gemspec path: '../' gem 'activemodel', '~> 4' gem 'activerecord', '~> 4' +gem 'sqlite3' From c7a7ad97579e84aa237ef8636a97616720f05dcd Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 22 Jan 2016 14:58:50 +0100 Subject: [PATCH 254/582] [MODEL] Refactored the suggestions output wrapper Refactored the accessor for the `suggest` part of the response introduced in #483 to allow a richer interface. Added a `terms` helper method which just returns a flat array of response.suggestions.terms # => [ 'Foo', 'Bar' ] Related: https://github.com/rubygems/rubygems.org/pull/1135 --- .../lib/elasticsearch/model.rb | 1 + .../lib/elasticsearch/model/response.rb | 6 ++--- .../model/response/suggestions.rb | 13 ++++++++++ .../integration/active_record_basic_test.rb | 8 +++--- .../test/unit/response_test.rb | 25 ++++++++++++++++--- 5 files changed, 42 insertions(+), 11 deletions(-) create mode 100644 elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index ce05d215d..9d2b93da5 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -31,6 +31,7 @@ require 'elasticsearch/model/response/results' require 'elasticsearch/model/response/records' require 'elasticsearch/model/response/pagination' +require 'elasticsearch/model/response/suggestions' require 'elasticsearch/model/ext/active_record' diff --git a/elasticsearch-model/lib/elasticsearch/model/response.rb b/elasticsearch-model/lib/elasticsearch/model/response.rb index ec043b8b6..e85a1fd9f 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response.rb @@ -72,10 +72,10 @@ def aggregations response['aggregations'] ? Hashie::Mash.new(response['aggregations']) : nil end - # Returns a Hashie::Mash of the suggest + # Returns a Hashie::Mash of the suggestions # - def suggest - response['suggest'] ? Hashie::Mash.new(response['suggest']) : nil + def suggestions + Suggestions.new(response['suggest']) end end end diff --git a/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb b/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb new file mode 100644 index 000000000..5088767ce --- /dev/null +++ b/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb @@ -0,0 +1,13 @@ +module Elasticsearch + module Model + module Response + + class Suggestions < Hashie::Mash + def terms + self.to_a.map { |k,v| v.first['options'] }.flatten.map {|v| v['text']}.uniq + end + end + + end + end +end diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index 4ad62cff7..e6ca97d6d 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -23,7 +23,6 @@ class ::Article < ActiveRecord::Base settings index: { number_of_shards: 1, number_of_replicas: 0 } do mapping do indexes :title, type: 'string', analyzer: 'snowball' - indexes :suggest_title, type: 'completion' indexes :body, type: 'string' indexes :created_at, type: 'date' end @@ -219,13 +218,14 @@ def as_indexed_json(options = {}) should "allow dot access to response" do response = Article.search query: { match: { title: { query: 'test' } } }, aggregations: { dates: { date_histogram: { field: 'created_at', interval: 'hour' } } }, - suggest: { title_suggest: { text: 'test', completion: { field: 'suggest_title' } } } + suggest: { text: 'tezt', title: { term: { field: 'title', suggest_mode: 'always' } } } response.response.respond_to?(:aggregations) - assert_equal 2, response.response.aggregations.dates.buckets.first.doc_count + assert_equal 2, response.aggregations.dates.buckets.first.doc_count response.response.respond_to?(:suggest) - assert_equal 2, response.response.suggest.title_suggest.first.options.size + assert_equal 1, response.suggestions.title.first.options.size + assert_equal ['test'], response.suggestions.terms end end diff --git a/elasticsearch-model/test/unit/response_test.rb b/elasticsearch-model/test/unit/response_test.rb index 8e04bb886..71cfb2d6d 100644 --- a/elasticsearch-model/test/unit/response_test.rb +++ b/elasticsearch-model/test/unit/response_test.rb @@ -9,7 +9,7 @@ def self.document_type; 'bar'; end RESPONSE = { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, 'hits' => { 'hits' => [] }, 'aggregations' => {'foo' => {'bar' => 10}}, - 'suggest' => {'my_suggest' => []}} + 'suggest' => {'my_suggest' => [ { 'text' => 'foo', 'options' => [ { 'text' => 'Foo', 'score' => 2.0 }, { 'text' => 'Bar', 'score' => 1.0 } ] } ]}} setup do @search = Elasticsearch::Model::Searching::SearchRequest.new OriginClass, '*' @@ -79,9 +79,26 @@ def self.document_type; 'bar'; end @search.expects(:execute!).returns(RESPONSE) response = Elasticsearch::Model::Response::Response.new OriginClass, @search - assert_respond_to response, :suggest - assert_kind_of Hashie::Mash, response.suggest - assert_equal [], response.suggest.my_suggest + + assert_respond_to response, :suggestions + assert_kind_of Hashie::Mash, response.suggestions + assert_equal 'Foo', response.suggestions.my_suggest.first.options.first.text + end + + should "return array of terms from the suggestions" do + @search.expects(:execute!).returns(RESPONSE) + response = Elasticsearch::Model::Response::Response.new OriginClass, @search + + assert_not_empty response.suggestions + assert_equal [ 'Foo', 'Bar' ], response.suggestions.terms + end + + should "return empty array as suggest terms when there are no suggestions" do + @search.expects(:execute!).returns({}) + response = Elasticsearch::Model::Response::Response.new OriginClass, @search + + assert_empty response.suggestions + assert_equal [], response.suggestions.terms end end end From e191fa70c0bfc691d354c926eb92a2ebadb3cc9e Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 22 Jan 2016 15:07:42 +0100 Subject: [PATCH 255/582] [RAILS] Changed the view in the example application to use the helper method for suggestions Simplify the view code and use the `terms` helper. --- elasticsearch-rails/lib/rails/templates/index.html.dsl.erb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb b/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb index ea9a76e68..4f8d512b0 100644 --- a/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb +++ b/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb @@ -47,13 +47,13 @@ <hr> </div> -<% if @articles.size < 1 && @articles.response.suggest.present? %> +<% if @articles.size < 1 && @articles.response.suggestions.present? %> <div class="col-md-12"> <p class="alert alert-warning"> No documents have been found. - <% if suggestions['suggest_title'].present? || suggestions['suggest_body'].present? %> + <% if @articles.response.suggestions.terms.present? %> Maybe you mean - <%= suggestions.map { |k,v| v.first['options'] }.flatten.map {|v| v['text']}.uniq.map do |term| + <%= @articles.response.suggestions.terms.map do |term| link_to term, search_path(params.except(:controller, :action).merge q: term) end.to_sentence(last_word_connector: ' or ').html_safe %>? <% end %> From 164a2470831cc45e40fa9c2c8bc1d3724b3fdfac Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 22 Jan 2016 16:26:12 +0100 Subject: [PATCH 256/582] [RAILS] Fixed missing `git add` in the 03-expert.rb application template --- elasticsearch-rails/lib/rails/templates/03-expert.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-rails/lib/rails/templates/03-expert.rb b/elasticsearch-rails/lib/rails/templates/03-expert.rb index eff2f4725..267ddf2ed 100644 --- a/elasticsearch-rails/lib/rails/templates/03-expert.rb +++ b/elasticsearch-rails/lib/rails/templates/03-expert.rb @@ -196,7 +196,7 @@ class Article < ActiveRecord::Base "require 'sidekiq/testing'\n\n", before: "class ActiveSupport::TestCase\n" -git add: "Gemfile* app/workers/" +git add: "Gemfile* app/workers/ test/test_helper.rb" git commit: "-m 'Added a Sidekiq indexer\n\nRun:\n\n $ bundle exec sidekiq --queue elasticsearch --verbose\n\nSee http://sidekiq.org'" # ----- Add SearchController ----------------------------------------------------------------------- From eb48291685174be79262a9d8d804f52bde8e0862 Mon Sep 17 00:00:00 2001 From: Joe Ferris <jferris@thoughtbot.com> Date: Tue, 5 Jan 2016 17:40:18 -0500 Subject: [PATCH 257/582] [MODEL] Fixed `#dup` behaviour for Elasticsearch::Model Previously, calling #dup for an Elasticsearch::Model instance would retain the original __elasticsearch__ reference. Given the following example: user = User.create!(name: "Will") other = user.dup other.update!(name: "Bill") You'd end up with two references to "Will" in Elasticsearch, and none for "Bill," because the duplicate instance proxied to the original instance's attributes. With this fix, each duplicate gets its own proxy, so attributes are saved correctly. Closes #517 --- elasticsearch-model/lib/elasticsearch/model/proxy.rb | 9 +++++++++ elasticsearch-model/test/unit/proxy_test.rb | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/elasticsearch-model/lib/elasticsearch/model/proxy.rb b/elasticsearch-model/lib/elasticsearch/model/proxy.rb index 64dd4da1f..3e37f28ec 100644 --- a/elasticsearch-model/lib/elasticsearch/model/proxy.rb +++ b/elasticsearch-model/lib/elasticsearch/model/proxy.rb @@ -66,6 +66,15 @@ def __elasticsearch__ &block end end + # @overload dup + # + # Returns a copy of this object. Resets the __elasticsearch__ proxy so + # the duplicate will build its own proxy. + def initialize_dup(_) + @__elasticsearch__ = nil + super + end + # Common module for the proxy classes # module Base diff --git a/elasticsearch-model/test/unit/proxy_test.rb b/elasticsearch-model/test/unit/proxy_test.rb index 6cd745ce6..d7299f884 100644 --- a/elasticsearch-model/test/unit/proxy_test.rb +++ b/elasticsearch-model/test/unit/proxy_test.rb @@ -69,6 +69,17 @@ def changes assert_equal 'insta barr', DummyProxyModel.new.__elasticsearch__.bar end + should "reset the proxy target for duplicates" do + model = DummyProxyModel.new + model_target = model.__elasticsearch__.target + duplicate = model.dup + duplicate_target = duplicate.__elasticsearch__.target + + assert_not_equal model, duplicate + assert_equal model, model_target + assert_equal duplicate, duplicate_target + end + should "return the proxy class from instance proxy" do assert_equal Elasticsearch::Model::Proxy::ClassMethodsProxy, DummyProxyModel.new.__elasticsearch__.class.class end From 489ab24998a87a0323c3085e29aad14367bfaf89 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 22 Jan 2016 19:40:39 +0100 Subject: [PATCH 258/582] [RAILS] Added checks for Redis availability to the 03-expert.rb template Related: #521 --- .../lib/rails/templates/03-expert.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/elasticsearch-rails/lib/rails/templates/03-expert.rb b/elasticsearch-rails/lib/rails/templates/03-expert.rb index 267ddf2ed..ec098a420 100644 --- a/elasticsearch-rails/lib/rails/templates/03-expert.rb +++ b/elasticsearch-rails/lib/rails/templates/03-expert.rb @@ -5,6 +5,22 @@ exit(1) end +begin + require 'redis' +rescue LoadError + say_status "ERROR", "Please install the 'redis' gem before running this template", :red + exit(1) +end + +begin + Redis.new.info +rescue Redis::CannotConnectError + puts + say_status "ERROR", "Redis not available", :red + say_status "", "This template uses an asynchronous indexer via Sidekiq, and requires a running Redis server." + exit(1) +end + append_to_file 'README.rdoc', <<-README == [3] Expert From 35287a5d9a60c9740df5e6f9c353e4a8251997c1 Mon Sep 17 00:00:00 2001 From: Kenta Suzuki <kenta.suzuki@crowdworks.co.jp> Date: Thu, 25 Feb 2016 23:25:49 +0900 Subject: [PATCH 259/582] [CI] Update Bundler to 1.11.2 on Travis Latest builds on Travis are broken because of bundler is too old and contains some issues See: bundler/bundler#3558, travis-ci/travis-ci#3531 Related: #538 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3bdba79ff..72704bbaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ before_script: script: - SERVER=launch TEST_CLUSTER_COMMAND=/usr/share/elasticsearch/bin/elasticsearch TEST_CLUSTER_PARAMS='-Des.default.path.conf=/etc/elasticsearch/ -Des.default.path.logs==/var/log/elasticsearch/' bundle exec rake test:all -install: "gem uninstall bundler -axI && gem install bundler -v 1.6.2" +install: "gem uninstall bundler -axI && gem install bundler -v 1.11.2" notifications: disable: true From ee824a5e055c18c90128fe188f86d3352217783e Mon Sep 17 00:00:00 2001 From: Kenta Suzuki <kenta.suzuki@crowdworks.co.jp> Date: Fri, 26 Feb 2016 00:03:12 +0900 Subject: [PATCH 260/582] [CI] Enable running on the new Travis' container-based infrastructure * Disable sudo (https://docs.travis-ci.com/user/workers/container-based-infrastructure/) * Fix buffer overflow Buffer overflow in Java_java_net_Inet4AddressImpl_getLocalHostName of OpenJDK6 and OpenJDK 7 (Refs: travis-ci/travis-ci#5227). Travis container-based infrastructure does not suffer from the long hostnames. Closes: #538 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 72704bbaa..9c148e5db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,3 +33,5 @@ install: "gem uninstall bundler -axI && gem install bundler -v 1.11.2" notifications: disable: true + +sudo: false From 88bb6e850666fef8734e54ff1c4674538ffb4b44 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Thu, 10 Mar 2016 13:44:51 +0100 Subject: [PATCH 261/582] [CI] Move the Bundler install command on Travis to the `before_install` section Related: #538 --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9c148e5db..d69951ae2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,11 @@ jdk: services: - mongodb +before_install: + - gem update --system + - gem --version + - gem install bundler -v 1.11.2 + before_script: - ls -la /usr/share/elasticsearch/bin/elasticsearch - echo $PWD @@ -29,8 +34,6 @@ before_script: script: - SERVER=launch TEST_CLUSTER_COMMAND=/usr/share/elasticsearch/bin/elasticsearch TEST_CLUSTER_PARAMS='-Des.default.path.conf=/etc/elasticsearch/ -Des.default.path.logs==/var/log/elasticsearch/' bundle exec rake test:all -install: "gem uninstall bundler -axI && gem install bundler -v 1.11.2" - notifications: disable: true From c9cff49f84f5e813a03acedf5c7bf54109772737 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Thu, 10 Mar 2016 13:48:34 +0100 Subject: [PATCH 262/582] [CI] Added, that Elasticsearch version is printed in `before_script` --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d69951ae2..39d626bdb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,7 @@ before_install: before_script: - ls -la /usr/share/elasticsearch/bin/elasticsearch - - echo $PWD + - elasticsearch -v - rake bundle:clean - rake bundle:install From 599b2952969876dd69777bd70bb40c7112ff3975 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Thu, 10 Mar 2016 14:05:36 +0100 Subject: [PATCH 263/582] [CI] Pinned Rake to version 10.x to prevent clashes with the Yard gem The builds have been failing on Travis with: cd /home/travis/build/elastic/elasticsearch-rails/elasticsearch-model && unset BUNDLE_GEMFILE && bundle exec rake test:unit rm -rf tmp/reports rake aborted! NoMethodError: undefined method `last_comment' for #<Rake::Application:0x00000000a9c478> /home/travis/.rvm/gems/ruby-1.9.3-p551/gems/yard-0.8.7.6/lib/yard/rake/yardoc_task.rb:69:in `define' Related: #538 --- Gemfile | 2 +- elasticsearch-model/elasticsearch-model.gemspec | 2 +- elasticsearch-persistence/elasticsearch-persistence.gemspec | 2 +- elasticsearch-rails/elasticsearch-rails.gemspec | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index b47e577b9..9bd388126 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source 'https://rubygems.org' gem "bundler", "~> 1" -gem "rake" +gem "rake", "< 11.0" gem 'elasticsearch' gem 'elasticsearch-extensions' diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index c943c4002..df9509f06 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -28,7 +28,7 @@ Gem::Specification.new do |s| s.add_dependency "hashie" s.add_development_dependency "bundler", "~> 1.3" - s.add_development_dependency "rake" + s.add_development_dependency "rake", "< 11.0" s.add_development_dependency "elasticsearch-extensions" diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index c7a3b44e4..d71bfcf93 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -31,7 +31,7 @@ Gem::Specification.new do |s| s.add_dependency "virtus" s.add_development_dependency "bundler", "~> 1.5" - s.add_development_dependency "rake" + s.add_development_dependency "rake", "< 11.0" s.add_development_dependency "oj" diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index 5901601a3..14dee1ddf 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -24,7 +24,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 1.9.3" s.add_development_dependency "bundler", "~> 1.3" - s.add_development_dependency "rake" + s.add_development_dependency "rake", "< 11.0" s.add_development_dependency "elasticsearch-extensions" s.add_development_dependency "elasticsearch-model" From a714d5931182501531d2ce6b767bcb3c97ce56a7 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" <tobias.maier@baucloud.com> Date: Sat, 5 Mar 2016 22:21:22 +0100 Subject: [PATCH 264/582] [RAILS] Copy files from Github rather than locally in 05-settings-files.rb Closes #541 --- elasticsearch-rails/lib/rails/templates/05-settings-files.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/elasticsearch-rails/lib/rails/templates/05-settings-files.rb b/elasticsearch-rails/lib/rails/templates/05-settings-files.rb index 3ce868b6e..9717699fa 100644 --- a/elasticsearch-rails/lib/rails/templates/05-settings-files.rb +++ b/elasticsearch-rails/lib/rails/templates/05-settings-files.rb @@ -25,7 +25,9 @@ # ----- Copy the articles_settings.json file ------------------------------------------------------- -copy_file File.expand_path('../articles_settings.json', __FILE__), 'config/elasticsearch/articles_settings.json' +# copy_file File.expand_path('../articles_settings.json', __FILE__), 'config/elasticsearch/articles_settings.json' +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/articles_settings.json', + 'config/elasticsearch/articles_settings.json', force: true git add: "config/elasticsearch/articles_settings.json" git commit: "-m 'Create the articles settings file'" From 15be8e86299cb888d459b093ead4c64ff7ecba19 Mon Sep 17 00:00:00 2001 From: David Padilla <david@easybroker.com> Date: Tue, 22 Sep 2015 15:31:33 -0500 Subject: [PATCH 265/582] [MODEL] Added the `:includes` option to Adapter::ActiveRecord::Records This patch adds the `:includes` option to ActiveRecord adapter which can be used to eager load submodels just like you'd do with regular ActiveRecord. This helps to eliminate the N+1 problem when using elasticsearch-model searches. Example: class Article < ActiveRecord::Base belongs_to :author # ... end Article.search(...).records(includes: [:author]) # Would be similar to using: Article.includes(:author).where(...) Closes #472 --- .../lib/elasticsearch/model/adapters/active_record.rb | 7 +++++++ .../lib/elasticsearch/model/response.rb | 4 ++-- .../lib/elasticsearch/model/response/records.rb | 3 +++ .../test/unit/adapter_active_record_test.rb | 10 ++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb index b0f61bc8a..2d9bb5378 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb @@ -10,10 +10,17 @@ module ActiveRecord lambda { |klass| !!defined?(::ActiveRecord::Base) && klass.respond_to?(:ancestors) && klass.ancestors.include?(::ActiveRecord::Base) } module Records + attr_writer :options + + def options + @options ||= {} + end + # Returns an `ActiveRecord::Relation` instance # def records sql_records = klass.where(klass.primary_key => ids) + sql_records = sql_records.includes(self.options[:includes]) if self.options[:includes] # Re-order records based on the order from Elasticsearch hits # by redefining `to_a`, unless the user has called `order()` diff --git a/elasticsearch-model/lib/elasticsearch/model/response.rb b/elasticsearch-model/lib/elasticsearch/model/response.rb index e85a1fd9f..fad3828b3 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response.rb @@ -44,8 +44,8 @@ def results # # @return [Records] # - def records - @records ||= Records.new(klass, self) + def records(options = {}) + @records ||= Records.new(klass, self, options) end # Returns the "took" time diff --git a/elasticsearch-model/lib/elasticsearch/model/response/records.rb b/elasticsearch-model/lib/elasticsearch/model/response/records.rb index cd936559b..4638ca689 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/records.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/records.rb @@ -12,6 +12,8 @@ class Records delegate :each, :empty?, :size, :slice, :[], :to_a, :to_ary, to: :records + attr_accessor :options + include Base # @see Base#initialize @@ -25,6 +27,7 @@ def initialize(klass, response, options={}) metaclass = class << self; self; end metaclass.__send__ :include, adapter.records_mixin + self.options = options self end diff --git a/elasticsearch-model/test/unit/adapter_active_record_test.rb b/elasticsearch-model/test/unit/adapter_active_record_test.rb index 19fa238f4..335e3bd10 100644 --- a/elasticsearch-model/test/unit/adapter_active_record_test.rb +++ b/elasticsearch-model/test/unit/adapter_active_record_test.rb @@ -51,6 +51,16 @@ def ids instance.load end + should "load the records with its submodels when using :includes" do + klass = mock('class', primary_key: :some_key, where: @records) + @records.expects(:includes).with([:submodel]).at_least_once + + instance = DummyClassForActiveRecord.new + instance.expects(:klass).returns(klass).at_least_once + instance.options[:includes] = [:submodel] + instance.records + end + should "reorder the records based on hits order" do @records.instance_variable_set(:@records, @records) From 39c1456c4138c8681e54332f907dddf312e1d8dc Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Wed, 4 May 2016 17:25:17 +0200 Subject: [PATCH 266/582] [MODEL] Added an integration test for the eager loading in 22a4c1c Related: #472 --- .../active_record_associations_test.rb | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/elasticsearch-model/test/integration/active_record_associations_test.rb b/elasticsearch-model/test/integration/active_record_associations_test.rb index 984d4fe37..af67ad889 100644 --- a/elasticsearch-model/test/integration/active_record_associations_test.rb +++ b/elasticsearch-model/test/integration/active_record_associations_test.rb @@ -133,11 +133,16 @@ def as_indexed_json(options={}) # Include the search integration # Post.__send__ :include, Searchable + Comment.__send__ :include, Elasticsearch::Model + Comment.__send__ :include, Elasticsearch::Model::Callbacks - # ----- Reset the index ----------------------------------------------------------------- + # ----- Reset the indices ----------------------------------------------------------------- Post.delete_all Post.__elasticsearch__.create_index! force: true + + Comment.delete_all + Comment.__elasticsearch__.create_index! force: true end should "index and find a document" do @@ -300,6 +305,20 @@ def as_indexed_json(options={}) assert_equal 0, Post.search('categories:One').size assert_equal 1, Post.search('categories:Updated').size end + + should "eagerly load associated records" do + post_1 = Post.create(title: 'One') + post_2 = Post.create(title: 'Two') + post_1.comments.create text: 'First comment' + post_1.comments.create text: 'Second comment' + + Comment.__elasticsearch__.refresh_index! + + records = Comment.search('first').records(includes: :post) + + assert records.first.association(:post).loaded?, "The associated Post should be eagerly loaded" + assert_equal 'One', records.first.post.title + end end end From 138781992a8a945bd716a83718fc74d54229baa6 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Thu, 5 May 2016 17:00:59 +0200 Subject: [PATCH 267/582] Release 0.1.9 --- elasticsearch-model/CHANGELOG.md | 8 ++++++++ elasticsearch-model/lib/elasticsearch/model/version.rb | 2 +- elasticsearch-persistence/CHANGELOG.md | 4 ++++ .../lib/elasticsearch/persistence/version.rb | 2 +- elasticsearch-rails/CHANGELOG.md | 6 ++++++ elasticsearch-rails/lib/elasticsearch/rails/version.rb | 2 +- 6 files changed, 21 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/CHANGELOG.md b/elasticsearch-model/CHANGELOG.md index 55db448ef..1b2803383 100644 --- a/elasticsearch-model/CHANGELOG.md +++ b/elasticsearch-model/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.1.9 + +* Added a `suggest` method to wrap the suggestions in response +* Added the `:includes` option to Adapter::ActiveRecord::Records for eagerly loading associated models +* Delegated `max_pages` method properly for Kaminari's `next_page` +* Fixed `#dup` behaviour for Elasticsearch::Model +* Fixed typos in the README and examples + ## 0.1.8 * Added "default per page" methods for pagination with multi model searches diff --git a/elasticsearch-model/lib/elasticsearch/model/version.rb b/elasticsearch-model/lib/elasticsearch/model/version.rb index 2b34a8b27..44cfdabea 100644 --- a/elasticsearch-model/lib/elasticsearch/model/version.rb +++ b/elasticsearch-model/lib/elasticsearch/model/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Model - VERSION = "0.1.8" + VERSION = "0.1.9" end end diff --git a/elasticsearch-persistence/CHANGELOG.md b/elasticsearch-persistence/CHANGELOG.md index cde1f6211..7d25720bb 100644 --- a/elasticsearch-persistence/CHANGELOG.md +++ b/elasticsearch-persistence/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.9 + +* Added, that raw `_source` is accessible from a model instance + ## 0.1.8 * Added `cluster.health wait_for_status: 'yellow'` to Repository integration test diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb index 4c103e53e..93852c314 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Persistence - VERSION = "0.1.8" + VERSION = "0.1.9" end end diff --git a/elasticsearch-rails/CHANGELOG.md b/elasticsearch-rails/CHANGELOG.md index a32bfb7cc..5bb4e13bd 100644 --- a/elasticsearch-rails/CHANGELOG.md +++ b/elasticsearch-rails/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.1.9 + +* Added checks for proper launch order and other updates to the example application templates +* Updated the example application to work with Elasticsearch 2.x +* Used the `suggest` method instead of `response['suggest']` in the application template + ## 0.1.8 * Added an example application template that loads settings from a file diff --git a/elasticsearch-rails/lib/elasticsearch/rails/version.rb b/elasticsearch-rails/lib/elasticsearch/rails/version.rb index 5ae9014f7..88b2dd758 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/version.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Rails - VERSION = "0.1.8" + VERSION = "0.1.9" end end From 31d04c7b482b0008a4bdb4835dd06939e63efbd8 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Tue, 10 May 2016 10:29:49 +0200 Subject: [PATCH 268/582] Updated the Rake dependency to 11.1 In 599b295, the Rake dependency has been pinned to < 11, but that prevents installing of gems depending on later versions. The offending `last_comment` method was restored in Rake 11.1, so it is safe to pin that version (relaxed). Furthermore, a patch to Yard is coming up which fixes the original problem there. Thanks for reporting and all the pointers, @dominicsayers! Closes #566 Related: lsegal/yard#964 --- Gemfile | 2 +- elasticsearch-model/elasticsearch-model.gemspec | 2 +- elasticsearch-persistence/elasticsearch-persistence.gemspec | 2 +- elasticsearch-rails/elasticsearch-rails.gemspec | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 9bd388126..13be68981 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source 'https://rubygems.org' gem "bundler", "~> 1" -gem "rake", "< 11.0" +gem "rake", "~> 11.1" gem 'elasticsearch' gem 'elasticsearch-extensions' diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index df9509f06..cba0808ea 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -28,7 +28,7 @@ Gem::Specification.new do |s| s.add_dependency "hashie" s.add_development_dependency "bundler", "~> 1.3" - s.add_development_dependency "rake", "< 11.0" + s.add_development_dependency "rake", "~> 11.1" s.add_development_dependency "elasticsearch-extensions" diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index d71bfcf93..2b7a7cbea 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -31,7 +31,7 @@ Gem::Specification.new do |s| s.add_dependency "virtus" s.add_development_dependency "bundler", "~> 1.5" - s.add_development_dependency "rake", "< 11.0" + s.add_development_dependency "rake", "~> 11.1" s.add_development_dependency "oj" diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index 14dee1ddf..f33c9f6b7 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -24,7 +24,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 1.9.3" s.add_development_dependency "bundler", "~> 1.3" - s.add_development_dependency "rake", "< 11.0" + s.add_development_dependency "rake", "~> 11.1" s.add_development_dependency "elasticsearch-extensions" s.add_development_dependency "elasticsearch-model" From 6d10abbdcbd7d06405e35ffbd32b21f28727d77b Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Mon, 23 May 2016 17:46:48 +0200 Subject: [PATCH 269/582] [MODEL] Fixed a problem where `Hashie::Mash#min` and `#max` returned unexpected values While people can define names for aggregations such as `min` and `max`, these methods are defined in `Enumerable#min` and `Enumerable#max`, and return an unexpected value: result.aggregations # => {"price"=>{"doc_count"=>9428, "min"=>{"value"=>1.0}, "max"=>{"value"=>260000.0}}} result.aggregations.price # => {"doc_count"=>9428, "min"=>{"value"=>1.0}, "max"=>{"value"=>260000.0}} result.aggregations.price.min # => ["doc_count", 9428] Therefore, the whole Hash with aggregations is being traversed, in a wrapper class, and a `min` and `max` method is defined for Hashie::Mash instances, because these names are too common. The wrapper class can have its uses further down the line, so a dedicated unit test file has been added as well. Closes #568 Related: elastic/elasticsearch-ruby#306 --- .../lib/elasticsearch/model.rb | 1 + .../lib/elasticsearch/model/response.rb | 2 +- .../model/response/aggregations.rb | 36 +++++++++++++++ .../integration/active_record_basic_test.rb | 20 +++++--- .../test/unit/response_aggregations_test.rb | 46 +++++++++++++++++++ 5 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb create mode 100644 elasticsearch-model/test/unit/response_aggregations_test.rb diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index 9d2b93da5..d66b28e51 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -31,6 +31,7 @@ require 'elasticsearch/model/response/results' require 'elasticsearch/model/response/records' require 'elasticsearch/model/response/pagination' +require 'elasticsearch/model/response/aggregations' require 'elasticsearch/model/response/suggestions' require 'elasticsearch/model/ext/active_record' diff --git a/elasticsearch-model/lib/elasticsearch/model/response.rb b/elasticsearch-model/lib/elasticsearch/model/response.rb index fad3828b3..fba25aaea 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response.rb @@ -69,7 +69,7 @@ def shards # Returns a Hashie::Mash of the aggregations # def aggregations - response['aggregations'] ? Hashie::Mash.new(response['aggregations']) : nil + Aggregations.new(response['aggregations']) end # Returns a Hashie::Mash of the suggestions diff --git a/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb b/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb new file mode 100644 index 000000000..06d46d1e3 --- /dev/null +++ b/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb @@ -0,0 +1,36 @@ +module Elasticsearch + module Model + module Response + + class Aggregations < Hashie::Mash + def initialize(attributes={}) + __redefine_enumerable_methods super(attributes) + end + + # Fix the problem of Hashie::Mash returning unexpected values for `min` and `max` methods + # + # People can define names for aggregations such as `min` and `max`, but these + # methods are defined in `Enumerable#min` and `Enumerable#max` + # + # { foo: 'bar' }.min + # # => [:foo, "bar"] + # + # Therefore, any Hashie::Mash instance value has the `min` and `max` + # methods redefined to return the real value + # + def __redefine_enumerable_methods(h) + if h.respond_to?(:each_pair) + h.each_pair { |k, v| v = __redefine_enumerable_methods(v) } + end + if h.is_a?(Hashie::Mash) + class << h + define_method(:min) { self[:min] } + define_method(:max) { self[:max] } + end + end + end + end + + end + end +end diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index e6ca97d6d..3636f98f4 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -12,6 +12,7 @@ class ActiveRecordBasicIntegrationTest < Elasticsearch::Test::IntegrationTestCas create_table :articles do |t| t.string :title t.string :body + t.integer :clicks, :default => 0 t.datetime :created_at, :default => 'NOW()' end end @@ -24,6 +25,7 @@ class ::Article < ActiveRecord::Base mapping do indexes :title, type: 'string', analyzer: 'snowball' indexes :body, type: 'string' + indexes :clicks, type: 'integer' indexes :created_at, type: 'date' end end @@ -31,7 +33,7 @@ class ::Article < ActiveRecord::Base def as_indexed_json(options = {}) attributes .symbolize_keys - .slice(:title, :body, :created_at) + .slice(:title, :body, :clicks, :created_at) .merge(suggest_title: title) end end @@ -39,9 +41,9 @@ def as_indexed_json(options = {}) Article.delete_all Article.__elasticsearch__.create_index! force: true - ::Article.create! title: 'Test', body: '' - ::Article.create! title: 'Testing Coding', body: '' - ::Article.create! title: 'Coding', body: '' + ::Article.create! title: 'Test', body: '', clicks: 1 + ::Article.create! title: 'Testing Coding', body: '', clicks: 2 + ::Article.create! title: 'Coding', body: '', clicks: 3 Article.__elasticsearch__.refresh_index! end @@ -217,11 +219,17 @@ def as_indexed_json(options = {}) should "allow dot access to response" do response = Article.search query: { match: { title: { query: 'test' } } }, - aggregations: { dates: { date_histogram: { field: 'created_at', interval: 'hour' } } }, + aggregations: { + dates: { date_histogram: { field: 'created_at', interval: 'hour' } }, + clicks: { global: {}, aggregations: { min: { min: { field: 'clicks' } } } } + }, suggest: { text: 'tezt', title: { term: { field: 'title', suggest_mode: 'always' } } } response.response.respond_to?(:aggregations) - assert_equal 2, response.aggregations.dates.buckets.first.doc_count + assert_equal 2, response.aggregations.dates.buckets.first.doc_count + assert_equal 3, response.aggregations.clicks.doc_count + assert_equal 1.0, response.aggregations.clicks.min.value + assert_nil response.aggregations.clicks.max response.response.respond_to?(:suggest) assert_equal 1, response.suggestions.title.first.options.size diff --git a/elasticsearch-model/test/unit/response_aggregations_test.rb b/elasticsearch-model/test/unit/response_aggregations_test.rb new file mode 100644 index 000000000..cac17759d --- /dev/null +++ b/elasticsearch-model/test/unit/response_aggregations_test.rb @@ -0,0 +1,46 @@ +require 'test_helper' + +class Elasticsearch::Model::ResponseAggregationsTest < Test::Unit::TestCase + context "Response aggregations" do + class OriginClass + def self.index_name; 'foo'; end + def self.document_type; 'bar'; end + end + + RESPONSE = { + 'aggregations' => { + 'foo' => {'bar' => 10 }, + 'price' => { 'doc_count' => 123, + 'min' => { 'value' => 1.0}, + 'max' => { 'value' => 99 } + } + } + } + + setup do + @search = Elasticsearch::Model::Searching::SearchRequest.new OriginClass, '*' + @search.stubs(:execute!).returns(RESPONSE) + end + + should "access the aggregations" do + @search.expects(:execute!).returns(RESPONSE) + + response = Elasticsearch::Model::Response::Response.new OriginClass, @search + assert_respond_to response, :aggregations + assert_kind_of Elasticsearch::Model::Response::Aggregations, response.aggregations + assert_kind_of Hashie::Mash, response.aggregations.foo + assert_equal 10, response.aggregations.foo.bar + end + + should "properly return min and max values" do + @search.expects(:execute!).returns(RESPONSE) + + response = Elasticsearch::Model::Response::Response.new OriginClass, @search + + assert_equal 123, response.aggregations.price.doc_count + assert_equal 1, response.aggregations.price.min.value + assert_equal 99, response.aggregations.price.max.value + end + + end +end From ceef80eafce6f3cf5513496c2ac7bca6cb5b45f4 Mon Sep 17 00:00:00 2001 From: bogdanvlviv <bogdanvlviv@gmail.com> Date: Sun, 29 May 2016 14:04:45 +0300 Subject: [PATCH 270/582] [MODEL] Added information about `elasticsearch-dsl` to the README Closes #574 [ci skip] --- elasticsearch-model/README.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index c219a4600..28b8d0a72 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -320,8 +320,8 @@ response.results.first.highlight.title # ["Quick brown <em>fox</em>"] ``` -You can pass any object which implements a `to_hash` method, or you can use your favourite JSON builder -to build the search definition as a JSON string: +You can pass any object which implements a `to_hash` method, which is called automatically, +so you can use a custom class or your favourite JSON builder to build the search definition: ```ruby require 'jbuilder' @@ -341,6 +341,25 @@ response.results.first.title # => "Quick brown fox" ``` +Also, you can use the [**`elasticsearch-dsl`**](https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch-dsl) library, which provides a specialized Ruby API for +the Elasticsearch Query DSL: + +```ruby +require 'elasticsearch/dsl' + +query = Elasticsearch::DSL::Search.search do + query do + match :title do + query 'fox dogs' + end + end +end + +response = Article.search query +response.results.first.title +# => "Quick brown fox" +``` + ### Index Configuration For proper search engine function, it's often necessary to configure the index properly. From b8455db186664e21927bfb271bab6390853e7ff3 Mon Sep 17 00:00:00 2001 From: Ryan Mohr <ryan.mohr@gmail.com> Date: Tue, 17 Feb 2015 10:10:37 -1000 Subject: [PATCH 271/582] [MODEL] Added support for inherited index names and doc types This patch adds support for inheriting index_name and document_type on an opt-in basis: Elasticsearch::Model.inheritance_enabled = true class Animal < ActiveRecord::Base document_type 'mammal' index_name 'mammals' end class Dog < Animal end Animal.document_type # 'mammal' Animal.index_name # 'mammals' Dog.document_type # 'mammal' Dog.index_name # 'mammals' Closes #332 Related: #28, #92, #170, #344 --- .../lib/elasticsearch/model.rb | 19 ++++- .../lib/elasticsearch/model/naming.rb | 28 ++++++- .../test/unit/naming_inheritance_test.rb | 78 +++++++++++++++++++ 3 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 elasticsearch-model/test/unit/naming_inheritance_test.rb diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index d66b28e51..8406026e7 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -131,7 +131,6 @@ class << self end module ClassMethods - # Get the client common for all models # # @example Get the client @@ -181,6 +180,24 @@ def search(query_or_payload, models=[], options={}) request = Searching::SearchRequest.new(models, query_or_payload, options) Response::Response.new(models, request) end + + # Check if inheritance is enabled + # + # @note Inheritance is disabled by default. + # + def inheritance_enabled + @inheritance_enabled ||= false + end + + # Enable inheritance of index_name and document_type + # + # @example Enable inheritance + # + # Elasticsearch::Model.inheritance_enabled = true + # + def inheritance_enabled=(inheritance_enabled) + @inheritance_enabled = inheritance_enabled + end end extend ClassMethods diff --git a/elasticsearch-model/lib/elasticsearch/model/naming.rb b/elasticsearch-model/lib/elasticsearch/model/naming.rb index ce510d2d4..2de9f8e60 100644 --- a/elasticsearch-model/lib/elasticsearch/model/naming.rb +++ b/elasticsearch-model/lib/elasticsearch/model/naming.rb @@ -34,7 +34,7 @@ def index_name name=nil, &block if @index_name.respond_to?(:call) @index_name.call else - @index_name || self.model_name.collection.gsub(/\//, '-') + @index_name || implicit(:index_name) end end @@ -58,7 +58,7 @@ def index_name=(name) # Article.document_type "my-article" # def document_type name=nil - @document_type = name || @document_type || self.model_name.element + @document_type = name || @document_type || implicit(:document_type) end @@ -69,6 +69,30 @@ def document_type name=nil def document_type=(name) @document_type = name end + + private + + def implicit(prop) + value = nil + + if Elasticsearch::Model.inheritance_enabled + self.ancestors.each do |klass| + next if klass == self + break if value = klass.respond_to?(prop) && klass.send(prop) + end + end + + value || self.send("default_#{prop}") + end + + def default_index_name + self.model_name.collection.gsub(/\//, '-') + end + + def default_document_type + self.model_name.element + end + end module InstanceMethods diff --git a/elasticsearch-model/test/unit/naming_inheritance_test.rb b/elasticsearch-model/test/unit/naming_inheritance_test.rb new file mode 100644 index 000000000..6faf136a3 --- /dev/null +++ b/elasticsearch-model/test/unit/naming_inheritance_test.rb @@ -0,0 +1,78 @@ +require "test_helper" + +class Elasticsearch::Model::NamingInheritanceTest < Test::Unit::TestCase + def setup + Elasticsearch::Model.inheritance_enabled = true + end + + def teardown + Elasticsearch::Model.inheritance_enabled = false + end + + context "Naming module with inheritance" do + class ::TestBase + extend ActiveModel::Naming + + extend Elasticsearch::Model::Naming::ClassMethods + include Elasticsearch::Model::Naming::InstanceMethods + end + + class ::Animal < ::TestBase + extend ActiveModel::Naming + + extend Elasticsearch::Model::Naming::ClassMethods + include Elasticsearch::Model::Naming::InstanceMethods + + index_name "mammals" + document_type "mammal" + end + + class ::Dog < ::Animal + end + + module ::MyNamespace + class Dog < ::Animal + end + end + + should "return the default index_name" do + assert_equal "test_bases", TestBase.index_name + assert_equal "test_bases", TestBase.new.index_name + end + + should "return the explicit index_name" do + assert_equal "mammals", Animal.index_name + assert_equal "mammals", Animal.new.index_name + end + + should "return the ancestor index_name" do + assert_equal "mammals", Dog.index_name + assert_equal "mammals", Dog.new.index_name + end + + should "return the ancestor index_name for namespaced model" do + assert_equal "mammals", ::MyNamespace::Dog.index_name + assert_equal "mammals", ::MyNamespace::Dog.new.index_name + end + + should "return the default document_type" do + assert_equal "test_base", TestBase.document_type + assert_equal "test_base", TestBase.new.document_type + end + + should "return the explicit document_type" do + assert_equal "mammal", Animal.document_type + assert_equal "mammal", Animal.new.document_type + end + + should "return the ancestor document_type" do + assert_equal "mammal", Dog.document_type + assert_equal "mammal", Dog.new.document_type + end + + should "return the ancestor document_type for namespaced model" do + assert_equal "mammal", ::MyNamespace::Dog.document_type + assert_equal "mammal", ::MyNamespace::Dog.new.document_type + end + end +end From 2a08d09e0eba9f4322ec3e4e2560cc0aaa568b12 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sun, 26 Jun 2016 17:17:56 +0200 Subject: [PATCH 272/582] [MODEL] Added a `Elasticsearch::Model.settings` method --- elasticsearch-model/lib/elasticsearch/model.rb | 6 ++++++ elasticsearch-model/test/unit/module_test.rb | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index 8406026e7..aa3bf9c5f 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -130,6 +130,12 @@ class << self end end + # Access the module settings + # + def self.settings + @settings ||= {} + end + module ClassMethods # Get the client common for all models # diff --git a/elasticsearch-model/test/unit/module_test.rb b/elasticsearch-model/test/unit/module_test.rb index a429b3d11..fb8e0ba61 100644 --- a/elasticsearch-model/test/unit/module_test.rb +++ b/elasticsearch-model/test/unit/module_test.rb @@ -53,5 +53,16 @@ def self.search(query, options={}) end end + context "settings" do + should "access the settings" do + assert_not_nil Elasticsearch::Model.settings + end + + should "allow to set settings" do + assert_nothing_raised { Elasticsearch::Model.settings[:foo] = 'bar' } + assert_equal 'bar', Elasticsearch::Model.settings[:foo] + end + end + end end From 5251a5201f0b3fa1278e4366e364f9aaded81be6 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sun, 26 Jun 2016 17:18:15 +0200 Subject: [PATCH 273/582] [MODEL] Changed the naming inheritance logic to use `Elasticsearch::Model.settings` This patch removes the `Elasticsearch::Model.inheritance_enabled` method added in b8455db, and changes the logic to use the `settings` for the module. Related: #332 --- .../lib/elasticsearch/model/naming.rb | 2 +- .../test/unit/naming_inheritance_test.rb | 20 +++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/naming.rb b/elasticsearch-model/lib/elasticsearch/model/naming.rb index 2de9f8e60..7bf24f089 100644 --- a/elasticsearch-model/lib/elasticsearch/model/naming.rb +++ b/elasticsearch-model/lib/elasticsearch/model/naming.rb @@ -75,7 +75,7 @@ def document_type=(name) def implicit(prop) value = nil - if Elasticsearch::Model.inheritance_enabled + if Elasticsearch::Model.settings[:inheritance_enabled] self.ancestors.each do |klass| next if klass == self break if value = klass.respond_to?(prop) && klass.send(prop) diff --git a/elasticsearch-model/test/unit/naming_inheritance_test.rb b/elasticsearch-model/test/unit/naming_inheritance_test.rb index 6faf136a3..b66d415a0 100644 --- a/elasticsearch-model/test/unit/naming_inheritance_test.rb +++ b/elasticsearch-model/test/unit/naming_inheritance_test.rb @@ -2,11 +2,11 @@ class Elasticsearch::Model::NamingInheritanceTest < Test::Unit::TestCase def setup - Elasticsearch::Model.inheritance_enabled = true + Elasticsearch::Model.settings[:inheritance_enabled] = true end def teardown - Elasticsearch::Model.inheritance_enabled = false + Elasticsearch::Model.settings[:inheritance_enabled] = false end context "Naming module with inheritance" do @@ -35,6 +35,16 @@ class Dog < ::Animal end end + class ::Cat < ::Animal + extend ActiveModel::Naming + + extend Elasticsearch::Model::Naming::ClassMethods + include Elasticsearch::Model::Naming::InstanceMethods + + index_name "cats" + document_type "cat" + end + should "return the default index_name" do assert_equal "test_bases", TestBase.index_name assert_equal "test_bases", TestBase.new.index_name @@ -43,6 +53,9 @@ class Dog < ::Animal should "return the explicit index_name" do assert_equal "mammals", Animal.index_name assert_equal "mammals", Animal.new.index_name + + assert_equal "cats", Cat.index_name + assert_equal "cats", Cat.new.index_name end should "return the ancestor index_name" do @@ -63,6 +76,9 @@ class Dog < ::Animal should "return the explicit document_type" do assert_equal "mammal", Animal.document_type assert_equal "mammal", Animal.new.document_type + + assert_equal "cat", Cat.document_type + assert_equal "cat", Cat.new.document_type end should "return the ancestor document_type" do From 15761247f3e99654bda946a178e50b5365414b59 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Tue, 28 Jun 2016 14:17:44 +0200 Subject: [PATCH 274/582] [MODEL] Added information about the `settings` method and the `inheritance_enabled` setting into the README --- elasticsearch-model/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 28b8d0a72..b631b2a9b 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -704,6 +704,18 @@ response.records.records.class More examples can be found in the `examples` folder. Please see the `Elasticsearch::Model::Adapter` module and its submodules for technical information. +### Settings + +The module provides a common `settings` method to customize various features. + +At the moment, the only supported setting is `:inheritance_enabled`, which makes the class receiving the module +respect index names and document types of a super-class, eg. in case you're using "single table inheritance" (STI) +in Rails: + +```ruby +Elasticsearch::Model.settings[:inheritance_enabled] = true +``` + ## Development and Community For local development, clone the repository and run `bundle install`. See `rake -T` for a list of From 94d0b28f08dfae63c0cfa47d510b563218e0e856 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Tue, 13 Dec 2016 09:31:41 +0100 Subject: [PATCH 275/582] [MODEL] Disable "verbose" and "warnings" in integration tests --- elasticsearch-model/Rakefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index 2825f58b0..93027ccd7 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -26,6 +26,8 @@ namespace :test do Rake::Task['test:ci_reporter'].invoke if ENV['CI'] test.libs << 'lib' << 'test' test.test_files = FileList["test/integration/**/*_test.rb"] + test.verbose = false + test.warning = false end desc "Run integration tests against ActiveModel 3 and 4" From 66bb01273a53fe632898ef7cc6b8117d1047cd0a Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Tue, 13 Dec 2016 10:09:24 +0100 Subject: [PATCH 276/582] [MODEL] Added code for establishing ActiveRecord connections to test classes With ActiveRecord 3.x, the connection setup in the main `setup` method doesn't seem to be called properly. --- .../integration/active_record_associations_parent_child.rb | 3 +++ .../test/integration/active_record_associations_test.rb | 3 +++ .../test/integration/active_record_basic_test.rb | 3 +++ .../integration/active_record_custom_serialization_test.rb | 3 +++ .../test/integration/active_record_import_test.rb | 3 +++ .../test/integration/active_record_namespaced_model_test.rb | 3 +++ .../test/integration/active_record_pagination_test.rb | 3 +++ .../test/integration/dynamic_index_name_test.rb | 3 +++ elasticsearch-model/test/integration/multiple_models_test.rb | 3 +++ 9 files changed, 27 insertions(+) diff --git a/elasticsearch-model/test/integration/active_record_associations_parent_child.rb b/elasticsearch-model/test/integration/active_record_associations_parent_child.rb index 39be1144b..5e9b49f9e 100644 --- a/elasticsearch-model/test/integration/active_record_associations_parent_child.rb +++ b/elasticsearch-model/test/integration/active_record_associations_parent_child.rb @@ -1,6 +1,9 @@ require 'test_helper' require 'active_record' +# Needed for ActiveRecord 3.x ? +ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? + class Question < ActiveRecord::Base include Elasticsearch::Model diff --git a/elasticsearch-model/test/integration/active_record_associations_test.rb b/elasticsearch-model/test/integration/active_record_associations_test.rb index af67ad889..8177c193e 100644 --- a/elasticsearch-model/test/integration/active_record_associations_test.rb +++ b/elasticsearch-model/test/integration/active_record_associations_test.rb @@ -1,6 +1,9 @@ require 'test_helper' require 'active_record' +# Needed for ActiveRecord 3.x ? +ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? + module Elasticsearch module Model class ActiveRecordAssociationsIntegrationTest < Elasticsearch::Test::IntegrationTestCase diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index 3636f98f4..4800abc4a 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -3,6 +3,9 @@ puts "ActiveRecord #{ActiveRecord::VERSION::STRING}", '-'*80 +# Needed for ActiveRecord 3.x ? +ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? + module Elasticsearch module Model class ActiveRecordBasicIntegrationTest < Elasticsearch::Test::IntegrationTestCase diff --git a/elasticsearch-model/test/integration/active_record_custom_serialization_test.rb b/elasticsearch-model/test/integration/active_record_custom_serialization_test.rb index 03eb9a441..60b1f6d32 100644 --- a/elasticsearch-model/test/integration/active_record_custom_serialization_test.rb +++ b/elasticsearch-model/test/integration/active_record_custom_serialization_test.rb @@ -1,6 +1,9 @@ require 'test_helper' require 'active_record' +# Needed for ActiveRecord 3.x ? +ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? + module Elasticsearch module Model class ActiveRecordCustomSerializationTest < Elasticsearch::Test::IntegrationTestCase diff --git a/elasticsearch-model/test/integration/active_record_import_test.rb b/elasticsearch-model/test/integration/active_record_import_test.rb index 8cc7448cc..49c0e5685 100644 --- a/elasticsearch-model/test/integration/active_record_import_test.rb +++ b/elasticsearch-model/test/integration/active_record_import_test.rb @@ -1,6 +1,9 @@ require 'test_helper' require 'active_record' +# Needed for ActiveRecord 3.x ? +ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? + module Elasticsearch module Model class ActiveRecordImportIntegrationTest < Elasticsearch::Test::IntegrationTestCase diff --git a/elasticsearch-model/test/integration/active_record_namespaced_model_test.rb b/elasticsearch-model/test/integration/active_record_namespaced_model_test.rb index be047f4e7..d1eb11f27 100644 --- a/elasticsearch-model/test/integration/active_record_namespaced_model_test.rb +++ b/elasticsearch-model/test/integration/active_record_namespaced_model_test.rb @@ -1,6 +1,9 @@ require 'test_helper' require 'active_record' +# Needed for ActiveRecord 3.x ? +ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? + module Elasticsearch module Model class ActiveRecordNamespacedModelIntegrationTest < Elasticsearch::Test::IntegrationTestCase diff --git a/elasticsearch-model/test/integration/active_record_pagination_test.rb b/elasticsearch-model/test/integration/active_record_pagination_test.rb index e1d6fefb1..9a043a1e8 100644 --- a/elasticsearch-model/test/integration/active_record_pagination_test.rb +++ b/elasticsearch-model/test/integration/active_record_pagination_test.rb @@ -1,6 +1,9 @@ require 'test_helper' require 'active_record' +# Needed for ActiveRecord 3.x ? +ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? + module Elasticsearch module Model class ActiveRecordPaginationTest < Elasticsearch::Test::IntegrationTestCase diff --git a/elasticsearch-model/test/integration/dynamic_index_name_test.rb b/elasticsearch-model/test/integration/dynamic_index_name_test.rb index a71633c6a..afecc26c0 100755 --- a/elasticsearch-model/test/integration/dynamic_index_name_test.rb +++ b/elasticsearch-model/test/integration/dynamic_index_name_test.rb @@ -1,6 +1,9 @@ require 'test_helper' require 'active_record' +# Needed for ActiveRecord 3.x ? +ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? + module Elasticsearch module Model class DynamicIndexNameTest < Elasticsearch::Test::IntegrationTestCase diff --git a/elasticsearch-model/test/integration/multiple_models_test.rb b/elasticsearch-model/test/integration/multiple_models_test.rb index 7d0bf7b6b..55ccd0175 100644 --- a/elasticsearch-model/test/integration/multiple_models_test.rb +++ b/elasticsearch-model/test/integration/multiple_models_test.rb @@ -1,6 +1,9 @@ require 'test_helper' require 'active_record' +# Needed for ActiveRecord 3.x ? +ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? + Mongo.setup! module Elasticsearch From fa2b52556cd4a363e97b3010620724bcbb66e452 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Tue, 13 Dec 2016 10:11:26 +0100 Subject: [PATCH 277/582] [MODEL] Reorganized the class definitions in the integration tests With ActiveRecord 4 and 5, the class definitions within Shoulda's `setup` method don't work correctly. --- .../active_record_associations_test.rb | 108 +++++++++--------- .../integration/active_record_basic_test.rb | 43 +++---- .../integration/active_record_import_test.rb | 27 ++--- .../active_record_pagination_test.rb | 22 ++-- .../test/integration/multiple_models_test.rb | 49 ++++---- 5 files changed, 126 insertions(+), 123 deletions(-) diff --git a/elasticsearch-model/test/integration/active_record_associations_test.rb b/elasticsearch-model/test/integration/active_record_associations_test.rb index 8177c193e..ca0361d89 100644 --- a/elasticsearch-model/test/integration/active_record_associations_test.rb +++ b/elasticsearch-model/test/integration/active_record_associations_test.rb @@ -8,6 +8,58 @@ module Elasticsearch module Model class ActiveRecordAssociationsIntegrationTest < Elasticsearch::Test::IntegrationTestCase + # ----- Search integration via Concern module ----------------------------------------------------- + + module Searchable + extend ActiveSupport::Concern + + included do + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + # Set up the mapping + # + settings index: { number_of_shards: 1, number_of_replicas: 0 } do + mapping do + indexes :title, analyzer: 'snowball' + indexes :created_at, type: 'date' + + indexes :authors do + indexes :first_name + indexes :last_name + indexes :full_name, type: 'multi_field' do + indexes :full_name + indexes :raw, analyzer: 'keyword' + end + end + + indexes :categories, analyzer: 'keyword' + + indexes :comments, type: 'nested' do + indexes :text + indexes :author + end + end + end + + # Customize the JSON serialization for Elasticsearch + # + def as_indexed_json(options={}) + { + title: title, + text: text, + categories: categories.map(&:title), + authors: authors.as_json(methods: [:full_name], only: [:full_name, :first_name, :last_name]), + comments: comments.as_json(only: [:text, :author]) + } + end + + # Update document in the index after touch + # + after_touch() { __elasticsearch__.index_document } + end + end + context "ActiveRecord associations" do setup do @@ -40,7 +92,9 @@ class ActiveRecordAssociationsIntegrationTest < Elasticsearch::Test::Integration t.string :author t.references :post t.timestamps - end and add_index(:comments, :post_id) + end + + add_index(:comments, :post_id) unless index_exists?(:comments, :post_id) create_table :posts do |t| t.string :title @@ -81,58 +135,6 @@ class Post < ActiveRecord::Base has_many :comments end - # ----- Search integration via Concern module ----------------------------------------------------- - - module Searchable - extend ActiveSupport::Concern - - included do - include Elasticsearch::Model - include Elasticsearch::Model::Callbacks - - # Set up the mapping - # - settings index: { number_of_shards: 1, number_of_replicas: 0 } do - mapping do - indexes :title, analyzer: 'snowball' - indexes :created_at, type: 'date' - - indexes :authors do - indexes :first_name - indexes :last_name - indexes :full_name, type: 'multi_field' do - indexes :full_name - indexes :raw, analyzer: 'keyword' - end - end - - indexes :categories, analyzer: 'keyword' - - indexes :comments, type: 'nested' do - indexes :text - indexes :author - end - end - end - - # Customize the JSON serialization for Elasticsearch - # - def as_indexed_json(options={}) - { - title: title, - text: text, - categories: categories.map(&:title), - authors: authors.as_json(methods: [:full_name], only: [:full_name, :first_name, :last_name]), - comments: comments.as_json(only: [:text, :author]) - } - end - - # Update document in the index after touch - # - after_touch() { __elasticsearch__.index_document } - end - end - # Include the search integration # Post.__send__ :include, Searchable diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index 4800abc4a..0f2acbd0b 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -9,6 +9,28 @@ module Elasticsearch module Model class ActiveRecordBasicIntegrationTest < Elasticsearch::Test::IntegrationTestCase + + class ::Article < ActiveRecord::Base + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + settings index: { number_of_shards: 1, number_of_replicas: 0 } do + mapping do + indexes :title, type: 'string', analyzer: 'snowball' + indexes :body, type: 'string' + indexes :clicks, type: 'integer' + indexes :created_at, type: 'date' + end + end + + def as_indexed_json(options = {}) + attributes + .symbolize_keys + .slice(:title, :body, :clicks, :created_at) + .merge(suggest_title: title) + end + end + context "ActiveRecord basic integration" do setup do ActiveRecord::Schema.define(:version => 1) do @@ -20,27 +42,6 @@ class ActiveRecordBasicIntegrationTest < Elasticsearch::Test::IntegrationTestCas end end - class ::Article < ActiveRecord::Base - include Elasticsearch::Model - include Elasticsearch::Model::Callbacks - - settings index: { number_of_shards: 1, number_of_replicas: 0 } do - mapping do - indexes :title, type: 'string', analyzer: 'snowball' - indexes :body, type: 'string' - indexes :clicks, type: 'integer' - indexes :created_at, type: 'date' - end - end - - def as_indexed_json(options = {}) - attributes - .symbolize_keys - .slice(:title, :body, :clicks, :created_at) - .merge(suggest_title: title) - end - end - Article.delete_all Article.__elasticsearch__.create_index! force: true diff --git a/elasticsearch-model/test/integration/active_record_import_test.rb b/elasticsearch-model/test/integration/active_record_import_test.rb index 49c0e5685..31febcb2c 100644 --- a/elasticsearch-model/test/integration/active_record_import_test.rb +++ b/elasticsearch-model/test/integration/active_record_import_test.rb @@ -7,6 +7,20 @@ module Elasticsearch module Model class ActiveRecordImportIntegrationTest < Elasticsearch::Test::IntegrationTestCase + + class ::ImportArticle < ActiveRecord::Base + include Elasticsearch::Model + + scope :popular, -> { where('views >= 50') } + + mapping do + indexes :title, type: 'string' + indexes :views, type: 'integer' + indexes :numeric, type: 'integer' + indexes :created_at, type: 'date' + end + end + context "ActiveRecord importing" do setup do ActiveRecord::Schema.define(:version => 1) do @@ -18,19 +32,6 @@ class ActiveRecordImportIntegrationTest < Elasticsearch::Test::IntegrationTestCa end end - class ::ImportArticle < ActiveRecord::Base - include Elasticsearch::Model - - scope :popular, -> { where('views >= 50') } - - mapping do - indexes :title, type: 'string' - indexes :views, type: 'integer' - indexes :numeric, type: 'integer' - indexes :created_at, type: 'date' - end - end - ImportArticle.delete_all ImportArticle.__elasticsearch__.create_index! force: true ImportArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' diff --git a/elasticsearch-model/test/integration/active_record_pagination_test.rb b/elasticsearch-model/test/integration/active_record_pagination_test.rb index 9a043a1e8..78892b73c 100644 --- a/elasticsearch-model/test/integration/active_record_pagination_test.rb +++ b/elasticsearch-model/test/integration/active_record_pagination_test.rb @@ -7,21 +7,21 @@ module Elasticsearch module Model class ActiveRecordPaginationTest < Elasticsearch::Test::IntegrationTestCase - context "ActiveRecord pagination" do - setup do - class ::ArticleForPagination < ActiveRecord::Base - include Elasticsearch::Model + class ::ArticleForPagination < ActiveRecord::Base + include Elasticsearch::Model - scope :published, -> { where(published: true) } + scope :published, -> { where(published: true) } - settings index: { number_of_shards: 1, number_of_replicas: 0 } do - mapping do - indexes :title, type: 'string', analyzer: 'snowball' - indexes :created_at, type: 'date' - end - end + settings index: { number_of_shards: 1, number_of_replicas: 0 } do + mapping do + indexes :title, type: 'string', analyzer: 'snowball' + indexes :created_at, type: 'date' end + end + end + context "ActiveRecord pagination" do + setup do ActiveRecord::Schema.define(:version => 1) do create_table ::ArticleForPagination.table_name do |t| t.string :title diff --git a/elasticsearch-model/test/integration/multiple_models_test.rb b/elasticsearch-model/test/integration/multiple_models_test.rb index 55ccd0175..957aca852 100644 --- a/elasticsearch-model/test/integration/multiple_models_test.rb +++ b/elasticsearch-model/test/integration/multiple_models_test.rb @@ -9,6 +9,30 @@ module Elasticsearch module Model class MultipleModelsIntegration < Elasticsearch::Test::IntegrationTestCase + module ::NameSearch + extend ActiveSupport::Concern + + included do + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + settings index: {number_of_shards: 1, number_of_replicas: 0} do + mapping do + indexes :name, type: 'string', analyzer: 'snowball' + indexes :created_at, type: 'date' + end + end + end + end + + class ::Episode < ActiveRecord::Base + include NameSearch + end + + class ::Series < ActiveRecord::Base + include NameSearch + end + context "Multiple models" do setup do ActiveRecord::Schema.define(:version => 1) do @@ -23,30 +47,6 @@ class MultipleModelsIntegration < Elasticsearch::Test::IntegrationTestCase end end - module ::NameSearch - extend ActiveSupport::Concern - - included do - include Elasticsearch::Model - include Elasticsearch::Model::Callbacks - - settings index: {number_of_shards: 1, number_of_replicas: 0} do - mapping do - indexes :name, type: 'string', analyzer: 'snowball' - indexes :created_at, type: 'date' - end - end - end - end - - class ::Episode < ActiveRecord::Base - include NameSearch - end - - class ::Series < ActiveRecord::Base - include NameSearch - end - [::Episode, ::Series].each do |model| model.delete_all model.__elasticsearch__.create_index! force: true @@ -55,7 +55,6 @@ class ::Series < ActiveRecord::Base model.create name: "The greatest #{model.name}" model.__elasticsearch__.refresh_index! end - end should "find matching documents across multiple models" do From e64baf326de9b4692262ca79263bc0c1ffbb29fa Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Tue, 13 Dec 2016 12:02:16 +0100 Subject: [PATCH 278/582] [MODEL] Moved `require` within unit test to the top of the file This is to avoid: activesupport-4.0.13/lib/active_support/json.rb:2: warning: loading in progress, circular require considered harmful --- elasticsearch-model/test/unit/response_result_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/test/unit/response_result_test.rb b/elasticsearch-model/test/unit/response_result_test.rb index ff78d2579..c357d46ba 100644 --- a/elasticsearch-model/test/unit/response_result_test.rb +++ b/elasticsearch-model/test/unit/response_result_test.rb @@ -1,4 +1,5 @@ require 'test_helper' +require 'active_support/json/encoding' class Elasticsearch::Model::ResultTest < Test::Unit::TestCase context "Response result" do @@ -80,7 +81,6 @@ class Elasticsearch::Model::ResultTest < Test::Unit::TestCase end should "delegate as_json to @result even when ActiveSupport changed half of Ruby" do - require 'active_support/json/encoding' result = Elasticsearch::Model::Response::Result.new foo: 'bar' result.instance_variable_get(:@result).expects(:as_json) From 8f8761a0cd6877f13956ce958be0a19de3210c10 Mon Sep 17 00:00:00 2001 From: Savater Sebastien <savater.sebastien@gmail.com> Date: Wed, 17 Aug 2016 18:45:27 +0200 Subject: [PATCH 279/582] [MODEL] Added ActiveRecord 5 support to integration test configuration --- Rakefile | 3 +++ elasticsearch-model/.gitignore | 1 + elasticsearch-model/Rakefile | 3 ++- elasticsearch-model/elasticsearch-model.gemspec | 2 +- elasticsearch-model/gemfiles/5.0.gemfile | 12 ++++++++++++ 5 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 elasticsearch-model/gemfiles/5.0.gemfile diff --git a/Rakefile b/Rakefile index e6c517970..776e3004a 100644 --- a/Rakefile +++ b/Rakefile @@ -35,6 +35,8 @@ namespace :bundle do sh "bundle install --gemfile #{__current__.join('elasticsearch-model/gemfiles')}/3.0.gemfile" puts '-'*80 sh "bundle install --gemfile #{__current__.join('elasticsearch-model/gemfiles')}/4.0.gemfile" + puts '-'*80 + sh "bundle install --gemfile #{__current__.join('elasticsearch-model/gemfiles')}/5.0.gemfile" end desc "Remove Gemfile.lock in all subprojects" @@ -45,6 +47,7 @@ namespace :bundle do end sh "rm -f #{__current__.join('elasticsearch-model/gemfiles')}/3.0.gemfile.lock" sh "rm -f #{__current__.join('elasticsearch-model/gemfiles')}/4.0.gemfile.lock" + sh "rm -f #{__current__.join('elasticsearch-model/gemfiles')}/5.0.gemfile.lock" end end diff --git a/elasticsearch-model/.gitignore b/elasticsearch-model/.gitignore index 3934d7e55..8a8ab4613 100644 --- a/elasticsearch-model/.gitignore +++ b/elasticsearch-model/.gitignore @@ -18,3 +18,4 @@ tmp gemfiles/3.0.gemfile.lock gemfiles/4.0.gemfile.lock +gemfiles/5.0.gemfile.lock diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index 93027ccd7..031ca8599 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -30,10 +30,11 @@ namespace :test do test.warning = false end - desc "Run integration tests against ActiveModel 3 and 4" + desc "Run integration tests against ActiveModel 3, 4 and 5" task :integration do sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/3.0.gemfile', __FILE__)}' bundle exec rake test:run_integration" unless defined?(RUBY_VERSION) && RUBY_VERSION > '2.2' sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/4.0.gemfile', __FILE__)}' bundle exec rake test:run_integration" + sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/5.0.gemfile', __FILE__)}' bundle exec rake test:run_integration" end desc "Run unit and integration tests" diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index cba0808ea..07ecc87b9 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -39,7 +39,7 @@ Gem::Specification.new do |s| s.add_development_dependency "kaminari" s.add_development_dependency "will_paginate" - s.add_development_dependency "minitest", "~> 4.2" + s.add_development_dependency "minitest", ">= 4.2" s.add_development_dependency "test-unit" if defined?(RUBY_VERSION) && RUBY_VERSION > '2.2' s.add_development_dependency "shoulda-context" s.add_development_dependency "mocha" diff --git a/elasticsearch-model/gemfiles/5.0.gemfile b/elasticsearch-model/gemfiles/5.0.gemfile new file mode 100644 index 000000000..75b8a7ca9 --- /dev/null +++ b/elasticsearch-model/gemfiles/5.0.gemfile @@ -0,0 +1,12 @@ +# Usage: +# +# $ BUNDLE_GEMFILE=./gemfiles/5.0.gemfile bundle install +# $ BUNDLE_GEMFILE=./gemfiles/5.0.gemfile bundle exec rake test:integration + +source 'https://rubygems.org' + +gemspec path: '../' + +gem 'activemodel', '~> 5' +gem 'activerecord', '~> 5' +gem 'sqlite3' From c9c4d2f1e0146b68feec76a54e8b27c569049d61 Mon Sep 17 00:00:00 2001 From: Savater Sebastien <savater.sebastien@gmail.com> Date: Wed, 17 Aug 2016 21:22:27 +0200 Subject: [PATCH 280/582] [MODEL] Fixed records sorting with ActiveRecord 5.x Closes #618 --- .../lib/elasticsearch/model/adapters/active_record.rb | 5 ++++- .../test/integration/active_record_basic_test.rb | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb index 2d9bb5378..9d092dd10 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb @@ -26,7 +26,10 @@ def records # by redefining `to_a`, unless the user has called `order()` # sql_records.instance_exec(response.response['hits']['hits']) do |hits| - define_singleton_method :to_a do + ar_records_method_name = :to_a + ar_records_method_name = :records if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 5 + + define_singleton_method(ar_records_method_name) do if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4 self.load else diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index 0f2acbd0b..fe3972301 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -104,7 +104,10 @@ def as_indexed_json(options = {}) end should "preserve the search results order for records" do - response = Article.search('title:code') + response = Article.search query: { match: { title: 'code' }}, sort: { clicks: :desc } + + assert_equal response.records[0].clicks, 3 + assert_equal response.records[1].clicks, 2 response.records.each_with_hit do |r, h| assert_equal h._id, r.id.to_s From 40f9e7eae2696d44abf4a23ae4a499650f0b29f4 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Tue, 13 Dec 2016 12:25:21 +0100 Subject: [PATCH 281/582] [MODEL] Added, that `add_index` for ActiveRecord models is only called when it doesn't exist already --- elasticsearch-model/examples/activerecord_associations.rb | 3 ++- .../integration/active_record_associations_parent_child.rb | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/examples/activerecord_associations.rb b/elasticsearch-model/examples/activerecord_associations.rb index 6143a0356..89b280c26 100644 --- a/elasticsearch-model/examples/activerecord_associations.rb +++ b/elasticsearch-model/examples/activerecord_associations.rb @@ -56,7 +56,8 @@ t.references :article t.timestamps end - add_index(:comments, :article_id) + + add_index(:comments, :article_id) unless index_exists?(:comments, :article_id) end # ----- Elasticsearch client setup ---------------------------------------------------------------- diff --git a/elasticsearch-model/test/integration/active_record_associations_parent_child.rb b/elasticsearch-model/test/integration/active_record_associations_parent_child.rb index 5e9b49f9e..4d964ac5e 100644 --- a/elasticsearch-model/test/integration/active_record_associations_parent_child.rb +++ b/elasticsearch-model/test/integration/active_record_associations_parent_child.rb @@ -71,12 +71,15 @@ class ActiveRecordAssociationsParentChildIntegrationTest < Elasticsearch::Test:: t.string :author t.timestamps end + create_table :answers do |t| t.text :text t.string :author t.references :question t.timestamps - end and add_index(:answers, :question_id) + end + + add_index(:answers, :question_id) unless index_exists?(:answers, :question_id) end Question.delete_all From b6d485748c71a07d064ea2a46a6da82d64a04cd7 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Tue, 13 Dec 2016 12:25:48 +0100 Subject: [PATCH 282/582] [MODEL] Use `records.__send__ :load` instead of `records.load` in the ActiveRecord adapter This is to prevent the "private method load called for Array" exceptions in unit tests. --- .../lib/elasticsearch/model/adapters/active_record.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb index 9d092dd10..23c4267fa 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb @@ -36,7 +36,7 @@ def records self.__send__(:exec_queries) end @records.sort_by { |record| hits.index { |hit| hit['_id'].to_s == record.id.to_s } } - end + end if self end sql_records @@ -45,7 +45,7 @@ def records # Prevent clash with `ActiveSupport::Dependencies::Loadable` # def load - records.load + records.__send__(:load) end # Intercept call to the `order` method, so we can ignore the order from Elasticsearch From 02a8407c8dda5c351832287e65b12d6ea2193d19 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 28 Jan 2017 11:21:51 +0100 Subject: [PATCH 283/582] Reduced verbosity of `rake test:unit` and `rake test:integration` --- elasticsearch-persistence/Rakefile | 6 ++++-- elasticsearch-rails/Rakefile | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/elasticsearch-persistence/Rakefile b/elasticsearch-persistence/Rakefile index 303f43997..f92cfd64e 100644 --- a/elasticsearch-persistence/Rakefile +++ b/elasticsearch-persistence/Rakefile @@ -18,14 +18,16 @@ namespace :test do Rake::Task['test:ci_reporter'].invoke if ENV['CI'] test.libs << 'lib' << 'test' test.test_files = FileList["test/unit/**/*_test.rb"] - # test.verbose = true - # test.warning = true + test.verbose = false + test.warning = false end Rake::TestTask.new(:integration) do |test| Rake::Task['test:ci_reporter'].invoke if ENV['CI'] test.libs << 'lib' << 'test' test.test_files = FileList["test/integration/**/*_test.rb"] + test.verbose = false + test.warning = false end Rake::TestTask.new(:all) do |test| diff --git a/elasticsearch-rails/Rakefile b/elasticsearch-rails/Rakefile index 3cf581a91..dfad126cb 100644 --- a/elasticsearch-rails/Rakefile +++ b/elasticsearch-rails/Rakefile @@ -18,14 +18,16 @@ namespace :test do Rake::Task['test:ci_reporter'].invoke if ENV['CI'] test.libs << 'lib' << 'test' test.test_files = FileList["test/unit/**/*_test.rb"] - # test.verbose = true - # test.warning = true + test.verbose = false + test.warning = false end Rake::TestTask.new(:integration) do |test| Rake::Task['test:ci_reporter'].invoke if ENV['CI'] test.libs << 'lib' << 'test' test.test_files = FileList["test/integration/**/*_test.rb"] + test.verbose = false + test.warning = false end Rake::TestTask.new(:all) do |test| From be5b44d80c97e1460a05ba40898a9ebfe6db4dc2 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 27 Jan 2017 19:26:50 +0100 Subject: [PATCH 284/582] [MODEL] Call `Kaminari::Hooks.init` only when available See: kaminari/kaminari@653143b Originally reported by @twe4ked in #660. Closes #660 --- elasticsearch-model/README.md | 2 +- elasticsearch-model/examples/activerecord_article.rb | 2 +- .../test/integration/active_record_pagination_test.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index b631b2a9b..8f963018d 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -303,7 +303,7 @@ In a Rails controller, use the the `params[:page]` parameter to paginate through To initialize and include the Kaminari pagination support manually: ```ruby -Kaminari::Hooks.init +Kaminari::Hooks.init if defined?(Kaminari::Hooks) Elasticsearch::Model::Response::Response.__send__ :include, Elasticsearch::Model::Response::Pagination::Kaminari ``` diff --git a/elasticsearch-model/examples/activerecord_article.rb b/elasticsearch-model/examples/activerecord_article.rb index b18ee9c7b..428c4dcc1 100644 --- a/elasticsearch-model/examples/activerecord_article.rb +++ b/elasticsearch-model/examples/activerecord_article.rb @@ -26,7 +26,7 @@ end end -Kaminari::Hooks.init +Kaminari::Hooks.init if defined?(Kaminari::Hooks) if defined?(Kaminari::Hooks) class Article < ActiveRecord::Base end diff --git a/elasticsearch-model/test/integration/active_record_pagination_test.rb b/elasticsearch-model/test/integration/active_record_pagination_test.rb index 78892b73c..e4d30b059 100644 --- a/elasticsearch-model/test/integration/active_record_pagination_test.rb +++ b/elasticsearch-model/test/integration/active_record_pagination_test.rb @@ -30,7 +30,7 @@ class ::ArticleForPagination < ActiveRecord::Base end end - Kaminari::Hooks.init + Kaminari::Hooks.init if defined?(Kaminari::Hooks) ArticleForPagination.delete_all ArticleForPagination.__elasticsearch__.create_index! force: true From 245f033bd07590bfa70da0574bf46292fdd81698 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 27 Jan 2017 19:38:37 +0100 Subject: [PATCH 285/582] [MODEL] Fixed the deprecation messages for `raise_in_transactional_callbacks` See: http://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#error-handling-in-transaction-callbacks --- .../test/integration/active_record_associations_parent_child.rb | 2 ++ .../test/integration/active_record_associations_test.rb | 2 ++ .../test/integration/active_record_basic_test.rb | 2 ++ .../test/integration/active_record_custom_serialization_test.rb | 2 ++ .../test/integration/active_record_import_test.rb | 2 ++ .../test/integration/active_record_namespaced_model_test.rb | 2 ++ .../test/integration/active_record_pagination_test.rb | 2 ++ elasticsearch-model/test/integration/dynamic_index_name_test.rb | 2 ++ elasticsearch-model/test/integration/multiple_models_test.rb | 2 ++ 9 files changed, 18 insertions(+) diff --git a/elasticsearch-model/test/integration/active_record_associations_parent_child.rb b/elasticsearch-model/test/integration/active_record_associations_parent_child.rb index 4d964ac5e..08d8f81df 100644 --- a/elasticsearch-model/test/integration/active_record_associations_parent_child.rb +++ b/elasticsearch-model/test/integration/active_record_associations_parent_child.rb @@ -4,6 +4,8 @@ # Needed for ActiveRecord 3.x ? ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? +::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' + class Question < ActiveRecord::Base include Elasticsearch::Model diff --git a/elasticsearch-model/test/integration/active_record_associations_test.rb b/elasticsearch-model/test/integration/active_record_associations_test.rb index ca0361d89..1271f2b97 100644 --- a/elasticsearch-model/test/integration/active_record_associations_test.rb +++ b/elasticsearch-model/test/integration/active_record_associations_test.rb @@ -4,6 +4,8 @@ # Needed for ActiveRecord 3.x ? ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? +::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' + module Elasticsearch module Model class ActiveRecordAssociationsIntegrationTest < Elasticsearch::Test::IntegrationTestCase diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index fe3972301..f6015da70 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -6,6 +6,8 @@ # Needed for ActiveRecord 3.x ? ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? +::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' + module Elasticsearch module Model class ActiveRecordBasicIntegrationTest < Elasticsearch::Test::IntegrationTestCase diff --git a/elasticsearch-model/test/integration/active_record_custom_serialization_test.rb b/elasticsearch-model/test/integration/active_record_custom_serialization_test.rb index 60b1f6d32..cb706cf99 100644 --- a/elasticsearch-model/test/integration/active_record_custom_serialization_test.rb +++ b/elasticsearch-model/test/integration/active_record_custom_serialization_test.rb @@ -4,6 +4,8 @@ # Needed for ActiveRecord 3.x ? ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? +::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' + module Elasticsearch module Model class ActiveRecordCustomSerializationTest < Elasticsearch::Test::IntegrationTestCase diff --git a/elasticsearch-model/test/integration/active_record_import_test.rb b/elasticsearch-model/test/integration/active_record_import_test.rb index 31febcb2c..4b9560d3a 100644 --- a/elasticsearch-model/test/integration/active_record_import_test.rb +++ b/elasticsearch-model/test/integration/active_record_import_test.rb @@ -4,6 +4,8 @@ # Needed for ActiveRecord 3.x ? ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? +::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' + module Elasticsearch module Model class ActiveRecordImportIntegrationTest < Elasticsearch::Test::IntegrationTestCase diff --git a/elasticsearch-model/test/integration/active_record_namespaced_model_test.rb b/elasticsearch-model/test/integration/active_record_namespaced_model_test.rb index d1eb11f27..9885b3a1a 100644 --- a/elasticsearch-model/test/integration/active_record_namespaced_model_test.rb +++ b/elasticsearch-model/test/integration/active_record_namespaced_model_test.rb @@ -4,6 +4,8 @@ # Needed for ActiveRecord 3.x ? ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? +::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' + module Elasticsearch module Model class ActiveRecordNamespacedModelIntegrationTest < Elasticsearch::Test::IntegrationTestCase diff --git a/elasticsearch-model/test/integration/active_record_pagination_test.rb b/elasticsearch-model/test/integration/active_record_pagination_test.rb index e4d30b059..e2f58f7e5 100644 --- a/elasticsearch-model/test/integration/active_record_pagination_test.rb +++ b/elasticsearch-model/test/integration/active_record_pagination_test.rb @@ -4,6 +4,8 @@ # Needed for ActiveRecord 3.x ? ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? +::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' + module Elasticsearch module Model class ActiveRecordPaginationTest < Elasticsearch::Test::IntegrationTestCase diff --git a/elasticsearch-model/test/integration/dynamic_index_name_test.rb b/elasticsearch-model/test/integration/dynamic_index_name_test.rb index afecc26c0..f87db7978 100755 --- a/elasticsearch-model/test/integration/dynamic_index_name_test.rb +++ b/elasticsearch-model/test/integration/dynamic_index_name_test.rb @@ -4,6 +4,8 @@ # Needed for ActiveRecord 3.x ? ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? +::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' + module Elasticsearch module Model class DynamicIndexNameTest < Elasticsearch::Test::IntegrationTestCase diff --git a/elasticsearch-model/test/integration/multiple_models_test.rb b/elasticsearch-model/test/integration/multiple_models_test.rb index 957aca852..612c695d1 100644 --- a/elasticsearch-model/test/integration/multiple_models_test.rb +++ b/elasticsearch-model/test/integration/multiple_models_test.rb @@ -4,6 +4,8 @@ # Needed for ActiveRecord 3.x ? ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? +::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' + Mongo.setup! module Elasticsearch From e6cf1289f541275cbcf977a38e4f87a26e9574c9 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 28 Jan 2017 09:07:58 +0100 Subject: [PATCH 286/582] [MODEL] Fixed the deprecation messages for `timestamps` in migrations in integration tests --- .../examples/activerecord_associations.rb | 10 +++++----- .../active_record_associations_parent_child.rb | 4 ++-- .../integration/active_record_associations_test.rb | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/elasticsearch-model/examples/activerecord_associations.rb b/elasticsearch-model/examples/activerecord_associations.rb index 89b280c26..8e5066eaa 100644 --- a/elasticsearch-model/examples/activerecord_associations.rb +++ b/elasticsearch-model/examples/activerecord_associations.rb @@ -28,23 +28,23 @@ ActiveRecord::Schema.define(version: 1) do create_table :categories do |t| t.string :title - t.timestamps + t.timestamps null: false end create_table :authors do |t| t.string :first_name, :last_name - t.timestamps + t.timestamps null: false end create_table :authorships do |t| t.references :article t.references :author - t.timestamps + t.timestamps null: false end create_table :articles do |t| t.string :title - t.timestamps + t.timestamps null: false end create_table :articles_categories, id: false do |t| @@ -54,7 +54,7 @@ create_table :comments do |t| t.string :text t.references :article - t.timestamps + t.timestamps null: false end add_index(:comments, :article_id) unless index_exists?(:comments, :article_id) diff --git a/elasticsearch-model/test/integration/active_record_associations_parent_child.rb b/elasticsearch-model/test/integration/active_record_associations_parent_child.rb index 08d8f81df..956c80036 100644 --- a/elasticsearch-model/test/integration/active_record_associations_parent_child.rb +++ b/elasticsearch-model/test/integration/active_record_associations_parent_child.rb @@ -71,14 +71,14 @@ class ActiveRecordAssociationsParentChildIntegrationTest < Elasticsearch::Test:: t.string :title t.text :text t.string :author - t.timestamps + t.timestamps null: false end create_table :answers do |t| t.text :text t.string :author t.references :question - t.timestamps + t.timestamps null: false end add_index(:answers, :question_id) unless index_exists?(:answers, :question_id) diff --git a/elasticsearch-model/test/integration/active_record_associations_test.rb b/elasticsearch-model/test/integration/active_record_associations_test.rb index 1271f2b97..933672e25 100644 --- a/elasticsearch-model/test/integration/active_record_associations_test.rb +++ b/elasticsearch-model/test/integration/active_record_associations_test.rb @@ -70,7 +70,7 @@ def as_indexed_json(options={}) ActiveRecord::Schema.define(version: 1) do create_table :categories do |t| t.string :title - t.timestamps + t.timestamps null: false end create_table :categories_posts, id: false do |t| @@ -79,21 +79,21 @@ def as_indexed_json(options={}) create_table :authors do |t| t.string :first_name, :last_name - t.timestamps + t.timestamps null: false end create_table :authorships do |t| t.string :first_name, :last_name t.references :post t.references :author - t.timestamps + t.timestamps null: false end create_table :comments do |t| t.string :text t.string :author t.references :post - t.timestamps + t.timestamps null: false end add_index(:comments, :post_id) unless index_exists?(:comments, :post_id) @@ -102,7 +102,7 @@ def as_indexed_json(options={}) t.string :title t.text :text t.boolean :published - t.timestamps + t.timestamps null: false end end From a13dd89cba0f5ac6e64dee672591d2c14740d2b1 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 28 Jan 2017 09:14:17 +0100 Subject: [PATCH 287/582] [MODEL] Fixed the naming for the indexing integration tests --- elasticsearch-model/test/unit/indexing_test.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/elasticsearch-model/test/unit/indexing_test.rb b/elasticsearch-model/test/unit/indexing_test.rb index a52603a1e..b415f3316 100644 --- a/elasticsearch-model/test/unit/indexing_test.rb +++ b/elasticsearch-model/test/unit/indexing_test.rb @@ -423,7 +423,7 @@ def as_indexed_json(options={}) end context "Checking for index existence" do - context "the index exists" do + context "when the index exists" do should "return true" do indices = mock('indices', exists: true) client = stub('client', indices: indices) @@ -434,7 +434,7 @@ def as_indexed_json(options={}) end end - context "the index does not exists" do + context "when the index does not exists" do should "return false" do indices = mock('indices', exists: false) client = stub('client', indices: indices) @@ -445,7 +445,7 @@ def as_indexed_json(options={}) end end - context "the indices raises" do + context "when the indices API raises an error" do should "return false" do client = stub('client') client.expects(:indices).raises(StandardError) @@ -456,7 +456,7 @@ def as_indexed_json(options={}) end end - context "the indices raises" do + context "the indices.exists API raises an error" do should "return false" do indices = stub('indices') client = stub('client') From 9ec745499f9092fb3aa5923d8be215e7d11e481b Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 28 Jan 2017 09:14:54 +0100 Subject: [PATCH 288/582] [MODEL] Fixed the failing integration tests for ActiveRecord associations --- .../integration/active_record_associations_test.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/test/integration/active_record_associations_test.rb b/elasticsearch-model/test/integration/active_record_associations_test.rb index 933672e25..0648057f3 100644 --- a/elasticsearch-model/test/integration/active_record_associations_test.rb +++ b/elasticsearch-model/test/integration/active_record_associations_test.rb @@ -115,6 +115,8 @@ class Category < ActiveRecord::Base class Author < ActiveRecord::Base has_many :authorships + after_update { self.authorships.each(&:touch) } + def full_name [first_name, last_name].compact.join(' ') end @@ -133,8 +135,13 @@ class Post < ActiveRecord::Base has_and_belongs_to_many :categories, after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ], after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ] has_many :authorships - has_many :authors, through: :authorships - has_many :comments + has_many :authors, through: :authorships, + after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ], + after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ] + has_many :comments, after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ], + after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ] + + after_touch() { __elasticsearch__.index_document } end # Include the search integration From 39b1167b00dc46a2707e5852a3d611a019a33f01 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 28 Jan 2017 09:15:30 +0100 Subject: [PATCH 289/582] [MODEL] Fixed integration tests for ActiveRecord pagination --- .../test/integration/active_record_pagination_test.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/elasticsearch-model/test/integration/active_record_pagination_test.rb b/elasticsearch-model/test/integration/active_record_pagination_test.rb index e2f58f7e5..9c07ef115 100644 --- a/elasticsearch-model/test/integration/active_record_pagination_test.rb +++ b/elasticsearch-model/test/integration/active_record_pagination_test.rb @@ -92,12 +92,11 @@ class ::ArticleForPagination < ActiveRecord::Base assert_equal 0, records.size assert_equal 6, records.current_page - assert_equal 5, records.prev_page + assert_equal nil, records.next_page assert_equal 3, records.total_pages assert ! records.first_page?, "Should NOT be the first page" - assert records.last_page?, "Should be the last page" assert records.out_of_range?, "Should be out of range" end From f547cfa94eef8e654015d73451fb8a96ab7c164c Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 28 Jan 2017 11:23:30 +0100 Subject: [PATCH 290/582] [MODEL] Added the `rake bundle:install` Rake task to install dependencies for all gemfiles --- elasticsearch-model/Rakefile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index 031ca8599..25b71168e 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -4,6 +4,15 @@ desc "Run unit tests" task :default => 'test:unit' task :test => 'test:unit' +namespace :bundler do + desc "Install dependencies for all the Gemfiles" + task :install do + sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/3.0.gemfile', __FILE__)}' bundle install" + sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/4.0.gemfile', __FILE__)}' bundle install" + sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/5.0.gemfile', __FILE__)}' bundle install" + end +end + # ----- Test tasks ------------------------------------------------------------ require 'rake/testtask' From 029be4cc3577611c51612323c2a53384c72dca1f Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 28 Jan 2017 09:31:02 +0100 Subject: [PATCH 291/582] [MODEL] Run unit tests against all Gemfiles Also, reduce the verbosity of tests --- elasticsearch-model/Rakefile | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index 25b71168e..24b0a7126 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -23,12 +23,12 @@ namespace :test do Rake::Task['ci:setup:minitest'].invoke end - Rake::TestTask.new(:unit) do |test| + Rake::TestTask.new(:run_unit) do |test| Rake::Task['test:ci_reporter'].invoke if ENV['CI'] test.libs << 'lib' << 'test' test.test_files = FileList["test/unit/**/*_test.rb"] - # test.verbose = true - # test.warning = true + test.verbose = false + test.warning = false end Rake::TestTask.new(:run_integration) do |test| @@ -39,9 +39,16 @@ namespace :test do test.warning = false end + desc "Run unit tests against ActiveModel 3, 4 and 5" + task :unit do + sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/3.0.gemfile', __FILE__)}' bundle exec rake test:run_unit" + sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/4.0.gemfile', __FILE__)}' bundle exec rake test:run_unit" + sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/5.0.gemfile', __FILE__)}' bundle exec rake test:run_unit" + end + desc "Run integration tests against ActiveModel 3, 4 and 5" task :integration do - sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/3.0.gemfile', __FILE__)}' bundle exec rake test:run_integration" unless defined?(RUBY_VERSION) && RUBY_VERSION > '2.2' + sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/3.0.gemfile', __FILE__)}' bundle exec rake test:run_integration" sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/4.0.gemfile', __FILE__)}' bundle exec rake test:run_integration" sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/5.0.gemfile', __FILE__)}' bundle exec rake test:run_integration" end From db2cd1cccecea678457f90142c0d00240799fb19 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 28 Jan 2017 11:22:36 +0100 Subject: [PATCH 292/582] [MODEL] Updated dependencies in gemspec --- elasticsearch-model/elasticsearch-model.gemspec | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index 07ecc87b9..2f289b6bc 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 1.9.3" - s.add_dependency "elasticsearch", '> 0.4' + s.add_dependency "elasticsearch", '~> 1.1' s.add_dependency "activesupport", '> 3' s.add_dependency "hashie" @@ -33,25 +33,22 @@ Gem::Specification.new do |s| s.add_development_dependency "elasticsearch-extensions" s.add_development_dependency "sqlite3" - s.add_development_dependency "activemodel", "> 3.0" + s.add_development_dependency "activemodel", "> 3" s.add_development_dependency "oj" s.add_development_dependency "kaminari" s.add_development_dependency "will_paginate" - s.add_development_dependency "minitest", ">= 4.2" - s.add_development_dependency "test-unit" if defined?(RUBY_VERSION) && RUBY_VERSION > '2.2' + s.add_development_dependency "minitest" + s.add_development_dependency "test-unit" s.add_development_dependency "shoulda-context" s.add_development_dependency "mocha" s.add_development_dependency "turn" s.add_development_dependency "yard" s.add_development_dependency "ruby-prof" s.add_development_dependency "pry" - s.add_development_dependency "ci_reporter", "~> 1.9" - if defined?(RUBY_VERSION) && RUBY_VERSION > '1.9' - s.add_development_dependency "simplecov" - s.add_development_dependency "cane" - s.add_development_dependency "require-prof" - end + s.add_development_dependency "simplecov" + s.add_development_dependency "cane" + s.add_development_dependency "require-prof" end From fca8b6b4126aa01c2df10014888c8634331a5220 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 28 Jan 2017 11:23:07 +0100 Subject: [PATCH 293/582] [STORE] Updated dependencies in gemspec --- .../elasticsearch-persistence.gemspec | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index 2b7a7cbea..ab8ecc163 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -23,10 +23,10 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 1.9.3" - s.add_dependency "elasticsearch", '> 0.4' + s.add_dependency "elasticsearch", '~> 1.1' s.add_dependency "elasticsearch-model", '>= 0.1' - s.add_dependency "activesupport", '> 3' - s.add_dependency "activemodel", '> 3' + s.add_dependency "activesupport", '> 4' + s.add_dependency "activemodel", '> 4' s.add_dependency "hashie" s.add_dependency "virtus" @@ -35,22 +35,19 @@ Gem::Specification.new do |s| s.add_development_dependency "oj" - s.add_development_dependency "rails" + s.add_development_dependency "rails", '> 4' s.add_development_dependency "elasticsearch-extensions" - s.add_development_dependency "minitest", "~> 4.2" - s.add_development_dependency "test-unit" if defined?(RUBY_VERSION) && RUBY_VERSION > '2.2' + s.add_development_dependency "minitest" + s.add_development_dependency "test-unit" s.add_development_dependency "shoulda-context" s.add_development_dependency "mocha" s.add_development_dependency "turn" s.add_development_dependency "yard" s.add_development_dependency "ruby-prof" s.add_development_dependency "pry" - s.add_development_dependency "ci_reporter", "~> 1.9" - if defined?(RUBY_VERSION) && RUBY_VERSION > '1.9' - s.add_development_dependency "simplecov" - s.add_development_dependency "cane" - end + s.add_development_dependency "simplecov" + s.add_development_dependency "cane" end From eb9ef48d806380258a4579a7d6bcdbd1fca1c033 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 28 Jan 2017 11:23:22 +0100 Subject: [PATCH 294/582] [STORE] Updated dependencies in gemspec --- elasticsearch-rails/elasticsearch-rails.gemspec | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index f33c9f6b7..71b4c1abb 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -34,19 +34,16 @@ Gem::Specification.new do |s| s.add_development_dependency "lograge" - s.add_development_dependency "minitest", "~> 4.2" - s.add_development_dependency "test-unit" if defined?(RUBY_VERSION) && RUBY_VERSION > '2.2' + s.add_development_dependency "minitest" + s.add_development_dependency "test-unit" s.add_development_dependency "shoulda-context" s.add_development_dependency "mocha" s.add_development_dependency "turn" s.add_development_dependency "yard" s.add_development_dependency "ruby-prof" s.add_development_dependency "pry" - s.add_development_dependency "ci_reporter", "~> 1.9" - if defined?(RUBY_VERSION) && RUBY_VERSION > '1.9' - s.add_development_dependency "simplecov" - s.add_development_dependency "cane" - s.add_development_dependency "require-prof" - end + s.add_development_dependency "simplecov" + s.add_development_dependency "cane" + s.add_development_dependency "require-prof" end From f6c1fdb90ea564c02168f8b15d73207626cc0180 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 28 Jan 2017 09:59:45 +0100 Subject: [PATCH 295/582] Removed the "CI Reporter" integration from test Rake tasks The integration is failing, so let's first remove it and figure out later how to generate code statistics for test runs. --- Rakefile | 16 ---------------- elasticsearch-model/Rakefile | 10 ---------- elasticsearch-persistence/Rakefile | 9 --------- elasticsearch-rails/Rakefile | 9 --------- 4 files changed, 44 deletions(-) diff --git a/Rakefile b/Rakefile index 776e3004a..841b9c1b9 100644 --- a/Rakefile +++ b/Rakefile @@ -56,7 +56,6 @@ namespace :test do desc "Run unit tests in all subprojects" task :unit do - Rake::Task['test:ci_reporter'].invoke if ENV['CI'] subprojects.each do |project| puts '-'*80 sh "cd #{__current__.join(project)} && unset BUNDLE_GEMFILE && bundle exec rake test:unit" @@ -66,8 +65,6 @@ namespace :test do desc "Run integration tests in all subprojects" task :integration do - Rake::Task['test:ci_reporter'].invoke if ENV['CI'] - # 1/ elasticsearch-model # puts '-'*80 @@ -93,23 +90,10 @@ namespace :test do desc "Run all tests in all subprojects" task :all do - Rake::Task['test:ci_reporter'].invoke if ENV['CI'] - Rake::Task['test:unit'].invoke Rake::Task['test:integration'].invoke end - task :ci_reporter do - ENV['CI_REPORTS'] ||= 'tmp/reports' - if defined?(RUBY_VERSION) && RUBY_VERSION < '1.9' - require 'ci/reporter/rake/test_unit' - Rake::Task['ci:setup:testunit'].invoke - else - require 'ci/reporter/rake/minitest' - Rake::Task['ci:setup:minitest'].invoke - end - end - namespace :cluster do desc "Start Elasticsearch nodes for tests" task :start do diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index 24b0a7126..1efad46da 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -17,14 +17,7 @@ end require 'rake/testtask' namespace :test do - task :ci_reporter do - ENV['CI_REPORTS'] ||= 'tmp/reports' - require 'ci/reporter/rake/minitest' - Rake::Task['ci:setup:minitest'].invoke - end - Rake::TestTask.new(:run_unit) do |test| - Rake::Task['test:ci_reporter'].invoke if ENV['CI'] test.libs << 'lib' << 'test' test.test_files = FileList["test/unit/**/*_test.rb"] test.verbose = false @@ -32,7 +25,6 @@ namespace :test do end Rake::TestTask.new(:run_integration) do |test| - Rake::Task['test:ci_reporter'].invoke if ENV['CI'] test.libs << 'lib' << 'test' test.test_files = FileList["test/integration/**/*_test.rb"] test.verbose = false @@ -55,8 +47,6 @@ namespace :test do desc "Run unit and integration tests" task :all do - Rake::Task['test:ci_reporter'].invoke if ENV['CI'] - Rake::Task['test:unit'].invoke Rake::Task['test:integration'].invoke end diff --git a/elasticsearch-persistence/Rakefile b/elasticsearch-persistence/Rakefile index f92cfd64e..61038d08c 100644 --- a/elasticsearch-persistence/Rakefile +++ b/elasticsearch-persistence/Rakefile @@ -8,14 +8,7 @@ task :test => 'test:unit' require 'rake/testtask' namespace :test do - task :ci_reporter do - ENV['CI_REPORTS'] ||= 'tmp/reports' - require 'ci/reporter/rake/minitest' - Rake::Task['ci:setup:minitest'].invoke - end - Rake::TestTask.new(:unit) do |test| - Rake::Task['test:ci_reporter'].invoke if ENV['CI'] test.libs << 'lib' << 'test' test.test_files = FileList["test/unit/**/*_test.rb"] test.verbose = false @@ -23,7 +16,6 @@ namespace :test do end Rake::TestTask.new(:integration) do |test| - Rake::Task['test:ci_reporter'].invoke if ENV['CI'] test.libs << 'lib' << 'test' test.test_files = FileList["test/integration/**/*_test.rb"] test.verbose = false @@ -31,7 +23,6 @@ namespace :test do end Rake::TestTask.new(:all) do |test| - Rake::Task['test:ci_reporter'].invoke if ENV['CI'] test.libs << 'lib' << 'test' test.test_files = FileList["test/unit/**/*_test.rb", "test/integration/**/*_test.rb"] end diff --git a/elasticsearch-rails/Rakefile b/elasticsearch-rails/Rakefile index dfad126cb..622731c66 100644 --- a/elasticsearch-rails/Rakefile +++ b/elasticsearch-rails/Rakefile @@ -8,14 +8,7 @@ task :test => 'test:unit' require 'rake/testtask' namespace :test do - task :ci_reporter do - ENV['CI_REPORTS'] ||= 'tmp/reports' - require 'ci/reporter/rake/minitest' - Rake::Task['ci:setup:minitest'].invoke - end - Rake::TestTask.new(:unit) do |test| - Rake::Task['test:ci_reporter'].invoke if ENV['CI'] test.libs << 'lib' << 'test' test.test_files = FileList["test/unit/**/*_test.rb"] test.verbose = false @@ -23,7 +16,6 @@ namespace :test do end Rake::TestTask.new(:integration) do |test| - Rake::Task['test:ci_reporter'].invoke if ENV['CI'] test.libs << 'lib' << 'test' test.test_files = FileList["test/integration/**/*_test.rb"] test.verbose = false @@ -31,7 +23,6 @@ namespace :test do end Rake::TestTask.new(:all) do |test| - Rake::Task['test:ci_reporter'].invoke if ENV['CI'] test.libs << 'lib' << 'test' test.test_files = FileList["test/unit/**/*_test.rb", "test/integration/**/*_test.rb"] end From a4696efe94d034d03f60cb8544f6286b782f2741 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 28 Jan 2017 11:44:17 +0100 Subject: [PATCH 296/582] Changed, that the main `Gemfile.lock` is not removed in the `bundle:clean` Rake task --- Rakefile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Rakefile b/Rakefile index 841b9c1b9..64fab6ac6 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,6 @@ require 'pathname' -subprojects = %w| elasticsearch-model elasticsearch-rails elasticsearch-persistence | +subprojects = %w| elasticsearch-rails elasticsearch-persistence elasticsearch-model | __current__ = Pathname( File.expand_path('..', __FILE__) ) @@ -23,9 +23,6 @@ task :bundle => 'bundle:install' namespace :bundle do desc "Run `bundle install` in all subprojects" task :install do - puts '-'*80 - sh "bundle install --gemfile #{__current__}/Gemfile" - puts subprojects.each do |project| puts '-'*80 sh "bundle install --gemfile #{__current__.join(project)}/Gemfile" @@ -41,7 +38,6 @@ namespace :bundle do desc "Remove Gemfile.lock in all subprojects" task :clean do - sh "rm -f Gemfile.lock" subprojects.each do |project| sh "rm -f #{__current__.join(project)}/Gemfile.lock" end From 135fd285af1fc16e434462a79c1808ff7639c841 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 28 Jan 2017 17:28:23 +0100 Subject: [PATCH 297/582] Removed unnecessary gems from the main Gemfile --- Gemfile | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Gemfile b/Gemfile index 13be68981..4a7fe0473 100644 --- a/Gemfile +++ b/Gemfile @@ -3,21 +3,8 @@ source 'https://rubygems.org' gem "bundler", "~> 1" gem "rake", "~> 11.1" -gem 'elasticsearch' gem 'elasticsearch-extensions' -gem 'elasticsearch-model', :path => File.expand_path("../elasticsearch-model", __FILE__), :require => false -gem 'elasticsearch-rails', :path => File.expand_path("../elasticsearch-rails", __FILE__), :require => false - gem "pry" gem "ansi" -gem "shoulda-context" -gem "mocha" -gem "turn" -gem "yard" -gem "ci_reporter", "~> 1.9" -gem "ruby-prof" -gem "simplecov" -gem "simplecov-rcov" gem "cane" -gem "require-prof" From a063458586033a517a4268a88e1ba29882e12bb2 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 28 Jan 2017 17:56:12 +0100 Subject: [PATCH 298/582] Updated the Travis CI configuration Related: https://github.com/elastic/elasticsearch-ruby/issues/389 --- .travis.yml | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 39d626bdb..ba677f628 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,39 +2,48 @@ # Configuration file for http://travis-ci.org/elasticsearch/elasticsearch-rails # ----------------------------------------------------------------------------- +dist: trusty + +sudo: required + language: ruby +services: + - mongodb + branches: only: - master - travis -rvm: - - 1.9.3 - - 2.1 - - 2.2 +matrix: + include: + - rvm: 2.2.6 + jdk: oraclejdk8 + env: TEST_SUITE=unit -jdk: - - openjdk7 + - rvm: 2.3.3 + jdk: oraclejdk8 + env: TEST_SUITE=unit -services: - - mongodb + - rvm: 2.3.3 + jdk: oraclejdk8 + env: TEST_SUITE=integration SERVER=start TEST_CLUSTER_LOGS=/tmp/log TEST_CLUSTER_COMMAND=/tmp/elasticsearch-1.7.5/bin/elasticsearch before_install: - - gem update --system + - gem update --system --no-rdoc --no-ri - gem --version - - gem install bundler -v 1.11.2 + - gem install bundler -v 1.14.3 --no-rdoc --no-ri + - bundle version + - curl -sS https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-1.7.5.tar.gz | tar xz -C /tmp -before_script: - - ls -la /usr/share/elasticsearch/bin/elasticsearch - - elasticsearch -v +install: + - bundle install - rake bundle:clean - rake bundle:install script: - - SERVER=launch TEST_CLUSTER_COMMAND=/usr/share/elasticsearch/bin/elasticsearch TEST_CLUSTER_PARAMS='-Des.default.path.conf=/etc/elasticsearch/ -Des.default.path.logs==/var/log/elasticsearch/' bundle exec rake test:all + - rake test:$TEST_SUITE notifications: disable: true - -sudo: false From 1dc6b8e1b84828d75308abdd62cc1d55ded1453a Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sun, 29 Jan 2017 08:54:29 +0100 Subject: [PATCH 299/582] Updated the main README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added a "Development" chapter and improved the instructions * Added Travis CI and Code Climate badges --- README.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fc1275f98..67c57d11f 100644 --- a/README.md +++ b/README.md @@ -132,15 +132,26 @@ Article.create title: 'Test' * [[Documentation]](http://rubydoc.info/gems/elasticsearch-rails) * [[Test Suite]](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-rails/test) -## Running the Test Suite +## Development + +[![Build Status](https://travis-ci.org/elastic/elasticsearch-ruby.svg?branch=master)](https://travis-ci.org/elastic/elasticsearch-ruby) [![Code Climate](https://codeclimate.com/github/elastic/elasticsearch-ruby/badges/gpa.svg)](https://codeclimate.com/github/elastic/elasticsearch-ruby) + +To work on the code, clone the repository and install all dependencies first: + +``` +git clone https://github.com/elastic/elasticsearch-rails.git +cd elasticsearch-rails/ +bundle install +rake bundle:install +``` + +### Running the Test Suite You can run unit and integration tests for each sub-project by running the respective Rake tasks in their folders. -You can also unit, integration, or both tests in the top level directory for each sub-project: +You can also unit, integration, or both tests for all sub-projects from the top-level directory: - rake bundle:clean - rake bundle:install - bundle exec rake test:all + rake test:all The test suite expects an Elasticsearch cluster running on port 9250, and **will delete all the data**. You can launch an isolated, in-memory Elasticsearch cluster with the following Rake task: From 915556c8d1187f1e897b3e8544c40a719a79ccaf Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 3 Feb 2017 14:32:59 +0100 Subject: [PATCH 300/582] [MODEL] Relaxed the dependency on the "elasticsearch" gem Related: #665 --- elasticsearch-model/elasticsearch-model.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index 2f289b6bc..e058ac43d 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 1.9.3" - s.add_dependency "elasticsearch", '~> 1.1' + s.add_dependency "elasticsearch", '> 1' s.add_dependency "activesupport", '> 3' s.add_dependency "hashie" From 69a561bcb4892f5c4aedd6f4838687e422615ee4 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 3 Feb 2017 14:33:33 +0100 Subject: [PATCH 301/582] [STORE] Relaxed the dependency on the "elasticsearch" gem Related: #665 --- elasticsearch-persistence/elasticsearch-persistence.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index ab8ecc163..510da3a3e 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 1.9.3" - s.add_dependency "elasticsearch", '~> 1.1' + s.add_dependency "elasticsearch", '> 1' s.add_dependency "elasticsearch-model", '>= 0.1' s.add_dependency "activesupport", '> 4' s.add_dependency "activemodel", '> 4' From 5c692b74f8cd5d1fb1bbca4670d726bbf850ef4b Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 11 Feb 2017 09:40:20 +0100 Subject: [PATCH 302/582] [RAILS] Updated the application templates to support Rails 5 & Elasticsearch 5 This patch adds support for both Rails 5 and Elasticsearch 5 for the application templates in `elasticsearch-rails/lib/rails/templates/`. The main problems on the Elasticsearch side have been in incorrect mapping, which still used the `multi_field` property type. The mapping definition in the `searchable.rb` and `searchable.dsl.rb` has been fixed, along with small fixes in HTML views an the `SearchController`. On the Rails side, the `quiet_assets` gem has been removed, as originally reported by @frontandstart in #634, and various fixes in the HTML views (using `params.permit`, ...) and in the controller test (using the new syntax for passing parameters) have been done. The templates are still compatible with Rails 4. Closes #655 --- .../lib/rails/templates/01-basic.rb | 18 +++++----- .../lib/rails/templates/02-pretty.rb | 4 +-- .../lib/rails/templates/03-expert.rb | 36 +++++-------------- .../lib/rails/templates/04-dsl.rb | 12 +++---- .../lib/rails/templates/05-settings-files.rb | 6 ---- .../lib/rails/templates/index.html.dsl.erb | 18 +++++----- .../lib/rails/templates/index.html.erb | 24 ++++++------- .../templates/search_controller_test.dsl.rb | 23 ++++++------ .../rails/templates/search_controller_test.rb | 22 ++++++------ .../lib/rails/templates/searchable.dsl.rb | 18 +++++----- .../lib/rails/templates/searchable.rb | 21 +++++------ 11 files changed, 89 insertions(+), 113 deletions(-) diff --git a/elasticsearch-rails/lib/rails/templates/01-basic.rb b/elasticsearch-rails/lib/rails/templates/01-basic.rb index 129d5ffd7..eb06e167b 100644 --- a/elasticsearch-rails/lib/rails/templates/01-basic.rb +++ b/elasticsearch-rails/lib/rails/templates/01-basic.rb @@ -41,7 +41,9 @@ # ----- Download Elasticsearch -------------------------------------------------------------------- -unless (Net::HTTP.get(URI.parse('http://localhost:9200')) rescue false) +ELASTICSEARCH_URL = ENV.fetch('ELASTICSEARCH_URL', 'http://localhost:9200') + +unless (Net::HTTP.get(URI.parse(ELASTICSEARCH_URL)) rescue false) COMMAND = <<-COMMAND.gsub(/^ /, '') curl -# -O "http://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.0.1.tar.gz" tar -zxf elasticsearch-1.0.1.tar.gz @@ -116,6 +118,7 @@ # ----- Auxiliary gems ---------------------------------------------------------------------------- gem 'mocha', group: 'test', require: 'mocha/api' +gem 'rails-controller-testing', group: 'test' # ----- Remove CoffeeScript, Sass and "all that jazz" --------------------------------------------- @@ -145,9 +148,8 @@ puts '-'*80, ''; sleep 0.25 environment 'config.assets.logger = false', env: 'development' -gem 'quiet_assets', group: "development" +environment 'config.assets.quiet = true', env: 'development' -git add: "Gemfile*" git add: "config/" git commit: "-m 'Disabled asset logging in development'" @@ -208,7 +210,7 @@ def search CODE end -inject_into_file 'app/views/articles/index.html.erb', after: %r{<h1>Listing articles</h1>}i do +inject_into_file 'app/views/articles/index.html.erb', after: %r{<h1>.*Articles</h1>}i do <<-CODE @@ -236,21 +238,21 @@ def search end CODE -gsub_file "#{Rails::VERSION::STRING > '4' ? 'test/controllers' : 'test/functional'}/articles_controller_test.rb", %r{setup do.*?end}m, <<-CODE +gsub_file "test/controllers/articles_controller_test.rb", %r{setup do.*?end}m, <<-CODE setup do @article = articles(:one) - Article.__elasticsearch__.import + Article.__elasticsearch__.import force: true Article.__elasticsearch__.refresh_index! end CODE -inject_into_file "#{Rails::VERSION::STRING > '4' ? 'test/controllers' : 'test/functional'}/articles_controller_test.rb", after: %r{test "should get index" do.*?end}m do +inject_into_file "test/controllers/articles_controller_test.rb", after: %r{test "should get index" do.*?end}m do <<-CODE test "should get search results" do - get :search, q: 'mystring' + #{ Rails::VERSION::STRING > '5' ? 'get search_articles_url(q: "mystring")' : 'get :search, q: "mystring"' } assert_response :success assert_not_nil assigns(:articles) assert_equal 2, assigns(:articles).size diff --git a/elasticsearch-rails/lib/rails/templates/02-pretty.rb b/elasticsearch-rails/lib/rails/templates/02-pretty.rb index 7fd6e5048..ef588247c 100644 --- a/elasticsearch-rails/lib/rails/templates/02-pretty.rb +++ b/elasticsearch-rails/lib/rails/templates/02-pretty.rb @@ -154,8 +154,8 @@ def self.search(query) # ----- Customize the header ----------------------------------------------------------------- -gsub_file 'app/views/articles/index.html.erb', %r{<h1>Listing articles</h1>} do |match| - "<h1><%= controller.action_name == 'search' ? 'Searching articles' : 'Listing articles' %></h1>" +gsub_file 'app/views/articles/index.html.erb', %r{<h1>.*Articles</h1>} do |match| + "<h1><%= controller.action_name == 'search' ? 'Search results' : 'Articles' %></h1>" end # ----- Customize the results listing ------------------------------------------------------------- diff --git a/elasticsearch-rails/lib/rails/templates/03-expert.rb b/elasticsearch-rails/lib/rails/templates/03-expert.rb index ec098a420..59b2b0d5f 100644 --- a/elasticsearch-rails/lib/rails/templates/03-expert.rb +++ b/elasticsearch-rails/lib/rails/templates/03-expert.rb @@ -77,19 +77,6 @@ git add: "config/" git commit: "-m 'Added Pry as the console for development'" -# ----- Disable asset logging in development ------------------------------------------------------ - -puts -say_status "Application", "Disabling asset logging in development...\n", :yellow -puts '-'*80, ''; sleep 0.25 - -environment 'config.assets.logger = false', env: 'development' -gem 'quiet_assets', group: "development" - -git add: "Gemfile*" -git add: "config/" -git commit: "-m 'Disabled asset logging in development'" - # ----- Run bundle install ------------------------------------------------------------------------ run "bundle install" @@ -173,11 +160,10 @@ class Article < ActiveRecord::Base end CODE -gsub_file "#{Rails::VERSION::STRING > '4' ? 'test/models' : 'test/unit' }/article_test.rb", %r{assert_equal 'foo', definition\[:query\]\[:multi_match\]\[:query\]}, "assert_equal 'foo', definition.to_hash[:query][:bool][:should][0][:multi_match][:query]" +gsub_file "test/models/article_test.rb", %r{assert_equal 'foo', definition\[:query\]\[:multi_match\]\[:query\]}, "assert_equal 'foo', definition.to_hash[:query][:bool][:should][0][:multi_match][:query]" # copy_file File.expand_path('../searchable.rb', __FILE__), 'app/models/concerns/searchable.rb' -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/searchable.rb', - 'app/models/concerns/searchable.rb' +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/searchable.rb', 'app/models/concerns/searchable.rb' insert_into_file "app/models/article.rb", after: "ActiveRecord::Base" do <<-CODE @@ -205,8 +191,7 @@ class Article < ActiveRecord::Base run "bundle install" # copy_file File.expand_path('../indexer.rb', __FILE__), 'app/workers/indexer.rb' -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/indexer.rb', - 'app/workers/indexer.rb' +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/indexer.rb', 'app/workers/indexer.rb' insert_into_file "test/test_helper.rb", "require 'sidekiq/testing'\n\n", @@ -241,19 +226,16 @@ def index end # copy_file File.expand_path('../search_controller_test.rb', __FILE__), 'test/controllers/search_controller_test.rb' -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/search_controller_test.rb', - 'test/controllers/search_controller_test.rb' +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/search_controller_test.rb', 'test/controllers/search_controller_test.rb' route "get '/search', to: 'search#index', as: 'search'" gsub_file 'config/routes.rb', %r{root to: 'articles#index'$}, "root to: 'search#index'" # copy_file File.expand_path('../index.html.erb', __FILE__), 'app/views/search/index.html.erb' -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/index.html.erb', - 'app/views/search/index.html.erb' +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/index.html.erb', 'app/views/search/index.html.erb' # copy_file File.expand_path('../search.css', __FILE__), 'app/assets/stylesheets/search.css' -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/search.css', - 'app/assets/stylesheets/search.css' +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/search.css', 'app/assets/stylesheets/search.css' git add: "app/controllers/ test/controllers/ config/routes.rb" git add: "app/views/search/ app/assets/stylesheets/search.css" @@ -304,13 +286,11 @@ def index puts '-'*80, ''; sleep 0.25 # copy_file File.expand_path('../articles.yml.gz', __FILE__), 'db/articles.yml.gz' -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/articles.yml.gz', - 'db/articles.yml.gz' +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/articles.yml.gz', 'db/articles.yml.gz' remove_file 'db/seeds.rb' # copy_file File.expand_path('../seeds.rb', __FILE__), 'db/seeds.rb' -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/seeds.rb', - 'db/seeds.rb' +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/seeds.rb', 'db/seeds.rb' rake "db:reset" rake "environment elasticsearch:import:model CLASS='Article' BATCH=100 FORCE=y" diff --git a/elasticsearch-rails/lib/rails/templates/04-dsl.rb b/elasticsearch-rails/lib/rails/templates/04-dsl.rb index 232903e33..81a390a1f 100644 --- a/elasticsearch-rails/lib/rails/templates/04-dsl.rb +++ b/elasticsearch-rails/lib/rails/templates/04-dsl.rb @@ -39,16 +39,14 @@ # ----- Change the search definition implementation and associated views and tests ---------------- # copy_file File.expand_path('../searchable.dsl.rb', __FILE__), 'app/models/concerns/searchable.rb', force: true -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb', - 'app/models/concerns/searchable.rb', force: true +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb', 'app/models/concerns/searchable.rb', force: true # copy_file File.expand_path('../index.html.dsl.erb', __FILE__), 'app/views/search/index.html.erb', force: true -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb', - 'app/views/search/index.html.erb', force: true +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb', 'app/views/search/index.html.erb', force: true gsub_file "test/controllers/search_controller_test.rb", %r{test "should return facets" do.*?end}m, <<-CODE test "should return aggregations" do - get :index, q: 'one' + get :index, params: { q: 'one' } assert_response :success aggregations = assigns(:articles).response.response['aggregations'] @@ -65,7 +63,7 @@ gsub_file "test/controllers/search_controller_test.rb", %r{test "should filter search results and the author and published date facets when user selects a category" do.*?end}m, <<-CODE test "should filter search results and the author and published date facets when user selects a category" do - get :index, q: 'one', c: 'One' + get :index, params: { q: 'one', c: 'One' } assert_response :success assert_equal 2, assigns(:articles).size @@ -82,7 +80,7 @@ gsub_file "test/controllers/search_controller_test.rb", %r{test "should filter search results and the category and published date facets when user selects a category" do.*?end}m, <<-CODE test "should filter search results and the category and published date facets when user selects a category" do - get :index, q: 'one', a: 'Mary Smith' + get :index, params: { q: 'one', a: 'Mary Smith' } assert_response :success assert_equal 1, assigns(:articles).size diff --git a/elasticsearch-rails/lib/rails/templates/05-settings-files.rb b/elasticsearch-rails/lib/rails/templates/05-settings-files.rb index 9717699fa..803e2acb9 100644 --- a/elasticsearch-rails/lib/rails/templates/05-settings-files.rb +++ b/elasticsearch-rails/lib/rails/templates/05-settings-files.rb @@ -32,12 +32,6 @@ git add: "config/elasticsearch/articles_settings.json" git commit: "-m 'Create the articles settings file'" -# ----- Temporarily set local repo for testing ---------------------------------------------------- - -gsub_file "Gemfile", - %r{gem 'elasticsearch-model', git: 'git://github.com/elasticsearch/elasticsearch-rails.git'}, - "gem 'elasticsearch-model', path: File.expand_path('../../../../../../elasticsearch-model', __FILE__)" - # ----- Run bundle install ------------------------------------------------------------------------ run "bundle install" diff --git a/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb b/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb index 4f8d512b0..64cfb0916 100644 --- a/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb +++ b/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb @@ -37,8 +37,8 @@ sorted by <%= sort.humanize.downcase %> <span class="caret"></span> </button> <ul class="dropdown-menu" role="menu"> - <li><%= link_to "Sort by published on", search_path(params.except(:controller, :action).merge(s: 'published_on')), class: 'btn-xs' %></li> - <li><%= link_to "Sort by relevancy", search_path(params.except(:controller, :action).merge(s: nil)), class: 'btn-xs' %></li> + <li><%= link_to "Sort by published on", search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(s: 'published_on')), class: 'btn-xs' %></li> + <li><%= link_to "Sort by relevancy", search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(s: nil)), class: 'btn-xs' %></li> </ul> </div> </div> @@ -54,7 +54,7 @@ <% if @articles.response.suggestions.terms.present? %> Maybe you mean <%= @articles.response.suggestions.terms.map do |term| - link_to term, search_path(params.except(:controller, :action).merge q: term) + link_to term, search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge q: term) end.to_sentence(last_word_connector: ' or ').html_safe %>? <% end %> </p> @@ -65,12 +65,12 @@ <% unless @articles.size < 1 %> <div class="categories panel panel-default"> - <p class="panel-heading"><%= link_to 'All Sections →'.html_safe, search_path(params.except(:controller, :action).merge(c: nil))%></p> + <p class="panel-heading"><%= link_to 'All Sections →'.html_safe, search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(c: nil))%></p> <div class="list-group"> <% @articles.response.response['aggregations']['categories']['categories']['buckets'].each do |c| %> <%= - link_to search_path(params.except(:controller, :action).merge(c: c['key'])), + link_to search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(c: c['key'])), class: "list-group-item#{' active' if params[:c] == c['key']}" do c['key'].titleize.html_safe + content_tag(:small, c['doc_count'], class: 'badge').html_safe end @@ -80,12 +80,12 @@ </div> <div class="authors panel panel-default"> - <p class="panel-heading"><%= link_to 'All Authors →'.html_safe, search_path(params.except(:controller, :action).merge(a: nil))%></p> + <p class="panel-heading"><%= link_to 'All Authors →'.html_safe, search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(a: nil))%></p> <div class="list-group"> <% @articles.response.response['aggregations']['authors']['authors']['buckets'].each do |a| %> <%= - link_to search_path(params.except(:controller, :action).merge(a: a['key'])), + link_to search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(a: a['key'])), class: "list-group-item#{' active' if params[:a] == a['key']}" do a['key'].titleize.html_safe + content_tag(:small, a['doc_count'], class: 'badge').html_safe end @@ -95,7 +95,7 @@ </div> <div class="authors panel panel-default"> - <p class="panel-heading"><%= link_to 'Any Date →'.html_safe, search_path(params.except(:controller, :action).merge(w: nil))%></p> + <p class="panel-heading"><%= link_to 'Any Date →'.html_safe, search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(w: nil))%></p> <div class="list-group"> <% @articles.response.response['aggregations']['published']['published']['buckets'].each do |w| %> @@ -104,7 +104,7 @@ __end = __start.end_of_week __date = __start.to_date.to_s(:iso) - link_to search_path(params.except(:controller, :action).merge(w: __date)), + link_to search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(w: __date)), class: "list-group-item#{' active' if params[:w] == __date}" do "#{__start.to_date.to_s(:short)} — #{__end.to_date.to_s(:short)}".html_safe + \ content_tag(:small, w['doc_count'], class: 'badge').html_safe diff --git a/elasticsearch-rails/lib/rails/templates/index.html.erb b/elasticsearch-rails/lib/rails/templates/index.html.erb index 9d849ef04..0d0f1cc68 100644 --- a/elasticsearch-rails/lib/rails/templates/index.html.erb +++ b/elasticsearch-rails/lib/rails/templates/index.html.erb @@ -37,8 +37,8 @@ sorted by <%= sort.humanize.downcase %> <span class="caret"></span> </button> <ul class="dropdown-menu" role="menu"> - <li><%= link_to "Sort by published on", search_path(params.except(:controller, :action).merge(s: 'published_on')), class: 'btn-xs' %></li> - <li><%= link_to "Sort by relevancy", search_path(params.except(:controller, :action).merge(s: nil)), class: 'btn-xs' %></li> + <li><%= link_to "Sort by published on", search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(s: 'published_on')), class: 'btn-xs' %></li> + <li><%= link_to "Sort by relevancy", search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(s: nil)), class: 'btn-xs' %></li> </ul> </div> </div> @@ -47,14 +47,14 @@ <hr> </div> -<% if @articles.size < 1 && @articles.response.suggest.present? %> +<% if @articles.size < 1 && @articles.response.suggestions.present? %> <div class="col-md-12"> <p class="alert alert-warning"> No documents have been found. - <% if @articles.response.suggest['suggest_title'].present? || @articles.response.suggest['suggest_body'].present? %> + <% if @articles.response.suggestions['suggest_title'].present? || @articles.response.suggestions['suggest_body'].present? %> Maybe you mean - <%= @articles.response.suggest.map { |k,v| v.first['options'] }.flatten.map {|v| v['text']}.uniq.map do |term| - link_to term, search_path(params.except(:controller, :action).merge q: term) + <%= @articles.response.suggestions.map { |k,v| v.first['options'] }.flatten.map {|v| v['text']}.uniq.map do |term| + link_to term, search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge q: term) end.to_sentence(last_word_connector: ' or ').html_safe %>? <% end %> </p> @@ -65,12 +65,12 @@ <% unless @articles.size < 1 %> <div class="categories panel panel-default"> - <p class="panel-heading"><%= link_to 'All Sections →'.html_safe, search_path(params.except(:controller, :action).merge(c: nil))%></p> + <p class="panel-heading"><%= link_to 'All Sections →'.html_safe, search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(c: nil))%></p> <div class="list-group"> <% @articles.response.response['aggregations']['categories']['categories']['buckets'].each do |c| %> <%= - link_to search_path(params.except(:controller, :action).merge(c: c['key'])), + link_to search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(c: c['key'])), class: "list-group-item#{' active' if params[:c] == c['key']}" do c['key'].titleize.html_safe + content_tag(:small, c['doc_count'], class: 'badge').html_safe end @@ -80,12 +80,12 @@ </div> <div class="authors panel panel-default"> - <p class="panel-heading"><%= link_to 'All Authors →'.html_safe, search_path(params.except(:controller, :action).merge(a: nil))%></p> + <p class="panel-heading"><%= link_to 'All Authors →'.html_safe, search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(a: nil))%></p> <div class="list-group"> <% @articles.response.response['aggregations']['authors']['authors']['buckets'].each do |a| %> <%= - link_to search_path(params.except(:controller, :action).merge(a: a['key'])), + link_to search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(a: a['key'])), class: "list-group-item#{' active' if params[:a] == a['key']}" do a['key'].titleize.html_safe + content_tag(:small, a['doc_count'], class: 'badge').html_safe end @@ -95,7 +95,7 @@ </div> <div class="authors panel panel-default"> - <p class="panel-heading"><%= link_to 'Any Date →'.html_safe, search_path(params.except(:controller, :action).merge(w: nil))%></p> + <p class="panel-heading"><%= link_to 'Any Date →'.html_safe, search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(w: nil))%></p> <div class="list-group"> <% @articles.response.response['aggregations']['published']['published']['buckets'].each do |w| %> @@ -104,7 +104,7 @@ __end = __start.end_of_week __date = __start.to_date.to_s(:iso) - link_to search_path(params.except(:controller, :action).merge(w: __date)), + link_to search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(w: __date)), class: "list-group-item#{' active' if params[:w] == __date}" do "#{__start.to_date.to_s(:short)} — #{__end.to_date.to_s(:short)}".html_safe + \ content_tag(:small, w['doc_count'], class: 'badge').html_safe diff --git a/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb b/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb index 7fe7dd955..fe2da30d5 100644 --- a/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb +++ b/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb @@ -1,8 +1,9 @@ require 'test_helper' +require 'sidekiq/api' class SearchControllerTest < ActionController::TestCase setup do - Time.stubs(:now).returns(Time.parse('2015-03-16 10:00:00 UTC')) + Time.stubs(:now).returns(Time.new(2015, 03, 16, 10, 00, 00, 0)) Article.delete_all @@ -37,34 +38,34 @@ class SearchControllerTest < ActionController::TestCase end test "should return search results" do - get :index, q: 'one' + get :index, params: { q: 'one' } assert_response :success assert_equal 3, assigns(:articles).size end test "should return search results in comments" do - get :index, q: 'one', comments: 'y' + get :index, params: { q: 'one', comments: 'y' } assert_response :success assert_equal 4, assigns(:articles).size end test "should return highlighted snippets" do - get :index, q: 'one' + get :index, params: { q: 'one' } assert_response :success assert_match %r{<em class="label label-highlight">One</em>}, assigns(:articles).first.highlight.title.first end test "should return suggestions" do - get :index, q: 'one' + get :index, params: { q: 'one' } assert_response :success - suggestions = assigns(:articles).response.suggest + suggestions = assigns(:articles).response.suggestions assert_equal 'one', suggestions['suggest_title'][0]['text'] end test "should return aggregations" do - get :index, q: 'one' + get :index, params: { q: 'one' } assert_response :success aggregations = assigns(:articles).response.response['aggregations'] @@ -79,7 +80,7 @@ class SearchControllerTest < ActionController::TestCase end test "should sort on the published date" do - get :index, q: 'one', s: 'published_on' + get :index, params: { q: 'one', s: 'published_on' } assert_response :success assert_equal 3, assigns(:articles).size @@ -89,7 +90,7 @@ class SearchControllerTest < ActionController::TestCase end test "should sort on the published date when no query is provided" do - get :index, q: '' + get :index, params: { q: '' } assert_response :success assert_equal 5, assigns(:articles).size @@ -99,7 +100,7 @@ class SearchControllerTest < ActionController::TestCase end test "should filter search results and the author and published date facets when user selects a category" do - get :index, q: 'one', c: 'One' + get :index, params: { q: 'one', c: 'One' } assert_response :success assert_equal 2, assigns(:articles).size @@ -114,7 +115,7 @@ class SearchControllerTest < ActionController::TestCase end test "should filter search results and the category and published date facets when user selects a category" do - get :index, q: 'one', a: 'Mary Smith' + get :index, params: { q: 'one', a: 'Mary Smith' } assert_response :success assert_equal 1, assigns(:articles).size diff --git a/elasticsearch-rails/lib/rails/templates/search_controller_test.rb b/elasticsearch-rails/lib/rails/templates/search_controller_test.rb index 472d35d75..efdc1c2ca 100644 --- a/elasticsearch-rails/lib/rails/templates/search_controller_test.rb +++ b/elasticsearch-rails/lib/rails/templates/search_controller_test.rb @@ -2,7 +2,7 @@ class SearchControllerTest < ActionController::TestCase setup do - Time.stubs(:now).returns(Time.parse('2015-03-16 10:00:00 UTC')) + Time.stubs(:now).returns(Time.new(2015, 03, 16, 10, 00, 00, 0)) Article.delete_all @@ -37,35 +37,35 @@ class SearchControllerTest < ActionController::TestCase end test "should return search results" do - get :index, q: 'one' + get :index, params: { q: 'one' } assert_response :success assert_equal 3, assigns(:articles).size end test "should return search results in comments" do - get :index, q: 'one', comments: 'y' + get :index, params: { q: 'one', comments: 'y' } assert_response :success assert_equal 4, assigns(:articles).size end test "should return highlighted snippets" do - get :index, q: 'one' + get :index, params: { q: 'one' } assert_response :success assert_match %r{<em class="label label-highlight">One</em>}, assigns(:articles).first.highlight.title.first end test "should return suggestions" do - get :index, q: 'one' + get :index, params: { q: 'one' } assert_response :success - suggestions = assigns(:articles).response.suggest + suggestions = assigns(:articles).response.suggestions assert_equal 'one', suggestions['suggest_title'][0]['text'] end test "should return facets" do - get :index, q: 'one' + get :index, params: { q: 'one' } assert_response :success aggregations = assigns(:articles).response.response['aggregations'] @@ -80,7 +80,7 @@ class SearchControllerTest < ActionController::TestCase end test "should sort on the published date" do - get :index, q: 'one', s: 'published_on' + get :index, params: { q: 'one', s: 'published_on' } assert_response :success assert_equal 3, assigns(:articles).size @@ -90,7 +90,7 @@ class SearchControllerTest < ActionController::TestCase end test "should sort on the published date when no query is provided" do - get :index, q: '' + get :index, params: { q: '' } assert_response :success assert_equal 5, assigns(:articles).size @@ -100,7 +100,7 @@ class SearchControllerTest < ActionController::TestCase end test "should filter search results and the author and published date facets when user selects a category" do - get :index, q: 'one', c: 'One' + get :index, params: { q: 'one', c: 'One' } assert_response :success assert_equal 2, assigns(:articles).size @@ -115,7 +115,7 @@ class SearchControllerTest < ActionController::TestCase end test "should filter search results and the category and published date facets when user selects a category" do - get :index, q: 'one', a: 'Mary Smith' + get :index, params: { q: 'one', a: 'Mary Smith' } assert_response :success assert_equal 1, assigns(:articles).size diff --git a/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb b/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb index e58510469..9ba6a0bb0 100644 --- a/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb +++ b/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb @@ -12,12 +12,12 @@ module Searchable # settings index: { number_of_shards: 1, number_of_replicas: 0 } do mapping do - indexes :title, type: 'multi_field' do + indexes :title, type: 'text' do indexes :title, analyzer: 'snowball' indexes :tokenized, analyzer: 'simple' end - indexes :content, type: 'multi_field' do + indexes :content, type: 'text' do indexes :content, analyzer: 'snowball' indexes :tokenized, analyzer: 'simple' end @@ -25,22 +25,22 @@ module Searchable indexes :published_on, type: 'date' indexes :authors do - indexes :full_name, type: 'multi_field' do + indexes :full_name, type: 'text' do indexes :full_name - indexes :raw, analyzer: 'keyword' + indexes :raw, type: 'keyword' end end - indexes :categories, analyzer: 'keyword' + indexes :categories, type: 'keyword' indexes :comments, type: 'nested' do indexes :body, analyzer: 'snowball' indexes :stars indexes :pick - indexes :user, analyzer: 'keyword' - indexes :user_location, type: 'multi_field' do + indexes :user, type: 'keyword' + indexes :user_location, type: 'text' do indexes :user_location - indexes :raw, analyzer: 'keyword' + indexes :raw, type: 'keyword' end end end @@ -98,7 +98,7 @@ def self.search(q, options={}) query do multi_match do query q - fields 'body' + fields 'comments.body' operator 'and' end end diff --git a/elasticsearch-rails/lib/rails/templates/searchable.rb b/elasticsearch-rails/lib/rails/templates/searchable.rb index 55b031532..e03dad123 100644 --- a/elasticsearch-rails/lib/rails/templates/searchable.rb +++ b/elasticsearch-rails/lib/rails/templates/searchable.rb @@ -12,12 +12,12 @@ module Searchable # settings index: { number_of_shards: 1, number_of_replicas: 0 } do mapping do - indexes :title, type: 'multi_field' do + indexes :title, type: 'text' do indexes :title, analyzer: 'snowball' indexes :tokenized, analyzer: 'simple' end - indexes :content, type: 'multi_field' do + indexes :content, type: 'text' do indexes :content, analyzer: 'snowball' indexes :tokenized, analyzer: 'simple' end @@ -25,22 +25,22 @@ module Searchable indexes :published_on, type: 'date' indexes :authors do - indexes :full_name, type: 'multi_field' do + indexes :full_name, type: 'text' do indexes :full_name - indexes :raw, analyzer: 'keyword' + indexes :raw, type: 'keyword' end end - indexes :categories, analyzer: 'keyword' + indexes :categories, type: 'keyword' indexes :comments, type: 'nested' do indexes :body, analyzer: 'snowball' indexes :stars indexes :pick - indexes :user, analyzer: 'keyword' - indexes :user_location, type: 'multi_field' do + indexes :user, type: 'keyword' + indexes :user_location, type: 'text' do indexes :user_location - indexes :raw, analyzer: 'keyword' + indexes :raw, type: 'keyword' end end end @@ -74,8 +74,9 @@ def self.search(query, options={}) # Prefill and set the filters (top-level `post_filter` and aggregation `filter` elements) # __set_filters = lambda do |key, f| - @search_definition[:post_filter][:and] ||= [] - @search_definition[:post_filter][:and] |= [f] + @search_definition[:post_filter][:bool] ||= {} + @search_definition[:post_filter][:bool][:must] ||= [] + @search_definition[:post_filter][:bool][:must] |= [f] @search_definition[:aggregations][key.to_sym][:filter][:bool][:must] ||= [] @search_definition[:aggregations][key.to_sym][:filter][:bool][:must] |= [f] From 9c79376c525225ba8e85b51ac11f1bfee278728c Mon Sep 17 00:00:00 2001 From: Justin Smestad <justin.smestad@gmail.com> Date: Tue, 7 Feb 2017 17:17:08 -0700 Subject: [PATCH 303/582] [RAILS] Updated the `03-expert` application template to work with Rails 5 Latest Rails versions do not have a README.rdoc, use README.md Closes #668 --- elasticsearch-rails/lib/rails/templates/03-expert.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/elasticsearch-rails/lib/rails/templates/03-expert.rb b/elasticsearch-rails/lib/rails/templates/03-expert.rb index 59b2b0d5f..d746a0b9a 100644 --- a/elasticsearch-rails/lib/rails/templates/03-expert.rb +++ b/elasticsearch-rails/lib/rails/templates/03-expert.rb @@ -1,6 +1,6 @@ # $ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/03-expert.rb -unless File.read('README.rdoc').include? '== [2] Pretty' +unless File.read('README.md').include? '== [2] Pretty' say_status "ERROR", "You have to run the 01-basic.rb and 02-pretty.rb templates first.", :red exit(1) end @@ -21,9 +21,9 @@ exit(1) end -append_to_file 'README.rdoc', <<-README +append_to_file 'README.md', <<-README -== [3] Expert +## [3] Expert The `expert` template changes to a complex database schema with model relationships: article belongs to a category, has many authors and comments. @@ -39,7 +39,7 @@ README -git add: "README.rdoc" +git add: "README.md" git commit: "-m '[03] Updated the application README'" # ----- Add gems into Gemfile --------------------------------------------------------------------- From ed0ee82ce411a718991f65821a2b3f4556fd4843 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 11 Feb 2017 10:19:05 +0100 Subject: [PATCH 304/582] [RAILS] Updated the application templates to work with README.md instead of README.rdoc Current Rails version uses `README.md` instead of the old `.rdoc` file, and the templates have not been updated to reflect that. This patch changes both the logic to insert content into the README file and the check whether a previous template has been already run. Related: #668 --- elasticsearch-rails/lib/rails/templates/01-basic.rb | 8 ++++---- elasticsearch-rails/lib/rails/templates/02-pretty.rb | 8 ++++---- elasticsearch-rails/lib/rails/templates/03-expert.rb | 2 +- elasticsearch-rails/lib/rails/templates/04-dsl.rb | 8 ++++---- .../lib/rails/templates/05-settings-files.rb | 6 +++--- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/elasticsearch-rails/lib/rails/templates/01-basic.rb b/elasticsearch-rails/lib/rails/templates/01-basic.rb index eb06e167b..593c35174 100644 --- a/elasticsearch-rails/lib/rails/templates/01-basic.rb +++ b/elasticsearch-rails/lib/rails/templates/01-basic.rb @@ -80,10 +80,10 @@ say_status "README", "Adding Readme...\n", :yellow puts '-'*80, ''; sleep 0.25 -remove_file 'README.rdoc' +remove_file 'README.md' -create_file 'README.rdoc', <<-README -= Ruby on Rails and Elasticsearch: Example application +create_file 'README.md', <<-README +# Ruby on Rails and Elasticsearch: Example application This application is an example of integrating the {Elasticsearch}[http://www.elasticsearch.org] search engine with the {Ruby On Rails}[http://rubyonrails.org] web framework. @@ -91,7 +91,7 @@ It has been generated by application templates available at https://github.com/elasticsearch/elasticsearch-rails/tree/master/elasticsearch-rails/lib/rails/templates. -== [1] Basic +## [1] Basic The `basic` version provides a simple integration for a simple Rails model, `Article`, showing how to include the search engine support in your model, automatically index changes to records, diff --git a/elasticsearch-rails/lib/rails/templates/02-pretty.rb b/elasticsearch-rails/lib/rails/templates/02-pretty.rb index ef588247c..8902d756f 100644 --- a/elasticsearch-rails/lib/rails/templates/02-pretty.rb +++ b/elasticsearch-rails/lib/rails/templates/02-pretty.rb @@ -1,6 +1,6 @@ # $ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/02-pretty.rb -unless File.read('README.rdoc').include? '== [1] Basic' +unless File.read('README.md').include? '## [1] Basic' say_status "ERROR", "You have to run the 01-basic.rb template first.", :red exit(1) end @@ -9,9 +9,9 @@ say_status "README", "Updating Readme...\n", :yellow puts '-'*80, ''; sleep 0.25 -append_to_file 'README.rdoc', <<-README +append_to_file 'README.md', <<-README -== [2] Pretty +## [2] Pretty The `pretty` template builds on the `basic` version and brings couple of improvements: @@ -22,7 +22,7 @@ README -git add: "README.rdoc" +git add: "README.md" git commit: "-m '[02] Updated the application README'" # ----- Update application.rb --------------------------------------------------------------------- diff --git a/elasticsearch-rails/lib/rails/templates/03-expert.rb b/elasticsearch-rails/lib/rails/templates/03-expert.rb index d746a0b9a..7ffbd5553 100644 --- a/elasticsearch-rails/lib/rails/templates/03-expert.rb +++ b/elasticsearch-rails/lib/rails/templates/03-expert.rb @@ -1,6 +1,6 @@ # $ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/03-expert.rb -unless File.read('README.md').include? '== [2] Pretty' +unless File.read('README.md').include? '## [2] Pretty' say_status "ERROR", "You have to run the 01-basic.rb and 02-pretty.rb templates first.", :red exit(1) end diff --git a/elasticsearch-rails/lib/rails/templates/04-dsl.rb b/elasticsearch-rails/lib/rails/templates/04-dsl.rb index 81a390a1f..163160b34 100644 --- a/elasticsearch-rails/lib/rails/templates/04-dsl.rb +++ b/elasticsearch-rails/lib/rails/templates/04-dsl.rb @@ -1,13 +1,13 @@ # $ rails new searchapp --skip --skip-bundle --template https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/04-dsl.rb -unless File.read('README.rdoc').include? '== [3] Expert' +unless File.read('README.md').include? '## [3] Expert' say_status "ERROR", "You have to run the 01-basic.rb, 02-pretty.rb and 03-expert.rb templates first.", :red exit(1) end -append_to_file 'README.rdoc', <<-README +append_to_file 'README.md', <<-README -== [4] DSL +## [4] DSL The `dsl` template refactors the search definition in SearchController#index to use the [`elasticsearch-dsl`](https://github.com/elastic/elasticsearch-ruby/tree/dsl/elasticsearch-dsl) @@ -15,7 +15,7 @@ README -git add: "README.rdoc" +git add: "README.md" git commit: "-m '[03] Updated the application README'" run 'rm -f app/assets/stylesheets/*.scss' diff --git a/elasticsearch-rails/lib/rails/templates/05-settings-files.rb b/elasticsearch-rails/lib/rails/templates/05-settings-files.rb index 803e2acb9..4d09f4847 100644 --- a/elasticsearch-rails/lib/rails/templates/05-settings-files.rb +++ b/elasticsearch-rails/lib/rails/templates/05-settings-files.rb @@ -2,16 +2,16 @@ # (See: 01-basic.rb, 02-pretty.rb, 03-expert.rb, 04-dsl.rb) -append_to_file 'README.rdoc', <<-README +append_to_file 'README.md', <<-README -== [5] Settings Files +## [5] Settings Files The `settings-files` template refactors the `Searchable` module to load the index settings from an external file. README -git add: "README.rdoc" +git add: "README.md" git commit: "-m '[05] Updated the application README'" # ----- Setup the Searchable module to load settings from config/elasticsearch/articles_settings.json From 16ef3934b4408de950e6c676fac0c88f4eb5ff7b Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 11 Feb 2017 14:03:46 +0100 Subject: [PATCH 305/582] [MODEL] Fixed the completion example for ActiveRecord for Elasticsearch 5 --- .../examples/activerecord_mapping_completion.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/elasticsearch-model/examples/activerecord_mapping_completion.rb b/elasticsearch-model/examples/activerecord_mapping_completion.rb index 46d986011..3f276a16c 100644 --- a/elasticsearch-model/examples/activerecord_mapping_completion.rb +++ b/elasticsearch-model/examples/activerecord_mapping_completion.rb @@ -18,17 +18,15 @@ class Article < ActiveRecord::Base include Elasticsearch::Model::Callbacks mapping do - indexes :title - indexes :title_suggest, type: 'completion', payloads: true + indexes :title, type: 'text' + indexes :title_suggest, type: 'completion' + indexes :url, type: 'keyword' end def as_indexed_json(options={}) as_json.merge \ - title_suggest: { - input: title, - output: title, - payload: { url: "/articles/#{id}" } - } + title_suggest: { input: title }, + url: "/articles/#{id}" end end @@ -63,7 +61,7 @@ def as_indexed_json(options={}) }; puts "Article suggest:".ansi(:bold), - response_2['articles'].first['options'].map { |d| "#{d['text']} -> #{d['payload']['url']}" }. + response_2['articles'].first['options'].map { |d| "#{d['text']} -> #{d['_source']['url']}" }. inspect.ansi(:bold, :green) require 'pry'; binding.pry; From f8df53aedfd37ab83cb19abfccaaa71b34018e22 Mon Sep 17 00:00:00 2001 From: Tomas Valent <equivalent@eq8.eu> Date: Mon, 27 Jun 2016 17:24:14 +0100 Subject: [PATCH 306/582] [MODEL] Added an example with Edge NGram mapping for auto-completion This example uses an [Edge Ngram](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-edgengram-tokenizer.html) mapping to demonstrate how to create a custom "settings" for a model, and how to use the NGram feature for autocomplete. NOTE: The "Completion Suggester" should be used in all cases where it's applicable; see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-completion.html. Closes #582 --- .../activerecord_mapping_edge_ngram.rb | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 elasticsearch-model/examples/activerecord_mapping_edge_ngram.rb diff --git a/elasticsearch-model/examples/activerecord_mapping_edge_ngram.rb b/elasticsearch-model/examples/activerecord_mapping_edge_ngram.rb new file mode 100644 index 000000000..7a6a1e8c6 --- /dev/null +++ b/elasticsearch-model/examples/activerecord_mapping_edge_ngram.rb @@ -0,0 +1,101 @@ +require 'ansi' +require 'sqlite3' +require 'active_record' +require 'elasticsearch/model' + +ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT) +ActiveRecord::Base.establish_connection( adapter: 'sqlite3', database: ":memory:" ) + +ActiveRecord::Schema.define(version: 1) do + create_table :articles do |t| + t.string :title + t.date :published_at + t.timestamps + end +end + +class Article < ActiveRecord::Base + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + article_es_settings = { + index: { + analysis: { + filter: { + autocomplete_filter: { + type: "edge_ngram", + min_gram: 1, + max_gram: 20 + } + }, + analyzer:{ + autocomplete: { + type: "custom", + tokenizer: "standard", + filter: ["lowercase", "autocomplete_filter"] + } + } + } + } + } + + settings article_es_settings do + mapping do + indexes :title + indexes :suggestable_title, type: 'string', analyzer: 'autocomplete' + end + end + + def as_indexed_json(options={}) + as_json.merge(suggestable_title: title) + end +end + +Article.__elasticsearch__.client = Elasticsearch::Client.new log: true + +# Create index + +Article.__elasticsearch__.create_index! force: true + +# Store data + +Article.delete_all +Article.create title: 'Foo' +Article.create title: 'Bar' +Article.create title: 'Foo Foo' +Article.__elasticsearch__.refresh_index! + +# Search and suggest +fulltext_search_response = Article.search(query: { match: { title: 'foo'} } ) + +puts "", "Article search for 'foo':".ansi(:bold), + fulltext_search_response.to_a.map { |d| "Title: #{d.title}" }.inspect.ansi(:bold, :yellow), + "" + +fulltext_search_response_2 = Article.search(query: { match: { title: 'fo'} } ) + +puts "", "Article search for 'fo':".ansi(:bold), + fulltext_search_response_2.to_a.map { |d| "Title: #{d.title}" }.inspect.ansi(:bold, :red), + "" + +autocomplete_search_response = Article.search(query: { match: { suggestable_title: { query: 'fo', analyzer: 'standard'} } } ) + +puts "", "Article autocomplete for 'fo':".ansi(:bold), + autocomplete_search_response.to_a.map { |d| "Title: #{d.suggestable_title}" }.inspect.ansi(:bold, :green), + "" + +puts "", "Text 'Foo Bar' analyzed with the default analyzer:".ansi(:bold), + Article.__elasticsearch__.client.indices.analyze( + index: Article.__elasticsearch__.index_name, + field: 'title', + text: 'Foo Bar')['tokens'].map { |t| t['token'] }.inspect.ansi(:bold, :yellow), + "" + +puts "", "Text 'Foo Bar' analyzed with the autocomplete filter:".ansi(:bold), + Article.__elasticsearch__.client.indices.analyze( + index: Article.__elasticsearch__.index_name, + field: 'suggestable_title', + text: 'Foo Bar')['tokens'].map { |t| t['token'] }.inspect.ansi(:bold, :yellow), + "" + +require 'pry'; binding.pry; From a086ded741815f040373752e8d71a6502922a252 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 11 Feb 2017 15:29:28 +0100 Subject: [PATCH 307/582] [MODEL] Expanded the example for indexing and searching ActiveRecord associations The example did a good job for demonstrating how to set up relationships between models and how to index the "core" model, but it didn't really demonstrated the difference between the `records` and `results` of the response. Related: #586 --- .../examples/activerecord_associations.rb | 43 +++++++++++++++---- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/elasticsearch-model/examples/activerecord_associations.rb b/elasticsearch-model/examples/activerecord_associations.rb index 8e5066eaa..4e022b2af 100644 --- a/elasticsearch-model/examples/activerecord_associations.rb +++ b/elasticsearch-model/examples/activerecord_associations.rb @@ -12,12 +12,12 @@ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'pry' -Pry.config.history.file = File.expand_path('../../tmp/elasticsearch_development.pry', __FILE__) require 'logger' require 'ansi/core' require 'active_record' +require 'json' require 'elasticsearch/model' ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT) @@ -63,7 +63,7 @@ # ----- Elasticsearch client setup ---------------------------------------------------------------- Elasticsearch::Model.client = Elasticsearch::Client.new log: true -Elasticsearch::Model.client.transport.logger.formatter = proc { |s, d, p, m| "\e[32m#{m}\n\e[0m" } +Elasticsearch::Model.client.transport.logger.formatter = proc { |s, d, p, m| "\e[2m#{m}\n\e[0m" } # ----- Search integration ------------------------------------------------------------------------ @@ -161,18 +161,43 @@ class Comment < ActiveRecord::Base Elasticsearch::Model.client.indices.refresh index: Elasticsearch::Model::Registry.all.map(&:index_name) -puts "\n\e[1mArticles containing 'one':\e[0m", Article.search('one').records.to_a.map(&:inspect), "" +# Search for a term and return records +# +puts "", + "Articles containing 'one':".ansi(:bold), + Article.search('one').records.to_a.map(&:inspect), + "" -puts "\n\e[1mModels containing 'one':\e[0m", Elasticsearch::Model.search('one').records.to_a.map(&:inspect), "" +puts "", + "All Models containing 'one':".ansi(:bold), + Elasticsearch::Model.search('one').records.to_a.map(&:inspect), + "" -# Load model +# Difference between `records` and `results` # -article = Article.all.includes(:categories, :authors, :comments).first +response = Article.search query: { match: { title: 'first' } } -# ----- Pry --------------------------------------------------------------------------------------- +puts "", + "Search results are wrapped in the <#{response.class}> class", + "" + +puts "", + "Access the <ActiveRecord> instances with the `#records` method:".ansi(:bold), + response.records.map { |r| "* #{r.title} | Authors: #{r.authors.map(&:full_name) } | Comment count: #{r.comments.size}" }.join("\n"), + "" -puts '', '-'*Pry::Terminal.width! +puts "", + "Access the Elasticsearch documents with the `#results` method (without touching the database):".ansi(:bold), + response.results.map { |r| "* #{r.title} | Authors: #{r.authors.map(&:full_name) } | Comment count: #{r.comments.size}" }.join("\n"), + "" + +puts "", + "The indexed document:".ansi(:bold), + JSON.pretty_generate(response.results.first._source.to_hash), + "" + +# ----- Pry --------------------------------------------------------------------------------------- Pry.start(binding, prompt: lambda { |obj, nest_level, _| '> ' }, - input: StringIO.new("article.as_indexed_json\n"), + input: StringIO.new('response.records.first'), quiet: true) From a01586a967642d55de25cb765aebce0ee11fa4ac Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sat, 11 Feb 2017 15:49:44 +0100 Subject: [PATCH 308/582] [MODEL] Added an example for source filtering to the ActiveRecord associations example See https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-source-filtering.html Related: a086de, #586 --- .../examples/activerecord_associations.rb | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/examples/activerecord_associations.rb b/elasticsearch-model/examples/activerecord_associations.rb index 4e022b2af..b086283a0 100644 --- a/elasticsearch-model/examples/activerecord_associations.rb +++ b/elasticsearch-model/examples/activerecord_associations.rb @@ -33,6 +33,7 @@ create_table :authors do |t| t.string :first_name, :last_name + t.string :department t.timestamps null: false end @@ -84,7 +85,7 @@ module Indexing def as_indexed_json(options={}) self.as_json( include: { categories: { only: :title}, - authors: { methods: [:full_name], only: [:full_name] }, + authors: { methods: [:full_name, :department], only: [:full_name, :department] }, comments: { only: :text } }) end @@ -140,7 +141,7 @@ class Comment < ActiveRecord::Base # Create author # -author = Author.create first_name: 'John', last_name: 'Smith' +author = Author.create first_name: 'John', last_name: 'Smith', department: 'Business' # Create article @@ -192,7 +193,16 @@ class Comment < ActiveRecord::Base "" puts "", - "The indexed document:".ansi(:bold), + "The whole indexed document (according to `Article#as_indexed_json`):".ansi(:bold), + JSON.pretty_generate(response.results.first._source.to_hash), + "" + +# Retrieve only selected fields from Elasticsearch +# +response = Article.search query: { match: { title: 'first' } }, _source: ['title', 'authors.full_name'] + +puts "", + "Retrieve only selected fields from Elasticsearch:".ansi(:bold), JSON.pretty_generate(response.results.first._source.to_hash), "" From 6bfc6e0bce600d5261fc78744b80ad4af8bbdf60 Mon Sep 17 00:00:00 2001 From: Koen Punt <koen@koenpunt.nl> Date: Tue, 7 Feb 2017 22:22:31 +0100 Subject: [PATCH 309/582] [MODEL] Fixed a typo in the README Closes #667 --- elasticsearch-model/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 8f963018d..01ca0cc74 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -290,7 +290,7 @@ response.page(2).results response.page(2).records ``` -In a Rails controller, use the the `params[:page]` parameter to paginate through results: +In a Rails controller, use the `params[:page]` parameter to paginate through results: ```ruby @articles = Article.search(params[:q]).page(params[:page]).records From 757c57a958401220c010eb097fcf07ad91611b9b Mon Sep 17 00:00:00 2001 From: JA <ja@josealberto.org> Date: Mon, 19 Dec 2016 13:46:11 +0100 Subject: [PATCH 310/582] [STORE] Use `text` instead of `string` for the <String> data types In order to be compatible with Elasticsearch 5.x, the default property type for Ruby <String> objects has been changed to `text` from `string`. See: https://www.elastic.co/guide/en/elasticsearch/reference/current/string.html Closes #649 --- .../lib/elasticsearch/persistence/model/base.rb | 2 +- elasticsearch-persistence/test/unit/model_gateway_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb index bc55c583c..a0af32411 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb @@ -70,7 +70,7 @@ module Utils def lookup_type(type) case when type == String - 'string' + 'text' when type == Integer 'integer' when type == Float diff --git a/elasticsearch-persistence/test/unit/model_gateway_test.rb b/elasticsearch-persistence/test/unit/model_gateway_test.rb index e3962ed40..2451241d6 100644 --- a/elasticsearch-persistence/test/unit/model_gateway_test.rb +++ b/elasticsearch-persistence/test/unit/model_gateway_test.rb @@ -70,7 +70,7 @@ def run!; DummyGatewayModel.gateway { |g| @b += 1 }; end end should "properly look up types for classes" do - assert_equal 'string', Elasticsearch::Persistence::Model::Utils::lookup_type(String) + assert_equal 'text', Elasticsearch::Persistence::Model::Utils::lookup_type(String) assert_equal 'integer', Elasticsearch::Persistence::Model::Utils::lookup_type(Integer) assert_equal 'float', Elasticsearch::Persistence::Model::Utils::lookup_type(Float) assert_equal 'date', Elasticsearch::Persistence::Model::Utils::lookup_type(Date) From ffc1aa4e0a566d9deeee56e3f636ffc060c9ec07 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sun, 12 Feb 2017 11:20:18 +0100 Subject: [PATCH 311/582] [MODEL] [STORE] Changed the default mapping type to `text` The default type for properties defined with the `indexes` method has been changed to the `text` field type. Also, fixed the integration test to have the correct mapping and to use the `bool` query instead of the old `filtered` query. See: * https://www.elastic.co/guide/en/elasticsearch/reference/current/string.html * https://www.elastic.co/guide/en/elasticsearch/reference/current/text.html Related: #649 --- .../lib/elasticsearch/model/indexing.rb | 4 +-- .../active_record_associations_test.rb | 11 +++---- .../test/unit/indexing_test.rb | 32 +++++++------------ .../test/unit/repository_indexing_test.rb | 2 +- 4 files changed, 19 insertions(+), 30 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index 9c90e9d82..d0160a4e8 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -65,8 +65,8 @@ def indexes(name, options={}, &block) end end - # Set the type to `string` by default - @mapping[name][:type] ||= 'string' + # Set the type to `text` by default + @mapping[name][:type] ||= 'text' self end diff --git a/elasticsearch-model/test/integration/active_record_associations_test.rb b/elasticsearch-model/test/integration/active_record_associations_test.rb index 0648057f3..87a1301d8 100644 --- a/elasticsearch-model/test/integration/active_record_associations_test.rb +++ b/elasticsearch-model/test/integration/active_record_associations_test.rb @@ -29,13 +29,12 @@ module Searchable indexes :authors do indexes :first_name indexes :last_name - indexes :full_name, type: 'multi_field' do - indexes :full_name - indexes :raw, analyzer: 'keyword' + indexes :full_name, type: 'text' do + indexes :raw, type: 'keyword' end end - indexes :categories, analyzer: 'keyword' + indexes :categories, type: 'keyword' indexes :comments, type: 'nested' do indexes :text @@ -188,8 +187,8 @@ class Post < ActiveRecord::Base Post.__elasticsearch__.refresh_index! query = { query: { - filtered: { - query: { + bool: { + must: { multi_match: { fields: ['title'], query: 'first' diff --git a/elasticsearch-model/test/unit/indexing_test.rb b/elasticsearch-model/test/unit/indexing_test.rb index b415f3316..dd9236dbd 100644 --- a/elasticsearch-model/test/unit/indexing_test.rb +++ b/elasticsearch-model/test/unit/indexing_test.rb @@ -83,33 +83,23 @@ class NotFound < Exception; end assert_equal 'boolean', mappings.to_hash[:mytype][:properties][:foo][:type] end - should "define type as string by default" do + should "define type as 'text' by default" do mappings = Elasticsearch::Model::Indexing::Mappings.new :mytype - mappings.indexes :bar, {} - assert_equal 'string', mappings.to_hash[:mytype][:properties][:bar][:type] + mappings.indexes :bar + assert_equal 'text', mappings.to_hash[:mytype][:properties][:bar][:type] end should "define multiple fields" do mappings = Elasticsearch::Model::Indexing::Mappings.new :mytype - mappings.indexes :foo_1, type: 'string' do - indexes :raw, analyzer: 'keyword' + mappings.indexes :my_field, type: 'text' do + indexes :raw, type: 'keyword' end - mappings.indexes :foo_2, type: 'multi_field' do - indexes :raw, analyzer: 'keyword' - end - - assert_equal 'string', mappings.to_hash[:mytype][:properties][:foo_1][:type] - assert_equal 'string', mappings.to_hash[:mytype][:properties][:foo_1][:fields][:raw][:type] - assert_equal 'keyword', mappings.to_hash[:mytype][:properties][:foo_1][:fields][:raw][:analyzer] - assert_nil mappings.to_hash[:mytype][:properties][:foo_1][:properties] - - assert_equal 'multi_field', mappings.to_hash[:mytype][:properties][:foo_2][:type] - assert_equal 'string', mappings.to_hash[:mytype][:properties][:foo_2][:fields][:raw][:type] - assert_equal 'keyword', mappings.to_hash[:mytype][:properties][:foo_2][:fields][:raw][:analyzer] - assert_nil mappings.to_hash[:mytype][:properties][:foo_2][:properties] + assert_equal 'text', mappings.to_hash[:mytype][:properties][:my_field][:type] + assert_equal 'keyword', mappings.to_hash[:mytype][:properties][:my_field][:fields][:raw][:type] + assert_nil mappings.to_hash[:mytype][:properties][:my_field][:properties] end should "define embedded properties" do @@ -134,15 +124,15 @@ class NotFound < Exception; end # Object is the default when `type` is missing and there's a block passed # assert_equal 'object', mappings.to_hash[:mytype][:properties][:foo][:type] - assert_equal 'string', mappings.to_hash[:mytype][:properties][:foo][:properties][:bar][:type] + assert_equal 'text', mappings.to_hash[:mytype][:properties][:foo][:properties][:bar][:type] assert_nil mappings.to_hash[:mytype][:properties][:foo][:fields] assert_equal 'object', mappings.to_hash[:mytype][:properties][:foo_object][:type] - assert_equal 'string', mappings.to_hash[:mytype][:properties][:foo_object][:properties][:bar][:type] + assert_equal 'text', mappings.to_hash[:mytype][:properties][:foo_object][:properties][:bar][:type] assert_nil mappings.to_hash[:mytype][:properties][:foo_object][:fields] assert_equal 'nested', mappings.to_hash[:mytype][:properties][:foo_nested][:type] - assert_equal 'string', mappings.to_hash[:mytype][:properties][:foo_nested][:properties][:bar][:type] + assert_equal 'text', mappings.to_hash[:mytype][:properties][:foo_nested][:properties][:bar][:type] assert_nil mappings.to_hash[:mytype][:properties][:foo_nested][:fields] assert_equal :nested, mappings.to_hash[:mytype][:properties][:foo_nested_as_symbol][:type] diff --git a/elasticsearch-persistence/test/unit/repository_indexing_test.rb b/elasticsearch-persistence/test/unit/repository_indexing_test.rb index 5ee22578d..51b2b8b21 100644 --- a/elasticsearch-persistence/test/unit/repository_indexing_test.rb +++ b/elasticsearch-persistence/test/unit/repository_indexing_test.rb @@ -22,7 +22,7 @@ class MyDocument; end indexes :title end - assert_equal( {:"my_document"=>{:properties=>{:title=>{:type=>"string"}}}}, subject.mappings.to_hash ) + assert_equal( {:"my_document"=>{:properties=>{:title=>{:type=>"text"}}}}, subject.mappings.to_hash ) end end From e81fc5b5d0648280fdf054566823e647352b45eb Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sun, 12 Feb 2017 11:33:28 +0100 Subject: [PATCH 312/582] [STORE] Removed the `search_type=scan` in the `find_in_batches` method Since the `search_type` parameter has been deprecated in Elasticsearch 2 and removed in Elasticsearch 5, the `find_in_batches` method has been updated to stop using the parameter, and to use the new way of setting up search context for scrolling with the `sort:_doc` option. See: https://www.elastic.co/guide/en/elasticsearch/reference/2.1/breaking_21_search_changes.html#_literal_search_type_scan_literal_deprecated --- elasticsearch-persistence/README.md | 2 +- .../elasticsearch/persistence/model/find.rb | 19 ++++++++----------- .../test/unit/model_find_test.rb | 12 +++++++----- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index 053366813..5a7921e09 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -581,7 +581,7 @@ retrieve big collections of model instances, using the Elasticsearch's _Scan API ```ruby Article.find_each(_source_include: 'title') { |a| puts "===> #{a.title.upcase}" } -# GET http://localhost:9200/articles/article/_search?scroll=5m&search_type=scan&size=20 +# GET http://localhost:9200/articles/article/_search?scroll=5m&size=20 # GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhb... # ===> TEST # GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhb... diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb index de31eb8bf..43276dc03 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb @@ -55,7 +55,7 @@ def count(query_or_definition=nil, options={}) gateway.count( query_or_definition, options ) end - # Returns all models efficiently via the Elasticsearch's scan/scroll API + # Returns all models efficiently via the Elasticsearch's scroll API # # You can restrict the models being returned with a query. # @@ -117,20 +117,17 @@ def find_in_batches(options={}, &block) body = options - # Get the initial scroll_id + # Get the initial batch of documents and the scroll_id # response = gateway.client.search( { index: gateway.index_name, type: gateway.document_type, - search_type: 'scan', - scroll: scroll, - size: 20, - body: body }.merge(search_params) ) + scroll: scroll, + sort: ['_doc'], + size: 20, + body: body }.merge(search_params) ) - # Get the initial batch of documents - # - response = gateway.client.scroll( { scroll_id: response['_scroll_id'], scroll: scroll } ) - # Break when receiving an empty array of hits + # Scroll the search object and break when receiving an empty array of hits # while response['hits']['hits'].any? do yield Repository::Response::Results.new(gateway, response) @@ -149,7 +146,7 @@ def find_in_batches(options={}, &block) # # Person.find_each { |person| puts person.name } # - # # # GET http://localhost:9200/people/person/_search?scroll=5m&search_type=scan&size=20 + # # # GET http://localhost:9200/people/person/_search?scroll=5m&size=20 # # # GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhbj... # # Test 0 # # Test 1 diff --git a/elasticsearch-persistence/test/unit/model_find_test.rb b/elasticsearch-persistence/test/unit/model_find_test.rb index 4662cade8..92370c1e4 100644 --- a/elasticsearch-persistence/test/unit/model_find_test.rb +++ b/elasticsearch-persistence/test/unit/model_find_test.rb @@ -83,27 +83,29 @@ class DummyFindModel DummyFindModel.all( { query: { match: { title: 'test' } } }, { routing: 'abc123' } ) end - context "finding via scan/scroll" do + context "finding via scroll" do setup do @gateway .expects(:deserialize) - .with('_source' => {'foo' => 'bar'}) .returns('_source' => {'foo' => 'bar'}) + .at_least_once + # 1. Initial batch of documents and the scroll_id @gateway.client .expects(:search) .with do |arguments| - assert_equal 'scan', arguments[:search_type] assert_equal 'foo', arguments[:index] assert_equal 'bar', arguments[:type] true end - .returns(MultiJson.load('{"_scroll_id":"abc123==", "hits":{"hits":[]}}')) + .returns(MultiJson.load('{"_scroll_id":"abc123==", "hits":{"hits":[{"_source":{"foo":"bar_1"}}]}}')) + # 2. Second batch of documents and the scroll_id + # 3. Last, empty batch of documents @gateway.client .expects(:scroll) .twice - .returns(MultiJson.load('{"_scroll_id":"abc456==", "hits":{"hits":[{"_source":{"foo":"bar"}}]}}')) + .returns(MultiJson.load('{"_scroll_id":"abc456==", "hits":{"hits":[{"_source":{"foo":"bar_2"}}]}}')) .then .returns(MultiJson.load('{"_scroll_id":"abc789==", "hits":{"hits":[]}}')) end From c3f3601c444f81fb21fd53bf0ee753ee7d3d2048 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sun, 12 Feb 2017 11:37:02 +0100 Subject: [PATCH 313/582] [STORE] Updated the `count` method in the "repository" module Since the `search_type: 'count'` parameter has been deprecated in Elasticsearch 2 and removed in Elasticsearch 5, the `Elasticsearch::Persistence::Model::Find::ClassMethods#count` method has been updated to use the "Count" API, instead of the "Search" API with the deprecated parameter. See: https://www.elastic.co/guide/en/elasticsearch/reference/2.1/breaking_21_search_changes.html#_literal_search_type_count_literal_deprecated --- .../persistence/repository/search.rb | 14 ++++++++++++-- .../test/unit/repository_search_test.rb | 18 +++++++++--------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb index 201d2979d..de9e01aac 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb @@ -75,8 +75,18 @@ def search(query_or_definition, options={}) # def count(query_or_definition=nil, options={}) query_or_definition ||= { query: { match_all: {} } } - response = search query_or_definition, options.update(search_type: 'count') - response.response.hits.total + type = document_type || (klass ? __get_type_from_class(klass) : nil ) + + case + when query_or_definition.respond_to?(:to_hash) + response = client.count( { index: index_name, type: type, body: query_or_definition.to_hash }.merge(options) ) + when query_or_definition.is_a?(String) + response = client.count( { index: index_name, type: type, q: query_or_definition }.merge(options) ) + else + raise ArgumentError, "[!] Pass the search definition as a Hash-like object or pass the query as a String, not as [#{query_or_definition.class}]" + end + + response['count'] end end diff --git a/elasticsearch-persistence/test/unit/repository_search_test.rb b/elasticsearch-persistence/test/unit/repository_search_test.rb index 759a3dd04..d0d5beef0 100644 --- a/elasticsearch-persistence/test/unit/repository_search_test.rb +++ b/elasticsearch-persistence/test/unit/repository_search_test.rb @@ -96,21 +96,21 @@ class MyDocument; end end should "return the number of domain objects" do - subject.expects(:search) - .returns(Elasticsearch::Persistence::Repository::Response::Results.new( subject, {'hits' => { 'total' => 1 }})) + subject.client.expects(:count).returns({'count' => 1}) assert_equal 1, subject.count end - should "pass arguments from count to search" do - subject.expects(:search) - .with do |query_or_definition, options| - assert_equal 'bar', query_or_definition[:query][:match][:foo] - assert_equal true, options[:ignore_unavailable] + should "pass arguments to count" do + subject.client.expects(:count) + .with do |arguments| + assert_equal 'test', arguments[:index] + assert_equal 'bar', arguments[:body][:query][:match][:foo] + assert_equal true, arguments[:ignore_unavailable] true end - .returns(Elasticsearch::Persistence::Repository::Response::Results.new( subject, {'hits' => { 'total' => 1 }})) + .returns({'count' => 1}) - subject.count( { query: { match: { foo: 'bar' } } }, { ignore_unavailable: true } ) + assert_equal 1, subject.count( { query: { match: { foo: 'bar' } } }, { ignore_unavailable: true } ) end end From d7945b4859b40cb61fb0b08cce44999bc765b598 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sun, 12 Feb 2017 11:43:31 +0100 Subject: [PATCH 314/582] [STORE] Updated the "update by script" integration test for Elasticsearch 5 See: https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html#_scripted_updates --- .../test/integration/repository/virtus_model_test.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb b/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb index fbef15ba7..fdefe2d45 100644 --- a/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb +++ b/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb @@ -102,7 +102,11 @@ def deserialize(document) should "update the object with a script and params" do response = @repository.save Page.new(title: 'Test Page') - @repository.update id: response['_id'], script: 'ctx._source.views += count', params: { count: 3 } + @repository.update id: response['_id'], + script: { + inline: 'ctx._source.views += params.count', + params: { count: 3 } + } page = @repository.find(response['_id']) assert_equal 3, page.views From 451786bd1940dc7173ac5fba08a1376aeb25d6ba Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sun, 12 Feb 2017 11:44:35 +0100 Subject: [PATCH 315/582] [CI] Updated Travis configuration to run integration tests against Elasticsearch 5.2.0 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ba677f628..405bebab8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,14 +28,14 @@ matrix: - rvm: 2.3.3 jdk: oraclejdk8 - env: TEST_SUITE=integration SERVER=start TEST_CLUSTER_LOGS=/tmp/log TEST_CLUSTER_COMMAND=/tmp/elasticsearch-1.7.5/bin/elasticsearch + env: TEST_SUITE=integration SERVER=start TEST_CLUSTER_LOGS=/tmp/log TEST_CLUSTER_COMMAND=/tmp/elasticsearch-5.2.0/bin/elasticsearch before_install: - gem update --system --no-rdoc --no-ri - gem --version - gem install bundler -v 1.14.3 --no-rdoc --no-ri - bundle version - - curl -sS https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-1.7.5.tar.gz | tar xz -C /tmp + - curl -sS https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.2.0.tar.gz | tar xz -C /tmp install: - bundle install From 69878562625a59aba944c449768c198b85913b68 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sun, 12 Feb 2017 12:08:21 +0100 Subject: [PATCH 316/582] [CI] Set QUIET=y to prevent Travis failing the build Because of the Hashie::Mash warnings flooding the logs, the log exceeds the Travis limit and it fails the build. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 405bebab8..364a58c3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ matrix: - rvm: 2.3.3 jdk: oraclejdk8 - env: TEST_SUITE=integration SERVER=start TEST_CLUSTER_LOGS=/tmp/log TEST_CLUSTER_COMMAND=/tmp/elasticsearch-5.2.0/bin/elasticsearch + env: TEST_SUITE=integration QUIET=y SERVER=start TEST_CLUSTER_LOGS=/tmp/log TEST_CLUSTER_COMMAND=/tmp/elasticsearch-5.2.0/bin/elasticsearch before_install: - gem update --system --no-rdoc --no-ri From 5732fa31fb84c3fe977fa7d52e5a352264860dfa Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sun, 12 Feb 2017 17:01:32 +0100 Subject: [PATCH 317/582] [MODEL] Added a `HashWrapper` class to wrap Hash structures instead of raw `Hashie::Mash` Since Hashie now prints out a warning whenever a key which clashes with a built-in method is added, such as `sort`, the logs are flooded. Hashie has a `disable_warnings` method in intridea/hashie@b2f94a4 to supress the logging, so a simple sub-class has been added to allow calling it cleanly, and to serve as a possible extension point in the future. Many thanks to @michaelherold for the pointers to a similar implementation in the Omniauth gem and adding quickly the infrastructure in the Hashie gem! Closes #666 --- .../lib/elasticsearch/model/hash_wrapper.rb | 15 +++++++++++++++ .../test/unit/hash_wrapper_test.rb | 13 +++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 elasticsearch-model/lib/elasticsearch/model/hash_wrapper.rb create mode 100644 elasticsearch-model/test/unit/hash_wrapper_test.rb diff --git a/elasticsearch-model/lib/elasticsearch/model/hash_wrapper.rb b/elasticsearch-model/lib/elasticsearch/model/hash_wrapper.rb new file mode 100644 index 000000000..484d292a6 --- /dev/null +++ b/elasticsearch-model/lib/elasticsearch/model/hash_wrapper.rb @@ -0,0 +1,15 @@ +module Elasticsearch + module Model + + # Subclass of `Hashie::Mash` to wrap Hash-like structures + # (responses from Elasticsearch, search definitions, etc) + # + # The primary goal of the subclass is to disable the + # warning being printed by Hashie for re-defined + # methods, such as `sort`. + # + class HashWrapper < ::Hashie::Mash + disable_warnings if respond_to?(:disable_warnings) + end + end +end diff --git a/elasticsearch-model/test/unit/hash_wrapper_test.rb b/elasticsearch-model/test/unit/hash_wrapper_test.rb new file mode 100644 index 000000000..b7b1989d5 --- /dev/null +++ b/elasticsearch-model/test/unit/hash_wrapper_test.rb @@ -0,0 +1,13 @@ +require 'test_helper' + +require 'hashie/version' + +class Elasticsearch::Model::HashWrapperTest < Test::Unit::TestCase + context "The HashWrapper class" do + should "not print the warning for re-defined methods" do + Hashie.logger.expects(:warn).never + + subject = Elasticsearch::Model::HashWrapper.new(:foo => 'bar', :sort => true) + end if Hashie::VERSION >= '3.5.3' + end +end From a102c922d1eaf1116ee9a25d7184c7994bdecfed Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sun, 12 Feb 2017 17:08:11 +0100 Subject: [PATCH 318/582] [MODEL] Call `Hashie.disable_warnings` method in Response wrappers Adding to change introduced in 5732fa3, this patch disables the warnings Hashie prints out when a key clashing with a built-in method is being added. Related: #666 --- .../lib/elasticsearch/model/response/aggregations.rb | 2 ++ .../lib/elasticsearch/model/response/suggestions.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb b/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb index 06d46d1e3..43dfd23e8 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb @@ -3,6 +3,8 @@ module Model module Response class Aggregations < Hashie::Mash + disable_warnings if respond_to?(:disable_warnings) + def initialize(attributes={}) __redefine_enumerable_methods super(attributes) end diff --git a/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb b/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb index 5088767ce..1b1cc6598 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb @@ -3,6 +3,8 @@ module Model module Response class Suggestions < Hashie::Mash + disable_warnings if respond_to?(:disable_warnings) + def terms self.to_a.map { |k,v| v.first['options'] }.flatten.map {|v| v['text']}.uniq end From dd26f59b9e4dbe25f6da9f69588ab680a3f6b730 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sun, 12 Feb 2017 17:10:52 +0100 Subject: [PATCH 319/582] [MODEL] Added, that `HashWrapper`, a sub-class of `Hashie::Mash` is used To prevent Hashie warnings, use the class introduced in 5732fa3 for wrapping Hashes, instead of Hashie::Mash directly. Closes #666 --- elasticsearch-model/lib/elasticsearch/model.rb | 7 ++++--- elasticsearch-model/lib/elasticsearch/model/response.rb | 4 ++-- .../lib/elasticsearch/model/response/result.rb | 2 +- elasticsearch-model/test/unit/response_test.rb | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index aa3bf9c5f..2c395bd84 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -1,11 +1,12 @@ -require 'elasticsearch' - -require 'hashie' +require 'hashie/mash' require 'active_support/core_ext/module/delegation' +require 'elasticsearch' + require 'elasticsearch/model/version' +require 'elasticsearch/model/hash_wrapper' require 'elasticsearch/model/client' require 'elasticsearch/model/multimodel' diff --git a/elasticsearch-model/lib/elasticsearch/model/response.rb b/elasticsearch-model/lib/elasticsearch/model/response.rb index fba25aaea..d15c24a04 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response.rb @@ -28,7 +28,7 @@ def initialize(klass, search, options={}) # def response @response ||= begin - Hashie::Mash.new(search.execute!) + HashWrapper.new(search.execute!) end end @@ -63,7 +63,7 @@ def timed_out # Returns the statistics on shards # def shards - Hashie::Mash.new(response['_shards']) + HashWrapper.new(response['_shards']) end # Returns a Hashie::Mash of the aggregations diff --git a/elasticsearch-model/lib/elasticsearch/model/response/result.rb b/elasticsearch-model/lib/elasticsearch/model/response/result.rb index 217723e8b..01481d0e1 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/result.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/result.rb @@ -14,7 +14,7 @@ class Result # @param attributes [Hash] A Hash with document properties # def initialize(attributes={}) - @result = Hashie::Mash.new(attributes) + @result = HashWrapper.new(attributes) end # Return document `_id` as `id` diff --git a/elasticsearch-model/test/unit/response_test.rb b/elasticsearch-model/test/unit/response_test.rb index 71cfb2d6d..e18f6c7ef 100644 --- a/elasticsearch-model/test/unit/response_test.rb +++ b/elasticsearch-model/test/unit/response_test.rb @@ -27,7 +27,7 @@ def self.document_type; 'bar'; end assert_equal 'OK', response.shards.one end - should "wrap the raw Hash response in Hashie::Mash" do + should "wrap the raw Hash response in a HashWrapper" do @search = Elasticsearch::Model::Searching::SearchRequest.new OriginClass, '*' @search.stubs(:execute!).returns({'hits' => { 'hits' => [] }, 'aggregations' => { 'dates' => 'FOO' }}) From c18d0a9649fd1f6af28a497e3e5c71497bcccf56 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sun, 12 Feb 2017 17:12:09 +0100 Subject: [PATCH 320/582] [STORE] Added, that `HashWrapper`, a sub-class of `Hashie::Mash` is used To prevent Hashie warnings, use the class introduced in 5732fa3 for wrapping Hashes, instead of Hashie::Mash directly. Related: #666 --- elasticsearch-persistence/lib/elasticsearch/persistence.rb | 5 ++++- .../lib/elasticsearch/persistence/model.rb | 2 +- .../elasticsearch/persistence/repository/response/results.rb | 4 ++-- .../test/unit/repository_response_results_test.rb | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence.rb b/elasticsearch-persistence/lib/elasticsearch/persistence.rb index 5befd483e..986ef1bc4 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence.rb @@ -1,7 +1,10 @@ +require 'hashie/mash' + require 'elasticsearch' + +require 'elasticsearch/model/hash_wrapper' require 'elasticsearch/model/indexing' require 'elasticsearch/model/searching' -require 'hashie' require 'active_support/inflector' diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb index 70b2ccb6b..ce948d4f3 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb @@ -113,7 +113,7 @@ def deserialize(document) # Store the "hit" information (highlighting, score, ...) # object.instance_variable_set :@hit, - Hashie::Mash.new(document.except('_index', '_type', '_id', '_version', '_source')) + Elasticsearch::Model::HashWrapper.new(document.except('_index', '_type', '_id', '_version', '_source')) object.instance_variable_set(:@persisted, true) object diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb index fe64ac9b0..73fb4f836 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb @@ -18,7 +18,7 @@ class Results # def initialize(repository, response, options={}) @repository = repository - @response = Hashie::Mash.new(response) + @response = Elasticsearch::Model::HashWrapper.new(response) @options = options end @@ -78,7 +78,7 @@ def results # results.response.aggregations.titles.buckets.map { |term| "#{term['key']}: #{term['doc_count']}" } # # => ["brown: 1", "dog: 1", ...] # - # @return [Hashie::Mash] + # @return [Elasticsearch::Model::HashWrapper] # def response @response diff --git a/elasticsearch-persistence/test/unit/repository_response_results_test.rb b/elasticsearch-persistence/test/unit/repository_response_results_test.rb index 294a96efd..df8e08ae5 100644 --- a/elasticsearch-persistence/test/unit/repository_response_results_test.rb +++ b/elasticsearch-persistence/test/unit/repository_response_results_test.rb @@ -41,7 +41,7 @@ class MyDocument; end assert_equal 5, subject.response['_shards']['total'] end - should "wrap the response in Hashie::Mash" do + should "wrap the response in HashWrapper" do assert_equal 5, subject.response._shards.total end From 49e93a3f4f41ea6017f86b94f54bba5e32a90686 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Sun, 12 Feb 2017 17:37:44 +0100 Subject: [PATCH 321/582] Fixed the incorrect links to Travis and CodeClimate in the main README [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 67c57d11f..081508540 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ Article.create title: 'Test' ## Development -[![Build Status](https://travis-ci.org/elastic/elasticsearch-ruby.svg?branch=master)](https://travis-ci.org/elastic/elasticsearch-ruby) [![Code Climate](https://codeclimate.com/github/elastic/elasticsearch-ruby/badges/gpa.svg)](https://codeclimate.com/github/elastic/elasticsearch-ruby) +[![Build Status](https://travis-ci.org/elastic/elasticsearch-rails.svg?branch=master)](https://travis-ci.org/elastic/elasticsearch-rails) [![Code Climate](https://codeclimate.com/github/elastic/elasticsearch-rails/badges/gpa.svg)](https://codeclimate.com/github/elastic/elasticsearch-rails) To work on the code, clone the repository and install all dependencies first: From 2db5634d1fb782ea19b0343b5a285449c313f475 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Tue, 14 Feb 2017 11:01:00 +0100 Subject: [PATCH 322/582] [STORE] Updated the "Notes" example application for Elasticsearch 5.x --- elasticsearch-persistence/examples/notes/application.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/elasticsearch-persistence/examples/notes/application.rb b/elasticsearch-persistence/examples/notes/application.rb index 50f56dece..03ffa5d10 100644 --- a/elasticsearch-persistence/examples/notes/application.rb +++ b/elasticsearch-persistence/examples/notes/application.rb @@ -62,8 +62,8 @@ class NoteRepository mapping do indexes :text, analyzer: 'snowball' - indexes :tags, analyzer: 'keyword' - indexes :created_at, type: 'date' + indexes :tags, type: 'keyword' + indexes :created_at, type: 'date' end create_index! @@ -105,7 +105,7 @@ class Application < Sinatra::Base end if filter - { filtered: { query: query, filter: filter } } + { bool: { must: [ query ], filter: filter } } else query end From 5af7c90df550116859083a0d00c4cd52c30bdb5b Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Wed, 22 Feb 2017 10:29:15 +0100 Subject: [PATCH 323/582] [STORE] Updated the "Music" example application for Elasticsearch 5.x The "Music" application had mapping and query definitions incompatible with the latest major Elasticsearch version. This patch fixes that. The main changes are: * Update the field types in mappings (`string` -> `text`, etc) * Update the mapping for the `suggest` field. Instead of using the `input`, `output` and `payload` options when indexing the field, replicate this structure as regular JSON hierarchy. * Correspondingly, update the `IndexManager` to index documents in a proper structure. * Rewrite the output of `Suggester.as_json` method to be easily consumable by the JQuery Autocomplete component. * Small fixes and tweaks for the visual style of the application. * Update the template itself for Rails 5, to download Elasticsearch 5 when it's not installed, etc --- .../examples/music/album.rb | 25 ++++- .../examples/music/artist.rb | 30 ++++- .../examples/music/artists/index.html.erb | 7 +- .../examples/music/artists/show.html.erb | 89 ++++++++------- .../examples/music/assets/application.css | 37 +++++- .../examples/music/assets/blank_artist.png | Bin 0 -> 30857 bytes .../examples/music/index_manager.rb | 49 +++++--- .../examples/music/search/index.html.erb | 18 +-- .../music/search/search_controller_test.rb | 7 +- .../examples/music/suggester.rb | 76 ++++++++----- .../examples/music/template.rb | 105 ++++++++++++------ .../ui-bg_highlight-soft_100_eeeeee_1x100.png | Bin 0 -> 90 bytes 12 files changed, 300 insertions(+), 143 deletions(-) create mode 100644 elasticsearch-persistence/examples/music/assets/blank_artist.png create mode 100644 elasticsearch-persistence/examples/music/vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png diff --git a/elasticsearch-persistence/examples/music/album.rb b/elasticsearch-persistence/examples/music/album.rb index dc3dfdf07..a805a2111 100644 --- a/elasticsearch-persistence/examples/music/album.rb +++ b/elasticsearch-persistence/examples/music/album.rb @@ -12,9 +12,8 @@ class Album index_name [Rails.application.engine_name, Rails.env].join('-') + mapping _parent: { type: 'artist' } do - indexes :suggest_title, type: 'completion', payloads: true - indexes :suggest_track, type: 'completion', payloads: true end attribute :artist @@ -30,4 +29,26 @@ class Album attribute :styles attribute :meta, Meta, mapping: { type: 'object' } + + attribute :suggest, Hashie::Mash, mapping: { + type: 'object', + properties: { + title: { + type: 'object', + properties: { + input: { type: 'completion' }, + output: { type: 'keyword', index: false }, + payload: { type: 'object', enabled: false } + } + }, + track: { + type: 'object', + properties: { + input: { type: 'completion' }, + output: { type: 'keyword', index: false }, + payload: { type: 'object', enabled: false } + } + } + } + } end diff --git a/elasticsearch-persistence/examples/music/artist.rb b/elasticsearch-persistence/examples/music/artist.rb index bcf123dc3..17c525619 100644 --- a/elasticsearch-persistence/examples/music/artist.rb +++ b/elasticsearch-persistence/examples/music/artist.rb @@ -4,23 +4,43 @@ class Artist index_name [Rails.application.engine_name, Rails.env].join('-') analyzed_and_raw = { fields: { - name: { type: 'string', analyzer: 'snowball' }, - raw: { type: 'string', analyzer: 'keyword' } + name: { type: 'text', analyzer: 'snowball' }, + raw: { type: 'keyword' } } } attribute :name, String, mapping: analyzed_and_raw - attribute :suggest_name, String, default: {}, mapping: { type: 'completion', payloads: true } attribute :profile attribute :date, Date attribute :members, String, default: [], mapping: analyzed_and_raw attribute :members_combined, String, default: [], mapping: { analyzer: 'snowball' } - attribute :suggest_member, String, default: {}, mapping: { type: 'completion', payloads: true } attribute :urls, String, default: [] attribute :album_count, Integer, default: 0 + attribute :suggest, Hashie::Mash, mapping: { + type: 'object', + properties: { + name: { + type: 'object', + properties: { + input: { type: 'completion' }, + output: { type: 'keyword', index: false }, + payload: { type: 'object', enabled: false } + } + }, + member: { + type: 'object', + properties: { + input: { type: 'completion' }, + output: { type: 'keyword', index: false }, + payload: { type: 'object', enabled: false } + } + } + } + } + validates :name, presence: true def albums @@ -29,7 +49,7 @@ def albums has_parent: { type: 'artist', query: { - filtered: { + bool: { filter: { ids: { values: [ self.id ] } } diff --git a/elasticsearch-persistence/examples/music/artists/index.html.erb b/elasticsearch-persistence/examples/music/artists/index.html.erb index c0c0c7304..6747b3056 100644 --- a/elasticsearch-persistence/examples/music/artists/index.html.erb +++ b/elasticsearch-persistence/examples/music/artists/index.html.erb @@ -15,6 +15,7 @@ <% @artists.each do |artist| %> <%= div_for artist, class: 'result clearfix' do %> <h2> + <%= image_tag "http://ruby.elastic.co.s3-website-us-east-1.amazonaws.com/demo/music/bands/#{artist.id}.jpeg", height: '50px', class: 'band' %> <%= link_to artist do %> <span class="name"><%= artist.name %></span> <small><%= pluralize artist.album_count, 'album' %></small> @@ -41,7 +42,7 @@ $.widget( "custom.suggest", $.ui.autocomplete, { var category = ul.append( "<li class='ui-autocomplete-category'>" + item.label + "</li>" ); $.each( item.value, function( index, item ) { - var li = $('<li class="ui-autocomplete-item"><a href="<%= Rails.application.config.relative_url_root %>'+ item.payload.url +'">'+ item.text +'</a></li>').data('ui-autocomplete-item', item ) + var li = $('<li class="ui-autocomplete-item"><a href="<%= Rails.application.config.relative_url_root %>'+ item.url +'">'+ item.text +'</a></li>').data('ui-autocomplete-item', item ) category.append(li) } ) }); @@ -51,7 +52,9 @@ $.widget( "custom.suggest", $.ui.autocomplete, { $( "#q" ).suggest({ source: '<%= suggest_path %>', select: function(event, ui) { - document.location.href = '<%= Rails.application.config.relative_url_root %>'+ui.item.payload.url + document.location.href = '<%= Rails.application.config.relative_url_root %>'+ui.item.url } }); </script> + +<script>$('img.band').error(function(){ $(this).attr('src', '/images/blank_artist.png'); });</script> diff --git a/elasticsearch-persistence/examples/music/artists/show.html.erb b/elasticsearch-persistence/examples/music/artists/show.html.erb index 984f1f726..e1a9cdf01 100644 --- a/elasticsearch-persistence/examples/music/artists/show.html.erb +++ b/elasticsearch-persistence/examples/music/artists/show.html.erb @@ -1,51 +1,54 @@ -<header> - <h1> - <span class="back"><%= link_to "〈".html_safe, artists_path, title: "Back" %></span> - <%= @artist.name %> - <%= button_to 'Edit', edit_artist_path(@artist), method: 'get' %> - </h1> -</header> +<div class="artist"> + <header> + <h1> + <span class="back"><%= link_to "〈".html_safe, artists_path, title: "Back" %></span> + <%= image_tag "http://ruby.elastic.co.s3-website-us-east-1.amazonaws.com/demo/music/bands/#{@artist.id}.jpeg", height: '50px', class: 'band' %> + <%= @artist.name %> + <%= button_to 'Edit', edit_artist_path(@artist), method: 'get' %> + </h1> + </header> -<p id="notice"><%= notice %></p> + <p id="notice"><%= notice %></p> -<section class="artist-info"> - <span><%= @artist.members.to_sentence last_word_connector: ' and ' %></span> | - <span><%= pluralize @albums.size, 'album' %></span> - <p class="artist-profile"><%= @artist.profile %></p> -</section> + <section class="artist-info"> + <span><%= @artist.members.to_sentence last_word_connector: ' and ' %></span> | + <span><%= pluralize @albums.size, 'album' %></span> + <p class="artist-profile"><%= @artist.profile %></p> + </section> -<section class="albums"> - <% @albums.each do |album| %> - <%= div_for album, class: 'clearfix' do %> - <h3> - <span class="title"><%= album.title %></span> - <div class="info"> - <small><%= album.meta.formats.join(', ') %></small> - <small><%= album.released %></small> + <section class="albums"> + <% @albums.each do |album| %> + <%= div_for album, class: 'clearfix' do %> + <h3> + <span class="title"><%= album.title %></span> + <div class="info"> + <small><%= album.meta.formats.join(', ') %></small> + <small><%= album.released %></small> + </div> + </h3> + + <div class="cover"> + <%= image_tag "http://ruby.elastic.co.s3-website-us-east-1.amazonaws.com/demo/music/covers/#{album.id}.jpeg", width: '100px', class: 'cover' %> </div> - </h3> - <div class="cover"> - <%= image_tag "http://ruby-demo-assets.s3.amazonaws.com/discogs/covers/#{album.id}.jpeg", width: '100px', class: 'cover' %> - </div> + <div class="content"> + <% album.tracklist.in_groups_of(album.tracklist.size/2+1).each_with_index do |half, g| %> + <ul class=<%= cycle 'first', 'second' %> start="<%= g < 1 ? 1 : album.tracklist.size/2+2 %>"> + <% half.compact.each_with_index do |track, i| %> + <li> + <i class="counter"><%= g < 1 ? i+1 : i+(g*album.tracklist.size/2+2) %></i> + <%= track['title'] %> + <small><%= track['duration'] %></small> + </li> + <% end %> + </ul> + <% end %> + </div> + <% end %> - <div class="content"> - <% album.tracklist.in_groups_of(album.tracklist.size/2+1).each_with_index do |half, g| %> - <ul class=<%= cycle 'first', 'second' %> start="<%= g < 1 ? 1 : album.tracklist.size/2+2 %>"> - <% half.compact.each_with_index do |track, i| %> - <li> - <i class="counter"><%= g < 1 ? i+1 : i+(g*album.tracklist.size/2+2) %></i> - <%= track['title'] %> - <small><%= track['duration'] %></small> - </li> - <% end %> - </ul> - <% end %> - </div> <% end %> - <% end %> - - <script>$('img').error(function(){ $(this).attr('src', '/images/blank_cover.png'); });</script> - <script>$(document.location.hash).effect('highlight', 1500)</script> -</section> + <script>$('img').error(function(){ $(this).attr('src', '/images/blank_cover.png'); });</script> + <script>$(document.location.hash).effect('highlight', 1500)</script> + </section> +</div> diff --git a/elasticsearch-persistence/examples/music/assets/application.css b/elasticsearch-persistence/examples/music/assets/application.css index 7be6447f4..816ebff8c 100644 --- a/elasticsearch-persistence/examples/music/assets/application.css +++ b/elasticsearch-persistence/examples/music/assets/application.css @@ -100,6 +100,20 @@ h1 form { .artist h2 { float: left; + margin-left: 50px; +} + +.artist.search.result h2 { + float: none; +} + +.artist h1 .back { + margin-right: 65px; +} + +.artist h1 img.band { + left: 120px; + top: 50px; } .result h2 a, @@ -124,17 +138,17 @@ h1 form { text-decoration: underline; } -.result .small { +.result .highlight.small { font-size: 90%; font-weight: 200; padding: 0; - margin: 0.25em 0 0.25em 0; + margin: 0.25em 0 0.25em 50px; } .result .small .label { color: #999; font-size: 80%; - min-width: 5em; + /*min-width: 5em;*/ display: inline-block; } @@ -156,15 +170,32 @@ h1 form { margin: 0.25em 0 0 0; } +.artist img.band { + position: absolute; + left: 85px; + margin-top: 14px; + transform: translate(-50%,-50%); + clip-path: circle(20px at center); +} + .album { margin: 0 0 4em 0; } +.album.search.result { + margin: 0; +} + .album .cover { float: left; width: 150px; } +.album.search.result .cover { + width: 40px; + margin-right: 10px; +} + .album .cover img { border: 1px solid rgba(0,0,0,0.15); box-shadow: 0px 0px 1px 0px rgba(0,0,0,0.05); diff --git a/elasticsearch-persistence/examples/music/assets/blank_artist.png b/elasticsearch-persistence/examples/music/assets/blank_artist.png new file mode 100644 index 0000000000000000000000000000000000000000..0dfda13bdea1b87f705ed12e378be32a9a7b0bbf GIT binary patch literal 30857 zcmeHwc|6ry+qa66N*WBAX^?H$C_{)tDjC8yZ&TVNHZ~cz@ni^@DkMXO5Mq;IXC{P$ z5VCj1ZOA;^*k<qAI`_HV=eh5v_kBOlKhNir&*`-K{nonHTHk9}*LAJyS3&A`Z=axH zrlFvqIB`c&0Y*V_P>=kJ`XG2BOnEXG{C3n%QQv`r;@BDTuLBgZ@k|sH5)ex*Jx4uN z6-fljM$ps@b>Cdj)y59=rl63Pb+t1^Ak7^)@0(j#+Fs|LFRS9_v^2ZUtt+Z3q-uA= z{Gp|y+aq&Lx4T*hHzeYk8MmwqjkK#IAYfzeXv*noV{Pjo>3W@ekFF$WlOIF5Irq9a zBCm7HktgKTQ&s1@fqG=lDJm!?fDjTB=M=vtC@d;2epQ^GQ$$Eu6e@HDdPPV;<cj1~ zA<3&EoPYjs%g}(9^dqwek}!o^e@+Mfbe;R5qobW96zbyQBIt5O5cS9cDtztQHK>pX zR76Ao^bl}B+d7)M3fMZF|3Ty*bQH`T5RWYF94%3{oMgJD_fbxc*SWdL6aDq`=e}(0 z{+h_v;ZN)UicnWmJE*Xr5cJ;-GDG||#?I-H_1@CV5Kwb#a~pG8M+Y!Y_}`ARdx&yG zIXp!DhbjMh{U0X=JW*BsYwmx$UK^W#JGF!3O=keXpOF6Bkq%mDJ98+^+yUkE2w{HH z8EobJzuUc|<%54R=U*t1-~7k5o4Z>6hhgM5KZfmz!VeBfgS|@Lcw}zsh<c=jLRriF zkTUguK*)LH#-3DhURE_lSlW_t<&}p1eb7HS{!e=53Z{<cGGvJq5E2&<64nwHmK41v zDJ%s3C@CbgkI0Xnf1{y_GP8Vu-bdq_fY4Pf;VY7&5|USi_tV(d^Y1i(2AP>Un*Lu= z+23~`9W#XF1JolMQ%4y~8&eB&sGY5aH1wYj_x1cst|V`utWl4^vdm?!NJIbK<9(y{ zWKHspt%IYfEyDbcf(&>dXlZFCDU1*^H4_sN61Z<FDkgB%%uHP1zL}`0z%_AUvHRkp zLK0@;!h0a9s_q~DUq~yU5Kd%$`IEHS|0C&r!~Y9uwMUk~)|p!G>qB1s|LYY0hRlDN zO3~5*NGN)r<O2EKvu2v+_W%0yA1|ye_pFkg=_3bovd&%S{)f{2Wyb!pQsgmvx+`gl zAlow;1X(4_&7`6K^7Nm}`fKE$!z}-Q8Tf<#zTy9Q4wr}Kw*Q?%{UEj1>pv55Ks|7D zF@0n%X8~m5|4!R~(EZnef1f}aN<IOsE&r7jKc4<8hyEe6|COWCWWy#2qyenVbdL)_ z!2ip$f6n})zaE*Z)=qo<#YKd`4^)`^|38QBfBpBN`v3jV{jdK%^q(WOZ7m&TME;!Z zZ;$tlB3s`dhCvfJ3g$9mqM}zsrJ;X+{Pz*+magX3dJ2|6gB{3nDMsGd-k`s~`L`qW z|KrGgZ~h$lgIW6y^*vtxcnVww;H^Oaa##M9&3`Gw|8V1<Tlya+yr0~!N%o`h)Ald8 zehSh44!_{qkH$~izu@{QMEg7Zf@?n-KW+bl>!%Ry@9+z*{b>BO{R^(2LbSibFSz!j z@zeG%xPA)J{tmz3+K<Lh+rQxYDMb4_{DNyg8b59Sg6pRc?eFjluKj5IwEYXNpF*_1 z!!Nk@qw&-BFSvdR(f$s<;M$MIPusuX`YA;FJN$xcKN>%6|AOnM5bf{q3$FcW{IvZG zuAf4*zr!!M_M`FB_Aj`83eo-!zu?-B#!uV7;QA><`#b!CYd;!4ZU2Jnrx5M$@C&Z} zX#BMO3$C9+w7<hIxb~y*)Ald8ehSh44!_{qkH$~izu@{QMEg7Zf@?n-KW+bl>!%Ry z@9+z*{b>BO{olex^Un_e&27O~e=gwjzLY;UYrv<0oCrmjDg}kxMGA`FeJLo`cfs$I z6ckQE6ckhUC@3UfQBW|VUYOL~qM(rNzM~+g<=QjTk2W}~|0ZKI5X=3-&n&@0;eg!P z7>%rrWtnll{&5-7$ZT1e(#_w_(%(Gw9L;{_(NocQ*?figcXaBF-?wMSyZep$wXH6E zYJRHcTUK5<N<m?#e^l@Q#qV?nDSo3qK*8xpK|v=^K|#$$LE%S7K_O2~LBU1&zvcD+ zuGRiGIRB4BUmPecc;NToMyLL^ou}BB%?IVFIkR9cpX`kqdK{-_OhSacx9N3@Yv?(4 z8zxn})(k?hx4of2A`F+8j&d2|yXqgN`DlqS1YbNIWX63eyaCo|28p8a`7Lws2JLK~ z84ZEBrT)iB|3{B%N=izu4cSS=$3;hv2gt*excr1CMLKSR-7-F*lcye%xJySF<SNbF zPw4Ny7C6Vj&iZLYI4u&^#6-j^&e!fbu#S&Z$tGZ{b}F#A=P)H+PeNNuu)@-LDyo}x zb#?3h{zG;-<LkIQ924R=$AO)>@q*D6ft-WCbBL^+k*78-3}a)To13%F$(^6G<xohG zqhrFzqJ=i<rawj$aGYzf_j{HcAHTKb^P(ev>FLuq!!P>ErH5z`S8q~sQ7#nJd&8{K z-n4Iru3)iIQ56;aX56NjJ4K89-BPB^p~90Ug5u&u#i@nwO5c{}i^x{}Mt$d$9QA>% z46dYS9rp3Hz7su7OG_OcbFI#hs7FS(sn=6RNCNdt$PlM^JBjqv!txI}mHDOMyW0Dh zE;upsG2KClXiA#NJM@$+EYrab^LRY&bkM8mV)>o?9Z#oMuLMmloX&iglZmp1@bNLi ztS~V;H>i(t{oXks=eLj=(lElO987eWFDeoX6<*?Ana@Hfq+plJm!3A&^R;w|Vw2Uw za8FrJ%Tui7Xe3RejiuQQUd6?Eil6BxI2h?F#Rz-<zD&{X^fXvGE*JY%^y-TnK#h1* zlOl~pVqSU6N=aQ|3!OWr8+cr4Fuf2LhP`?5qHZo$?5gpihZ^s!#P?@(2S4H!+pfT& z0-vkU*Re$f0;wucy3dZ2;{N8N^ft3>U7}7o(?U^CKu4aA;9+rbvA4!DCME_>@gZ(> zZXK);JiVyX%B;98Lv-ZBx(FDUKWs!IY#iOFFx)b&J3nxjFHIVX6i>^?Sz&;LVenEp z9}3+nbEAfj0mD`NiA1fdsFouraNT>=4CR6+mIq6pUoKCzc==LWtFOFYQ$r(@i5Vae z?acW~Db2VlJ|4Y!sX}lVw!MgjCCr=5Pgq-)&C!^;A{BweLn`M{C>zy~a2s1&!xxVS zqqDI}nXx^kISG|MHS+Y79;QH+qX#PoOJPmxq=L2tnLC%x=x^Ox@QbA3UZ?86?%CzE z@;Qmik0L+*SqC$5X}w@lTyyf!h2l$LBM&kD&%`BBA3nIGim!~t0!sRdZUAWyNwNH5 zA#Y2L054@Xc}pS|mosO~d-2$bg2P9M2iNSR7Rnzw?k=VbVKAx$QVC~bIEx{kxaERM zA6axo?cS4Mee2)-IDnmkwZYzlK<FOuT4>m{wy|A+EzCo|HgyayZ0~-eJz1jeNB7%@ z5Gqe^gs11)4#$!9j0U|0N`bzC9yixr+09M&Lcq^Ip3vmvVr1ObETq{-K6rHmkD=Ym z%1-WXwav}ZGKPW<_h3z-SZsR$DI(~TxdGIBm4RI$<^0JVGedx&*$k<QE>F+e!%-qe z1y<dO6=DtzlP>qRqZ>U4xDe9-pWdvT574`0|B_*Db}mfX^;uwpB^MXvGc?!n(eGcs zb`=~S3;}BzVC7)ZP|Di{N6<4%fva!deh}l~39zYcts!kkjiy<sHL&4LT~z_k)=-;> z6b6iwsfN8in##APplD&ctO#XX5vdXn7$GRqMjWV$>((=obxXdOmYs#V$K>D)bJK*H z@IzbO*^$CPp@QMnn3m8Wo^ail4B-ibmKeS)<P!I~^##vcIn&Gtp85t7iS$b12AHmV z-kIxk(5KD@1bjL)6zeLTV4Ey_2^fabw=aDy+7RlO5qY&iiy9bF=rKU~7-LN|G+}ne zdb84-DbzMQmn{^YjXe>nnlOLGe9Z7kKQb!|_11VXA7Eds=wd-Id6nqCYkR5U$Wy2I zy7iJ2i)8rpp1D$b`#E0sSh`6S+{DPd;ha46;F}PtlP777WfJXPS6w10^NNd$udX_W zANH4Tdh(V}x><@bXW9y5q9T1K?L{{K1%QPDb-Hn-MBuHufSHmUmJ1Qh!N=wTdJA>l z0h5-L5h(6IF0WKlQ6aj$fxU-24s0S4olultepOa{vpe34YjkN?UgWuA_$dr_$gVg! zFQ_DC+;!CAEx{UPyF}i>nz^BFDlAhs0-7xrwz)23@9pjFgr1z3%)pgaE-Kj?`!OVR z*17m{u2?OVi1wHs<^^*oE2_1&RQ7gnY$Sbh>R&JEP6^tnf3@Y~Q~KmE*z-t|osARP z(*Cjj6+gO<xofKo!wZA=9&E(rW~N3O#KrX*#Bty?m^vLttuCLv&sKLW>^MQCLg``z zKOfk#mmD>r!qdUdZbAGEBNpZ`Zy~#L8x#Cg+3H&OFzb;~7&tDf5<MH@<N=^Oy6U$` zq;W!<o3|8F!)>5xsbgSnZczh4#OkyWna(DRs9h+y(i!9$pLQ0krLAJU(w!x|p}(qP zy)-9I<-*uN)ftW=cI`BCb5X1loKJ8By`wHdN;shiBq0$!-ftk${Td(f$kKFYVd2{u zEqp~!#}vA<HfbSVk(ZF}EjXkKRAJ#tWnP-zD{l_j?g9zjK)o3|Q`@-_Nt!gJX1;e; zR`e*EHq`Iz{R9KJ;ox5X+LD~WhWp?o;PIy8@r#f1WDKqgOGqgV>+NxV4S2d)^0}?$ z9p^oDprBiNu%}&Vbq}w)EoGjQ5A>bjf#wzJ0AuZR;skv0?LxT>V@X*>Mc#+{JwzHh z;86b0p7R+usD(+kV{4ypA!o5Kr<2ubeQSvKLlP3>M%{p3%Q}C{E2scJ0-s1Hr2Hb= zCw6MK-0iI!urE3~p1i$u*ngZf>B=&VB#7!A0Xy_G#2dlm@l#i!Z~W{C6|ch@?%yu5 zM5rhmxZ4^hn3LKaHXL6BeOlZ$0_SV|yr<5Eh#+iN)I1xl7j?4qH$q~Zr;B5pSpC_v z48}H=<{J|wp10()`2`NE!~m7|qU5bx#o>nJmcNSYbB)fF6$Lann`<hGo7nI|1Rr!_ zi6!GSd1Yk<RYlF68i4NsdPc^$o_HBQ`A@X84=e_}`kwU0#`pE%rrB`t2k7{QjDBso z_mv;UO8dM3CyAA{mm(TlLJL}38DXuhtuwQ8>?S6JFW<DZjQ}q_WO%u7IGsqCG}}c# ziq@4l{XpPL)YjHs88G&zYgF*qSjkE$FvDQwn^sxG^-a8u6Tj}v4GdMwZ~=jBMJp6z z^K#k{@my&|#X?mjm0phV**fpa-z+H+MeATsiEBW^zOSx+xHos1i_$JEL^zIxWe%-U zZD*v*yYgTvP3D7D4O2gEKto;qp6hRJ>>tE%-JS8k*QlB|;~LKUCg*4p-`OeFM#Skv zGmei-x5?7j*k0%uKnJI%j4z-|=2}~68*pdjDaPxa`X4&VE~=%U^qQ9?wtQ_?7#?|x zoYcR9T{)pdWk|eYWB>@(s)bb^1S_d$nVwtykT+hk8%+1e+r{O<MoV7WS)5aUSX>b} zx}Q`$XkEQ=i;i-0^K&%dQfm;_+1q=*Z#S=lI8gC=U|>Mf8R9P}C|EMkeuuiv*2SXN zo*JlwonURS*3=*;#1K8V`&|pNK2=9t))U%hjDqPJz_YTDujJfrwQqZ3M19FR%=#!w z<-+}dfE<ZkgLmWTEIwu8yhV7JP7#iVOA_TW{{6d<p!9YNS+9hsUmT|a{B)o;<#%2i zskg|PzFQF=!q)q{7ShNN=C*w+U6?;jqBb15=qmEu>^Go3@#`-kpN58p!rlM4<R>j1 zSn0MtyteuZo|%P*Bd3`G+*5QAOpJsQ=-MFAWe?EdC@oiW8|d5Y9!*0Nc9Hs~8wVy^ zdat9=X#PA|Hl&I)!$>=skvjFST%~tzB)%`nnbzAJ?pBytSXevM;Zsu+XVj0g*E9Ey zJ=xl$z}=cc7TN3|k@^c~f;c?0!y`pXt9GYGk8yo!_v(YFuY~Hx=6+cCec3V5n48_A z<`ke!d%ng@Wbup_R~vEc0YRX(ZIk1{Gs!S4&e6GF5Sp9hPw?DzdTl0R8DdIB1^VOY z4$Cc^v3)yfGF(@{)BbRI;y|WwJ!yNc^0A$*AoN%j$>*@#M@@6`LhL)>+l2>@a?zeV zX{;iBZR*X41o_13$mzWvHvPz8H>d3`6)$@zuo4z&kz%-=!gOT9I8;EeJT68?@>Pv} zJSR1CY))aJ(fEN$m!k*k!fAnN)eoSD^C3UC#rs3kTvvwI>o;Oai{{BG*tv;vZ6i(P zV`{DN+CXsPNEjSpYhd1txJ{qK=c6?>x%N<cXYtu3BqlPytVs5B&_ez~DwiWGGKL=< z-tlO?MWVOrB)UvWsx2W*DzTFPGnBu)zjvOd`nT~5vY#Yy%(Xw@O`+LT9=NhzStTJW zzWrco?GTKf5_qbwi)<6;hXT9d@|(e4VF$P<^QWDsN~@;2V<R6~E!9mmEiDFJfoHz_ z0PN2+WRP>yo2juZY&suL=J1E1q><8LUH>mmMwpe(#k|_u3<>9F6~d(jS!FqSK@2;c zRE~brBA3Wk;A*x+<#;#r+*x)75>Gvc0V3=Jx&T+2Gcn0_IV;2L^rY<8WiG!Fb+X9w z6Y60!x?44)4->|7`Jt5_iz-dw2u+0JWuC9%5|JkANihl=vmECsHE#iFFE~MsnctM( z8J;)it(e4XUg<>180hP%tA7)><2g$s9JI`mXcXw;2e@!81R_1T-9zx5E|m{tY)cRn z<aga^2)d$_xzyCuTd^HhJBKtG+LSp$1l%2`<B~nQF&eXu9R57=e72>fjX~?`RinwN zNzwW*HEp;2lGXp%P7BA%Q9rgWj-lbI8Ctkm9HYb=(fDcP`A}``p>@;KmTrcwn6#A# zx<$gVaXX7`f;_A{qv6=<BNS#kMR{~XedWhaGx1hzZ}&Hx5BK3*Tl>-m$2cL~Z{N0v z#uYBqGWFO$av;8fka@lCHB~y)w@_D-=$25G_d!xvW=tZ?-I@oM;<Ops`XP)K^Gab# zBJ6zX@$s=G1Yo6|4z5S>f(fhp+4X6IiA@k#C8|lEYH+T|!!aXl2c{}<xcbM1I5^(v z{B!C(Q|+ZJ`dqOxa!I})cD>Dgb8O{1(Lz2@4gSzZ*9AqyzErOIGVtWlBf-`yHX!Ks zJvZX#PzwUkU}xnhSf!WfwUtLOrS&}jdWkTHvh=D&B?r48@q1V5$3{o}1wT5P(8+W1 z)iWf=D_$TNzdyubhzFx$J$A-c_;@Q2aji^TeFKk^$FlH|rKxR-7%cj^2Z3~yY%ytz zv?kI1{x=yHUd9?flwF^h{Je}rTX$6n-zv6sA$-K*rlXzNE$!`(np6Nbm(;y4PW|L0 zIIQJ?cQR`5Ty@;qo(b``?mS1tU+Eb5uo|ZPO`c1^{?5@_IUsI*hXLujft#MKFO3xz z9X%Z9wzKC0!gfD|=82<7j*bHz(UzDB(Mznfx4GB-$U4fg=guj5Rf;k$*F*f?*UOw^ zLGR81F>GB#r@{%v=xH6@BGG!|Fo)StEENdz%cWPjdHg8u&PQav!fjNV7{aV(>Ws1k z{e<eJ&#}bBc#W{dNg+TKU4(UUURl<kHhdzchiGczOMT{X4jf(S-_Zk8O-jIoe(PSg zMc*8)^H6my_Ya#^36V|k+FmMnhQ#Dx!7(<lC=D%ZgQ}?6Lin=Y$2TYg$qAd^<ZD(A zXWz=hmT<|&6Yqrx3h*5ZaDc9L-o(@A*a5M4?Y2&4bjHCh$29cBK-JRHlH;miQBnQ( zo810&b<&LB<iBfBomQ83`=ll~t#L1jrXT!;FP(0LLU@HY-VlsCkq~1U@71NcPm7)w ztPMwflJehe3{>r86&I>x&KTOFLU^IM#{+6#(gEEfG!w~DPu}w(PHGV14GgHH!ESRs zhw12OTYOSbz&rUzYC?Iyy6(6vPE^4lmoGy~Nx9zmucDN4gJ%8ZO1gpXVwo#n0rl_H z0)Mv{dZxXWGwKDbG$)Xa9k{%80oNGoUo}m|#s}Mr4c&oH+_|>xX5}&S9JjHy?hnsY zzkJno+;~->NKKYeK!C4#j+6xCW}WEiZDVfkxF!dQQsTHeRBHQ7$;LK^Ba|A8158Xa zA3f&~;bcUPzud*Q2E$#Yowp3#5g~MM%8GIevU(+PX3AoPIBo5&k{K)r<vd7wU~S9o zzFH6`kMG}U@=D9IR~pb4Ysfi*bNIEDuC61y>xr*l+st=jEJB3wC`a0XbpamM*04^9 zUnJn>RdNhcNn@Lh36t@7rgUjEJ%Cn;*TtoX<m?i@Q;I8nS2}jk_T%<R?^|uX1cK*U z!!Ga|*$Gct;8{LNIhBh=-n)rUpEjw;)~O?nv<zR|x#0{Xo;&2F7#@F%eL&4iqxvd6 zWmZ=6@QB>PB`6f8sT*xy1m~?V;}%|8*!kRR*+vXgyQmGh%+FuCnd@CtXry-X%$4QN ztMwqivX=a;)`5I_ry{CXR&Jl1=ZQGF9(riPNH-h9e?-pCS}wuz;lri!&r*cjik6}1 z4Ohpd^-mU>cd$CJrn!NgfgQOv;uU)Z^N_}1g{f(l6MOpTN8i_@$ANa0jZtKk{WuZ) zII^#|zki_TY(qlW%gC2HI=UA31q`|T=s?IDLMy9y>2#uMqK1;F337ebd%LW-Si!s} zx$@R@PtS&;m(2785Rx<6cJ2;{??c-P+^d+p!G`u1uwxGM{b~*!eSO@1e|!?R%9#dM zRY;aJ?!DuV@a!jUx9Rjnzl<Vmn(TIc_0Z;x;?>B*Z6vE}I$Vjk`GcYblGyGmJF@tV z5JDyE#iCqNn$Ox#kac^Rfz8Qy2jR>8qc7dAa{e}n#J;mGi!!NVkoPDZHujfKuB?`- z*!E3UzkL2dgcSxkH5II^9CHa6`7KSV>nIE4bi427qU+WLdPmmV)Xx<^F*1&YCxpR~ zX_0t1JUl#2BrGyPc&s}jZ?XNh9Niyk91+ZjSOd8CE@?L>`yGtYT`UueBbFltFxL4% z4!0brTwI*luWwy(c3lWpQ;@YuHZ**teChP<QBAlm9v7vnW3jv>g?CCDZ>fwc*-hN_ zc5&~OU43=6BLB_%jJ3J>-AeoE4|KydJ9n@!tkcTH>{yUp-^ud<sT5#V&Php2$ZT92 zO0x4tq0sh2BZP;*UBS+7ECxJ1*F@TWJ&W1&-XhI>?rmcS`!&K>*b{LT-rKt9n{`x% z4<BMqjD9aH#1@$O0BH}PnVX+IA-gs*$mv$Y^TvcWVW34N{Y7KS-OFhpTC|#)x?<}y zs++6!jHbTfR{gCf5sj(ip7|3p)IRx7pStgE<gyf%3if<a=LLMjriK*xGDdHC?+|vw zvnaMU0C7N@$XPm%9+=$Wk`L75X9!XNfoRwmjD~CJ7|lxt#t`9i+m%8_Zg?k`4@DrR zIRAXBW_Z|<YPTs_SU&LGRP58Hrf);0S4$Ij47_INyO|=Qqw$@x6L-Sb-vBEj&d1bG z@LnEl^WCn{)LnRJeKwd4hZjGJE^H48Rp6F;1~)O-%!v+T{NN93GULuGEPkeRQCX>3 zKKaWc{aJ4TILf-|==j{MHBUriTdUMoV<8TBX}a2YAy(i>RW7nkpaa?`QUbi^=3ird zCKpYl8Jb&~F&P=sO}^N>_IDaLrj)p!|KUUOpOk$&A`v$YezMT$=WPK`fWKv+p`mYN zkQLcO=vCz~1KwkzQs%pNQwBxuOvlKEd2KbqcxGV`2sSV=ZN}B-+bb$!OE=UnFAwb? zZ(O@J)Yq3*M>T#>T0lBCIr*6gdpK}?FK9Ntc+sJLxq->u;~^7m0CIW?Y>4(GEiLVv zNuDg(S(k^Q&lB*nt~}uwGit8iQCjNt2yl|@_JO>41I=EpAzM_`>BMc43Buac-d@_d z7>kXeI;mL2%AAAA`TqU;S$f8@2C>dO92|Kp_~{fgbM4T=&VaF-%9g{UjHuFq_U>;U z+7@+_8h`^QGww<6OjIXI_4bbIRae0;e;OFD)koiyldIM^X4JsT%V%QJoNy%d_K;eY zsG+f;ghU_%J>&SO=c6&3jgp5q&9zl+D){;)0D0dfj@TP%TISkLE+(=k$%>z=EA(73 zM0(4?9Z6gh`Q5?V7Y1RM&CW$hR&Gi2@bJi1-Lo_uXnc7jwV`*ucy6wBb!qqw_5*N8 zNZW0f+Wq8{V`EF3rmW~G+b6u-4h)AE(@vTo2U{!p%Bg=$pd}Qe5+Yq*g)P}R4|#RT zFYs0haI&5@y+fQ-R5dkyp6BdobCWv8oDt__#FYxO0>>W$ArK#7pr>^~#uFB6{8?R> zpsr(lDiwC*h(^lFdRTa88=}n34X}lNZwe^iE2T@f*=4>Nnh-s919sq(7hUM1v>Q0+ z;mGD@zEu)PySey9cDwj@Mm)TqSDRMn58~#kBu*%lu<m#_6DwBr{(WU~UTWdh7!`I_ zh1qpgK5Zo<q$RS-K4RrGU;!TTN-6ZXte%O%inkqA=N)RFLC#VW{FlpqjxsYOe!uLD z3_}Bhmi%b{#1nyR4c+|%#_)KxixI#RyH-+ylf2DX%{(G;*HlBp^9JJ19ZQHJJ3DLq zwrM6z4zPzX_-wkgoDHahOqD1feaLmU;?n%BGZ5G1;9Du1Ku2R^5zRC<ckkY{x1Vh4 zxW9o*jAE3&e)VcSxJtqBL(d^;js?qKyO!9JVsBKGQCyru-U~mq&$pqWOot3?EA%o` z`KEzHd~+7rK_qforq#2b0HMfsAr1R>N3VfFU0T}Bn{^E)3=C(_0v8F~TinH|#;=P6 z0QEM|*Ei6&H!OPp{twa(ld<>jGcqcZle5cfm>pbQlYBm|C$KW7-0*t1Fjw*7^y*pQ zS;;6rkAzP*q|_=02g{cDOrB;+DH0QZ_YMh!T!SJ+_+naG!Z=0{y8S(Btm;yL{Dm`% zz<XN3igE#&e$2Qb9e}eZ9;2CNW(Mg4zhw}lYl&FON<Z9Y!7=0MD|~NsVVzW-fgFVl z$Fp;Y`r8}=f}eM{z*g8nQVM91kx@nD65<#}AUETESV857SPb--Y-2Lrq`-vEO9nh% z*9hOz23?SCNfxbVj);ra)if|Lz&~RQN`*Bl@V^aR$AupQiuoFZP^vAu1%r_%0cuCI z>}J|)`ucL;4YtpJzvnFr0#58oYo2BY1{<ZV4YJKxQBt(+-R&#Tn;WmfM$_s-SF{@> zpZoX#L$dDpz|u|N9^kd-4b40HF$f!O_LDx5*u>*>ox9Bj{nrI-@R|1x?>;LmudLIi z@cMKD58pJi{=Pj^W@1o%UMUlF!S|vl8*ICCrb}x)SIxF&<M0XNMo$>n+u`Y?d&trb zL&BHH=tNZL`pgLvw{d2qjia|W$jh2r+Pk>iMhc{<$U1{#&)NO9#Rf-R^mUNt8`@N& z#^<TAhgT$`r;S2rBstiJS5{9NmblFl8A|7Z$Io<X=5~-s$}6VlQbNVh>+36_swYBq zpFVwRh~E~mmZ#=22QDQFq#U5YtX&vhWDzgx>5<Cd1jNm$UI#7a#T0e!_-76V#8sY> zUq0E7K|S2c_LmO?Irl2Bb$P=|hPSyP?UIpaa^B_GmUkE$YfP?Jwr>>p(@k1t<cxr8 z5s<A1mx0SPo|QAeTB$u1S^U1(E1v7w<{)US5h9#0eqvx7Q~Gw5$e^^oy7KjV!O`h& z%|f1IS-uZ2Sh_c<h1tn@nWZ1ZPB#{XAu7dwQ#r}H9t%PjncP8Q5Km{W2|8h1g3-}1 z4NDtP>+9kT&CSr7ieOM`GkE2`yS(_2j#7k$bWdviVm|Ks=uIHQ)66W&%FSEqm#<rW zp9X2WwKAH!AW+!o`d*Tlz3dDBChJ)AuJqEHV`<9#<}?yHf;fMqy@I1i!1b0;JyS~B z^3$imS;#3!);pTur_dn>1duHS`bZc^3(RzV4_1jmy}{D@%U2w_;k0rh5&MN$4pNip z;kR|8qrYE_Q`*pmP)Fi0_};Qo_wL(~V173$6V*yr(RGzTl~~LBA*W!=%O}K)EpbY0 zRaMHoSX}O<#41l>58S<xBf>4gW#j?A>!r@ydT_sGk~=u9OO8tdj~hlL3|JB$=(;HP zI@yF=7rP^}ecQko?y=*z#mT&~rA&JD6`mCZ;pHpq>Nn8PU+He6A-hbmMk3c&fDf$s zJWr}X4^b$LhxgbzJ1MJqiJj>nZi6e=?*&11{_D$%i}}YXqaOJe_jKUeyC(x4U5yI_ z=d;O@2WqS(4qO*q@gi6u52d|BTDfl=Isg0*3#r@b(J=njwu)E}FS^hN+Xl7Ezr8Om zoT+ay0LlA$rZ8=8;68xsCUDV~7#42cGI>Id?kqjM3u#4HM?0&H{ZMJnI6BYQ7oTg~ z+SUR|6WsayP4kj^mipy!IMM*5vM-+<zIqg(A4l)8Yx`hpF#}OxHCuv%yX9A;6r^E{ zs295tFfKp7Ph%TBUz;1r>TF*yc_$O5M5Cc;;$5+Q%l^)!1xf|vz&&8q;Jy*CdXlX< z-%cx4%gM$_*G0`#BX8sWlR)6zx>?$LfrRTSPRH%8>XUJkYhl21>FycO&^C(LdHj2> z!`?a!;}cm}&b2ntuye4Fj*ayfqq7#3h|%srX(No3)x%qTWigW#ui1DGIlLNBZ*l4e zm*hwRd=?UT($dF(;oV90X;wa#QhO2$<L(_-V@71>bz!lyuZxtLn<`5WfxBldk&^uK z<(>ZCSqx@bhM#da)Y7fsY8$DU50YzGp<|>OXDr@0MGaQN0VxJIOOji^W&Re}od5JU zUV0jTaJP#sG-^8<P-Unb&CUi%6T{&d#s+e8F&883@$`&#zrT{Xjfcm@#p!BmcDkmO zWM@lnLn0{wr*{pj!41Ll0Fq|rSM%Nnb?}rZK?tv<B{DY)wd^_H{K!M%Bd(i3FwlqV z=^OX-_NzkcHb?pRWtZDrjWbdU3v()yOW%q?WeIsNT^@7#eGGC<s4B?=ei28b*;0qC z*vl{zeKZ=dRn_KoWCG5l+wvwgZz{FVJr{SRIU4PN5Gbw6_4#Pusk(IsrQPYCoR&BW zG8cMUh9F@H{?jt#zdqmGP)-)>w>|@_xUrNY-t{Ak&E=zG8nkQ;nHkxoZWwHSX%9FB zTTD0jX>|iRqxuNFy?Q2up1yCu0ljeg77}D=xLQ58%_Ti2DV5qlRw7tgsfmvdxFNtB zzfLHs7or5k43%X*Ajert+C3A@rZj0Ux6_mi3!4VfpqOz9T-PKqF3uvPy<UPK_@-U* zRQNn7nwYhnpPK=`$(cnxI!YY80p74Zkz3VnUnz}SbG4s65{58pa55A>+tk?73=X%; zyaId>US59w&qv3VxH=4K7Z~XL=p<CBfEyezziaPG7sXD$)im)19$}*X?aLP}O%oF> zE&kN4xb(0bI#{4%EiBVanjyT7_T-!PTHlA<&p}+;XvR%_kZ7t!(k;za)fIl-b(PEP zGS9a6HQ<gbdU;)_;)I@VY-w5XBslfwvh$s!lONoKFon*Y++A<j*-5xfRV7eyw)*5~ zWK>kwS3C#-#%^-YnWXo45wG_(q^E>M7^i1McEBxdwCE`1lz3P0imgm|CpM7N;k({O zlx`zkZzH8fCh%NWqglj%T(}{(uKpYK<AN6*Qqm!`gk1#6C7LF|Z(Hcl6>(^8dHI|C zccq2p(y~>YMonk>Lk#ij)71|*Oe&WSw!_D3i5kf8?IWN_Vcf9Vb<1vTCsE2EE_#8k zvVcK&JO7J4xDr{XqG}NkIB`N%rTx>*`tZ_^7W9!p3Lf8wd|%HBKwM)va{L$k>{xl8 z2f8PyoTND*Pi=OX%N!EJ%I5VRn|GJ0O`ca<OH0~uo>cRO+j$=FT_n{<K7*6VbJ@<* zrYqgYBI};1M*EivZ+Zp>m>kF_A|M%9c9wI-M*44kEZB}QFrMt+uK1k>1d`{p<Pe|N zCQ?T0s35LmG%t_$Z&oBF{dlD*hn$Vbd+xt}4dPl)14=cH_itZMq~R{GJ!p8)@WEu7 z)fu(=F+v-x`iiy?^;2b~fH@60A#O^McRo3EZ3)r|uhGr4Ru(MB4qr4kzv1E?{(W5Y z$7(M7xh`7Yf3Sfa7iN&|)O%l8?lCbpU-F!qUg6Sk`(;5SRhozB`f$rv<<>}vU`}cf zc|Muj;S)dIu0<C@{a7V4DROD$Yjb9FTUun*>cX;0?mZAB4}$s@2Vw>%HP<s?w#Wq1 zk8_(VV=xAyr=c(EN-DCG$QGq1<2A}RKzcy9E1Sr~D6d{!p!Gigoz>voQlI#BT7Wh+ zfkbF)Rgp$}Y+Z2lfYMWHcZnWynH&4AE@yV=QuE+xZ9CJQZBPpUvcjW$yT=jLQ?b=j zB*1}F2hZtL10QX%{S-)eX)P?%XJ=w&IZ8oYlon8wKT>IOa`?lqGwUeb;Vt1z&$(w~ zWVHL`I)Ri)UiCGT;mWGKN}1J(WEz;(0zW+{TbN)eA3^VpP0B^J5xV<;6g*S8lnFKk z^h?%NSIP4D-j;xI85n3y89K7~jmsb?A<YL=8vvgcrieJsNC~Ds-a!OKPS1~cv$HE) zhq%Ha)XZ@?%e2SS!mYpi0C_zJljB7M965h-9{gxl7lcs7<y<8#pWG9mTF5KK3%5Wh z)Zaa(7o?dlraq-a#LiV7;4*{&t8R9jO92waOqS1*YPquXw~<zrxDIf^Qo6VD3>qs8 zhb;4eo<W+AMaDflY}%|dDse>OPe|ZoUmL_5Zdwak*;x2A5`za`2*xQp&4<(Pw<mT+ zc*$09Z&F8=!<RH4K41yu{WcgZ?vbjUzE{ZDX-*>FWk0&M#IUx?xMUxT%zFmz4VXE0 z2=y&6I+V))Y(!f^t4h^_v`_jF*X>&t<1K=SADCtOz?wkSf^n4_Z(74C-}J1c_R<vM z18}V%981=Y7zMY1xJ+H(>L~MO4)XFn{2Xdv1j?4IRH?vyM#%6cNd1We>NG&eG9eE` zKqkE3cDh)O{PuI!VBbucg<8E)L$Xgg%P}|t)jwxhF>v?Km&03I5e+Fgcvzflzup&k zdRF>8eA)>^zsJmMpDe!S5kz=MyS&|}K^J8dgF{)-yp?jCjusuK-yhkzx&%;8#lqsn zM0w!IY(kDt&a^XW#iXR?P2{5t&fln?-!aKfs4Bo-licEr87^M$)O}y%{XTQuijLBd z?sq!w0?5Sd>?{bM69XG0Y<C>#&&mT)5EB#Clml;T72BjFB$zzc1_uX!pGDl&mE$!h z461wHI73fK%_V~Ifn6HTYaiNlcQ4agxJv)Ehqzt&SPFaM#NOh{X%1)Jn`vKI9lOM0 zmFR<$JWI0zT=Yy#iVp>s!jn_-m($q8X>&0-c3<WuA+I7Lt_T6w_U%iWgMM@|y1?di zHl%<8w@Qv-pZGUELw2r%ttb!)?a>wjVM>k^@YVY^0WV`|AW?DA;y|(Umy{sXbn<4# zRQ`0(gzKIp=UARM>CDI8m2Yh&msLf*jN026QRH%3e5}XfgDLu~F*P|}ZFO}aR}yLX z1?<P@!{qxMx2cP_k0*Ta@xOT!7%eD21om`qY4xNE()<Hszz<$NrnB_XwEdfyq$G9T zI;sNzlpLQO;7D~Y3e)OJs#5tTrDj_LlLlg&y>)+tDiBPJjM+I@?x2Disn!MM=pv(a zj}L4LK%w$f2jl~fZ$XyJTUJ)`o(CxLLR_Eb-7`1u`q)pX84}vVJfD290}t%Qojv+l z?~sS*`i_l$e~2mJ1gMT_3zb&Mig?Jfhig=8YQ2A(Q|UbsHXpDj3+U9yyl+Dj3%(OO zG7U-|xt!EfEX?jlo=6kY$s8Fw1$zt2Y#$1ck}^nWSQRGJ9e!+OZ8gWnbXK0}AE<s& z3JpDu%FfLT>LG<wX%oAU{ho)&MN-A&>7Us9zsJd(V=>Z)r}_}YPZ#R&I$^MwtaQB7 z-U@=~x9qY%ZT0cfZw&xjV31oxDD6kE1(0z)r!U7l3fy^@PE^Jq*g9~Mi8|53_3&m< zGOpo~1Jjv}ptk76wKoKPxfG|{<Cw5kgZhViE4x4-S-ef>NmK>oNjhJcJJDsEZks1C znPeTvqc;S>*39I{vXs2wA4kt2S*QDE<}7WHd)q#&YiEvLZ~S%{Kw?4_3Njo(1Fe*g z#RZZVwMjpQ1_c)3x1SuMe;?H2qODHd-rwszIweXQdju@2*vH-7Jtds|Lc~$Xnh}Vz zR##8r#Ep{4fWCbJV`pa%qb0~nOWfmYpU+HMm99c9+%@t%w8zV+XCg5wVJBq2^>#1n ziMZZ}PpjW;{rIJSDi#=JAjH;G{$G@?v$G9+)3X@ba?uuIr9IhKqN|~;N|qFrr~#$r z$&tLSw2P3n>-1W{NvbAGxc23}!B$j8y2W#!Iu}bw^BIGUylFHvG>7MRj16IL&hE|r zGID7&c>GFdZVVgIh@Kw;VFx)JIgL}(dtx}Yxw9OH2UiA)lX;$>gZP*tBO^g5EKmh} z0<wM@RhN(opqCP;fyKMS$3$sPALZ*ZH+qYEPt3nV&$k)q8@p}1QvF(agO0MROVsm* zOYPA6w`5#y>DnQY#Kk|}!=3`8VqD!*y0KF&<#jm~JLUsw&(9fFZ-Ww~p@2pOAI-yR z#??>i-wr$mwF`S9RQ*GUQ;vXS-P@pqavxCc;7mFJ)<I@<wI7`vWM!>=VXl>yUiv5! zgL{_-<gW~GZ-h9GrvxTbD~B%|3nD2bGK)W6)}G3#cZKD3QCVj|tAi?qeBftH5}zN@ z(Zj>iG=V1Jy-)osAY^Q>#Y2TzuL6hD%$X`}*|_qRD=;jGi+A<pkT&C(Uc8j;!ylqH zWFidwjh5-m=!on{iKB>+X$hnL3Oqa)iP>9Uw+A5+6!>`z2hj=kHpByWEf8dZT6A-% z9zSB;<J&U+>3l9GTbhl<bn{ij7r1)@zE}3^m#?3O_H@)c*>#{5hx_)2-N#j7o~$Ge zU{eP&frgRa)<?iVF(x9M9n@97Jf4u`17U4tD|X0z;X4KdhM&_<G3^E7eC_7urq%)n zZ`u-(cw}x%2X1_q%<_=&(i$eBtD~6%;`K#eo=mdqIQW_X(h1-w0+L<@&X!W(=tVO! zW(>;GXsN^DGOpw7w`YJj=y#BHmuOLN1%>9`Xrn(4i_-4ezCn6KT-w_<y~(*sapKs@ zv~C1U>E-bqO)B5m9x~ID<5|PLQBiZ{4>#8kK{ZkWxN;XfHojZpb3#?MV*3V6DNOqy zc#YtnKxJdDqowbMp5i~ZC5FVn-mOnz=LWoVssHLRJ3A-Mj%G?d$k4j$Nrfe6F;D|@ z_CL%^_ixna!s|d@l7pRHL~d-J?_r^BRXcj*f$m1&#hjx+cBtsj!m}Du!ks@9>G3a~ zq`&&MAS(_L1%zT|s5ED!_>3BRZVta@b(Lv|JuAr>MZhxrm^v_WsjeWXr28CXjRX!* zM-nYaacT4Jv9pEo@qC*~mDiOOm{KKs<sIbgrq}#OaD<-UvoHGE)oK3g<7$b;U{_@{ z@8aX_b_e^XdVUyy_J(<~@llgNlIHm`v}}#d23z}|HbM3%hcEN`hTmp?3*&ux+$$LN zE-A@wVGdPyF>Ox++2xzGAS<qBYn;sHjf3Elg%bGQz;~rTdlqt&%x-J2C(*IVk@l7r zr#3|F1fEJxT|-;+<KxpnkXE(T71y^K*J`#{gD-+`73<{W>FXlTMH>me56Pc7PbwGJ z%pItV84j8tXuV%-ib|-In?$o-J^(ZXKR&y8jOY;2#zUEY+$$8T!{>xa+dBe`ZjdMD zXE|nTlP!yuZn__Y(ovuO5Z2miV1#>=L<x9clp1;DNIPJ{K?Y?HtWUijQmV|>1T}y4 z38oN?o#FNa5x0lgh0>~qtL@Np2t#~Df2TV(E%eYuz$fbnFGdMrnGboDQ^Kmt$Kc2| zqT9w{=G?0VX6!)qYiN{sc@kygyk(WCs^Hb?ckdEDzV0cN+=FxE$P5~YFQGg(lK<se zzK;^HWruE%Ky1BthD6aX>+0$zTxWBU&>udglvz{r=CKs%sj~8(a^#0cKJrGncq~7M zJ+*veq9t@$fRCe?XyD}~L)INo8_OM~B*X50R!Z9JNK6~0oVvDAr>rASeDkF@6x60- z8$znfi>vjA&zMza-@Ad(RNGCO-?ZkY`JL_o5ASI6cpJmmc*dapWGDipNSvKWG+f^l z&QfrF?oj(m8XssWPjG8=uWsXexV#d^B5ph2%-Xza3FyA`iccuY`>;AQSxg){z{|>0 zfy}7r>gvP|RM!iG_c*2bOOi6Hm#JZ}rADvpB)VZ|wcV&Jd?D~I0rk5%e33A<`dhSb zfTaa_;LT)plihnpH_j`M9iXepZcPHgos6-8(dd`;aC$9Bd^8^9CiCCL(j4UU>;6nC z2h~NOf?u=xJcJtLA~)yz1bBH7jnWkIue#`$$psCK<7ZwJILlJ|BxRSs@t&}ilS_u} z&1?*70!IMIN`~TmR!(z3^fV3dpp@xMYnl%kuFdJ<{=DZmRy8(%qmt8faB!LbeH?Ag zdI-RE4etaho<X_yNLSK`1P-3!)V#6^D(G^i^X-p=nMZ*E_S&{u?LQ7~iyN2_m<s#p zRe)Ok`@PpI9C72AoSbrx9TlZZf22l#E-M1}_{K=n5he;hq0KaxXJ|>WcZCmbXkf7O zn-zzAomNy;LJ#~#U0|{?iwD7p?;+=B_K`4f7=v4D?JoY9Bfn8YTkICrKSf}p%-hOl zat~{RG-CAez7hj1LnSHzWb>jWdSI(>vk3L}o2Ef#(wlehPz$!k7i!LEQ1~t6YuUM2 zjIVdvH`qhhPbtgMeHFh7e2k4`R)%8$=V%|YXkBOH0Qb{Nr>mG%5=Fep9#<Ji6oDg& zLf+L<USi$jz5`-gB$v4md^v;aEAR3ed5h&|015{^0YcoWxR}>y_n7n`W`rSEsk=Ln zv3xxs0Obm)%fJ|#NV`v!Mw7ad_B{CI6FAYUBBF=MuMP3qkXNoEFBLBc&f2N-(g%Gf zo#@ZZ&b}W*2`27-;`<2XZtFKjJw=%)<t+8}foZE5z7L`Poz6JFt)3LkFb0ap4F~e> z(S7}jTczzgIzINO3CM@*WWCRk_KDyi9*}k-b<AelfKne(QIoR%-iAgX0YjrBrMMo( zGiq<zCoXziwh9%-eQ@cUDMmNjv)<j?79o8YjWSUa5D+x1p#j?hW}@F6gAFx54Mv+^ zxQ=phpYCdAls7Epc{6E#x_{gE#EH<yFj{&tCM<X-EUb+uA@xOLv)Juhyiq3VU&QUw z%ky6x1=>0^%0_!vwNlT-XhpF5Mmr~UQ#0SjlR0p+B_s!QvOJqU@{zlPgWFVBT8~aI zJS*}Y>fy{J3-##t%a;Pl9MjyGSO?W~hvlZ0{Jy}Eg|;@xDo~Q>Nwua#0cM}(8A9i# z8{1^(8YiTThYE*HgAZBy-70FCYAwmDAHA5?*WZr@2MRU%&dwijB!N`6xxo?}i-qw= zkO`D!HZnH4ZoT;YTtkko5egCqn)-DzJ-~7&J{|{GS>OiRFo6bqAhUd;kM}ZvDj87o z@lsnzRJ@xg`rOX@$)j&T&Y<6OJDKi`0x;>RE}HH{Hy3xQX&22az$E9o^cC9|1e3ww z0w10D(E(n8_!stIo%LJ__Sq6{b}mc629Dg@u3~_CYq{qcDe2F1LrE}*xOi^5chcJ& z<2>KXz+zwJt-xSHK{%D~t_Gk~Qc~JH9Pm`R>6_FE3a(EkhIlNl9K9k!?K5*CS^M!j zOxFA)$MFD%s5fBITrza>*4Eh|fOOU0aQ?!K2(c{jsvznVJ%jz2dd*Rxc)?4DH!2Es z1dD_Hkuxyx5+{bin);BZZhPVz5O8D~nWcYCUyB#SkN8e;bxqy20bn_~Dw*X=uqD7@ z$e)i40xWK72658Ur^>IGfsq-W&PEi8iRD`-nUUoZ6gkdK9`mmyTdq&y=P;yIXG45T zg6wnQun|xWXH{k2Y0uj7gX~S}=Yb8U7Zq@R>U{Jx<Bm=wQ8A--I1xr3OK{N4)S^qx zKko-_8loeYmrq7TM>|9UT?#y7_Qfd@2Dj8TbEd{%;hCjRp3Eie*cr2v1$L3`XkM7p z>*KV^!u5G~-N#$NA;8Av$_w;txa_ch%(k8uGAiOsWmiA`+t*a=hwNxzlopL@K{Zj@ zav?6}TP-=W*5tl%SgGlo0t;A4<{B4)<e8@R!2b(81#4T{(bv_<*whH38fvb)wM=Jd z$Da_F?&s+p-u?i9ZOWPJ1um{s_F1qPdj|QYH*bb9p`j}4C-{K7adu#CHa90z+nR%h zvw5!3&?m9YDIOGAg}4e@VYDF`nV8*K!fyJzw_?->foV9!)!M|QsiP6Y81xAt%q2M2 z#tlbueF|;O`4GmEGra_mIxin@KwVo>0<v)%2cgpHa^PyUuY}~@&>}cqYx{=cV<6r5 zr=I$^W8lsN*Au)N@|6h@l8Hf0Oic!VAHPCJN$#MAG=l5s7~qoxzb!W}k9;vO>Ic_z z%`Gh;JHd2n_=pv9UeCaY)@P8roL6O@4Q>eU>FU?j*G`KRc_M(I5aRw6&gxDd9a(6M z5@#G#O&0E9?(FN>*xcP9&__jmrlkUt$oj(GrC~fB5S~uxZsNAdYA>}9NMwVE;e@Jg zi15?8C4i?FExB4bD_yp9myq`@IdUoeBxR`Z+seGmf-EWUl_Bo{^}%AtKJu52HP9W? zPedGECxBxsQ=9Ws8#0;3r?0#mgea=Ixg}vK*Axx{?m>~s^U#(y5U8^tylOt3Lxf=8 ze#l8Kn0A&kef|J{l_Gkb_VdMJYAtY<h+PZ2BDSQ6Tp3At4o618sHmuBUyHLa=e{>N zPC-`_%uNlx!@0yY3i7`S(t;Jl-G`AdSQ99f!b?e>Utd?^T@5}!!5(V-o<l_%WnnSi z`0bBX+}m;VICe6my+;q+<#l_F=$LD3yM-OsWdWAv=7sCfC>3xUY3=-=-hSpBxfm-1 zM|JZi@Ycb!t_clO)Zk-e#@BQ;^m9wY!(YD#gNi?1zqK7!D-1^X`IgTZQUF|=*A5wC zV$L1{r?P`<LbTGQaZuUb_IYN0{#w}iw5^7KG#oP~bXo!s4pnUda4&J6N)c_7KLC7$ z!G@924&v#>Pd!Z?9m6%Pt2!dj--G(9u#sYpVT<q`5~$kg=*TbKsBfB@inn`32|)e) zlvT+x<Qk}b0pG37%_*d8%0bj2cC5Ner)WmMkB#{|)UH#3y4X5U5j<TAN3#2o2SLCU zw%<r`7y^r%thnb#2LqLu%OAgpoIguZd1RXUO5>vW$w|}u8w4bVO-%6+IQcTchd~sr zRO6=(0@3*2_$4;D_uap8aNYI4__OkToB5ypMxGJB%SM6tIv*Jg<5?#EW5IWB-c`tv IzvuIR05xHvF#rGn literal 0 HcmV?d00001 diff --git a/elasticsearch-persistence/examples/music/index_manager.rb b/elasticsearch-persistence/examples/music/index_manager.rb index fae8a93d1..cbf9e09ee 100644 --- a/elasticsearch-persistence/examples/music/index_manager.rb +++ b/elasticsearch-persistence/examples/music/index_manager.rb @@ -26,33 +26,46 @@ def self.import_from_yaml(source, options={}) Artist.create artist.update( 'album_count' => artist['releases'].size, 'members_combined' => artist['members'].join(', '), - 'suggest_name' => { - 'input' => artist['namevariations'].unshift(artist['name']), - 'output' => artist['name'], - 'payload' => { 'url' => "/artists/#{artist['id']}" } - }, - 'suggest_member' => { - 'input' => artist['members'], - 'output' => artist['name'], - 'payload' => { 'url' => "/artists/#{artist['id']}" } + 'suggest' => { + 'name' => { + 'input' => { 'input' => artist['namevariations'].unshift(artist['name']).reject { |d| d.to_s.empty? } }, + 'output' => artist['name'], + 'payload' => { + 'url' => "/artists/#{artist['id']}" + } + }, + 'member' => { + 'input' => { 'input' => artist['members'] }, + 'output' => artist['name'], + 'payload' => { + 'url' => "/artists/#{artist['id']}" + } + } } ) artist['releases'].each do |album| album.update( - 'suggest_title' => { - 'input' => album['title'], - 'output' => album['title'], - 'payload' => { 'url' => "/artists/#{artist['id']}#album_#{album['id']}" } - }, - 'suggest_track' => { - 'input' => album['tracklist'].map { |d| d['title'] }, - 'output' => album['title'], - 'payload' => { 'url' => "/artists/#{artist['id']}#album_#{album['id']}" } + 'suggest' => { + 'title' => { + 'input' => { 'input' => album['title'] }, + 'output' => album['title'], + 'payload' => { + 'url' => "/artists/#{artist['id']}#album_#{album['id']}" + } + }, + 'track' => { + 'input' => { 'input' => album['tracklist'].map { |d| d['title'] }.reject { |d| d.to_s.empty? } }, + 'output' => album['title'], + 'payload' => { + 'url' => "/artists/#{artist['id']}#album_#{album['id']}" + } + } } ) album['notes'] = album['notes'].to_s.gsub(/<.+?>/, '').gsub(/ {2,}/, '') album['released'] = nil if album['released'] < 1 + Album.create album, id: album['id'], parent: artist['id'] end end diff --git a/elasticsearch-persistence/examples/music/search/index.html.erb b/elasticsearch-persistence/examples/music/search/index.html.erb index aed20f590..098f626e5 100644 --- a/elasticsearch-persistence/examples/music/search/index.html.erb +++ b/elasticsearch-persistence/examples/music/search/index.html.erb @@ -13,21 +13,22 @@ <section class="artists"> <% @artists.each do |artist| %> - <%= content_tag :div, class: 'result clearfix' do %> + <%= content_tag :div, class: 'artist search result clearfix' do %> <h2> + <%= image_tag "http://ruby.elastic.co.s3-website-us-east-1.amazonaws.com/demo/music/bands/#{artist.id}.jpeg", height: '45px', class: 'band' %> <%= link_to artist do %> <span class="name"><%= highlighted(artist, :name) %></span> <small><%= pluralize artist.album_count, 'album' %></small> <% end %> </h2> <% if highlight = highlight(artist, :members_combined) %> - <p class="small"> + <p class="highlight small"> <span class="label">Members</span> <%= highlight.first.html_safe %> </p> <% end %> <% if highlight = highlight(artist, :profile) %> - <p class="small"> + <p class="highlight small"> <span class="label">Profile</span> <%= highlight.join('…').html_safe %> </p> @@ -38,8 +39,9 @@ <section class="albums"> <% @albums.each do |album| %> - <%= content_tag :div, class: 'result clearfix' do %> + <%= content_tag :div, class: 'album search result clearfix' do %> <h2> + <%= image_tag "http://ruby.elastic.co.s3-website-us-east-1.amazonaws.com/demo/music/covers/#{album.id}.jpeg", width: '45px', class: 'cover' %> <%= link_to artist_path(album.artist_id, anchor: "album_#{album.id}") do %> <span class="name"><%= highlighted(album, :title) %></span> <small><%= album.artist %></small> @@ -48,14 +50,14 @@ </h2> <% if highlight = highlight(album, 'tracklist.title') %> - <p class="small"> + <p class="highlight small"> <span class="label">Tracks</span> <%= highlight.join('…').html_safe %> </p> <% end %> <% if highlight = highlight(album, :notes) %> - <p class="small"> + <p class="highlight small"> <span class="label">Notes</span> <%= highlight.map { |d| d.gsub(/^\.\s?/, '') }.join('…').html_safe %> </p> @@ -77,7 +79,7 @@ $.widget( "custom.suggest", $.ui.autocomplete, { var category = ul.append( "<li class='ui-autocomplete-category'>" + item.label + "</li>" ); $.each( item.value, function( index, item ) { - var li = $('<li class="ui-autocomplete-item"><a href="<%= Rails.application.config.relative_url_root %>'+ item.payload.url +'">'+ item.text +'</a></li>').data('ui-autocomplete-item', item ) + var li = $('<li class="ui-autocomplete-item"><a href="<%= Rails.application.config.relative_url_root %>'+ item.url +'">'+ item.text +'</a></li>').data('ui-autocomplete-item', item ) category.append(li) } ) }); @@ -87,7 +89,7 @@ $.widget( "custom.suggest", $.ui.autocomplete, { $( "#q" ).suggest({ source: '<%= suggest_path %>', select: function(event, ui) { - document.location.href = '<%= Rails.application.config.relative_url_root %>'+ui.item.payload.url + document.location.href = '<%= Rails.application.config.relative_url_root %>'+ui.item.url } }); </script> diff --git a/elasticsearch-persistence/examples/music/search/search_controller_test.rb b/elasticsearch-persistence/examples/music/search/search_controller_test.rb index 308bad200..a1c95cd0c 100644 --- a/elasticsearch-persistence/examples/music/search/search_controller_test.rb +++ b/elasticsearch-persistence/examples/music/search/search_controller_test.rb @@ -1,9 +1,12 @@ require 'test_helper' class SearchControllerTest < ActionController::TestCase + setup do + IndexManager.create_index force: true + end + test "should get suggest" do - get :suggest + get :suggest, term: 'foo' assert_response :success end - end diff --git a/elasticsearch-persistence/examples/music/suggester.rb b/elasticsearch-persistence/examples/music/suggester.rb index bd162e12d..5438cf11a 100644 --- a/elasticsearch-persistence/examples/music/suggester.rb +++ b/elasticsearch-persistence/examples/music/suggester.rb @@ -7,39 +7,63 @@ def initialize(params={}) def response @response ||= begin - Elasticsearch::Persistence.client.suggest \ + Elasticsearch::Persistence.client.search \ index: Artist.index_name, body: { - artists: { - text: @term, - completion: { field: 'suggest_name', size: 25 } + suggest: { + artists: { + text: @term, + completion: { field: 'suggest.name.input', size: 25 } + }, + members: { + text: @term, + completion: { field: 'suggest.member.input', size: 25 } + }, + albums: { + text: @term, + completion: { field: 'suggest.title.input', size: 25 } + }, + tracks: { + text: @term, + completion: { field: 'suggest.track.input', size: 25 } + } }, - members: { - text: @term, - completion: { field: 'suggest_member', size: 25 } - }, - albums: { - text: @term, - completion: { field: 'suggest_title', size: 25 } - }, - tracks: { - text: @term, - completion: { field: 'suggest_track', size: 25 } - } + _source: ['suggest.*'] } end end def as_json(options={}) - response - .except('_shards') - .reduce([]) do |sum,d| - # category = { d.first => d.second.first['options'] } - item = { :label => d.first.titleize, :value => d.second.first['options'] } - sum << item - end - .reject do |d| - d[:value].empty? - end + return [] unless response['suggest'] + + output = [ + { label: 'Bands', + value: response['suggest']['artists'][0]['options'].map do |d| + { text: d['_source']['suggest']['name']['output'], + url: d['_source']['suggest']['name']['payload']['url'] } + end + }, + + { label: 'Albums', + value: response['suggest']['albums'][0]['options'].map do |d| + { text: d['_source']['suggest']['title']['output'], + url: d['_source']['suggest']['title']['payload']['url'] } + end + }, + + { label: 'Band Members', + value: response['suggest']['members'][0]['options'].map do |d| + { text: "#{d['text']} (#{d['_source']['suggest']['member']['output']})", + url: d['_source']['suggest']['member']['payload']['url'] } + end + }, + + { label: 'Album Tracks', + value: response['suggest']['tracks'][0]['options'].map do |d| + { text: "#{d['text']} (#{d['_source']['suggest']['track']['output']})", + url: d['_source']['suggest']['track']['payload']['url'] } + end + } + ] end end diff --git a/elasticsearch-persistence/examples/music/template.rb b/elasticsearch-persistence/examples/music/template.rb index eba34cb01..2684e8200 100644 --- a/elasticsearch-persistence/examples/music/template.rb +++ b/elasticsearch-persistence/examples/music/template.rb @@ -11,8 +11,8 @@ # # * Git # * Ruby >= 1.9.3 -# * Rails >= 4 -# * Java >= 7 (for Elasticsearch) +# * Rails >= 5 +# * Java >= 8 (for Elasticsearch) # # Usage: # ------ @@ -25,6 +25,7 @@ STDERR.sync = true require 'uri' +require 'json' require 'net/http' at_exit do @@ -35,29 +36,32 @@ end end -run "touch tmp/.gitignore" +$elasticsearch_url = ENV.fetch('ELASTICSEARCH_URL', 'http://localhost:9200') -append_to_file ".gitignore", "vendor/elasticsearch-1.2.1/\n" +# ----- Check & download Elasticsearch ------------------------------------------------------------ -git :init -git add: "." -git commit: "-m 'Initial commit: Clean application'" +cluster_info = Net::HTTP.get(URI.parse($elasticsearch_url)) rescue nil +cluster_info = JSON.parse(cluster_info) if cluster_info -# ----- Download Elasticsearch -------------------------------------------------------------------- +if cluster_info.nil? || cluster_info['version']['number'] < '5' + # Change the port when incompatible Elasticsearch version is running on localhost:9200 + if $elasticsearch_url == 'http://localhost:9200' && cluster_info && cluster_info['version']['number'] < '5' + $change_port = '9280' + $elasticsearch_url = "http://localhost:#{$change_port}" + end -unless (Net::HTTP.get(URI.parse('http://localhost:9200')) rescue false) COMMAND = <<-COMMAND.gsub(/^ /, '') - curl -# -O "http://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.2.1.tar.gz" - tar -zxf elasticsearch-1.2.1.tar.gz - rm -f elasticsearch-1.2.1.tar.gz - ./elasticsearch-1.2.1/bin/elasticsearch -d -p #{destination_root}/tmp/pids/elasticsearch.pid + curl -# -O "https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.2.1.tar.gz" + tar -zxf elasticsearch-5.2.1.tar.gz + rm -f elasticsearch-5.2.1.tar.gz + ./elasticsearch-5.2.1/bin/elasticsearch -d -p #{destination_root}/tmp/pids/elasticsearch.pid #{$change_port.nil? ? '' : "-E http.port=#{$change_port}" } COMMAND puts "\n" say_status "ERROR", "Elasticsearch not running!\n", :red puts '-'*80 - say_status '', "It appears that Elasticsearch is not running on this machine." - say_status '', "Is it installed? Do you want me to install it for you with this command?\n\n" + say_status '', "It appears that Elasticsearch 5 is not running on this machine." + say_status '', "Is it installed? Do you want me to install and run it for you with this command?\n\n" COMMAND.each_line { |l| say_status '', "$ #{l}" } puts say_status '', "(To uninstall, just remove the generated application directory.)" @@ -69,9 +73,9 @@ java_info = `java -version 2>&1` - unless java_info.match /1\.[7-9]/ + unless java_info.match /1\.[8-9]/ puts - say_status "ERROR", "Required Java version (1.7) not found, exiting...", :red + say_status "ERROR", "Required Java version (1.8) not found, exiting...", :red exit(1) end @@ -81,18 +85,36 @@ commands.each { |command| run command } run "(#{exec})" # Launch Elasticsearch in subshell end + + # Wait for Elasticsearch to be up... + # + system <<-COMMAND + until $(curl --silent --head --fail #{$elasticsearch_url} > /dev/null 2>&1); do + printf '.'; sleep 1 + done + COMMAND end end unless ENV['RAILS_NO_ES_INSTALL'] +# ----- Application skeleton ---------------------------------------------------------------------- + +run "touch tmp/.gitignore" + +append_to_file ".gitignore", "vendor/elasticsearch-5.2.1/\n" + +git :init +git add: "." +git commit: "-m 'Initial commit: Clean application'" + # ----- Add README -------------------------------------------------------------------------------- puts say_status "README", "Adding Readme...\n", :yellow puts '-'*80, ''; sleep 0.25 -remove_file 'README.rdoc' +remove_file 'README.md' -create_file 'README.rdoc', <<-README +create_file 'README.md', <<-README = Ruby on Rails and Elasticsearch persistence: Example application README @@ -101,20 +123,31 @@ git add: "." git commit: "-m 'Added README for the application'" -# ----- Use Thin ---------------------------------------------------------------------------------- +# ----- Use Pry as the Rails console -------------------------------------------------------------- -begin - require 'thin' - puts - say_status "Rubygems", "Adding Thin into Gemfile...\n", :yellow - puts '-'*80, ''; +puts +say_status "Rubygems", "Adding Pry into Gemfile...\n", :yellow +puts '-'*80, ''; - gem 'thin' -rescue LoadError +gem_group :development do + gem 'pry' + gem 'pry-rails' end +git add: "Gemfile*" +git commit: "-m 'Added Pry into the Gemfile'" + # ----- Auxiliary gems ---------------------------------------------------------------------------- +puts +say_status "Rubygems", "Adding libraries into the Gemfile...\n", :yellow +puts '-'*80, ''; sleep 0.75 + +gem "simple_form" + +git add: "Gemfile*" +git commit: "-m 'Added auxiliary libraries into the Gemfile'" + # ----- Remove CoffeeScript, Sass and "all that jazz" --------------------------------------------- comment_lines 'Gemfile', /gem 'coffee/ @@ -128,16 +161,13 @@ say_status "Rubygems", "Adding Elasticsearch libraries into Gemfile...\n", :yellow puts '-'*80, ''; sleep 0.75 -gem "quiet_assets" -gem "simple_form" - gem 'elasticsearch', git: 'git://github.com/elasticsearch/elasticsearch-ruby.git' gem 'elasticsearch-model', git: 'git://github.com/elasticsearch/elasticsearch-rails.git', require: 'elasticsearch/model' gem 'elasticsearch-persistence', git: 'git://github.com/elasticsearch/elasticsearch-rails.git', require: 'elasticsearch/persistence/model' gem 'elasticsearch-rails', git: 'git://github.com/elasticsearch/elasticsearch-rails.git' git add: "Gemfile*" -git commit: "-m 'Added libraries into Gemfile'" +git commit: "-m 'Added the Elasticsearch libraries into the Gemfile'" # ----- Install gems ------------------------------------------------------------------------------ @@ -173,11 +203,15 @@ 'vendor/assets/javascripts/jquery-ui-1.10.4.custom.min.js' copy_file File.expand_path('../vendor/assets/jquery-ui-1.10.4.custom.min.css', __FILE__), 'vendor/assets/stylesheets/ui-lightness/jquery-ui-1.10.4.custom.min.css' + copy_file File.expand_path('../vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png', __FILE__), + 'vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png' else get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.js', 'vendor/assets/javascripts/jquery-ui-1.10.4.custom.min.js' get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.css', 'vendor/assets/stylesheets/ui-lightness/jquery-ui-1.10.4.custom.min.css' + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png', + 'vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png' end append_to_file 'app/assets/javascripts/application.js', "//= require jquery-ui-1.10.4.custom.min.js" @@ -301,6 +335,7 @@ copy_file File.expand_path('../assets/autocomplete.css', __FILE__), 'app/assets/stylesheets/autocomplete.css' copy_file File.expand_path('../assets/form.css', __FILE__), 'app/assets/stylesheets/form.css' copy_file File.expand_path('../assets/blank_cover.png', __FILE__), 'public/images/blank_cover.png' + copy_file File.expand_path('../assets/blank_artist.png', __FILE__), 'public/images/blank_artist.png' else get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/assets/application.css', 'app/assets/stylesheets/application.css' @@ -310,6 +345,8 @@ 'app/assets/stylesheets/form.css' get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/assets/blank_cover.png', 'public/images/blank_cover.png' + get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/assets/blank_artist.png', + 'public/images/blank_artist.png' end git add: "." @@ -359,9 +396,9 @@ say_status "Data", "Import the data...", :yellow puts '-'*80, ''; sleep 0.25 -source = ENV.fetch('DATA_SOURCE', 'http://ruby-demo-assets.s3.amazonaws.com/dischord.yml') +source = ENV.fetch('DATA_SOURCE', 'https://github.com/elastic/elasticsearch-rails/releases/download/dischord.yml/dischord.yml') -run "rails runner 'IndexManager.import_from_yaml(\"#{source}\", force: true)'" +run "ELASTICSEARCH_URL=#{$elasticsearch_url} rails runner 'IndexManager.import_from_yaml(\"#{source}\", force: true)'" # ----- Print Git log ----------------------------------------------------------------------------- @@ -389,5 +426,5 @@ say_status "DONE", "\e[1mStarting the application.\e[0m", :yellow puts "="*80, "" - run "rails server --port=#{port}" + run "ELASTICSEARCH_URL=#{$elasticsearch_url} rails server --port=#{port}" end diff --git a/elasticsearch-persistence/examples/music/vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png b/elasticsearch-persistence/examples/music/vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png new file mode 100644 index 0000000000000000000000000000000000000000..f1273672d253263b7564e9e21d69d7d9d0b337d9 GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^j6j^i!3HGVb)pi0l%l7LV~E7m<R2d&AFo$qV`FPm oboulDzr6KyA+fs7hb{~ZQx+&qVC9&67pR!Q)78&qol`;+0H8b=ng9R* literal 0 HcmV?d00001 From fece6fb62e4cee6a83c8a9d7bf1675c3e1e49dee Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Thu, 23 Feb 2017 14:53:06 +0100 Subject: [PATCH 324/582] [STORE] Updated the URLs in the "Music" application template --- elasticsearch-persistence/examples/music/template.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/elasticsearch-persistence/examples/music/template.rb b/elasticsearch-persistence/examples/music/template.rb index 2684e8200..4759b642d 100644 --- a/elasticsearch-persistence/examples/music/template.rb +++ b/elasticsearch-persistence/examples/music/template.rb @@ -161,10 +161,10 @@ say_status "Rubygems", "Adding Elasticsearch libraries into Gemfile...\n", :yellow puts '-'*80, ''; sleep 0.75 -gem 'elasticsearch', git: 'git://github.com/elasticsearch/elasticsearch-ruby.git' -gem 'elasticsearch-model', git: 'git://github.com/elasticsearch/elasticsearch-rails.git', require: 'elasticsearch/model' -gem 'elasticsearch-persistence', git: 'git://github.com/elasticsearch/elasticsearch-rails.git', require: 'elasticsearch/persistence/model' -gem 'elasticsearch-rails', git: 'git://github.com/elasticsearch/elasticsearch-rails.git' +gem 'elasticsearch', git: 'https://github.com/elastic/elasticsearch-ruby.git' +gem 'elasticsearch-model', git: 'https://github.com/elastic/elasticsearch-rails.git', require: 'elasticsearch/model' +gem 'elasticsearch-persistence', git: 'https://github.com/elastic/elasticsearch-rails.git', require: 'elasticsearch/persistence/model' +gem 'elasticsearch-rails', git: 'https://github.com/elastic/elasticsearch-rails.git' git add: "Gemfile*" git commit: "-m 'Added the Elasticsearch libraries into the Gemfile'" From 7aa03e87b7fee71007ff7aa1a6fb452de298587e Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Thu, 23 Feb 2017 16:46:46 +0100 Subject: [PATCH 325/582] [STORE] Updated the Git URLs in the "Notes" example application --- elasticsearch-persistence/examples/notes/Gemfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-persistence/examples/notes/Gemfile b/elasticsearch-persistence/examples/notes/Gemfile index 63d5d75c5..c70894e27 100644 --- a/elasticsearch-persistence/examples/notes/Gemfile +++ b/elasticsearch-persistence/examples/notes/Gemfile @@ -9,8 +9,8 @@ gem 'hashie' gem 'patron' gem 'elasticsearch' -gem 'elasticsearch-model', path: File.expand_path('../../../../elasticsearch-model', __FILE__) -gem 'elasticsearch-persistence', path: File.expand_path('../../../', __FILE__) +gem 'elasticsearch-model', git: 'https://github.com/elastic/elasticsearch-rails.git' +gem 'elasticsearch-persistence', git: 'https://github.com/elastic/elasticsearch-rails.git' gem 'sinatra', require: false gem 'thin' From 0df1a1f5da94378dbb39c3f7f4e6b2b28d78e21b Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 24 Feb 2017 09:04:54 +0100 Subject: [PATCH 326/582] [RAILS] Updated the installation process in the "01-basic" application template --- .../lib/rails/templates/01-basic.rb | 61 +++++++++++++------ 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/elasticsearch-rails/lib/rails/templates/01-basic.rb b/elasticsearch-rails/lib/rails/templates/01-basic.rb index 593c35174..1019a2d30 100644 --- a/elasticsearch-rails/lib/rails/templates/01-basic.rb +++ b/elasticsearch-rails/lib/rails/templates/01-basic.rb @@ -10,8 +10,8 @@ # # * Git # * Ruby >= 1.9.3 -# * Rails >= 4 -# * Java >= 7 (for Elasticsearch) +# * Rails >= 5 +# * Java >= 8 (for Elasticsearch) # # Usage: # ------ @@ -31,31 +31,32 @@ end end -run "touch tmp/.gitignore" - -append_to_file ".gitignore", "vendor/elasticsearch-1.0.1/\n" +$elasticsearch_url = ENV.fetch('ELASTICSEARCH_URL', 'http://localhost:9200') -git :init -git add: "." -git commit: "-m 'Initial commit: Clean application'" +# ----- Check & download Elasticsearch ------------------------------------------------------------ -# ----- Download Elasticsearch -------------------------------------------------------------------- +cluster_info = Net::HTTP.get(URI.parse($elasticsearch_url)) rescue nil +cluster_info = JSON.parse(cluster_info) if cluster_info -ELASTICSEARCH_URL = ENV.fetch('ELASTICSEARCH_URL', 'http://localhost:9200') +if cluster_info.nil? || cluster_info['version']['number'] < '5' + # Change the port when incompatible Elasticsearch version is running on localhost:9200 + if $elasticsearch_url == 'http://localhost:9200' && cluster_info && cluster_info['version']['number'] < '5' + $change_port = '9280' + $elasticsearch_url = "http://localhost:#{$change_port}" + end -unless (Net::HTTP.get(URI.parse(ELASTICSEARCH_URL)) rescue false) COMMAND = <<-COMMAND.gsub(/^ /, '') - curl -# -O "http://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.0.1.tar.gz" - tar -zxf elasticsearch-1.0.1.tar.gz - rm -f elasticsearch-1.0.1.tar.gz - ./elasticsearch-1.0.1/bin/elasticsearch -d -p #{destination_root}/tmp/pids/elasticsearch.pid + curl -# -O "https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.2.1.tar.gz" + tar -zxf elasticsearch-5.2.1.tar.gz + rm -f elasticsearch-5.2.1.tar.gz + ./elasticsearch-5.2.1/bin/elasticsearch -d -p #{destination_root}/tmp/pids/elasticsearch.pid #{$change_port.nil? ? '' : "-E http.port=#{$change_port}" } COMMAND puts "\n" say_status "ERROR", "Elasticsearch not running!\n", :red puts '-'*80 - say_status '', "It appears that Elasticsearch is not running on this machine." - say_status '', "Is it installed? Do you want me to install it for you with this command?\n\n" + say_status '', "It appears that Elasticsearch 5 is not running on this machine." + say_status '', "Is it installed? Do you want me to install and run it for you with this command?\n\n" COMMAND.each_line { |l| say_status '', "$ #{l}" } puts say_status '', "(To uninstall, just remove the generated application directory.)" @@ -65,15 +66,41 @@ puts say_status "Install", "Elasticsearch", :yellow + java_info = `java -version 2>&1` + + unless java_info.match /1\.[8-9]/ + puts + say_status "ERROR", "Required Java version (1.8) not found, exiting...", :red + exit(1) + end + commands = COMMAND.split("\n") exec = commands.pop inside("vendor") do commands.each { |command| run command } run "(#{exec})" # Launch Elasticsearch in subshell end + + # Wait for Elasticsearch to be up... + # + system <<-COMMAND + until $(curl --silent --head --fail #{$elasticsearch_url} > /dev/null 2>&1); do + printf '.'; sleep 1 + done + COMMAND end end unless ENV['RAILS_NO_ES_INSTALL'] +# ----- Application skeleton ---------------------------------------------------------------------- + +run "touch tmp/.gitignore" + +append_to_file ".gitignore", "vendor/elasticsearch-5.2.1/\n" + +git :init +git add: "." +git commit: "-m 'Initial commit: Clean application'" + # ----- Add README -------------------------------------------------------------------------------- puts From 85d84abe061bbc329369fd293fd36e8893c47261 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 10 Mar 2017 08:26:45 -0800 Subject: [PATCH 327/582] [MODEL] Updated the configuration for required routing in the integration test The syntax for making the routing value required has changed in Elasticsearch, and this patch reflects that. See: https://www.elastic.co/guide/en/elasticsearch/reference/5.x/mapping-routing-field.html Related: #677 --- .../test/integration/active_record_associations_parent_child.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/test/integration/active_record_associations_parent_child.rb b/elasticsearch-model/test/integration/active_record_associations_parent_child.rb index 956c80036..de5db34b0 100644 --- a/elasticsearch-model/test/integration/active_record_associations_parent_child.rb +++ b/elasticsearch-model/test/integration/active_record_associations_parent_child.rb @@ -31,7 +31,7 @@ class Answer < ActiveRecord::Base index_name 'questions_and_answers' - mapping _parent: { type: 'question', required: true } do + mapping _parent: { type: 'question' }, _routing: { required: true } do indexes :text indexes :author end From 70b8ca87e8d40636b7c6db165dad1a5c8e02a459 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 10 Mar 2017 08:31:27 -0800 Subject: [PATCH 328/582] [MODEL] Fixed incorrect name for the parent/child integration test Closes #677 --- ...t_child.rb => active_record_associations_parent_child_test.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename elasticsearch-model/test/integration/{active_record_associations_parent_child.rb => active_record_associations_parent_child_test.rb} (100%) diff --git a/elasticsearch-model/test/integration/active_record_associations_parent_child.rb b/elasticsearch-model/test/integration/active_record_associations_parent_child_test.rb similarity index 100% rename from elasticsearch-model/test/integration/active_record_associations_parent_child.rb rename to elasticsearch-model/test/integration/active_record_associations_parent_child_test.rb From 844e1fed80f9e540ce5cdd79e50ee916b2076f50 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 10 Mar 2017 08:35:54 -0800 Subject: [PATCH 329/582] [MODEL] Fixed incorrect mapping configuration in the integration tests --- .../test/integration/active_record_basic_test.rb | 4 ++-- .../test/integration/active_record_import_test.rb | 2 +- .../test/integration/active_record_pagination_test.rb | 2 +- elasticsearch-model/test/integration/mongoid_basic_test.rb | 2 +- elasticsearch-model/test/integration/multiple_models_test.rb | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index f6015da70..26c5785f2 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -18,8 +18,8 @@ class ::Article < ActiveRecord::Base settings index: { number_of_shards: 1, number_of_replicas: 0 } do mapping do - indexes :title, type: 'string', analyzer: 'snowball' - indexes :body, type: 'string' + indexes :title, type: 'text', analyzer: 'snowball' + indexes :body, type: 'text' indexes :clicks, type: 'integer' indexes :created_at, type: 'date' end diff --git a/elasticsearch-model/test/integration/active_record_import_test.rb b/elasticsearch-model/test/integration/active_record_import_test.rb index 4b9560d3a..ed8b85c1d 100644 --- a/elasticsearch-model/test/integration/active_record_import_test.rb +++ b/elasticsearch-model/test/integration/active_record_import_test.rb @@ -16,7 +16,7 @@ class ::ImportArticle < ActiveRecord::Base scope :popular, -> { where('views >= 50') } mapping do - indexes :title, type: 'string' + indexes :title, type: 'text' indexes :views, type: 'integer' indexes :numeric, type: 'integer' indexes :created_at, type: 'date' diff --git a/elasticsearch-model/test/integration/active_record_pagination_test.rb b/elasticsearch-model/test/integration/active_record_pagination_test.rb index 9c07ef115..0b5e259ee 100644 --- a/elasticsearch-model/test/integration/active_record_pagination_test.rb +++ b/elasticsearch-model/test/integration/active_record_pagination_test.rb @@ -16,7 +16,7 @@ class ::ArticleForPagination < ActiveRecord::Base settings index: { number_of_shards: 1, number_of_replicas: 0 } do mapping do - indexes :title, type: 'string', analyzer: 'snowball' + indexes :title, type: 'text', analyzer: 'snowball' indexes :created_at, type: 'date' end end diff --git a/elasticsearch-model/test/integration/mongoid_basic_test.rb b/elasticsearch-model/test/integration/mongoid_basic_test.rb index e370bd82a..d46a75d05 100644 --- a/elasticsearch-model/test/integration/mongoid_basic_test.rb +++ b/elasticsearch-model/test/integration/mongoid_basic_test.rb @@ -20,7 +20,7 @@ class ::MongoidArticle settings index: { number_of_shards: 1, number_of_replicas: 0 } do mapping do - indexes :title, type: 'string', analyzer: 'snowball' + indexes :title, type: 'text', analyzer: 'snowball' indexes :created_at, type: 'date' end end diff --git a/elasticsearch-model/test/integration/multiple_models_test.rb b/elasticsearch-model/test/integration/multiple_models_test.rb index 612c695d1..7d3a62705 100644 --- a/elasticsearch-model/test/integration/multiple_models_test.rb +++ b/elasticsearch-model/test/integration/multiple_models_test.rb @@ -20,7 +20,7 @@ module ::NameSearch settings index: {number_of_shards: 1, number_of_replicas: 0} do mapping do - indexes :name, type: 'string', analyzer: 'snowball' + indexes :name, type: 'text', analyzer: 'snowball' indexes :created_at, type: 'date' end end @@ -130,7 +130,7 @@ class ::Image settings index: {number_of_shards: 1, number_of_replicas: 0} do mapping do - indexes :name, type: 'string', analyzer: 'snowball' + indexes :name, type: 'text', analyzer: 'snowball' indexes :created_at, type: 'date' end end From d8859aed2aa75c93840b763d70e8fce8ce7761ff Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Wed, 29 Mar 2017 14:41:34 +0200 Subject: [PATCH 330/582] [MODEL] Allow passing the index settings and mappings as arguments to `create_index!` --- .../lib/elasticsearch/model/indexing.rb | 10 ++++++--- .../test/unit/indexing_test.rb | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index d0160a4e8..ac507b079 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -220,15 +220,19 @@ def load_settings_from_io(settings) # Article.__elasticsearch__.create_index! index: 'my-index' # def create_index!(options={}) - target_index = options.delete(:index) || self.index_name + options = options.clone + + target_index = options.delete(:index) || self.index_name + settings = options.delete(:settings) || self.settings.to_hash + mappings = options.delete(:mappings) || self.mappings.to_hash delete_index!(options.merge index: target_index) if options[:force] unless index_exists?(index: target_index) self.client.indices.create index: target_index, body: { - settings: self.settings.to_hash, - mappings: self.mappings.to_hash } + settings: settings, + mappings: mappings } end end diff --git a/elasticsearch-model/test/unit/indexing_test.rb b/elasticsearch-model/test/unit/indexing_test.rb index dd9236dbd..b78651340 100644 --- a/elasticsearch-model/test/unit/indexing_test.rb +++ b/elasticsearch-model/test/unit/indexing_test.rb @@ -528,6 +528,27 @@ class ::DummyIndexingModelForRecreate assert_nothing_raised { DummyIndexingModelForRecreate.create_index! } end + should "get the index settings and mappings from options" do + client = stub('client') + indices = stub('indices') + client.stubs(:indices).returns(indices) + + indices.expects(:create).with do |payload| + assert_equal 'foobar', payload[:index] + assert_equal 3, payload[:body][:settings][:index][:number_of_shards] + assert_equal 'bar', payload[:body][:mappings][:foobar][:properties][:foo][:analyzer] + true + end.returns({}) + + DummyIndexingModelForRecreate.expects(:index_exists?).returns(false) + DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once + + DummyIndexingModelForRecreate.create_index! \ + index: 'foobar', + settings: { index: { number_of_shards: 3 } }, + mappings: { foobar: { properties: { foo: { analyzer: 'bar' } } } } + end + should "not create the index when it exists" do client = stub('client') indices = stub('indices') From 1088670d420d2732e413fa77a0427c6baefaf9b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Samami=20=E3=83=83?= <martin@digitalkookie.io> Date: Fri, 24 Mar 2017 15:20:40 +0100 Subject: [PATCH 331/582] [MODEL] Added instructions about creating the index into the README Closes #686 Closes #675 --- elasticsearch-model/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 01ca0cc74..3f5b8df74 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -365,6 +365,10 @@ response.results.first.title For proper search engine function, it's often necessary to configure the index properly. The `Elasticsearch::Model` integration provides class methods to set up index settings and mappings. +**NOTE**: Elasticsearch will automatically create an index when a document is indexed, + with default settings and mappings. Create the index in advance with the `create_index!` + method, so your index configuration is respected. + ```ruby class Article settings index: { number_of_shards: 1 } do From 109c155df6c8b8989aa935bc1fe163a1452a2b4e Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 31 Mar 2017 15:00:15 +0200 Subject: [PATCH 332/582] [MODEL] Updated the "completion suggester" example Let's use an embedded field instead of a suffixed property for the `suggest` document property. Also added an example of search with suggest, including source filtering. Related: #690 --- .../activerecord_mapping_completion.rb | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/examples/activerecord_mapping_completion.rb b/elasticsearch-model/examples/activerecord_mapping_completion.rb index 3f276a16c..8088b8b34 100644 --- a/elasticsearch-model/examples/activerecord_mapping_completion.rb +++ b/elasticsearch-model/examples/activerecord_mapping_completion.rb @@ -18,8 +18,9 @@ class Article < ActiveRecord::Base include Elasticsearch::Model::Callbacks mapping do - indexes :title, type: 'text' - indexes :title_suggest, type: 'completion' + indexes :title, type: 'text' do + indexes :suggest, type: 'completion' + end indexes :url, type: 'keyword' end @@ -56,7 +57,7 @@ def as_indexed_json(options={}) body: { articles: { text: 'foo', - completion: { field: 'title_suggest', size: 25 } + completion: { field: 'title.suggest' } } }; @@ -64,4 +65,20 @@ def as_indexed_json(options={}) response_2['articles'].first['options'].map { |d| "#{d['text']} -> #{d['_source']['url']}" }. inspect.ansi(:bold, :green) +response_3 = Article.search \ + query: { + match: { title: 'foo' } + }, + suggest: { + articles: { + text: 'foo', + completion: { field: 'title.suggest' } + } + }, + _source: ['title', 'url'] + +puts "Article search with suggest:".ansi(:bold), + response_3.response['suggest']['articles'].first['options'].map { |d| "#{d['text']} -> #{d['_source']['url']}" }. + inspect.ansi(:bold, :blue) + require 'pry'; binding.pry; From 151d5645e068e1b4699dc44b03d3a6602c263ed2 Mon Sep 17 00:00:00 2001 From: Eric Bouchut <ebouchut@gmail.com> Date: Tue, 28 Mar 2017 17:27:30 +0200 Subject: [PATCH 333/582] [RAILS] Fixed typo in README Closes #688 --- elasticsearch-rails/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-rails/README.md b/elasticsearch-rails/README.md index 476054938..117e05fc3 100644 --- a/elasticsearch-rails/README.md +++ b/elasticsearch-rails/README.md @@ -1,6 +1,6 @@ # Elasticsearch::Rails -The `elasticsearch-rails` library is a companion for the +The `elasticsearch-rails` library is a companion for the [`elasticsearch-model`](https://github.com/elasticsearch/elasticsearch-rails/tree/master/elasticsearch-model) library, providing features suitable for Ruby on Rails applications. From c8712274e43f8c339e222ebc1711f3bf030538bc Mon Sep 17 00:00:00 2001 From: Thomas Nys <nysthee@gmail.com> Date: Fri, 8 Jul 2016 13:41:37 +0200 Subject: [PATCH 334/582] [RAILS] Fix typo in rake import task Closes #592 --- elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb b/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb index bb2f9ff3d..9b0fa2039 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb @@ -1,4 +1,4 @@ -# A collection of Rake tasks to facilitate importing data from yout models into Elasticsearch. +# A collection of Rake tasks to facilitate importing data from your models into Elasticsearch. # # Add this e.g. into the `lib/tasks/elasticsearch.rake` file in your Rails application: # From 3cc92cd131252cd2ef1e7fd31d16ddccb109f62e Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 7 Apr 2017 14:59:19 +0200 Subject: [PATCH 335/582] Added the "Compatibility" chapter to the READMEs In order to make the support for different Elasticsearch versions more clear, a special "Compatibility" chapter has been added to the main README and individual gem READMEs. The versioning scheme follows the scheme for the Elasticsearch Ruby client, ie. the version number matches the supported major version of Elasticsearch. See: * https://github.com/elastic/elasticsearch-ruby/commit/3bdb385 --- README.md | 20 ++++++++++++++++---- elasticsearch-model/README.md | 16 ++++++++++++++-- elasticsearch-persistence/README.md | 14 +++++++++++++- elasticsearch-rails/README.md | 18 +++++++++++++++--- 4 files changed, 58 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 081508540..043677574 100644 --- a/README.md +++ b/README.md @@ -16,15 +16,27 @@ This repository contains various Ruby and Rails integrations for [Elasticsearch] Elasticsearch client and Ruby API is provided by the **[elasticsearch-ruby](https://github.com/elasticsearch/elasticsearch-ruby)** project. -## Installation +## Compatibility The libraries are compatible with Ruby 1.9.3 and higher. -Install the `elasticsearch-model` and/or `elasticsearch-rails` package from -[Rubygems](https://rubygems.org/gems/elasticsearch): +The version numbers follow the Elasticsearch major versions, and the `master` branch +is compatible with the Elasticsearch `master` branch, therefore, with the next major version. + +| Rubygem | | Elasticsearch | +|:-------------:|:-:| :-----------: | +| 0.1 | → | 1.x | +| 2.x | → | 2.x | +| 5.x | → | 5.x | +| master | → | master | + +## Installation + +Install each library from [Rubygems](https://rubygems.org/gems/elasticsearch): ```ruby -gem install elasticsearch-model elasticsearch-rails +gem install elasticsearch-model +gem install elasticsearch-rails ``` To use an unreleased version, either add it to your `Gemfile` for [Bundler](http://gembundler.com): diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 3f5b8df74..b580e1c2a 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -1,13 +1,25 @@ # Elasticsearch::Model The `elasticsearch-model` library builds on top of the -the [`elasticsearch`](https://github.com/elasticsearch/elasticsearch-ruby) library. +the [`elasticsearch`](https://github.com/elastic/elasticsearch-ruby) library. It aims to simplify integration of Ruby classes ("models"), commonly found e.g. in [Ruby on Rails](http://rubyonrails.org) applications, with the [Elasticsearch](http://www.elasticsearch.org) search and analytics engine. -The library is compatible with Ruby 1.9.3 and higher. +## Compatibility + +This library is compatible with Ruby 1.9.3 and higher. + +The library version numbers follow the Elasticsearch major versions, and the `master` branch +is compatible with the Elasticsearch `master` branch, therefore, with the next major version. + +| Rubygem | | Elasticsearch | +|:-------------:|:-:| :-----------: | +| 0.1 | → | 1.x | +| 2.x | → | 2.x | +| 5.x | → | 5.x | +| master | → | master | ## Installation diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index 5a7921e09..d6b31f6d6 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -2,7 +2,19 @@ Persistence layer for Ruby domain objects in Elasticsearch, using the Repository and ActiveRecord patterns. -The library is compatible with Ruby 1.9.3 (or higher) and Elasticsearch 1.0 (or higher). +## Compatibility + +This library is compatible with Ruby 1.9.3 and higher. + +The library version numbers follow the Elasticsearch major versions, and the `master` branch +is compatible with the Elasticsearch `master` branch, therefore, with the next major version. + +| Rubygem | | Elasticsearch | +|:-------------:|:-:| :-----------: | +| 0.1 | → | 1.x | +| 2.x | → | 2.x | +| 5.x | → | 5.x | +| master | → | master | ## Installation diff --git a/elasticsearch-rails/README.md b/elasticsearch-rails/README.md index 117e05fc3..ee513b5be 100644 --- a/elasticsearch-rails/README.md +++ b/elasticsearch-rails/README.md @@ -1,10 +1,22 @@ # Elasticsearch::Rails -The `elasticsearch-rails` library is a companion for -the [`elasticsearch-model`](https://github.com/elasticsearch/elasticsearch-rails/tree/master/elasticsearch-model) +The `elasticsearch-rails` library is a companion for the +the [`elasticsearch-model`](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-model) library, providing features suitable for Ruby on Rails applications. -The library is compatible with Ruby 1.9.3 and higher. +## Compatibility + +This library is compatible with Ruby 1.9.3 and higher. + +The library version numbers follow the Elasticsearch major versions, and the `master` branch +is compatible with the Elasticsearch `master` branch, therefore, with the next major version. + +| Rubygem | | Elasticsearch | +|:-------------:|:-:| :-----------: | +| 0.1 | → | 1.x | +| 2.x | → | 2.x | +| 5.x | → | 5.x | +| master | → | master | ## Installation From f79217f683aab285a296ea981b569434c260a545 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 7 Apr 2017 15:14:28 +0200 Subject: [PATCH 336/582] Updated the Bundler instructions and Github URLs in the READMEs --- README.md | 18 +++--------------- elasticsearch-model/README.md | 10 +++++----- elasticsearch-persistence/README.md | 12 ++++++------ elasticsearch-rails/README.md | 16 ++++++++-------- 4 files changed, 22 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 043677574..f7c8e1151 100644 --- a/README.md +++ b/README.md @@ -39,23 +39,11 @@ gem install elasticsearch-model gem install elasticsearch-rails ``` -To use an unreleased version, either add it to your `Gemfile` for [Bundler](http://gembundler.com): +To use an unreleased version, add it to your `Gemfile` for [Bundler](http://bundler.io): ```ruby -gem 'elasticsearch-model', git: 'git://github.com/elasticsearch/elasticsearch-rails.git' -gem 'elasticsearch-rails', git: 'git://github.com/elasticsearch/elasticsearch-rails.git' -``` - -or install it from a source code checkout: - -```bash -git clone https://github.com/elasticsearch/elasticsearch-rails.git -cd elasticsearch-model -bundle install -rake install -cd elasticsearch-rails -bundle install -rake install +gem 'elasticsearch-model', github: 'elastic/elasticsearch-rails', branch: '5.x' +gem 'elasticsearch-rails', github: 'elastic/elasticsearch-rails', branch: '5.x' ``` ## Usage diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index b580e1c2a..1d06e8b89 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -29,11 +29,11 @@ Install the package from [Rubygems](https://rubygems.org): To use an unreleased version, either add it to your `Gemfile` for [Bundler](http://bundler.io): - gem 'elasticsearch-model', git: 'git://github.com/elasticsearch/elasticsearch-rails.git' + gem 'elasticsearch-model', git: 'git://github.com/elastic/elasticsearch-rails.git', branch: '5.x' or install it from a source code checkout: - git clone https://github.com/elasticsearch/elasticsearch-rails.git + git clone https://github.com/elastic/elasticsearch-rails.git cd elasticsearch-rails/elasticsearch-model bundle install rake install @@ -121,7 +121,7 @@ See the `Elasticsearch::Model` module documentation for technical information. ### The Elasticsearch client -The module will set up a [client](https://github.com/elasticsearch/elasticsearch-ruby/tree/master/elasticsearch), +The module will set up a [client](https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch), connected to `localhost:9200`, by default. You can access and use it as any other `Elasticsearch::Client`: ```ruby @@ -144,7 +144,7 @@ Elasticsearch::Model.client = Elasticsearch::Client.new log: true You might want to do this during your application bootstrap process, e.g. in a Rails initializer. Please refer to the -[`elasticsearch-transport`](https://github.com/elasticsearch/elasticsearch-ruby/tree/master/elasticsearch-transport) +[`elasticsearch-transport`](https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch-transport) library documentation for all the configuration options, and to the [`elasticsearch-api`](http://rubydoc.info/gems/elasticsearch-api) library documentation for information about the Ruby client API. @@ -253,7 +253,7 @@ response.records.order(:title).to_a The `records` method returns the real instances of your model, which is useful when you want to access your model methods -- at the expense of slowing down your application, of course. In most cases, working with `results` coming from Elasticsearch is sufficient, and much faster. See the -[`elasticsearch-rails`](https://github.com/elasticsearch/elasticsearch-rails/tree/master/elasticsearch-rails) +[`elasticsearch-rails`](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-rails) library for more information about compatibility with the Ruby on Rails framework. When you want to access both the database `records` and search `results`, use the `each_with_hit` diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index d6b31f6d6..8d02d227c 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -24,11 +24,11 @@ Install the package from [Rubygems](https://rubygems.org): To use an unreleased version, either add it to your `Gemfile` for [Bundler](http://bundler.io): - gem 'elasticsearch-persistence', git: 'git://github.com/elasticsearch/elasticsearch-rails.git' + gem 'elasticsearch-persistence', git: 'git://github.com/elastic/elasticsearch-rails.git', branch: '5.x' or install it from a source code checkout: - git clone https://github.com/elasticsearch/elasticsearch-rails.git + git clone https://github.com/elastic/elasticsearch-rails.git cd elasticsearch-rails/elasticsearch-persistence bundle install rake install @@ -112,7 +112,7 @@ repository.delete(note) The repository module provides a number of features and facilities to configure and customize the behavior: -* Configuring the Elasticsearch [client](https://github.com/elasticsearch/elasticsearch-ruby#usage) being used +* Configuring the Elasticsearch [client](https://github.com/elastic/elasticsearch-ruby#usage) being used * Setting the index name, document type, and object class for deserialization * Composing mappings and settings for the index * Creating, deleting or refreshing the index @@ -263,7 +263,7 @@ puts repository.find(1).attributes['image'] ##### Client -The repository uses the standard Elasticsearch [client](https://github.com/elasticsearch/elasticsearch-ruby#usage), +The repository uses the standard Elasticsearch [client](https://github.com/elastic/elasticsearch-ruby#usage), which is accessible with the `client` getter and setter methods: ```ruby @@ -622,7 +622,7 @@ puts results.response.aggregations.authors.buckets.each { |b| puts "#{b['key']} #### The Elasticsearch Client -The module will set up a [client](https://github.com/elasticsearch/elasticsearch-ruby/tree/master/elasticsearch), +The module will set up a [client](https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch), connected to `localhost:9200`, by default. To use a client with different configuration: @@ -688,7 +688,7 @@ rails generate scaffold Person name:String email:String birthday:Date --orm=elas A fully working Ruby on Rails application can be generated with the following command: ```bash -rails new music --force --skip --skip-bundle --skip-active-record --template https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/template.rb +rails new music --force --skip --skip-bundle --skip-active-record --template https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-persistence/examples/music/template.rb ``` The application demonstrates: diff --git a/elasticsearch-rails/README.md b/elasticsearch-rails/README.md index ee513b5be..eb0cb2782 100644 --- a/elasticsearch-rails/README.md +++ b/elasticsearch-rails/README.md @@ -26,11 +26,11 @@ Install the package from [Rubygems](https://rubygems.org): To use an unreleased version, either add it to your `Gemfile` for [Bundler](http://bundler.io): - gem 'elasticsearch-rails', git: 'git://github.com/elasticsearch/elasticsearch-rails.git' + gem 'elasticsearch-rails', git: 'git://github.com/elastic/elasticsearch-rails.git', branch: '5.x' or install it from a source code checkout: - git clone https://github.com/elasticsearch/elasticsearch-rails.git + git clone https://github.com/elastic/elasticsearch-rails.git cd elasticsearch-rails/elasticsearch-rails bundle install rake install @@ -99,22 +99,22 @@ You should see the duration of the request to Elasticsearch as part of each log You can generate a fully working example Ruby on Rails application, with an `Article` model and a search form, to play with (it even downloads _Elasticsearch_ itself, generates the application skeleton and leaves you with a _Git_ repository to explore the steps and the code) with the -[`01-basic.rb`](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-rails/lib/rails/templates/01-basic.rb) template: +[`01-basic.rb`](https://github.com/elastic/elasticsearch-rails/blob/master/elasticsearch-rails/lib/rails/templates/01-basic.rb) template: ```bash -rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/01-basic.rb +rails new searchapp --skip --skip-bundle --template https://raw.github.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/01-basic.rb ``` Run the same command again, in the same folder, with the -[`02-pretty`](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-rails/lib/rails/templates/02-pretty.rb) +[`02-pretty`](https://github.com/elastic/elasticsearch-rails/blob/master/elasticsearch-rails/lib/rails/templates/02-pretty.rb) template to add features such as a custom `Article.search` method, result highlighting and [_Bootstrap_](http://getbootstrap.com) integration: ```bash -rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/02-pretty.rb +rails new searchapp --skip --skip-bundle --template https://raw.github.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/02-pretty.rb ``` -Run the same command with the [`03-expert.rb`](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-rails/lib/rails/templates/03-expert.rb) +Run the same command with the [`03-expert.rb`](https://github.com/elastic/elasticsearch-rails/blob/master/elasticsearch-rails/lib/rails/templates/03-expert.rb) template to refactor the application into a more complex use case, with couple of hundreds of The New York Times articles as the example content. The template will extract the Elasticsearch integration into a `Searchable` "concern" module, @@ -122,7 +122,7 @@ define complex mapping, custom serialization, implement faceted navigation and s a complex query, and add a _Sidekiq_-based worker for updating the index in the background. ```bash -rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/03-expert.rb +rails new searchapp --skip --skip-bundle --template https://raw.github.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/03-expert.rb ``` ## License From c25885449e13619deb0fbc4aca7d4da9dc7cd4a2 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 7 Apr 2017 15:53:27 +0200 Subject: [PATCH 337/582] Added a CHANGELOG to the repository --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..ed56ccac5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +## 0.1.9 + +The last version for the old versioning scheme -- please see the Git commit log +at https://github.com/elastic/elasticsearch-rails/commits/v0.1.9 From 98021ba6d9b5facf5fc5b8e3a8dd4f6cfd267bf0 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 7 Apr 2017 16:46:08 +0200 Subject: [PATCH 338/582] Added a Rake task for updating the version and compiling the CHANGELOG --- Rakefile | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/Rakefile b/Rakefile index 64fab6ac6..b4c90d710 100644 --- a/Rakefile +++ b/Rakefile @@ -126,3 +126,117 @@ task :release do puts '-'*80 end end + +desc <<-DESC + Update Rubygems versions in version.rb and *.gemspec files + + Example: + + $ rake update_version[5.0.0,5.0.1] +DESC +task :update_version, :old, :new do |task, args| + require 'ansi' + + puts "[!!!] Required argument [old] missing".ansi(:red) unless args[:old] + puts "[!!!] Required argument [new] missing".ansi(:red) unless args[:new] + + files = Dir['**/**/version.rb','**/**/*.gemspec'] + + longest_line = files.map { |f| f.size }.max + + puts "\n", "= FILES ".ansi(:faint) + ('='*92).ansi(:faint), "\n" + + files.each do |file| + begin + File.open(file, 'r+') do |f| + content = f.read + if content.match Regexp.new(args[:old]) + content.gsub! Regexp.new(args[:old]), args[:new] + puts "+ [#{file}]".ansi(:green).ljust(longest_line+20) + " [#{args[:old]}] -> [#{args[:new]}]".ansi(:green,:bold) + f.rewind + f.write content + else + puts "- [#{file}]".ansi(:yellow).ljust(longest_line+20) + " -".ansi(:faint,:strike) + end + end + rescue Exception => e + puts "[!!!] #{e.class} : #{e.message}".ansi(:red,:bold) + raise e + end + end + + puts "\n\n", "= CHANGELOG ".ansi(:faint) + ('='*88).ansi(:faint), "\n" + + log = `git --no-pager log --reverse --no-color --pretty='* %s' HEAD --not v#{args[:old]} elasticsearch-*`.split("\n") + + puts log.join("\n") + + log_entries = {} + log_entries[:common] = log.reject { |l| l =~ /^* \[/ } + log_entries[:model] = log.select { |l| l =~ /^* \[MODEL\]/ } + log_entries[:store] = log.select { |l| l =~ /^* \[STORE\]/ } + log_entries[:rails] = log.select { |l| l =~ /^* \[RAILS\]/ } + + changelog = File.read(File.open('CHANGELOG.md', 'r')) + + changelog_update = '' + + changelog_update << "## #{args[:new]}\n\n" + + unless log_entries[:common].empty? + changelog_update << log_entries[:common] + .map { |l| "#{l}" } + .join("\n") + changelog_update << "\n\n" + end + + unless log_entries[:model].empty? + changelog_update << "### ActiveModel\n\n" + changelog_update << log_entries[:model] + .map { |l| l.gsub /\[.+\] /, '' } + .map { |l| "#{l}" } + .join("\n") + changelog_update << "\n\n" + end + + unless log_entries[:store].empty? + changelog_update << "### Persistence\n\n" + changelog_update << log_entries[:store] + .map { |l| l.gsub /\[.+\] /, '' } + .map { |l| "#{l}" } + .join("\n") + changelog_update << "\n\n" + end + + unless log_entries[:rails].empty? + changelog_update << "### Ruby on Rails\n\n" + changelog_update << log_entries[:rails] + .map { |l| l.gsub /\[.+\] /, '' } + .map { |l| "#{l}" } + .join("\n") + changelog_update << "\n\n" + end + + unless changelog =~ /^## #{args[:new]}/ + File.open('CHANGELOG.md', 'w+') { |f| f.write changelog_update and f.write changelog } + end + + puts "\n\n", "= DIFF ".ansi(:faint) + ('='*93).ansi(:faint) + + diff = `git --no-pager diff --patch --word-diff=color --minimal elasticsearch-*`.split("\n") + + puts diff + .reject { |l| l =~ /^\e\[1mdiff \-\-git/ } + .reject { |l| l =~ /^\e\[1mindex [a-z0-9]{7}/ } + .reject { |l| l =~ /^\e\[1m\-\-\- i/ } + .reject { |l| l =~ /^\e\[36m@@/ } + .map { |l| l =~ /^\e\[1m\+\+\+ w/ ? "\n#{l} " + '-'*(104-l.size) : l } + .join("\n") + + puts "\n\n", "= COMMIT ".ansi(:faint) + ('='*91).ansi(:faint), "\n" + + puts "git add CHANGELOG.md elasticsearch-*", + "git commit --verbose --message='Release #{args[:new]}' --edit", + "rake release" + "\n" +end From b4a5ca7733a0f08953644e901176c7a1092f6cb4 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 7 Apr 2017 16:52:13 +0200 Subject: [PATCH 339/582] [CI] Updated the Elasticsearch version at Travis to "6.0.0-alpha1-SNAPSHOT" --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 364a58c3b..094305681 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,14 +28,14 @@ matrix: - rvm: 2.3.3 jdk: oraclejdk8 - env: TEST_SUITE=integration QUIET=y SERVER=start TEST_CLUSTER_LOGS=/tmp/log TEST_CLUSTER_COMMAND=/tmp/elasticsearch-5.2.0/bin/elasticsearch + env: TEST_SUITE=integration QUIET=y SERVER=start TEST_CLUSTER_LOGS=/tmp/log TEST_CLUSTER_COMMAND=/tmp/elasticsearch-6.0.0-alpha1-SNAPSHOT/bin/elasticsearch before_install: - gem update --system --no-rdoc --no-ri - gem --version - gem install bundler -v 1.14.3 --no-rdoc --no-ri - bundle version - - curl -sS https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.2.0.tar.gz | tar xz -C /tmp + - curl -sS https://snapshots.elastic.co/downloads/elasticsearch/elasticsearch-6.0.0-alpha1-SNAPSHOT.tar.gz | tar xz -C /tmp install: - bundle install From ac5e0f3a52c44531cc0c6fb108c116e14013aa6d Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 7 Apr 2017 16:53:32 +0200 Subject: [PATCH 340/582] [CI] Updated the list of branches for Travis to include "2.x" and "5.x" --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 094305681..862ec50f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,8 @@ branches: only: - master - travis + - 5.x + - 2.x matrix: include: From ffda22df1b98f4bc7353184a0f8e09c4bf8f5c07 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Mon, 10 Apr 2017 11:18:48 +0200 Subject: [PATCH 341/582] Updated the version on the `master` branch to `6.0.0.alpha1` --- CHANGELOG.md | 75 +++++++++++++++++++ .../lib/elasticsearch/model/version.rb | 2 +- .../lib/elasticsearch/persistence/version.rb | 2 +- .../lib/elasticsearch/rails/version.rb | 2 +- 4 files changed, 78 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed56ccac5..d81c0e69e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,78 @@ +## 6.0.0.alpha1 + +* Updated the Rake dependency to 11.1 +* Reduced verbosity of `rake test:unit` and `rake test:integration` +* Removed the "CI Reporter" integration from test Rake tasks +* Added the "Compatibility" chapter to the READMEs +* Updated the Bundler instructions and Github URLs in the READMEs + +### ActiveModel + +* Fixed a problem where `Hashie::Mash#min` and `#max` returned unexpected values +* Added information about `elasticsearch-dsl` to the README +* Added support for inherited index names and doc types +* Added a `Elasticsearch::Model.settings` method +* Changed the naming inheritance logic to use `Elasticsearch::Model.settings` +* Added information about the `settings` method and the `inheritance_enabled` setting into the README +* Disable "verbose" and "warnings" in integration tests +* Added code for establishing ActiveRecord connections to test classes +* Reorganized the class definitions in the integration tests +* Moved `require` within unit test to the top of the file +* Added ActiveRecord 5 support to integration test configuration +* Fixed records sorting with ActiveRecord 5.x +* Added, that `add_index` for ActiveRecord models is only called when it doesn't exist already +* Use `records.__send__ :load` instead of `records.load` in the ActiveRecord adapter +* Call `Kaminari::Hooks.init` only when available +* Fixed the deprecation messages for `raise_in_transactional_callbacks` +* Fixed the deprecation messages for `timestamps` in migrations in integration tests +* Fixed the naming for the indexing integration tests +* Fixed the failing integration tests for ActiveRecord associations +* Fixed integration tests for ActiveRecord pagination +* Added the `rake bundle:install` Rake task to install dependencies for all gemfiles +* Run unit tests against all Gemfiles +* Updated dependencies in gemspec +* Relaxed the dependency on the "elasticsearch" gem +* Fixed the completion example for ActiveRecord for Elasticsearch 5 +* Added an example with Edge NGram mapping for auto-completion +* Expanded the example for indexing and searching ActiveRecord associations +* Added an example for source filtering to the ActiveRecord associations example +* Fixed a typo in the README +* Changed the default mapping type to `text` +* Added a `HashWrapper` class to wrap Hash structures instead of raw `Hashie::Mash` +* Call `Hashie.disable_warnings` method in Response wrappers +* Added, that `HashWrapper`, a sub-class of `Hashie::Mash` is used +* Updated the configuration for required routing in the integration test +* Fixed incorrect name for the parent/child integration test +* Fixed incorrect mapping configuration in the integration tests +* Allow passing the index settings and mappings as arguments to `create_index!` +* Added instructions about creating the index into the README +* Updated the "completion suggester" example + +### Persistence + +* Updated dependencies in gemspec +* Updated dependencies in gemspec +* Relaxed the dependency on the "elasticsearch" gem +* Use `text` instead of `string` for the <String> data types +* Changed the default mapping type to `text` +* Removed the `search_type=scan` in the `find_in_batches` method +* Updated the `count` method in the "repository" module +* Updated the "update by script" integration test for Elasticsearch 5 +* Added, that `HashWrapper`, a sub-class of `Hashie::Mash` is used +* Updated the "Notes" example application for Elasticsearch 5.x +* Updated the "Music" example application for Elasticsearch 5.x +* Updated the URLs in the "Music" application template +* Updated the Git URLs in the "Notes" example application + +### Ruby on Rails + +* Updated the application templates to support Rails 5 & Elasticsearch 5 +* Updated the `03-expert` application template to work with Rails 5 +* Updated the application templates to work with README.md instead of README.rdoc +* Updated the installation process in the "01-basic" application template +* Fixed typo in README +* Fix typo in rake import task + ## 0.1.9 The last version for the old versioning scheme -- please see the Git commit log diff --git a/elasticsearch-model/lib/elasticsearch/model/version.rb b/elasticsearch-model/lib/elasticsearch/model/version.rb index 44cfdabea..cfa7e4ab6 100644 --- a/elasticsearch-model/lib/elasticsearch/model/version.rb +++ b/elasticsearch-model/lib/elasticsearch/model/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Model - VERSION = "0.1.9" + VERSION = "6.0.0.alpha1" end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb index 93852c314..d54a3a37f 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Persistence - VERSION = "0.1.9" + VERSION = "6.0.0.alpha1" end end diff --git a/elasticsearch-rails/lib/elasticsearch/rails/version.rb b/elasticsearch-rails/lib/elasticsearch/rails/version.rb index 88b2dd758..4b65f5196 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/version.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Rails - VERSION = "0.1.9" + VERSION = "6.0.0.alpha1" end end From 5bc6dccf864cc06f1087aecdaa0c2b95f10cef96 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Mon, 10 Apr 2017 16:36:49 +0200 Subject: [PATCH 342/582] [STORE] Updated the failing integration tests for Elasticsearch 5.x --- elasticsearch-persistence/README.md | 4 ++-- .../lib/elasticsearch/persistence/model/find.rb | 3 ++- .../test/integration/model/model_basic_test.rb | 4 ++-- elasticsearch-persistence/test/unit/model_rails_test.rb | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index 8d02d227c..4c6dead10 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -491,8 +491,8 @@ class Article # Define an `author` attribute, with multiple analyzers for this field # attribute :author, String, mapping: { fields: { - author: { type: 'string'}, - raw: { type: 'string', analyzer: 'keyword' } + author: { type: 'text'}, + raw: { type: 'keyword' } } } diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb index 43276dc03..c09c9ac38 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb @@ -119,7 +119,8 @@ def find_in_batches(options={}, &block) # Get the initial batch of documents and the scroll_id # - response = gateway.client.search( { index: gateway.index_name, + response = gateway.client.search( { + index: gateway.index_name, type: gateway.document_type, scroll: scroll, sort: ['_doc'], diff --git a/elasticsearch-persistence/test/integration/model/model_basic_test.rb b/elasticsearch-persistence/test/integration/model/model_basic_test.rb index b052114f8..33555c980 100644 --- a/elasticsearch-persistence/test/integration/model/model_basic_test.rb +++ b/elasticsearch-persistence/test/integration/model/model_basic_test.rb @@ -16,8 +16,8 @@ class ::Person attribute :name, String, mapping: { fields: { - name: { type: 'string', analyzer: 'snowball' }, - raw: { type: 'string', analyzer: 'keyword' } + name: { type: 'text', analyzer: 'snowball' }, + raw: { type: 'keyword' } } } attribute :birthday, Date diff --git a/elasticsearch-persistence/test/unit/model_rails_test.rb b/elasticsearch-persistence/test/unit/model_rails_test.rb index 9ead42896..3f0ce913b 100644 --- a/elasticsearch-persistence/test/unit/model_rails_test.rb +++ b/elasticsearch-persistence/test/unit/model_rails_test.rb @@ -11,7 +11,7 @@ class ::MyRailsModel include Elasticsearch::Persistence::Model include Elasticsearch::Persistence::Model::Rails - attribute :name, String, mapping: { analyzer: 'string' } + attribute :name, String, mapping: { analyzer: 'english' } attribute :published_at, DateTime attribute :published_on, Date end From 82b5e81d0282cd35a8639b9c5a7b91020f5418e7 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Mon, 10 Apr 2017 16:43:36 +0200 Subject: [PATCH 343/582] [STORE] Updated the dependency for "elasticsearch" and "elasticsearch-model" to `5.x` Related: #693 --- elasticsearch-persistence/elasticsearch-persistence.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index 510da3a3e..311325fdc 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -23,8 +23,8 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 1.9.3" - s.add_dependency "elasticsearch", '> 1' - s.add_dependency "elasticsearch-model", '>= 0.1' + s.add_dependency "elasticsearch", '~> 5' + s.add_dependency "elasticsearch-model", '~> 5' s.add_dependency "activesupport", '> 4' s.add_dependency "activemodel", '> 4' s.add_dependency "hashie" From 1630cf6d4a5494117e42ac0c4e054ad2d31bc07e Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Mon, 10 Apr 2017 17:34:46 +0200 Subject: [PATCH 344/582] [MODEL] Added an example with a custom "pattern" analyzer Related: #697 --- .../examples/activerecord_custom_analyzer.rb | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 elasticsearch-model/examples/activerecord_custom_analyzer.rb diff --git a/elasticsearch-model/examples/activerecord_custom_analyzer.rb b/elasticsearch-model/examples/activerecord_custom_analyzer.rb new file mode 100644 index 000000000..e81a77634 --- /dev/null +++ b/elasticsearch-model/examples/activerecord_custom_analyzer.rb @@ -0,0 +1,106 @@ +# Custom Analyzer for ActiveRecord integration with Elasticsearch +# =============================================================== + +$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) + +require 'ansi' +require 'logger' + +require 'active_record' +require 'elasticsearch/model' + +ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT) +ActiveRecord::Base.establish_connection( adapter: 'sqlite3', database: ":memory:" ) + +ActiveRecord::Schema.define(version: 1) do + create_table :articles do |t| + t.string :title + t.date :published_at + t.timestamps + end +end + +Elasticsearch::Model.client.transport.logger = ActiveSupport::Logger.new(STDOUT) +Elasticsearch::Model.client.transport.logger.formatter = lambda { |s, d, p, m| "#{m.ansi(:faint)}\n" } + +class Article < ActiveRecord::Base + include Elasticsearch::Model + + settings index: { + number_of_shards: 1, + number_of_replicas: 0, + analysis: { + analyzer: { + pattern: { + type: 'pattern', + pattern: "_|-|\\.", + lowercase: true + } + } + } } do + mapping do + indexes :title, type: 'text' do + indexes :keyword, analyzer: 'keyword' + indexes :pattern, analyzer: 'pattern' + end + end + end +end + +# Create example records +# +Article.delete_all +Article.create title: 'Foo' +Article.create title: 'Foo-Bar' +Article.create title: 'Foo_Bar_Baz' +Article.create title: 'Foo.Bar' + +# Index records +# +errors = Article.import force: true, refresh: true, return: 'errors' +puts "[!] Errors importing records: #{errors.map { |d| d['index']['error'] }.join(', ')}".ansi(:red) && exit(1) unless errors.empty? + +puts '', '-'*80 + +puts "Fulltext analyzer [Foo_Bar_1]".ansi(:bold), + "Tokens: " + + Article.__elasticsearch__.client.indices + .analyze(index: Article.index_name, body: { field: 'title', text: 'Foo_Bar_1' })['tokens'] + .map { |d| "[#{d['token']}]" }.join(' '), + "\n" + +puts "Keyword analyzer [Foo_Bar_1]".ansi(:bold), + "Tokens: " + + Article.__elasticsearch__.client.indices + .analyze(index: Article.index_name, body: { field: 'title.keyword', text: 'Foo_Bar_1' })['tokens'] + .map { |d| "[#{d['token']}]" }.join(' '), + "\n" + +puts "Pattern analyzer [Foo_Bar_1]".ansi(:bold), + "Tokens: " + + Article.__elasticsearch__.client.indices + .analyze(index: Article.index_name, body: { field: 'title.pattern', text: 'Foo_Bar_1' })['tokens'] + .map { |d| "[#{d['token']}]" }.join(' '), + "\n" + +puts '', '-'*80 + +response = Article.search query: { match: { 'title' => 'foo' } } ; + +puts "Search for 'foo'".ansi(:bold), + "#{response.response.hits.total} matches: " + + response.records.map { |d| d.title }.join(', '), + "\n" + +puts '', '-'*80 + +response = Article.search query: { match: { 'title.pattern' => 'foo' } } ; + +puts "Pattern search for 'foo'".ansi(:bold), + "#{response.response.hits.total} matches: " + + response.records.map { |d| d.title }.join(', '), + "\n" + +puts '', '-'*80 + +require 'pry'; binding.pry; From dfe3f12aeb27cdfc2599e810f303f7af36bc7ace Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Mon, 10 Apr 2017 17:51:43 +0200 Subject: [PATCH 345/582] [MODEL] Added a "trigram" custom analyzer to the example Related: #697 --- .../examples/activerecord_custom_analyzer.rb | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/elasticsearch-model/examples/activerecord_custom_analyzer.rb b/elasticsearch-model/examples/activerecord_custom_analyzer.rb index e81a77634..83cbf506d 100644 --- a/elasticsearch-model/examples/activerecord_custom_analyzer.rb +++ b/elasticsearch-model/examples/activerecord_custom_analyzer.rb @@ -35,6 +35,17 @@ class Article < ActiveRecord::Base type: 'pattern', pattern: "_|-|\\.", lowercase: true + }, + trigram: { + tokenizer: 'trigram' + } + }, + tokenizer: { + trigram: { + type: 'ngram', + min_gram: 3, + max_gram: 3, + token_chars: ['letter', 'digit'] } } } } do @@ -42,6 +53,7 @@ class Article < ActiveRecord::Base indexes :title, type: 'text' do indexes :keyword, analyzer: 'keyword' indexes :pattern, analyzer: 'pattern' + indexes :trigram, analyzer: 'trigram' end end end @@ -52,7 +64,7 @@ class Article < ActiveRecord::Base Article.delete_all Article.create title: 'Foo' Article.create title: 'Foo-Bar' -Article.create title: 'Foo_Bar_Baz' +Article.create title: 'Foo_Bar_Bazooka' Article.create title: 'Foo.Bar' # Index records @@ -62,24 +74,31 @@ class Article < ActiveRecord::Base puts '', '-'*80 -puts "Fulltext analyzer [Foo_Bar_1]".ansi(:bold), +puts "Fulltext analyzer [Foo_Bar_1_Bazooka]".ansi(:bold), "Tokens: " + Article.__elasticsearch__.client.indices - .analyze(index: Article.index_name, body: { field: 'title', text: 'Foo_Bar_1' })['tokens'] + .analyze(index: Article.index_name, body: { field: 'title', text: 'Foo_Bar_1_Bazooka' })['tokens'] .map { |d| "[#{d['token']}]" }.join(' '), "\n" -puts "Keyword analyzer [Foo_Bar_1]".ansi(:bold), +puts "Keyword analyzer [Foo_Bar_1_Bazooka]".ansi(:bold), "Tokens: " + Article.__elasticsearch__.client.indices - .analyze(index: Article.index_name, body: { field: 'title.keyword', text: 'Foo_Bar_1' })['tokens'] + .analyze(index: Article.index_name, body: { field: 'title.keyword', text: 'Foo_Bar_1_Bazooka' })['tokens'] .map { |d| "[#{d['token']}]" }.join(' '), "\n" -puts "Pattern analyzer [Foo_Bar_1]".ansi(:bold), +puts "Pattern analyzer [Foo_Bar_1_Bazooka]".ansi(:bold), "Tokens: " + Article.__elasticsearch__.client.indices - .analyze(index: Article.index_name, body: { field: 'title.pattern', text: 'Foo_Bar_1' })['tokens'] + .analyze(index: Article.index_name, body: { field: 'title.pattern', text: 'Foo_Bar_1_Bazooka' })['tokens'] + .map { |d| "[#{d['token']}]" }.join(' '), + "\n" + +puts "Trigram analyzer [Foo_Bar_1_Bazooka]".ansi(:bold), + "Tokens: " + + Article.__elasticsearch__.client.indices + .analyze(index: Article.index_name, body: { field: 'title.trigram', text: 'Foo_Bar_1_Bazooka' })['tokens'] .map { |d| "[#{d['token']}]" }.join(' '), "\n" @@ -103,4 +122,14 @@ class Article < ActiveRecord::Base puts '', '-'*80 +response = Article.search query: { match: { 'title.trigram' => 'zoo' } } ; + +puts "Trigram search for 'zoo'".ansi(:bold), + "#{response.response.hits.total} matches: " + + response.records.map { |d| d.title }.join(', '), + "\n" + +puts '', '-'*80 + + require 'pry'; binding.pry; From c989def857df3bb9a92374b846e6496cab56c392 Mon Sep 17 00:00:00 2001 From: Tyler Langlois <tjl@byu.net> Date: Mon, 10 Apr 2017 12:04:11 -0400 Subject: [PATCH 346/582] [MODEL] Fix README typo (s/situation/situations) Thanks for catching, @adamcrown [ci skip] --- elasticsearch-model/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 1d06e8b89..3ec11beb3 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -321,7 +321,7 @@ Elasticsearch::Model::Response::Response.__send__ :include, Elasticsearch::Model #### The Elasticsearch DSL -In most situation, you'll want to pass the search definition +In most situations, you'll want to pass the search definition in the Elasticsearch [domain-specific language](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html) to the client: ```ruby From 71b4ed9f4ed4a8e1fc681f3b9cf654943001d42e Mon Sep 17 00:00:00 2001 From: Tony Pitale <tpitale@gmail.com> Date: Fri, 15 Aug 2014 21:14:50 -0400 Subject: [PATCH 347/582] [MODEL] Fix reference to @ids in example and README There is a method called `ids` which collections the ids from hits: https://github.com/elasticsearch/elasticsearch-rails/blob/104f91f24ba8d3dc7629b0eaf058329dc2cdc08c/elasticsearch-model/lib/elasticsearch/model/response/records.rb#L33 Closes #212 --- elasticsearch-model/README.md | 2 +- elasticsearch-model/examples/datamapper_article.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 3ec11beb3..e7c403e46 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -674,7 +674,7 @@ module DataMapperAdapter # module Records def records - klass.all(id: @ids) + klass.all(id: ids) end # ... diff --git a/elasticsearch-model/examples/datamapper_article.rb b/elasticsearch-model/examples/datamapper_article.rb index 383b3738f..b051361d1 100644 --- a/elasticsearch-model/examples/datamapper_article.rb +++ b/elasticsearch-model/examples/datamapper_article.rb @@ -50,7 +50,7 @@ module DataMapperAdapter # module Records def records - klass.all(id: @ids) + klass.all(id: ids) end # ... From 26563ee2b821e953fd48ab97e05f51da479b81dd Mon Sep 17 00:00:00 2001 From: Tony Pitale <tpitale@gmail.com> Date: Tue, 26 Aug 2014 21:07:35 -0400 Subject: [PATCH 348/582] [MODEL] Add Callbacks to the example datamapper adapter I added these callbacks in my code, and thought it might be useful for other people. Closes #220 --- elasticsearch-model/examples/datamapper_article.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/elasticsearch-model/examples/datamapper_article.rb b/elasticsearch-model/examples/datamapper_article.rb index b051361d1..291df7d3d 100644 --- a/elasticsearch-model/examples/datamapper_article.rb +++ b/elasticsearch-model/examples/datamapper_article.rb @@ -55,6 +55,16 @@ def records # ... end + + module Callbacks + def self.included(model) + model.class_eval do + after(:create) { __elasticsearch__.index_document } + after(:save) { __elasticsearch__.update_document } + after(:destroy) { __elasticsearch__.delete_document } + end + end + end end # Register the adapter From 0afd8b80c45c04615355620a1627323c44e848dd Mon Sep 17 00:00:00 2001 From: Wai-Yin Kwan <wykhuh@users.noreply.github.com> Date: Mon, 11 Jan 2016 15:38:05 -0800 Subject: [PATCH 349/582] [MODEL] Fix `Asynchronous Callbacks` example Change `record.as_indexed_json ` to `record.__elasticsearch__.as_indexed_json` in the `Asynchronous Callbacks` example. Fixes #518 --- elasticsearch-model/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index e7c403e46..136912fd4 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -534,7 +534,7 @@ class Indexer case operation.to_s when /index/ record = Article.find(record_id) - Client.index index: 'articles', type: 'article', id: record.id, body: record.as_indexed_json + Client.index index: 'articles', type: 'article', id: record.id, body: record.__elasticsearch__.as_indexed_json when /delete/ Client.delete index: 'articles', type: 'article', id: record_id else raise ArgumentError, "Unknown operation '#{operation}'" From b5c01fd840ae069f8c7288450f1c39d65f3a4370 Mon Sep 17 00:00:00 2001 From: Celso Dantas <celsodantas@gmail.com> Date: Tue, 22 Mar 2016 16:25:38 -0400 Subject: [PATCH 350/582] [STORE] Documentation for Model should include Model and not Repository Fixes #548 --- .../lib/elasticsearch/persistence/model.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb index ce948d4f3..f6cd50eb0 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb @@ -19,7 +19,7 @@ module Persistence # require 'elasticsearch/persistence/model' # # class MyObject - # include Elasticsearch::Persistence::Repository + # include Elasticsearch::Persistence::Model # end # module Model From d023d43cbb2f616e7ac5b3a33437ca610333295e Mon Sep 17 00:00:00 2001 From: Vicente Alencar <vicentealencar@users.noreply.github.com> Date: Tue, 11 Apr 2017 23:24:28 -0300 Subject: [PATCH 351/582] [MODEL] Fixed a typo in the README Typo Explicitely -> Explicitly Closes #700 --- elasticsearch-model/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 136912fd4..22b9b378c 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -425,7 +425,7 @@ Article.__elasticsearch__.refresh_index! ``` By default, index name and document type will be inferred from your class name, -you can set it explicitely, however: +you can set it explicitly, however: ```ruby class Article From 02d9814aa50985b295b3aa1e5755a781dcb81964 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Thu, 4 May 2017 15:37:20 +0200 Subject: [PATCH 352/582] [MODEL] Improved the custom analyzer example Related: #697 --- .../examples/activerecord_custom_analyzer.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/elasticsearch-model/examples/activerecord_custom_analyzer.rb b/elasticsearch-model/examples/activerecord_custom_analyzer.rb index 83cbf506d..07371508a 100644 --- a/elasticsearch-model/examples/activerecord_custom_analyzer.rb +++ b/elasticsearch-model/examples/activerecord_custom_analyzer.rb @@ -33,7 +33,7 @@ class Article < ActiveRecord::Base analyzer: { pattern: { type: 'pattern', - pattern: "_|-|\\.", + pattern: "\\s|_|-|\\.", lowercase: true }, trigram: { @@ -50,7 +50,7 @@ class Article < ActiveRecord::Base } } } do mapping do - indexes :title, type: 'text' do + indexes :title, type: 'text', analyzer: 'english' do indexes :keyword, analyzer: 'keyword' indexes :pattern, analyzer: 'pattern' indexes :trigram, analyzer: 'trigram' @@ -74,7 +74,7 @@ class Article < ActiveRecord::Base puts '', '-'*80 -puts "Fulltext analyzer [Foo_Bar_1_Bazooka]".ansi(:bold), +puts "English analyzer [Foo_Bar_1_Bazooka]".ansi(:bold), "Tokens: " + Article.__elasticsearch__.client.indices .analyze(index: Article.index_name, body: { field: 'title', text: 'Foo_Bar_1_Bazooka' })['tokens'] @@ -106,7 +106,7 @@ class Article < ActiveRecord::Base response = Article.search query: { match: { 'title' => 'foo' } } ; -puts "Search for 'foo'".ansi(:bold), +puts "English search for 'foo'".ansi(:bold), "#{response.response.hits.total} matches: " + response.records.map { |d| d.title }.join(', '), "\n" From 64f31b41c073f05eac7d089aeea07c4366b7adca Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Mon, 15 May 2017 16:01:49 +0200 Subject: [PATCH 353/582] [MODEL] Removed left-overs from previous implementation in the "completion suggester" example Related: 109c155 --- .../examples/activerecord_mapping_completion.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/elasticsearch-model/examples/activerecord_mapping_completion.rb b/elasticsearch-model/examples/activerecord_mapping_completion.rb index 8088b8b34..b8270313c 100644 --- a/elasticsearch-model/examples/activerecord_mapping_completion.rb +++ b/elasticsearch-model/examples/activerecord_mapping_completion.rb @@ -25,9 +25,7 @@ class Article < ActiveRecord::Base end def as_indexed_json(options={}) - as_json.merge \ - title_suggest: { input: title }, - url: "/articles/#{id}" + as_json.merge 'url' => "/articles/#{id}" end end From 82b45f58690236fbf878c2ce24dc7066a5e3b7c7 Mon Sep 17 00:00:00 2001 From: Josh Becker <beckerbi@gmail.com> Date: Tue, 28 Nov 2017 15:09:27 -0500 Subject: [PATCH 354/582] [MODEL] Updated the `changes` method name in `Indexing` to `changes_to_save` for compatibility with Rails 5.1 Without this patch, the log on Rails 5.1 is full of deprecation warnings like: DEPRECATION WARNING: The behavior of `changed_attributes` inside of after callbacks will be changing in the next version of Rails. The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now). To maintain the current behavior, use `saved_changes.transform_values(&:first)` instead. (called from update_funding_caches at... It was first reported in elastic/elasticsearch-rails#714. This patch fixes #758. --- .../lib/elasticsearch/model/indexing.rb | 14 ++++++------ .../lib/elasticsearch/model/proxy.rb | 10 ++++----- .../test/unit/indexing_test.rb | 22 +++++++++---------- elasticsearch-model/test/unit/proxy_test.rb | 8 +++---- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index ac507b079..d58472a9f 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -307,7 +307,7 @@ module InstanceMethods def self.included(base) # Register callback for storing changed attributes for models - # which implement `before_save` and `changed_attributes` methods + # which implement `before_save` and `attributes_in_database` methods # # @note This is typically triggered only when the module would be # included in the model directly, not within the proxy. @@ -315,9 +315,9 @@ def self.included(base) # @see #update_document # base.before_save do |instance| - instance.instance_variable_set(:@__changed_attributes, - Hash[ instance.changes.map { |key, value| [key, value.last] } ]) - end if base.respond_to?(:before_save) && base.instance_methods.include?(:changed_attributes) + instance.instance_variable_set(:@__attributes_in_database, + Hash[ instance.changes_to_save.map { |key, value| [key, value.last] } ]) + end if base.respond_to?(:before_save) && base.instance_methods.include?(:attributes_in_database) end # Serializes the model instance into JSON (by calling `as_indexed_json`), @@ -391,11 +391,11 @@ def delete_document(options={}) # @see http://rubydoc.info/gems/elasticsearch-api/Elasticsearch/API/Actions:update # def update_document(options={}) - if changed_attributes = self.instance_variable_get(:@__changed_attributes) + if attributes_in_database = self.instance_variable_get(:@__attributes_in_database) attributes = if respond_to?(:as_indexed_json) - self.as_indexed_json.select { |k,v| changed_attributes.keys.map(&:to_s).include? k.to_s } + self.as_indexed_json.select { |k,v| attributes_in_database.keys.map(&:to_s).include? k.to_s } else - changed_attributes + attributes_in_database end client.update( diff --git a/elasticsearch-model/lib/elasticsearch/model/proxy.rb b/elasticsearch-model/lib/elasticsearch/model/proxy.rb index 3e37f28ec..606c079a6 100644 --- a/elasticsearch-model/lib/elasticsearch/model/proxy.rb +++ b/elasticsearch-model/lib/elasticsearch/model/proxy.rb @@ -54,15 +54,15 @@ def __elasticsearch__ &block end # Register a callback for storing changed attributes for models which implement - # `before_save` and `changed_attributes` methods (when `Elasticsearch::Model` is included) + # `before_save` and `attributes_in_database` methods (when `Elasticsearch::Model` is included) # # @see http://api.rubyonrails.org/classes/ActiveModel/Dirty.html # before_save do |i| - changed_attr = i.__elasticsearch__.instance_variable_get(:@__changed_attributes) || {} - i.__elasticsearch__.instance_variable_set(:@__changed_attributes, - changed_attr.merge(Hash[ i.changes.map { |key, value| [key, value.last] } ])) - end if respond_to?(:before_save) && instance_methods.include?(:changed_attributes) + changed_attr = i.__elasticsearch__.instance_variable_get(:@__attributes_in_database) || {} + i.__elasticsearch__.instance_variable_set(:@__attributes_in_database, + changed_attr.merge(Hash[ i.changes_to_save.map { |key, value| [key, value.last] } ])) + end if respond_to?(:before_save) && instance_methods.include?(:attributes_in_database) end end diff --git a/elasticsearch-model/test/unit/indexing_test.rb b/elasticsearch-model/test/unit/indexing_test.rb index b78651340..94f3d967b 100644 --- a/elasticsearch-model/test/unit/indexing_test.rb +++ b/elasticsearch-model/test/unit/indexing_test.rb @@ -171,9 +171,9 @@ def self.before_save(&block) (@callbacks ||= {})[block.hash] = block end - def changed_attributes; [:foo]; end + def attributes_in_database; [:foo]; end - def changes + def changes_to_save {:foo => ['One', 'Two']} end end @@ -186,9 +186,9 @@ def self.before_save(&block) (@callbacks ||= {})[block.hash] = block end - def changed_attributes; [:foo, :bar]; end + def attributes_in_database; [:foo, :bar]; end - def changes + def changes_to_save {:foo => ['A', 'B'], :bar => ['C', 'D']} end @@ -202,10 +202,10 @@ def as_indexed_json(options={}) ::DummyIndexingModelWithCallbacks.__send__ :include, Elasticsearch::Model::Indexing::InstanceMethods end - should "set the @__changed_attributes variable before save" do + should "set the @__attributes_in_database variable before save" do instance = ::DummyIndexingModelWithCallbacks.new instance.expects(:instance_variable_set).with do |name, value| - assert_equal :@__changed_attributes, name + assert_equal :@__attributes_in_database, name assert_equal({foo: 'Two'}, value) true end @@ -297,7 +297,7 @@ def as_indexed_json(options={}) instance = ::DummyIndexingModelWithCallbacks.new # Reset the fake `changes` - instance.instance_variable_set(:@__changed_attributes, nil) + instance.instance_variable_set(:@__attributes_in_database, nil) instance.expects(:index_document) instance.update_document @@ -308,7 +308,7 @@ def as_indexed_json(options={}) instance = ::DummyIndexingModelWithCallbacks.new # Set the fake `changes` hash - instance.instance_variable_set(:@__changed_attributes, {foo: 'bar'}) + instance.instance_variable_set(:@__attributes_in_database, {foo: 'bar'}) client.expects(:update).with do |payload| assert_equal 'foo', payload[:index] @@ -331,7 +331,7 @@ def as_indexed_json(options={}) instance = ::DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new # Set the fake `changes` hash - instance.instance_variable_set(:@__changed_attributes, {'foo' => 'B', 'bar' => 'D' }) + instance.instance_variable_set(:@__attributes_in_database, {'foo' => 'B', 'bar' => 'D' }) client.expects(:update).with do |payload| assert_equal({:foo => 'B'}, payload[:body][:doc]) @@ -350,7 +350,7 @@ def as_indexed_json(options={}) client = mock('client') instance = ::DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new - instance.instance_variable_set(:@__changed_attributes, { 'foo' => { 'bar' => 'BAR'} }) + instance.instance_variable_set(:@__attributes_in_database, { 'foo' => { 'bar' => 'BAR'} }) # Overload as_indexed_json instance.expects(:as_indexed_json).returns({ 'foo' => 'BAR' }) @@ -372,7 +372,7 @@ def as_indexed_json(options={}) instance = ::DummyIndexingModelWithCallbacks.new # Set the fake `changes` hash - instance.instance_variable_set(:@__changed_attributes, {author: 'john'}) + instance.instance_variable_set(:@__attributes_in_database, {author: 'john'}) client.expects(:update).with do |payload| assert_equal 'foo', payload[:index] diff --git a/elasticsearch-model/test/unit/proxy_test.rb b/elasticsearch-model/test/unit/proxy_test.rb index d7299f884..e403b6141 100644 --- a/elasticsearch-model/test/unit/proxy_test.rb +++ b/elasticsearch-model/test/unit/proxy_test.rb @@ -23,9 +23,9 @@ def self.before_save(&block) (@callbacks ||= {})[block.hash] = block end - def changed_attributes; [:foo]; end + def attributes_in_database; [:foo]; end - def changes + def changes_to_save {:foo => ['One', 'Two']} end end @@ -43,10 +43,10 @@ def changes DummyProxyModelWithCallbacks.__send__ :include, Elasticsearch::Model::Proxy end - should "set the @__changed_attributes variable before save" do + should "set the @__attributes_in_database variable before save" do instance = ::DummyProxyModelWithCallbacks.new instance.__elasticsearch__.expects(:instance_variable_set).with do |name, value| - assert_equal :@__changed_attributes, name + assert_equal :@__attributes_in_database, name assert_equal({foo: 'Two'}, value) true end From 7815039c0f78ed0b9b896936875ee4d01855390e Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Mon, 4 Dec 2017 17:41:21 +0100 Subject: [PATCH 355/582] [MODEL] Fixed the handling of changed attributes in `Indexing` to work with older Rails versions This patch builds on work in #738 by @jkeam and #758 by @Geesu to prevent deprecation warnings on Rails 5.1 due to changes in the handling of "changed attributes", originally reported by @davedash in #714. It's primary focus is to restore the compatibility with older Rails versions (so we don't break compatibility without proper version bump and in a single isolated commit), and to make the naming a bit more descriptive. First, the condition has been changed to work with the `changes_to_save` and `changes` methods, as opposed to the original `changed_attributes` / `attributes_in_database` naming. This communicates much more clearly the intent, and the original usage of `changed_attributes` has been misleading. Also, the "internal instance variable" which keeps track of the changes has been renamed to `@__changed_model_attributes`, in order to cleary differentiate it from the `changed_attributes` method name in older ActiveRecord versions. Closes #758 --- .../lib/elasticsearch/model/indexing.rb | 18 ++++--- .../lib/elasticsearch/model/proxy.rb | 16 +++++-- .../test/unit/indexing_test.rb | 48 ++++++++++++++----- elasticsearch-model/test/unit/proxy_test.rb | 6 +-- 4 files changed, 62 insertions(+), 26 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index d58472a9f..39ad06bc3 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -307,17 +307,23 @@ module InstanceMethods def self.included(base) # Register callback for storing changed attributes for models - # which implement `before_save` and `attributes_in_database` methods + # which implement `before_save` and return changed attributes + # (ie. when `Elasticsearch::Model` is included) # # @note This is typically triggered only when the module would be # included in the model directly, not within the proxy. # # @see #update_document # - base.before_save do |instance| - instance.instance_variable_set(:@__attributes_in_database, - Hash[ instance.changes_to_save.map { |key, value| [key, value.last] } ]) - end if base.respond_to?(:before_save) && base.instance_methods.include?(:attributes_in_database) + base.before_save do |i| + if i.class.instance_methods.include?(:changes_to_save) # Rails 5.1 + i.instance_variable_set(:@__changed_model_attributes, + Hash[ i.changes_to_save.map { |key, value| [key, value.last] } ]) + elsif i.class.instance_methods.include?(:changes) + i.instance_variable_set(:@__changed_model_attributes, + Hash[ i.changes.map { |key, value| [key, value.last] } ]) + end + end if base.respond_to?(:before_save) end # Serializes the model instance into JSON (by calling `as_indexed_json`), @@ -391,7 +397,7 @@ def delete_document(options={}) # @see http://rubydoc.info/gems/elasticsearch-api/Elasticsearch/API/Actions:update # def update_document(options={}) - if attributes_in_database = self.instance_variable_get(:@__attributes_in_database) + if attributes_in_database = self.instance_variable_get(:@__changed_model_attributes) attributes = if respond_to?(:as_indexed_json) self.as_indexed_json.select { |k,v| attributes_in_database.keys.map(&:to_s).include? k.to_s } else diff --git a/elasticsearch-model/lib/elasticsearch/model/proxy.rb b/elasticsearch-model/lib/elasticsearch/model/proxy.rb index 606c079a6..a1c89ca95 100644 --- a/elasticsearch-model/lib/elasticsearch/model/proxy.rb +++ b/elasticsearch-model/lib/elasticsearch/model/proxy.rb @@ -54,15 +54,21 @@ def __elasticsearch__ &block end # Register a callback for storing changed attributes for models which implement - # `before_save` and `attributes_in_database` methods (when `Elasticsearch::Model` is included) + # `before_save` method and return changed attributes (ie. when `Elasticsearch::Model` is included) # # @see http://api.rubyonrails.org/classes/ActiveModel/Dirty.html # before_save do |i| - changed_attr = i.__elasticsearch__.instance_variable_get(:@__attributes_in_database) || {} - i.__elasticsearch__.instance_variable_set(:@__attributes_in_database, - changed_attr.merge(Hash[ i.changes_to_save.map { |key, value| [key, value.last] } ])) - end if respond_to?(:before_save) && instance_methods.include?(:attributes_in_database) + if i.class.instance_methods.include?(:changes_to_save) # Rails 5.1 + a = i.__elasticsearch__.instance_variable_get(:@__changed_model_attributes) || {} + i.__elasticsearch__.instance_variable_set(:@__changed_model_attributes, + a.merge(Hash[ i.changes_to_save.map { |key, value| [key, value.last] } ])) + elsif i.class.instance_methods.include?(:changes) + a = i.__elasticsearch__.instance_variable_get(:@__changed_model_attributes) || {} + i.__elasticsearch__.instance_variable_set(:@__changed_model_attributes, + a.merge(Hash[ i.changes.map { |key, value| [key, value.last] } ])) + end + end if respond_to?(:before_save) end end diff --git a/elasticsearch-model/test/unit/indexing_test.rb b/elasticsearch-model/test/unit/indexing_test.rb index 94f3d967b..e3bfad868 100644 --- a/elasticsearch-model/test/unit/indexing_test.rb +++ b/elasticsearch-model/test/unit/indexing_test.rb @@ -171,8 +171,6 @@ def self.before_save(&block) (@callbacks ||= {})[block.hash] = block end - def attributes_in_database; [:foo]; end - def changes_to_save {:foo => ['One', 'Two']} end @@ -186,8 +184,6 @@ def self.before_save(&block) (@callbacks ||= {})[block.hash] = block end - def attributes_in_database; [:foo, :bar]; end - def changes_to_save {:foo => ['A', 'B'], :bar => ['C', 'D']} end @@ -197,15 +193,28 @@ def as_indexed_json(options={}) end end + class ::DummyIndexingModelWithOldDirty + extend Elasticsearch::Model::Indexing::ClassMethods + include Elasticsearch::Model::Indexing::InstanceMethods + + def self.before_save(&block) + (@callbacks ||= {})[block.hash] = block + end + + def changes + {:foo => ['One', 'Two']} + end + end + should "register before_save callback when included" do ::DummyIndexingModelWithCallbacks.expects(:before_save).returns(true) ::DummyIndexingModelWithCallbacks.__send__ :include, Elasticsearch::Model::Indexing::InstanceMethods end - should "set the @__attributes_in_database variable before save" do + should "set the @__changed_model_attributes variable before save" do instance = ::DummyIndexingModelWithCallbacks.new instance.expects(:instance_variable_set).with do |name, value| - assert_equal :@__attributes_in_database, name + assert_equal :@__changed_model_attributes, name assert_equal({foo: 'Two'}, value) true end @@ -217,6 +226,23 @@ def as_indexed_json(options={}) end end + # https://github.com/elastic/elasticsearch-rails/issues/714 + # https://github.com/rails/rails/pull/25337#issuecomment-225166796 + should "set the @__changed_model_attributes variable before save for old ActiveModel::Dirty" do + instance = ::DummyIndexingModelWithOldDirty.new + instance.expects(:instance_variable_set).with do |name, value| + assert_equal :@__changed_model_attributes, name + assert_equal({foo: 'Two'}, value) + true + end + + ::DummyIndexingModelWithOldDirty.__send__ :include, Elasticsearch::Model::Indexing::InstanceMethods + + ::DummyIndexingModelWithOldDirty.instance_variable_get(:@callbacks).each do |n,b| + instance.instance_eval(&b) + end + end + should "have the index_document method" do client = mock('client') instance = ::DummyIndexingModelWithCallbacks.new @@ -297,7 +323,7 @@ def as_indexed_json(options={}) instance = ::DummyIndexingModelWithCallbacks.new # Reset the fake `changes` - instance.instance_variable_set(:@__attributes_in_database, nil) + instance.instance_variable_set(:@__changed_model_attributes, nil) instance.expects(:index_document) instance.update_document @@ -308,7 +334,7 @@ def as_indexed_json(options={}) instance = ::DummyIndexingModelWithCallbacks.new # Set the fake `changes` hash - instance.instance_variable_set(:@__attributes_in_database, {foo: 'bar'}) + instance.instance_variable_set(:@__changed_model_attributes, {foo: 'bar'}) client.expects(:update).with do |payload| assert_equal 'foo', payload[:index] @@ -331,7 +357,7 @@ def as_indexed_json(options={}) instance = ::DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new # Set the fake `changes` hash - instance.instance_variable_set(:@__attributes_in_database, {'foo' => 'B', 'bar' => 'D' }) + instance.instance_variable_set(:@__changed_model_attributes, {'foo' => 'B', 'bar' => 'D' }) client.expects(:update).with do |payload| assert_equal({:foo => 'B'}, payload[:body][:doc]) @@ -350,7 +376,7 @@ def as_indexed_json(options={}) client = mock('client') instance = ::DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new - instance.instance_variable_set(:@__attributes_in_database, { 'foo' => { 'bar' => 'BAR'} }) + instance.instance_variable_set(:@__changed_model_attributes, { 'foo' => { 'bar' => 'BAR'} }) # Overload as_indexed_json instance.expects(:as_indexed_json).returns({ 'foo' => 'BAR' }) @@ -372,7 +398,7 @@ def as_indexed_json(options={}) instance = ::DummyIndexingModelWithCallbacks.new # Set the fake `changes` hash - instance.instance_variable_set(:@__attributes_in_database, {author: 'john'}) + instance.instance_variable_set(:@__changed_model_attributes, {author: 'john'}) client.expects(:update).with do |payload| assert_equal 'foo', payload[:index] diff --git a/elasticsearch-model/test/unit/proxy_test.rb b/elasticsearch-model/test/unit/proxy_test.rb index e403b6141..a64b5b175 100644 --- a/elasticsearch-model/test/unit/proxy_test.rb +++ b/elasticsearch-model/test/unit/proxy_test.rb @@ -23,8 +23,6 @@ def self.before_save(&block) (@callbacks ||= {})[block.hash] = block end - def attributes_in_database; [:foo]; end - def changes_to_save {:foo => ['One', 'Two']} end @@ -43,10 +41,10 @@ def changes_to_save DummyProxyModelWithCallbacks.__send__ :include, Elasticsearch::Model::Proxy end - should "set the @__attributes_in_database variable before save" do + should "set the @__changed_model_attributes variable before save" do instance = ::DummyProxyModelWithCallbacks.new instance.__elasticsearch__.expects(:instance_variable_set).with do |name, value| - assert_equal :@__attributes_in_database, name + assert_equal :@__changed_model_attributes, name assert_equal({foo: 'Two'}, value) true end From e1fc0e6df0b85d2eb65df1dec1459fc1da980dc8 Mon Sep 17 00:00:00 2001 From: Karel Minarik <karel.minarik@elasticsearch.com> Date: Fri, 15 Jun 2018 08:15:32 +0200 Subject: [PATCH 356/582] [RAILS] Updated the templates for example Rails applications * Removed the code for Elasticsearch installation and added instructions for running Elasticsearch via Docker * Updated the Bootstrap CSS * Various fixes for application visual style * Various fixes in tests (Mocha stubbing, proper requires, etc) * Fixed the Elasticsearch error due to empty `post_filter` --- .../lib/rails/templates/01-basic.rb | 113 +++++++----------- .../lib/rails/templates/02-pretty.rb | 50 +++++--- .../lib/rails/templates/03-expert.rb | 14 ++- .../lib/rails/templates/index.html.dsl.erb | 4 +- .../lib/rails/templates/index.html.erb | 4 +- .../lib/rails/templates/search.css | 4 + .../templates/search_controller_test.dsl.rb | 2 +- .../rails/templates/search_controller_test.rb | 2 +- .../lib/rails/templates/searchable.rb | 2 +- 9 files changed, 105 insertions(+), 90 deletions(-) diff --git a/elasticsearch-rails/lib/rails/templates/01-basic.rb b/elasticsearch-rails/lib/rails/templates/01-basic.rb index 1019a2d30..e9c378f84 100644 --- a/elasticsearch-rails/lib/rails/templates/01-basic.rb +++ b/elasticsearch-rails/lib/rails/templates/01-basic.rb @@ -11,7 +11,6 @@ # * Git # * Ruby >= 1.9.3 # * Rails >= 5 -# * Java >= 8 (for Elasticsearch) # # Usage: # ------ @@ -22,74 +21,54 @@ require 'uri' require 'net/http' - -at_exit do - pid = File.read("#{destination_root}/tmp/pids/elasticsearch.pid") rescue nil - if pid - say_status "Stop", "Elasticsearch", :yellow - run "kill #{pid}" - end -end +require 'json' $elasticsearch_url = ENV.fetch('ELASTICSEARCH_URL', 'http://localhost:9200') -# ----- Check & download Elasticsearch ------------------------------------------------------------ +# ----- Check for Elasticsearch ------------------------------------------------------------------- -cluster_info = Net::HTTP.get(URI.parse($elasticsearch_url)) rescue nil -cluster_info = JSON.parse(cluster_info) if cluster_info +required_elasticsearch_version = '6' -if cluster_info.nil? || cluster_info['version']['number'] < '5' - # Change the port when incompatible Elasticsearch version is running on localhost:9200 - if $elasticsearch_url == 'http://localhost:9200' && cluster_info && cluster_info['version']['number'] < '5' - $change_port = '9280' - $elasticsearch_url = "http://localhost:#{$change_port}" - end +docker_command =<<-CMD.gsub(/\s{1,}/, ' ').strip + docker run \ + --name elasticsearch-rails-searchapp \ + --publish 9200:9200 \ + --env "discovery.type=single-node" \ + --env "cluster.name=elasticsearch-rails" \ + --env "cluster.routing.allocation.disk.threshold_enabled=false" \ + --rm \ + docker.elastic.co/elasticsearch/elasticsearch-oss:6.3.0 +CMD - COMMAND = <<-COMMAND.gsub(/^ /, '') - curl -# -O "https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.2.1.tar.gz" - tar -zxf elasticsearch-5.2.1.tar.gz - rm -f elasticsearch-5.2.1.tar.gz - ./elasticsearch-5.2.1/bin/elasticsearch -d -p #{destination_root}/tmp/pids/elasticsearch.pid #{$change_port.nil? ? '' : "-E http.port=#{$change_port}" } - COMMAND - - puts "\n" - say_status "ERROR", "Elasticsearch not running!\n", :red - puts '-'*80 - say_status '', "It appears that Elasticsearch 5 is not running on this machine." - say_status '', "Is it installed? Do you want me to install and run it for you with this command?\n\n" - COMMAND.each_line { |l| say_status '', "$ #{l}" } - puts - say_status '', "(To uninstall, just remove the generated application directory.)" - puts '-'*80, '' - - if yes?("Install Elasticsearch?", :bold) - puts - say_status "Install", "Elasticsearch", :yellow - - java_info = `java -version 2>&1` - - unless java_info.match /1\.[8-9]/ - puts - say_status "ERROR", "Required Java version (1.8) not found, exiting...", :red - exit(1) - end - - commands = COMMAND.split("\n") - exec = commands.pop - inside("vendor") do - commands.each { |command| run command } - run "(#{exec})" # Launch Elasticsearch in subshell - end - - # Wait for Elasticsearch to be up... - # - system <<-COMMAND - until $(curl --silent --head --fail #{$elasticsearch_url} > /dev/null 2>&1); do - printf '.'; sleep 1 - done - COMMAND - end -end unless ENV['RAILS_NO_ES_INSTALL'] +begin + cluster_info = Net::HTTP.get(URI.parse($elasticsearch_url)) +rescue Errno::ECONNREFUSED => e + say_status "ERROR", "Cannot connect to Elasticsearch on <#{$elasticsearch_url}>\n\n", :red + say_status "", "The application requires an Elasticsearch cluster running, " + + "but no cluster has been found on <#{$elasticsearch_url}>." + say_status "", "The easiest way of launching Elasticsearch is by running it with Docker (https://www.docker.com/get-docker):\n\n" + say_status "", docker_command + "\n" + exit(1) +rescue StandardError => e + say_status "ERROR", "#{e.class}: #{e.message}", :red + exit(1) +end + +cluster_info = JSON.parse(cluster_info) + +unless cluster_info['version'] + say_status "ERROR", "Cannot determine Elasticsearch version from <#{$elasticsearch_url}>", :red + say_status "", JSON.dump(cluster_info), :red + exit(1) +end + +if cluster_info['version']['number'] < required_elasticsearch_version + say_status "ERROR", + "The application requires Elasticsearch version #{required_elasticsearch_version} or higher, found version #{cluster_info['version']['number']}.\n\n", :red + say_status "", "The easiest way of launching Elasticsearch is by running it with Docker (https://www.docker.com/get-docker):\n\n" + say_status "", docker_command + "\n" + exit(1) +end # ----- Application skeleton ---------------------------------------------------------------------- @@ -144,7 +123,7 @@ # ----- Auxiliary gems ---------------------------------------------------------------------------- -gem 'mocha', group: 'test', require: 'mocha/api' +gem 'mocha', group: 'test' gem 'rails-controller-testing', group: 'test' # ----- Remove CoffeeScript, Sass and "all that jazz" --------------------------------------------- @@ -160,9 +139,9 @@ say_status "Rubygems", "Adding Elasticsearch libraries into Gemfile...\n", :yellow puts '-'*80, ''; sleep 0.75 -gem 'elasticsearch', git: 'git://github.com/elasticsearch/elasticsearch-ruby.git' -gem 'elasticsearch-model', git: 'git://github.com/elasticsearch/elasticsearch-rails.git' -gem 'elasticsearch-rails', git: 'git://github.com/elasticsearch/elasticsearch-rails.git' +gem 'elasticsearch', git: 'https://github.com/elasticsearch/elasticsearch-ruby.git' +gem 'elasticsearch-model', git: 'https://github.com/elasticsearch/elasticsearch-rails.git' +gem 'elasticsearch-rails', git: 'https://github.com/elasticsearch/elasticsearch-rails.git' git add: "Gemfile*" diff --git a/elasticsearch-rails/lib/rails/templates/02-pretty.rb b/elasticsearch-rails/lib/rails/templates/02-pretty.rb index 8902d756f..2c3318304 100644 --- a/elasticsearch-rails/lib/rails/templates/02-pretty.rb +++ b/elasticsearch-rails/lib/rails/templates/02-pretty.rb @@ -109,8 +109,13 @@ def self.search(query) end CODE +insert_into_file "test/test_helper.rb", + "require 'mocha/minitest'\n\n", + before: "class ActiveSupport::TestCase\n" + git add: "app/models/article.rb" git add: "test/**/article_test.rb" +git add: "test/test_helper.rb" git commit: "-m 'Added an `Article.search` method'" # ----- Add loading Bootstrap assets -------------------------------------------------------------- @@ -126,8 +131,9 @@ def self.search(query) CODE insert_into_file 'app/views/layouts/application.html.erb', <<-CODE, before: '</head>' - <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css"> - <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.2/js/bootstrap.min.js"></script> + <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> + <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script> + <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> CODE git commit: "-a -m 'Added loading Bootstrap assets in the application layout'" @@ -142,12 +148,6 @@ def self.search(query) <<-CODE <div class="input-group"> <%= text_field_tag :q, params[:q], class: 'form-control', placeholder: 'Search...' %> - - <span class="input-group-btn"> - <button type="button" class="btn btn-default"> - <span class="glyphicon glyphicon-search"></span> - </button> - </span> </div> CODE end @@ -166,7 +166,7 @@ def self.search(query) gsub_file 'app/views/articles/index.html.erb', %r{<td><%= link_to [^%]+} do |match| match.gsub!('<td>', '<td style="width: 50px">') - match.include?("btn") ? match : (match + ", class: 'btn btn-default btn-xs'") + match.include?("btn") ? match : (match + ", class: 'btn btn-outline-primary btn-sm'") end gsub_file 'app/views/articles/index.html.erb', %r{<br>\s*(<\%= link_to 'New Article'.*)}m do |content| @@ -190,6 +190,12 @@ def self.search(query) "\n " + match + ", class: 'btn btn-primary btn-xs', style: 'color: #fff'" end +# ----- Customize the form ----------------------------------------------------------------- + +gsub_file 'app/views/articles/_form.html.erb', %r{<div class="field">} do |match| + %Q|<div class="form-group">| +end + git add: "app/views" git commit: "-m 'Refactored the articles listing to use Bootstrap components'" @@ -229,9 +235,9 @@ def self.search(query) CODE end -generate "kaminari:views", "bootstrap2", "--force" +generate "kaminari:views", "bootstrap3", "--force" -gsub_file 'app/views/kaminari/_paginator.html.erb', %r{<ul>}, '<ul class="pagination">' +gsub_file 'app/views/kaminari/_paginator.html.erb', %r{<nav>}, '<nav class="pagination">' git add: "." git commit: "-m 'Added pagination to articles listing'" @@ -246,21 +252,35 @@ def self.search(query) unless File.read('app/assets/stylesheets/application.css').include?('.label-highlight') <<-CODE + body * { + font-size: 100% !important; + } + +.table { + border-bottom: 1px solid #dee2e6; +} + +.table td { + vertical-align: middle !important; +} + .label-highlight { font-size: 100% !important; font-weight: inherit !important; font-style: inherit !important; color: #333 !important; background: #fff401 !important; + padding: 0.25em 0.5em + border-radius: 5px; } -div.pagination { +nav.pagination { text-align: center; - display: block; + display: inline-block; } -div.pagination ul { - display: inline-block; +ul.pagination { + margin-bottom: 0; } CODE diff --git a/elasticsearch-rails/lib/rails/templates/03-expert.rb b/elasticsearch-rails/lib/rails/templates/03-expert.rb index 7ffbd5553..d892b4c85 100644 --- a/elasticsearch-rails/lib/rails/templates/03-expert.rb +++ b/elasticsearch-rails/lib/rails/templates/03-expert.rb @@ -183,7 +183,7 @@ class Article < ActiveRecord::Base # ----- Add Sidekiq indexer ----------------------------------------------------------------------- puts -say_status "Application", "Adding Sidekiq worker for updating the index...\n", :yellow +say_status "Sidekiq", "Adding Sidekiq worker for updating the index...\n", :yellow puts '-'*80, ''; sleep 0.25 gem "sidekiq" @@ -241,6 +241,18 @@ def index git add: "app/views/search/ app/assets/stylesheets/search.css" git commit: "-m 'Added SearchController#index'" +# ----- Add SearchController ----------------------------------------------------------------------- + +puts +say_status "Views", "Updating application layout...\n", :yellow +puts '-'*80, ''; sleep 0.25 + +insert_into_file 'app/views/layouts/application.html.erb', <<-CODE, before: '</head>' + <link href="https://fonts.googleapis.com/css?family=Rokkitt:400,700" rel="stylesheet"> +CODE + +git commit: "-a -m 'Updated application template'" + # ----- Add initializer --------------------------------------------------------------------------- puts diff --git a/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb b/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb index 64cfb0916..97a9b8f81 100644 --- a/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb +++ b/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb @@ -38,7 +38,7 @@ </button> <ul class="dropdown-menu" role="menu"> <li><%= link_to "Sort by published on", search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(s: 'published_on')), class: 'btn-xs' %></li> - <li><%= link_to "Sort by relevancy", search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(s: nil)), class: 'btn-xs' %></li> + <li><%= link_to "Sort by relevancy", search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(s: nil)), class: 'btn-xs' unless params[:q].blank? %></li> </ul> </div> </div> @@ -122,7 +122,7 @@ <div class="result"> <h3 class="title"> <%= (article.try(:highlight).try(:title) ? article.highlight.title.join.html_safe : article.title) %> - <small class="category"><%= article.categories.to_sentence %></small> + <small class="category"> | <%= article.categories.to_sentence %></small> </h3> <p class="body"> diff --git a/elasticsearch-rails/lib/rails/templates/index.html.erb b/elasticsearch-rails/lib/rails/templates/index.html.erb index 0d0f1cc68..656c1f891 100644 --- a/elasticsearch-rails/lib/rails/templates/index.html.erb +++ b/elasticsearch-rails/lib/rails/templates/index.html.erb @@ -38,7 +38,7 @@ </button> <ul class="dropdown-menu" role="menu"> <li><%= link_to "Sort by published on", search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(s: 'published_on')), class: 'btn-xs' %></li> - <li><%= link_to "Sort by relevancy", search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(s: nil)), class: 'btn-xs' %></li> + <li><%= link_to "Sort by relevancy", search_path(params.permit(:q, :a, :c, :s, :w, :comments).merge(s: nil)), class: 'btn-xs' unless params[:q].blank? %></li> </ul> </div> </div> @@ -122,7 +122,7 @@ <div class="result"> <h3 class="title"> <%= (article.try(:highlight).try(:title) ? article.highlight.title.join.html_safe : article.title) %> - <small class="category"><%= article.categories.to_sentence %></small> + <small class="category"> | <%= article.categories.to_sentence %></small> </h3> <p class="body"> diff --git a/elasticsearch-rails/lib/rails/templates/search.css b/elasticsearch-rails/lib/rails/templates/search.css index a04b9a6a2..dd2211d49 100644 --- a/elasticsearch-rails/lib/rails/templates/search.css +++ b/elasticsearch-rails/lib/rails/templates/search.css @@ -54,9 +54,13 @@ form #form-options input { } .result h3.title { + font-size: 180% !important; font-family: 'Rokkitt', sans-serif; margin-top: 0; } +.result h3.title small { + font-size: 75% !important; +} .result .body { font-family: Georgia, serif; diff --git a/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb b/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb index fe2da30d5..d4c9cac05 100644 --- a/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb +++ b/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb @@ -3,7 +3,7 @@ class SearchControllerTest < ActionController::TestCase setup do - Time.stubs(:now).returns(Time.new(2015, 03, 16, 10, 00, 00, 0)) + travel_to Time.new(2015, 03, 16, 10, 00, 00, 0) Article.delete_all diff --git a/elasticsearch-rails/lib/rails/templates/search_controller_test.rb b/elasticsearch-rails/lib/rails/templates/search_controller_test.rb index efdc1c2ca..ca5ca29ba 100644 --- a/elasticsearch-rails/lib/rails/templates/search_controller_test.rb +++ b/elasticsearch-rails/lib/rails/templates/search_controller_test.rb @@ -2,7 +2,7 @@ class SearchControllerTest < ActionController::TestCase setup do - Time.stubs(:now).returns(Time.new(2015, 03, 16, 10, 00, 00, 0)) + travel_to Time.new(2015, 03, 16, 10, 00, 00, 0) Article.delete_all diff --git a/elasticsearch-rails/lib/rails/templates/searchable.rb b/elasticsearch-rails/lib/rails/templates/searchable.rb index e03dad123..45fbf75d4 100644 --- a/elasticsearch-rails/lib/rails/templates/searchable.rb +++ b/elasticsearch-rails/lib/rails/templates/searchable.rb @@ -95,7 +95,7 @@ def self.search(query, options={}) } }, - post_filter: {}, + post_filter: { bool: { must: [ match_all: {} ] } }, aggregations: { categories: { From 8823f98efce7d419a69418eee2027c96c167fe20 Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Fri, 22 Jun 2018 16:33:33 +0200 Subject: [PATCH 357/582] [CI] Update travis.yml download url and matrix (#801) * [CI] Update url to download elasticsearch in travis yml * [CI] Include ruby 2.5.1 in travis matrix * [CI] Update test to account for #to_ary called on objects recursively via #flatten in ruby < 2.3 * [CI] Further updates to travis matrix for latest versions in each series * [CI] Remove extra line for debugging --- .travis.yml | 12 ++++++++---- elasticsearch-model/test/unit/multimodel_test.rb | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 862ec50f3..fed580991 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,24 +20,28 @@ branches: matrix: include: - - rvm: 2.2.6 + - rvm: 2.2.10 jdk: oraclejdk8 env: TEST_SUITE=unit - - rvm: 2.3.3 + - rvm: 2.3.7 + jdk: oraclejdk8 + env: TEST_SUITE=unit + + - rvm: 2.5.1 jdk: oraclejdk8 env: TEST_SUITE=unit - rvm: 2.3.3 jdk: oraclejdk8 - env: TEST_SUITE=integration QUIET=y SERVER=start TEST_CLUSTER_LOGS=/tmp/log TEST_CLUSTER_COMMAND=/tmp/elasticsearch-6.0.0-alpha1-SNAPSHOT/bin/elasticsearch + env: TEST_SUITE=integration QUIET=y SERVER=start TEST_CLUSTER_LOGS=/tmp/log TEST_CLUSTER_COMMAND=/tmp/elasticsearch-6.3.0/bin/elasticsearch before_install: - gem update --system --no-rdoc --no-ri - gem --version - gem install bundler -v 1.14.3 --no-rdoc --no-ri - bundle version - - curl -sS https://snapshots.elastic.co/downloads/elasticsearch/elasticsearch-6.0.0-alpha1-SNAPSHOT.tar.gz | tar xz -C /tmp + - curl -sS https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.0.tar.gz | tar xz -C /tmp install: - bundle install diff --git a/elasticsearch-model/test/unit/multimodel_test.rb b/elasticsearch-model/test/unit/multimodel_test.rb index 89e88f7a1..daf9f4043 100644 --- a/elasticsearch-model/test/unit/multimodel_test.rb +++ b/elasticsearch-model/test/unit/multimodel_test.rb @@ -4,8 +4,8 @@ class Elasticsearch::Model::MultimodelTest < Test::Unit::TestCase context "Multimodel class" do setup do - title = stub('Foo', index_name: 'foo_index', document_type: 'foo') - series = stub('Bar', index_name: 'bar_index', document_type: 'bar') + title = stub('Foo', index_name: 'foo_index', document_type: 'foo', to_ary: nil) + series = stub('Bar', index_name: 'bar_index', document_type: 'bar', to_ary: nil) @multimodel = Elasticsearch::Model::Multimodel.new(title, series) end From 5439ddb614c3aa6e30772b48a90b23d6c3a29734 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Wed, 27 Jun 2018 12:32:59 +0200 Subject: [PATCH 358/582] Update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6076dc96c..43ddc2e30 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store *.log tmp/ +.idea/* .yardoc/ _yardoc/ From dd2eaa3d084b671357b1fd521f49dffae50a39bd Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emily.s@elastic.co> Date: Wed, 27 Jun 2018 14:30:54 +0200 Subject: [PATCH 359/582] Master is 7.0.0 --- elasticsearch-model/lib/elasticsearch/model/version.rb | 2 +- .../lib/elasticsearch/persistence/version.rb | 2 +- elasticsearch-rails/lib/elasticsearch/rails/version.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/version.rb b/elasticsearch-model/lib/elasticsearch/model/version.rb index cfa7e4ab6..3b98efb5c 100644 --- a/elasticsearch-model/lib/elasticsearch/model/version.rb +++ b/elasticsearch-model/lib/elasticsearch/model/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Model - VERSION = "6.0.0.alpha1" + VERSION = "7.0.0" end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb index d54a3a37f..c27d0fb54 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Persistence - VERSION = "6.0.0.alpha1" + VERSION = "7.0.0" end end diff --git a/elasticsearch-rails/lib/elasticsearch/rails/version.rb b/elasticsearch-rails/lib/elasticsearch/rails/version.rb index 4b65f5196..eaf50e581 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/version.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Rails - VERSION = "6.0.0.alpha1" + VERSION = "7.0.0" end end From 270ff8c87e91d0cfb4c21a700ea2bf0d8671f4a5 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emily.s@elastic.co> Date: Wed, 27 Jun 2018 14:32:06 +0200 Subject: [PATCH 360/582] [CI] Test 6.x branch in travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index fed580991..cf39473ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ branches: - master - travis - 5.x + - 6.x - 2.x matrix: From d8917d1b281eca2dbfb35455a176672cd0adb06d Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Wed, 27 Jun 2018 15:54:04 +0200 Subject: [PATCH 361/582] [CI] Use docker to launch Elasticsearch in rake task (#803) --- .travis.yml | 9 ++++----- Rakefile | 25 ++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index cf39473ad..99a2571de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,16 +33,15 @@ matrix: jdk: oraclejdk8 env: TEST_SUITE=unit - - rvm: 2.3.3 + - rvm: 2.5.1 jdk: oraclejdk8 - env: TEST_SUITE=integration QUIET=y SERVER=start TEST_CLUSTER_LOGS=/tmp/log TEST_CLUSTER_COMMAND=/tmp/elasticsearch-6.3.0/bin/elasticsearch + env: TEST_SUITE=integration QUIET=y before_install: - - gem update --system --no-rdoc --no-ri + - gem update --system -q + - gem update bundler -q - gem --version - - gem install bundler -v 1.14.3 --no-rdoc --no-ri - bundle version - - curl -sS https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.0.tar.gz | tar xz -C /tmp install: - bundle install diff --git a/Rakefile b/Rakefile index b4c90d710..76479929d 100644 --- a/Rakefile +++ b/Rakefile @@ -59,8 +59,31 @@ namespace :test do end end + desc "Run Elasticsearch (Docker)" + task :setup_elasticsearch do + begin + sh <<-COMMAND.gsub(/^\s*/, '').gsub(/\s{1,}/, ' ') + docker stop $(docker ps -aq); + docker rm $(docker ps -aq); + docker rmi $(docker images -q); + docker run -d=true \ + --env "discovery.type=single-node" \ + --env "cluster.name=elasticsearch-rails" \ + --env "http.port=9200" \ + --env "cluster.routing.allocation.disk.threshold_enabled=false" \ + --publish 9250:9200 \ + --rm \ + docker.elastic.co/elasticsearch/elasticsearch:6.3.0 + COMMAND + require 'elasticsearch/extensions/test/cluster' + Elasticsearch::Extensions::Test::Cluster::Cluster.new(version: '6.3.0', + number_of_nodes: 1).wait_for_green + rescue + end + end + desc "Run integration tests in all subprojects" - task :integration do + task :integration => :setup_elasticsearch do # 1/ elasticsearch-model # puts '-'*80 From c5f55155e908d22546a0ad0e05ee53afd39b24de Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emily.s@elastic.co> Date: Thu, 28 Jun 2018 10:20:01 +0200 Subject: [PATCH 362/582] [CI] Don't delete all images and containers in docker rake task --- Rakefile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Rakefile b/Rakefile index 76479929d..c953c5c48 100644 --- a/Rakefile +++ b/Rakefile @@ -63,9 +63,6 @@ namespace :test do task :setup_elasticsearch do begin sh <<-COMMAND.gsub(/^\s*/, '').gsub(/\s{1,}/, ' ') - docker stop $(docker ps -aq); - docker rm $(docker ps -aq); - docker rmi $(docker images -q); docker run -d=true \ --env "discovery.type=single-node" \ --env "cluster.name=elasticsearch-rails" \ From e873a2e432ab7b83dbca94c80f58b30b0de9c2fb Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Thu, 28 Jun 2018 15:45:46 +0200 Subject: [PATCH 363/582] [MODEL] Update child-parent integration test to use single index type for ES 6.3 (#805) * [MODEL] Update child-parent integration test to use single index type for ES 6.3 * [CI] Expand unit test matrix and only test integration on latest stable Rails, latest stable Ruby * [CI] Don't test JRuby in Travis until newer version of elasticsearch-extensions gem is released without oj/patron dependencies --- .travis.yml | 16 ++- elasticsearch-model/Rakefile | 6 +- .../elasticsearch-model.gemspec | 2 +- ...e_record_associations_parent_child_test.rb | 109 ++++++++++++------ .../elasticsearch-persistence.gemspec | 2 +- .../elasticsearch-rails.gemspec | 2 +- 6 files changed, 93 insertions(+), 44 deletions(-) diff --git a/.travis.yml b/.travis.yml index 99a2571de..51766c1e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,19 +21,27 @@ branches: matrix: include: - - rvm: 2.2.10 + - rvm: 2.2 jdk: oraclejdk8 env: TEST_SUITE=unit - - rvm: 2.3.7 + - rvm: 2.3 jdk: oraclejdk8 env: TEST_SUITE=unit - - rvm: 2.5.1 + - rvm: 2.4 jdk: oraclejdk8 env: TEST_SUITE=unit - - rvm: 2.5.1 + - rvm: 2.5 + jdk: oraclejdk8 + env: TEST_SUITE=unit + +# - rvm: jruby-9.1 +# jdk: oraclejdk8 +# env: TEST_SUITE=unit + + - rvm: 2.5 jdk: oraclejdk8 env: TEST_SUITE=integration QUIET=y diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index 1efad46da..f80c46b23 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -38,10 +38,10 @@ namespace :test do sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/5.0.gemfile', __FILE__)}' bundle exec rake test:run_unit" end - desc "Run integration tests against ActiveModel 3, 4 and 5" + desc "Run integration tests against latest stable ActiveModel (5)" task :integration do - sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/3.0.gemfile', __FILE__)}' bundle exec rake test:run_integration" - sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/4.0.gemfile', __FILE__)}' bundle exec rake test:run_integration" + #sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/3.0.gemfile', __FILE__)}' bundle exec rake test:run_integration" + #sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/4.0.gemfile', __FILE__)}' bundle exec rake test:run_integration" sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/5.0.gemfile', __FILE__)}' bundle exec rake test:run_integration" end diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index e058ac43d..5cd1bcec1 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -35,7 +35,7 @@ Gem::Specification.new do |s| s.add_development_dependency "sqlite3" s.add_development_dependency "activemodel", "> 3" - s.add_development_dependency "oj" + s.add_development_dependency "oj" unless defined?(JRUBY_VERSION) s.add_development_dependency "kaminari" s.add_development_dependency "will_paginate" diff --git a/elasticsearch-model/test/integration/active_record_associations_parent_child_test.rb b/elasticsearch-model/test/integration/active_record_associations_parent_child_test.rb index de5db34b0..fb3b07e1d 100644 --- a/elasticsearch-model/test/integration/active_record_associations_parent_child_test.rb +++ b/elasticsearch-model/test/integration/active_record_associations_parent_child_test.rb @@ -11,7 +11,11 @@ class Question < ActiveRecord::Base has_many :answers, dependent: :destroy - index_name 'questions_and_answers' + JOIN_TYPE = 'question'.freeze + JOIN_METADATA = { join_field: JOIN_TYPE}.freeze + + index_name 'questions_and_answers'.freeze + document_type 'doc'.freeze mapping do indexes :title @@ -19,6 +23,12 @@ class Question < ActiveRecord::Base indexes :author end + def as_indexed_json(options={}) + # This line is necessary for differences between ActiveModel::Serializers::JSON#as_json versions + json = as_json(options)[JOIN_TYPE] || as_json(options) + json.merge(JOIN_METADATA) + end + after_commit lambda { __elasticsearch__.index_document }, on: :create after_commit lambda { __elasticsearch__.update_document }, on: :update after_commit lambda { __elasticsearch__.delete_document }, on: :destroy @@ -29,32 +39,55 @@ class Answer < ActiveRecord::Base belongs_to :question - index_name 'questions_and_answers' + JOIN_TYPE = 'answer'.freeze + + index_name 'questions_and_answers'.freeze + document_type 'doc'.freeze + + before_create :randomize_id + + def randomize_id + begin + self.id = SecureRandom.random_number(1_000_000) + end while Answer.where(id: self.id).exists? + end - mapping _parent: { type: 'question' }, _routing: { required: true } do + mapping do indexes :text indexes :author end - after_commit lambda { __elasticsearch__.index_document(parent: question_id) }, on: :create - after_commit lambda { __elasticsearch__.update_document(parent: question_id) }, on: :update - after_commit lambda { __elasticsearch__.delete_document(parent: question_id) }, on: :destroy + def as_indexed_json(options={}) + # This line is necessary for differences between ActiveModel::Serializers::JSON#as_json versions + json = as_json(options)[JOIN_TYPE] || as_json(options) + json.merge(join_field: { name: JOIN_TYPE, parent: question_id }) + end + + after_commit lambda { __elasticsearch__.index_document(routing: (question_id || 1)) }, on: :create + after_commit lambda { __elasticsearch__.update_document(routing: (question_id || 1)) }, on: :update + after_commit lambda {__elasticsearch__.delete_document(routing: (question_id || 1)) }, on: :destroy end module ParentChildSearchable - INDEX_NAME = 'questions_and_answers' + INDEX_NAME = 'questions_and_answers'.freeze + JOIN = 'join'.freeze def create_index!(options={}) client = Question.__elasticsearch__.client client.indices.delete index: INDEX_NAME rescue nil if options[:force] settings = Question.settings.to_hash.merge Answer.settings.to_hash - mappings = Question.mappings.to_hash.merge Answer.mappings.to_hash + mapping_properties = { join_field: { type: JOIN, + relations: { Question::JOIN_TYPE => Answer::JOIN_TYPE } } } + + merged_properties = mapping_properties.merge(Question.mappings.to_hash[:doc][:properties]).merge( + Answer.mappings.to_hash[:doc][:properties]) + mappings = { doc: { properties: merged_properties }} client.indices.create index: INDEX_NAME, body: { - settings: settings.to_hash, - mappings: mappings.to_hash } + settings: settings.to_hash, + mappings: mappings } end extend self @@ -100,34 +133,34 @@ class ActiveRecordAssociationsParentChildIntegrationTest < Elasticsearch::Test:: should "find questions by matching answers" do response = Question.search( - { query: { - has_child: { - type: 'answer', - query: { - match: { - author: 'john' - } - } - } - } - }) + { query: { + has_child: { + type: 'answer', + query: { + match: { + author: 'john' + } + } + } + } + }) assert_equal 'Second Question', response.records.first.title end should "find answers for matching questions" do response = Answer.search( - { query: { - has_parent: { - parent_type: 'question', - query: { - match: { - author: 'john' - } - } - } - } - }) + { query: { + has_parent: { + parent_type: 'question', + query: { + match: { + author: 'john' + } + } + } + } + }) assert_same_elements ['Adam', 'Ryan'], response.records.map(&:author) end @@ -136,12 +169,20 @@ class ActiveRecordAssociationsParentChildIntegrationTest < Elasticsearch::Test:: Question.where(title: 'First Question').each(&:destroy) Question.__elasticsearch__.refresh_index! - response = Answer.search query: { match_all: {} } + response = Answer.search( + { query: { + has_parent: { + parent_type: 'question', + query: { + match_all: {} + } + } + } + }) assert_equal 1, response.results.total end end - end end end diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index 311325fdc..38ba93594 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -33,7 +33,7 @@ Gem::Specification.new do |s| s.add_development_dependency "bundler", "~> 1.5" s.add_development_dependency "rake", "~> 11.1" - s.add_development_dependency "oj" + s.add_development_dependency "oj" unless defined?(JRUBY_VERSION) s.add_development_dependency "rails", '> 4' diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index 71b4c1abb..aa976c675 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -29,7 +29,7 @@ Gem::Specification.new do |s| s.add_development_dependency "elasticsearch-extensions" s.add_development_dependency "elasticsearch-model" - s.add_development_dependency "oj" + s.add_development_dependency "oj" unless defined?(JRUBY_VERSION) s.add_development_dependency "rails", ">= 3.1" s.add_development_dependency "lograge" From e9d74b885845235b133d7f2c586b9cca7e3e9547 Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Fri, 29 Jun 2018 11:37:30 +0200 Subject: [PATCH 364/582] [CI] Update references to MongoDB Ruby driver (#807) --- .../test/integration/mongoid_basic_test.rb | 7 +++---- .../test/integration/multiple_models_test.rb | 6 +++--- elasticsearch-model/test/test_helper.rb | 9 ++++----- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/elasticsearch-model/test/integration/mongoid_basic_test.rb b/elasticsearch-model/test/integration/mongoid_basic_test.rb index d46a75d05..c8b44dda0 100644 --- a/elasticsearch-model/test/integration/mongoid_basic_test.rb +++ b/elasticsearch-model/test/integration/mongoid_basic_test.rb @@ -1,9 +1,8 @@ require 'test_helper' +MongoDB.setup! -Mongo.setup! - -if Mongo.available? - Mongo.connect_to 'mongoid_articles' +if MongoDB.available? + MongoDB.connect_to 'mongoid_articles' module Elasticsearch module Model diff --git a/elasticsearch-model/test/integration/multiple_models_test.rb b/elasticsearch-model/test/integration/multiple_models_test.rb index 7d3a62705..02022b60f 100644 --- a/elasticsearch-model/test/integration/multiple_models_test.rb +++ b/elasticsearch-model/test/integration/multiple_models_test.rb @@ -6,7 +6,7 @@ ::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' -Mongo.setup! +MongoDB.setup! module Elasticsearch module Model @@ -115,8 +115,8 @@ class ::Series < ActiveRecord::Base assert_equal 0, response.page(3).per(3).results.size end - if Mongo.available? - Mongo.connect_to 'mongoid_collections' + if MongoDB.available? + MongoDB.connect_to 'mongoid_collections' context "Across mongoid models" do setup do diff --git a/elasticsearch-model/test/test_helper.rb b/elasticsearch-model/test/test_helper.rb index ff3a6d935..7471e475d 100644 --- a/elasticsearch-model/test/test_helper.rb +++ b/elasticsearch-model/test/test_helper.rb @@ -62,14 +62,13 @@ def setup end end -class Mongo +class MongoDB def self.setup! begin require 'mongoid' - session = Moped::Connection.new("localhost", 27017, 0.5) - session.connect + Mongo::Client.new(["localhost:27017"]) ENV['MONGODB_AVAILABLE'] = 'yes' - rescue LoadError, Moped::Errors::ConnectionFailure => e + rescue LoadError, Mongo::Error => e $stderr.puts "MongoDB not installed or running: #{e}" end end @@ -86,7 +85,7 @@ def self.connect_to(source) logger.level = ::Logger::DEBUG Mongoid.logger = logger unless ENV['QUIET'] - Moped.logger = logger unless ENV['QUIET'] + Mongo::Logger.logger = logger unless ENV['QUIET'] Mongoid.connect_to source end From 319d0b7da4812d6c83477d0ae532dea36a78621f Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emily.s@elastic.co> Date: Wed, 4 Jul 2018 15:09:02 +0200 Subject: [PATCH 365/582] Add yard as development dependency in Gemfile --- Gemfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Gemfile b/Gemfile index 4a7fe0473..a52a3c58c 100644 --- a/Gemfile +++ b/Gemfile @@ -8,3 +8,7 @@ gem 'elasticsearch-extensions' gem "pry" gem "ansi" gem "cane" + +group :development do + gem 'yard' +end \ No newline at end of file From 4f71d242d801e39e9f49e64f569e08d93c135076 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emily.s@elastic.co> Date: Thu, 5 Jul 2018 11:11:28 +0200 Subject: [PATCH 366/582] Add newline at end of Gemfile --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index a52a3c58c..5cb504908 100644 --- a/Gemfile +++ b/Gemfile @@ -11,4 +11,4 @@ gem "cane" group :development do gem 'yard' -end \ No newline at end of file +end From 433c0f076ad60b3e793765f715da522dcce7ff18 Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Mon, 9 Jul 2018 13:28:11 +0200 Subject: [PATCH 367/582] [STORE] Reduce repeated string instantiation (#813) --- .../persistence/repository/find.rb | 22 +++++++++++++++---- .../persistence/repository/naming.rb | 14 ++++++++++-- .../repository/response/results.rb | 22 ++++++++++++++----- .../persistence/repository/search.rb | 6 ++++- .../persistence/repository/serialize.rb | 15 ++++++++++--- .../test/unit/repository_naming_test.rb | 2 +- 6 files changed, 65 insertions(+), 16 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb index c6a9a6a4e..493ec1ba1 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb @@ -7,6 +7,20 @@ class DocumentNotFound < StandardError; end # module Find + # The default type of document. + # + ALL = '_all'.freeze + + # The key for accessing the document found and returned from an + # Elasticsearch _mget query. + # + DOCS = 'docs'.freeze + + # The key for the boolean value indicating whether a particular id + # has been successfully found in an Elasticsearch _mget query. + # + FOUND = 'found'.freeze + # Retrieve a single object or multiple objects from Elasticsearch by ID or IDs # # @example Retrieve a single object by ID @@ -43,14 +57,14 @@ def find(*args) # @return [true, false] # def exists?(id, options={}) - type = document_type || (klass ? __get_type_from_class(klass) : '_all') + type = document_type || (klass ? __get_type_from_class(klass) : ALL) client.exists( { index: index_name, type: type, id: id }.merge(options) ) end # @api private # def __find_one(id, options={}) - type = document_type || (klass ? __get_type_from_class(klass) : '_all') + type = document_type || (klass ? __get_type_from_class(klass) : ALL) document = client.get( { index: index_name, type: type, id: id }.merge(options) ) deserialize(document) @@ -61,10 +75,10 @@ def __find_one(id, options={}) # @api private # def __find_many(ids, options={}) - type = document_type || (klass ? __get_type_from_class(klass) : '_all') + type = document_type || (klass ? __get_type_from_class(klass) : ALL) documents = client.mget( { index: index_name, type: type, body: { ids: ids } }.merge(options) ) - documents['docs'].map { |document| document['found'] ? deserialize(document) : nil } + documents[DOCS].map { |document| document[FOUND] ? deserialize(document) : nil } end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb index 4c5794446..ba82505a1 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb @@ -6,6 +6,10 @@ module Repository # module Naming + # The possible keys for a document id. + # + IDS = [:id, 'id', :_id, '_id'].freeze + # Get or set the class used to initialize domain objects when deserializing them # def klass name=nil @@ -89,7 +93,7 @@ def __get_type_from_class(klass) # @api private # def __get_id_from_document(document) - document[:id] || document['id'] || document[:_id] || document['_id'] + document[IDS.find { |id| document[id] }] end # Extract a document ID from the document (assuming Hash or Hash-like object) @@ -106,7 +110,13 @@ def __get_id_from_document(document) # @api private # def __extract_id_from_document(document) - document.delete(:id) || document.delete('id') || document.delete(:_id) || document.delete('_id') + IDS.inject(nil) do |deleted, id| + if document[id] + document.delete(id) + else + deleted + end + end end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb index 73fb4f836..169ecd42e 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb @@ -12,6 +12,18 @@ class Results attr_reader :repository + # The key for accessing the results in an Elasticsearch query response. + # + HITS = 'hits'.freeze + + # The key for accessing the total number of hits in an Elasticsearch query response. + # + TOTAL = 'total'.freeze + + # The key for accessing the maximum score in an Elasticsearch query response. + # + MAX_SCORE = 'max_score'.freeze + # @param repository [Elasticsearch::Persistence::Repository::Class] The repository instance # @param response [Hash] The full response returned from the Elasticsearch client # @param options [Hash] Optional parameters @@ -33,25 +45,25 @@ def respond_to?(method_name, include_private = false) # The number of total hits for a query # def total - response['hits']['total'] + response[HITS][TOTAL] end # The maximum score for a query # def max_score - response['hits']['max_score'] + response[HITS][MAX_SCORE] end # Yields [object, hit] pairs to the block # def each_with_hit(&block) - results.zip(response['hits']['hits']).each(&block) + results.zip(response[HITS][HITS]).each(&block) end # Yields [object, hit] pairs and returns the result # def map_with_hit(&block) - results.zip(response['hits']['hits']).map(&block) + results.zip(response[HITS][HITS]).map(&block) end # Return the collection of domain objects @@ -64,7 +76,7 @@ def map_with_hit(&block) # @return [Array] # def results - @results ||= response['hits']['hits'].map do |document| + @results ||= response[HITS][HITS].map do |document| repository.deserialize(document.to_hash) end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb index de9e01aac..381b4d981 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb @@ -6,6 +6,10 @@ module Repository # module Search + # The key for accessing the count in a Elasticsearch query response. + # + COUNT = 'count'.freeze + # Returns a collection of domain objects by an Elasticsearch query # # Pass the query either as a string or a Hash-like object @@ -86,7 +90,7 @@ def count(query_or_definition=nil, options={}) raise ArgumentError, "[!] Pass the search definition as a Hash-like object or pass the query as a String, not as [#{query_or_definition.class}]" end - response['count'] + response[COUNT] end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb index 027f000b6..dc0865fd2 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb @@ -8,6 +8,16 @@ module Repository # module Serialize + # The key for document fields in an Elasticsearch query response. + # + SOURCE = '_source'.freeze + + # The key for the document type in an Elasticsearch query response. + # Note that it will be removed eventually, as multiple types in a single + # index are deprecated as of Elasticsearch 6.0. + # + TYPE = '_type'.freeze + # Serialize the object for storing it in Elasticsearch # # In the default implementation, call the `to_hash` method on the passed object. @@ -21,11 +31,10 @@ def serialize(document) # Use the `klass` property, if defined, otherwise try to get the class from the document's `_type`. # def deserialize(document) - _klass = klass || __get_klass_from_type(document['_type']) - _klass.new document['_source'] + _klass = klass || __get_klass_from_type(document[TYPE]) + _klass.new document[SOURCE] end end - end end end diff --git a/elasticsearch-persistence/test/unit/repository_naming_test.rb b/elasticsearch-persistence/test/unit/repository_naming_test.rb index a4845674d..4110205fb 100644 --- a/elasticsearch-persistence/test/unit/repository_naming_test.rb +++ b/elasticsearch-persistence/test/unit/repository_naming_test.rb @@ -50,7 +50,7 @@ module ::Foo; class Bar; end; end end context "extract an ID from the document" do - should "delete the key from theHash" do + should "delete the key from the Hash" do d1 = { :id => 1 } d2 = { :_id => 1 } d3 = { 'id' => 1 } From 4d056ed2184845e881c816b434debb1cb471fbc1 Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Thu, 19 Jul 2018 12:16:24 +0200 Subject: [PATCH 368/582] [MODEL] Use default doc type: _doc (#814) --- elasticsearch-model/lib/elasticsearch/model/naming.rb | 4 +++- .../test/integration/active_record_basic_test.rb | 2 ++ .../integration/active_record_custom_serialization_test.rb | 4 ++-- .../test/integration/active_record_namespaced_model_test.rb | 2 ++ elasticsearch-model/test/unit/indexing_test.rb | 4 ++-- elasticsearch-model/test/unit/naming_inheritance_test.rb | 4 ++-- elasticsearch-model/test/unit/naming_test.rb | 4 ++-- 7 files changed, 15 insertions(+), 9 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/naming.rb b/elasticsearch-model/lib/elasticsearch/model/naming.rb index 7bf24f089..2c5151886 100644 --- a/elasticsearch-model/lib/elasticsearch/model/naming.rb +++ b/elasticsearch-model/lib/elasticsearch/model/naming.rb @@ -5,6 +5,8 @@ module Model # module Naming + DEFAULT_DOC_TYPE= '_doc'.freeze + module ClassMethods # Get or set the name of the index @@ -90,7 +92,7 @@ def default_index_name end def default_document_type - self.model_name.element + DEFAULT_DOC_TYPE end end diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index 26c5785f2..02c7b24c3 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -16,6 +16,8 @@ class ::Article < ActiveRecord::Base include Elasticsearch::Model include Elasticsearch::Model::Callbacks + document_type 'article' + settings index: { number_of_shards: 1, number_of_replicas: 0 } do mapping do indexes :title, type: 'text', analyzer: 'snowball' diff --git a/elasticsearch-model/test/integration/active_record_custom_serialization_test.rb b/elasticsearch-model/test/integration/active_record_custom_serialization_test.rb index cb706cf99..9847967e5 100644 --- a/elasticsearch-model/test/integration/active_record_custom_serialization_test.rb +++ b/elasticsearch-model/test/integration/active_record_custom_serialization_test.rb @@ -41,7 +41,7 @@ def as_indexed_json(options={}) a = ArticleWithCustomSerialization.__elasticsearch__.client.get \ index: 'article_with_custom_serializations', - type: 'article_with_custom_serialization', + type: '_doc', id: '1' assert_equal( { 'title' => 'Test' }, a['_source'] ) @@ -55,7 +55,7 @@ def as_indexed_json(options={}) a = ArticleWithCustomSerialization.__elasticsearch__.client.get \ index: 'article_with_custom_serializations', - type: 'article_with_custom_serialization', + type: '_doc', id: '1' assert_equal( { 'title' => 'UPDATED' }, a['_source'] ) diff --git a/elasticsearch-model/test/integration/active_record_namespaced_model_test.rb b/elasticsearch-model/test/integration/active_record_namespaced_model_test.rb index 9885b3a1a..75e7aa745 100644 --- a/elasticsearch-model/test/integration/active_record_namespaced_model_test.rb +++ b/elasticsearch-model/test/integration/active_record_namespaced_model_test.rb @@ -22,6 +22,8 @@ class Article < ActiveRecord::Base include Elasticsearch::Model include Elasticsearch::Model::Callbacks + document_type 'article' + mapping { indexes :title } end end diff --git a/elasticsearch-model/test/unit/indexing_test.rb b/elasticsearch-model/test/unit/indexing_test.rb index e3bfad868..591977751 100644 --- a/elasticsearch-model/test/unit/indexing_test.rb +++ b/elasticsearch-model/test/unit/indexing_test.rb @@ -149,7 +149,7 @@ class NotFound < Exception; end should "update and return the index mappings" do DummyIndexingModel.mappings foo: 'boo' DummyIndexingModel.mappings bar: 'bam' - assert_equal( { dummy_indexing_model: { foo: "boo", bar: "bam", properties: {} } }, + assert_equal( { _doc: { foo: "boo", bar: "bam", properties: {} } }, DummyIndexingModel.mappings.to_hash ) end @@ -544,7 +544,7 @@ class ::DummyIndexingModelForRecreate indices.expects(:create).with do |payload| assert_equal 'dummy_indexing_model_for_recreates', payload[:index] assert_equal 1, payload[:body][:settings][:index][:number_of_shards] - assert_equal 'keyword', payload[:body][:mappings][:dummy_indexing_model_for_recreate][:properties][:foo][:analyzer] + assert_equal 'keyword', payload[:body][:mappings][:_doc][:properties][:foo][:analyzer] true end.returns({}) diff --git a/elasticsearch-model/test/unit/naming_inheritance_test.rb b/elasticsearch-model/test/unit/naming_inheritance_test.rb index b66d415a0..1b40e9cfd 100644 --- a/elasticsearch-model/test/unit/naming_inheritance_test.rb +++ b/elasticsearch-model/test/unit/naming_inheritance_test.rb @@ -69,8 +69,8 @@ class ::Cat < ::Animal end should "return the default document_type" do - assert_equal "test_base", TestBase.document_type - assert_equal "test_base", TestBase.new.document_type + assert_equal "_doc", TestBase.document_type + assert_equal "_doc", TestBase.new.document_type end should "return the explicit document_type" do diff --git a/elasticsearch-model/test/unit/naming_test.rb b/elasticsearch-model/test/unit/naming_test.rb index 424adf7cc..3f2368891 100644 --- a/elasticsearch-model/test/unit/naming_test.rb +++ b/elasticsearch-model/test/unit/naming_test.rb @@ -29,8 +29,8 @@ class DummyNamingModelInNamespace end should "return the default document_type" do - assert_equal 'dummy_naming_model', DummyNamingModel.document_type - assert_equal 'dummy_naming_model', DummyNamingModel.new.document_type + assert_equal '_doc', DummyNamingModel.document_type + assert_equal '_doc', DummyNamingModel.new.document_type end should "set and return the index_name" do From d11e498424a4d96e148c95d1dfad41044987c015 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Thu, 19 Jul 2018 12:34:37 +0200 Subject: [PATCH 369/582] minor: Fix spacing --- elasticsearch-model/lib/elasticsearch/model/naming.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/naming.rb b/elasticsearch-model/lib/elasticsearch/model/naming.rb index 2c5151886..cef23e810 100644 --- a/elasticsearch-model/lib/elasticsearch/model/naming.rb +++ b/elasticsearch-model/lib/elasticsearch/model/naming.rb @@ -5,7 +5,7 @@ module Model # module Naming - DEFAULT_DOC_TYPE= '_doc'.freeze + DEFAULT_DOC_TYPE = '_doc'.freeze module ClassMethods From b8d73bc6026ca07a41b6337b884f3840ffc1067d Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Mon, 23 Jul 2018 15:12:44 +0200 Subject: [PATCH 370/582] [STORE] Make default doc type '_doc' in preparation for deprecation of mapping types (#816) * [STORE] Require document_type or method options to set type in find requests * [STORE] Require document_type for a Repository to have a document type set, otherwise use _doc * [STORE] Use document_type or type in options for search requests * [STORE] Require #klass to be defined on a repository for deserialization * [STORE] Only rely on document_type setting in storage requests * [STORE] Fix up tests to work with default document_type and klass on Repository object --- .../persistence/repository/find.rb | 6 +- .../persistence/repository/naming.rb | 44 ++------- .../persistence/repository/search.rb | 4 +- .../persistence/repository/serialize.rb | 12 ++- .../persistence/repository/store.rb | 9 +- .../repository/custom_class_test.rb | 3 + .../repository/default_class_test.rb | 3 + .../repository/virtus_model_test.rb | 1 + .../test/unit/repository_find_test.rb | 99 ++----------------- .../test/unit/repository_naming_test.rb | 39 +------- .../test/unit/repository_search_test.rb | 23 +---- .../test/unit/repository_serialize_test.rb | 22 +---- .../test/unit/repository_store_test.rb | 98 ++---------------- 13 files changed, 62 insertions(+), 301 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb index 493ec1ba1..5e214cfb7 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb @@ -57,14 +57,14 @@ def find(*args) # @return [true, false] # def exists?(id, options={}) - type = document_type || (klass ? __get_type_from_class(klass) : ALL) + type = document_type || ALL client.exists( { index: index_name, type: type, id: id }.merge(options) ) end # @api private # def __find_one(id, options={}) - type = document_type || (klass ? __get_type_from_class(klass) : ALL) + type = document_type || ALL document = client.get( { index: index_name, type: type, id: id }.merge(options) ) deserialize(document) @@ -75,7 +75,7 @@ def __find_one(id, options={}) # @api private # def __find_many(ids, options={}) - type = document_type || (klass ? __get_type_from_class(klass) : ALL) + type = document_type || ALL documents = client.mget( { index: index_name, type: type, body: { ids: ids } }.merge(options) ) documents[DOCS].map { |document| document[FOUND] ? deserialize(document) : nil } diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb index ba82505a1..d559d8d51 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb @@ -10,10 +10,16 @@ module Naming # IDS = [:id, 'id', :_id, '_id'].freeze + DEFAULT_DOC_TYPE = '_doc'.freeze + # Get or set the class used to initialize domain objects when deserializing them # - def klass name=nil - @klass = name || @klass + def klass(name=nil) + if name + @klass = name + else + @klass + end end # Set the class used to initialize domain objects when deserializing them @@ -43,7 +49,7 @@ def index_name=(name) # Get or set the document type used when storing and retrieving documents # def document_type name=nil - @document_type = name || @document_type || (klass ? klass.to_s.underscore : nil) + @document_type = name || @document_type || DEFAULT_DOC_TYPE end; alias :type :document_type # Set the document type used when storing and retrieving documents @@ -52,38 +58,6 @@ def document_type=(name) @document_type = name end; alias :type= :document_type= - # Get the Ruby class from the Elasticsearch `_type` - # - # @example - # repository.__get_klass_from_type 'note' - # => Note - # - # @return [Class] The class corresponding to the passed type - # @raise [NameError] if the class cannot be found - # - # @api private - # - def __get_klass_from_type(type) - klass = type.classify - klass.constantize - rescue NameError => e - raise NameError, "Attempted to get class '#{klass}' from the '#{type}' type, but no such class can be found." - end - - # Get the Elasticsearch `_type` from the Ruby class - # - # @example - # repository.__get_type_from_class Note - # => "note" - # - # @return [String] The type corresponding to the passed class - # - # @api private - # - def __get_type_from_class(klass) - klass.to_s.underscore - end - # Get a document ID from the document (assuming Hash or Hash-like object) # # @example diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb index 381b4d981..07c0e4d3d 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb @@ -44,7 +44,7 @@ module Search # @return [Elasticsearch::Persistence::Repository::Response::Results] # def search(query_or_definition, options={}) - type = document_type || (klass ? __get_type_from_class(klass) : nil ) + type = document_type case when query_or_definition.respond_to?(:to_hash) @@ -79,7 +79,7 @@ def search(query_or_definition, options={}) # def count(query_or_definition=nil, options={}) query_or_definition ||= { query: { match_all: {} } } - type = document_type || (klass ? __get_type_from_class(klass) : nil ) + type = document_type case when query_or_definition.respond_to?(:to_hash) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb index dc0865fd2..e9a8d875e 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb @@ -8,6 +8,14 @@ module Repository # module Serialize + # Error message raised when documents are attempted to be deserialized and no klass is defined for + # the Repository. + # + # @since 6.0.0 + NO_CLASS_ERROR_MESSAGE = "No class is defined for deserializing documents. " + + "Please define a 'klass' for the Repository or define a custom " + + "deserialize method.".freeze + # The key for document fields in an Elasticsearch query response. # SOURCE = '_source'.freeze @@ -31,8 +39,8 @@ def serialize(document) # Use the `klass` property, if defined, otherwise try to get the class from the document's `_type`. # def deserialize(document) - _klass = klass || __get_klass_from_type(document[TYPE]) - _klass.new document[SOURCE] + raise NameError.new(NO_CLASS_ERROR_MESSAGE) unless klass + klass.new document[SOURCE] end end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb index 5bec487ce..2327d23a6 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb @@ -17,7 +17,7 @@ module Store def save(document, options={}) serialized = serialize(document) id = __get_id_from_document(serialized) - type = document_type || __get_type_from_class(klass || document.class) + type = document_type client.index( { index: index_name, type: type, id: id, body: serialized }.merge(options) ) end @@ -48,8 +48,7 @@ def update(document, options={}) type = options.delete(:type) || \ (defined?(serialized) && serialized && serialized.delete(:type)) || \ - document_type || \ - __get_type_from_class(klass) + document_type if defined?(serialized) && serialized body = if serialized[:script] @@ -80,11 +79,11 @@ def update(document, options={}) def delete(document, options={}) if document.is_a?(String) || document.is_a?(Integer) id = document - type = document_type || __get_type_from_class(klass) + type = document_type else serialized = serialize(document) id = __get_id_from_document(serialized) - type = document_type || __get_type_from_class(klass || document.class) + type = document_type end client.delete( { index: index_name, type: type, id: id }.merge(options) ) end diff --git a/elasticsearch-persistence/test/integration/repository/custom_class_test.rb b/elasticsearch-persistence/test/integration/repository/custom_class_test.rb index 0aaae91ff..6528dd438 100644 --- a/elasticsearch-persistence/test/integration/repository/custom_class_test.rb +++ b/elasticsearch-persistence/test/integration/repository/custom_class_test.rb @@ -30,6 +30,7 @@ class ::MyNotesRepository include Elasticsearch::Persistence::Repository klass MyNote + document_type 'my_note' settings number_of_shards: 1 do mapping do @@ -45,6 +46,8 @@ def deserialize(document) end @repository = MyNotesRepository.new + @repository.klass = MyNotesRepository.klass + @repository.document_type = MyNotesRepository.document_type @repository.client.cluster.health wait_for_status: 'yellow' end diff --git a/elasticsearch-persistence/test/integration/repository/default_class_test.rb b/elasticsearch-persistence/test/integration/repository/default_class_test.rb index c27d587be..1674a3a42 100644 --- a/elasticsearch-persistence/test/integration/repository/default_class_test.rb +++ b/elasticsearch-persistence/test/integration/repository/default_class_test.rb @@ -19,6 +19,8 @@ def to_hash context "The default repository class" do setup do @repository = Elasticsearch::Persistence::Repository.new + @repository.klass = ::Note + @repository.document_type = 'note' @repository.client.cluster.health wait_for_status: 'yellow' end @@ -105,6 +107,7 @@ def to_hash end should "save and find a plain hash" do + @repository.klass = Hash @repository.save id: 1, title: 'Hash' result = @repository.find(1) assert_equal 'Hash', result['_source']['title'] diff --git a/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb b/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb index fdefe2d45..c6ad88177 100644 --- a/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb +++ b/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb @@ -25,6 +25,7 @@ def set_id(id) @repository = Elasticsearch::Persistence::Repository.new do index :pages klass Page + document_type 'page' def deserialize(document) page = klass.new document['_source'] diff --git a/elasticsearch-persistence/test/unit/repository_find_test.rb b/elasticsearch-persistence/test/unit/repository_find_test.rb index 3af93ef3c..b20006ca1 100644 --- a/elasticsearch-persistence/test/unit/repository_find_test.rb +++ b/elasticsearch-persistence/test/unit/repository_find_test.rb @@ -48,27 +48,8 @@ class MyDocument; end assert_equal false, subject.exists?('1') end - should "return whether document for klass exists" do - subject.expects(:document_type).returns(nil) - subject.expects(:klass).returns(MyDocument).at_least_once - subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') - - @client - .expects(:exists) - .with do |arguments| - assert_equal 'my_document', arguments[:type] - assert_equal '1', arguments[:id] - true - end - .returns(true) - - assert_equal true, subject.exists?('1') - end - should "return whether document for document_type exists" do subject.expects(:document_type).returns('my_document') - subject.expects(:klass).returns(MyDocument).at_most_once - subject.expects(:__get_type_from_class).never @client .expects(:exists) @@ -82,9 +63,7 @@ class MyDocument; end assert_equal true, subject.exists?('1') end - should "return whether document exists" do - subject.expects(:klass).returns(nil) - subject.expects(:__get_type_from_class).never + should "return whether document exists using _all type" do @client .expects(:exists) @@ -100,39 +79,20 @@ class MyDocument; end should "pass options to the client" do @client.expects(:exists).with do |arguments| + assert_equal 'my_document', arguments[:type] assert_equal 'foobarbam', arguments[:index] assert_equal 'bambam', arguments[:routing] true end - subject.exists? '1', index: 'foobarbam', routing: 'bambam' + subject.exists? '1', index: 'foobarbam', routing: 'bambam', type: 'my_document' end end context "'__find_one' method" do - should "find document based on klass and return a deserialized object" do - subject.expects(:document_type).returns(nil) - subject.expects(:klass).returns(MyDocument).at_least_once - subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') - subject.expects(:deserialize).with({'_source' => {'foo' => 'bar'}}).returns(MyDocument.new) - - @client - .expects(:get) - .with do |arguments| - assert_equal 'my_document', arguments[:type] - assert_equal '1', arguments[:id] - true - end - .returns({'_source' => { 'foo' => 'bar' }}) - - assert_instance_of MyDocument, subject.__find_one('1') - end - - should "find document based on document_type and return a deserialized object" do + should "find a document based on document_type and return a deserialized object" do subject.expects(:document_type).returns('my_document') - subject.expects(:klass).returns(MyDocument).at_most_once - subject.expects(:__get_type_from_class).never subject.expects(:deserialize).with({'_source' => {'foo' => 'bar'}}).returns(MyDocument.new) @@ -148,10 +108,8 @@ class MyDocument; end assert_instance_of MyDocument, subject.__find_one('1') end - should "find document and return a deserialized object" do + should "find a document using _all if document_type is not defined" do subject.expects(:document_type).returns(nil) - subject.expects(:klass).returns(nil).at_least_once - subject.expects(:__get_type_from_class).never subject.expects(:deserialize).with({'_source' => {'foo' => 'bar'}}).returns(MyDocument.new) @@ -169,7 +127,6 @@ class MyDocument; end should "raise DocumentNotFound exception when the document cannot be found" do subject.expects(:document_type).returns(nil) - subject.expects(:klass).returns(nil).at_least_once subject.expects(:deserialize).never @@ -183,8 +140,6 @@ class MyDocument; end end should "pass other exceptions" do - subject.expects(:klass).returns(nil).at_least_once - subject.expects(:deserialize).never @client @@ -197,7 +152,6 @@ class MyDocument; end end should "pass options to the client" do - subject.expects(:klass).returns(nil).at_least_once subject.expects(:deserialize) @client @@ -209,7 +163,7 @@ class MyDocument; end end .returns({'_source' => { 'foo' => 'bar' }}) - subject.__find_one '1', index: 'foobarbam', routing: 'bambam' + subject.__find_one '1', index: 'foobarbam', routing: 'bambam', type: 'my_document' end end @@ -232,39 +186,8 @@ class MyDocument; end ]} end - should "find documents based on klass and return an Array of deserialized objects" do - subject.expects(:document_type).returns(nil) - subject.expects(:klass).returns(MyDocument).at_least_once - subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') - - subject - .expects(:deserialize) - .with(@response['docs'][0]) - .returns(MyDocument.new) - - subject - .expects(:deserialize) - .with(@response['docs'][1]) - .returns(MyDocument.new) - - @client - .expects(:mget) - .with do |arguments| - assert_equal 'my_document', arguments[:type] - assert_equal ['1', '2'], arguments[:body][:ids] - true - end - .returns(@response) - - results = subject.__find_many(['1', '2']) - assert_instance_of MyDocument, results[0] - assert_instance_of MyDocument, results[1] - end - should "find documents based on document_type and return an Array of deserialized objects" do subject.expects(:document_type).returns('my_document') - subject.expects(:klass).returns(MyDocument).at_most_once - subject.expects(:__get_type_from_class).never subject.expects(:deserialize).twice @@ -282,8 +205,6 @@ class MyDocument; end should "find documents and return an Array of deserialized objects" do subject.expects(:document_type).returns(nil) - subject.expects(:klass).returns(nil).at_least_once - subject.expects(:__get_type_from_class).never subject .expects(:deserialize) @@ -335,9 +256,7 @@ class MyDocument; end "_source"=>{"id"=>"2", "title"=>"Test 2"}} ]} - subject.expects(:document_type).returns(nil) - subject.expects(:klass).returns(MyDocument).at_least_once - subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') + subject.expects(:document_type).returns('my_document') subject .expects(:deserialize) @@ -368,19 +287,19 @@ class MyDocument; end end should "pass options to the client" do - subject.expects(:klass).returns(nil).at_least_once subject.expects(:deserialize).twice @client .expects(:mget) .with do |arguments| + assert_equal 'my_document', arguments[:type] assert_equal 'foobarbam', arguments[:index] assert_equal 'bambam', arguments[:routing] true end .returns(@response) - subject.__find_many ['1', '2'], index: 'foobarbam', routing: 'bambam' + subject.__find_many ['1', '2'], index: 'foobarbam', routing: 'bambam', type: 'my_document' end end diff --git a/elasticsearch-persistence/test/unit/repository_naming_test.rb b/elasticsearch-persistence/test/unit/repository_naming_test.rb index 4110205fb..aed34598f 100644 --- a/elasticsearch-persistence/test/unit/repository_naming_test.rb +++ b/elasticsearch-persistence/test/unit/repository_naming_test.rb @@ -11,35 +11,6 @@ module ::Foo; class Bar; end; end @shoulda_subject = Class.new() { include Elasticsearch::Persistence::Repository::Naming }.new end - context "get Ruby class from the Elasticsearch type" do - should "get a simple class" do - assert_equal Foobar, subject.__get_klass_from_type('foobar') - end - should "get a camelcased class" do - assert_equal FooBar, subject.__get_klass_from_type('foo_bar') - end - should "get a namespaced class" do - assert_equal Foo::Bar, subject.__get_klass_from_type('foo/bar') - end - should "re-raise a NameError exception" do - assert_raise NameError do - subject.__get_klass_from_type('foobarbazbam') - end - end - end - - context "get Elasticsearch type from the Ruby class" do - should "encode a simple class" do - assert_equal 'foobar', subject.__get_type_from_class(Foobar) - end - should "encode a camelcased class" do - assert_equal 'foo_bar', subject.__get_type_from_class(FooBar) - end - should "encode a namespaced class" do - assert_equal 'foo/bar', subject.__get_type_from_class(Foo::Bar) - end - end - context "get an ID from the document" do should "get an ID from Hash" do assert_equal 1, subject.__get_id_from_document(id: 1) @@ -118,17 +89,17 @@ class ::MySpecialRepository; end end context "document_type" do - should "be nil when no klass is set" do - assert_equal nil, subject.document_type + should "be the default doc type when no klass is set" do + assert_equal '_doc', subject.document_type end - should "default to klass" do + should "does not use the klass" do subject.klass Foobar - assert_equal 'foobar', subject.document_type + assert_equal '_doc', subject.document_type end should "be aliased as `type`" do - subject.klass Foobar + subject.document_type 'foobar' assert_equal 'foobar', subject.type end diff --git a/elasticsearch-persistence/test/unit/repository_search_test.rb b/elasticsearch-persistence/test/unit/repository_search_test.rb index d0d5beef0..5cf6d5ab3 100644 --- a/elasticsearch-persistence/test/unit/repository_search_test.rb +++ b/elasticsearch-persistence/test/unit/repository_search_test.rb @@ -14,13 +14,9 @@ class MyDocument; end @shoulda_subject.stubs(:client).returns(@client) end - should "search in type based on klass" do - subject.expects(:klass).returns(MyDocument).at_least_once - subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') - + should "search in type does not use the klass setting" do @client.expects(:search).with do |arguments| assert_equal 'test', arguments[:index] - assert_equal 'my_document', arguments[:type] assert_equal({foo: 'bar'}, arguments[:body]) true end @@ -30,7 +26,6 @@ class MyDocument; end should "search in type based on document_type" do subject.expects(:document_type).returns('my_special_document').at_least_once - subject.expects(:__get_type_from_class).never @client.expects(:search).with do |arguments| assert_equal 'test', arguments[:index] @@ -44,24 +39,19 @@ class MyDocument; end should "search across all types" do subject.expects(:document_type).returns(nil).at_least_once - subject.expects(:klass).returns(nil).at_least_once - subject.expects(:__get_type_from_class).never @client.expects(:search).with do |arguments| assert_equal 'test', arguments[:index] - assert_equal nil, arguments[:type] + assert_equal '_all', arguments[:type] assert_equal({foo: 'bar'}, arguments[:body]) true end assert_instance_of Elasticsearch::Persistence::Repository::Response::Results, - subject.search(foo: 'bar') + subject.search({ foo: 'bar' }, type: '_all') end should "pass options to the client" do - subject.expects(:klass).returns(nil).at_least_once - subject.expects(:__get_type_from_class).never - @client.expects(:search).twice.with do |arguments| assert_equal 'bambam', arguments[:routing] true @@ -74,9 +64,6 @@ class MyDocument; end end should "search with simple search" do - subject.expects(:klass).returns(nil).at_least_once - subject.expects(:__get_type_from_class).never - @client.expects(:search).with do |arguments| assert_equal 'foobar', arguments[:q] true @@ -87,9 +74,6 @@ class MyDocument; end end should "raise error for incorrect search definitions" do - subject.expects(:klass).returns(nil).at_least_once - subject.expects(:__get_type_from_class).never - assert_raise ArgumentError do subject.search 123 end @@ -113,5 +97,4 @@ class MyDocument; end assert_equal 1, subject.count( { query: { match: { foo: 'bar' } } }, { ignore_unavailable: true } ) end end - end diff --git a/elasticsearch-persistence/test/unit/repository_serialize_test.rb b/elasticsearch-persistence/test/unit/repository_serialize_test.rb index 48d0323d8..a69145464 100644 --- a/elasticsearch-persistence/test/unit/repository_serialize_test.rb +++ b/elasticsearch-persistence/test/unit/repository_serialize_test.rb @@ -24,33 +24,17 @@ class MyDocument; end context "deserialize" do should "get the class name from #klass" do subject.expects(:klass) - .returns(MyDocument) + .returns(MyDocument).twice MyDocument.expects(:new) subject.deserialize( {} ) end - should "get the class name from Elasticsearch _type" do - subject.expects(:klass) - .returns(nil) - - subject.expects(:__get_klass_from_type) - .returns(MyDocument) - - MyDocument.expects(:new) - - subject.deserialize( {} ) - end - - should "create the class instance with _source attributes" do + should "raise an error when klass isn't set" do subject.expects(:klass).returns(nil) - subject.expects(:__get_klass_from_type).returns(MyDocument) - - MyDocument.expects(:new).with({ 'foo' => 'bar' }) - - subject.deserialize( {'_source' => { 'foo' => 'bar' } } ) + assert_raise(NameError) { subject.deserialize( {} ) } end end end diff --git a/elasticsearch-persistence/test/unit/repository_store_test.rb b/elasticsearch-persistence/test/unit/repository_store_test.rb index 69f3defc3..b886eecdf 100644 --- a/elasticsearch-persistence/test/unit/repository_store_test.rb +++ b/elasticsearch-persistence/test/unit/repository_store_test.rb @@ -13,52 +13,12 @@ class MyDocument; end end context "save" do - should "serialize the document, get type from klass and index it" do - subject.expects(:serialize).returns({foo: 'bar'}) - subject.expects(:document_type).returns(nil) - subject.expects(:klass).at_least_once.returns(MyDocument) - subject.expects(:__get_type_from_class).with(MyDocument).at_least_once.returns('my_document') - subject.expects(:__get_id_from_document).returns('1') - - client = mock - client.expects(:index).with do |arguments| - assert_equal 'my_document', arguments[:type] - assert_equal '1', arguments[:id] - assert_equal({foo: 'bar'}, arguments[:body]) - true - end - subject.expects(:client).returns(client) - - subject.save({foo: 'bar'}) - end - - should "serialize the document, get type from document class and index it" do - subject.expects(:serialize).returns({foo: 'bar'}) - subject.expects(:document_type).returns(nil) - subject.expects(:klass).at_least_once.returns(nil) - subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') - subject.expects(:__get_id_from_document).returns('1') - - client = mock - client.expects(:index).with do |arguments| - assert_equal 'my_document', arguments[:type] - assert_equal '1', arguments[:id] - assert_equal({foo: 'bar'}, arguments[:body]) - true - end - subject.expects(:client).returns(client) - - subject.save(MyDocument.new) - end should "serialize the document, get type from document_type and index it" do subject.expects(:serialize).returns({foo: 'bar'}) subject.expects(:document_type).returns('my_document') - subject.expects(:klass).never - subject.expects(:__get_type_from_class).never - subject.expects(:__get_id_from_document).returns('1') client = mock @@ -76,19 +36,18 @@ class MyDocument; end should "pass the options to the client" do subject.expects(:serialize).returns({foo: 'bar'}) subject.expects(:document_type).returns(nil) - subject.expects(:klass).at_least_once.returns(MyDocument) - subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') subject.expects(:__get_id_from_document).returns('1') client = mock client.expects(:index).with do |arguments| + assert_equal 'my_document', arguments[:type] assert_equal 'foobarbam', arguments[:index] assert_equal 'bambam', arguments[:routing] true end subject.expects(:client).returns(client) - subject.save({foo: 'bar'}, { index: 'foobarbam', routing: 'bambam' }) + subject.save({foo: 'bar'}, { index: 'foobarbam', routing: 'bambam', type: 'my_document' }) end end @@ -208,50 +167,12 @@ class MyDocument; end end context "delete" do - should "get type from klass when passed only ID" do - subject.expects(:serialize).never - subject.expects(:document_type).returns(nil) - subject.expects(:klass).at_least_once.returns(MyDocument) - subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') - subject.expects(:__get_id_from_document).never - - client = mock - client.expects(:delete).with do |arguments| - assert_equal 'my_document', arguments[:type] - assert_equal '1', arguments[:id] - true - end - subject.expects(:client).returns(client) - - subject.delete('1') - end - - should "get ID from document and type from klass when passed a document" do - subject.expects(:serialize).returns({id: '1', foo: 'bar'}) - subject.expects(:document_type).returns(nil) - subject.expects(:klass).at_least_once.returns(MyDocument) - subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') - subject.expects(:__get_id_from_document).with({id: '1', foo: 'bar'}).returns('1') - - client = mock - client.expects(:delete).with do |arguments| - assert_equal 'my_document', arguments[:type] - assert_equal '1', arguments[:id] - true - end - subject.expects(:client).returns(client) - - subject.delete({id: '1', foo: 'bar'}) - end should "get ID from document and type from document_type when passed a document" do subject.expects(:serialize).returns({id: '1', foo: 'bar'}) subject.expects(:document_type).returns('my_document') - subject.expects(:klass).never - subject.expects(:__get_type_from_class).never - subject.expects(:__get_id_from_document).with({id: '1', foo: 'bar'}).returns('1') client = mock @@ -265,16 +186,14 @@ class MyDocument; end subject.delete({id: '1', foo: 'bar'}) end - should "get ID and type from document when passed a document" do + should "get ID and uses the default document type" do subject.expects(:serialize).returns({id: '1', foo: 'bar'}) - subject.expects(:document_type).returns(nil) - subject.expects(:klass).at_least_once.returns(nil) - subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document') + subject.expects(:document_type).returns('_doc') subject.expects(:__get_id_from_document).with({id: '1', foo: 'bar'}).returns('1') client = mock client.expects(:delete).with do |arguments| - assert_equal 'my_document', arguments[:type] + assert_equal '_doc', arguments[:type] assert_equal '1', arguments[:id] true end @@ -284,19 +203,16 @@ class MyDocument; end end should "pass the options to the client" do - subject.expects(:document_type).returns(nil) - subject.expects(:klass).at_least_once.returns(MyDocument) - subject.expects(:__get_type_from_class).returns('my_document') - client = mock client.expects(:delete).with do |arguments| + assert_equal 'my_document', arguments[:type] assert_equal 'foobarbam', arguments[:index] assert_equal 'bambam', arguments[:routing] true end subject.expects(:client).returns(client) - subject.delete('1', index: 'foobarbam', routing: 'bambam') + subject.delete('1', index: 'foobarbam', routing: 'bambam', type: 'my_document') end end end From 071fa968b588d513b2bd9583db5ce9e4b7c61096 Mon Sep 17 00:00:00 2001 From: Roman Novoselov <rnovoselov93@gmail.com> Date: Tue, 24 Jul 2018 16:20:04 +0300 Subject: [PATCH 371/582] Added condition for :update after commit & rescue if record NotFound (#780) * Handle the case on trying to delete non existing article * Update/delete document depending on #published? --- elasticsearch-model/README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 22b9b378c..6274463b1 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -494,7 +494,11 @@ class Article < ActiveRecord::Base end after_commit on: [:update] do - __elasticsearch__.update_document if self.published? + if self.published? + __elasticsearch__.update_document + else + __elasticsearch__.delete_document + end end after_commit on: [:destroy] do @@ -536,7 +540,11 @@ class Indexer record = Article.find(record_id) Client.index index: 'articles', type: 'article', id: record.id, body: record.__elasticsearch__.as_indexed_json when /delete/ - Client.delete index: 'articles', type: 'article', id: record_id + begin + Client.delete index: 'articles', type: 'article', id: record_id + rescue Elasticsearch::Transport::Transport::Errors::NotFound + logger.debug "Article not found, ID: #{record_id}" + end else raise ArgumentError, "Unknown operation '#{operation}'" end end From 5f9ca8f805a3f3eeef543e8a3a858457f05439fc Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Wed, 25 Jul 2018 13:52:21 +0200 Subject: [PATCH 372/582] Update various gemspecs to conditionally depend on gems incompatible with JRuby (#810) * [MODEL] Update dependencies for JRuby compatibility * [STORE] Update dependencies for JRuby compatibility * [RAILS] Update dependencies for JRuby compatibility --- elasticsearch-model/elasticsearch-model.gemspec | 4 ++-- elasticsearch-model/gemfiles/3.0.gemfile | 2 +- elasticsearch-model/gemfiles/4.0.gemfile | 2 +- elasticsearch-model/gemfiles/5.0.gemfile | 2 +- elasticsearch-persistence/elasticsearch-persistence.gemspec | 2 +- elasticsearch-rails/elasticsearch-rails.gemspec | 3 +-- 6 files changed, 7 insertions(+), 8 deletions(-) diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index 5cd1bcec1..9e52c400a 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -32,7 +32,7 @@ Gem::Specification.new do |s| s.add_development_dependency "elasticsearch-extensions" - s.add_development_dependency "sqlite3" + s.add_development_dependency "sqlite3" unless defined?(JRUBY_VERSION) s.add_development_dependency "activemodel", "> 3" s.add_development_dependency "oj" unless defined?(JRUBY_VERSION) @@ -45,7 +45,7 @@ Gem::Specification.new do |s| s.add_development_dependency "mocha" s.add_development_dependency "turn" s.add_development_dependency "yard" - s.add_development_dependency "ruby-prof" + s.add_development_dependency "ruby-prof" unless defined?(JRUBY_VERSION) s.add_development_dependency "pry" s.add_development_dependency "simplecov" diff --git a/elasticsearch-model/gemfiles/3.0.gemfile b/elasticsearch-model/gemfiles/3.0.gemfile index 23cbdf53d..97ea5f60a 100644 --- a/elasticsearch-model/gemfiles/3.0.gemfile +++ b/elasticsearch-model/gemfiles/3.0.gemfile @@ -10,4 +10,4 @@ gemspec path: '../' gem 'activemodel', '>= 3.0' gem 'activerecord', '~> 3.2' gem 'mongoid', '>= 3.0' -gem 'sqlite3' +gem 'sqlite3' unless defined?(JRUBY_VERSION) diff --git a/elasticsearch-model/gemfiles/4.0.gemfile b/elasticsearch-model/gemfiles/4.0.gemfile index 89044bb19..fa0cc73e9 100644 --- a/elasticsearch-model/gemfiles/4.0.gemfile +++ b/elasticsearch-model/gemfiles/4.0.gemfile @@ -9,4 +9,4 @@ gemspec path: '../' gem 'activemodel', '~> 4' gem 'activerecord', '~> 4' -gem 'sqlite3' +gem 'sqlite3' unless defined?(JRUBY_VERSION) diff --git a/elasticsearch-model/gemfiles/5.0.gemfile b/elasticsearch-model/gemfiles/5.0.gemfile index 75b8a7ca9..77e10c7bb 100644 --- a/elasticsearch-model/gemfiles/5.0.gemfile +++ b/elasticsearch-model/gemfiles/5.0.gemfile @@ -9,4 +9,4 @@ gemspec path: '../' gem 'activemodel', '~> 5' gem 'activerecord', '~> 5' -gem 'sqlite3' +gem 'sqlite3' unless defined?(JRUBY_VERSION) diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index 38ba93594..e90f2afb1 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -45,7 +45,7 @@ Gem::Specification.new do |s| s.add_development_dependency "mocha" s.add_development_dependency "turn" s.add_development_dependency "yard" - s.add_development_dependency "ruby-prof" + s.add_development_dependency "ruby-prof" unless defined?(JRUBY_VERSION) s.add_development_dependency "pry" s.add_development_dependency "simplecov" diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index aa976c675..c7dbf32d5 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -29,7 +29,6 @@ Gem::Specification.new do |s| s.add_development_dependency "elasticsearch-extensions" s.add_development_dependency "elasticsearch-model" - s.add_development_dependency "oj" unless defined?(JRUBY_VERSION) s.add_development_dependency "rails", ">= 3.1" s.add_development_dependency "lograge" @@ -40,7 +39,7 @@ Gem::Specification.new do |s| s.add_development_dependency "mocha" s.add_development_dependency "turn" s.add_development_dependency "yard" - s.add_development_dependency "ruby-prof" + s.add_development_dependency "ruby-prof" unless defined?(JRUBY_VERSION) s.add_development_dependency "pry" s.add_development_dependency "simplecov" From 1f742f6a5cc48198964439f4ac7ab17c8b31d10a Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emily.s@elastic.co> Date: Wed, 25 Jul 2018 14:30:09 +0200 Subject: [PATCH 373/582] [RAILS] Add 'oj' back as a development dependency in gemspec --- elasticsearch-rails/elasticsearch-rails.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index c7dbf32d5..1fad14c36 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -39,6 +39,7 @@ Gem::Specification.new do |s| s.add_development_dependency "mocha" s.add_development_dependency "turn" s.add_development_dependency "yard" + s.add_development_dependency "oj" unless defined?(JRUBY_VERSION) s.add_development_dependency "ruby-prof" unless defined?(JRUBY_VERSION) s.add_development_dependency "pry" From 3541fce71dcdfec0dce255be68dab9aed04ab4ba Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Thu, 26 Jul 2018 19:03:20 +0200 Subject: [PATCH 374/582] [STORE] Remove Elasticsearch::Persistence::Model (ActiveRecord persistence pattern) (#812) --- README.md | 14 - elasticsearch-persistence/README.md | 259 +------- .../lib/elasticsearch/persistence/model.rb | 135 ---- .../elasticsearch/persistence/model/base.rb | 87 --- .../elasticsearch/persistence/model/errors.rb | 8 - .../elasticsearch/persistence/model/find.rb | 180 ------ .../elasticsearch/persistence/model/rails.rb | 47 -- .../elasticsearch/persistence/model/store.rb | 254 -------- .../elasticsearch/persistence/model/utils.rb | 0 .../elasticsearch/model/model_generator.rb | 21 - .../elasticsearch/model/templates/model.rb.tt | 9 - .../generators/elasticsearch_generator.rb | 2 - .../integration/model/model_basic_test.rb | 233 ------- .../test/unit/model_base_test.rb | 72 --- .../test/unit/model_find_test.rb | 153 ----- .../test/unit/model_gateway_test.rb | 101 --- .../test/unit/model_rails_test.rb | 112 ---- .../test/unit/model_store_test.rb | 576 ------------------ .../rails/instrumentation/railtie.rb | 4 - .../lib/elasticsearch/rails/lograge.rb | 4 - 20 files changed, 2 insertions(+), 2269 deletions(-) delete mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/model.rb delete mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb delete mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/model/errors.rb delete mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb delete mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb delete mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb delete mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/model/utils.rb delete mode 100644 elasticsearch-persistence/lib/rails/generators/elasticsearch/model/model_generator.rb delete mode 100644 elasticsearch-persistence/lib/rails/generators/elasticsearch/model/templates/model.rb.tt delete mode 100644 elasticsearch-persistence/lib/rails/generators/elasticsearch_generator.rb delete mode 100644 elasticsearch-persistence/test/integration/model/model_basic_test.rb delete mode 100644 elasticsearch-persistence/test/unit/model_base_test.rb delete mode 100644 elasticsearch-persistence/test/unit/model_find_test.rb delete mode 100644 elasticsearch-persistence/test/unit/model_gateway_test.rb delete mode 100644 elasticsearch-persistence/test/unit/model_rails_test.rb delete mode 100644 elasticsearch-persistence/test/unit/model_store_test.rb diff --git a/README.md b/README.md index f7c8e1151..ef1a8476e 100644 --- a/README.md +++ b/README.md @@ -98,20 +98,6 @@ repository.save Article.new(title: 'Test') # => {"_index"=>"repository", "_type"=>"article", "_id"=>"Ak75E0U9Q96T5Y999_39NA", ...} ``` -Example of using Elasticsearch as a persistence layer for a Ruby model: - -```ruby -require 'elasticsearch/persistence/model' -class Article - include Elasticsearch::Persistence::Model - attribute :title, String, mapping: { analyzer: 'snowball' } -end - -Article.create title: 'Test' -# POST http://localhost:9200/articles/article -# => #<Article {title: "Test", id: "lUOQ9lhHToWa7oYPxwjqPQ", ...}> -``` - **Please refer to each library documentation for detailed information and examples.** ### Model diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index 4c6dead10..e95a7f005 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -38,7 +38,6 @@ or install it from a source code checkout: The library provides two different patterns for adding persistence to your Ruby objects: * [Repository Pattern](#the-repository-pattern) -* [ActiveRecord Pattern](#the-activerecord-pattern) ### The Repository Pattern @@ -445,262 +444,8 @@ and demonstrates a rich set of features: ### The ActiveRecord Pattern -The `Elasticsearch::Persistence::Model` module provides an implementation of the -active record [pattern](http://www.martinfowler.com/eaaCatalog/activeRecord.html), -with a familiar interface for using Elasticsearch as a persistence layer in -Ruby on Rails applications. - -All the methods are documented with comprehensive examples in the source code, -available also online at <http://rubydoc.info/gems/elasticsearch-persistence/Elasticsearch/Persistence/Model>. - -#### Installation/Usage - -To use the library in a Rails application, add it to your `Gemfile` with a `require` statement: - -```ruby -gem "elasticsearch-persistence", require: 'elasticsearch/persistence/model' -``` - -To use the library without Bundler, install it, and require the file: - -```bash -gem install elasticsearch-persistence -``` - -```ruby -# In your code -require 'elasticsearch/persistence/model' -``` - -#### Model Definition - -The integration is implemented by including the module in a Ruby class. -The model attribute definition support is implemented with the -[_Virtus_](https://github.com/solnic/virtus) Rubygem, and the -naming, validation, etc. features with the -[_ActiveModel_](https://github.com/rails/rails/tree/master/activemodel) Rubygem. - -```ruby -class Article - include Elasticsearch::Persistence::Model - - # Define a plain `title` attribute - # - attribute :title, String - - # Define an `author` attribute, with multiple analyzers for this field - # - attribute :author, String, mapping: { fields: { - author: { type: 'text'}, - raw: { type: 'keyword' } - } } - - - # Define a `views` attribute, with default value - # - attribute :views, Integer, default: 0, mapping: { type: 'integer' } - - # Validate the presence of the `title` attribute - # - validates :title, presence: true - - # Execute code after saving the model. - # - after_save { puts "Successfully saved: #{self}" } -end -``` - -Attribute validations work like for any other _ActiveModel_-compatible implementation: - -```ruby -article = Article.new # => #<Article { ... }> - -article.valid? -# => false - -article.errors.to_a -# => ["Title can't be blank"] -``` - -#### Persistence - -We can create a new article in the database... - -```ruby -Article.create id: 1, title: 'Test', author: 'John' -# PUT http://localhost:9200/articles/article/1 [status:201, request:0.015s, query:n/a] -``` - -... and find it: - -```ruby -article = Article.find(1) -# => #<Article { ... }> - -article._index -# => "articles" - -article.id -# => "1" - -article.title -# => "Test" -``` - -To update the model, either update the attribute and save the model: - -```ruby -article.title = 'Updated' - -article.save -# => {"_index"=>"articles", "_type"=>"article", "_id"=>"1", "_version"=>2, "created"=>false} -``` - -... or use the `update_attributes` method: - -```ruby -article.update_attributes title: 'Test', author: 'Mary' -# => {"_index"=>"articles", "_type"=>"article", "_id"=>"1", "_version"=>3} -``` - -The implementation supports the familiar interface for updating model timestamps: - -```ruby -article.touch -# => => { ... "_version"=>4} -``` - -... and numeric attributes: - -```ruby -article.views -# => 0 - -article.increment :views -article.views -# => 1 -``` - -Any callbacks defined in the model will be triggered during the persistence operations: - -```ruby -article.save -# Successfully saved: #<Article {...}> -``` - -The model also supports familiar `find_in_batches` and `find_each` methods to efficiently -retrieve big collections of model instances, using the Elasticsearch's _Scan API_: - -```ruby -Article.find_each(_source_include: 'title') { |a| puts "===> #{a.title.upcase}" } -# GET http://localhost:9200/articles/article/_search?scroll=5m&size=20 -# GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhb... -# ===> TEST -# GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhb... -# => "c2Nhb..." -``` - -#### Search - -The model class provides a `search` method to retrieve model instances with a regular -search definition, including highlighting, aggregations, etc: - -```ruby -results = Article.search query: { match: { title: 'test' } }, - aggregations: { authors: { terms: { field: 'author.raw' } } }, - highlight: { fields: { title: {} } } - -puts results.first.title -# Test - -puts results.first.hit.highlight['title'] -# <em>Test</em> - -puts results.response.aggregations.authors.buckets.each { |b| puts "#{b['key']} : #{b['doc_count']}" } -# John : 1 -``` - -#### The Elasticsearch Client - -The module will set up a [client](https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch), -connected to `localhost:9200`, by default. - -To use a client with different configuration: - -```ruby -Elasticsearch::Persistence.client = Elasticsearch::Client.new log: true -``` - -To set up a specific client for a specific model: - -```ruby -Article.gateway.client = Elasticsearch::Client.new host: 'api.server.org' -``` - -You might want to do this during you application bootstrap process, e.g. in a Rails initializer. - -Please refer to the -[`elasticsearch-transport`](https://github.com/elasticsearch/elasticsearch-ruby/tree/master/elasticsearch-transport) -library documentation for all the configuration options, and to the -[`elasticsearch-api`](http://rubydoc.info/gems/elasticsearch-api) library documentation -for information about the Ruby client API. - -#### Accessing the Repository Gateway and the Client - -The integration with Elasticsearch is implemented by embedding the repository object in the model. -You can access it through the `gateway` method: - -```ruby -Artist.gateway.client.info -# GET http://localhost:9200/ [status:200, request:0.011s, query:n/a] -# => {"status"=>200, "name"=>"Lightspeed", ...} -``` - -#### Rails Compatibility - -The model instances are fully compatible with Rails' conventions and helpers: - -```ruby -url_for article -# => "http://localhost:3000/articles/1" - -div_for article -# => '<div class="article" id="article_1"></div>' -``` - -... as well as form values for dates and times: - -```ruby -article = Article.new "title" => "Date", "published(1i)"=>"2014", "published(2i)"=>"1", "published(3i)"=>"1" - -article.published.iso8601 -# => "2014-01-01" -``` - -The library provides a Rails ORM generator to facilitate building the application scaffolding: - -```bash -rails generate scaffold Person name:String email:String birthday:Date --orm=elasticsearch -``` - -#### Example application - -A fully working Ruby on Rails application can be generated with the following command: - -```bash -rails new music --force --skip --skip-bundle --skip-active-record --template https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-persistence/examples/music/template.rb -``` - -The application demonstrates: - -* How to set up model attributes with custom mappings -* How to define model relationships with Elasticsearch's parent/child -* How to configure models to use a common index, and create the index with proper mappings -* How to use Elasticsearch's completion suggester to drive auto-complete functionality -* How to use Elasticsearch-persisted models in Rails' views and forms -* How to write controller tests - -The source files for the application are available in the [`examples/music`](examples/music) folder. +The ActiveRecord pattern has been deprecated as of version 6.0.0 of this gem. Please use the +[Repository Pattern](#the-repository-pattern) instead. ## License diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb deleted file mode 100644 index f6cd50eb0..000000000 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model.rb +++ /dev/null @@ -1,135 +0,0 @@ -require 'active_support/core_ext/module/delegation' - -require 'active_model' -require 'virtus' - -require 'elasticsearch/persistence' -require 'elasticsearch/persistence/model/base' -require 'elasticsearch/persistence/model/errors' -require 'elasticsearch/persistence/model/store' -require 'elasticsearch/persistence/model/find' - -module Elasticsearch - module Persistence - - # When included, extends a plain Ruby class with persistence-related features via the ActiveRecord pattern - # - # @example Include the repository in a custom class - # - # require 'elasticsearch/persistence/model' - # - # class MyObject - # include Elasticsearch::Persistence::Model - # end - # - module Model - def self.included(base) - base.class_eval do - include ActiveModel::Naming - include ActiveModel::Conversion - include ActiveModel::Serialization - include ActiveModel::Serializers::JSON - include ActiveModel::Validations - - include Virtus.model - - extend ActiveModel::Callbacks - define_model_callbacks :create, :save, :update, :destroy - define_model_callbacks :find, :touch, only: :after - - include Elasticsearch::Persistence::Model::Base::InstanceMethods - - extend Elasticsearch::Persistence::Model::Store::ClassMethods - include Elasticsearch::Persistence::Model::Store::InstanceMethods - - extend Elasticsearch::Persistence::Model::Find::ClassMethods - - class << self - # Re-define the Virtus' `attribute` method, to configure Elasticsearch mapping as well - # - def attribute(name, type=nil, options={}, &block) - mapping = options.delete(:mapping) || {} - super - - gateway.mapping do - indexes name, {type: Utils::lookup_type(type)}.merge(mapping) - end - - gateway.mapping(&block) if block_given? - end - - # Return the {Repository::Class} instance - # - def gateway(&block) - @gateway ||= Elasticsearch::Persistence::Repository::Class.new host: self - block.arity < 1 ? @gateway.instance_eval(&block) : block.call(@gateway) if block_given? - @gateway - end - - # Delegate methods to repository - # - delegate :settings, - :mappings, - :mapping, - :document_type=, - :index_name, - :index_name=, - :find, - :exists?, - :create_index!, - :refresh_index!, - to: :gateway - - # forward document type to mappings when set - def document_type(type = nil) - return gateway.document_type unless type - gateway.document_type type - mapping.type = type - end - end - - # Configure the repository based on the model (set up index_name, etc) - # - gateway do - klass base - index_name base.model_name.collection.gsub(/\//, '-') - document_type base.model_name.element - - def serialize(document) - document.to_hash.except(:id, 'id') - end - - def deserialize(document) - object = klass.new document['_source'] - - # Set the meta attributes when fetching the document from Elasticsearch - # - object.instance_variable_set :@_id, document['_id'] - object.instance_variable_set :@_index, document['_index'] - object.instance_variable_set :@_type, document['_type'] - object.instance_variable_set :@_version, document['_version'] - object.instance_variable_set :@_source, document['_source'] - - # Store the "hit" information (highlighting, score, ...) - # - object.instance_variable_set :@hit, - Elasticsearch::Model::HashWrapper.new(document.except('_index', '_type', '_id', '_version', '_source')) - - object.instance_variable_set(:@persisted, true) - object - end - end - - # Set up common attributes - # - attribute :created_at, Time, default: lambda { |o,a| Time.now.utc } - attribute :updated_at, Time, default: lambda { |o,a| Time.now.utc } - - attr_reader :hit - end - - end - end - - end -end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb deleted file mode 100644 index a0af32411..000000000 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/base.rb +++ /dev/null @@ -1,87 +0,0 @@ -module Elasticsearch - module Persistence - module Model - # This module contains the base interface for models - # - module Base - module InstanceMethods - - # Model initializer sets the `@id` variable if passed - # - def initialize(attributes={}) - @_id = attributes[:id] || attributes['id'] - super - end - - # Return model attributes as a Hash, merging in the `id` - # - def attributes - super.merge id: id - end - - # Return the document `_id` - # - def id - @_id - end; alias :_id :id - - # Set the document `_id` - # - def id=(value) - @_id = value - end; alias :_id= :id= - - # Return the document `_index` - # - def _index - @_index - end - - # Return the document `_type` - # - def _type - @_type - end - - # Return the document `_version` - # - def _version - @_version - end - - # Return the raw document `_source` - # - def _source - @_source - end - - def to_s - "#<#{self.class} #{attributes.to_hash.inspect.gsub(/:(\w+)=>/, '\1: ')}>" - end; alias :inspect :to_s - end - end - - # Utility methods for {Elasticsearch::Persistence::Model} - # - module Utils - - # Return Elasticsearch type based on passed Ruby class (used in the `attribute` method) - # - def lookup_type(type) - case - when type == String - 'text' - when type == Integer - 'integer' - when type == Float - 'float' - when type == Date || type == Time || type == DateTime - 'date' - when type == Virtus::Attribute::Boolean - 'boolean' - end - end; module_function :lookup_type - end - end - end -end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/errors.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/errors.rb deleted file mode 100644 index 3d8ebb88e..000000000 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/errors.rb +++ /dev/null @@ -1,8 +0,0 @@ -module Elasticsearch - module Persistence - module Model - class DocumentNotSaved < StandardError; end - class DocumentNotPersisted < StandardError; end - end - end -end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb deleted file mode 100644 index c09c9ac38..000000000 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/find.rb +++ /dev/null @@ -1,180 +0,0 @@ -module Elasticsearch - module Persistence - module Model - - module Find - class SearchRequest < Elasticsearch::Model::Searching::SearchRequest - def execute! - klass.gateway.search(definition[:body] || definition[:q], options) - end - end - - module ClassMethods - - def search(query_or_definition, options={}) - SearchRequest.new(self, query_or_definition, options).execute! - end - - # Returns all models (up to 10,000) - # - # @example Retrieve all people - # - # Person.all - # # => [#<Person:0x007ff1d8fb04b0 ... ] - # - # @example Retrieve all people matching a query - # - # Person.all query: { match: { last_name: 'Smith' } } - # # => [#<Person:0x007ff1d8fb04b0 ... ] - # - def all(query={ query: { match_all: {} } }, options={}) - query[:size] ||= 10_000 - search(query, options) - end - - # Returns the number of models - # - # @example Return the count of all models - # - # Person.count - # # => 2 - # - # @example Return the count of models matching a simple query - # - # Person.count('fox or dog') - # # => 1 - # - # @example Return the count of models matching a query in the Elasticsearch DSL - # - # Person.search(query: { match: { title: 'fox dog' } }) - # # => 1 - # - # @return [Integer] - # - def count(query_or_definition=nil, options={}) - gateway.count( query_or_definition, options ) - end - - # Returns all models efficiently via the Elasticsearch's scroll API - # - # You can restrict the models being returned with a query. - # - # The {http://rubydoc.info/gems/elasticsearch-api/Elasticsearch/API/Actions#search-instance_method Search API} - # options are passed to the search method as parameters, all remaining options are passed - # as the `:body` parameter. - # - # The full {Persistence::Repository::Response::Results} instance is yielded to the passed - # block in each batch, so you can access any of its properties; calling `to_a` will - # convert the object to an Array of model instances. - # - # @example Return all models in batches of 20 x number of primary shards - # - # Person.find_in_batches { |batch| puts batch.map(&:name) } - # - # @example Return all models in batches of 100 x number of primary shards - # - # Person.find_in_batches(size: 100) { |batch| puts batch.map(&:name) } - # - # @example Return all models matching a specific query - # - # Person.find_in_batches(query: { match: { name: 'test' } }) { |batch| puts batch.map(&:name) } - # - # @example Return all models, fetching only the `name` attribute from Elasticsearch - # - # Person.find_in_batches( _source_include: 'name') { |_| puts _.response.hits.hits.map(&:to_hash) } - # - # @example Leave out the block to return an Enumerator instance - # - # Person.find_in_batches(size: 100).map { |batch| batch.size } - # # => [100, 100, 100, ... ] - # - # @return [String,Enumerator] The `scroll_id` for the request or Enumerator when the block is not passed - # - def find_in_batches(options={}, &block) - return to_enum(:find_in_batches, options) unless block_given? - - search_params = options.extract!( - :index, - :type, - :scroll, - :size, - :explain, - :ignore_indices, - :ignore_unavailable, - :allow_no_indices, - :expand_wildcards, - :preference, - :q, - :routing, - :source, - :_source, - :_source_include, - :_source_exclude, - :stats, - :timeout) - - scroll = search_params.delete(:scroll) || '5m' - - body = options - - # Get the initial batch of documents and the scroll_id - # - response = gateway.client.search( { - index: gateway.index_name, - type: gateway.document_type, - scroll: scroll, - sort: ['_doc'], - size: 20, - body: body }.merge(search_params) ) - - - # Scroll the search object and break when receiving an empty array of hits - # - while response['hits']['hits'].any? do - yield Repository::Response::Results.new(gateway, response) - - response = gateway.client.scroll( { scroll_id: response['_scroll_id'], scroll: scroll } ) - end - - return response['_scroll_id'] - end - - # Iterate effectively over models using the `find_in_batches` method. - # - # All the options are passed to `find_in_batches` and each result is yielded to the passed block. - # - # @example Print out the people's names by scrolling through the index - # - # Person.find_each { |person| puts person.name } - # - # # # GET http://localhost:9200/people/person/_search?scroll=5m&size=20 - # # # GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhbj... - # # Test 0 - # # Test 1 - # # Test 2 - # # ... - # # # GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhbj... - # # Test 20 - # # Test 21 - # # Test 22 - # - # @example Leave out the block to return an Enumerator instance - # - # Person.find_each.select { |person| person.name =~ /John/ } - # # => => [#<Person {id: "NkltJP5vRxqk9_RMP7SU8Q", name: "John Smith", ...}>] - # - # @return [String,Enumerator] The `scroll_id` for the request or Enumerator when the block is not passed - # - def find_each(options = {}) - return to_enum(:find_each, options) unless block_given? - - find_in_batches(options) do |batch| - batch.each { |result| yield result } - end - end - end - end - - end - end -end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb deleted file mode 100644 index 5ed510e21..000000000 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/rails.rb +++ /dev/null @@ -1,47 +0,0 @@ -module Elasticsearch - module Persistence - module Model - - # Make the `Persistence::Model` models compatible with Ruby On Rails applications - # - module Rails - def self.included(base) - base.class_eval do - - def initialize(attributes={}) - super(__convert_rails_dates(attributes)) - end - - def update(attributes={}, options={}) - super(__convert_rails_dates(attributes), options) - end - end - end - - # Decorates the passed in `attributes` so they extract the date & time values from Rails forms - # - # @example Correctly combine the date and time to a datetime string - # - # params = { "published_on(1i)"=>"2014", - # "published_on(2i)"=>"1", - # "published_on(3i)"=>"1", - # "published_on(4i)"=>"12", - # "published_on(5i)"=>"00" - # } - # MyRailsModel.new(params).published_on.iso8601 - # # => "2014-01-01T12:00:00+00:00" - # - def __convert_rails_dates(attributes={}) - day = attributes.select { |p| p =~ /\([1-3]/ }.reduce({}) { |sum, item| (sum[item.first.gsub(/\(.+\)/, '')] ||= '' )<< item.last+'-'; sum } - time = attributes.select { |p| p =~ /\([4-6]/ }.reduce({}) { |sum, item| (sum[item.first.gsub(/\(.+\)/, '')] ||= '' )<< item.last+':'; sum } - unless day.empty? - attributes.update day.reduce({}) { |sum, item| sum[item.first] = item.last; sum[item.first] += ' ' + time[item.first] unless time.empty?; sum } - end - - return attributes - end; module_function :__convert_rails_dates - end - - end - end -end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb deleted file mode 100644 index a6c44c998..000000000 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/model/store.rb +++ /dev/null @@ -1,254 +0,0 @@ -module Elasticsearch - module Persistence - module Model - - # This module contains the storage related features of {Elasticsearch::Persistence::Model} - # - module Store - module ClassMethods #:nodoc: - - # Creates a class instance, saves it, if validations pass, and returns it - # - # @example Create a new person - # - # Person.create name: 'John Smith' - # # => #<Person:0x007f889e302b30 ... @id="bG7yQDAXRhCi3ZfVcx6oAA", @name="John Smith" ...> - # - # @return [Object] The model instance - # - def create(attributes, options={}) - object = self.new(attributes) - object.run_callbacks :create do - object.save(options) - object - end - end - end - - module InstanceMethods - - # Saves the model (if validations pass) and returns the response (or `false`) - # - # @example Save a valid model instance - # - # p = Person.new(name: 'John') - # p.save - # => {"_index"=>"people", ... "_id"=>"RzFSXFR0R8u1CZIWNs2Gvg", "_version"=>1, "created"=>true} - # - # @example Save an invalid model instance - # - # p = Person.new(name: nil) - # p.save - # # => false - # - # @return [Hash,FalseClass] The Elasticsearch response as a Hash or `false` - # - def save(options={}) - unless options.delete(:validate) == false - return false unless valid? - end - - run_callbacks :save do - options.update id: self.id - options.update index: self._index if self._index - options.update type: self._type if self._type - - self[:updated_at] = Time.now.utc - - response = self.class.gateway.save(self, options) - - @_id = response['_id'] - @_index = response['_index'] - @_type = response['_type'] - @_version = response['_version'] - @persisted = true - - response - end - end - - # Deletes the model from Elasticsearch (if it's persisted), freezes it, and returns the response - # - # @example Delete a model instance - # - # p.destroy - # => {"_index"=>"people", ... "_id"=>"RzFSXFR0R8u1CZIWNs2Gvg", "_version"=>2 ...} - # - # @return [Hash] The Elasticsearch response as a Hash - # - def destroy(options={}) - raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? - - run_callbacks :destroy do - options.update index: self._index if self._index - options.update type: self._type if self._type - - response = self.class.gateway.delete(self.id, options) - - @destroyed = true - @persisted = false - self.freeze - response - end - end; alias :delete :destroy - - # Updates the model (via Elasticsearch's "Update" API) and returns the response - # - # @example Update a model with partial attributes - # - # p.update name: 'UPDATED' - # => {"_index"=>"people", ... "_version"=>2} - # - # @example Pass a version for concurrency control - # - # p.update( { name: 'UPDATED' }, { version: 2 } ) - # => {"_index"=>"people", ... "_version"=>3} - # - # @example An exception is raised when the version doesn't match - # - # p.update( { name: 'UPDATED' }, { version: 2 } ) - # => Elasticsearch::Transport::Transport::Errors::Conflict: [409] {"error" ... } - # - # @return [Hash] The Elasticsearch response as a Hash - # - def update(attributes={}, options={}) - unless options.delete(:validate) == false - return false unless valid? - end - raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? - - run_callbacks :update do - options.update index: self._index if self._index - options.update type: self._type if self._type - - attributes.update( { updated_at: Time.now.utc } ) - response = self.class.gateway.update(self.id, { doc: attributes}.merge(options)) - - self.attributes = self.attributes.merge(attributes) - @_index = response['_index'] - @_type = response['_type'] - @_version = response['_version'] - - response - end - end; alias :update_attributes :update - - # Increments a numeric attribute (via Elasticsearch's "Update" API) and returns the response - # - # @example Increment the `salary` attribute by 1 - # - # p.increment :salary - # - # @example Increment the `salary` attribute by 100 - # - # p.increment :salary, 100 - # - # @return [Hash] The Elasticsearch response as a Hash - # - def increment(attribute, value=1, options={}) - raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? - - options.update index: self._index if self._index - options.update type: self._type if self._type - - response = self.class.gateway.update(self.id, { script: "ctx._source.#{attribute} += #{value}"}.merge(options)) - - self[attribute] += value - - @_index = response['_index'] - @_type = response['_type'] - @_version = response['_version'] - - response - end - - # Decrements a numeric attribute (via Elasticsearch's "Update" API) and returns the response - # - # @example Decrement the `salary` attribute by 1 - # - # p.decrement :salary - # - # @example Decrement the `salary` attribute by 100 - # - # p.decrement :salary, 100 - # - # @return [Hash] The Elasticsearch response as a Hash - # - def decrement(attribute, value=1, options={}) - raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? - - options.update index: self._index if self._index - options.update type: self._type if self._type - - response = self.class.gateway.update(self.id, { script: "ctx._source.#{attribute} = ctx._source.#{attribute} - #{value}"}.merge(options)) - self[attribute] -= value - - @_index = response['_index'] - @_type = response['_type'] - @_version = response['_version'] - - response - end - - # Updates the `updated_at` attribute, saves the model and returns the response - # - # @example Update the `updated_at` attribute (default) - # - # p.touch - # - # @example Update a custom attribute: `saved_on` - # - # p.touch :saved_on - # - # @return [Hash] The Elasticsearch response as a Hash - # - def touch(attribute=:updated_at, options={}) - raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted? - raise ArgumentError, "Object does not have '#{attribute}' attribute" unless respond_to?(attribute) - - run_callbacks :touch do - options.update index: self._index if self._index - options.update type: self._type if self._type - - value = Time.now.utc - response = self.class.gateway.update(self.id, { doc: { attribute => value.iso8601 }}.merge(options)) - - self[attribute] = value - - @_index = response['_index'] - @_type = response['_type'] - @_version = response['_version'] - - response - end - end - - # Returns true when the model has been destroyed, false otherwise - # - # @return [TrueClass,FalseClass] - # - def destroyed? - !!@destroyed - end - - # Returns true when the model has been already saved to the database, false otherwise - # - # @return [TrueClass,FalseClass] - # - def persisted? - !!@persisted && !destroyed? - end - - # Returns true when the model has not been saved yet, false otherwise - # - # @return [TrueClass,FalseClass] - # - def new_record? - !persisted? && !destroyed? - end - end - end - - end - end -end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/model/utils.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/model/utils.rb deleted file mode 100644 index e69de29bb..000000000 diff --git a/elasticsearch-persistence/lib/rails/generators/elasticsearch/model/model_generator.rb b/elasticsearch-persistence/lib/rails/generators/elasticsearch/model/model_generator.rb deleted file mode 100644 index 84fb632cd..000000000 --- a/elasticsearch-persistence/lib/rails/generators/elasticsearch/model/model_generator.rb +++ /dev/null @@ -1,21 +0,0 @@ -require "rails/generators/elasticsearch_generator" - -module Elasticsearch - module Generators - class ModelGenerator < ::Rails::Generators::NamedBase - source_root File.expand_path('../templates', __FILE__) - - desc "Creates an Elasticsearch::Persistence model" - argument :attributes, type: :array, default: [], banner: "attribute:type attribute:type" - - check_class_collision - - def create_model_file - @padding = attributes.map { |a| a.name.size }.max - template "model.rb.tt", File.join("app/models", class_path, "#{file_name}.rb") - end - - hook_for :test_framework - end - end -end diff --git a/elasticsearch-persistence/lib/rails/generators/elasticsearch/model/templates/model.rb.tt b/elasticsearch-persistence/lib/rails/generators/elasticsearch/model/templates/model.rb.tt deleted file mode 100644 index 0597174da..000000000 --- a/elasticsearch-persistence/lib/rails/generators/elasticsearch/model/templates/model.rb.tt +++ /dev/null @@ -1,9 +0,0 @@ -<% module_namespacing do -%> -class <%= class_name %> - include Elasticsearch::Persistence::Model - -<% attributes.each do |attribute| -%> - <%= "attribute :#{attribute.name},".ljust(@padding+12) %> <%= attribute.type %> -<% end -%> -end -<% end -%> diff --git a/elasticsearch-persistence/lib/rails/generators/elasticsearch_generator.rb b/elasticsearch-persistence/lib/rails/generators/elasticsearch_generator.rb deleted file mode 100644 index 20072ed93..000000000 --- a/elasticsearch-persistence/lib/rails/generators/elasticsearch_generator.rb +++ /dev/null @@ -1,2 +0,0 @@ -require "rails/generators/named_base" -require "rails/generators/active_model" diff --git a/elasticsearch-persistence/test/integration/model/model_basic_test.rb b/elasticsearch-persistence/test/integration/model/model_basic_test.rb deleted file mode 100644 index 33555c980..000000000 --- a/elasticsearch-persistence/test/integration/model/model_basic_test.rb +++ /dev/null @@ -1,233 +0,0 @@ -require 'test_helper' - -require 'elasticsearch/persistence/model' -require 'elasticsearch/persistence/model/rails' - -module Elasticsearch - module Persistence - class PersistenceModelBasicIntegrationTest < Elasticsearch::Test::IntegrationTestCase - - class ::Person - include Elasticsearch::Persistence::Model - include Elasticsearch::Persistence::Model::Rails - - settings index: { number_of_shards: 1 } - document_type 'human_being' - - attribute :name, String, - mapping: { fields: { - name: { type: 'text', analyzer: 'snowball' }, - raw: { type: 'keyword' } - } } - - attribute :birthday, Date - attribute :department, String - attribute :salary, Integer - attribute :admin, Boolean, default: false - - validates :name, presence: true - end - - context "A basic persistence model" do - setup do - Person.create_index! force: true - end - - should "save the object with custom ID" do - person = Person.new id: 1, name: 'Number One' - person.save - - document = Person.find(1) - assert_not_nil document - assert_equal 'Number One', document.name - end - - should "create the object with custom ID" do - person = Person.create id: 1, name: 'Number One' - - document = Person.find(1) - assert_not_nil document - assert_equal 'Number One', document.name - end - - should "save and find the object" do - person = Person.new name: 'John Smith', birthday: Date.parse('1970-01-01') - assert person.save - - assert_not_nil person.id - document = Person.find(person.id) - - assert_instance_of Person, document - assert_equal 'John Smith', document.name - assert_equal 'John Smith', Person.find(person.id).name - - assert_not_nil Elasticsearch::Persistence.client.get index: 'people', type: 'human_being', id: person.id - end - - should "not save an invalid object" do - person = Person.new name: nil - assert ! person.save - end - - should "save an invalid object with the :validate option" do - person = Person.new name: nil, salary: 100 - assert person.save validate: false - - assert_not_nil person.id - document = Person.find(person.id) - assert_equal 100, document.salary - end - - should "delete the object" do - person = Person.create name: 'John Smith', birthday: Date.parse('1970-01-01') - - person.destroy - assert person.frozen? - - assert_raise Elasticsearch::Transport::Transport::Errors::NotFound do - Elasticsearch::Persistence.client.get index: 'people', type: 'person', id: person.id - end - end - - should "update an object attribute" do - person = Person.create name: 'John Smith' - - person.update name: 'UPDATED' - - assert_equal 'UPDATED', person.name - assert_equal 'UPDATED', Person.find(person.id).name - end - - should "create the model with correct Date form Rails' form attributes" do - params = { "birthday(1i)"=>"2014", - "birthday(2i)"=>"1", - "birthday(3i)"=>"1" - } - person = Person.create params.merge(name: 'TEST') - - assert_equal Date.parse('2014-01-01'), person.birthday - assert_equal Date.parse('2014-01-01'), Person.find(person.id).birthday - end - - should_eventually "update the model with correct Date form Rails' form attributes" do - params = { "birthday(1i)"=>"2014", - "birthday(2i)"=>"1", - "birthday(3i)"=>"1" - } - person = Person.create params.merge(name: 'TEST') - - person.update params.merge('birthday(1i)' => '2015') - - assert_equal Date.parse('2015-01-01'), person.birthday - assert_equal Date.parse('2015-01-01'), Person.find(person.id).birthday - end - - should "increment an object attribute" do - person = Person.create name: 'John Smith', salary: 1_000 - - person.increment :salary - - assert_equal 1_001, person.salary - assert_equal 1_001, Person.find(person.id).salary - end - - should "update the object timestamp" do - person = Person.create name: 'John Smith' - updated_at = person.updated_at - - sleep 1 - person.touch - - assert person.updated_at > updated_at, [person.updated_at, updated_at].inspect - - found = Person.find(person.id) - assert found.updated_at > updated_at, [found.updated_at, updated_at].inspect - end - - should 'update the object timestamp on save' do - person = Person.create name: 'John Smith' - person.admin = true - sleep 1 - person.save - - Person.gateway.refresh_index! - - found = Person.find(person.id) - - # Compare without milliseconds - assert_equal person.updated_at.to_i, found.updated_at.to_i - end - - should "respect the version" do - person = Person.create name: 'John Smith' - - person.update( { name: 'UPDATE 1' }) - - assert_raise Elasticsearch::Transport::Transport::Errors::Conflict do - person.update( { name: 'UPDATE 2' }, { version: 1 } ) - end - end - - should "find all instances" do - Person.create name: 'John Smith' - Person.create name: 'Mary Smith' - Person.gateway.refresh_index! - - people = Person.all - - assert_equal 2, people.total - assert_equal 2, people.size - end - - should "find instances by search" do - Person.create name: 'John Smith' - Person.create name: 'Mary Smith' - Person.gateway.refresh_index! - - people = Person.search query: { match: { name: 'smith' } }, - highlight: { fields: { name: {} } } - - assert_equal 2, people.total - assert_equal 2, people.size - - assert people.map_with_hit { |o,h| h._score }.all? { |s| s > 0 } - - assert_not_nil people.first.hit - assert_match /smith/i, people.first.hit.highlight['name'].first - end - - should "find instances in batches" do - 50.times { |i| Person.create name: "John #{i+1}" } - Person.gateway.refresh_index! - - @batches = 0 - @results = [] - - Person.find_in_batches(_source_include: 'name') do |batch| - @batches += 1 - @results += batch.map(&:name) - end - - assert_equal 3, @batches - assert_equal 50, @results.size - assert_contains @results, 'John 1' - end - - should "find each instance" do - 50.times { |i| Person.create name: "John #{i+1}" } - Person.gateway.refresh_index! - - @results = [] - - Person.find_each(_source_include: 'name') do |person| - @results << person.name - end - - assert_equal 50, @results.size - assert_contains @results, 'John 1' - end - end - - end - end -end diff --git a/elasticsearch-persistence/test/unit/model_base_test.rb b/elasticsearch-persistence/test/unit/model_base_test.rb deleted file mode 100644 index 2d55e1620..000000000 --- a/elasticsearch-persistence/test/unit/model_base_test.rb +++ /dev/null @@ -1,72 +0,0 @@ -require 'test_helper' - -require 'elasticsearch/persistence/model' -require 'elasticsearch/persistence/model/rails' - -class Elasticsearch::Persistence::ModelBaseTest < Test::Unit::TestCase - context "The model" do - setup do - class DummyBaseModel - include Elasticsearch::Persistence::Model - - attribute :name, String - end - end - - should "respond to id, _id, _index, _type and _version" do - model = DummyBaseModel.new - - [:id, :_id, :_index, :_type, :_version].each { |method| assert_respond_to model, method } - end - - should "set the ID from attributes during initialization" do - model = DummyBaseModel.new id: 1 - assert_equal 1, model.id - - model = DummyBaseModel.new 'id' => 2 - assert_equal 2, model.id - end - - should "set the ID using setter method" do - model = DummyBaseModel.new id: 1 - assert_equal 1, model.id - - model.id = 2 - assert_equal 2, model.id - end - - should "have ID in attributes" do - model = DummyBaseModel.new id: 1, name: 'Test' - assert_equal 1, model.attributes[:id] - end - - should "have the customized inspect method" do - model = DummyBaseModel.new name: 'Test' - assert_match /name\: "Test"/, model.inspect - end - - context "with custom document_type" do - setup do - @model = DummyBaseModel - @gateway = mock() - @mapping = mock() - @model.stubs(:gateway).returns(@gateway) - @gateway.stubs(:mapping).returns(@mapping) - @document_type = 'dummybase' - end - - should "forward the argument to mapping" do - @gateway.expects(:document_type).with(@document_type).once - @mapping.expects(:type=).with(@document_type).once - @model.document_type @document_type - end - - should "return the value from the gateway" do - @gateway.expects(:document_type).once.returns(@document_type) - @mapping.expects(:type=).never - returned_type = @model.document_type - assert_equal @document_type, returned_type - end - end - end -end diff --git a/elasticsearch-persistence/test/unit/model_find_test.rb b/elasticsearch-persistence/test/unit/model_find_test.rb deleted file mode 100644 index 92370c1e4..000000000 --- a/elasticsearch-persistence/test/unit/model_find_test.rb +++ /dev/null @@ -1,153 +0,0 @@ -require 'test_helper' - -require 'active_model' -require 'virtus' - -require 'elasticsearch/persistence/model/errors' -require 'elasticsearch/persistence/model/find' - -class Elasticsearch::Persistence::ModelFindTest < Test::Unit::TestCase - context "The model find module," do - - class DummyFindModel - include ActiveModel::Naming - include ActiveModel::Conversion - include ActiveModel::Serialization - include ActiveModel::Serializers::JSON - include ActiveModel::Validations - - include Virtus.model - - extend Elasticsearch::Persistence::Model::Find::ClassMethods - - extend ActiveModel::Callbacks - define_model_callbacks :create, :save, :update, :destroy - define_model_callbacks :find, :touch, only: :after - - attribute :title, String - attribute :count, Integer, default: 0 - attribute :created_at, DateTime, default: lambda { |o,a| Time.now.utc } - attribute :updated_at, DateTime, default: lambda { |o,a| Time.now.utc } - end - - setup do - @gateway = stub(client: stub(), index_name: 'foo', document_type: 'bar') - DummyFindModel.stubs(:gateway).returns(@gateway) - - DummyFindModel.stubs(:index_name).returns('foo') - DummyFindModel.stubs(:document_type).returns('bar') - - @response = MultiJson.load <<-JSON - { - "took": 14, - "timed_out": false, - "_shards": { - "total": 5, - "successful": 5, - "failed": 0 - }, - "hits": { - "total": 1, - "max_score": 1.0, - "hits": [ - { - "_index": "dummy", - "_type": "dummy", - "_id": "abc123", - "_score": 1.0, - "_source": { - "name": "TEST" - } - } - ] - } - } - JSON - end - - should "find all records" do - DummyFindModel - .stubs(:search) - .with({ query: { match_all: {} }, size: 10_000 }, {}) - .returns(@response) - - DummyFindModel.all - end - - should "pass options when finding all records" do - DummyFindModel - .expects(:search) - .with({ query: { match: { title: 'test' } }, size: 10_000 }, { routing: 'abc123' }) - .returns(@response) - - DummyFindModel.all( { query: { match: { title: 'test' } } }, { routing: 'abc123' } ) - end - - context "finding via scroll" do - setup do - @gateway - .expects(:deserialize) - .returns('_source' => {'foo' => 'bar'}) - .at_least_once - - # 1. Initial batch of documents and the scroll_id - @gateway.client - .expects(:search) - .with do |arguments| - assert_equal 'foo', arguments[:index] - assert_equal 'bar', arguments[:type] - true - end - .returns(MultiJson.load('{"_scroll_id":"abc123==", "hits":{"hits":[{"_source":{"foo":"bar_1"}}]}}')) - - # 2. Second batch of documents and the scroll_id - # 3. Last, empty batch of documents - @gateway.client - .expects(:scroll) - .twice - .returns(MultiJson.load('{"_scroll_id":"abc456==", "hits":{"hits":[{"_source":{"foo":"bar_2"}}]}}')) - .then - .returns(MultiJson.load('{"_scroll_id":"abc789==", "hits":{"hits":[]}}')) - end - - should "find all records in batches" do - @doc = nil - result = DummyFindModel.find_in_batches { |batch| @doc = batch.first['_source']['foo'] } - - assert_equal 'abc789==', result - assert_equal 'bar', @doc - end - - should "return an Enumerator for find in batches" do - @doc = nil - assert_nothing_raised do - e = DummyFindModel.find_in_batches - assert_instance_of Enumerator, e - - e.each { |batch| @doc = batch.first['_source']['foo'] } - assert_equal 'bar', @doc - end - end - - should "find each" do - @doc = nil - result = DummyFindModel.find_each { |doc| @doc = doc['_source']['foo'] } - - assert_equal 'abc789==', result - assert_equal 'bar', @doc - end - - should "return an Enumerator for find each" do - @doc = nil - assert_nothing_raised do - e = DummyFindModel.find_each - assert_instance_of Enumerator, e - - e.each { |doc| @doc = doc['_source']['foo'] } - assert_equal 'bar', @doc - end - end - end - - end -end diff --git a/elasticsearch-persistence/test/unit/model_gateway_test.rb b/elasticsearch-persistence/test/unit/model_gateway_test.rb deleted file mode 100644 index 2451241d6..000000000 --- a/elasticsearch-persistence/test/unit/model_gateway_test.rb +++ /dev/null @@ -1,101 +0,0 @@ -require 'test_helper' - -require 'elasticsearch/persistence/model' -require 'elasticsearch/persistence/model/rails' - -class Elasticsearch::Persistence::ModelGatewayTest < Test::Unit::TestCase - context "The model gateway" do - setup do - class DummyGatewayModel - include Elasticsearch::Persistence::Model - end - end - - teardown do - Elasticsearch::Persistence::ModelGatewayTest.__send__ :remove_const, :DummyGatewayModel \ - rescue NameError; nil - end - - should "be accessible" do - assert_instance_of Elasticsearch::Persistence::Repository::Class, DummyGatewayModel.gateway - - $a = 0 - DummyGatewayModel.gateway { $a += 1 } - assert_equal 1, $a - - @b = 0 - def run!; DummyGatewayModel.gateway { |g| @b += 1 }; end - run! - assert_equal 1, @b - - assert_equal DummyGatewayModel, DummyGatewayModel.gateway.klass - end - - should "define common attributes" do - d = DummyGatewayModel.new - - assert_respond_to d, :updated_at - assert_respond_to d, :created_at - end - - should "allow to configure settings" do - DummyGatewayModel.settings(number_of_shards: 1) - - assert_equal 1, DummyGatewayModel.settings.to_hash[:number_of_shards] - end - - should "allow to configure mappings" do - DummyGatewayModel.mapping { indexes :name, analyzer: 'snowball' } - - assert_equal 'snowball', - DummyGatewayModel.mapping.to_hash[:dummy_gateway_model][:properties][:name][:analyzer] - end - - should "configure the mapping via attribute" do - DummyGatewayModel.attribute :name, String, mapping: { analyzer: 'snowball' } - - assert_respond_to DummyGatewayModel, :name - assert_equal 'snowball', - DummyGatewayModel.mapping.to_hash[:dummy_gateway_model][:properties][:name][:analyzer] - end - - should "configure the mapping via an attribute block" do - DummyGatewayModel.attribute :name, String do - indexes :name, analyzer: 'custom' - end - - assert_respond_to DummyGatewayModel, :name - assert_equal 'custom', - DummyGatewayModel.mapping.to_hash[:dummy_gateway_model][:properties][:name][:analyzer] - end - - should "properly look up types for classes" do - assert_equal 'text', Elasticsearch::Persistence::Model::Utils::lookup_type(String) - assert_equal 'integer', Elasticsearch::Persistence::Model::Utils::lookup_type(Integer) - assert_equal 'float', Elasticsearch::Persistence::Model::Utils::lookup_type(Float) - assert_equal 'date', Elasticsearch::Persistence::Model::Utils::lookup_type(Date) - assert_equal 'boolean', Elasticsearch::Persistence::Model::Utils::lookup_type(Virtus::Attribute::Boolean) - end - - should "remove IDs from hash when serializing" do - assert_equal( {foo: 'bar'}, DummyGatewayModel.gateway.serialize(id: '123', foo: 'bar') ) - end - - should "set IDs from hash when deserializing" do - assert_equal 'abc123', DummyGatewayModel.gateway.deserialize('_id' => 'abc123', '_source' => {}).id - end - - should "set @persisted variable from hash when deserializing" do - assert DummyGatewayModel.gateway.deserialize('_id' => 'abc123', '_source' => {}).instance_variable_get(:@persisted) - end - - should "allow accessing the raw _source" do - assert_equal 'bar', DummyGatewayModel.gateway.deserialize('_source' => { 'foo' => 'bar' })._source['foo'] - end - - should "allow to access the raw hit from results as Hashie::Mash" do - assert_equal 0.42, DummyGatewayModel.gateway.deserialize('_score' => 0.42, '_source' => {}).hit._score - end - - end -end diff --git a/elasticsearch-persistence/test/unit/model_rails_test.rb b/elasticsearch-persistence/test/unit/model_rails_test.rb deleted file mode 100644 index 3f0ce913b..000000000 --- a/elasticsearch-persistence/test/unit/model_rails_test.rb +++ /dev/null @@ -1,112 +0,0 @@ -require 'test_helper' - -require 'elasticsearch/persistence/model' -require 'elasticsearch/persistence/model/rails' - -require 'rails' -require 'action_controller/railtie' -require 'action_view/railtie' - -class ::MyRailsModel - include Elasticsearch::Persistence::Model - include Elasticsearch::Persistence::Model::Rails - - attribute :name, String, mapping: { analyzer: 'english' } - attribute :published_at, DateTime - attribute :published_on, Date -end - -class Application < Rails::Application - config.eager_load = false - config.root = File.dirname(File.expand_path('../../../tmp', __FILE__)) - config.logger = Logger.new($stderr) - - routes.append do - resources :my_rails_models - end -end - -class ApplicationController < ActionController::Base - include Application.routes.url_helpers - include ActionController::UrlFor -end -ApplicationController.default_url_options = { host: 'localhost' } -ApplicationController._routes.append { resources :my_rails_models } - -class MyRailsModelController < ApplicationController; end - -Application.initialize! - -class Elasticsearch::Persistence::ModelRailsTest < Test::Unit::TestCase - context "The model in a Rails application" do - - should "generate proper URLs and paths" do - model = MyRailsModel.new name: 'Test' - model.stubs(:id).returns(1) - model.stubs(:persisted?).returns(true) - - controller = MyRailsModelController.new - controller.request = ActionDispatch::Request.new({}) - - assert_equal 'http://localhost/my_rails_models/1', controller.url_for(model) - assert_equal '/my_rails_models/1/edit', controller.edit_my_rails_model_path(model) - end - - should "generate a link" do - class MyView; include ActionView::Helpers::UrlHelper; end - - model = MyRailsModel.new name: 'Test' - view = MyView.new - view.expects(:url_for).with(model).returns('foo/bar') - - assert_equal '<a href="foo/bar">Show</a>', view.link_to('Show', model) - end - - should "parse DateTime from Rails forms" do - params = { "published_at(1i)"=>"2014", - "published_at(2i)"=>"1", - "published_at(3i)"=>"1", - "published_at(4i)"=>"12", - "published_at(5i)"=>"00" - } - - assert_equal '2014-1-1- 12:00:', - Elasticsearch::Persistence::Model::Rails.__convert_rails_dates(params)['published_at'] - - m = MyRailsModel.new params - assert_equal "2014-01-01T12:00:00+00:00", m.published_at.iso8601 - end - - should "parse Date from Rails forms" do - params = { "published_on(1i)"=>"2014", - "published_on(2i)"=>"1", - "published_on(3i)"=>"1" - } - - assert_equal '2014-1-1-', - Elasticsearch::Persistence::Model::Rails.__convert_rails_dates(params)['published_on'] - - - m = MyRailsModel.new params - assert_equal "2014-01-01", m.published_on.iso8601 - end - - context "when updating," do - should "pass the options to gateway" do - model = MyRailsModel.new name: 'Test' - model.stubs(:persisted?).returns(true) - - model.class.gateway - .expects(:update) - .with do |object, options| - assert_equal 'ABC', options[:routing] - true - end - .returns({'_id' => 'abc123'}) - - assert model.update( { title: 'UPDATED' }, { routing: 'ABC' } ) - end - end - - end -end diff --git a/elasticsearch-persistence/test/unit/model_store_test.rb b/elasticsearch-persistence/test/unit/model_store_test.rb deleted file mode 100644 index 442ab56ee..000000000 --- a/elasticsearch-persistence/test/unit/model_store_test.rb +++ /dev/null @@ -1,576 +0,0 @@ -require 'test_helper' - -require 'active_model' -require 'virtus' - -require 'elasticsearch/persistence/model/base' -require 'elasticsearch/persistence/model/errors' -require 'elasticsearch/persistence/model/store' - -class Elasticsearch::Persistence::ModelStoreTest < Test::Unit::TestCase - context "The model store module," do - - class DummyStoreModel - include ActiveModel::Naming - include ActiveModel::Conversion - include ActiveModel::Serialization - include ActiveModel::Serializers::JSON - include ActiveModel::Validations - - include Virtus.model - - include Elasticsearch::Persistence::Model::Base::InstanceMethods - extend Elasticsearch::Persistence::Model::Store::ClassMethods - include Elasticsearch::Persistence::Model::Store::InstanceMethods - - extend ActiveModel::Callbacks - define_model_callbacks :create, :save, :update, :destroy - define_model_callbacks :find, :touch, only: :after - - attribute :title, String - attribute :count, Integer, default: 0 - attribute :created_at, DateTime, default: lambda { |o,a| Time.now.utc } - attribute :updated_at, DateTime, default: lambda { |o,a| Time.now.utc } - end - - setup do - @shoulda_subject = DummyStoreModel.new title: 'Test' - @gateway = stub - DummyStoreModel.stubs(:gateway).returns(@gateway) - end - - teardown do - Elasticsearch::Persistence::ModelStoreTest.__send__ :remove_const, :DummyStoreModelWithCallback \ - rescue NameError; nil - end - - should "be new_record" do - assert subject.new_record? - end - - context "when creating," do - should "save the object and return it" do - DummyStoreModel.any_instance.expects(:save).returns({'_id' => 'X'}) - - assert_instance_of DummyStoreModel, DummyStoreModel.create(title: 'Test') - end - - should "execute the callbacks" do - DummyStoreModelWithCallback = Class.new(DummyStoreModel) - @gateway.expects(:save).returns({'_id' => 'X'}) - - DummyStoreModelWithCallback.after_create { $stderr.puts "CREATED" } - DummyStoreModelWithCallback.after_save { $stderr.puts "SAVED" } - - $stderr.expects(:puts).with('CREATED') - $stderr.expects(:puts).with('SAVED') - - DummyStoreModelWithCallback.create name: 'test' - end - end - - context "when saving," do - should "save the model" do - @gateway - .expects(:save) - .with do |object, options| - assert_equal subject, object - assert_equal nil, options[:id] - true - end - .returns({'_id' => 'abc123'}) - - assert subject.save - end - - should "save the model and set the ID" do - @gateway - .expects(:save) - .returns({'_id' => 'abc123'}) - - assert_nil subject.id - - subject.save - assert_equal 'abc123', subject.id - end - - should "save the model and update the timestamp" do - now = Time.parse('2014-01-01T00:00:00Z') - Time.expects(:now).returns(now).at_least_once - @gateway - .expects(:save) - .returns({'_id' => 'abc123'}) - - subject.save - assert_equal Time.parse('2014-01-01T00:00:00Z'), subject.updated_at - end - - should "not save an invalid model" do - @gateway - .expects(:save) - .never - - subject.instance_eval do - def valid?; false; end; - end - - assert ! subject.save - assert ! subject.persisted? - end - - should "skip the validation with the :validate option" do - @gateway - .expects(:save) - .with do |object, options| - assert_equal subject, object - assert_equal nil, options[:id] - true - end - .returns({'_id' => 'abc123'}) - - subject.instance_eval do - def valid?; false; end; - end - - assert subject.save validate: false - assert subject.persisted? - end - - should "pass the options to gateway" do - @gateway - .expects(:save) - .with do |object, options| - assert_equal 'ABC', options[:routing] - true - end - .returns({'_id' => 'abc123'}) - - assert subject.save routing: 'ABC' - end - - should "return the response" do - @gateway - .expects(:save) - .returns('FOOBAR') - - assert_equal 'FOOBAR', subject.save - end - - should "execute the callbacks" do - @gateway.expects(:save).returns({'_id' => 'abc'}) - DummyStoreModelWithCallback = Class.new(DummyStoreModel) - - DummyStoreModelWithCallback.after_save { $stderr.puts "SAVED" } - - $stderr.expects(:puts).with('SAVED') - d = DummyStoreModelWithCallback.new name: 'Test' - d.save - end - - should "save the model to its own index" do - @gateway.expects(:save) - .with do |model, options| - assert_equal 'my_custom_index', options[:index] - assert_equal 'my_custom_type', options[:type] - true - end - .returns({'_id' => 'abc'}) - - d = DummyStoreModel.new name: 'Test' - d.instance_variable_set(:@_index, 'my_custom_index') - d.instance_variable_set(:@_type, 'my_custom_type') - d.save - end - - should "set the meta attributes from response" do - @gateway.expects(:save) - .with do |model, options| - assert_equal 'my_custom_index', options[:index] - assert_equal 'my_custom_type', options[:type] - true - end - .returns({'_id' => 'abc', '_index' => 'foo', '_type' => 'bar', '_version' => '100'}) - - d = DummyStoreModel.new name: 'Test' - d.instance_variable_set(:@_index, 'my_custom_index') - d.instance_variable_set(:@_type, 'my_custom_type') - d.save - - assert_equal 'foo', d._index - assert_equal 'bar', d._type - assert_equal '100', d._version - end - end - - context "when destroying," do - should "remove the model from Elasticsearch" do - subject.expects(:persisted?).returns(true) - subject.expects(:id).returns('abc123') - subject.expects(:freeze).returns(subject) - - @gateway - .expects(:delete) - .with('abc123', {}) - .returns({'_id' => 'abc123', 'version' => 2}) - - assert subject.destroy - assert subject.destroyed? - end - - should "pass the options to gateway" do - subject.expects(:persisted?).returns(true) - subject.expects(:freeze).returns(subject) - - @gateway - .expects(:delete) - .with do |object, options| - assert_equal 'ABC', options[:routing] - true - end - .returns({'_id' => 'abc123'}) - - assert subject.destroy routing: 'ABC' - end - - should "return the response" do - subject.expects(:persisted?).returns(true) - subject.expects(:freeze).returns(subject) - - @gateway - .expects(:delete) - .returns('FOOBAR') - - assert_equal 'FOOBAR', subject.destroy - end - - should "execute the callbacks" do - @gateway.expects(:delete).returns({'_id' => 'abc'}) - DummyStoreModelWithCallback = Class.new(DummyStoreModel) - - DummyStoreModelWithCallback.after_destroy { $stderr.puts "DELETED" } - - $stderr.expects(:puts).with('DELETED') - d = DummyStoreModelWithCallback.new name: 'Test' - d.expects(:persisted?).returns(true) - d.expects(:freeze).returns(d) - - d.destroy - end - - should "remove the model from its own index" do - @gateway.expects(:delete) - .with do |model, options| - assert_equal 'my_custom_index', options[:index] - assert_equal 'my_custom_type', options[:type] - true - end - .returns({'_id' => 'abc'}) - - d = DummyStoreModel.new name: 'Test' - d.instance_variable_set(:@_index, 'my_custom_index') - d.instance_variable_set(:@_type, 'my_custom_type') - d.expects(:persisted?).returns(true) - d.expects(:freeze).returns(d) - - d.destroy - end - end - - context "when updating," do - should "update the document with partial attributes" do - subject.expects(:persisted?).returns(true) - subject.expects(:id).returns('abc123').at_least_once - - @gateway - .expects(:update) - .with do |id, options| - assert_equal 'abc123', id - assert_equal 'UPDATED', options[:doc][:title] - true - end - .returns({'_id' => 'abc123', 'version' => 2}) - - assert subject.update title: 'UPDATED' - - assert_equal 'UPDATED', subject.title - end - - should "allow to update the document with a custom script" do - subject.expects(:persisted?).returns(true) - subject.expects(:id).returns('abc123').at_least_once - - @gateway - .expects(:update) - .with do |id, options| - assert_equal 'abc123', id - assert_equal 'EXEC', options[:script] - true - end - .returns({'_id' => 'abc123', 'version' => 2}) - - assert subject.update( {}, { script: 'EXEC' } ) - end - - should "not update an invalid model" do - @gateway - .expects(:update) - .never - - subject.instance_eval do - def valid?; false; end; - end - - assert ! subject.update(title: 'INVALID') - end - - should "skip the validation with the :validate option" do - subject.expects(:persisted?).returns(true).at_least_once - subject.expects(:id).returns('abc123').at_least_once - - @gateway - .expects(:update) - .with do |object, options| - assert_equal 'abc123', object - assert_equal nil, options[:id] - assert_equal 'INVALID', options[:doc][:title] - true - end - .returns({'_id' => 'abc123'}) - - subject.instance_eval do - def valid?; false; end; - end - - assert subject.update( { title: 'INVALID' }, { validate: false } ) - assert subject.persisted? - end - - should "pass the options to gateway" do - subject.expects(:persisted?).returns(true) - - @gateway - .expects(:update) - .with do |object, options| - assert_equal 'ABC', options[:routing] - true - end - .returns({'_id' => 'abc123'}) - - assert subject.update( { title: 'UPDATED' }, { routing: 'ABC' } ) - end - - should "return the response" do - subject.expects(:persisted?).returns(true) - - @gateway - .expects(:update) - .returns('FOOBAR') - - assert_equal 'FOOBAR', subject.update - end - - should "execute the callbacks" do - @gateway.expects(:update).returns({'_id' => 'abc'}) - DummyStoreModelWithCallback = Class.new(DummyStoreModel) - - DummyStoreModelWithCallback.after_update { $stderr.puts "UPDATED" } - - $stderr.expects(:puts).with('UPDATED') - d = DummyStoreModelWithCallback.new name: 'Test' - d.expects(:persisted?).returns(true) - d.update name: 'Update' - end - - should "update the model in its own index" do - @gateway.expects(:update) - .with do |model, options| - assert_equal 'my_custom_index', options[:index] - assert_equal 'my_custom_type', options[:type] - true - end - .returns({'_id' => 'abc'}) - - d = DummyStoreModel.new name: 'Test' - d.instance_variable_set(:@_index, 'my_custom_index') - d.instance_variable_set(:@_type, 'my_custom_type') - d.expects(:persisted?).returns(true) - - d.update name: 'Update' - end - - should "set the meta attributes from response" do - @gateway.expects(:update) - .with do |model, options| - assert_equal 'my_custom_index', options[:index] - assert_equal 'my_custom_type', options[:type] - true - end - .returns({'_id' => 'abc', '_index' => 'foo', '_type' => 'bar', '_version' => '100'}) - - d = DummyStoreModel.new name: 'Test' - d.instance_variable_set(:@_index, 'my_custom_index') - d.instance_variable_set(:@_type, 'my_custom_type') - d.expects(:persisted?).returns(true) - - d.update name: 'Update' - - assert_equal 'foo', d._index - assert_equal 'bar', d._type - assert_equal '100', d._version - end - end - - context "when incrementing," do - should "increment the attribute" do - subject.expects(:persisted?).returns(true) - - @gateway - .expects(:update) - .with do |id, options| - assert_equal 'ctx._source.count += 1', options[:script] - true - end - .returns({'_id' => 'abc123', 'version' => 2}) - - assert subject.increment :count - - assert_equal 1, subject.count - end - - should "set the meta attributes from response" do - subject.expects(:persisted?).returns(true) - - @gateway.expects(:update) - .with do |model, options| - assert_equal 'my_custom_index', options[:index] - assert_equal 'my_custom_type', options[:type] - true - end - .returns({'_id' => 'abc', '_index' => 'foo', '_type' => 'bar', '_version' => '100'}) - - subject.instance_variable_set(:@_index, 'my_custom_index') - subject.instance_variable_set(:@_type, 'my_custom_type') - - subject.increment :count - - assert_equal 'foo', subject._index - assert_equal 'bar', subject._type - assert_equal '100', subject._version - end - end - - context "when decrement," do - should "decrement the attribute" do - subject.expects(:persisted?).returns(true) - - @gateway - .expects(:update) - .with do |id, options| - assert_equal 'ctx._source.count = ctx._source.count - 1', options[:script] - true - end - .returns({'_id' => 'abc123', 'version' => 2}) - - assert subject.decrement :count - - assert_equal -1, subject.count - end - - should "set the meta attributes from response" do - subject.expects(:persisted?).returns(true) - - @gateway.expects(:update) - .with do |model, options| - assert_equal 'my_custom_index', options[:index] - assert_equal 'my_custom_type', options[:type] - true - end - .returns({'_id' => 'abc', '_index' => 'foo', '_type' => 'bar', '_version' => '100'}) - - subject.instance_variable_set(:@_index, 'my_custom_index') - subject.instance_variable_set(:@_type, 'my_custom_type') - - subject.decrement :count - - assert_equal 'foo', subject._index - assert_equal 'bar', subject._type - assert_equal '100', subject._version - end - end - - context "when touching," do - should "raise exception when touching not existing attribute" do - subject.expects(:persisted?).returns(true) - assert_raise(ArgumentError) { subject.touch :foobar } - end - - should "update updated_at by default" do - subject.expects(:persisted?).returns(true) - now = Time.parse('2014-01-01T00:00:00Z') - Time.expects(:now).returns(now).at_least_once - - @gateway - .expects(:update) - .with do |id, options| - assert_equal '2014-01-01T00:00:00Z', options[:doc][:updated_at] - true - end - .returns({'_id' => 'abc123', 'version' => 2}) - - subject.touch - assert_equal Time.parse('2014-01-01T00:00:00Z'), subject.updated_at - end - - should "update a custom attribute by default" do - subject.expects(:persisted?).returns(true) - now = Time.parse('2014-01-01T00:00:00Z') - Time.expects(:now).returns(now).at_least_once - - @gateway - .expects(:update) - .with do |id, options| - assert_equal '2014-01-01T00:00:00Z', options[:doc][:created_at] - true - end - .returns({'_id' => 'abc123', 'version' => 2}) - - subject.touch :created_at - assert_equal Time.parse('2014-01-01T00:00:00Z'), subject.created_at - end - - should "execute the callbacks" do - @gateway.expects(:update).returns({'_id' => 'abc'}) - DummyStoreModelWithCallback = Class.new(DummyStoreModel) - - DummyStoreModelWithCallback.after_touch { $stderr.puts "TOUCHED" } - - $stderr.expects(:puts).with('TOUCHED') - d = DummyStoreModelWithCallback.new name: 'Test' - d.expects(:persisted?).returns(true) - d.touch - end - - should "set the meta attributes from response" do - subject.expects(:persisted?).returns(true) - - @gateway.expects(:update) - .with do |model, options| - assert_equal 'my_custom_index', options[:index] - assert_equal 'my_custom_type', options[:type] - true - end - .returns({'_id' => 'abc', '_index' => 'foo', '_type' => 'bar', '_version' => '100'}) - - subject.instance_variable_set(:@_index, 'my_custom_index') - subject.instance_variable_set(:@_type, 'my_custom_type') - - subject.touch - - assert_equal 'foo', subject._index - assert_equal 'bar', subject._type - assert_equal '100', subject._version - end - end - - end -end diff --git a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/railtie.rb b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/railtie.rb index dbcd0fc38..6aeb6a866 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/railtie.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/railtie.rb @@ -16,10 +16,6 @@ class Railtie < ::Rails::Railtie include Elasticsearch::Rails::Instrumentation::Publishers::SearchRequest end if defined?(Elasticsearch::Model::Searching::SearchRequest) - Elasticsearch::Persistence::Model::Find::SearchRequest.class_eval do - include Elasticsearch::Rails::Instrumentation::Publishers::SearchRequest - end if defined?(Elasticsearch::Persistence::Model::Find::SearchRequest) - ActiveSupport.on_load(:action_controller) do include Elasticsearch::Rails::Instrumentation::ControllerRuntime end diff --git a/elasticsearch-rails/lib/elasticsearch/rails/lograge.rb b/elasticsearch-rails/lib/elasticsearch/rails/lograge.rb index a8edd8084..aa0f4f1f3 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/lograge.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/lograge.rb @@ -25,10 +25,6 @@ class Railtie < ::Rails::Railtie include Elasticsearch::Rails::Instrumentation::Publishers::SearchRequest end if defined?(Elasticsearch::Model::Searching::SearchRequest) - Elasticsearch::Persistence::Model::Find::SearchRequest.class_eval do - include Elasticsearch::Rails::Instrumentation::Publishers::SearchRequest - end if defined?(Elasticsearch::Persistence::Model::Find::SearchRequest) - ActiveSupport.on_load(:action_controller) do include Elasticsearch::Rails::Instrumentation::ControllerRuntime end From 2110bc3046bea70658876821bfadad117dc9ce9c Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Mon, 30 Jul 2018 15:56:18 +0200 Subject: [PATCH 375/582] [STORE] Deprecate _all field in ES 6.x (#820) --- .../persistence/repository/find.rb | 22 ++++++++----------- .../test/unit/repository_find_test.rb | 18 +++++++-------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb index 5e214cfb7..1ea0233fd 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb @@ -7,10 +7,6 @@ class DocumentNotFound < StandardError; end # module Find - # The default type of document. - # - ALL = '_all'.freeze - # The key for accessing the document found and returned from an # Elasticsearch _mget query. # @@ -57,16 +53,17 @@ def find(*args) # @return [true, false] # def exists?(id, options={}) - type = document_type || ALL - client.exists( { index: index_name, type: type, id: id }.merge(options) ) + request = { index: index_name, id: id } + request[:type] = document_type if document_type + client.exists(request.merge(options)) end # @api private # def __find_one(id, options={}) - type = document_type || ALL - document = client.get( { index: index_name, type: type, id: id }.merge(options) ) - + request = { index: index_name, id: id } + request[:type] = document_type if document_type + document = client.get(request.merge(options)) deserialize(document) rescue Elasticsearch::Transport::Transport::Errors::NotFound => e raise DocumentNotFound, e.message, caller @@ -75,13 +72,12 @@ def __find_one(id, options={}) # @api private # def __find_many(ids, options={}) - type = document_type || ALL - documents = client.mget( { index: index_name, type: type, body: { ids: ids } }.merge(options) ) - + request = { index: index_name, body: { ids: ids } } + request[:type] = document_type if document_type + documents = client.mget(request.merge(options)) documents[DOCS].map { |document| document[FOUND] ? deserialize(document) : nil } end end - end end end diff --git a/elasticsearch-persistence/test/unit/repository_find_test.rb b/elasticsearch-persistence/test/unit/repository_find_test.rb index b20006ca1..bcf7f8a1f 100644 --- a/elasticsearch-persistence/test/unit/repository_find_test.rb +++ b/elasticsearch-persistence/test/unit/repository_find_test.rb @@ -49,7 +49,7 @@ class MyDocument; end end should "return whether document for document_type exists" do - subject.expects(:document_type).returns('my_document') + subject.expects(:document_type).twice.returns('my_document') @client .expects(:exists) @@ -63,12 +63,12 @@ class MyDocument; end assert_equal true, subject.exists?('1') end - should "return whether document exists using _all type" do + should "return whether document exists using no document type" do @client .expects(:exists) .with do |arguments| - assert_equal '_all', arguments[:type] + assert_equal nil, arguments[:type] assert_equal '1', arguments[:id] true end @@ -92,7 +92,7 @@ class MyDocument; end context "'__find_one' method" do should "find a document based on document_type and return a deserialized object" do - subject.expects(:document_type).returns('my_document') + subject.expects(:document_type).twice.returns('my_document') subject.expects(:deserialize).with({'_source' => {'foo' => 'bar'}}).returns(MyDocument.new) @@ -108,7 +108,7 @@ class MyDocument; end assert_instance_of MyDocument, subject.__find_one('1') end - should "find a document using _all if document_type is not defined" do + should "find a document using no type if document_type is not defined" do subject.expects(:document_type).returns(nil) subject.expects(:deserialize).with({'_source' => {'foo' => 'bar'}}).returns(MyDocument.new) @@ -116,7 +116,7 @@ class MyDocument; end @client .expects(:get) .with do |arguments| - assert_equal '_all', arguments[:type] + assert_equal nil, arguments[:type] assert_equal '1', arguments[:id] true end @@ -187,7 +187,7 @@ class MyDocument; end end should "find documents based on document_type and return an Array of deserialized objects" do - subject.expects(:document_type).returns('my_document') + subject.expects(:document_type).twice.returns('my_document') subject.expects(:deserialize).twice @@ -219,7 +219,7 @@ class MyDocument; end @client .expects(:mget) .with do |arguments| - assert_equal '_all', arguments[:type] + assert_equal nil, arguments[:type] assert_equal ['1', '2'], arguments[:body][:ids] true end @@ -256,7 +256,7 @@ class MyDocument; end "_source"=>{"id"=>"2", "title"=>"Test 2"}} ]} - subject.expects(:document_type).returns('my_document') + subject.expects(:document_type).twice.returns('my_document') subject .expects(:deserialize) From 735163a340fecc430d016cebc3326e7ff379a4a8 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Fri, 3 Aug 2018 17:10:41 +0200 Subject: [PATCH 376/582] [STORE] Remove development dependency on virtus, include explicitly in Gemfile for integration test --- elasticsearch-persistence/Gemfile | 3 +++ elasticsearch-persistence/elasticsearch-persistence.gemspec | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/elasticsearch-persistence/Gemfile b/elasticsearch-persistence/Gemfile index a60150cf4..04b2d31d4 100644 --- a/elasticsearch-persistence/Gemfile +++ b/elasticsearch-persistence/Gemfile @@ -2,3 +2,6 @@ source 'https://rubygems.org' # Specify your gem's dependencies in elasticsearch-persistence.gemspec gemspec + + +gem 'virtus' diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index e90f2afb1..8e6e5fb27 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -28,7 +28,6 @@ Gem::Specification.new do |s| s.add_dependency "activesupport", '> 4' s.add_dependency "activemodel", '> 4' s.add_dependency "hashie" - s.add_dependency "virtus" s.add_development_dependency "bundler", "~> 1.5" s.add_development_dependency "rake", "~> 11.1" From 99b0f39cdbf8a8582ca6c22afc2aa038e04d24f6 Mon Sep 17 00:00:00 2001 From: Edward Anderson <nilbus@nilbus.com> Date: Tue, 7 Aug 2018 10:36:50 -0500 Subject: [PATCH 377/582] [MODEL] Avoid making an update when no attributes are changed (#762) Closes #743 --- .../lib/elasticsearch/model/indexing.rb | 2 +- .../test/unit/indexing_test.rb | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index 39ad06bc3..0763f1c2b 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -397,7 +397,7 @@ def delete_document(options={}) # @see http://rubydoc.info/gems/elasticsearch-api/Elasticsearch/API/Actions:update # def update_document(options={}) - if attributes_in_database = self.instance_variable_get(:@__changed_model_attributes) + if attributes_in_database = self.instance_variable_get(:@__changed_model_attributes).presence attributes = if respond_to?(:as_indexed_json) self.as_indexed_json.select { |k,v| attributes_in_database.keys.map(&:to_s).include? k.to_s } else diff --git a/elasticsearch-model/test/unit/indexing_test.rb b/elasticsearch-model/test/unit/indexing_test.rb index 591977751..f09186a93 100644 --- a/elasticsearch-model/test/unit/indexing_test.rb +++ b/elasticsearch-model/test/unit/indexing_test.rb @@ -176,6 +176,19 @@ def changes_to_save end end + class ::DummyIndexingModelWithNoChanges + extend Elasticsearch::Model::Indexing::ClassMethods + include Elasticsearch::Model::Indexing::InstanceMethods + + def self.before_save(&block) + (@callbacks ||= {})[block.hash] = block + end + + def changes_to_save + {} + end + end + class ::DummyIndexingModelWithCallbacksAndCustomAsIndexedJson extend Elasticsearch::Model::Indexing::ClassMethods include Elasticsearch::Model::Indexing::InstanceMethods @@ -393,6 +406,26 @@ def changes instance.update_document end + should "index instead of update when nothing was changed" do + client = mock('client') + instance = ::DummyIndexingModelWithNoChanges.new + + # Set the fake `changes` hash + instance.instance_variable_set(:@__changed_model_attributes, {}) + # Overload as_indexed_json for running index + instance.expects(:as_indexed_json).returns({ 'foo' => 'BAR' }) + + client.expects(:index) + client.expects(:update).never + + instance.expects(:client).returns(client) + instance.expects(:index_name).returns('foo') + instance.expects(:document_type).returns('bar') + instance.expects(:id).returns('1') + + instance.update_document({}) + end + should "update only the specific attributes" do client = mock('client') instance = ::DummyIndexingModelWithCallbacks.new From f2d7c2356801a2ae1f1ec3caeefb75fa614b081b Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Tue, 7 Aug 2018 17:45:36 +0200 Subject: [PATCH 378/582] [RAILS] Remove reference to ActiveRecord persistence pattern in README --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index ef1a8476e..788428638 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ This repository contains various Ruby and Rails integrations for [Elasticsearch] * ActiveModel integration with adapters for ActiveRecord and Mongoid * _Repository pattern_ based persistence layer for Ruby objects -* _Active Record pattern_ based persistence layer for Ruby models * Enumerable-based wrapper for search results * ActiveRecord::Relation-based wrapper for returning search results as records * Convenience model methods such as `search`, `mapping`, `import`, etc From 2139ca3532c6ea94f93f45ac82884faf6fbdbda3 Mon Sep 17 00:00:00 2001 From: Joe Francis <joe@lostapathy.com> Date: Wed, 8 Aug 2018 09:15:42 -0500 Subject: [PATCH 379/582] Move badges to top of README (#747) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 788428638..283f18e02 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Elasticsearch +[![Build Status](https://travis-ci.org/elastic/elasticsearch-rails.svg?branch=master)](https://travis-ci.org/elastic/elasticsearch-rails) [![Code Climate](https://codeclimate.com/github/elastic/elasticsearch-rails/badges/gpa.svg)](https://codeclimate.com/github/elastic/elasticsearch-rails) + This repository contains various Ruby and Rails integrations for [Elasticsearch](http://elasticsearch.org): * ActiveModel integration with adapters for ActiveRecord and Mongoid @@ -119,8 +121,6 @@ repository.save Article.new(title: 'Test') ## Development -[![Build Status](https://travis-ci.org/elastic/elasticsearch-rails.svg?branch=master)](https://travis-ci.org/elastic/elasticsearch-rails) [![Code Climate](https://codeclimate.com/github/elastic/elasticsearch-rails/badges/gpa.svg)](https://codeclimate.com/github/elastic/elasticsearch-rails) - To work on the code, clone the repository and install all dependencies first: ``` From e3fcfa0134968a5876baf9f005f812725c6dadda Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Wed, 8 Aug 2018 16:39:23 +0200 Subject: [PATCH 380/582] Use local as source for gem dependencies when possible --- elasticsearch-persistence/Gemfile | 1 + elasticsearch-persistence/elasticsearch-persistence.gemspec | 4 ++-- elasticsearch-rails/Gemfile | 5 +++++ elasticsearch-rails/elasticsearch-rails.gemspec | 1 - 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/elasticsearch-persistence/Gemfile b/elasticsearch-persistence/Gemfile index 04b2d31d4..c88afe19f 100644 --- a/elasticsearch-persistence/Gemfile +++ b/elasticsearch-persistence/Gemfile @@ -3,5 +3,6 @@ source 'https://rubygems.org' # Specify your gem's dependencies in elasticsearch-persistence.gemspec gemspec +gem 'elasticsearch-model', :path => File.expand_path("../../elasticsearch-model", __FILE__), :require => false gem 'virtus' diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index 8e6e5fb27..775c3b9e5 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -23,8 +23,8 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 1.9.3" - s.add_dependency "elasticsearch", '~> 5' - s.add_dependency "elasticsearch-model", '~> 5' + s.add_dependency "elasticsearch", '~> 6' + s.add_dependency "elasticsearch-model", '~> 6' s.add_dependency "activesupport", '> 4' s.add_dependency "activemodel", '> 4' s.add_dependency "hashie" diff --git a/elasticsearch-rails/Gemfile b/elasticsearch-rails/Gemfile index 1aeec6c9a..81fc68ac4 100644 --- a/elasticsearch-rails/Gemfile +++ b/elasticsearch-rails/Gemfile @@ -7,3 +7,8 @@ gemspec # if File.exists? File.expand_path("../../elasticsearch-model", __FILE__) # gem 'elasticsearch-model', :path => File.expand_path("../../elasticsearch-model", __FILE__), :require => true # end + + + +gem 'elasticsearch-model', :path => File.expand_path("../../elasticsearch-model", __FILE__), :require => false +gem 'elasticsearch-persistence', :path => File.expand_path("../../elasticsearch-persistence", __FILE__), :require => false diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index 1fad14c36..e4ad351ba 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -27,7 +27,6 @@ Gem::Specification.new do |s| s.add_development_dependency "rake", "~> 11.1" s.add_development_dependency "elasticsearch-extensions" - s.add_development_dependency "elasticsearch-model" s.add_development_dependency "rails", ">= 3.1" From 982124cd637c13de0957087edfc398d320e1295b Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Wed, 8 Aug 2018 16:59:10 +0200 Subject: [PATCH 381/582] [STORE] Update gem dependency --- elasticsearch-persistence/elasticsearch-persistence.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index 775c3b9e5..34a99b690 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -24,7 +24,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 1.9.3" s.add_dependency "elasticsearch", '~> 6' - s.add_dependency "elasticsearch-model", '~> 6' + s.add_dependency "elasticsearch-model", '~> 7' s.add_dependency "activesupport", '> 4' s.add_dependency "activemodel", '> 4' s.add_dependency "hashie" From 8bd4235a9676724269171395676ff9dc667e9e89 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Thu, 9 Aug 2018 12:23:59 +0200 Subject: [PATCH 382/582] Only require 'oj' gem if not using JRuby --- elasticsearch-model/test/test_helper.rb | 2 +- elasticsearch-persistence/test/test_helper.rb | 2 +- elasticsearch-rails/test/test_helper.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/test/test_helper.rb b/elasticsearch-model/test/test_helper.rb index 7471e475d..5a727e2b2 100644 --- a/elasticsearch-model/test/test_helper.rb +++ b/elasticsearch-model/test/test_helper.rb @@ -23,7 +23,7 @@ require 'turn' unless ENV["TM_FILEPATH"] || ENV["NOTURN"] || defined?(RUBY_VERSION) && RUBY_VERSION > '2.2' require 'ansi' -require 'oj' +require 'oj' unless defined?(JRUBY_VERSION) require 'active_model' diff --git a/elasticsearch-persistence/test/test_helper.rb b/elasticsearch-persistence/test/test_helper.rb index fd317fce8..c881ac02e 100644 --- a/elasticsearch-persistence/test/test_helper.rb +++ b/elasticsearch-persistence/test/test_helper.rb @@ -23,7 +23,7 @@ require 'turn' unless ENV["TM_FILEPATH"] || ENV["NOTURN"] || defined?(RUBY_VERSION) && RUBY_VERSION > '2.2' require 'ansi' -require 'oj' +require 'oj' unless defined?(JRUBY_VERSION) require 'elasticsearch/extensions/test/cluster' require 'elasticsearch/extensions/test/startup_shutdown' diff --git a/elasticsearch-rails/test/test_helper.rb b/elasticsearch-rails/test/test_helper.rb index bf9c55bd1..e06554fc0 100644 --- a/elasticsearch-rails/test/test_helper.rb +++ b/elasticsearch-rails/test/test_helper.rb @@ -23,7 +23,7 @@ require 'turn' unless ENV["TM_FILEPATH"] || ENV["NOTURN"] || defined?(RUBY_VERSION) && RUBY_VERSION > '2.2' require 'ansi' -require 'oj' +require 'oj' unless defined?(JRUBY_VERSION) require 'rails/version' require 'active_record' From e051a87e3357f25a4dace522d7afd09761d0fe7f Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Fri, 10 Aug 2018 11:25:38 +0200 Subject: [PATCH 383/582] [CI] Run unit tests using JRuby on Travis (#819) --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 51766c1e1..426ccf85a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,9 +37,9 @@ matrix: jdk: oraclejdk8 env: TEST_SUITE=unit -# - rvm: jruby-9.1 -# jdk: oraclejdk8 -# env: TEST_SUITE=unit + - rvm: jruby-9.1 + jdk: oraclejdk8 + env: TEST_SUITE=unit - rvm: 2.5 jdk: oraclejdk8 From 8acd1574a8e293554df5f58ce1f968a165b760cd Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Mon, 13 Aug 2018 13:43:24 +0200 Subject: [PATCH 384/582] [STORE] Refactor Repository as mixin (#824) * [STORE] Refactor Repository * [STORE] Add more tests and update documentation * [STORE] Remove #with method * [STORE] Raise NotImplementedError if administrative index methods are called on a Repository class * [STORE] Put class-level index admin methods into a DSL module, to be included separately * [STORE] Fix documentation and minor typos in tests * [STORE] Add one more test for ArgumentError on #search * [STORE] Remove test unit files and define only test 'integration' and 'all' rake tasks --- elasticsearch-persistence/.rspec | 2 + elasticsearch-persistence/Gemfile | 5 + elasticsearch-persistence/Rakefile | 16 +- .../lib/elasticsearch/persistence.rb | 112 +-- .../lib/elasticsearch/persistence/client.rb | 51 -- .../elasticsearch/persistence/repository.rb | 255 +++++-- .../persistence/repository/class.rb | 71 -- .../persistence/repository/dsl.rb | 94 +++ .../persistence/repository/find.rb | 29 +- .../persistence/repository/naming.rb | 99 --- .../persistence/repository/search.rb | 17 +- .../persistence/repository/serialize.rb | 73 +- .../persistence/repository/store.rb | 81 +- .../lib/elasticsearch/persistence/version.rb | 2 +- .../spec/repository/find_spec.rb | 179 +++++ .../spec/repository/search_spec.rb | 181 +++++ .../spec/repository/serialize_spec.rb | 53 ++ .../spec/repository/store_spec.rb | 327 ++++++++ .../spec/repository_spec.rb | 716 ++++++++++++++++++ elasticsearch-persistence/spec/spec_helper.rb | 28 + .../repository/custom_class_test.rb | 88 --- .../repository/customized_class_test.rb | 82 -- .../repository/default_class_test.rb | 119 --- .../repository/virtus_model_test.rb | 119 --- elasticsearch-persistence/test/test_helper.rb | 55 -- .../test/unit/persistence_test.rb | 32 - .../test/unit/repository_class_test.rb | 51 -- .../test/unit/repository_client_test.rb | 32 - .../test/unit/repository_find_test.rb | 307 -------- .../test/unit/repository_indexing_test.rb | 37 - .../test/unit/repository_module_test.rb | 146 ---- .../test/unit/repository_naming_test.rb | 117 --- .../unit/repository_response_results_test.rb | 98 --- .../test/unit/repository_search_test.rb | 100 --- .../test/unit/repository_serialize_test.rb | 41 - .../test/unit/repository_store_test.rb | 219 ------ 36 files changed, 1922 insertions(+), 2112 deletions(-) create mode 100644 elasticsearch-persistence/.rspec delete mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/client.rb delete mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb create mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb delete mode 100644 elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb create mode 100644 elasticsearch-persistence/spec/repository/find_spec.rb create mode 100644 elasticsearch-persistence/spec/repository/search_spec.rb create mode 100644 elasticsearch-persistence/spec/repository/serialize_spec.rb create mode 100644 elasticsearch-persistence/spec/repository/store_spec.rb create mode 100644 elasticsearch-persistence/spec/repository_spec.rb create mode 100644 elasticsearch-persistence/spec/spec_helper.rb delete mode 100644 elasticsearch-persistence/test/integration/repository/custom_class_test.rb delete mode 100644 elasticsearch-persistence/test/integration/repository/customized_class_test.rb delete mode 100644 elasticsearch-persistence/test/integration/repository/default_class_test.rb delete mode 100644 elasticsearch-persistence/test/integration/repository/virtus_model_test.rb delete mode 100644 elasticsearch-persistence/test/test_helper.rb delete mode 100644 elasticsearch-persistence/test/unit/persistence_test.rb delete mode 100644 elasticsearch-persistence/test/unit/repository_class_test.rb delete mode 100644 elasticsearch-persistence/test/unit/repository_client_test.rb delete mode 100644 elasticsearch-persistence/test/unit/repository_find_test.rb delete mode 100644 elasticsearch-persistence/test/unit/repository_indexing_test.rb delete mode 100644 elasticsearch-persistence/test/unit/repository_module_test.rb delete mode 100644 elasticsearch-persistence/test/unit/repository_naming_test.rb delete mode 100644 elasticsearch-persistence/test/unit/repository_response_results_test.rb delete mode 100644 elasticsearch-persistence/test/unit/repository_search_test.rb delete mode 100644 elasticsearch-persistence/test/unit/repository_serialize_test.rb delete mode 100644 elasticsearch-persistence/test/unit/repository_store_test.rb diff --git a/elasticsearch-persistence/.rspec b/elasticsearch-persistence/.rspec new file mode 100644 index 000000000..77d185827 --- /dev/null +++ b/elasticsearch-persistence/.rspec @@ -0,0 +1,2 @@ +--tty +--colour diff --git a/elasticsearch-persistence/Gemfile b/elasticsearch-persistence/Gemfile index c88afe19f..de011df05 100644 --- a/elasticsearch-persistence/Gemfile +++ b/elasticsearch-persistence/Gemfile @@ -6,3 +6,8 @@ gemspec gem 'elasticsearch-model', :path => File.expand_path("../../elasticsearch-model", __FILE__), :require => false gem 'virtus' + +group :development, :testing do + gem 'rspec' + gem 'pry-nav' +end diff --git a/elasticsearch-persistence/Rakefile b/elasticsearch-persistence/Rakefile index 61038d08c..660e57211 100644 --- a/elasticsearch-persistence/Rakefile +++ b/elasticsearch-persistence/Rakefile @@ -7,24 +7,24 @@ task :test => 'test:unit' # ----- Test tasks ------------------------------------------------------------ require 'rake/testtask' +require 'rspec/core/rake_task' + namespace :test do + + RSpec::Core::RakeTask.new(:spec) Rake::TestTask.new(:unit) do |test| - test.libs << 'lib' << 'test' - test.test_files = FileList["test/unit/**/*_test.rb"] - test.verbose = false - test.warning = false end Rake::TestTask.new(:integration) do |test| - test.libs << 'lib' << 'test' - test.test_files = FileList["test/integration/**/*_test.rb"] test.verbose = false test.warning = false + test.deps = [ :spec ] end Rake::TestTask.new(:all) do |test| - test.libs << 'lib' << 'test' - test.test_files = FileList["test/unit/**/*_test.rb", "test/integration/**/*_test.rb"] + test.verbose = false + test.warning = false + test.deps = [ :spec ] end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence.rb b/elasticsearch-persistence/lib/elasticsearch/persistence.rb index 986ef1bc4..ce0d25b60 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence.rb @@ -1,116 +1,8 @@ require 'hashie/mash' require 'elasticsearch' - -require 'elasticsearch/model/hash_wrapper' -require 'elasticsearch/model/indexing' -require 'elasticsearch/model/searching' - -require 'active_support/inflector' +require 'elasticsearch/model' require 'elasticsearch/persistence/version' - -require 'elasticsearch/persistence/client' -require 'elasticsearch/persistence/repository/response/results' -require 'elasticsearch/persistence/repository/naming' -require 'elasticsearch/persistence/repository/serialize' -require 'elasticsearch/persistence/repository/store' -require 'elasticsearch/persistence/repository/find' -require 'elasticsearch/persistence/repository/search' -require 'elasticsearch/persistence/repository/class' require 'elasticsearch/persistence/repository' - -module Elasticsearch - - # Persistence for Ruby domain objects and models in Elasticsearch - # =============================================================== - # - # `Elasticsearch::Persistence` contains modules for storing and retrieving Ruby domain objects and models - # in Elasticsearch. - # - # == Repository - # - # The repository patterns allows to store and retrieve Ruby objects in Elasticsearch. - # - # require 'elasticsearch/persistence' - # - # class Note - # def to_hash; {foo: 'bar'}; end - # end - # - # repository = Elasticsearch::Persistence::Repository.new - # - # repository.save Note.new - # # => {"_index"=>"repository", "_type"=>"note", "_id"=>"mY108X9mSHajxIy2rzH2CA", ...} - # - # Customize your repository by including the main module in a Ruby class - # class MyRepository - # include Elasticsearch::Persistence::Repository - # - # index 'my_notes' - # klass Note - # - # client Elasticsearch::Client.new log: true - # end - # - # repository = MyRepository.new - # - # repository.save Note.new - # # 2014-04-04 22:15:25 +0200: POST http://localhost:9200/my_notes/note [status:201, request:0.009s, query:n/a] - # # 2014-04-04 22:15:25 +0200: > {"foo":"bar"} - # # 2014-04-04 22:15:25 +0200: < {"_index":"my_notes","_type":"note","_id":"-d28yXLFSlusnTxb13WIZQ", ...} - # - # == Model - # - # The active record pattern allows to use the interface familiar from ActiveRecord models: - # - # require 'elasticsearch/persistence' - # - # class Article - # attribute :title, String, mapping: { analyzer: 'snowball' } - # end - # - # article = Article.new id: 1, title: 'Test' - # article.save - # - # Article.find(1) - # - # article.update_attributes title: 'Update' - # - # article.destroy - # - module Persistence - - # :nodoc: - module ClassMethods - - # Get or set the default client for all repositories and models - # - # @example Set and configure the default client - # - # Elasticsearch::Persistence.client Elasticsearch::Client.new host: 'http://localhost:9200', tracer: true - # - # @example Perform an API request through the client - # - # Elasticsearch::Persistence.client.cluster.health - # # => { "cluster_name" => "elasticsearch" ... } - # - def client client=nil - @client = client || @client || Elasticsearch::Client.new - end - - # Set the default client for all repositories and models - # - # @example Set and configure the default client - # - # Elasticsearch::Persistence.client = Elasticsearch::Client.new host: 'http://localhost:9200', tracer: true - # => #<Elasticsearch::Transport::Client:0x007f96a6dd0d80 @transport=... > - # - def client=(client) - @client = client - end - end - - extend ClassMethods - end -end +require 'elasticsearch/persistence/repository/response/results' diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/client.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/client.rb deleted file mode 100644 index 3c9d618d4..000000000 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/client.rb +++ /dev/null @@ -1,51 +0,0 @@ -module Elasticsearch - module Persistence - module Repository - - # Wraps the Elasticsearch Ruby - # [client](https://github.com/elasticsearch/elasticsearch-ruby/tree/master/elasticsearch#usage) - # - module Client - - # Get or set the default client for this repository - # - # @example Set and configure the client for the repository class - # - # class MyRepository - # include Elasticsearch::Persistence::Repository - # client Elasticsearch::Client.new host: 'http://localhost:9200', log: true - # end - # - # @example Set and configure the client for this repository instance - # - # repository.client Elasticsearch::Client.new host: 'http://localhost:9200', tracer: true - # - # @example Perform an API request through the client - # - # MyRepository.client.cluster.health - # repository.client.cluster.health - # # => { "cluster_name" => "elasticsearch" ... } - # - def client client=nil - @client = client || @client || Elasticsearch::Persistence.client - end - - # Set the default client for this repository - # - # @example Set and configure the client for the repository class - # - # MyRepository.client = Elasticsearch::Client.new host: 'http://localhost:9200', log: true - # - # @example Set and configure the client for this repository instance - # - # repository.client = Elasticsearch::Client.new host: 'http://localhost:9200', tracer: true - # - def client=(client) - @client = client - @client - end - end - - end - end -end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index f6a202029..33cbdc2c4 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -1,77 +1,224 @@ +require 'elasticsearch/persistence/repository/dsl' +require 'elasticsearch/persistence/repository/find' +require 'elasticsearch/persistence/repository/store' +require 'elasticsearch/persistence/repository/serialize' +require 'elasticsearch/persistence/repository/search' + module Elasticsearch module Persistence - # Delegate methods to the repository (acting as a gateway) + # The base Repository mixin. This module should be included in classes that + # represent an Elasticsearch repository. # - module GatewayDelegation - def method_missing(method_name, *arguments, &block) - gateway.respond_to?(method_name) ? gateway.__send__(method_name, *arguments, &block) : super - end + # @since 6.0.0 + module Repository + include Store + include Serialize + include Find + include Search + include Elasticsearch::Model::Indexing::ClassMethods - def respond_to?(method_name, include_private=false) - gateway.respond_to?(method_name) || super + def self.included(base) + base.send(:extend, ClassMethods) end - def respond_to_missing?(method_name, *) - gateway.respond_to?(method_name) || super - end - end + module ClassMethods - # When included, creates an instance of the {Repository::Class Repository} class as a "gateway" - # - # @example Include the repository in a custom class - # - # require 'elasticsearch/persistence' - # - # class MyRepository - # include Elasticsearch::Persistence::Repository - # end - # - module Repository - def self.included(base) - gateway = Elasticsearch::Persistence::Repository::Class.new host: base - - # Define the instance level gateway + # Initialize a repository instance. Optionally provide a block to define index mappings or + # settings on the repository instance. + # + # @example Create a new repository. + # MyRepository.create(index_name: 'notes', klass: Note) + # + # @example Create a new repository and evaluate a block on it. + # MyRepository.create(index_name: 'notes', klass: Note) do + # mapping dynamic: 'strict' do + # indexes :title + # end + # end # - base.class_eval do - define_method :gateway do - @gateway ||= gateway + # @param [ Hash ] options The options to use. + # @param [ Proc ] block A block to evaluate on the new repository instance. + # + # @option options [ Symbol, String ] :index_name The name of the index. + # @option options [ Symbol, String ] :document_type The type of documents persisted in this repository. + # @option options [ Symbol, String ] :client The client used to handle requests to and from Elasticsearch. + # @option options [ Symbol, String ] :klass The class used to instantiate an object when documents are + # deserialized. The default is nil, in which case the raw document will be returned as a Hash. + # @option options [ Elasticsearch::Model::Indexing::Mappings, Hash ] :mapping The mapping for this index. + # @option options [ Elasticsearch::Model::Indexing::Settings, Hash ] :settings The settings for this index. + # + # @since 6.0.0 + def create(options = {}, &block) + new(options).tap do |obj| + obj.instance_eval(&block) if block_given? end - - include GatewayDelegation end + end - # Define the class level gateway - # - (class << base; self; end).class_eval do - define_method :gateway do |&block| - @gateway ||= gateway - @gateway.instance_eval(&block) if block - @gateway - end + # The default index name. + # + # @return [ String ] The default index name. + # + # @since 6.0.0 + DEFAULT_INDEX_NAME = 'repository'.freeze - include GatewayDelegation - end + # The default document type. + # + # @return [ String ] The default document type. + # + # @note the document type will no longer be configurable in future versions + # of Elasticsearch. + # + # @since 6.0.0 + DEFAULT_DOC_TYPE = '_doc'.freeze - # Catch repository methods (such as `serialize` and others) defined in the receiving class, - # and overload the default definition in the gateway - # - def base.method_added(name) - if :gateway != name && respond_to?(:gateway) && (gateway.public_methods - Object.public_methods).include?(name) - gateway.define_singleton_method(name, self.new.method(name).to_proc) + # The repository options. + # + # @return [ Hash ] + # + # @since 6.0.0 + attr_reader :options + + # Initialize a repository instance. + # + # @example Initialize the repository. + # MyRepository.new(index_name: 'notes', klass: Note) + # + # @param [ Hash ] options The options to use. + # + # @option options [ Symbol, String ] :index_name The name of the index. + # @option options [ Symbol, String ] :document_type The type of documents persisted in this repository. + # @option options [ Symbol, String ] :client The client used to handle requests to and from Elasticsearch. + # @option options [ Symbol, String ] :klass The class used to instantiate an object when documents are + # deserialized. The default is nil, in which case the raw document will be returned as a Hash. + # @option options [ Elasticsearch::Model::Indexing::Mappings, Hash ] :mapping The mapping for this index. + # @option options [ Elasticsearch::Model::Indexing::Settings, Hash ] :settings The settings for this index. + # + # @since 6.0.0 + def initialize(options = {}) + @options = options + end + + # Get the client used by the repository. + # + # @example + # repository.client + # + # @return [ Elasticsearch::Transport::Client ] The repository's client. + # + # @since 6.0.0 + def client + @client ||= @options[:client] || + __get_class_value(:client) || + Elasticsearch::Transport::Client.new + end + + # Get the document type used by the repository object. + # + # @example + # repository.document_type + # + # @return [ String, Symbol ] The repository's document type. + # + # @since 6.0.0 + def document_type + @document_type ||= @options[:document_type] || + __get_class_value(:document_type) || + DEFAULT_DOC_TYPE + end + + # Get the index name used by the repository. + # + # @example + # repository.index_name + # + # @return [ String, Symbol ] The repository's index name. + # + # @since 6.0.0 + def index_name + @index_name ||= @options[:index_name] || + __get_class_value(:index_name) || + DEFAULT_INDEX_NAME + end + + # Get the class used by the repository when deserializing. + # + # @example + # repository.klass + # + # @return [ Class ] The repository's klass for deserializing. + # + # @since 6.0.0 + def klass + @klass ||= @options[:klass] || __get_class_value(:klass) + end + + # Get the index mapping. Optionally pass a block to define the mappings. + # + # @example + # repository.mapping + # + # @example Set the mappings with a block. + # repository.mapping dynamic: 'strict' do + # indexes :foo + # end + # end + # + # @note If mappings were set when the repository was created, a block passed to this + # method will not be evaluated. + # + # @return [ Elasticsearch::Model::Indexing::Mappings ] The index mappings. + # + # @since 6.0.0 + def mapping(*args) + @memoized_mapping ||= @options[:mapping] || (begin + if _mapping = __get_class_value(:mapping) + _mapping.instance_variable_set(:@type, document_type) + _mapping end - end + end) || (super && @mapping) + end + alias :mappings :mapping + + # Get the index settings. + # + # @example + # repository.settings + # + # @example Set the settings with a block. + # repository.settings number_of_shards: 1, number_of_replicas: 0 do + # mapping dynamic: 'strict' do + # indexes :foo do + # indexes :bar + # end + # end + # end + # + # @return [ Elasticsearch::Model::Indexing::Settings ] The index settings. + # + # @since 6.0.0 + def settings(*args) + @memoized_settings ||= @options[:settings] || __get_class_value(:settings) || (super && @settings) end - # Shortcut method to allow concise repository initialization + # Determine whether the index with this repository's index name exists. # - # @example Create a new default repository + # @example + # repository.index_exists? # - # repository = Elasticsearch::Persistence::Repository.new + # @return [ true, false ] Whether the index exists. # - def new(options={}, &block) - Elasticsearch::Persistence::Repository::Class.new( {index: 'repository'}.merge(options), &block ) - end; module_function :new + # @since 6.0.0 + def index_exists?(*args) + super(index_name: index_name) + end + + private + + def __get_class_value(_method_) + self.class.send(_method_) if self.class.respond_to?(_method_) + end end end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb deleted file mode 100644 index 0dcd2d576..000000000 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/class.rb +++ /dev/null @@ -1,71 +0,0 @@ -module Elasticsearch - module Persistence - module Repository - - # The default repository class, to be used either directly, or as a gateway in a custom repository class - # - # @example Standalone use - # - # repository = Elasticsearch::Persistence::Repository::Class.new - # # => #<Elasticsearch::Persistence::Repository::Class ...> - # repository.save(my_object) - # # => {"_index"=> ... } - # - # @example Shortcut use - # - # repository = Elasticsearch::Persistence::Repository.new - # # => #<Elasticsearch::Persistence::Repository::Class ...> - # - # @example Configuration via a block - # - # repository = Elasticsearch::Persistence::Repository.new do - # index 'my_notes' - # end - # - # # => #<Elasticsearch::Persistence::Repository::Class ...> - # # > repository.save(my_object) - # # => {"_index"=> ... } - # - # @example Accessing the gateway in a custom class - # - # class MyRepository - # include Elasticsearch::Persistence::Repository - # end - # - # repository = MyRepository.new - # - # repository.gateway.client.info - # # => {"status"=>200, "name"=>"Venom", ... } - # - class Class - include Elasticsearch::Persistence::Repository::Client - include Elasticsearch::Persistence::Repository::Naming - include Elasticsearch::Persistence::Repository::Serialize - include Elasticsearch::Persistence::Repository::Store - include Elasticsearch::Persistence::Repository::Find - include Elasticsearch::Persistence::Repository::Search - - include Elasticsearch::Model::Indexing::ClassMethods - - attr_reader :options - - def initialize(options={}, &block) - @options = options - index_name options.delete(:index) - block.arity < 1 ? instance_eval(&block) : block.call(self) if block_given? - end - - # Return the "host" class, if this repository is a gateway hosted in another class - # - # @return [nil, Class] - # - # @api private - # - def host - options[:host] - end - end - - end - end -end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb new file mode 100644 index 000000000..0c880d7ec --- /dev/null +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb @@ -0,0 +1,94 @@ +module Elasticsearch + module Persistence + module Repository + + # Include this module to get class-level methods for repository configuration. + # + # @since 6.0.0 + module DSL + + def self.included(base) + base.send(:extend, Elasticsearch::Model::Indexing::ClassMethods) + base.send(:extend, ClassMethods) + end + + # These methods are necessary to define at the class-level so that the methods available + # via Elasticsearch::Model::Indexing::ClassMethods have the references they depend on. + # + # @since 6.0.0 + module ClassMethods + + # Get or set the class-level document type setting. + # + # @example + # MyRepository.document_type + # + # @return [ String, Symbol ] _type The repository's document type. + # + # @since 6.0.0 + def document_type(_type = nil) + @document_type ||= (_type || DEFAULT_DOC_TYPE) + end + + # Get or set the class-level index name setting. + # + # @example + # MyRepository.index_name + # + # @return [ String, Symbol ] _name The repository's index name. + # + # @since 6.0.0 + def index_name(_name = nil) + @index_name ||= (_name || DEFAULT_INDEX_NAME) + end + + # Get or set the class-level setting for the class used by the repository when deserializing. + # + # @example + # MyRepository.klass + # + # @return [ Class ] _class The repository's klass for deserializing. + # + # @since 6.0.0 + def klass(_class = nil) + instance_variables.include?(:@klass) ? @klass : @klass = _class + end + + # Get or set the class-level setting for the client used by the repository. + # + # @example + # MyRepository.client + # + # @return [ Class ] _client The repository's client. + # + # @since 6.0.0 + def client(_client = nil) + @client ||= (_client || Elasticsearch::Transport::Client.new) + end + + def create_index!(*args) + __raise_not_implemented_error(__method__) + end + + def delete_index!(*args) + __raise_not_implemented_error(__method__) + end + + def refresh_index!(*args) + __raise_not_implemented_error(__method__) + end + + def index_exists?(*args) + __raise_not_implemented_error(__method__) + end + + private + + def __raise_not_implemented_error(_method_) + raise NotImplementedError, "The '#{_method_}' method is not implemented on the Repository class." + end + end + end + end + end +end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb index 1ea0233fd..cbd264ae2 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb @@ -7,16 +7,6 @@ class DocumentNotFound < StandardError; end # module Find - # The key for accessing the document found and returned from an - # Elasticsearch _mget query. - # - DOCS = 'docs'.freeze - - # The key for the boolean value indicating whether a particular id - # has been successfully found in an Elasticsearch _mget query. - # - FOUND = 'found'.freeze - # Retrieve a single object or multiple objects from Elasticsearch by ID or IDs # # @example Retrieve a single object by ID @@ -50,6 +40,9 @@ def find(*args) # repository.exists?(1) # => true # + # @param [ String, Integer ] id The id to search. + # @param [ Hash ] options The options. + # # @return [true, false] # def exists?(id, options={}) @@ -58,6 +51,18 @@ def exists?(id, options={}) client.exists(request.merge(options)) end + private + + # The key for accessing the document found and returned from an + # Elasticsearch _mget query. + # + DOCS = 'docs'.freeze + + # The key for the boolean value indicating whether a particular id + # has been successfully found in an Elasticsearch _mget query. + # + FOUND = 'found'.freeze + # @api private # def __find_one(id, options={}) @@ -75,7 +80,9 @@ def __find_many(ids, options={}) request = { index: index_name, body: { ids: ids } } request[:type] = document_type if document_type documents = client.mget(request.merge(options)) - documents[DOCS].map { |document| document[FOUND] ? deserialize(document) : nil } + documents[DOCS].map do |document| + deserialize(document) if document[FOUND] + end end end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb deleted file mode 100644 index d559d8d51..000000000 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/naming.rb +++ /dev/null @@ -1,99 +0,0 @@ -module Elasticsearch - module Persistence - module Repository - - # Wraps all naming-related features of the repository (index name, the domain object class, etc) - # - module Naming - - # The possible keys for a document id. - # - IDS = [:id, 'id', :_id, '_id'].freeze - - DEFAULT_DOC_TYPE = '_doc'.freeze - - # Get or set the class used to initialize domain objects when deserializing them - # - def klass(name=nil) - if name - @klass = name - else - @klass - end - end - - # Set the class used to initialize domain objects when deserializing them - # - def klass=klass - @klass = klass - end - - # Get or set the index name used when storing and retrieving documents - # - def index_name name=nil - @index_name = name || @index_name || begin - if respond_to?(:host) && host && host.is_a?(Module) - self.host.to_s.underscore.gsub(/\//, '-') - else - self.class.to_s.underscore.gsub(/\//, '-') - end - end - end; alias :index :index_name - - # Set the index name used when storing and retrieving documents - # - def index_name=(name) - @index_name = name - end; alias :index= :index_name= - - # Get or set the document type used when storing and retrieving documents - # - def document_type name=nil - @document_type = name || @document_type || DEFAULT_DOC_TYPE - end; alias :type :document_type - - # Set the document type used when storing and retrieving documents - # - def document_type=(name) - @document_type = name - end; alias :type= :document_type= - - # Get a document ID from the document (assuming Hash or Hash-like object) - # - # @example - # repository.__get_id_from_document title: 'Test', id: 'abc123' - # => "abc123" - # - # @api private - # - def __get_id_from_document(document) - document[IDS.find { |id| document[id] }] - end - - # Extract a document ID from the document (assuming Hash or Hash-like object) - # - # @note Calling this method will *remove* the `id` or `_id` key from the passed object. - # - # @example - # options = { title: 'Test', id: 'abc123' } - # repository.__extract_id_from_document options - # # => "abc123" - # options - # # => { title: 'Test' } - # - # @api private - # - def __extract_id_from_document(document) - IDS.inject(nil) do |deleted, id| - if document[id] - document.delete(id) - else - deleted - end - end - end - end - - end - end -end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb index 07c0e4d3d..203a7e246 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb @@ -6,10 +6,6 @@ module Repository # module Search - # The key for accessing the count in a Elasticsearch query response. - # - COUNT = 'count'.freeze - # Returns a collection of domain objects by an Elasticsearch query # # Pass the query either as a string or a Hash-like object @@ -41,6 +37,9 @@ module Search # # GET http://localhost:9200/notes/note/_search # # > {"query":{"match":{"title":"fox dog"}},"size":25} # + # @param [ Hash, String ] query_or_definition The query or search definition. + # @param [ Hash ] options The search options. + # # @return [Elasticsearch::Persistence::Repository::Response::Results] # def search(query_or_definition, options={}) @@ -75,6 +74,9 @@ def search(query_or_definition, options={}) # repository.search(query: { match: { title: 'fox dog' } }) # # => 1 # + # @param [ Hash, String ] query_or_definition The query or search definition. + # @param [ Hash ] options The search options. + # # @return [Integer] # def count(query_or_definition=nil, options={}) @@ -92,8 +94,13 @@ def count(query_or_definition=nil, options={}) response[COUNT] end - end + private + + # The key for accessing the count in a Elasticsearch query response. + # + COUNT = 'count'.freeze + end end end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb index e9a8d875e..067a7daad 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb @@ -2,19 +2,40 @@ module Elasticsearch module Persistence module Repository - # Provide serialization and deserialization between Ruby objects and Elasticsearch documents + # Provide serialization and deserialization between Ruby objects and Elasticsearch documents. # # Override these methods in your repository class to customize the logic. # module Serialize - # Error message raised when documents are attempted to be deserialized and no klass is defined for - # the Repository. + # Serialize the object for storing it in Elasticsearch. # - # @since 6.0.0 - NO_CLASS_ERROR_MESSAGE = "No class is defined for deserializing documents. " + - "Please define a 'klass' for the Repository or define a custom " + - "deserialize method.".freeze + # In the default implementation, call the `to_hash` method on the passed object. + # + # @param [ Object ] document The Ruby object to serialize. + # + # @return [ Hash ] The serialized document. + # + def serialize(document) + document.to_hash + end + + # Deserialize the document retrieved from Elasticsearch into a Ruby object. + # If no klass is set for the Repository then the raw document '_source' field will be returned. + # + # def deserialize(document) + # Note.new document[SOURCE] + # end + # + # @param [ Hash ] document The raw document. + # + # @return [ Object ] The deserialized object. + # + def deserialize(document) + klass ? klass.new(document[SOURCE]) : document[SOURCE] + end + + private # The key for document fields in an Elasticsearch query response. # @@ -26,21 +47,41 @@ module Serialize # TYPE = '_type'.freeze - # Serialize the object for storing it in Elasticsearch + IDS = [:id, 'id', :_id, '_id'].freeze + + # Get a document ID from the document (assuming Hash or Hash-like object) # - # In the default implementation, call the `to_hash` method on the passed object. + # @example + # repository.__get_id_from_document title: 'Test', id: 'abc123' + # => "abc123" # - def serialize(document) - document.to_hash + # @api private + # + def __get_id_from_document(document) + document[IDS.find { |id| document[id] }] end - # Deserialize the document retrieved from Elasticsearch into a Ruby object + # Extract a document ID from the document (assuming Hash or Hash-like object) # - # Use the `klass` property, if defined, otherwise try to get the class from the document's `_type`. + # @note Calling this method will *remove* the `id` or `_id` key from the passed object. # - def deserialize(document) - raise NameError.new(NO_CLASS_ERROR_MESSAGE) unless klass - klass.new document[SOURCE] + # @example + # options = { title: 'Test', id: 'abc123' } + # repository.__extract_id_from_document options + # # => "abc123" + # options + # # => { title: 'Test' } + # + # @api private + # + def __extract_id_from_document(document) + IDS.inject(nil) do |deleted, id| + if document[id] + document.delete(id) + else + deleted + end + end end end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb index 2327d23a6..6571e1078 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb @@ -12,13 +12,19 @@ module Store # repository.save(myobject) # => {"_index"=>"...", "_type"=>"...", "_id"=>"...", "_version"=>1, "created"=>true} # - # @return {Hash} The response from Elasticsearch + # @param [ Object ] document The document to save into Elasticsearch. + # @param [ Hash ] options The save request options. + # + # @return [ Hash ] The response from Elasticsearch # def save(document, options={}) serialized = serialize(document) - id = __get_id_from_document(serialized) - type = document_type - client.index( { index: index_name, type: type, id: id, body: serialized }.merge(options) ) + id = __get_id_from_document(serialized) + request = { index: index_name, + id: id, + body: serialized } + request[:type] = document_type if document_type + client.index(request.merge(options)) end # Update the serialized object in Elasticsearch with partial data or script @@ -33,38 +39,27 @@ def save(document, options={}) # repository.update 1, script: 'ctx._source.views += 1' # # => {"_index"=>"...", "_type"=>"...", "_id"=>"1", "_version"=>3} # - # @return {Hash} The response from Elasticsearch + # @param [ Object ] document_or_id The document to update or the id of the document to update. + # @param [ Hash ] options The update request options. # - def update(document, options={}) - case - when document.is_a?(String) || document.is_a?(Integer) - id = document - when document.respond_to?(:to_hash) - serialized = document.to_hash - id = __extract_id_from_document(serialized) - else - raise ArgumentError, "Expected a document ID or a Hash-like object, #{document.class} given" - end - - type = options.delete(:type) || \ - (defined?(serialized) && serialized && serialized.delete(:type)) || \ - document_type - - if defined?(serialized) && serialized - body = if serialized[:script] - serialized.select { |k, v| [:script, :params, :upsert].include? k } - else - { doc: serialized } - end + # @return [ Hash ] The response from Elasticsearch + # + def update(document_or_id, options = {}) + if document_or_id.is_a?(String) || document_or_id.is_a?(Integer) + id = document_or_id + body = options + type = document_type else - body = {} - body.update( doc: options.delete(:doc)) if options[:doc] - body.update( script: options.delete(:script)) if options[:script] - body.update( params: options.delete(:params)) if options[:params] - body.update( upsert: options.delete(:upsert)) if options[:upsert] + document = serialize(document_or_id) + id = __extract_id_from_document(document) + if options[:script] + body = options + else + body = { doc: document }.merge(options) + end + type = document.delete(:type) || document_type end - - client.update( { index: index_name, type: type, id: id, body: body }.merge(options) ) + client.update(index: index_name, id: id, type: type, body: body) end # Remove the serialized object or document with specified ID from Elasticsearch @@ -74,21 +69,21 @@ def update(document, options={}) # repository.delete(1) # # => {"_index"=>"...", "_type"=>"...", "_id"=>"1", "_version"=>4} # - # @return {Hash} The response from Elasticsearch + # @param [ Object ] document_or_id The document to delete or the id of the document to delete. + # @param [ Hash ] options The delete request options. # - def delete(document, options={}) - if document.is_a?(String) || document.is_a?(Integer) - id = document - type = document_type + # @return [ Hash ] The response from Elasticsearch + # + def delete(document_or_id, options = {}) + if document_or_id.is_a?(String) || document_or_id.is_a?(Integer) + id = document_or_id else - serialized = serialize(document) - id = __get_id_from_document(serialized) - type = document_type + serialized = serialize(document_or_id) + id = __get_id_from_document(serialized) end - client.delete( { index: index_name, type: type, id: id }.merge(options) ) + client.delete({ index: index_name, type: document_type, id: id }.merge(options)) end end - end end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb index c27d0fb54..393bf1343 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb @@ -1,5 +1,5 @@ module Elasticsearch module Persistence - VERSION = "7.0.0" + VERSION = '7.0.0' end end diff --git a/elasticsearch-persistence/spec/repository/find_spec.rb b/elasticsearch-persistence/spec/repository/find_spec.rb new file mode 100644 index 000000000..b630d45a2 --- /dev/null +++ b/elasticsearch-persistence/spec/repository/find_spec.rb @@ -0,0 +1,179 @@ +require 'spec_helper' + +describe Elasticsearch::Persistence::Repository::Find do + + after do + begin; repository.delete_index!; rescue; end + end + + let(:repository) do + DEFAULT_REPOSITORY + end + + describe '#exists?' do + + context 'when the document exists' do + + let(:id) do + repository.save(a: 1)['_id'] + end + + it 'returns true' do + expect(repository.exists?(id)).to be(true) + end + end + + context 'when the document does not exist' do + + it 'returns false' do + expect(repository.exists?('1')).to be(false) + end + end + + context 'when options are provided' do + + let(:id) do + repository.save(a: 1)['_id'] + end + + it 'applies the options' do + expect(repository.exists?(id, type: 'other_type')).to be(false) + end + end + end + + describe '#find' do + + context 'when options are not provided' do + + context 'when a single id is provided' do + + let!(:id) do + repository.save(a: 1)['_id'] + end + + it 'retrieves the document' do + expect(repository.find(id)).to eq('a' => 1) + end + end + + context 'when an array of ids is provided' do + + let!(:ids) do + 3.times.collect do |i| + repository.save(a: i)['_id'] + end + end + + it 'retrieves the documents' do + expect(repository.find(ids)).to eq([{ 'a' =>0 }, + { 'a' => 1 }, + { 'a' => 2 }]) + end + + context 'when some documents are found and some are not' do + + before do + ids[1] = 22 + ids + end + + it 'returns nil in the result list for the documents not found' do + expect(repository.find(ids)).to eq([{ 'a' =>0 }, + nil, + { 'a' => 2 }]) + end + end + end + + context 'when multiple ids are provided' do + + let!(:ids) do + 3.times.collect do |i| + repository.save(a: i)['_id'] + end + end + + it 'retrieves the documents' do + expect(repository.find(*ids)).to eq([{ 'a' =>0 }, + { 'a' => 1 }, + { 'a' => 2 }]) + end + end + + context 'when the document cannot be found' do + + before do + begin; repository.create_index!; rescue; end + end + + it 'raises a DocumentNotFound exception' do + expect { + repository.find(1) + }.to raise_exception(Elasticsearch::Persistence::Repository::DocumentNotFound) + end + end + end + + context 'when options are provided' do + + context 'when a single id is passed' do + + let!(:id) do + repository.save(a: 1)['_id'] + end + + it 'applies the options' do + expect { + repository.find(id, type: 'none') + }.to raise_exception(Elasticsearch::Persistence::Repository::DocumentNotFound) + end + end + + context 'when an array of ids is passed' do + + let!(:ids) do + 3.times.collect do |i| + repository.save(a: i)['_id'] + end + end + + it 'applies the options' do + expect(repository.find(ids, type: 'none')).to eq([nil, nil, nil]) + end + end + + context 'when multiple ids are passed' do + + let!(:ids) do + 3.times.collect do |i| + repository.save(a: i)['_id'] + end + end + + it 'applies the options' do + expect(repository.find(*ids, type: 'none')).to eq([nil, nil, nil]) + end + end + end + + context 'when a document_type is defined on the class' do + + let(:repository) do + MyTestRepository.new(document_type:'other_type', client: DEFAULT_CLIENT, index_name: 'test') + end + + let!(:ids) do + 3.times.collect do |i| + repository.save(a: i)['_id'] + end + end + + it 'uses the document type in the query' do + expect(repository.find(ids)).to eq([{ 'a' =>0 }, + { 'a' => 1 }, + { 'a' => 2 }]) + end + end + end +end diff --git a/elasticsearch-persistence/spec/repository/search_spec.rb b/elasticsearch-persistence/spec/repository/search_spec.rb new file mode 100644 index 000000000..939f09731 --- /dev/null +++ b/elasticsearch-persistence/spec/repository/search_spec.rb @@ -0,0 +1,181 @@ +require 'spec_helper' + +describe Elasticsearch::Persistence::Repository::Search do + + after do + begin; repository.delete_index!; rescue; end + end + + describe '#search' do + + let(:repository) do + DEFAULT_REPOSITORY + end + + context 'when the repository does not have a type set' do + + before do + repository.save({ name: 'user' }, refresh: true) + end + + context 'when a query definition is provided as a hash' do + + it 'uses the default document type' do + expect(repository.search({ query: { match: { name: 'user' } } }).first).to eq('name' => 'user') + end + end + + context 'when a query definition is provided as a string' do + + it 'uses the default document type' do + expect(repository.search('user').first).to eq('name' => 'user') + end + end + + context 'when the query definition is neither a String nor a Hash' do + + it 'raises an ArgumentError' do + expect { + repository.search(1) + }.to raise_exception(ArgumentError) + end + end + + context 'when options are provided' do + + context 'when a query definition is provided as a hash' do + + it 'uses the default document type' do + expect(repository.search({ query: { match: { name: 'user' } } }, type: 'other').first).to be_nil + end + end + + context 'when a query definition is provided as a string' do + + it 'uses the default document type' do + expect(repository.search('user', type: 'other').first).to be_nil + end + end + + context 'when the query definition is neither a String nor a Hash' do + + it 'raises an ArgumentError' do + expect { + repository.search(1) + }.to raise_exception(ArgumentError) + end + end + end + end + + context 'when the repository does have a type set' do + + let(:repository) do + MyTestRepository.new(document_type: 'other_note') + end + + before do + repository.save({ name: 'user' }, refresh: true) + end + + context 'when options are provided' do + + context 'when a query definition is provided as a hash' do + + it 'uses the options' do + expect(repository.search({ query: { match: { name: 'user' } } }, type: 'other').first).to be_nil + end + end + + context 'when a query definition is provided as a string' do + + it 'uses the options' do + expect(repository.search('user', type: 'other').first).to be_nil + end + end + + context 'when the query definition is neither a String nor a Hash' do + + it 'raises an ArgumentError' do + expect { + repository.search(1) + }.to raise_exception(ArgumentError) + end + end + end + end + end + + describe '#count' do + + context 'when the repository does not have a type set' do + + let(:repository) do + DEFAULT_REPOSITORY + end + + before do + repository.save({ name: 'user' }, refresh: true) + end + + context 'when a query definition is provided as a hash' do + + it 'uses the default document type' do + expect(repository.count({ query: { match: { name: 'user' } } })).to eq(1) + end + end + + context 'when a query definition is provided as a string' do + + it 'uses the default document type' do + expect(repository.count('user')).to eq(1) + end + end + + context 'when options are provided' do + + context 'when a query definition is provided as a hash' do + + it 'uses the options' do + expect(repository.count({ query: { match: { name: 'user' } } }, type: 'other')).to eq(0) + end + end + + context 'when a query definition is provided as a string' do + + it 'uses the options' do + expect(repository.count('user', type: 'other')).to eq(0) + end + end + end + end + + context 'when the repository does have a type set' do + + let(:repository) do + MyTestRepository.new(document_type: 'other_note') + end + + before do + repository.save({ name: 'user' }, refresh: true) + end + + context 'when options are provided' do + + context 'when a query definition is provided as a hash' do + + it 'uses the options' do + expect(repository.count({ query: { match: { name: 'user' } } }, type: 'other')).to eq(0) + end + end + + context 'when a query definition is provided as a string' do + + it 'uses the options' do + expect(repository.count('user', type: 'other')).to eq(0) + end + end + end + end + end +end diff --git a/elasticsearch-persistence/spec/repository/serialize_spec.rb b/elasticsearch-persistence/spec/repository/serialize_spec.rb new file mode 100644 index 000000000..bf0d7175e --- /dev/null +++ b/elasticsearch-persistence/spec/repository/serialize_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe Elasticsearch::Persistence::Repository::Serialize do + + let(:repository) do + DEFAULT_REPOSITORY + end + + describe '#serialize' do + + before do + class MyDocument + def to_hash + { a: 1 } + end + end + end + + it 'calls #to_hash on the object' do + expect(repository.serialize(MyDocument.new)).to eq(a: 1) + end + end + + describe '#deserialize' do + + context 'when klass is defined on the Repository' do + + let(:repository) do + require 'set' + MyTestRepository.new(klass: Set) + end + + it 'instantiates an object of the klass' do + expect(repository.deserialize('_source' => { a: 1 })).to be_a(Set) + end + + it 'uses the source field to instantiate the object' do + expect(repository.deserialize('_source' => { a: 1 })).to eq(Set.new({ a: 1})) + end + end + + context 'when klass is not defined on the Repository' do + + it 'returns the raw Hash' do + expect(repository.deserialize('_source' => { a: 1 })).to be_a(Hash) + end + + it 'uses the source field to instantiate the object' do + expect(repository.deserialize('_source' => { a: 1 })).to eq(a: 1) + end + end + end +end diff --git a/elasticsearch-persistence/spec/repository/store_spec.rb b/elasticsearch-persistence/spec/repository/store_spec.rb new file mode 100644 index 000000000..c42c811d2 --- /dev/null +++ b/elasticsearch-persistence/spec/repository/store_spec.rb @@ -0,0 +1,327 @@ +require 'spec_helper' + +describe Elasticsearch::Persistence::Repository::Store do + + let(:repository) do + DEFAULT_REPOSITORY + end + + after do + begin; repository.delete_index!; rescue; end + end + + describe '#save' do + + let(:document) do + { a: 1 } + end + + let(:response) do + repository.save(document) + end + + it 'saves the document' do + expect(repository.find(response['_id'])).to eq('a' => 1) + end + + context 'when the repository defines a custom serialize method' do + + before do + class OtherNoteRepository + include Elasticsearch::Persistence::Repository + def serialize(document) + { b: 1 } + end + end + end + + after do + if defined?(OtherNoteRepository) + Object.send(:remove_const, OtherNoteRepository.name) + end + end + + let(:repository) do + OtherNoteRepository.new(client: DEFAULT_CLIENT) + end + + let(:response) do + repository.save(document) + end + + it 'saves the document' do + expect(repository.find(response['_id'])).to eq('b' => 1) + end + end + + context 'when options are provided' do + + let!(:response) do + repository.save(document, type: 'other_note') + end + + it 'saves the document using the options' do + expect { + repository.find(response['_id']) + }.to raise_exception(Elasticsearch::Persistence::Repository::DocumentNotFound) + expect(repository.find(response['_id'], type: 'other_note')).to eq('a' => 1) + end + end + end + + describe '#update' do + + before(:all) do + class Note + def to_hash + { text: 'testing', views: 0 } + end + end + end + + after(:all) do + if defined?(Note) + Object.send(:remove_const, :Note) + end + end + + context 'when the document exists' do + + let!(:id) do + repository.save(Note.new)['_id'] + end + + context 'when an id is provided' do + + context 'when a doc is specified in the options' do + + before do + repository.update(id, doc: { text: 'testing_2' }) + end + + it 'updates using the doc parameter' do + expect(repository.find(id)).to eq('text' => 'testing_2', 'views' => 0) + end + end + + context 'when a script is specified in the options' do + + before do + repository.update(id, script: { inline: 'ctx._source.views += 1' }) + end + + it 'updates using the script parameter' do + expect(repository.find(id)).to eq('text' => 'testing', 'views' => 1) + end + end + + context 'when params are specified in the options' do + + before do + repository.update(id, script: { inline: 'ctx._source.views += params.count', + params: { count: 2 } }) + end + + it 'updates using the script parameter' do + expect(repository.find(id)).to eq('text' => 'testing', 'views' => 2) + end + end + + context 'when upsert is specified in the options' do + + before do + repository.update(id, script: { inline: 'ctx._source.views += 1' }, + upsert: { text: 'testing_2' }) + end + + it 'executes the script' do + expect(repository.find(id)).to eq('text' => 'testing', 'views' => 1) + end + end + + context 'when doc_as_upsert is specified in the options' do + + before do + repository.update(id, doc: { text: 'testing_2' }, + doc_as_upsert: true) + end + + it 'performs an upsert' do + expect(repository.find(id)).to eq('text' => 'testing_2', 'views' => 0) + end + end + end + + context 'when a document is provided as the query criteria' do + + context 'when no options are provided' do + + before do + repository.update(id: id, text: 'testing_2') + end + + it 'updates using the id and the document as the doc parameter' do + expect(repository.find(id)).to eq('text' => 'testing_2', 'views' => 0) + end + end + + context 'when options are provided' do + + context 'when a doc is specified in the options' do + + before do + repository.update({ id: id, text: 'testing' }, doc: { text: 'testing_2' }) + end + + it 'updates using the id and the doc in the options' do + expect(repository.find(id)).to eq('text' => 'testing_2', 'views' => 0) + end + end + + context 'when a script is specified in the options' do + + before do + repository.update({ id: id, text: 'testing' }, + script: { inline: 'ctx._source.views += 1' }) + end + + it 'updates using the id and script from the options' do + expect(repository.find(id)).to eq('text' => 'testing', 'views' => 1) + end + end + + context 'when params are specified in the options' do + + before do + repository.update({ id: id, text: 'testing' }, + script: { inline: 'ctx._source.views += params.count', + params: { count: 2 } }) + end + + it 'updates using the id and script and params from the options' do + expect(repository.find(id)).to eq('text' => 'testing', 'views' => 2) + end + end + + context 'when upsert is specified in the options' do + + before do + repository.update({ id: id, text: 'testing_2' }, + doc_as_upsert: true) + end + + it 'updates using the id and script and params from the options' do + expect(repository.find(id)).to eq('text' => 'testing_2', 'views' => 0) + end + end + end + end + end + + context 'when the document does not exist' do + + context 'when an id is provided 'do + + it 'raises an exception' do + expect { + repository.update(1, doc: { text: 'testing_2' }) + }.to raise_exception(Elasticsearch::Transport::Transport::Errors::NotFound) + end + + context 'when upsert is provided' do + + before do + repository.update(1, doc: { text: 'testing' }, doc_as_upsert: true) + end + + it 'upserts the document' do + expect(repository.find(1)).to eq('text' => 'testing') + end + end + end + + context 'when a document is provided' do + + it 'raises an exception' do + expect { + repository.update(id: 1, text: 'testing_2') + }.to raise_exception(Elasticsearch::Transport::Transport::Errors::NotFound) + end + + context 'when upsert is provided' do + + before do + repository.update({ id: 1, text: 'testing' }, doc_as_upsert: true) + end + + it 'upserts the document' do + expect(repository.find(1)).to eq('text' => 'testing') + end + end + end + end + end + + describe '#delete' do + + before(:all) do + class Note + def to_hash + { text: 'testing', views: 0 } + end + end + end + + after(:all) do + if defined?(Note) + Object.send(:remove_const, :Note) + end + end + + context 'when the document exists' do + + let!(:id) do + repository.save(Note.new)['_id'] + end + + context 'an id is provided' do + + before do + repository.delete(id) + end + + it 'deletes the document using the id' do + expect { + repository.find(id) + }.to raise_exception(Elasticsearch::Persistence::Repository::DocumentNotFound) + end + end + + context 'when a document is provided' do + + before do + repository.delete(id: id, text: 'testing') + end + + it 'deletes the document using the document' do + expect { + repository.find(id) + }.to raise_exception(Elasticsearch::Persistence::Repository::DocumentNotFound) + end + end + end + + context 'when the document does not exist' do + + before do + repository.create_index! + end + + it 'raises an exception' do + expect { + repository.delete(1) + }.to raise_exception(Elasticsearch::Transport::Transport::Errors::NotFound) + end + end + end +end diff --git a/elasticsearch-persistence/spec/repository_spec.rb b/elasticsearch-persistence/spec/repository_spec.rb new file mode 100644 index 000000000..2d2d4d4ca --- /dev/null +++ b/elasticsearch-persistence/spec/repository_spec.rb @@ -0,0 +1,716 @@ +require 'spec_helper' + +describe Elasticsearch::Persistence::Repository do + + describe '#create' do + + before(:all) do + class RepositoryWithoutDSL + include Elasticsearch::Persistence::Repository + end + end + + after(:all) do + if defined?(RepositoryWithoutDSL) + Object.send(:remove_const, RepositoryWithoutDSL.name) + end + end + + it 'creates a repository object' do + expect(RepositoryWithoutDSL.create).to be_a(RepositoryWithoutDSL) + end + + context 'when options are provided' do + + let(:repository) do + RepositoryWithoutDSL.create(document_type: 'note') + end + + it 'sets the options on the instance' do + expect(repository.document_type).to eq('note') + end + end + + context 'when a block is passed' do + + let(:repository) do + RepositoryWithoutDSL.create(document_type: 'note') do + mapping dynamic: 'strict' do + indexes :foo + end + end + end + + it 'executes the block on the instance' do + expect(repository.mapping.to_hash).to eq(note: { dynamic: 'strict', properties: { foo: { type: 'text' } } }) + end + + context 'when options are provided in the args and set in the block' do + + let(:repository) do + RepositoryWithoutDSL.create(mapping: double('mapping', to_hash: {}), document_type: 'note') do + mapping dynamic: 'strict' do + indexes :foo + end + end + end + + it 'uses the options from the args' do + expect(repository.mapping.to_hash).to eq({}) + end + end + end + end + + describe '#initialize' do + + before(:all) do + class RepositoryWithoutDSL + include Elasticsearch::Persistence::Repository + end + end + + after(:all) do + if defined?(RepositoryWithoutDSL) + Object.send(:remove_const, RepositoryWithoutDSL.name) + end + end + + after do + begin; repository.delete_index!; rescue; end + end + + context 'when options are not provided' do + + let(:repository) do + RepositoryWithoutDSL.new + end + + it 'sets a default client' do + expect(repository.client).to be_a(Elasticsearch::Transport::Client) + end + + it 'sets a default document type' do + expect(repository.document_type).to eq('_doc') + end + + it 'sets a default index name' do + expect(repository.index_name).to eq('repository') + end + + it 'does not set a klass' do + expect(repository.klass).to be_nil + end + end + + context 'when options are provided' do + + let(:client) do + Elasticsearch::Transport::Client.new + end + + let(:repository) do + RepositoryWithoutDSL.new(client: client, document_type: 'user', index_name: 'users', klass: Array) + end + + it 'sets the client' do + expect(repository.client).to be(client) + end + + it 'sets document type' do + expect(repository.document_type).to eq('user') + end + + it 'sets index name' do + expect(repository.index_name).to eq('users') + end + + it 'sets the klass' do + expect(repository.klass).to eq(Array) + end + end + end + + context 'when the DSL module is included' do + + before(:all) do + class RepositoryWithDSL + include Elasticsearch::Persistence::Repository + include Elasticsearch::Persistence::Repository::DSL + + document_type 'note' + index_name 'notes_repo' + klass Hash + client DEFAULT_CLIENT + + settings number_of_shards: 1, number_of_replicas: 0 do + mapping dynamic: 'strict' do + indexes :foo do + indexes :bar + end + indexes :baz + end + end + end + end + + after(:all) do + if defined?(RepositoryWithDSL) + Object.send(:remove_const, RepositoryWithDSL.name) + end + end + + after do + begin; repository.delete_index; rescue; end + end + + context '#client' do + + it 'allows the value to be set only once on the class' do + RepositoryWithDSL.client(double('client', class: 'other_client')) + expect(RepositoryWithDSL.client).to be(DEFAULT_CLIENT) + end + + it 'sets the value at the class level' do + expect(RepositoryWithDSL.client).to be(DEFAULT_CLIENT) + end + + it 'sets the value as the default at the instance level' do + expect(RepositoryWithDSL.new.client).to be(DEFAULT_CLIENT) + end + + it 'allows the value to be overridden with options on the instance' do + expect(RepositoryWithDSL.new(client: double('client', instance: 'other')).client.instance).to eq('other') + end + end + + context '#klass' do + + it 'allows the value to be set only once on the class' do + RepositoryWithDSL.klass(Array) + expect(RepositoryWithDSL.klass).to eq(Hash) + end + + it 'sets the value at the class level' do + expect(RepositoryWithDSL.klass).to eq(Hash) + end + + it 'sets the value as the default at the instance level' do + expect(RepositoryWithDSL.new.klass).to eq(Hash) + end + + it 'allows the value to be overridden with options on the instance' do + expect(RepositoryWithDSL.new(klass: Array).klass).to eq(Array) + end + + context 'when nil is passed to the method' do + + before do + RepositoryWithDSL.klass(nil) + end + + it 'allows the value to be set only once' do + expect(RepositoryWithDSL.klass).to eq(Hash) + end + end + end + + context '#document_type' do + + it 'allows the value to be set only once on the class' do + RepositoryWithDSL.document_type('other_note') + expect(RepositoryWithDSL.document_type).to eq('note') + end + + it 'sets the value at the class level' do + expect(RepositoryWithDSL.document_type).to eq('note') + end + + it 'sets the value as the default at the instance level' do + expect(RepositoryWithDSL.new.document_type).to eq('note') + end + + it 'allows the value to be overridden with options on the instance' do + expect(RepositoryWithDSL.new(document_type: 'other_note').document_type).to eq('other_note') + end + end + + context '#index_name' do + + it 'allows the value to be set only once on the class' do + RepositoryWithDSL.index_name('other_name') + expect(RepositoryWithDSL.index_name).to eq('notes_repo') + end + + it 'sets the value at the class level' do + expect(RepositoryWithDSL.index_name).to eq('notes_repo') + end + + it 'sets the value as the default at the instance level' do + expect(RepositoryWithDSL.new.index_name).to eq('notes_repo') + end + + it 'allows the value to be overridden with options on the instance' do + expect(RepositoryWithDSL.new(index_name: 'other_notes_repo').index_name).to eq('other_notes_repo') + end + end + + describe '#create_index!' do + + context 'when the method is called on an instance' do + + let(:repository) do + RepositoryWithDSL.new + end + + before do + begin; repository.delete_index!; rescue; end + repository.create_index! + end + + it 'creates the index' do + expect(repository.index_exists?).to be(true) + end + end + + context 'when the method is called on the class' do + + it 'raises a NotImplementedError' do + expect { + RepositoryWithDSL.create_index! + }.to raise_exception(NotImplementedError) + end + end + end + + describe '#delete_index!' do + + context 'when the method is called on an instance' do + + let(:repository) do + RepositoryWithDSL.new + end + + before do + repository.create_index! + begin; repository.delete_index!; rescue; end + end + + it 'deletes the index' do + expect(repository.index_exists?).to be(false) + end + end + + context 'when the method is called on the class' do + + it 'raises a NotImplementedError' do + expect { + RepositoryWithDSL.delete_index! + }.to raise_exception(NotImplementedError) + end + end + end + + describe '#refresh_index!' do + + context 'when the method is called on an instance' do + + let(:repository) do + RepositoryWithDSL.new + end + + before do + repository.create_index! + end + + it 'refreshes the index' do + expect(repository.refresh_index!['_shards']).to be_a(Hash) + end + end + + context 'when the method is called on the class' do + + it 'raises a NotImplementedError' do + expect { + RepositoryWithDSL.refresh_index! + }.to raise_exception(NotImplementedError) + end + end + end + + describe '#index_exists?' do + + context 'when the method is called on an instance' do + + let(:repository) do + RepositoryWithDSL.new + end + + before do + repository.create_index! + end + + it 'determines if the index exists' do + expect(repository.index_exists?).to be(true) + end + end + + context 'when the method is called on the class' do + + it 'raises a NotImplementedError' do + expect { + RepositoryWithDSL.index_exists? + }.to raise_exception(NotImplementedError) + end + end + end + + describe '#mapping' do + + let(:expected_mapping) do + { note: { dynamic: 'strict', + properties: { foo: { type: 'object', + properties: { bar: { type: 'text' } } }, + baz: { type: 'text' } } + } + } + end + + it 'sets the value at the class level' do + expect(RepositoryWithDSL.mapping.to_hash).to eq(expected_mapping) + end + + it 'sets the value as the default at the instance level' do + expect(RepositoryWithDSL.new.mapping.to_hash).to eq(expected_mapping) + end + + it 'allows the value to be overridden with options on the instance' do + expect(RepositoryWithDSL.new(mapping: double('mapping', to_hash: { note: {} })).mapping.to_hash).to eq(note: {}) + end + + context 'when the instance has a different document type' do + + let(:expected_mapping) do + { other_note: { dynamic: 'strict', + properties: { foo: { type: 'object', + properties: { bar: { type: 'text' } } }, + baz: { type: 'text' } } + } + } + end + + it 'updates the mapping to use the document type' do + expect(RepositoryWithDSL.new(document_type: 'other_note').mapping.to_hash).to eq(expected_mapping) + end + end + end + + describe '#settings' do + + it 'sets the value at the class level' do + expect(RepositoryWithDSL.settings.to_hash).to eq(number_of_shards: 1, number_of_replicas: 0) + end + + it 'sets the value as the default at the instance level' do + expect(RepositoryWithDSL.new.settings.to_hash).to eq(number_of_shards: 1, number_of_replicas: 0) + end + + it 'allows the value to be overridden with options on the instance' do + expect(RepositoryWithDSL.new(settings: { number_of_shards: 3 }).settings.to_hash).to eq({number_of_shards: 3}) + end + end + end + + context 'when the DSL module is not included' do + + before(:all) do + class RepositoryWithoutDSL + include Elasticsearch::Persistence::Repository + end + end + + after(:all) do + if defined?(RepositoryWithoutDSL) + Object.send(:remove_const, RepositoryWithoutDSL.name) + end + end + + context '#client' do + + it 'does not define the method at the class level' do + expect { + RepositoryWithoutDSL.client + }.to raise_exception(NoMethodError) + end + + it 'sets a default on the instance' do + expect(RepositoryWithoutDSL.new.client).to be_a(Elasticsearch::Transport::Client) + end + + it 'allows the value to be overridden with options on the instance' do + expect(RepositoryWithoutDSL.new(client: double('client', object_id: 123)).client.object_id).to eq(123) + end + end + + context '#klass' do + + it 'does not define the method at the class level' do + expect { + RepositoryWithoutDSL.klass + }.to raise_exception(NoMethodError) + end + + it 'does not set a default on an instance' do + expect(RepositoryWithoutDSL.new.klass).to be_nil + end + + it 'allows the value to be overridden with options on the instance' do + expect(RepositoryWithoutDSL.new(klass: Array).klass).to eq(Array) + end + end + + context '#document_type' do + + it 'does not define the method at the class level' do + expect { + RepositoryWithoutDSL.document_type + }.to raise_exception(NoMethodError) + end + + it 'sets a default on the instance' do + expect(RepositoryWithoutDSL.new.document_type).to eq('_doc') + end + + it 'allows the value to be overridden with options on the instance' do + expect(RepositoryWithoutDSL.new(document_type: 'notes').document_type).to eq('notes') + end + end + + context '#index_name' do + + it 'does not define the method at the class level' do + expect { + RepositoryWithoutDSL.index_name + }.to raise_exception(NoMethodError) + end + + it 'sets a default on the instance' do + expect(RepositoryWithoutDSL.new.index_name).to eq('repository') + end + + it 'allows the value to be overridden with options on the instance' do + expect(RepositoryWithoutDSL.new(index_name: 'notes_repository').index_name).to eq('notes_repository') + end + end + + describe '#create_index!' do + + let(:repository) do + RepositoryWithoutDSL.new(client: DEFAULT_CLIENT) + end + + after do + begin; repository.delete_index!; rescue; end + end + + it 'does not define the method at the class level' do + expect { + RepositoryWithoutDSL.create_index! + }.to raise_exception(NoMethodError) + end + + it 'creates an index' do + repository.create_index! + expect(repository.index_exists?).to eq(true) + end + end + + describe '#delete_index!' do + + let(:repository) do + RepositoryWithoutDSL.new(client: DEFAULT_CLIENT) + end + + it 'does not define the method at the class level' do + expect { + RepositoryWithoutDSL.delete_index! + }.to raise_exception(NoMethodError) + end + + it 'deletes an index' do + repository.create_index! + repository.delete_index! + expect(repository.index_exists?).to eq(false) + end + end + + describe '#refresh_index!' do + + let(:repository) do + RepositoryWithoutDSL.new(client: DEFAULT_CLIENT) + end + + after do + begin; repository.delete_index!; rescue; end + end + + it 'does not define the method at the class level' do + expect { + RepositoryWithoutDSL.refresh_index! + }.to raise_exception(NoMethodError) + end + + it 'refreshes an index' do + repository.create_index! + expect(repository.refresh_index!['_shards']).to be_a(Hash) + end + end + + describe '#index_exists?' do + + let(:repository) do + RepositoryWithoutDSL.new(client: DEFAULT_CLIENT) + end + + after do + begin; repository.delete_index!; rescue; end + end + + it 'does not define the method at the class level' do + expect { + RepositoryWithoutDSL.index_exists? + }.to raise_exception(NoMethodError) + end + + it 'returns whether the index exists' do + repository.create_index! + expect(repository.index_exists?).to be(true) + end + end + + describe '#mapping' do + + it 'does not define the method at the class level' do + expect { + RepositoryWithoutDSL.mapping + }.to raise_exception(NoMethodError) + end + + it 'sets a default on an instance' do + expect(RepositoryWithoutDSL.new.mapping.to_hash).to eq(_doc: { properties: {} }) + end + + it 'allows the mapping to be set as an option' do + expect(RepositoryWithoutDSL.new(mapping: double('mapping', to_hash: { note: {} })).mapping.to_hash).to eq(note: {}) + end + + context 'when a block is passed to the create method' do + + let(:expected_mapping) do + { note: { dynamic: 'strict', + properties: { foo: { type: 'object', + properties: { bar: { type: 'text' } } }, + baz: { type: 'text' } } + } + } + end + + let(:repository) do + RepositoryWithoutDSL.create(document_type: 'note') do + mapping dynamic: 'strict' do + indexes :foo do + indexes :bar + end + indexes :baz + end + end + end + + it 'allows the mapping to be set in the block' do + expect(repository.mapping.to_hash).to eq(expected_mapping) + end + + context 'when the mapping is set in the options' do + + let(:repository) do + RepositoryWithoutDSL.create(mapping: double('mapping', to_hash: { note: {} })) do + mapping dynamic: 'strict' do + indexes :foo do + indexes :bar + end + indexes :baz + end + end + end + + it 'uses the mapping from the options' do + expect(repository.mapping.to_hash).to eq(note: {}) + end + end + end + end + + describe '#settings' do + + it 'does not define the method at the class level' do + expect { + RepositoryWithoutDSL.settings + }.to raise_exception(NoMethodError) + end + + it 'sets a default on an instance' do + expect(RepositoryWithoutDSL.new.settings.to_hash).to eq({}) + end + + it 'allows the settings to be set as an option' do + expect(RepositoryWithoutDSL.new(settings: double('settings', to_hash: {})).settings.to_hash).to eq({}) + end + + context 'when a block is passed to the #create method' do + + let(:repository) do + RepositoryWithoutDSL.create(document_type: 'note') do + settings number_of_shards: 1, number_of_replicas: 0 + end + end + + it 'allows the settings to be set with a block' do + expect(repository.settings.to_hash).to eq(number_of_shards: 1, number_of_replicas: 0) + end + + context 'when a mapping is set in the block as well' do + + let(:expected_mapping) do + { note: { dynamic: 'strict', + properties: { foo: { type: 'object', + properties: { bar: { type: 'text' } } }, + baz: { type: 'text' } } + } + } + end + + let(:repository) do + RepositoryWithoutDSL.create(document_type: 'note') do + settings number_of_shards: 1, number_of_replicas: 0 do + mapping dynamic: 'strict' do + indexes :foo do + indexes :bar + end + indexes :baz + end + end + end + end + + it 'allows the settings to be set with a block' do + expect(repository.settings.to_hash).to eq(number_of_shards: 1, number_of_replicas: 0) + end + + it 'allows the mapping to be set with a block' do + expect(repository.mappings.to_hash).to eq(expected_mapping) + end + end + end + end + end +end diff --git a/elasticsearch-persistence/spec/spec_helper.rb b/elasticsearch-persistence/spec/spec_helper.rb new file mode 100644 index 000000000..9f2f0f5df --- /dev/null +++ b/elasticsearch-persistence/spec/spec_helper.rb @@ -0,0 +1,28 @@ +require 'pry-nav' +require 'elasticsearch/persistence' + +RSpec.configure do |config| + config.formatter = 'documentation' + config.color = true + + config.after(:suite) do + DEFAULT_CLIENT.indices.delete(index: '_all') + end +end + +# The default client to be used by the repositories. +# +# @since 6.0.0 +DEFAULT_CLIENT = Elasticsearch::Client.new(host: "localhost:#{(ENV['TEST_CLUSTER_PORT'] || 9250)}", + tracer: (ENV['QUIET'] ? nil : ::Logger.new(STDERR))) + +class MyTestRepository + include Elasticsearch::Persistence::Repository + include Elasticsearch::Persistence::Repository::DSL + client DEFAULT_CLIENT +end + +# The default repository to be used by tests. +# +# @since 6.0.0 +DEFAULT_REPOSITORY = MyTestRepository.new(index_name: 'my_test_repository', document_type: 'test') diff --git a/elasticsearch-persistence/test/integration/repository/custom_class_test.rb b/elasticsearch-persistence/test/integration/repository/custom_class_test.rb deleted file mode 100644 index 6528dd438..000000000 --- a/elasticsearch-persistence/test/integration/repository/custom_class_test.rb +++ /dev/null @@ -1,88 +0,0 @@ -require 'test_helper' - -module Elasticsearch - module Persistence - class RepositoryCustomClassIntegrationTest < Elasticsearch::Test::IntegrationTestCase - - class ::MyNote - attr_reader :attributes - - def initialize(attributes={}) - @attributes = Hashie::Mash.new(attributes) - end - - def method_missing(method_name, *arguments, &block) - attributes.respond_to?(method_name) ? attributes.__send__(method_name, *arguments, &block) : super - end - - def respond_to?(method_name, include_private=false) - attributes.respond_to?(method_name) || super - end - - def to_hash - @attributes - end - end - - context "A custom repository class" do - setup do - class ::MyNotesRepository - include Elasticsearch::Persistence::Repository - - klass MyNote - document_type 'my_note' - - settings number_of_shards: 1 do - mapping do - indexes :title, analyzer: 'snowball' - end - end - - create_index! - - def deserialize(document) - klass.new document.merge(document['_source']) - end - end - - @repository = MyNotesRepository.new - @repository.klass = MyNotesRepository.klass - @repository.document_type = MyNotesRepository.document_type - - @repository.client.cluster.health wait_for_status: 'yellow' - end - - should "save the object under a correct index and type" do - @repository.save MyNote.new(id: '1', title: 'Test') - result = @repository.find(1) - - assert_instance_of MyNote, result - assert_equal 'Test', result.title - - assert_not_nil Elasticsearch::Persistence.client.get index: 'my_notes_repository', - type: 'my_note', - id: '1' - end - - should "delete the object" do - note = MyNote.new id: 1, title: 'Test' - @repository.save note - - assert_not_nil @repository.find(1) - - @repository.delete(note) - assert_raise(Elasticsearch::Persistence::Repository::DocumentNotFound) { @repository.find(1) } - end - - should "retrieve the object via a search query" do - note = MyNote.new title: 'Testing' - @repository.save note, refresh: true - - results = @repository.search query: { match: { title: 'Test' } } - assert_equal 'Testing', results.first.title - end - end - - end - end -end diff --git a/elasticsearch-persistence/test/integration/repository/customized_class_test.rb b/elasticsearch-persistence/test/integration/repository/customized_class_test.rb deleted file mode 100644 index 06289b12d..000000000 --- a/elasticsearch-persistence/test/integration/repository/customized_class_test.rb +++ /dev/null @@ -1,82 +0,0 @@ -require 'test_helper' - -module Elasticsearch - module Persistence - class RepositoryCustomizedClassIntegrationTest < Elasticsearch::Test::IntegrationTestCase - - module ::My - class Note - attr_reader :attributes - - def initialize(attributes={}) - @attributes = attributes - end - - def to_hash - @attributes - end - end - end - - context "A custom repository class" do - setup do - @repository = Elasticsearch::Persistence::Repository.new do - index 'my_notes' - type 'my_note' - klass My::Note - - settings number_of_shards: 1 do - mapping do - indexes :title, analyzer: 'snowball' - end - end - - create_index! - end - - @repository.client.cluster.health wait_for_status: 'yellow' - end - - should "save the object under a correct index and type" do - @repository.save My::Note.new(id: '1', title: 'Test') - - assert_instance_of My::Note, @repository.find(1) - assert_not_nil Elasticsearch::Persistence.client.get index: 'my_notes', type: 'my_note', id: '1' - end - - should "update the document" do - @repository.save Note.new(id: 1, title: 'Test') - - @repository.update 1, doc: { title: 'UPDATED' } - assert_equal 'UPDATED', @repository.find(1).attributes['title'] - end - - should "update the document with a script" do - @repository.save Note.new(id: 1, title: 'Test') - - @repository.update 1, script: 'ctx._source.title = "UPDATED"' - assert_equal 'UPDATED', @repository.find(1).attributes['title'] - end - - should "delete the object" do - note = My::Note.new id: 1, title: 'Test' - @repository.save note - - assert_not_nil @repository.find(1) - - @repository.delete(note) - assert_raise(Elasticsearch::Persistence::Repository::DocumentNotFound) { @repository.find(1) } - end - - should "create the index with correct mapping" do - note = My::Note.new title: 'Testing' - @repository.save note, refresh: true - - results = @repository.search query: { match: { title: 'Test' } } - assert_equal 'Testing', results.first.attributes['title'] - end - end - - end - end -end diff --git a/elasticsearch-persistence/test/integration/repository/default_class_test.rb b/elasticsearch-persistence/test/integration/repository/default_class_test.rb deleted file mode 100644 index 1674a3a42..000000000 --- a/elasticsearch-persistence/test/integration/repository/default_class_test.rb +++ /dev/null @@ -1,119 +0,0 @@ -require 'test_helper' - -module Elasticsearch - module Persistence - class RepositoryDefaultClassIntegrationTest < Elasticsearch::Test::IntegrationTestCase - - class ::Note - attr_reader :attributes - - def initialize(attributes={}) - @attributes = attributes - end - - def to_hash - @attributes - end - end - - context "The default repository class" do - setup do - @repository = Elasticsearch::Persistence::Repository.new - @repository.klass = ::Note - @repository.document_type = 'note' - @repository.client.cluster.health wait_for_status: 'yellow' - end - - should "save the object with a custom ID and find it" do - @repository.save Note.new(id: '1', title: 'Test') - - assert_equal 'Test', @repository.find(1).attributes['title'] - end - - should "save the object with an auto-generated ID and find it" do - response = @repository.save Note.new(title: 'Test') - - assert_equal 'Test', @repository.find(response['_id']).attributes['title'] - end - - should "update the document" do - @repository.save Note.new(id: 1, title: 'Test') - - @repository.update 1, type: 'note', doc: { title: 'UPDATED' } - assert_equal 'UPDATED', @repository.find(1).attributes['title'] - end - - should "update the document with a script" do - @repository.save Note.new(id: 1, title: 'Test') - - @repository.update 1, type: 'note', script: 'ctx._source.title = "UPDATED"' - assert_equal 'UPDATED', @repository.find(1).attributes['title'] - end - - should "save the document with an upsert" do - @repository.update 1, type: 'note', script: 'ctx._source.clicks += 1', upsert: { clicks: 1 } - assert_equal 1, @repository.find(1).attributes['clicks'] - end - - should "delete an object" do - note = Note.new(id: '1', title: 'Test') - - @repository.save(note) - assert_not_nil @repository.find(1) - @repository.delete(note) - assert_raise(Elasticsearch::Persistence::Repository::DocumentNotFound) { @repository.find(1) } - end - - should "find multiple objects" do - (1..5).each { |i| @repository.save Note.new(id: i, title: "Test #{i}") } - - assert_equal 5, @repository.find(1, 2, 3, 4, 5).size - assert_equal 5, @repository.find([1, 2, 3, 4, 5]).size - end - - should "pass options to save and find" do - note = Note.new(id: '1', title: 'Test') - @repository.save note, routing: 'ABC' - - @repository.client.cluster.health level: 'indices', wait_for_status: 'yellow' - - assert_raise Elasticsearch::Persistence::Repository::DocumentNotFound do - @repository.find(1, routing: 'DEF') - end - - assert_nothing_raised do - note = @repository.find(1, routing: 'ABC') - assert_instance_of Note, note - end - end - - should "find notes with full text search" do - @repository.save Note.new(title: 'Test') - @repository.save Note.new(title: 'Test Test') - @repository.save Note.new(title: 'Crust') - @repository.client.indices.refresh index: @repository.index_name - - results = @repository.search 'test' - assert_equal 2, results.size - - results = @repository.search query: { match: { title: 'Test' } } - assert_equal 2, results.size - end - - should "count notes" do - @repository.save Note.new(title: 'Test') - @repository.client.indices.refresh index: @repository.index_name - assert_equal 1, @repository.count - end - - should "save and find a plain hash" do - @repository.klass = Hash - @repository.save id: 1, title: 'Hash' - result = @repository.find(1) - assert_equal 'Hash', result['_source']['title'] - end - end - - end - end -end diff --git a/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb b/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb deleted file mode 100644 index c6ad88177..000000000 --- a/elasticsearch-persistence/test/integration/repository/virtus_model_test.rb +++ /dev/null @@ -1,119 +0,0 @@ -require 'test_helper' - -require 'virtus' - -module Elasticsearch - module Persistence - class RepositoryWithVirtusIntegrationTest < Elasticsearch::Test::IntegrationTestCase - - class ::Page - include Virtus.model - - attribute :id, String, writer: :private - attribute :title, String - attribute :views, Integer, default: 0 - attribute :published, Boolean, default: false - attribute :slug, String, default: lambda { |page, attr| page.title.downcase.gsub(' ', '-') } - - def set_id(id) - self.id = id - end - end - - context "The repository with a Virtus model" do - setup do - @repository = Elasticsearch::Persistence::Repository.new do - index :pages - klass Page - document_type 'page' - - def deserialize(document) - page = klass.new document['_source'] - page.set_id document['_id'] - page - end - end - end - - should "save and find the object" do - page = Page.new title: 'Test Page' - - response = @repository.save page - id = response['_id'] - - result = @repository.find(id) - - assert_instance_of Page, result - assert_equal 'Test Page', result.title - assert_equal 0, result.views - - assert_not_nil Elasticsearch::Persistence.client.get index: 'pages', - type: 'page', - id: id - end - - should "update the object with a partial document" do - response = @repository.save Page.new(title: 'Test') - id = response['_id'] - - page = @repository.find(id) - - assert_equal 'Test', page.title - - @repository.update page.id, doc: { title: 'UPDATE' } - - page = @repository.find(id) - assert_equal 'UPDATE', page.title - end - - should "update the object with a Hash" do - response = @repository.save Page.new(title: 'Test') - id = response['_id'] - - page = @repository.find(id) - - assert_equal 'Test', page.title - - @repository.update id: page.id, title: 'UPDATE' - - page = @repository.find(id) - assert_equal 'UPDATE', page.title - end - - should "update the object with a script" do - response = @repository.save Page.new(title: 'Test Page') - id = response['_id'] - - page = @repository.find(id) - - assert_not_nil page.id - assert_equal 0, page.views - - @repository.update page.id, script: 'ctx._source.views += 1' - - page = @repository.find(id) - assert_equal 1, page.views - - @repository.update id: page.id, script: 'ctx._source.views += 1' - - page = @repository.find(id) - assert_equal 2, page.views - end - - should "update the object with a script and params" do - response = @repository.save Page.new(title: 'Test Page') - - @repository.update id: response['_id'], - script: { - inline: 'ctx._source.views += params.count', - params: { count: 3 } - } - - page = @repository.find(response['_id']) - assert_equal 3, page.views - end - end - - end - end -end diff --git a/elasticsearch-persistence/test/test_helper.rb b/elasticsearch-persistence/test/test_helper.rb deleted file mode 100644 index c881ac02e..000000000 --- a/elasticsearch-persistence/test/test_helper.rb +++ /dev/null @@ -1,55 +0,0 @@ -RUBY_1_8 = defined?(RUBY_VERSION) && RUBY_VERSION < '1.9' - -exit(0) if RUBY_1_8 - -$LOAD_PATH.unshift File.expand_path('../../../elasticsearch-model/lib', __FILE__) if File.exists? File.expand_path('../../../elasticsearch-model/lib', __FILE__) - -require 'simplecov' and SimpleCov.start { add_filter "/test|test_/" } if ENV["COVERAGE"] - -# Register `at_exit` handler for integration tests shutdown. -# MUST be called before requiring `test/unit`. -at_exit { Elasticsearch::Test::IntegrationTestCase.__run_at_exit_hooks } if ENV['SERVER'] - -if defined?(RUBY_VERSION) && RUBY_VERSION > '2.2' - require 'test-unit' - require 'mocha/test_unit' -else - require 'minitest/autorun' - require 'mocha/mini_test' -end - -require 'shoulda-context' - -require 'turn' unless ENV["TM_FILEPATH"] || ENV["NOTURN"] || defined?(RUBY_VERSION) && RUBY_VERSION > '2.2' - -require 'ansi' -require 'oj' unless defined?(JRUBY_VERSION) - -require 'elasticsearch/extensions/test/cluster' -require 'elasticsearch/extensions/test/startup_shutdown' - -require 'elasticsearch/persistence' - -module Elasticsearch - module Test - class IntegrationTestCase < ::Test::Unit::TestCase - extend Elasticsearch::Extensions::Test::StartupShutdown - - startup { Elasticsearch::Extensions::Test::Cluster.start(nodes: 1) if ENV['SERVER'] and not Elasticsearch::Extensions::Test::Cluster.running? } - shutdown { Elasticsearch::Extensions::Test::Cluster.stop if ENV['SERVER'] && started? } - context "IntegrationTest" do; should "noop on Ruby 1.8" do; end; end if RUBY_1_8 - - def setup - tracer = ::Logger.new(STDERR) - tracer.formatter = lambda { |s, d, p, m| "#{m.gsub(/^.*$/) { |n| ' ' + n }.ansi(:faint)}\n" } - Elasticsearch::Persistence.client = Elasticsearch::Client.new \ - host: "localhost:#{(ENV['TEST_CLUSTER_PORT'] || 9250)}", - tracer: (ENV['QUIET'] ? nil : tracer) - end - - def teardown - Elasticsearch::Persistence.client.indices.delete index: '_all' - end - end - end -end diff --git a/elasticsearch-persistence/test/unit/persistence_test.rb b/elasticsearch-persistence/test/unit/persistence_test.rb deleted file mode 100644 index cd17ba7dc..000000000 --- a/elasticsearch-persistence/test/unit/persistence_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Persistence::ModuleTest < Test::Unit::TestCase - context "The Persistence module" do - - context "client" do - should "have a default client" do - client = Elasticsearch::Persistence.client - assert_not_nil client - assert_instance_of Elasticsearch::Transport::Client, client - end - - should "allow to set a client" do - begin - Elasticsearch::Persistence.client = "Foobar" - assert_equal "Foobar", Elasticsearch::Persistence.client - ensure - Elasticsearch::Persistence.client = nil - end - end - - should "allow to set a client with DSL" do - begin - Elasticsearch::Persistence.client "Foobar" - assert_equal "Foobar", Elasticsearch::Persistence.client - ensure - Elasticsearch::Persistence.client = nil - end - end - end - end -end diff --git a/elasticsearch-persistence/test/unit/repository_class_test.rb b/elasticsearch-persistence/test/unit/repository_class_test.rb deleted file mode 100644 index d29711248..000000000 --- a/elasticsearch-persistence/test/unit/repository_class_test.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Persistence::RepositoryClassTest < Test::Unit::TestCase - context "The default repository class" do - - context "when initialized" do - should "be created from the module" do - repository = Elasticsearch::Persistence::Repository.new - assert_instance_of Elasticsearch::Persistence::Repository::Class, repository - end - - should "store and access the options" do - repository = Elasticsearch::Persistence::Repository::Class.new foo: 'bar' - assert_equal 'bar', repository.options[:foo] - end - - should "instance eval a passed block" do - $foo = 100 - repository = Elasticsearch::Persistence::Repository::Class.new() { $foo += 1 } - assert_equal 101, $foo - end - - should "call a passed block with self" do - foo = 100 - repository = Elasticsearch::Persistence::Repository::Class.new do |r| - assert_instance_of Elasticsearch::Persistence::Repository::Class, r - foo += 1 - end - assert_equal 101, foo - end - - should "configure the index name based on options" do - repository = Elasticsearch::Persistence::Repository::Class.new index: 'foobar' - assert_equal 'foobar', repository.index_name - end - end - - should "include the repository methods" do - repository = Elasticsearch::Persistence::Repository::Class.new - - %w( index_name document_type klass - mappings settings client client= - create_index! delete_index! refresh_index! - save delete serialize deserialize - exists? find search ).each do |method| - assert_respond_to repository, method - end - end - - end -end diff --git a/elasticsearch-persistence/test/unit/repository_client_test.rb b/elasticsearch-persistence/test/unit/repository_client_test.rb deleted file mode 100644 index 88e40193e..000000000 --- a/elasticsearch-persistence/test/unit/repository_client_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Persistence::RepositoryClientTest < Test::Unit::TestCase - context "The repository client" do - setup do - @shoulda_subject = Class.new() { include Elasticsearch::Persistence::Repository::Client }.new - end - - should "have a default client" do - assert_not_nil subject.client - assert_instance_of Elasticsearch::Transport::Client, subject.client - end - - should "allow to set a client" do - begin - subject.client = "Foobar" - assert_equal "Foobar", subject.client - ensure - subject.client = nil - end - end - - should "allow to set the client with DSL" do - begin - subject.client "Foobar" - assert_equal "Foobar", subject.client - ensure - subject.client = nil - end - end - end -end diff --git a/elasticsearch-persistence/test/unit/repository_find_test.rb b/elasticsearch-persistence/test/unit/repository_find_test.rb deleted file mode 100644 index bcf7f8a1f..000000000 --- a/elasticsearch-persistence/test/unit/repository_find_test.rb +++ /dev/null @@ -1,307 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Persistence::RepositoryFindTest < Test::Unit::TestCase - class MyDocument; end - - context "The repository" do - setup do - @shoulda_subject = Class.new() { include Elasticsearch::Persistence::Repository::Find }.new - - @client = mock - @shoulda_subject.stubs(:document_type).returns(nil) - @shoulda_subject.stubs(:klass).returns(nil) - @shoulda_subject.stubs(:index_name).returns('my_index') - @shoulda_subject.stubs(:client).returns(@client) - end - - context "find method" do - should "find one document when passed a single, literal ID" do - subject.expects(:__find_one).with(1, {}) - subject.find(1) - end - - should "find multiple documents when passed multiple IDs" do - subject.expects(:__find_many).with([1, 2], {}) - subject.find(1, 2) - end - - should "find multiple documents when passed an array of IDs" do - subject.expects(:__find_many).with([1, 2], {}) - subject.find([1, 2]) - end - - should "pass the options" do - subject.expects(:__find_one).with(1, { foo: 'bar' }) - subject.find(1, foo: 'bar') - - subject.expects(:__find_many).with([1, 2], { foo: 'bar' }) - subject.find([1, 2], foo: 'bar') - - subject.expects(:__find_many).with([1, 2], { foo: 'bar' }) - subject.find(1, 2, foo: 'bar') - end - end - - context "'exists?' method" do - should "return false when the document does not exist" do - @client.expects(:exists).returns(false) - assert_equal false, subject.exists?('1') - end - - should "return whether document for document_type exists" do - subject.expects(:document_type).twice.returns('my_document') - - @client - .expects(:exists) - .with do |arguments| - assert_equal 'my_document', arguments[:type] - assert_equal '1', arguments[:id] - true - end - .returns(true) - - assert_equal true, subject.exists?('1') - end - - should "return whether document exists using no document type" do - - @client - .expects(:exists) - .with do |arguments| - assert_equal nil, arguments[:type] - assert_equal '1', arguments[:id] - true - end - .returns(true) - - assert_equal true, subject.exists?('1') - end - - should "pass options to the client" do - @client.expects(:exists).with do |arguments| - assert_equal 'my_document', arguments[:type] - assert_equal 'foobarbam', arguments[:index] - assert_equal 'bambam', arguments[:routing] - true - end - - subject.exists? '1', index: 'foobarbam', routing: 'bambam', type: 'my_document' - end - end - - context "'__find_one' method" do - - should "find a document based on document_type and return a deserialized object" do - subject.expects(:document_type).twice.returns('my_document') - - subject.expects(:deserialize).with({'_source' => {'foo' => 'bar'}}).returns(MyDocument.new) - - @client - .expects(:get) - .with do |arguments| - assert_equal 'my_document', arguments[:type] - assert_equal '1', arguments[:id] - true - end - .returns({'_source' => { 'foo' => 'bar' }}) - - assert_instance_of MyDocument, subject.__find_one('1') - end - - should "find a document using no type if document_type is not defined" do - subject.expects(:document_type).returns(nil) - - subject.expects(:deserialize).with({'_source' => {'foo' => 'bar'}}).returns(MyDocument.new) - - @client - .expects(:get) - .with do |arguments| - assert_equal nil, arguments[:type] - assert_equal '1', arguments[:id] - true - end - .returns({'_source' => { 'foo' => 'bar' }}) - - assert_instance_of MyDocument, subject.__find_one('1') - end - - should "raise DocumentNotFound exception when the document cannot be found" do - subject.expects(:document_type).returns(nil) - - subject.expects(:deserialize).never - - @client - .expects(:get) - .raises(Elasticsearch::Transport::Transport::Errors::NotFound) - - assert_raise Elasticsearch::Persistence::Repository::DocumentNotFound do - subject.__find_one('foobar') - end - end - - should "pass other exceptions" do - subject.expects(:deserialize).never - - @client - .expects(:get) - .raises(RuntimeError) - - assert_raise RuntimeError do - subject.__find_one('foobar') - end - end - - should "pass options to the client" do - subject.expects(:deserialize) - - @client - .expects(:get) - .with do |arguments| - assert_equal 'foobarbam', arguments[:index] - assert_equal 'bambam', arguments[:routing] - true - end - .returns({'_source' => { 'foo' => 'bar' }}) - - subject.__find_one '1', index: 'foobarbam', routing: 'bambam', type: 'my_document' - end - end - - context "'__find_many' method" do - setup do - @response = {"docs"=> - [ {"_index"=>"my_index", - "_type"=>"note", - "_id"=>"1", - "_version"=>1, - "found"=>true, - "_source"=>{"id"=>"1", "title"=>"Test 1"}}, - - {"_index"=>"my_index", - "_type"=>"note", - "_id"=>"2", - "_version"=>1, - "found"=>true, - "_source"=>{"id"=>"2", "title"=>"Test 2"}} - ]} - end - - should "find documents based on document_type and return an Array of deserialized objects" do - subject.expects(:document_type).twice.returns('my_document') - - subject.expects(:deserialize).twice - - @client - .expects(:mget) - .with do |arguments| - assert_equal 'my_document', arguments[:type] - assert_equal ['1', '2'], arguments[:body][:ids] - true - end - .returns(@response) - - subject.__find_many(['1', '2']) - end - - should "find documents and return an Array of deserialized objects" do - subject.expects(:document_type).returns(nil) - - subject - .expects(:deserialize) - .with(@response['docs'][0]) - .returns(MyDocument.new) - - subject - .expects(:deserialize) - .with(@response['docs'][1]) - .returns(MyDocument.new) - - @client - .expects(:mget) - .with do |arguments| - assert_equal nil, arguments[:type] - assert_equal ['1', '2'], arguments[:body][:ids] - true - end - .returns(@response) - - results = subject.__find_many(['1', '2']) - - assert_equal 2, results.size - - assert_instance_of MyDocument, results[0] - assert_instance_of MyDocument, results[1] - end - - should "find keep missing documents in the result as nil" do - @response = {"docs"=> - [ {"_index"=>"my_index", - "_type"=>"note", - "_id"=>"1", - "_version"=>1, - "found"=>true, - "_source"=>{"id"=>"1", "title"=>"Test 1"}}, - - {"_index"=>"my_index", - "_type"=>"note", - "_id"=>"3", - "_version"=>1, - "found"=>false}, - - {"_index"=>"my_index", - "_type"=>"note", - "_id"=>"2", - "_version"=>1, - "found"=>true, - "_source"=>{"id"=>"2", "title"=>"Test 2"}} - ]} - - subject.expects(:document_type).twice.returns('my_document') - - subject - .expects(:deserialize) - .with(@response['docs'][0]) - .returns(MyDocument.new) - - subject - .expects(:deserialize) - .with(@response['docs'][2]) - .returns(MyDocument.new) - - @client - .expects(:mget) - .with do |arguments| - assert_equal 'my_document', arguments[:type] - assert_equal ['1', '3', '2'], arguments[:body][:ids] - true - end - .returns(@response) - - results = subject.__find_many(['1', '3', '2']) - - assert_equal 3, results.size - - assert_instance_of MyDocument, results[0] - assert_instance_of NilClass, results[1] - assert_instance_of MyDocument, results[2] - end - - should "pass options to the client" do - subject.expects(:deserialize).twice - - @client - .expects(:mget) - .with do |arguments| - assert_equal 'my_document', arguments[:type] - assert_equal 'foobarbam', arguments[:index] - assert_equal 'bambam', arguments[:routing] - true - end - .returns(@response) - - subject.__find_many ['1', '2'], index: 'foobarbam', routing: 'bambam', type: 'my_document' - end - end - - end -end diff --git a/elasticsearch-persistence/test/unit/repository_indexing_test.rb b/elasticsearch-persistence/test/unit/repository_indexing_test.rb deleted file mode 100644 index 51b2b8b21..000000000 --- a/elasticsearch-persistence/test/unit/repository_indexing_test.rb +++ /dev/null @@ -1,37 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Persistence::RepositoryIndexingTest < Test::Unit::TestCase - context "The repository index methods" do - class MyDocument; end - - setup do - @shoulda_subject = Class.new() { include Elasticsearch::Model::Indexing::ClassMethods }.new - @shoulda_subject.stubs(:index_name).returns('my_index') - @shoulda_subject.stubs(:document_type).returns('my_document') - end - - should "have the convenience index management methods" do - %w( create_index! delete_index! refresh_index! ).each do |method| - assert_respond_to subject, method - end - end - - context "mappings" do - should "configure the mappings for the type" do - subject.mappings do - indexes :title - end - - assert_equal( {:"my_document"=>{:properties=>{:title=>{:type=>"text"}}}}, subject.mappings.to_hash ) - end - end - - context "settings" do - should "configure the settings for the index" do - subject.settings foo: 'bar' - assert_equal( {foo: 'bar'}, subject.settings.to_hash) - end - end - - end -end diff --git a/elasticsearch-persistence/test/unit/repository_module_test.rb b/elasticsearch-persistence/test/unit/repository_module_test.rb deleted file mode 100644 index 2c4cd44c5..000000000 --- a/elasticsearch-persistence/test/unit/repository_module_test.rb +++ /dev/null @@ -1,146 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Persistence::RepositoryModuleTest < Test::Unit::TestCase - context "The repository module" do - - class DummyModel - def initialize(attributes={}) - @attributes = attributes - end - - def to_hash - @attributes - end - - def inspect - "<Note #{@attributes.inspect}>" - end - end - - setup do - class DummyRepository - include Elasticsearch::Persistence::Repository - end - end - - teardown do - Elasticsearch::Persistence::RepositoryModuleTest.__send__ :remove_const, :DummyRepository - end - - context "when included" do - should "set up the gateway for the class and instance" do - assert_respond_to DummyRepository, :gateway - assert_respond_to DummyRepository.new, :gateway - - assert_instance_of Elasticsearch::Persistence::Repository::Class, DummyRepository.gateway - assert_instance_of Elasticsearch::Persistence::Repository::Class, DummyRepository.new.gateway - end - - should "proxy repository methods from the class to the gateway" do - class DummyRepository - include Elasticsearch::Persistence::Repository - - index :foobar - klass DummyModel - type :my_dummy_model - mapping { indexes :title, analyzer: 'snowball' } - end - - repository = DummyRepository.new - - assert_equal :foobar, DummyRepository.index - assert_equal DummyModel, DummyRepository.klass - assert_equal :my_dummy_model, DummyRepository.type - assert_equal 'snowball', DummyRepository.mappings.to_hash[:my_dummy_model][:properties][:title][:analyzer] - - assert_equal :foobar, repository.index - assert_equal DummyModel, repository.klass - assert_equal :my_dummy_model, repository.type - assert_equal 'snowball', repository.mappings.to_hash[:my_dummy_model][:properties][:title][:analyzer] - end - - should "correctly delegate to the gateway" do - repository = DummyRepository.new - assert_instance_of Method, repository.method(:index) - end - - should "proxy repository methods from the instance to the gateway" do - class DummyRepository - include Elasticsearch::Persistence::Repository - end - - repository = DummyRepository.new - repository.index :foobar - repository.klass DummyModel - repository.type :my_dummy_model - repository.mapping { indexes :title, analyzer: 'snowball' } - - assert_equal :foobar, DummyRepository.index - assert_equal DummyModel, DummyRepository.klass - assert_equal :my_dummy_model, DummyRepository.type - assert_equal 'snowball', DummyRepository.mappings.to_hash[:my_dummy_model][:properties][:title][:analyzer] - - assert_equal :foobar, repository.index - assert_equal DummyModel, repository.klass - assert_equal :my_dummy_model, repository.type - assert_equal 'snowball', repository.mappings.to_hash[:my_dummy_model][:properties][:title][:analyzer] - end - - should "allow to define gateway methods in the class definition via block passed to the gateway method" do - class DummyRepositoryWithGatewaySerialize - include Elasticsearch::Persistence::Repository - - gateway do - def serialize(document) - 'FAKE!' - end - end - end - - repository = DummyRepositoryWithGatewaySerialize.new - repository.client.transport.logger = Logger.new(STDERR) - - client = mock - client.expects(:index).with do |arguments| - assert_equal('xxx', arguments[:id]) - assert_equal('FAKE!', arguments[:body]) - true - end - repository.gateway.expects(:client).returns(client) - - repository.gateway.expects(:__get_id_from_document).returns('xxx') - - repository.save( id: '123', foo: 'bar' ) - end - end - - should "allow to define gateway methods in the class definition via regular method definition" do - class DummyRepositoryWithDirectSerialize - include Elasticsearch::Persistence::Repository - - def serialize(document) - 'FAKE IN CLASS!' - end - end - - repository = DummyRepositoryWithDirectSerialize.new - repository.client.transport.logger = Logger.new(STDERR) - - client = mock - client.expects(:index).with do |arguments| - assert_equal('xxx', arguments[:id]) - assert_equal('FAKE IN CLASS!', arguments[:body]) - true - end - repository.gateway.expects(:client).returns(client) - - repository.gateway.expects(:__get_id_from_document).returns('xxx') - - repository.save( id: '123', foo: 'bar' ) - end - - should "configure the index name in the shortcut initializer" do - assert_equal 'repository', Elasticsearch::Persistence::Repository.new.index_name - end - end -end diff --git a/elasticsearch-persistence/test/unit/repository_naming_test.rb b/elasticsearch-persistence/test/unit/repository_naming_test.rb deleted file mode 100644 index aed34598f..000000000 --- a/elasticsearch-persistence/test/unit/repository_naming_test.rb +++ /dev/null @@ -1,117 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Persistence::RepositoryNamingTest < Test::Unit::TestCase - context "The repository naming" do - # Fake class for the naming tests - class ::Foobar; end - class ::FooBar; end - module ::Foo; class Bar; end; end - - setup do - @shoulda_subject = Class.new() { include Elasticsearch::Persistence::Repository::Naming }.new - end - - context "get an ID from the document" do - should "get an ID from Hash" do - assert_equal 1, subject.__get_id_from_document(id: 1) - assert_equal 1, subject.__get_id_from_document(_id: 1) - assert_equal 1, subject.__get_id_from_document('id' => 1) - assert_equal 1, subject.__get_id_from_document('_id' => 1) - end - end - - context "extract an ID from the document" do - should "delete the key from the Hash" do - d1 = { :id => 1 } - d2 = { :_id => 1 } - d3 = { 'id' => 1 } - d4 = { '_id' => 1 } - - assert_equal 1, subject.__extract_id_from_document(d1) - assert_nil d1[:id] - - assert_equal 1, subject.__extract_id_from_document(d2) - assert_nil d1[:_id] - - assert_equal 1, subject.__extract_id_from_document(d3) - assert_nil d1['id'] - - assert_equal 1, subject.__extract_id_from_document(d4) - assert_nil d1['_id'] - end - end - - context "document class name" do - should "be nil by default" do - assert_nil subject.klass - end - - should "be settable" do - subject.klass = Foobar - assert_equal Foobar, subject.klass - end - - should "be settable by DSL" do - subject.klass Foobar - assert_equal Foobar, subject.klass - end - end - - context "index_name" do - should "default to the class name" do - subject.instance_eval do - def self.class - 'FakeRepository' - end - end - - assert_equal 'fake_repository', subject.index_name - end - - should "be settable" do - subject.index_name = 'foobar1' - assert_equal 'foobar1', subject.index_name - - subject.index_name 'foobar2' - assert_equal 'foobar2', subject.index_name - end - - should "be aliased as `index`" do - subject.index_name = 'foobar1' - assert_equal 'foobar1', subject.index - end - - should "be inferred from the host class" do - class ::MySpecialRepository; end - subject.define_singleton_method(:host) { MySpecialRepository } - assert_equal 'my_special_repository', subject.index_name - end - end - - context "document_type" do - should "be the default doc type when no klass is set" do - assert_equal '_doc', subject.document_type - end - - should "does not use the klass" do - subject.klass Foobar - assert_equal '_doc', subject.document_type - end - - should "be aliased as `type`" do - subject.document_type 'foobar' - assert_equal 'foobar', subject.type - end - - should "be settable" do - subject.document_type = 'foobar' - assert_equal 'foobar', subject.document_type - end - - should "be settable by DSL" do - subject.document_type 'foobar' - assert_equal 'foobar', subject.document_type - end - end - end -end diff --git a/elasticsearch-persistence/test/unit/repository_response_results_test.rb b/elasticsearch-persistence/test/unit/repository_response_results_test.rb deleted file mode 100644 index df8e08ae5..000000000 --- a/elasticsearch-persistence/test/unit/repository_response_results_test.rb +++ /dev/null @@ -1,98 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Persistence::RepositoryResponseResultsTest < Test::Unit::TestCase - include Elasticsearch::Persistence - class MyDocument; end - - context "Response results" do - setup do - @repository = Repository.new - - @response = { "took" => 2, - "timed_out" => false, - "_shards" => {"total" => 5, "successful" => 5, "failed" => 0}, - "hits" => - { "total" => 2, - "max_score" => 0.19, - "hits" => - [{"_index" => "my_index", - "_type" => "note", - "_id" => "1", - "_score" => 0.19, - "_source" => {"id" => 1, "title" => "Test 1"}}, - - {"_index" => "my_index", - "_type" => "note", - "_id" => "2", - "_score" => 0.19, - "_source" => {"id" => 2, "title" => "Test 2"}} - ] - } - } - - @shoulda_subject = Repository::Response::Results.new @repository, @response - end - - should "provide the access to the repository" do - assert_instance_of Repository::Class, subject.repository - end - - should "provide the access to the response" do - assert_equal 5, subject.response['_shards']['total'] - end - - should "wrap the response in HashWrapper" do - assert_equal 5, subject.response._shards.total - end - - should "return the total" do - assert_equal 2, subject.total - end - - should "return the max_score" do - assert_equal 0.19, subject.max_score - end - - should "delegate methods to results" do - subject.repository - .expects(:deserialize) - .twice - .returns(MyDocument.new) - - assert_equal 2, subject.size - assert_respond_to subject, :each - end - - should "respond to missing" do - assert_instance_of Method, subject.method(:to_a) - end - - should "yield each object with hit" do - @shoulda_subject = Repository::Response::Results.new \ - @repository, - { 'hits' => { 'hits' => [{'_id' => '1', 'foo' => 'bar'}] } } - - subject.repository - .expects(:deserialize) - .returns('FOO') - - subject.each_with_hit do |object, hit| - assert_equal 'FOO', object - assert_equal 'bar', hit.foo - end - end - - should "map objects and hits" do - @shoulda_subject = Repository::Response::Results.new \ - @repository, - { 'hits' => { 'hits' => [{'_id' => '1', 'foo' => 'bar'}] } } - - subject.repository - .expects(:deserialize) - .returns('FOO') - - assert_equal ['FOO---bar'], subject.map_with_hit { |object, hit| "#{object}---#{hit.foo}" } - end - end - -end diff --git a/elasticsearch-persistence/test/unit/repository_search_test.rb b/elasticsearch-persistence/test/unit/repository_search_test.rb deleted file mode 100644 index 5cf6d5ab3..000000000 --- a/elasticsearch-persistence/test/unit/repository_search_test.rb +++ /dev/null @@ -1,100 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Persistence::RepositorySearchTest < Test::Unit::TestCase - class MyDocument; end - - context "The repository search" do - setup do - @shoulda_subject = Class.new() { include Elasticsearch::Persistence::Repository::Search }.new - - @client = mock - @shoulda_subject.stubs(:document_type).returns(nil) - @shoulda_subject.stubs(:klass).returns(nil) - @shoulda_subject.stubs(:index_name).returns('test') - @shoulda_subject.stubs(:client).returns(@client) - end - - should "search in type does not use the klass setting" do - @client.expects(:search).with do |arguments| - assert_equal 'test', arguments[:index] - assert_equal({foo: 'bar'}, arguments[:body]) - true - end - - subject.search foo: 'bar' - end - - should "search in type based on document_type" do - subject.expects(:document_type).returns('my_special_document').at_least_once - - @client.expects(:search).with do |arguments| - assert_equal 'test', arguments[:index] - assert_equal 'my_special_document', arguments[:type] - assert_equal({foo: 'bar'}, arguments[:body]) - true - end - - subject.search foo: 'bar' - end - - should "search across all types" do - subject.expects(:document_type).returns(nil).at_least_once - - @client.expects(:search).with do |arguments| - assert_equal 'test', arguments[:index] - assert_equal '_all', arguments[:type] - assert_equal({foo: 'bar'}, arguments[:body]) - true - end - - assert_instance_of Elasticsearch::Persistence::Repository::Response::Results, - subject.search({ foo: 'bar' }, type: '_all') - end - - should "pass options to the client" do - @client.expects(:search).twice.with do |arguments| - assert_equal 'bambam', arguments[:routing] - true - end - - assert_instance_of Elasticsearch::Persistence::Repository::Response::Results, - subject.search( {foo: 'bar'}, { routing: 'bambam' } ) - assert_instance_of Elasticsearch::Persistence::Repository::Response::Results, - subject.search( 'foobar', { routing: 'bambam' } ) - end - - should "search with simple search" do - @client.expects(:search).with do |arguments| - assert_equal 'foobar', arguments[:q] - true - end - - assert_instance_of Elasticsearch::Persistence::Repository::Response::Results, - subject.search('foobar') - end - - should "raise error for incorrect search definitions" do - assert_raise ArgumentError do - subject.search 123 - end - end - - should "return the number of domain objects" do - subject.client.expects(:count).returns({'count' => 1}) - assert_equal 1, subject.count - end - - should "pass arguments to count" do - subject.client.expects(:count) - .with do |arguments| - assert_equal 'test', arguments[:index] - assert_equal 'bar', arguments[:body][:query][:match][:foo] - assert_equal true, arguments[:ignore_unavailable] - true - end - .returns({'count' => 1}) - - assert_equal 1, subject.count( { query: { match: { foo: 'bar' } } }, { ignore_unavailable: true } ) - end - end -end diff --git a/elasticsearch-persistence/test/unit/repository_serialize_test.rb b/elasticsearch-persistence/test/unit/repository_serialize_test.rb deleted file mode 100644 index a69145464..000000000 --- a/elasticsearch-persistence/test/unit/repository_serialize_test.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Persistence::RepositorySerializeTest < Test::Unit::TestCase - context "The repository serialization" do - class DummyDocument - def to_hash - { foo: 'bar' } - end - end - - class MyDocument; end - - setup do - @shoulda_subject = Class.new() { include Elasticsearch::Persistence::Repository::Serialize }.new - end - - context "serialize" do - should "call #to_hash on passed object" do - document = DummyDocument.new - assert_equal( { foo: 'bar' }, subject.serialize(document)) - end - end - - context "deserialize" do - should "get the class name from #klass" do - subject.expects(:klass) - .returns(MyDocument).twice - - MyDocument.expects(:new) - - subject.deserialize( {} ) - end - - should "raise an error when klass isn't set" do - subject.expects(:klass).returns(nil) - - assert_raise(NameError) { subject.deserialize( {} ) } - end - end - end -end diff --git a/elasticsearch-persistence/test/unit/repository_store_test.rb b/elasticsearch-persistence/test/unit/repository_store_test.rb deleted file mode 100644 index b886eecdf..000000000 --- a/elasticsearch-persistence/test/unit/repository_store_test.rb +++ /dev/null @@ -1,219 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Persistence::RepositoryStoreTest < Test::Unit::TestCase - context "The repository store" do - class MyDocument; end - - setup do - @shoulda_subject = Class.new() do - include Elasticsearch::Persistence::Repository::Store - include Elasticsearch::Persistence::Repository::Naming - end.new - @shoulda_subject.stubs(:index_name).returns('test') - end - - context "save" do - - should "serialize the document, get type from document_type and index it" do - subject.expects(:serialize).returns({foo: 'bar'}) - - subject.expects(:document_type).returns('my_document') - - subject.expects(:__get_id_from_document).returns('1') - - client = mock - client.expects(:index).with do |arguments| - assert_equal 'my_document', arguments[:type] - assert_equal '1', arguments[:id] - assert_equal({foo: 'bar'}, arguments[:body]) - true - end - subject.expects(:client).returns(client) - - subject.save(MyDocument.new) - end - - should "pass the options to the client" do - subject.expects(:serialize).returns({foo: 'bar'}) - subject.expects(:document_type).returns(nil) - subject.expects(:__get_id_from_document).returns('1') - - client = mock - client.expects(:index).with do |arguments| - assert_equal 'my_document', arguments[:type] - assert_equal 'foobarbam', arguments[:index] - assert_equal 'bambam', arguments[:routing] - true - end - subject.expects(:client).returns(client) - - subject.save({foo: 'bar'}, { index: 'foobarbam', routing: 'bambam', type: 'my_document' }) - end - end - - context "update" do - should "get the ID from first argument and :doc from options" do - subject.expects(:serialize).never - subject.expects(:document_type).returns('mydoc') - subject.expects(:__extract_id_from_document).never - - client = mock - client.expects(:update).with do |arguments| - assert_equal '1', arguments[:id] - assert_equal 'mydoc', arguments[:type] - assert_equal({doc: { foo: 'bar' }}, arguments[:body]) - true - end - subject.expects(:client).returns(client) - - subject.update('1', doc: { foo: 'bar' }) - end - - should "get the ID from first argument and :script from options" do - subject.expects(:document_type).returns('mydoc') - subject.expects(:__extract_id_from_document).never - - client = mock - client.expects(:update).with do |arguments| - assert_equal '1', arguments[:id] - assert_equal 'mydoc', arguments[:type] - assert_equal({script: 'ctx._source.foo += 1'}, arguments[:body]) - true - end - subject.expects(:client).returns(client) - - subject.update('1', script: 'ctx._source.foo += 1') - end - - should "get the ID from first argument and :script with :upsert from options" do - subject.expects(:document_type).returns('mydoc') - subject.expects(:__extract_id_from_document).never - - client = mock - client.expects(:update).with do |arguments| - assert_equal '1', arguments[:id] - assert_equal 'mydoc', arguments[:type] - assert_equal({script: 'ctx._source.foo += 1', upsert: { foo: 1 }}, arguments[:body]) - true - end - subject.expects(:client).returns(client) - - subject.update('1', script: 'ctx._source.foo += 1', upsert: { foo: 1 }) - end - - should "get the ID and :doc from document" do - subject.expects(:document_type).returns('mydoc') - - client = mock - client.expects(:update).with do |arguments| - assert_equal '1', arguments[:id] - assert_equal 'mydoc', arguments[:type] - assert_equal({doc: { foo: 'bar' }}, arguments[:body]) - true - end - subject.expects(:client).returns(client) - - subject.update(id: '1', foo: 'bar') - end - - should "get the ID and :script from document" do - subject.expects(:document_type).returns('mydoc') - - client = mock - client.expects(:update).with do |arguments| - assert_equal '1', arguments[:id] - assert_equal 'mydoc', arguments[:type] - assert_equal({script: 'ctx._source.foo += 1'}, arguments[:body]) - true - end - subject.expects(:client).returns(client) - - subject.update(id: '1', script: 'ctx._source.foo += 1') - end - - should "get the ID and :script with :upsert from document" do - subject.expects(:document_type).returns('mydoc') - - client = mock - client.expects(:update).with do |arguments| - assert_equal '1', arguments[:id] - assert_equal 'mydoc', arguments[:type] - assert_equal({script: 'ctx._source.foo += 1', upsert: { foo: 1 } }, arguments[:body]) - true - end - subject.expects(:client).returns(client) - - subject.update(id: '1', script: 'ctx._source.foo += 1', upsert: { foo: 1 }) - end - - should "override the type from params" do - subject.expects(:document_type).never - - client = mock - client.expects(:update).with do |arguments| - assert_equal '1', arguments[:id] - assert_equal 'foo', arguments[:type] - assert_equal({script: 'ctx._source.foo += 1'}, arguments[:body]) - true - end - subject.expects(:client).returns(client) - - subject.update(id: '1', script: 'ctx._source.foo += 1', type: 'foo') - end - - should "raise an exception when passed incorrect argument" do - assert_raise(ArgumentError) { subject.update(MyDocument.new, foo: 'bar') } - end - end - - context "delete" do - - should "get ID from document and type from document_type when passed a document" do - subject.expects(:serialize).returns({id: '1', foo: 'bar'}) - - subject.expects(:document_type).returns('my_document') - - subject.expects(:__get_id_from_document).with({id: '1', foo: 'bar'}).returns('1') - - client = mock - client.expects(:delete).with do |arguments| - assert_equal 'my_document', arguments[:type] - assert_equal '1', arguments[:id] - true - end - subject.expects(:client).returns(client) - - subject.delete({id: '1', foo: 'bar'}) - end - - should "get ID and uses the default document type" do - subject.expects(:serialize).returns({id: '1', foo: 'bar'}) - subject.expects(:document_type).returns('_doc') - subject.expects(:__get_id_from_document).with({id: '1', foo: 'bar'}).returns('1') - - client = mock - client.expects(:delete).with do |arguments| - assert_equal '_doc', arguments[:type] - assert_equal '1', arguments[:id] - true - end - subject.expects(:client).returns(client) - - subject.delete(MyDocument.new) - end - - should "pass the options to the client" do - client = mock - client.expects(:delete).with do |arguments| - assert_equal 'my_document', arguments[:type] - assert_equal 'foobarbam', arguments[:index] - assert_equal 'bambam', arguments[:routing] - true - end - subject.expects(:client).returns(client) - - subject.delete('1', index: 'foobarbam', routing: 'bambam', type: 'my_document') - end - end - end -end From 049857aca8e38e20cb4a66f3a8c6f8d2329c7af8 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Mon, 13 Aug 2018 14:42:48 +0200 Subject: [PATCH 385/582] [STORE] Add missing Repository::Response::Results spec --- .../spec/repository/response/results_spec.rb | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 elasticsearch-persistence/spec/repository/response/results_spec.rb diff --git a/elasticsearch-persistence/spec/repository/response/results_spec.rb b/elasticsearch-persistence/spec/repository/response/results_spec.rb new file mode 100644 index 000000000..66a2cc6c0 --- /dev/null +++ b/elasticsearch-persistence/spec/repository/response/results_spec.rb @@ -0,0 +1,105 @@ +require 'spec_helper' + +describe Elasticsearch::Persistence::Repository::Response::Results do + + before(:all) do + class MyRepository + include Elasticsearch::Persistence::Repository + + def deserialize(document) + 'Object' + end + end + end + + let(:repository) do + MyRepository.new + end + + after(:all) do + if defined?(MyRepository) + Object.send(:remove_const, MyRepository.name) + end + end + + let(:response) do + { "took" => 2, + "timed_out" => false, + "_shards" => {"total" => 5, "successful" => 5, "failed" => 0}, + "hits" => + { "total" => 2, + "max_score" => 0.19, + "hits" => + [{"_index" => "my_index", + "_type" => "note", + "_id" => "1", + "_score" => 0.19, + "_source" => {"id" => 1, "title" => "Test 1"}}, + + {"_index" => "my_index", + "_type" => "note", + "_id" => "2", + "_score" => 0.19, + "_source" => {"id" => 2, "title" => "Test 2"}} + ] + } + } + end + + let(:results) do + described_class.new(repository, response) + end + + describe '#repository' do + + it 'should return the repository' do + expect(results.repository).to be(repository) + end + end + + describe '#response' do + + it 'returns the response' do + expect(results.response).to eq(response) + end + + it 'wraps the response in a HashWrapper' do + expect(results.response._shards.total).to eq(5) + end + end + + describe '#total' do + + it 'returns the total' do + expect(results.total).to eq(2) + end + end + + describe '#max_score' do + + it 'returns the max score' do + expect(results.max_score).to eq(0.19) + end + end + + describe '#each' do + + it 'delegates the method to the results' do + expect(results.size).to eq(2) + end + end + + describe '#each_with_hit' do + + it 'returns each deserialized object with the raw document' do + expect(results.each_with_hit { |pair| pair[0] = 'Obj'}).to eq(['Obj', 'Obj'].zip(response['hits']['hits'])) + end + end + + describe '#map_with_hit' do + + it 'returns the result of the block called on a pair of each raw document and the deserialized object' do + expect(results.map_with_hit { |pair| pair[0] }).to eq(['Object', 'Object']) + end + end +end From f29501d712e0958a978b10ab94e150a0a7e25fe6 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Mon, 13 Aug 2018 17:06:32 +0200 Subject: [PATCH 386/582] [STORE] Update README for Repository mixin refactor --- elasticsearch-persistence/README.md | 225 ++++++++++++++++++++-------- 1 file changed, 162 insertions(+), 63 deletions(-) diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index e95a7f005..d933c9519 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -1,6 +1,6 @@ # Elasticsearch::Persistence -Persistence layer for Ruby domain objects in Elasticsearch, using the Repository and ActiveRecord patterns. +Persistence layer for Ruby domain objects in Elasticsearch, using the Repository pattern. ## Compatibility @@ -14,6 +14,7 @@ is compatible with the Elasticsearch `master` branch, therefore, with the next m | 0.1 | → | 1.x | | 2.x | → | 2.x | | 5.x | → | 5.x | +| 6.x | → | 6.x | | master | → | master | ## Installation @@ -24,7 +25,7 @@ Install the package from [Rubygems](https://rubygems.org): To use an unreleased version, either add it to your `Gemfile` for [Bundler](http://bundler.io): - gem 'elasticsearch-persistence', git: 'git://github.com/elastic/elasticsearch-rails.git', branch: '5.x' + gem 'elasticsearch-persistence', git: 'git://github.com/elastic/elasticsearch-rails.git', branch: '6.x' or install it from a source code checkout: @@ -35,9 +36,7 @@ or install it from a source code checkout: ## Usage -The library provides two different patterns for adding persistence to your Ruby objects: - -* [Repository Pattern](#the-repository-pattern) +The library provides the Repository pattern for adding persistence to your Ruby objects. ### The Repository Pattern @@ -67,7 +66,8 @@ Let's create a default, "dumb" repository, as a first step: ```ruby require 'elasticsearch/persistence' -repository = Elasticsearch::Persistence::Repository.new +class MyRepository; include Elasticsearch::Persistence::Repository; end +repository = MyRepository.new ``` We can save a `Note` instance into the repository... @@ -120,32 +120,17 @@ The repository module provides a number of features and facilities to configure * Providing access to the Elasticsearch response for search results (aggregations, total, ...) * Defining the methods for serialization and deserialization -You can use the default repository class, or include the module in your own. Let's review it in detail. +There are two mixins you can include in your Repository class. The first `Elasticsearch::Persistence::Repository`, +provides the basic methods and settings you'll need. The second, `Elasticsearch::Persistence::Repository::DSL` adds +some additional class methods that allow you to set options that instances of the class will share. -#### The Default Class +#### Basic Repository mixin -For simple cases, you can use the default, bundled repository class, and configure/customize it: +For simple cases, you can just include the Elasticsearch::Persistence::Repository mixin to your class: ```ruby -repository = Elasticsearch::Persistence::Repository.new do - # Configure the Elasticsearch client - client Elasticsearch::Client.new url: ENV['ELASTICSEARCH_URL'], log: true - - # Set a custom index name - index :my_notes - - # Set a custom document type - type :my_note - - # Specify the class to initialize when deserializing documents - klass Note - - # Configure the settings and mappings for the Elasticsearch index - settings number_of_shards: 1 do - mapping do - indexes :text, analyzer: 'snowball' - end - end +class MyRepository + include Elasticsearch::Persistence::Repository # Customize the serialization logic def serialize(document) @@ -154,10 +139,18 @@ repository = Elasticsearch::Persistence::Repository.new do # Customize the de-serialization logic def deserialize(document) - puts "# ***** CUSTOM DESERIALIZE LOGIC KICKING IN... *****" + puts "# ***** CUSTOM DESERIALIZE LOGIC... *****" super end end + +client = Elasticsearch::Client.new(url: ENV['ELASTICSEARCH_URL'], log: true) +repository = MyRepository.new(client: client, index_name: :my_notes, type: :my_note, klass: Note) +repository.settings number_of_shards: 1 do + mapping do + indexes :text, analyzer: 'snowball' + end +end ``` The custom Elasticsearch client will be used now, with a custom index and type names, @@ -188,28 +181,27 @@ repository.find(1) <Note:0x007f9bd782b7a0 @attributes={... "my_special_key"=>"my_special_stuff"}> ``` -#### A Custom Class +#### The DSL mixin -In most cases, though, you'll want to use a custom class for the repository, so let's do that: +In some cases, you'll want to set some of the repository configurations at the class level. This makes +most sense when the instances of the repository will use that same configuration: ```ruby require 'base64' class NoteRepository include Elasticsearch::Persistence::Repository + include Elasticsearch::Persistence::Repository::DSL - def initialize(options={}) - index options[:index] || 'notes' - client Elasticsearch::Client.new url: options[:url], log: options[:log] - end - + index_name 'notes' + document_type 'note' klass Note settings number_of_shards: 1 do mapping do indexes :text, analyzer: 'snowball' # Do not index images - indexes :image, index: 'no' + indexes :image, index: false end end @@ -231,23 +223,26 @@ class NoteRepository end ``` -Include the `Elasticsearch::Persistence::Repository` module to add the repository methods into the class. +You can create an instance of this custom class and get each of the configurations. -You can customize the repository in the familiar way, by calling the DSL-like methods. +```ruby +client = Elasticsearch::Client.new(url: 'http://localhost:9200', log: true) +repository = NoteRepository.new(client: client) +repository.index_name +# => 'notes' -You can implement a custom initializer for your repository, add complex logic in its -class and instance methods -- in general, have all the freedom of a standard Ruby class. +``` + +You can also override the default configuration with options passed to the initialize method: ```ruby -repository = NoteRepository.new url: 'http://localhost:9200', log: true +client = Elasticsearch::Client.new(url: 'http://localhost:9250', log: true) +client.transport.logger.formatter = proc { |s, d, p, m| "\e[2m# #{m}\n\e[0m" } +repository = NoteRepository.new(client: client, index_name: 'notes_development') -# Configure the repository instance -repository.index = 'notes_development' -repository.client.transport.logger.formatter = proc { |s, d, p, m| "\e[2m# #{m}\n\e[0m" } +repository.create_index!(force: true) -repository.create_index! force: true - -note = Note.new 'id' => 1, 'text' => 'Document with image', 'image' => '... BINARY DATA ...' +note = Note.new('id' => 1, 'text' => 'Document with image', 'image' => '... BINARY DATA ...') repository.save(note) # PUT http://localhost:9200/notes_development/note/1 @@ -258,47 +253,110 @@ puts repository.find(1).attributes['image'] # => ... BINARY DATA ... ``` -#### Methods Provided by the Repository +#### Functionality Provided by the Repository mixin + +Each of the following configurations can be set for a repository instance. +If you have included the `Elasticsearch::Persistence::Repository::DSL` mixin, then you can use the class-level DSL +methods to set each configuration. You can override the configuration for any instance by passing options to the +`#initialize` method. +If you don't use the DSL mixin, you can set also the instance configuration with options passed the `#initialize` method. ##### Client -The repository uses the standard Elasticsearch [client](https://github.com/elastic/elasticsearch-ruby#usage), -which is accessible with the `client` getter and setter methods: +The repository uses the standard Elasticsearch [client](https://github.com/elastic/elasticsearch-ruby#usage). ```ruby -repository.client = Elasticsearch::Client.new url: 'http://search.server.org' +client = Elasticsearch::Client.new(url: 'http://search.server.org') +repository = NoteRepository.new(client: client) repository.client.transport.logger = Logger.new(STDERR) ``` +or with the DSL mixin: + +```ruby +class NoteRepository + include Elasticsearch::Persistence::Repository + include Elasticsearch::Persistence::Repository::DSL + + client Elasticsearch::Client.new url: 'http://search.server.org' +end + +repository = NoteRepository.new + +``` + ##### Naming -The `index` method specifies the Elasticsearch index to use for storage, lookup and search -(when not set, the value is inferred from the repository class name): +The `index_name` method specifies the Elasticsearch index to use for storage, lookup and search. The default index name +is 'repository'. + +```ruby +repository = NoteRepository.new(index_name: 'notes_development') +``` + +or with the DSL mixin: + +```ruby +class NoteRepository + include Elasticsearch::Persistence::Repository + include Elasticsearch::Persistence::Repository::DSL + + index_name 'notes_development' +end + +repository = NoteRepository.new + +``` + +The `type` method specifies the Elasticsearch document type to use for storage, lookup and search. The default value is +'_doc'. Keep in mind that future versions of Elasticsearch will not allow you to set this yourself and will use the type, +'_doc'. ```ruby -repository.index = 'notes_development' +repository = NoteRepository.new(document_type: 'note') ``` -The `type` method specifies the Elasticsearch document type to use for storage, lookup and search -(when not set, the value is inferred from the document class name, or `_all` is used): +or with the DSL mixin: ```ruby -repository.type = 'my_note' +class NoteRepository + include Elasticsearch::Persistence::Repository + include Elasticsearch::Persistence::Repository::DSL + + document_type 'note' +end + +repository = NoteRepository.new + ``` The `klass` method specifies the Ruby class name to use when initializing objects from -documents retrieved from the repository (when not set, the value is inferred from the -document `_type` as fetched from Elasticsearch): +documents retrieved from the repository. If this value is not set, a Hash representation of the document will be +returned instead. + +```ruby +repository = NoteRepository.new(klass: Note) +``` + +or with the DSL mixin: ```ruby -repository.klass = MyNote +class NoteRepository + include Elasticsearch::Persistence::Repository + include Elasticsearch::Persistence::Repository::DSL + + klass Note +end + +repository = NoteRepository.new + ``` ##### Index Configuration The `settings` and `mappings` methods, provided by the [`elasticsearch-model`](http://rubydoc.info/gems/elasticsearch-model/Elasticsearch/Model/Indexing/ClassMethods) -gem, allow to configure the index properties: +gem, allow you to configure the index properties: ```ruby repository.settings number_of_shards: 1 @@ -310,7 +368,39 @@ repository.mappings.to_hash # => { :note => {:properties=> ... }} ``` +or with the DSL mixin: + +```ruby +class NoteRepository + include Elasticsearch::Persistence::Repository + include Elasticsearch::Persistence::Repository::DSL + + mappings { indexes :title, analyzer: 'snowball' } + settings number_of_shards: 1 +end + +repository = NoteRepository.new + +``` + +You can also use the `#create` method defined on the repository class to create and set the mappings and settings +on an instance with a block in one call: + +```ruby +repository = NoteRepository.create(index_name: 'notes_development') do + settings number_of_shards: 1, number_of_replicas: 0 do + mapping dynamic: 'strict' do + indexes :foo do + indexes :bar + end + indexes :baz + end + end +end +``` + The convenience methods `create_index!`, `delete_index!` and `refresh_index!` allow you to manage the index lifecycle. +These methods can only be called on repository instances and are not implemented at the class level. ##### Serialization @@ -319,9 +409,12 @@ to the storage, and the initialization procedure when loading it from the storag ```ruby class NoteRepository + include Elasticsearch::Persistence::Repository + def serialize(document) Hash[document.to_hash.map() { |k,v| v.upcase! if k == :title; [k,v] }] end + def deserialize(document) MyNote.new ActiveSupport::HashWithIndifferentAccess.new(document['_source']).deep_symbolize_keys end @@ -426,9 +519,15 @@ end results.total # => 2 -# Access the raw response as a Hashie::Mash instance +# Access the raw response as a Hashie::Mash instance. +# Note that a Hashie::Mash will only be created if the 'response' method is called on the results. results.response._shards.failed # => 0 + +# Access the raw response +results.raw_response +# => 0 + ``` #### Example Application From 027734185b58ff978ca37f737548e2935165d40b Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Mon, 13 Aug 2018 17:13:56 +0200 Subject: [PATCH 387/582] [STORE] Minor typo in README --- elasticsearch-persistence/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index d933c9519..637313d1f 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -526,7 +526,7 @@ results.response._shards.failed # Access the raw response results.raw_response -# => 0 +# => {...} ``` From 723e432da1cfefd701e5bf7dd6a6b0376998b9b2 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Mon, 13 Aug 2018 17:21:46 +0200 Subject: [PATCH 388/582] [STORE] Add #inspect method for Repository --- .../lib/elasticsearch/persistence/repository.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index 33cbdc2c4..6aec19951 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -214,6 +214,18 @@ def index_exists?(*args) super(index_name: index_name) end + # Get the nicer formatted string for use in inspection. + # + # @example Inspect the repository. + # repository.inspect + # + # @return [ String ] The repository inspection. + # + # @since 6.0.0 + def inspect + "#<#{self.class}:0x#{object_id} index_name=#{index_name} document_type=#{document_type} klass=#{klass}>" + end + private def __get_class_value(_method_) From b9bdecea13bd6bd34197ec45e95bc87a295b0d20 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Mon, 13 Aug 2018 17:28:22 +0200 Subject: [PATCH 389/582] [STORE] Update references to Elasticsearch::Client --- .../lib/elasticsearch/persistence/repository.rb | 4 ++-- .../lib/elasticsearch/persistence/repository/dsl.rb | 2 +- elasticsearch-persistence/spec/repository_spec.rb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index 6aec19951..23e34c3e5 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -105,13 +105,13 @@ def initialize(options = {}) # @example # repository.client # - # @return [ Elasticsearch::Transport::Client ] The repository's client. + # @return [ Elasticsearch::Client ] The repository's client. # # @since 6.0.0 def client @client ||= @options[:client] || __get_class_value(:client) || - Elasticsearch::Transport::Client.new + Elasticsearch::Client.new end # Get the document type used by the repository object. diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb index 0c880d7ec..f997e63ce 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb @@ -63,7 +63,7 @@ def klass(_class = nil) # # @since 6.0.0 def client(_client = nil) - @client ||= (_client || Elasticsearch::Transport::Client.new) + @client ||= (_client || Elasticsearch::Client.new) end def create_index!(*args) diff --git a/elasticsearch-persistence/spec/repository_spec.rb b/elasticsearch-persistence/spec/repository_spec.rb index 2d2d4d4ca..292eb5d06 100644 --- a/elasticsearch-persistence/spec/repository_spec.rb +++ b/elasticsearch-persistence/spec/repository_spec.rb @@ -106,7 +106,7 @@ class RepositoryWithoutDSL context 'when options are provided' do let(:client) do - Elasticsearch::Transport::Client.new + Elasticsearch::Client.new end let(:repository) do From 391ddd169c48bd6a450acdc27d4c730f35c5aa30 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Mon, 13 Aug 2018 18:02:22 +0200 Subject: [PATCH 390/582] [RAILS] Add rspec to Gemfile --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index 5cb504908..536ee1c11 100644 --- a/Gemfile +++ b/Gemfile @@ -11,4 +11,5 @@ gem "cane" group :development do gem 'yard' + gem 'rspec' end From a95513e093a4fe9b7c4633411ac504b0d0cd3618 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Wed, 15 Aug 2018 15:55:27 +0200 Subject: [PATCH 391/582] [STORE] Minor refactor in Repository::Search --- .../persistence/repository/search.rb | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb index 203a7e246..c3893d74f 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb @@ -43,18 +43,18 @@ module Search # @return [Elasticsearch::Persistence::Repository::Response::Results] # def search(query_or_definition, options={}) - type = document_type - - case - when query_or_definition.respond_to?(:to_hash) - response = client.search( { index: index_name, type: type, body: query_or_definition.to_hash }.merge(options) ) - when query_or_definition.is_a?(String) - response = client.search( { index: index_name, type: type, q: query_or_definition }.merge(options) ) + request = { index: index_name, + type: document_type } + if query_or_definition.respond_to?(:to_hash) + request[:body] = query_or_definition.to_hash + elsif query_or_definition.is_a?(String) + request[:q] = query_or_definition else raise ArgumentError, "[!] Pass the search definition as a Hash-like object or pass the query as a String" + - " -- #{query_or_definition.class} given." + " -- #{query_or_definition.class} given." end - Response::Results.new(self, response) + + Response::Results.new(self, client.search(request.merge(options))) end # Return the number of domain object in the index @@ -81,18 +81,19 @@ def search(query_or_definition, options={}) # def count(query_or_definition=nil, options={}) query_or_definition ||= { query: { match_all: {} } } - type = document_type + request = { index: index_name, + type: document_type } - case - when query_or_definition.respond_to?(:to_hash) - response = client.count( { index: index_name, type: type, body: query_or_definition.to_hash }.merge(options) ) - when query_or_definition.is_a?(String) - response = client.count( { index: index_name, type: type, q: query_or_definition }.merge(options) ) + if query_or_definition.respond_to?(:to_hash) + request[:body] = query_or_definition.to_hash + elsif query_or_definition.is_a?(String) + request[:q] = query_or_definition else - raise ArgumentError, "[!] Pass the search definition as a Hash-like object or pass the query as a String, not as [#{query_or_definition.class}]" + raise ArgumentError, "[!] Pass the search definition as a Hash-like object or pass the query as a String" + + " -- #{query_or_definition.class} given." end - response[COUNT] + client.count(request.merge(options))[COUNT] end private From 82ec11f24cd6354842100d1041bd6f259b6ccbfc Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Wed, 15 Aug 2018 16:06:39 +0200 Subject: [PATCH 392/582] [STORE] Remove example music app that demonstrates the AR pattern --- .../examples/music/album.rb | 54 --- .../examples/music/artist.rb | 70 --- .../examples/music/artists/_form.html.erb | 8 - .../music/artists/artists_controller.rb | 67 --- .../music/artists/artists_controller_test.rb | 53 --- .../examples/music/artists/index.html.erb | 60 --- .../examples/music/artists/show.html.erb | 54 --- .../examples/music/assets/application.css | 257 ----------- .../examples/music/assets/autocomplete.css | 48 -- .../examples/music/assets/blank_artist.png | Bin 30857 -> 0 bytes .../examples/music/assets/blank_cover.png | Bin 22778 -> 0 bytes .../examples/music/assets/form.css | 113 ----- .../examples/music/index_manager.rb | 73 --- .../examples/music/search/index.html.erb | 95 ---- .../music/search/search_controller.rb | 41 -- .../music/search/search_controller_test.rb | 12 - .../examples/music/search/search_helper.rb | 15 - .../examples/music/suggester.rb | 69 --- .../examples/music/template.rb | 430 ------------------ .../assets/jquery-ui-1.10.4.custom.min.css | 7 - .../assets/jquery-ui-1.10.4.custom.min.js | 6 - .../ui-bg_highlight-soft_100_eeeeee_1x100.png | Bin 90 -> 0 bytes 22 files changed, 1532 deletions(-) delete mode 100644 elasticsearch-persistence/examples/music/album.rb delete mode 100644 elasticsearch-persistence/examples/music/artist.rb delete mode 100644 elasticsearch-persistence/examples/music/artists/_form.html.erb delete mode 100644 elasticsearch-persistence/examples/music/artists/artists_controller.rb delete mode 100644 elasticsearch-persistence/examples/music/artists/artists_controller_test.rb delete mode 100644 elasticsearch-persistence/examples/music/artists/index.html.erb delete mode 100644 elasticsearch-persistence/examples/music/artists/show.html.erb delete mode 100644 elasticsearch-persistence/examples/music/assets/application.css delete mode 100644 elasticsearch-persistence/examples/music/assets/autocomplete.css delete mode 100644 elasticsearch-persistence/examples/music/assets/blank_artist.png delete mode 100644 elasticsearch-persistence/examples/music/assets/blank_cover.png delete mode 100644 elasticsearch-persistence/examples/music/assets/form.css delete mode 100644 elasticsearch-persistence/examples/music/index_manager.rb delete mode 100644 elasticsearch-persistence/examples/music/search/index.html.erb delete mode 100644 elasticsearch-persistence/examples/music/search/search_controller.rb delete mode 100644 elasticsearch-persistence/examples/music/search/search_controller_test.rb delete mode 100644 elasticsearch-persistence/examples/music/search/search_helper.rb delete mode 100644 elasticsearch-persistence/examples/music/suggester.rb delete mode 100644 elasticsearch-persistence/examples/music/template.rb delete mode 100755 elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.css delete mode 100755 elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.js delete mode 100644 elasticsearch-persistence/examples/music/vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png diff --git a/elasticsearch-persistence/examples/music/album.rb b/elasticsearch-persistence/examples/music/album.rb deleted file mode 100644 index a805a2111..000000000 --- a/elasticsearch-persistence/examples/music/album.rb +++ /dev/null @@ -1,54 +0,0 @@ -class Meta - include Virtus.model - - attribute :rating - attribute :have - attribute :want - attribute :formats -end - -class Album - include Elasticsearch::Persistence::Model - - index_name [Rails.application.engine_name, Rails.env].join('-') - - - mapping _parent: { type: 'artist' } do - end - - attribute :artist - attribute :artist_id, String, mapping: { index: 'not_analyzed' } - attribute :label, Hash, mapping: { type: 'object' } - - attribute :title - attribute :released, Date - attribute :notes - attribute :uri - - attribute :tracklist, Array, mapping: { type: 'object' } - - attribute :styles - attribute :meta, Meta, mapping: { type: 'object' } - - attribute :suggest, Hashie::Mash, mapping: { - type: 'object', - properties: { - title: { - type: 'object', - properties: { - input: { type: 'completion' }, - output: { type: 'keyword', index: false }, - payload: { type: 'object', enabled: false } - } - }, - track: { - type: 'object', - properties: { - input: { type: 'completion' }, - output: { type: 'keyword', index: false }, - payload: { type: 'object', enabled: false } - } - } - } - } -end diff --git a/elasticsearch-persistence/examples/music/artist.rb b/elasticsearch-persistence/examples/music/artist.rb deleted file mode 100644 index 17c525619..000000000 --- a/elasticsearch-persistence/examples/music/artist.rb +++ /dev/null @@ -1,70 +0,0 @@ -class Artist - include Elasticsearch::Persistence::Model - - index_name [Rails.application.engine_name, Rails.env].join('-') - - analyzed_and_raw = { fields: { - name: { type: 'text', analyzer: 'snowball' }, - raw: { type: 'keyword' } - } } - - attribute :name, String, mapping: analyzed_and_raw - - attribute :profile - attribute :date, Date - - attribute :members, String, default: [], mapping: analyzed_and_raw - attribute :members_combined, String, default: [], mapping: { analyzer: 'snowball' } - - attribute :urls, String, default: [] - attribute :album_count, Integer, default: 0 - - attribute :suggest, Hashie::Mash, mapping: { - type: 'object', - properties: { - name: { - type: 'object', - properties: { - input: { type: 'completion' }, - output: { type: 'keyword', index: false }, - payload: { type: 'object', enabled: false } - } - }, - member: { - type: 'object', - properties: { - input: { type: 'completion' }, - output: { type: 'keyword', index: false }, - payload: { type: 'object', enabled: false } - } - } - } - } - - validates :name, presence: true - - def albums - Album.search( - { query: { - has_parent: { - type: 'artist', - query: { - bool: { - filter: { - ids: { values: [ self.id ] } - } - } - } - } - }, - sort: 'released', - size: 100 - }, - { type: 'album' } - ) - end - - def to_param - [id, name.parameterize].join('-') - end -end diff --git a/elasticsearch-persistence/examples/music/artists/_form.html.erb b/elasticsearch-persistence/examples/music/artists/_form.html.erb deleted file mode 100644 index 55273679c..000000000 --- a/elasticsearch-persistence/examples/music/artists/_form.html.erb +++ /dev/null @@ -1,8 +0,0 @@ -<%= simple_form_for @artist do |f| %> - <%= f.input :name %> - <%= f.input :profile, as: :text %> - <%= f.input :date, as: :date %> - <%= f.input :members, hint: 'Separate names by comma', input_html: { value: f.object.members.join(', ') } %> - - <%= f.button :submit %> -<% end %> diff --git a/elasticsearch-persistence/examples/music/artists/artists_controller.rb b/elasticsearch-persistence/examples/music/artists/artists_controller.rb deleted file mode 100644 index 458c243f7..000000000 --- a/elasticsearch-persistence/examples/music/artists/artists_controller.rb +++ /dev/null @@ -1,67 +0,0 @@ -class ArtistsController < ApplicationController - before_action :set_artist, only: [:show, :edit, :update, :destroy] - - rescue_from Elasticsearch::Persistence::Repository::DocumentNotFound do - render file: "public/404.html", status: 404, layout: false - end - - def index - @artists = Artist.all sort: 'name.raw', _source: ['name', 'album_count'] - end - - def show - @albums = @artist.albums - end - - def new - @artist = Artist.new - end - - def edit - end - - def create - @artist = Artist.new(artist_params) - - respond_to do |format| - if @artist.save refresh: true - format.html { redirect_to @artist, notice: 'Artist was successfully created.' } - format.json { render :show, status: :created, location: @artist } - else - format.html { render :new } - format.json { render json: @artist.errors, status: :unprocessable_entity } - end - end - end - - def update - respond_to do |format| - if @artist.update(artist_params, refresh: true) - format.html { redirect_to @artist, notice: 'Artist was successfully updated.' } - format.json { render :show, status: :ok, location: @artist } - else - format.html { render :edit } - format.json { render json: @artist.errors, status: :unprocessable_entity } - end - end - end - - def destroy - @artist.destroy refresh: true - respond_to do |format| - format.html { redirect_to artists_url, notice: 'Artist was successfully destroyed.' } - format.json { head :no_content } - end - end - - private - def set_artist - @artist = Artist.find(params[:id].split('-').first) - end - - def artist_params - a = params.require(:artist) - a[:members] = a[:members].split(/,\s?/) unless a[:members].is_a?(Array) || a[:members].blank? - return a - end -end diff --git a/elasticsearch-persistence/examples/music/artists/artists_controller_test.rb b/elasticsearch-persistence/examples/music/artists/artists_controller_test.rb deleted file mode 100644 index 3307f5e47..000000000 --- a/elasticsearch-persistence/examples/music/artists/artists_controller_test.rb +++ /dev/null @@ -1,53 +0,0 @@ -require 'test_helper' - -class ArtistsControllerTest < ActionController::TestCase - setup do - IndexManager.create_index force: true - @artist = Artist.create(id: 1, name: 'TEST') - Artist.gateway.refresh_index! - end - - test "should get index" do - get :index - assert_response :success - assert_not_nil assigns(:artists) - end - - test "should get new" do - get :new - assert_response :success - end - - test "should create artist" do - assert_difference('Artist.count') do - post :create, artist: { name: @artist.name } - Artist.gateway.refresh_index! - end - - assert_redirected_to artist_path(assigns(:artist)) - end - - test "should show artist" do - get :show, id: @artist - assert_response :success - end - - test "should get edit" do - get :edit, id: @artist - assert_response :success - end - - test "should update artist" do - patch :update, id: @artist, artist: { name: @artist.name } - assert_redirected_to artist_path(assigns(:artist)) - end - - test "should destroy artist" do - assert_difference('Artist.count', -1) do - delete :destroy, id: @artist - Artist.gateway.refresh_index! - end - - assert_redirected_to artists_path - end -end diff --git a/elasticsearch-persistence/examples/music/artists/index.html.erb b/elasticsearch-persistence/examples/music/artists/index.html.erb deleted file mode 100644 index 6747b3056..000000000 --- a/elasticsearch-persistence/examples/music/artists/index.html.erb +++ /dev/null @@ -1,60 +0,0 @@ -<header> - <h1> - Artists - <%= button_to 'New Artist', new_artist_path, method: 'get', tabindex: 5 %> - </h1> -</header> - -<section id="searchbox"> -<%= form_tag search_path, method: 'get' do %> - <input type="text" name="q" value="<%= params[:q] %>" id="q" autofocus="autofocus" placeholder="start typing to search..." tabindex="0" /> -<% end %> -</section> - -<section class="artists"> - <% @artists.each do |artist| %> - <%= div_for artist, class: 'result clearfix' do %> - <h2> - <%= image_tag "http://ruby.elastic.co.s3-website-us-east-1.amazonaws.com/demo/music/bands/#{artist.id}.jpeg", height: '50px', class: 'band' %> - <%= link_to artist do %> - <span class="name"><%= artist.name %></span> - <small><%= pluralize artist.album_count, 'album' %></small> - <% end %> - </h2> - <div class="actions clearfix"> - <%= button_to 'Edit', edit_artist_path(artist), method: 'get' %> - <%= button_to 'Destroy', artist, method: :delete, data: { confirm: 'Are you sure?' } %> - </div> - <% end %> - <% end %> -</section> - -<% if @artists.empty? %> -<section class="no-results"> - <p>The search hasn't returned any results...</p> -</section> -<% end %> - -<script> -$.widget( "custom.suggest", $.ui.autocomplete, { - _renderMenu: function( ul, items ) { - $.each( items, function( index, item ) { - var category = ul.append( "<li class='ui-autocomplete-category'>" + item.label + "</li>" ); - - $.each( item.value, function( index, item ) { - var li = $('<li class="ui-autocomplete-item"><a href="<%= Rails.application.config.relative_url_root %>'+ item.url +'">'+ item.text +'</a></li>').data('ui-autocomplete-item', item ) - category.append(li) - } ) - }); - } - }); - -$( "#q" ).suggest({ - source: '<%= suggest_path %>', - select: function(event, ui) { - document.location.href = '<%= Rails.application.config.relative_url_root %>'+ui.item.url - } -}); -</script> - -<script>$('img.band').error(function(){ $(this).attr('src', '/images/blank_artist.png'); });</script> diff --git a/elasticsearch-persistence/examples/music/artists/show.html.erb b/elasticsearch-persistence/examples/music/artists/show.html.erb deleted file mode 100644 index e1a9cdf01..000000000 --- a/elasticsearch-persistence/examples/music/artists/show.html.erb +++ /dev/null @@ -1,54 +0,0 @@ -<div class="artist"> - <header> - <h1> - <span class="back"><%= link_to "〈".html_safe, artists_path, title: "Back" %></span> - <%= image_tag "http://ruby.elastic.co.s3-website-us-east-1.amazonaws.com/demo/music/bands/#{@artist.id}.jpeg", height: '50px', class: 'band' %> - <%= @artist.name %> - <%= button_to 'Edit', edit_artist_path(@artist), method: 'get' %> - </h1> - </header> - - <p id="notice"><%= notice %></p> - - <section class="artist-info"> - <span><%= @artist.members.to_sentence last_word_connector: ' and ' %></span> | - <span><%= pluralize @albums.size, 'album' %></span> - <p class="artist-profile"><%= @artist.profile %></p> - </section> - - <section class="albums"> - <% @albums.each do |album| %> - <%= div_for album, class: 'clearfix' do %> - <h3> - <span class="title"><%= album.title %></span> - <div class="info"> - <small><%= album.meta.formats.join(', ') %></small> - <small><%= album.released %></small> - </div> - </h3> - - <div class="cover"> - <%= image_tag "http://ruby.elastic.co.s3-website-us-east-1.amazonaws.com/demo/music/covers/#{album.id}.jpeg", width: '100px', class: 'cover' %> - </div> - - <div class="content"> - <% album.tracklist.in_groups_of(album.tracklist.size/2+1).each_with_index do |half, g| %> - <ul class=<%= cycle 'first', 'second' %> start="<%= g < 1 ? 1 : album.tracklist.size/2+2 %>"> - <% half.compact.each_with_index do |track, i| %> - <li> - <i class="counter"><%= g < 1 ? i+1 : i+(g*album.tracklist.size/2+2) %></i> - <%= track['title'] %> - <small><%= track['duration'] %></small> - </li> - <% end %> - </ul> - <% end %> - </div> - <% end %> - - <% end %> - - <script>$('img').error(function(){ $(this).attr('src', '/images/blank_cover.png'); });</script> - <script>$(document.location.hash).effect('highlight', 1500)</script> - </section> -</div> diff --git a/elasticsearch-persistence/examples/music/assets/application.css b/elasticsearch-persistence/examples/music/assets/application.css deleted file mode 100644 index 816ebff8c..000000000 --- a/elasticsearch-persistence/examples/music/assets/application.css +++ /dev/null @@ -1,257 +0,0 @@ -/* - *= require_tree . - *= require_self - *= require ui-lightness/jquery-ui-1.10.4.custom.min.css - */ - -.clearfix { - *zoom: 1; -} - -.clearfix:before, -.clearfix:after { - display: table; - line-height: 0; - content: ""; -} - -.clearfix:after { - clear: both; -} - -body { - font-family: 'Helvetica Neue', Helvetica, sans-serif !important; - margin: 2em 4em; -} - -header { - margin: 0; - padding: 0 0 1em 0; - border-bottom: 1px solid #666; -} - -header h1 { - color: #999; - font-weight: 100; - text-transform: uppercase; - margin: 0; padding: 0; -} - -header a { - color: #0b6aff; - text-decoration: none; -} - -header .back { - font-size: 100%; - margin: 0 0.5em 0 -0.5em; -} - -h1 form { - float: right; -} - -#searchbox { - border-bottom: 1px solid #666; -} - -#searchbox input { - color: #444; - font-size: 100%; - font-weight: 100; - border: none; - padding: 1em 0 1em 0; - width: 100%; -} - -#searchbox input:focus { - outline-width: 0; -} - -.actions form { - float: right; - position: relative; - top: 0.2em; -} - -.no-results { - font-weight: 200; - font-size: 200%; -} - -.result, -.artist { - padding: 1em 0 1em 0; - margin: 0; - border-bottom: 1px solid #999; -} - -.result:hover, -.artist:hover { - background: #f9f9f9; -} - -.result h2, -.artist h2 { - color: #444; - margin: 0; - padding: 0; -} - -.artist h2 { - float: left; - margin-left: 50px; -} - -.artist.search.result h2 { - float: none; -} - -.artist h1 .back { - margin-right: 65px; -} - -.artist h1 img.band { - left: 120px; - top: 50px; -} - -.result h2 a, -.artist h2 a { - color: #444; -} - -.result h2 small, -.artist h2 small { - font-size: 70%; - font-weight: 100; - margin-left: 0.5em; -} - -.result h2 a, -.artist h2 a { - text-decoration: none; -} - -.result h2 a:hover name, -.artist h2 a:hover .name { - text-decoration: underline; -} - -.result .highlight.small { - font-size: 90%; - font-weight: 200; - padding: 0; - margin: 0.25em 0 0.25em 50px; -} - -.result .small .label { - color: #999; - font-size: 80%; - /*min-width: 5em;*/ - display: inline-block; -} - -.artist-info { - color: #5f5f5f; - text-transform: uppercase; - font-weight: 200; - border-bottom: 1px solid #666; - padding: 0 0 1em 0; - margin: 0 0 1em 0; -} - -.artist-profile { - color: #999; - font-size: 95%; - font-weight: 100; - text-transform: none; - padding: 0; - margin: 0.25em 0 0 0; -} - -.artist img.band { - position: absolute; - left: 85px; - margin-top: 14px; - transform: translate(-50%,-50%); - clip-path: circle(20px at center); -} - -.album { - margin: 0 0 4em 0; -} - -.album.search.result { - margin: 0; -} - -.album .cover { - float: left; - width: 150px; -} - -.album.search.result .cover { - width: 40px; - margin-right: 10px; -} - -.album .cover img { - border: 1px solid rgba(0,0,0,0.15); - box-shadow: 0px 0px 1px 0px rgba(0,0,0,0.05); -} - -.album .content { - float: left; - margin-left: 25px; -} - -.album .content ul { - float: left; - margin: 0 2em 0 0; - padding: 0; - min-width: 18em; -} - -.album .content ul li { - line-height: 1.5em; - padding: 0.5em 0 0.5em 0; - border-bottom:1px solid #f8f8f8; - list-style: none; -} - -.album .content ul li .counter { - color: #999; - font-style: normal; - font-size: 80%; - font-weight: 100; - margin-right: 0.5em; -} - -.album h3 { - margin: 0; padding: 0; - border-bottom: 2px solid #e0e0e0; - padding: 0 0 0.5em 0; - margin: 0 0 1em 0; -} - -.album h3 .title { - text-transform: uppercase; - font-weight: 200; -} - -.album small { - color: #a3a3a3; - font-weight: 200; -} - -.album .info { - float: right; -} - -em[class^=hl] { - font-style: normal; - background: #e6efff; - padding: 0.15em 0.35em; - border-radius: 5px; -} \ No newline at end of file diff --git a/elasticsearch-persistence/examples/music/assets/autocomplete.css b/elasticsearch-persistence/examples/music/assets/autocomplete.css deleted file mode 100644 index 7f2340969..000000000 --- a/elasticsearch-persistence/examples/music/assets/autocomplete.css +++ /dev/null @@ -1,48 +0,0 @@ -.ui-autocomplete { - font-family: 'Helvetica Neue', Helvetica, sans-serif !important; - border: none !important; - border-radius: 0 !important; - background-color: #fff !important; - margin: 0 !important; - padding: 0 !important; - box-shadow: 0px 3px 3px 0px rgba(0,0,0,0.75); -} - -.ui-autocomplete-category { - color: #fff; - background: #222; - font-size: 90%; - font-weight: 300; - text-transform: uppercase; - margin: 0 !important; - padding: 0.25em 0.5em 0.25em 0.5em; -} - -.ui-autocomplete-item { - border-bottom: 1px solid #000; - margin: 0 !important; - padding: 0 !important; -} - -.ui-autocomplete-item:hover, -.ui-autocomplete-item:focus { - color: #fff !important; - background: #0b6aff !important; -} - -.ui-state-focus, -.ui-state-focus a, -.ui-state-active, -.ui-state-active a, -.ui-autocomplete-item:hover a { - color: #fff !important; - background: #0b6aff !important; - outline: none !important; - border: none !important; - border-radius: 0 !important; -} - -a.ui-state-focus, -a.ui-state-active { - margin: 0px !important; -} diff --git a/elasticsearch-persistence/examples/music/assets/blank_artist.png b/elasticsearch-persistence/examples/music/assets/blank_artist.png deleted file mode 100644 index 0dfda13bdea1b87f705ed12e378be32a9a7b0bbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30857 zcmeHwc|6ry+qa66N*WBAX^?H$C_{)tDjC8yZ&TVNHZ~cz@ni^@DkMXO5Mq;IXC{P$ z5VCj1ZOA;^*k<qAI`_HV=eh5v_kBOlKhNir&*`-K{nonHTHk9}*LAJyS3&A`Z=axH zrlFvqIB`c&0Y*V_P>=kJ`XG2BOnEXG{C3n%QQv`r;@BDTuLBgZ@k|sH5)ex*Jx4uN z6-fljM$ps@b>Cdj)y59=rl63Pb+t1^Ak7^)@0(j#+Fs|LFRS9_v^2ZUtt+Z3q-uA= z{Gp|y+aq&Lx4T*hHzeYk8MmwqjkK#IAYfzeXv*noV{Pjo>3W@ekFF$WlOIF5Irq9a zBCm7HktgKTQ&s1@fqG=lDJm!?fDjTB=M=vtC@d;2epQ^GQ$$Eu6e@HDdPPV;<cj1~ zA<3&EoPYjs%g}(9^dqwek}!o^e@+Mfbe;R5qobW96zbyQBIt5O5cS9cDtztQHK>pX zR76Ao^bl}B+d7)M3fMZF|3Ty*bQH`T5RWYF94%3{oMgJD_fbxc*SWdL6aDq`=e}(0 z{+h_v;ZN)UicnWmJE*Xr5cJ;-GDG||#?I-H_1@CV5Kwb#a~pG8M+Y!Y_}`ARdx&yG zIXp!DhbjMh{U0X=JW*BsYwmx$UK^W#JGF!3O=keXpOF6Bkq%mDJ98+^+yUkE2w{HH z8EobJzuUc|<%54R=U*t1-~7k5o4Z>6hhgM5KZfmz!VeBfgS|@Lcw}zsh<c=jLRriF zkTUguK*)LH#-3DhURE_lSlW_t<&}p1eb7HS{!e=53Z{<cGGvJq5E2&<64nwHmK41v zDJ%s3C@CbgkI0Xnf1{y_GP8Vu-bdq_fY4Pf;VY7&5|USi_tV(d^Y1i(2AP>Un*Lu= z+23~`9W#XF1JolMQ%4y~8&eB&sGY5aH1wYj_x1cst|V`utWl4^vdm?!NJIbK<9(y{ zWKHspt%IYfEyDbcf(&>dXlZFCDU1*^H4_sN61Z<FDkgB%%uHP1zL}`0z%_AUvHRkp zLK0@;!h0a9s_q~DUq~yU5Kd%$`IEHS|0C&r!~Y9uwMUk~)|p!G>qB1s|LYY0hRlDN zO3~5*NGN)r<O2EKvu2v+_W%0yA1|ye_pFkg=_3bovd&%S{)f{2Wyb!pQsgmvx+`gl zAlow;1X(4_&7`6K^7Nm}`fKE$!z}-Q8Tf<#zTy9Q4wr}Kw*Q?%{UEj1>pv55Ks|7D zF@0n%X8~m5|4!R~(EZnef1f}aN<IOsE&r7jKc4<8hyEe6|COWCWWy#2qyenVbdL)_ z!2ip$f6n})zaE*Z)=qo<#YKd`4^)`^|38QBfBpBN`v3jV{jdK%^q(WOZ7m&TME;!Z zZ;$tlB3s`dhCvfJ3g$9mqM}zsrJ;X+{Pz*+magX3dJ2|6gB{3nDMsGd-k`s~`L`qW z|KrGgZ~h$lgIW6y^*vtxcnVww;H^Oaa##M9&3`Gw|8V1<Tlya+yr0~!N%o`h)Ald8 zehSh44!_{qkH$~izu@{QMEg7Zf@?n-KW+bl>!%Ry@9+z*{b>BO{R^(2LbSibFSz!j z@zeG%xPA)J{tmz3+K<Lh+rQxYDMb4_{DNyg8b59Sg6pRc?eFjluKj5IwEYXNpF*_1 z!!Nk@qw&-BFSvdR(f$s<;M$MIPusuX`YA;FJN$xcKN>%6|AOnM5bf{q3$FcW{IvZG zuAf4*zr!!M_M`FB_Aj`83eo-!zu?-B#!uV7;QA><`#b!CYd;!4ZU2Jnrx5M$@C&Z} zX#BMO3$C9+w7<hIxb~y*)Ald8ehSh44!_{qkH$~izu@{QMEg7Zf@?n-KW+bl>!%Ry z@9+z*{b>BO{olex^Un_e&27O~e=gwjzLY;UYrv<0oCrmjDg}kxMGA`FeJLo`cfs$I z6ckQE6ckhUC@3UfQBW|VUYOL~qM(rNzM~+g<=QjTk2W}~|0ZKI5X=3-&n&@0;eg!P z7>%rrWtnll{&5-7$ZT1e(#_w_(%(Gw9L;{_(NocQ*?figcXaBF-?wMSyZep$wXH6E zYJRHcTUK5<N<m?#e^l@Q#qV?nDSo3qK*8xpK|v=^K|#$$LE%S7K_O2~LBU1&zvcD+ zuGRiGIRB4BUmPecc;NToMyLL^ou}BB%?IVFIkR9cpX`kqdK{-_OhSacx9N3@Yv?(4 z8zxn})(k?hx4of2A`F+8j&d2|yXqgN`DlqS1YbNIWX63eyaCo|28p8a`7Lws2JLK~ z84ZEBrT)iB|3{B%N=izu4cSS=$3;hv2gt*excr1CMLKSR-7-F*lcye%xJySF<SNbF zPw4Ny7C6Vj&iZLYI4u&^#6-j^&e!fbu#S&Z$tGZ{b}F#A=P)H+PeNNuu)@-LDyo}x zb#?3h{zG;-<LkIQ924R=$AO)>@q*D6ft-WCbBL^+k*78-3}a)To13%F$(^6G<xohG zqhrFzqJ=i<rawj$aGYzf_j{HcAHTKb^P(ev>FLuq!!P>ErH5z`S8q~sQ7#nJd&8{K z-n4Iru3)iIQ56;aX56NjJ4K89-BPB^p~90Ug5u&u#i@nwO5c{}i^x{}Mt$d$9QA>% z46dYS9rp3Hz7su7OG_OcbFI#hs7FS(sn=6RNCNdt$PlM^JBjqv!txI}mHDOMyW0Dh zE;upsG2KClXiA#NJM@$+EYrab^LRY&bkM8mV)>o?9Z#oMuLMmloX&iglZmp1@bNLi ztS~V;H>i(t{oXks=eLj=(lElO987eWFDeoX6<*?Ana@Hfq+plJm!3A&^R;w|Vw2Uw za8FrJ%Tui7Xe3RejiuQQUd6?Eil6BxI2h?F#Rz-<zD&{X^fXvGE*JY%^y-TnK#h1* zlOl~pVqSU6N=aQ|3!OWr8+cr4Fuf2LhP`?5qHZo$?5gpihZ^s!#P?@(2S4H!+pfT& z0-vkU*Re$f0;wucy3dZ2;{N8N^ft3>U7}7o(?U^CKu4aA;9+rbvA4!DCME_>@gZ(> zZXK);JiVyX%B;98Lv-ZBx(FDUKWs!IY#iOFFx)b&J3nxjFHIVX6i>^?Sz&;LVenEp z9}3+nbEAfj0mD`NiA1fdsFouraNT>=4CR6+mIq6pUoKCzc==LWtFOFYQ$r(@i5Vae z?acW~Db2VlJ|4Y!sX}lVw!MgjCCr=5Pgq-)&C!^;A{BweLn`M{C>zy~a2s1&!xxVS zqqDI}nXx^kISG|MHS+Y79;QH+qX#PoOJPmxq=L2tnLC%x=x^Ox@QbA3UZ?86?%CzE z@;Qmik0L+*SqC$5X}w@lTyyf!h2l$LBM&kD&%`BBA3nIGim!~t0!sRdZUAWyNwNH5 zA#Y2L054@Xc}pS|mosO~d-2$bg2P9M2iNSR7Rnzw?k=VbVKAx$QVC~bIEx{kxaERM zA6axo?cS4Mee2)-IDnmkwZYzlK<FOuT4>m{wy|A+EzCo|HgyayZ0~-eJz1jeNB7%@ z5Gqe^gs11)4#$!9j0U|0N`bzC9yixr+09M&Lcq^Ip3vmvVr1ObETq{-K6rHmkD=Ym z%1-WXwav}ZGKPW<_h3z-SZsR$DI(~TxdGIBm4RI$<^0JVGedx&*$k<QE>F+e!%-qe z1y<dO6=DtzlP>qRqZ>U4xDe9-pWdvT574`0|B_*Db}mfX^;uwpB^MXvGc?!n(eGcs zb`=~S3;}BzVC7)ZP|Di{N6<4%fva!deh}l~39zYcts!kkjiy<sHL&4LT~z_k)=-;> z6b6iwsfN8in##APplD&ctO#XX5vdXn7$GRqMjWV$>((=obxXdOmYs#V$K>D)bJK*H z@IzbO*^$CPp@QMnn3m8Wo^ail4B-ibmKeS)<P!I~^##vcIn&Gtp85t7iS$b12AHmV z-kIxk(5KD@1bjL)6zeLTV4Ey_2^fabw=aDy+7RlO5qY&iiy9bF=rKU~7-LN|G+}ne zdb84-DbzMQmn{^YjXe>nnlOLGe9Z7kKQb!|_11VXA7Eds=wd-Id6nqCYkR5U$Wy2I zy7iJ2i)8rpp1D$b`#E0sSh`6S+{DPd;ha46;F}PtlP777WfJXPS6w10^NNd$udX_W zANH4Tdh(V}x><@bXW9y5q9T1K?L{{K1%QPDb-Hn-MBuHufSHmUmJ1Qh!N=wTdJA>l z0h5-L5h(6IF0WKlQ6aj$fxU-24s0S4olultepOa{vpe34YjkN?UgWuA_$dr_$gVg! zFQ_DC+;!CAEx{UPyF}i>nz^BFDlAhs0-7xrwz)23@9pjFgr1z3%)pgaE-Kj?`!OVR z*17m{u2?OVi1wHs<^^*oE2_1&RQ7gnY$Sbh>R&JEP6^tnf3@Y~Q~KmE*z-t|osARP z(*Cjj6+gO<xofKo!wZA=9&E(rW~N3O#KrX*#Bty?m^vLttuCLv&sKLW>^MQCLg``z zKOfk#mmD>r!qdUdZbAGEBNpZ`Zy~#L8x#Cg+3H&OFzb;~7&tDf5<MH@<N=^Oy6U$` zq;W!<o3|8F!)>5xsbgSnZczh4#OkyWna(DRs9h+y(i!9$pLQ0krLAJU(w!x|p}(qP zy)-9I<-*uN)ftW=cI`BCb5X1loKJ8By`wHdN;shiBq0$!-ftk${Td(f$kKFYVd2{u zEqp~!#}vA<HfbSVk(ZF}EjXkKRAJ#tWnP-zD{l_j?g9zjK)o3|Q`@-_Nt!gJX1;e; zR`e*EHq`Iz{R9KJ;ox5X+LD~WhWp?o;PIy8@r#f1WDKqgOGqgV>+NxV4S2d)^0}?$ z9p^oDprBiNu%}&Vbq}w)EoGjQ5A>bjf#wzJ0AuZR;skv0?LxT>V@X*>Mc#+{JwzHh z;86b0p7R+usD(+kV{4ypA!o5Kr<2ubeQSvKLlP3>M%{p3%Q}C{E2scJ0-s1Hr2Hb= zCw6MK-0iI!urE3~p1i$u*ngZf>B=&VB#7!A0Xy_G#2dlm@l#i!Z~W{C6|ch@?%yu5 zM5rhmxZ4^hn3LKaHXL6BeOlZ$0_SV|yr<5Eh#+iN)I1xl7j?4qH$q~Zr;B5pSpC_v z48}H=<{J|wp10()`2`NE!~m7|qU5bx#o>nJmcNSYbB)fF6$Lann`<hGo7nI|1Rr!_ zi6!GSd1Yk<RYlF68i4NsdPc^$o_HBQ`A@X84=e_}`kwU0#`pE%rrB`t2k7{QjDBso z_mv;UO8dM3CyAA{mm(TlLJL}38DXuhtuwQ8>?S6JFW<DZjQ}q_WO%u7IGsqCG}}c# ziq@4l{XpPL)YjHs88G&zYgF*qSjkE$FvDQwn^sxG^-a8u6Tj}v4GdMwZ~=jBMJp6z z^K#k{@my&|#X?mjm0phV**fpa-z+H+MeATsiEBW^zOSx+xHos1i_$JEL^zIxWe%-U zZD*v*yYgTvP3D7D4O2gEKto;qp6hRJ>>tE%-JS8k*QlB|;~LKUCg*4p-`OeFM#Skv zGmei-x5?7j*k0%uKnJI%j4z-|=2}~68*pdjDaPxa`X4&VE~=%U^qQ9?wtQ_?7#?|x zoYcR9T{)pdWk|eYWB>@(s)bb^1S_d$nVwtykT+hk8%+1e+r{O<MoV7WS)5aUSX>b} zx}Q`$XkEQ=i;i-0^K&%dQfm;_+1q=*Z#S=lI8gC=U|>Mf8R9P}C|EMkeuuiv*2SXN zo*JlwonURS*3=*;#1K8V`&|pNK2=9t))U%hjDqPJz_YTDujJfrwQqZ3M19FR%=#!w z<-+}dfE<ZkgLmWTEIwu8yhV7JP7#iVOA_TW{{6d<p!9YNS+9hsUmT|a{B)o;<#%2i zskg|PzFQF=!q)q{7ShNN=C*w+U6?;jqBb15=qmEu>^Go3@#`-kpN58p!rlM4<R>j1 zSn0MtyteuZo|%P*Bd3`G+*5QAOpJsQ=-MFAWe?EdC@oiW8|d5Y9!*0Nc9Hs~8wVy^ zdat9=X#PA|Hl&I)!$>=skvjFST%~tzB)%`nnbzAJ?pBytSXevM;Zsu+XVj0g*E9Ey zJ=xl$z}=cc7TN3|k@^c~f;c?0!y`pXt9GYGk8yo!_v(YFuY~Hx=6+cCec3V5n48_A z<`ke!d%ng@Wbup_R~vEc0YRX(ZIk1{Gs!S4&e6GF5Sp9hPw?DzdTl0R8DdIB1^VOY z4$Cc^v3)yfGF(@{)BbRI;y|WwJ!yNc^0A$*AoN%j$>*@#M@@6`LhL)>+l2>@a?zeV zX{;iBZR*X41o_13$mzWvHvPz8H>d3`6)$@zuo4z&kz%-=!gOT9I8;EeJT68?@>Pv} zJSR1CY))aJ(fEN$m!k*k!fAnN)eoSD^C3UC#rs3kTvvwI>o;Oai{{BG*tv;vZ6i(P zV`{DN+CXsPNEjSpYhd1txJ{qK=c6?>x%N<cXYtu3BqlPytVs5B&_ez~DwiWGGKL=< z-tlO?MWVOrB)UvWsx2W*DzTFPGnBu)zjvOd`nT~5vY#Yy%(Xw@O`+LT9=NhzStTJW zzWrco?GTKf5_qbwi)<6;hXT9d@|(e4VF$P<^QWDsN~@;2V<R6~E!9mmEiDFJfoHz_ z0PN2+WRP>yo2juZY&suL=J1E1q><8LUH>mmMwpe(#k|_u3<>9F6~d(jS!FqSK@2;c zRE~brBA3Wk;A*x+<#;#r+*x)75>Gvc0V3=Jx&T+2Gcn0_IV;2L^rY<8WiG!Fb+X9w z6Y60!x?44)4->|7`Jt5_iz-dw2u+0JWuC9%5|JkANihl=vmECsHE#iFFE~MsnctM( z8J;)it(e4XUg<>180hP%tA7)><2g$s9JI`mXcXw;2e@!81R_1T-9zx5E|m{tY)cRn z<aga^2)d$_xzyCuTd^HhJBKtG+LSp$1l%2`<B~nQF&eXu9R57=e72>fjX~?`RinwN zNzwW*HEp;2lGXp%P7BA%Q9rgWj-lbI8Ctkm9HYb=(fDcP`A}``p>@;KmTrcwn6#A# zx<$gVaXX7`f;_A{qv6=<BNS#kMR{~XedWhaGx1hzZ}&Hx5BK3*Tl>-m$2cL~Z{N0v z#uYBqGWFO$av;8fka@lCHB~y)w@_D-=$25G_d!xvW=tZ?-I@oM;<Ops`XP)K^Gab# zBJ6zX@$s=G1Yo6|4z5S>f(fhp+4X6IiA@k#C8|lEYH+T|!!aXl2c{}<xcbM1I5^(v z{B!C(Q|+ZJ`dqOxa!I})cD>Dgb8O{1(Lz2@4gSzZ*9AqyzErOIGVtWlBf-`yHX!Ks zJvZX#PzwUkU}xnhSf!WfwUtLOrS&}jdWkTHvh=D&B?r48@q1V5$3{o}1wT5P(8+W1 z)iWf=D_$TNzdyubhzFx$J$A-c_;@Q2aji^TeFKk^$FlH|rKxR-7%cj^2Z3~yY%ytz zv?kI1{x=yHUd9?flwF^h{Je}rTX$6n-zv6sA$-K*rlXzNE$!`(np6Nbm(;y4PW|L0 zIIQJ?cQR`5Ty@;qo(b``?mS1tU+Eb5uo|ZPO`c1^{?5@_IUsI*hXLujft#MKFO3xz z9X%Z9wzKC0!gfD|=82<7j*bHz(UzDB(Mznfx4GB-$U4fg=guj5Rf;k$*F*f?*UOw^ zLGR81F>GB#r@{%v=xH6@BGG!|Fo)StEENdz%cWPjdHg8u&PQav!fjNV7{aV(>Ws1k z{e<eJ&#}bBc#W{dNg+TKU4(UUURl<kHhdzchiGczOMT{X4jf(S-_Zk8O-jIoe(PSg zMc*8)^H6my_Ya#^36V|k+FmMnhQ#Dx!7(<lC=D%ZgQ}?6Lin=Y$2TYg$qAd^<ZD(A zXWz=hmT<|&6Yqrx3h*5ZaDc9L-o(@A*a5M4?Y2&4bjHCh$29cBK-JRHlH;miQBnQ( zo810&b<&LB<iBfBomQ83`=ll~t#L1jrXT!;FP(0LLU@HY-VlsCkq~1U@71NcPm7)w ztPMwflJehe3{>r86&I>x&KTOFLU^IM#{+6#(gEEfG!w~DPu}w(PHGV14GgHH!ESRs zhw12OTYOSbz&rUzYC?Iyy6(6vPE^4lmoGy~Nx9zmucDN4gJ%8ZO1gpXVwo#n0rl_H z0)Mv{dZxXWGwKDbG$)Xa9k{%80oNGoUo}m|#s}Mr4c&oH+_|>xX5}&S9JjHy?hnsY zzkJno+;~->NKKYeK!C4#j+6xCW}WEiZDVfkxF!dQQsTHeRBHQ7$;LK^Ba|A8158Xa zA3f&~;bcUPzud*Q2E$#Yowp3#5g~MM%8GIevU(+PX3AoPIBo5&k{K)r<vd7wU~S9o zzFH6`kMG}U@=D9IR~pb4Ysfi*bNIEDuC61y>xr*l+st=jEJB3wC`a0XbpamM*04^9 zUnJn>RdNhcNn@Lh36t@7rgUjEJ%Cn;*TtoX<m?i@Q;I8nS2}jk_T%<R?^|uX1cK*U z!!Ga|*$Gct;8{LNIhBh=-n)rUpEjw;)~O?nv<zR|x#0{Xo;&2F7#@F%eL&4iqxvd6 zWmZ=6@QB>PB`6f8sT*xy1m~?V;}%|8*!kRR*+vXgyQmGh%+FuCnd@CtXry-X%$4QN ztMwqivX=a;)`5I_ry{CXR&Jl1=ZQGF9(riPNH-h9e?-pCS}wuz;lri!&r*cjik6}1 z4Ohpd^-mU>cd$CJrn!NgfgQOv;uU)Z^N_}1g{f(l6MOpTN8i_@$ANa0jZtKk{WuZ) zII^#|zki_TY(qlW%gC2HI=UA31q`|T=s?IDLMy9y>2#uMqK1;F337ebd%LW-Si!s} zx$@R@PtS&;m(2785Rx<6cJ2;{??c-P+^d+p!G`u1uwxGM{b~*!eSO@1e|!?R%9#dM zRY;aJ?!DuV@a!jUx9Rjnzl<Vmn(TIc_0Z;x;?>B*Z6vE}I$Vjk`GcYblGyGmJF@tV z5JDyE#iCqNn$Ox#kac^Rfz8Qy2jR>8qc7dAa{e}n#J;mGi!!NVkoPDZHujfKuB?`- z*!E3UzkL2dgcSxkH5II^9CHa6`7KSV>nIE4bi427qU+WLdPmmV)Xx<^F*1&YCxpR~ zX_0t1JUl#2BrGyPc&s}jZ?XNh9Niyk91+ZjSOd8CE@?L>`yGtYT`UueBbFltFxL4% z4!0brTwI*luWwy(c3lWpQ;@YuHZ**teChP<QBAlm9v7vnW3jv>g?CCDZ>fwc*-hN_ zc5&~OU43=6BLB_%jJ3J>-AeoE4|KydJ9n@!tkcTH>{yUp-^ud<sT5#V&Php2$ZT92 zO0x4tq0sh2BZP;*UBS+7ECxJ1*F@TWJ&W1&-XhI>?rmcS`!&K>*b{LT-rKt9n{`x% z4<BMqjD9aH#1@$O0BH}PnVX+IA-gs*$mv$Y^TvcWVW34N{Y7KS-OFhpTC|#)x?<}y zs++6!jHbTfR{gCf5sj(ip7|3p)IRx7pStgE<gyf%3if<a=LLMjriK*xGDdHC?+|vw zvnaMU0C7N@$XPm%9+=$Wk`L75X9!XNfoRwmjD~CJ7|lxt#t`9i+m%8_Zg?k`4@DrR zIRAXBW_Z|<YPTs_SU&LGRP58Hrf);0S4$Ij47_INyO|=Qqw$@x6L-Sb-vBEj&d1bG z@LnEl^WCn{)LnRJeKwd4hZjGJE^H48Rp6F;1~)O-%!v+T{NN93GULuGEPkeRQCX>3 zKKaWc{aJ4TILf-|==j{MHBUriTdUMoV<8TBX}a2YAy(i>RW7nkpaa?`QUbi^=3ird zCKpYl8Jb&~F&P=sO}^N>_IDaLrj)p!|KUUOpOk$&A`v$YezMT$=WPK`fWKv+p`mYN zkQLcO=vCz~1KwkzQs%pNQwBxuOvlKEd2KbqcxGV`2sSV=ZN}B-+bb$!OE=UnFAwb? zZ(O@J)Yq3*M>T#>T0lBCIr*6gdpK}?FK9Ntc+sJLxq->u;~^7m0CIW?Y>4(GEiLVv zNuDg(S(k^Q&lB*nt~}uwGit8iQCjNt2yl|@_JO>41I=EpAzM_`>BMc43Buac-d@_d z7>kXeI;mL2%AAAA`TqU;S$f8@2C>dO92|Kp_~{fgbM4T=&VaF-%9g{UjHuFq_U>;U z+7@+_8h`^QGww<6OjIXI_4bbIRae0;e;OFD)koiyldIM^X4JsT%V%QJoNy%d_K;eY zsG+f;ghU_%J>&SO=c6&3jgp5q&9zl+D){;)0D0dfj@TP%TISkLE+(=k$%>z=EA(73 zM0(4?9Z6gh`Q5?V7Y1RM&CW$hR&Gi2@bJi1-Lo_uXnc7jwV`*ucy6wBb!qqw_5*N8 zNZW0f+Wq8{V`EF3rmW~G+b6u-4h)AE(@vTo2U{!p%Bg=$pd}Qe5+Yq*g)P}R4|#RT zFYs0haI&5@y+fQ-R5dkyp6BdobCWv8oDt__#FYxO0>>W$ArK#7pr>^~#uFB6{8?R> zpsr(lDiwC*h(^lFdRTa88=}n34X}lNZwe^iE2T@f*=4>Nnh-s919sq(7hUM1v>Q0+ z;mGD@zEu)PySey9cDwj@Mm)TqSDRMn58~#kBu*%lu<m#_6DwBr{(WU~UTWdh7!`I_ zh1qpgK5Zo<q$RS-K4RrGU;!TTN-6ZXte%O%inkqA=N)RFLC#VW{FlpqjxsYOe!uLD z3_}Bhmi%b{#1nyR4c+|%#_)KxixI#RyH-+ylf2DX%{(G;*HlBp^9JJ19ZQHJJ3DLq zwrM6z4zPzX_-wkgoDHahOqD1feaLmU;?n%BGZ5G1;9Du1Ku2R^5zRC<ckkY{x1Vh4 zxW9o*jAE3&e)VcSxJtqBL(d^;js?qKyO!9JVsBKGQCyru-U~mq&$pqWOot3?EA%o` z`KEzHd~+7rK_qforq#2b0HMfsAr1R>N3VfFU0T}Bn{^E)3=C(_0v8F~TinH|#;=P6 z0QEM|*Ei6&H!OPp{twa(ld<>jGcqcZle5cfm>pbQlYBm|C$KW7-0*t1Fjw*7^y*pQ zS;;6rkAzP*q|_=02g{cDOrB;+DH0QZ_YMh!T!SJ+_+naG!Z=0{y8S(Btm;yL{Dm`% zz<XN3igE#&e$2Qb9e}eZ9;2CNW(Mg4zhw}lYl&FON<Z9Y!7=0MD|~NsVVzW-fgFVl z$Fp;Y`r8}=f}eM{z*g8nQVM91kx@nD65<#}AUETESV857SPb--Y-2Lrq`-vEO9nh% z*9hOz23?SCNfxbVj);ra)if|Lz&~RQN`*Bl@V^aR$AupQiuoFZP^vAu1%r_%0cuCI z>}J|)`ucL;4YtpJzvnFr0#58oYo2BY1{<ZV4YJKxQBt(+-R&#Tn;WmfM$_s-SF{@> zpZoX#L$dDpz|u|N9^kd-4b40HF$f!O_LDx5*u>*>ox9Bj{nrI-@R|1x?>;LmudLIi z@cMKD58pJi{=Pj^W@1o%UMUlF!S|vl8*ICCrb}x)SIxF&<M0XNMo$>n+u`Y?d&trb zL&BHH=tNZL`pgLvw{d2qjia|W$jh2r+Pk>iMhc{<$U1{#&)NO9#Rf-R^mUNt8`@N& z#^<TAhgT$`r;S2rBstiJS5{9NmblFl8A|7Z$Io<X=5~-s$}6VlQbNVh>+36_swYBq zpFVwRh~E~mmZ#=22QDQFq#U5YtX&vhWDzgx>5<Cd1jNm$UI#7a#T0e!_-76V#8sY> zUq0E7K|S2c_LmO?Irl2Bb$P=|hPSyP?UIpaa^B_GmUkE$YfP?Jwr>>p(@k1t<cxr8 z5s<A1mx0SPo|QAeTB$u1S^U1(E1v7w<{)US5h9#0eqvx7Q~Gw5$e^^oy7KjV!O`h& z%|f1IS-uZ2Sh_c<h1tn@nWZ1ZPB#{XAu7dwQ#r}H9t%PjncP8Q5Km{W2|8h1g3-}1 z4NDtP>+9kT&CSr7ieOM`GkE2`yS(_2j#7k$bWdviVm|Ks=uIHQ)66W&%FSEqm#<rW zp9X2WwKAH!AW+!o`d*Tlz3dDBChJ)AuJqEHV`<9#<}?yHf;fMqy@I1i!1b0;JyS~B z^3$imS;#3!);pTur_dn>1duHS`bZc^3(RzV4_1jmy}{D@%U2w_;k0rh5&MN$4pNip z;kR|8qrYE_Q`*pmP)Fi0_};Qo_wL(~V173$6V*yr(RGzTl~~LBA*W!=%O}K)EpbY0 zRaMHoSX}O<#41l>58S<xBf>4gW#j?A>!r@ydT_sGk~=u9OO8tdj~hlL3|JB$=(;HP zI@yF=7rP^}ecQko?y=*z#mT&~rA&JD6`mCZ;pHpq>Nn8PU+He6A-hbmMk3c&fDf$s zJWr}X4^b$LhxgbzJ1MJqiJj>nZi6e=?*&11{_D$%i}}YXqaOJe_jKUeyC(x4U5yI_ z=d;O@2WqS(4qO*q@gi6u52d|BTDfl=Isg0*3#r@b(J=njwu)E}FS^hN+Xl7Ezr8Om zoT+ay0LlA$rZ8=8;68xsCUDV~7#42cGI>Id?kqjM3u#4HM?0&H{ZMJnI6BYQ7oTg~ z+SUR|6WsayP4kj^mipy!IMM*5vM-+<zIqg(A4l)8Yx`hpF#}OxHCuv%yX9A;6r^E{ zs295tFfKp7Ph%TBUz;1r>TF*yc_$O5M5Cc;;$5+Q%l^)!1xf|vz&&8q;Jy*CdXlX< z-%cx4%gM$_*G0`#BX8sWlR)6zx>?$LfrRTSPRH%8>XUJkYhl21>FycO&^C(LdHj2> z!`?a!;}cm}&b2ntuye4Fj*ayfqq7#3h|%srX(No3)x%qTWigW#ui1DGIlLNBZ*l4e zm*hwRd=?UT($dF(;oV90X;wa#QhO2$<L(_-V@71>bz!lyuZxtLn<`5WfxBldk&^uK z<(>ZCSqx@bhM#da)Y7fsY8$DU50YzGp<|>OXDr@0MGaQN0VxJIOOji^W&Re}od5JU zUV0jTaJP#sG-^8<P-Unb&CUi%6T{&d#s+e8F&883@$`&#zrT{Xjfcm@#p!BmcDkmO zWM@lnLn0{wr*{pj!41Ll0Fq|rSM%Nnb?}rZK?tv<B{DY)wd^_H{K!M%Bd(i3FwlqV z=^OX-_NzkcHb?pRWtZDrjWbdU3v()yOW%q?WeIsNT^@7#eGGC<s4B?=ei28b*;0qC z*vl{zeKZ=dRn_KoWCG5l+wvwgZz{FVJr{SRIU4PN5Gbw6_4#Pusk(IsrQPYCoR&BW zG8cMUh9F@H{?jt#zdqmGP)-)>w>|@_xUrNY-t{Ak&E=zG8nkQ;nHkxoZWwHSX%9FB zTTD0jX>|iRqxuNFy?Q2up1yCu0ljeg77}D=xLQ58%_Ti2DV5qlRw7tgsfmvdxFNtB zzfLHs7or5k43%X*Ajert+C3A@rZj0Ux6_mi3!4VfpqOz9T-PKqF3uvPy<UPK_@-U* zRQNn7nwYhnpPK=`$(cnxI!YY80p74Zkz3VnUnz}SbG4s65{58pa55A>+tk?73=X%; zyaId>US59w&qv3VxH=4K7Z~XL=p<CBfEyezziaPG7sXD$)im)19$}*X?aLP}O%oF> zE&kN4xb(0bI#{4%EiBVanjyT7_T-!PTHlA<&p}+;XvR%_kZ7t!(k;za)fIl-b(PEP zGS9a6HQ<gbdU;)_;)I@VY-w5XBslfwvh$s!lONoKFon*Y++A<j*-5xfRV7eyw)*5~ zWK>kwS3C#-#%^-YnWXo45wG_(q^E>M7^i1McEBxdwCE`1lz3P0imgm|CpM7N;k({O zlx`zkZzH8fCh%NWqglj%T(}{(uKpYK<AN6*Qqm!`gk1#6C7LF|Z(Hcl6>(^8dHI|C zccq2p(y~>YMonk>Lk#ij)71|*Oe&WSw!_D3i5kf8?IWN_Vcf9Vb<1vTCsE2EE_#8k zvVcK&JO7J4xDr{XqG}NkIB`N%rTx>*`tZ_^7W9!p3Lf8wd|%HBKwM)va{L$k>{xl8 z2f8PyoTND*Pi=OX%N!EJ%I5VRn|GJ0O`ca<OH0~uo>cRO+j$=FT_n{<K7*6VbJ@<* zrYqgYBI};1M*EivZ+Zp>m>kF_A|M%9c9wI-M*44kEZB}QFrMt+uK1k>1d`{p<Pe|N zCQ?T0s35LmG%t_$Z&oBF{dlD*hn$Vbd+xt}4dPl)14=cH_itZMq~R{GJ!p8)@WEu7 z)fu(=F+v-x`iiy?^;2b~fH@60A#O^McRo3EZ3)r|uhGr4Ru(MB4qr4kzv1E?{(W5Y z$7(M7xh`7Yf3Sfa7iN&|)O%l8?lCbpU-F!qUg6Sk`(;5SRhozB`f$rv<<>}vU`}cf zc|Muj;S)dIu0<C@{a7V4DROD$Yjb9FTUun*>cX;0?mZAB4}$s@2Vw>%HP<s?w#Wq1 zk8_(VV=xAyr=c(EN-DCG$QGq1<2A}RKzcy9E1Sr~D6d{!p!Gigoz>voQlI#BT7Wh+ zfkbF)Rgp$}Y+Z2lfYMWHcZnWynH&4AE@yV=QuE+xZ9CJQZBPpUvcjW$yT=jLQ?b=j zB*1}F2hZtL10QX%{S-)eX)P?%XJ=w&IZ8oYlon8wKT>IOa`?lqGwUeb;Vt1z&$(w~ zWVHL`I)Ri)UiCGT;mWGKN}1J(WEz;(0zW+{TbN)eA3^VpP0B^J5xV<;6g*S8lnFKk z^h?%NSIP4D-j;xI85n3y89K7~jmsb?A<YL=8vvgcrieJsNC~Ds-a!OKPS1~cv$HE) zhq%Ha)XZ@?%e2SS!mYpi0C_zJljB7M965h-9{gxl7lcs7<y<8#pWG9mTF5KK3%5Wh z)Zaa(7o?dlraq-a#LiV7;4*{&t8R9jO92waOqS1*YPquXw~<zrxDIf^Qo6VD3>qs8 zhb;4eo<W+AMaDflY}%|dDse>OPe|ZoUmL_5Zdwak*;x2A5`za`2*xQp&4<(Pw<mT+ zc*$09Z&F8=!<RH4K41yu{WcgZ?vbjUzE{ZDX-*>FWk0&M#IUx?xMUxT%zFmz4VXE0 z2=y&6I+V))Y(!f^t4h^_v`_jF*X>&t<1K=SADCtOz?wkSf^n4_Z(74C-}J1c_R<vM z18}V%981=Y7zMY1xJ+H(>L~MO4)XFn{2Xdv1j?4IRH?vyM#%6cNd1We>NG&eG9eE` zKqkE3cDh)O{PuI!VBbucg<8E)L$Xgg%P}|t)jwxhF>v?Km&03I5e+Fgcvzflzup&k zdRF>8eA)>^zsJmMpDe!S5kz=MyS&|}K^J8dgF{)-yp?jCjusuK-yhkzx&%;8#lqsn zM0w!IY(kDt&a^XW#iXR?P2{5t&fln?-!aKfs4Bo-licEr87^M$)O}y%{XTQuijLBd z?sq!w0?5Sd>?{bM69XG0Y<C>#&&mT)5EB#Clml;T72BjFB$zzc1_uX!pGDl&mE$!h z461wHI73fK%_V~Ifn6HTYaiNlcQ4agxJv)Ehqzt&SPFaM#NOh{X%1)Jn`vKI9lOM0 zmFR<$JWI0zT=Yy#iVp>s!jn_-m($q8X>&0-c3<WuA+I7Lt_T6w_U%iWgMM@|y1?di zHl%<8w@Qv-pZGUELw2r%ttb!)?a>wjVM>k^@YVY^0WV`|AW?DA;y|(Umy{sXbn<4# zRQ`0(gzKIp=UARM>CDI8m2Yh&msLf*jN026QRH%3e5}XfgDLu~F*P|}ZFO}aR}yLX z1?<P@!{qxMx2cP_k0*Ta@xOT!7%eD21om`qY4xNE()<Hszz<$NrnB_XwEdfyq$G9T zI;sNzlpLQO;7D~Y3e)OJs#5tTrDj_LlLlg&y>)+tDiBPJjM+I@?x2Disn!MM=pv(a zj}L4LK%w$f2jl~fZ$XyJTUJ)`o(CxLLR_Eb-7`1u`q)pX84}vVJfD290}t%Qojv+l z?~sS*`i_l$e~2mJ1gMT_3zb&Mig?Jfhig=8YQ2A(Q|UbsHXpDj3+U9yyl+Dj3%(OO zG7U-|xt!EfEX?jlo=6kY$s8Fw1$zt2Y#$1ck}^nWSQRGJ9e!+OZ8gWnbXK0}AE<s& z3JpDu%FfLT>LG<wX%oAU{ho)&MN-A&>7Us9zsJd(V=>Z)r}_}YPZ#R&I$^MwtaQB7 z-U@=~x9qY%ZT0cfZw&xjV31oxDD6kE1(0z)r!U7l3fy^@PE^Jq*g9~Mi8|53_3&m< zGOpo~1Jjv}ptk76wKoKPxfG|{<Cw5kgZhViE4x4-S-ef>NmK>oNjhJcJJDsEZks1C znPeTvqc;S>*39I{vXs2wA4kt2S*QDE<}7WHd)q#&YiEvLZ~S%{Kw?4_3Njo(1Fe*g z#RZZVwMjpQ1_c)3x1SuMe;?H2qODHd-rwszIweXQdju@2*vH-7Jtds|Lc~$Xnh}Vz zR##8r#Ep{4fWCbJV`pa%qb0~nOWfmYpU+HMm99c9+%@t%w8zV+XCg5wVJBq2^>#1n ziMZZ}PpjW;{rIJSDi#=JAjH;G{$G@?v$G9+)3X@ba?uuIr9IhKqN|~;N|qFrr~#$r z$&tLSw2P3n>-1W{NvbAGxc23}!B$j8y2W#!Iu}bw^BIGUylFHvG>7MRj16IL&hE|r zGID7&c>GFdZVVgIh@Kw;VFx)JIgL}(dtx}Yxw9OH2UiA)lX;$>gZP*tBO^g5EKmh} z0<wM@RhN(opqCP;fyKMS$3$sPALZ*ZH+qYEPt3nV&$k)q8@p}1QvF(agO0MROVsm* zOYPA6w`5#y>DnQY#Kk|}!=3`8VqD!*y0KF&<#jm~JLUsw&(9fFZ-Ww~p@2pOAI-yR z#??>i-wr$mwF`S9RQ*GUQ;vXS-P@pqavxCc;7mFJ)<I@<wI7`vWM!>=VXl>yUiv5! zgL{_-<gW~GZ-h9GrvxTbD~B%|3nD2bGK)W6)}G3#cZKD3QCVj|tAi?qeBftH5}zN@ z(Zj>iG=V1Jy-)osAY^Q>#Y2TzuL6hD%$X`}*|_qRD=;jGi+A<pkT&C(Uc8j;!ylqH zWFidwjh5-m=!on{iKB>+X$hnL3Oqa)iP>9Uw+A5+6!>`z2hj=kHpByWEf8dZT6A-% z9zSB;<J&U+>3l9GTbhl<bn{ij7r1)@zE}3^m#?3O_H@)c*>#{5hx_)2-N#j7o~$Ge zU{eP&frgRa)<?iVF(x9M9n@97Jf4u`17U4tD|X0z;X4KdhM&_<G3^E7eC_7urq%)n zZ`u-(cw}x%2X1_q%<_=&(i$eBtD~6%;`K#eo=mdqIQW_X(h1-w0+L<@&X!W(=tVO! zW(>;GXsN^DGOpw7w`YJj=y#BHmuOLN1%>9`Xrn(4i_-4ezCn6KT-w_<y~(*sapKs@ zv~C1U>E-bqO)B5m9x~ID<5|PLQBiZ{4>#8kK{ZkWxN;XfHojZpb3#?MV*3V6DNOqy zc#YtnKxJdDqowbMp5i~ZC5FVn-mOnz=LWoVssHLRJ3A-Mj%G?d$k4j$Nrfe6F;D|@ z_CL%^_ixna!s|d@l7pRHL~d-J?_r^BRXcj*f$m1&#hjx+cBtsj!m}Du!ks@9>G3a~ zq`&&MAS(_L1%zT|s5ED!_>3BRZVta@b(Lv|JuAr>MZhxrm^v_WsjeWXr28CXjRX!* zM-nYaacT4Jv9pEo@qC*~mDiOOm{KKs<sIbgrq}#OaD<-UvoHGE)oK3g<7$b;U{_@{ z@8aX_b_e^XdVUyy_J(<~@llgNlIHm`v}}#d23z}|HbM3%hcEN`hTmp?3*&ux+$$LN zE-A@wVGdPyF>Ox++2xzGAS<qBYn;sHjf3Elg%bGQz;~rTdlqt&%x-J2C(*IVk@l7r zr#3|F1fEJxT|-;+<KxpnkXE(T71y^K*J`#{gD-+`73<{W>FXlTMH>me56Pc7PbwGJ z%pItV84j8tXuV%-ib|-In?$o-J^(ZXKR&y8jOY;2#zUEY+$$8T!{>xa+dBe`ZjdMD zXE|nTlP!yuZn__Y(ovuO5Z2miV1#>=L<x9clp1;DNIPJ{K?Y?HtWUijQmV|>1T}y4 z38oN?o#FNa5x0lgh0>~qtL@Np2t#~Df2TV(E%eYuz$fbnFGdMrnGboDQ^Kmt$Kc2| zqT9w{=G?0VX6!)qYiN{sc@kygyk(WCs^Hb?ckdEDzV0cN+=FxE$P5~YFQGg(lK<se zzK;^HWruE%Ky1BthD6aX>+0$zTxWBU&>udglvz{r=CKs%sj~8(a^#0cKJrGncq~7M zJ+*veq9t@$fRCe?XyD}~L)INo8_OM~B*X50R!Z9JNK6~0oVvDAr>rASeDkF@6x60- z8$znfi>vjA&zMza-@Ad(RNGCO-?ZkY`JL_o5ASI6cpJmmc*dapWGDipNSvKWG+f^l z&QfrF?oj(m8XssWPjG8=uWsXexV#d^B5ph2%-Xza3FyA`iccuY`>;AQSxg){z{|>0 zfy}7r>gvP|RM!iG_c*2bOOi6Hm#JZ}rADvpB)VZ|wcV&Jd?D~I0rk5%e33A<`dhSb zfTaa_;LT)plihnpH_j`M9iXepZcPHgos6-8(dd`;aC$9Bd^8^9CiCCL(j4UU>;6nC z2h~NOf?u=xJcJtLA~)yz1bBH7jnWkIue#`$$psCK<7ZwJILlJ|BxRSs@t&}ilS_u} z&1?*70!IMIN`~TmR!(z3^fV3dpp@xMYnl%kuFdJ<{=DZmRy8(%qmt8faB!LbeH?Ag zdI-RE4etaho<X_yNLSK`1P-3!)V#6^D(G^i^X-p=nMZ*E_S&{u?LQ7~iyN2_m<s#p zRe)Ok`@PpI9C72AoSbrx9TlZZf22l#E-M1}_{K=n5he;hq0KaxXJ|>WcZCmbXkf7O zn-zzAomNy;LJ#~#U0|{?iwD7p?;+=B_K`4f7=v4D?JoY9Bfn8YTkICrKSf}p%-hOl zat~{RG-CAez7hj1LnSHzWb>jWdSI(>vk3L}o2Ef#(wlehPz$!k7i!LEQ1~t6YuUM2 zjIVdvH`qhhPbtgMeHFh7e2k4`R)%8$=V%|YXkBOH0Qb{Nr>mG%5=Fep9#<Ji6oDg& zLf+L<USi$jz5`-gB$v4md^v;aEAR3ed5h&|015{^0YcoWxR}>y_n7n`W`rSEsk=Ln zv3xxs0Obm)%fJ|#NV`v!Mw7ad_B{CI6FAYUBBF=MuMP3qkXNoEFBLBc&f2N-(g%Gf zo#@ZZ&b}W*2`27-;`<2XZtFKjJw=%)<t+8}foZE5z7L`Poz6JFt)3LkFb0ap4F~e> z(S7}jTczzgIzINO3CM@*WWCRk_KDyi9*}k-b<AelfKne(QIoR%-iAgX0YjrBrMMo( zGiq<zCoXziwh9%-eQ@cUDMmNjv)<j?79o8YjWSUa5D+x1p#j?hW}@F6gAFx54Mv+^ zxQ=phpYCdAls7Epc{6E#x_{gE#EH<yFj{&tCM<X-EUb+uA@xOLv)Juhyiq3VU&QUw z%ky6x1=>0^%0_!vwNlT-XhpF5Mmr~UQ#0SjlR0p+B_s!QvOJqU@{zlPgWFVBT8~aI zJS*}Y>fy{J3-##t%a;Pl9MjyGSO?W~hvlZ0{Jy}Eg|;@xDo~Q>Nwua#0cM}(8A9i# z8{1^(8YiTThYE*HgAZBy-70FCYAwmDAHA5?*WZr@2MRU%&dwijB!N`6xxo?}i-qw= zkO`D!HZnH4ZoT;YTtkko5egCqn)-DzJ-~7&J{|{GS>OiRFo6bqAhUd;kM}ZvDj87o z@lsnzRJ@xg`rOX@$)j&T&Y<6OJDKi`0x;>RE}HH{Hy3xQX&22az$E9o^cC9|1e3ww z0w10D(E(n8_!stIo%LJ__Sq6{b}mc629Dg@u3~_CYq{qcDe2F1LrE}*xOi^5chcJ& z<2>KXz+zwJt-xSHK{%D~t_Gk~Qc~JH9Pm`R>6_FE3a(EkhIlNl9K9k!?K5*CS^M!j zOxFA)$MFD%s5fBITrza>*4Eh|fOOU0aQ?!K2(c{jsvznVJ%jz2dd*Rxc)?4DH!2Es z1dD_Hkuxyx5+{bin);BZZhPVz5O8D~nWcYCUyB#SkN8e;bxqy20bn_~Dw*X=uqD7@ z$e)i40xWK72658Ur^>IGfsq-W&PEi8iRD`-nUUoZ6gkdK9`mmyTdq&y=P;yIXG45T zg6wnQun|xWXH{k2Y0uj7gX~S}=Yb8U7Zq@R>U{Jx<Bm=wQ8A--I1xr3OK{N4)S^qx zKko-_8loeYmrq7TM>|9UT?#y7_Qfd@2Dj8TbEd{%;hCjRp3Eie*cr2v1$L3`XkM7p z>*KV^!u5G~-N#$NA;8Av$_w;txa_ch%(k8uGAiOsWmiA`+t*a=hwNxzlopL@K{Zj@ zav?6}TP-=W*5tl%SgGlo0t;A4<{B4)<e8@R!2b(81#4T{(bv_<*whH38fvb)wM=Jd z$Da_F?&s+p-u?i9ZOWPJ1um{s_F1qPdj|QYH*bb9p`j}4C-{K7adu#CHa90z+nR%h zvw5!3&?m9YDIOGAg}4e@VYDF`nV8*K!fyJzw_?->foV9!)!M|QsiP6Y81xAt%q2M2 z#tlbueF|;O`4GmEGra_mIxin@KwVo>0<v)%2cgpHa^PyUuY}~@&>}cqYx{=cV<6r5 zr=I$^W8lsN*Au)N@|6h@l8Hf0Oic!VAHPCJN$#MAG=l5s7~qoxzb!W}k9;vO>Ic_z z%`Gh;JHd2n_=pv9UeCaY)@P8roL6O@4Q>eU>FU?j*G`KRc_M(I5aRw6&gxDd9a(6M z5@#G#O&0E9?(FN>*xcP9&__jmrlkUt$oj(GrC~fB5S~uxZsNAdYA>}9NMwVE;e@Jg zi15?8C4i?FExB4bD_yp9myq`@IdUoeBxR`Z+seGmf-EWUl_Bo{^}%AtKJu52HP9W? zPedGECxBxsQ=9Ws8#0;3r?0#mgea=Ixg}vK*Axx{?m>~s^U#(y5U8^tylOt3Lxf=8 ze#l8Kn0A&kef|J{l_Gkb_VdMJYAtY<h+PZ2BDSQ6Tp3At4o618sHmuBUyHLa=e{>N zPC-`_%uNlx!@0yY3i7`S(t;Jl-G`AdSQ99f!b?e>Utd?^T@5}!!5(V-o<l_%WnnSi z`0bBX+}m;VICe6my+;q+<#l_F=$LD3yM-OsWdWAv=7sCfC>3xUY3=-=-hSpBxfm-1 zM|JZi@Ycb!t_clO)Zk-e#@BQ;^m9wY!(YD#gNi?1zqK7!D-1^X`IgTZQUF|=*A5wC zV$L1{r?P`<LbTGQaZuUb_IYN0{#w}iw5^7KG#oP~bXo!s4pnUda4&J6N)c_7KLC7$ z!G@924&v#>Pd!Z?9m6%Pt2!dj--G(9u#sYpVT<q`5~$kg=*TbKsBfB@inn`32|)e) zlvT+x<Qk}b0pG37%_*d8%0bj2cC5Ner)WmMkB#{|)UH#3y4X5U5j<TAN3#2o2SLCU zw%<r`7y^r%thnb#2LqLu%OAgpoIguZd1RXUO5>vW$w|}u8w4bVO-%6+IQcTchd~sr zRO6=(0@3*2_$4;D_uap8aNYI4__OkToB5ypMxGJB%SM6tIv*Jg<5?#EW5IWB-c`tv IzvuIR05xHvF#rGn diff --git a/elasticsearch-persistence/examples/music/assets/blank_cover.png b/elasticsearch-persistence/examples/music/assets/blank_cover.png deleted file mode 100644 index 8c513407a0456537db0fd720756e7ba7a5da1737..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22778 zcmZ6yby$<{8#lbs($XCQN=h>YX$k4>7$UVXK$H*!1_&sPF$pQ@p1?*)ODNqiN<tJy z2r@wOz5V{)=Qy6@`GaHZ-W6wjuJbzUeq~~$Lqo|y2?Bv=9_ZdT1A&Na{{2yq0Z-c8 z(-(jb_NQ7lPtASZp9Z=3yMfdn`95^xdw_89a5HmrdGze@SGRj0kj;k&_th<eaodY@ zFWq_~_x78AUT(H6DjUhr@qT-m@{H{{o4Yid!_7uE2M6lgx37^!cS^00M)SW;72;Qa zb^oodhTv-<{@3)juLQkk>0eb=R^F+6WMg_msq((eJ=+X*m)W`cK;xk0_L^|Z0H0-^ zpT^6fBX^I_|ArU(_o-&61{hEM%`R88#H{#v0wkw?cqs~e@Ik>K5Iq|Bj{@N8|NT0f z)Z23z&3J41BMSKO_V@UU_r@$}5R{A@;WbzLaA0aBmmBT2Y<smRkFA&d{l|ejJo$-j z-46oQc?87!*-m9dAPxoTN^z!B)#*dIK6eAXR_h>=DuW}M<VcDtyyrMICOd!cN!GbV z#J2O2*5Gd+EH6l{j654hHFT$>#Qes<1|HilQD5y^-X;Z+h=W=J3@J>k<3Isk6vk(o z!R1jGuKfEND?2;%y-Zz3F`%Q6lYO{@9tcdOhS{de--J?_&cyHq2wBRe4QSkTrU;dN zQDv=hqR8^?K{+kJyKr^z)^77BwW8XT@~>as<$vJrFiMH3a^*+%n5X>{iT`?eoyOa5 zQx^KSMZPbweQTT<p>z`ME4z(ElJaqKWQ=$1Xu8IjmaVoZzMb#8%+PL0PJYKqBjBjE zI~Ci#LV87f)Jf#OQu~RInOw}mnyY?wl%QZqG{|}e1^48cCUR<~o847=2OZ3>(>UVf z_bcO)W?f>XRwJXZQB(2cKdizoFc2|Mgr$FpG})z$h){e{<$-wCd(yxTe)LjA1FBLV zyH#wW;&V<Ez$alRd<D19p^K4sQ0p4bxgiJz&y7)ZBd`lxQI=Vvom}28QV9+ej)^{C z67f6}wjzz|Qq83FXyn2~Q?Q+WK*_#<WujTWu3U3bT2)D+a|=fM9Uye*QvMe08fzJ2 zxd{qQ@TRaIVZefjSW;sGDa?bemz(6H36D+|^7Q^aduv5uo_RU(a*EP2igH{RRZO>* zCNb>|><4{Fpf7&<^wkn*LH`{{paOdjF;JuNYs*}S4|*Mqq#9HI8kdwy^!e=;==Rpz zee;*bY!SNhY8^MA;AHKRU5d{5x=Z9HH;p>}eQ%0CupkkOsIJb$0^(ebFJUTj-BbE= zF&!`D@)u8@3|Q==o{1?(RdXLIqTz>gQZA*S1NbW}4oj%XuUd4q>3bQ^2hE37V}A5u zKcIia_10jBQrn{Qhq5UUzwOFN0O_3gP#KT4L&6nPC0LMuZLT4HALkn`X}<&1&ZMvO zbUgz71M=TAUdRjV-?&k$jAC*yRlhW%;<tqT<-|$Z%Zf4vzL{!TgeBrAE4IoM!2Pd6 z@NYj7dbEkQfh_1p*P#l9O$V2k`Dlc!_*>F%;^FE)mNl}-0ma96ZmD|zjggo!=T;MP z;KiNuQrH1t{d(HCpT%7MrZsj8B)YjZN`4;tixH5zRFOU6*&HfD&YPODLy7J5#d}R& zFrZe)=C3CKtnHW|isi%|cO6(yDG1CEb8cXzpCypk`ci^3HM|nEoqQS}K^sQy%N>Oz zO}xvb!OSg)6yzDDH3dfIf2)T&YM!^thTGAD+yl&inTNxJMaW3REe73s_ogcwB5evO z`DhwqRW|zaN5B*|!V){<&vPXr<iZ;^6fKGPv<Ra!=hBDDAC3S*^?n+yK`5X1qnOKy z)nctQmjA?!vI44PTEg_UYhOIEuP#&s4UWCbmppa+l?6<o^vK<Z^M?D*`h(TlxG2LW z=ro-Hz@~=U{qakYxEeXZ(1B8|w<<2#RBRb3iCFyg1uP}wO1fGt$dN>|tf!UlX$p4u zp!^>7$iq-TT@M|JkvHOJ62eD*-=L+i>5sHeT!wFmS&$+bPV|0F_~eVKb#&cxfSu3Z zxq37J2B>_+MG-bXqUlTy?0RiS;16%?#Us$@3_$d63vv+S!+IVP@j~qy$-3KMwRcH1 z5I22bq*PsW?_S7YmOQBZcDU<lP`)n-g-v=qt&W>78)#N!>Cc`@IT4At!NYb5Nw$bP zu!JoT^4K#*<sfF%19<K*y3^Y(|3ttN3}Ov%{Uz&L@{g=0PekX4_h0gEX$VN_Rcrp< zd4@}sC^$j1gRSb;=)81X86_wkw=(*^*5@5ECVrx#*RHS(@^E&U6yzSncUX4Jj(q|s zv^Kkgs#kKnkP(D@#Ot(1`upBz2DAyk;?c@6c0tV&qz3b$2$Rs+0buHnSYvhBnk8qc zC008<id8xG$fqL%QwcHFP~9;AMsCn@PPYJ)6(OAM_}>@{QqbTKCSP*Hz#34sns4nd zrjxLL4;R};Z#@~L1yhk04ef@z2J6#<4u0j2>eNY#0MGoB_I;}tw1M?}7FkZW;H7{b z5B6y7FNaVSrIeul)(VZ>Fmg-v4h<DAj^Cl!^De1%YLHef!|CoGJL)N*7qRzq2a?$5 zG36j+*QwsG0m%YUK-}%oy>ybxy1=?Ci)^Qd(H5j&sy~RAlC%CofMARARW6*L`Q3!N zsL;+IjKp44f$|ao9xtW-?G)ICIB&9&C?^8xRWP5P`kFJ7h(DT`Z8HK^b9<g02P0w` zFDO%sO#tU*(sH6=03z;Ww+&}SflLtloU!EpcyJGDm1vjU9;5~RK8{+B2_*(-8sF*l z?d~Xk0z%HZtt|N|0ef|=?_z)OdV85OgOGLg-O8&z<x$9(#j|n=)nX11h;@I>Zv?o` zSobCD+cf1tpukO~l_f9{%k5F!wg<rXn}wl$$EC4<;U=U_1ujk-<6zLlX}p9O_IXZu zI<GeRpulHfnXwuKy4I+)V7>)Nq2k-q?M_MWri5}3NN~N{&v(U>o5)INlX-rb?MNC7 z65KoTOL3{-C9*2=Og_)`ZUO`gDqKhT)!60(g0)1?oDeJBwWbH9VmZ5&JtP4p$~77l z?^}XEGX5`9wK_!Wg6&d?6~Q3emL(}i-40QH37PXMmvu^zlxmzt|HBm}aE088p07W8 z19;>MVOzQ_2z`e<;`^&(?^6tTC3hb`X@q<-W&u~E)+baegt&^4fz*~37<Ns!PmDT5 zKjh9HJT3==Qq6hkjz#Iwl$y6PdH)IPZqS{DI<<~(t!Ig<V_YEQjT61bph)C9`{UUI z`Y8}dI6baQnVO_>p=k)kYeEA;MxClRz6S<yLfDsjK_Djk^CP8O$@FOdmAw74S7<Lz zP~NZAH#*fH0lxp#FQ2%Q-%(=(nXtT%P3>t-Ldvs-YBu7G0mk(<VBJ1@B$anxrgYo| zFbC4p!zV;&%hRK44=?;yz#tR%Alw0>9N3E#(ckOD4?>ooG)n}T(vVcjc}1k|{i7y+ zEF)%cfgWv^Pq0#jYzL5nN_nPv7=iEBR)pT~lmKi?OHX$G>X%UxSuw~xB+$j~uz^6M zt*JYl6gIC;awH&TG$7C&2-~y(Big-i#=QN$JP3sDe&40cKvMb2i+|a;6!2PyH*dvi z0mNSO;^!s5Lk#i@-|?$iAJib4uJ%$QGh!eIeebHKJT{<5-`>i?F6jMRL#!b-^cIE9 z&j2dcYakHgnR{a#1G+To_zrZoj2ndXn(S6S1Yo+h8WVB`oLW%v$IxHC20hO70??g9 zP5bI1Kt1UXel*;<0Y$V7pt>!HL7-QSvJ3XtY?zCiR{v=OB)COh{1N|&!e*Z8V3rRM z$fF&3=CH?!NVmZ-EZ0Y11>K%}zZxN^<+i>@yBG*GPB1k8)R`zsUdh}YZVduG;&)1} z)94UYMSS%`!g)YQ11^eVCnBp?i!P$)Tf!jF6MySQaz?b?uWzD4fD`P$(YNAc&>?z# z2z=GLBFqH>ZMgY1vc3<Yc{)WjZH;J3gL8tAP2;U6c#=y0QTFKN)l4!FA8VVJ-t$Xe zOCnS(zx>bn)sTPB>{gZ>zLj;AlT=F1`76)JfIv<DVU0SBXiD8PA1QeXkeW)^z*|4( zZ=xjPh_C1_0u@N@_9oATE0NVFPt7{}#y~KrACqPHRRJXVq+{I4diiH2DJYM=%~!A9 z*e!#2nvR+?o(N>Zbk^0Hg?#7Qfb+jK1Dur@e~-pEMzp_f!jC&}dJyuVXwQI`8?_Bx z1|`>xYaq1;n>?2YBC9s8+8+rHSpddy$}2fW9pAtR%$273!9)`PDrDF4!~hd|S3yWc zjsd`7lfuIMHJc}e)=Ipqb|j!Y3mzHLMSqc1Dj*2BYdH&ki}Snl`Fn)l>bm{2D_Kv9 zeYb9$7~k<0p}9;a?bTM=MP*k1MdOAl9D85hye?P%jD70R)!D&=S!IOf*yYrM{nwK` zUb;bOUi(am#6|wuHr?q_-)Vi(@=ty6fiVrC8~Zu)=khx9ndI{RaT0^YIUR+yTDTlX zz(gs;U@>gEK^32$Au2aV7sJgPzo1&1FY2E!dU<?AzHu)D_S1aT1~f6k%zpM{+HsZA z1&+RePd-oQJiWgW>cd$Zf;|jf%T=DhUvEQxzLymfdg07eE-4;d1n-;SY&=44i&V;G z(M)*D8FYs$a9sAycb0Bwr#S3+Y7U)vzNOMIMaX@VON=%-zY5^5P@vRWQ{VDd(m^M# zzB?nuMsSf=NGTTc&S0<FcaPm7B8J>2l0G(Fy*Gvy&zq0d7R^6V-NlV0B%KoL_Ry^4 zTv@oB_7fRjhrORsovqEq)AoP&G63sEej`gendlRir4$T*D@v;ou4nY)gMf|??zX{P zlHm%Cc0T-j@hO?hmhU~?B=YJzjZGpw^dE0Pdf0Z-qn?_-_JPU7Gp;75$sI2@M>JfV z9h<*Y`PHNB)-WHSE_P`FB2Inv27AtHKYehLB{spd!g|eGEi<y*$8HGah0Q#w^XfKd z(n#Wc7OzvZ`g?-+1SyBgU$B@TE^BC`l6X)Vrl{m+cpy97{q{$)$e<L4z1I4r4M*6# z6V*nz1gD&(WO$)Izc*Bo(muQTVp|GBQRorTc&uvnV%ghN2W|0{qt$LDgqUHA+8gPK zv2$0C{1y)TBAW9tK_|E?_#KsQrqvVk$49xIg5{jW<8mJvjjNbtZDPGfqzGla@bwCo z*YAsE8g#Z|3$8Z@$D2j$8kM+WnHslmna8VrlnW^`t$g$BEa{ya30|d(SxPCUIEFN) z9Cel$jp~ZluEc77kuoS^KR#YFe{{P2?ib?o(%r6n?Z)984sJv4Si&O=I$VFTT>A>Q zqjl_zK!|t;EO^}OB8S4n_=%tMAJbKla&<nmCxiByU>eciqo-_2Ih<cq6HGDm)stGD zTLZ?CUKYjne@)p{aX(9pE|_5OdbG1^$i8CDiY>m+VYb%o2|NK!Y9RT3Ug50^37#Jy z@)@5EIv+L`Qyw!mxg1;?&sx~hTDa|Tuct^&i*5En08?DioqBB7WO=booU5;)wyO*L z@fi8MayRqY)ncuB#hOZnHp$J8)V~q-Dkyw-1605Uf;%9ZVL>Z3Z?=qyhPocN@ZK-z z+4A9x9{Uxadz4U8E4LH3>BS+P6Eao*G1n+Dfn6dw$X5>+(Sm9Ep^ag32XEta5O-g{ zUmlzju+gHjX|MZ1Kf~k~?nCMKxRX?@!6d9+?DUAoc(>O_wWW2}Q!sXiY&?IDFDDtH zkm+(Lh08IU#4@=YKMNzLw9Cpe;3sPFn?$9o%R`yH;(sf2G5dvch}b$f$RRkZpRNUl zs>hUx2HiJkhv4S!<z$YRGLJJ$W;#2ouC)~emn$qaIW4ELjL{Y3pA9qhTcO?$#%f~} zAb*pQf0USNJ13r<4>7xu=>Ms*n+=zkVMM>t*2XE=Bu!hznEXhNe+AjDA94^7$c!JC z<MaRB>eXt+=9Q>}VL}^x{-GHRH&+ktCaVf=Wkqr)&mHi~&RGSQ32#wMbH5OS-h$Xo zIXx}lZI<?}sp4ohRA#QM-xM-8`nx{eGFQt|m14L#t%F~`kp!FD8#k0t$SQ7_gm7|Z z`B+eO^mnFS_YZnubX^+_0Mk&bW>t|Wk;+muJml-1F3(<{97k0x#KllLfUQcDsWqll z#3|<IKF03U2-AdD>!X}&Hyt$tf9HaP{~2pae#rco1b@4W8Ni%b%TBK1f~n+~r^mii z#P_4dNsHHA%zYT9H_I#quG1qQuHiRX7M*Hv*7`bsC*#sW&rD__zhxxRM6rCA?Q*dX z&n#KCSkeKABIwxD`d#GJcIrWJMOqE}KZR{FcDi4`t6X?VHi0ZEApOlTcF@u_6Ub#% zl~-gcf$(mA>^>0#&+A%3{~&fxF0j$!>}*c(izy$+aVD9aWNq1?DIr$-ov?1SvD;k{ zshZq5KLN{~9xRnz*cp)juv~aK?LS|&HXhOUFxX-;GL=22f52CGM8Kx9RYS;2{m;1B zJa_kMgWYd()<g2JhG55Zj`C`5^PWrv>X|tETzR(UwZ!3BlV0~vrFl)&J)+|jEbtl= zkxW`UY^UA|?EEEj(nC+fDJcU|QHT8IRnlEb+f2o2xlc<fhXr=MNieTmi5T&uaA`y$ zWz)BHPbRS%0h<uPGdla~jSzK9Z6VZq)B>j~&eLo~ui!e4%fiqiUk_q1T-lvWyWk{6 zxlU={9Job@<s=;DFE}gNNsV~k5?v3lSMl7Cinn}wy>kAVbt#b{`EAJt9eAZ2CBM)9 zfnVsC@?{SuqP-&ceWfq>A=6{`d>Z@ieBO&mOc7r9$^rj(dL~2RzL2oe`a|c+&&kk> zEQy&X*?EHkbeXgcPEH(#<UV@=a#D*<DFJfnqG+*4^4-FopLK8@JCu9fvx)-w!tK^c z%Ztk9T5O%oX?`&>6P{7U#F+LppQvy}Kd2%%Z?RlaLH11K1Ci94>cB<LnC4!lS^KJD zD!b-hyCd9$dvSAb@I1Xx@f&BKPe10KUEVxkL$`HdB4zvwtOoRboW&yQlX7Xdy#``d zMi+lSU2}a3t}|1cArz(-k^aVAIG%ZI-r<R<)<4>o<{UDFeC#2_zCKk1!%N`aP4DnT zZUv^%%$(JQyTi1J3vdDlhT}yUXEAY_AZZDP5D>JKAJv*{bx>eS!+AvR!<jmLc7(gy zpw<!1Pn6>f>_-*+Ii<OOw<1-N{fdm=grm8s)pw7lK}FjfV#inj`CRYkNG^S~X<YF_ ztWbLfRW0oZZ{B#iv&k`uDTSNOZ1_`LseOGOkkcNV!_wpo8;HP-Z)B>md$z>9L%$4j zapB)?=GLwvBZm0rWpSqx<jby41|v`!GbkG8@Gl8)hN6#E9QAJ9c-!-b9+Lhe6BN_$ zDXfL5l4w8s?g{z4fT|Z*nGP*BGF2{=PHibI^rzl+eA0aMl%_Y-Z4>9fmgMfZTJ4x8 zC?{~#^Xy{v8GEN0di>W^kprD@^SgI$4e2%>6bfDQ_IBj1-sbkMRm}bxk>9GQD%tBs zVNIqIt$xxJ<}+|O-h$y9bO<HpZEJYO`VCJp?b5|ec~$zT`>i&Jsr;$-2bF*?NqP+x zX~ffZ{HhB$@gTz^?U~IJOa=UI_yn`?fozRL(8wC=a5n`@oMOT6L)7;iVD}}xnjSdw zCtCO9WdMC*>lwwQ)~E|Y1+oaC8M1O37KF%dpf-K#i#upH{MPq#9k4=HUCi+YKPk2h z#U=QryIdCEDnJyvDLR#2h^^;Tn>toBy9exSaZF<>;dV3F>c@E<)pC~<Z3aL1p<&s3 zJX%ErD@FVexBhr(TugUZ11ctFTljS__;a<RV|Ozuzz~m{mam3$3!^11C7_NT=h{%U zd{5CIul0`_F``L+j9YOHBmsA1sa}{`GzSLI;O(a6tKcoyr`<L)XE1b3qxq?KMH$h% z8%jE3o-2D!5}?4)YnTNZolyJDGG_r0V4N)&C=F-Oype=6?9{^T-x5JXdv5zm@%S7A z-Y-8J`@?F&L)NTMh+{^*6$W$OF*gL6)QzEy|7uQ*WktG3qZJrPza2{DzlR)-$0kpY zv1cwijZl*}FU>4)zEq1WY<ANGaJH<~sFC*TTKJyO<)$Mf>y87Bcd1?2Psc81y%OGP zgyY(-UBk@h>M%$=d_D?CV!|iJANuHF4j9kV24{*%Zk`MM$`iDZr2T2t5gmF|{AaiY zzVT3?Q_XbqKk+E5B&i|P5b6jG1hH`HU~S)LqG=keL-%pwjZq%;_uICC69W5lh<)`T zTn2Y3kt2RTa!eFP1!IJ9a@9l=HQjuaI!-38U1LJA)1%b@E|`z)jHw8}Q~GT2!`(V{ z(tNY0LwH<}FB=Z~UDIkE_Yy~GLZ9&{wU_G>YPWFtxXpQ8#R0=Q1eoh=5gWR`CF(e0 zh+{3LXL!2wCPJ^=t6Tr5^4B75LrnC|{`aW)T?fsjk6K{i=<T{6Pcz&5?`flV<u3g^ z_?B177ZnJlyLymH<*_K|F6MG0M2AFp1HhMn&n=kj_G$8sr~BB2v?EEG6l6g99!rzY zvxM+<y#gz{{&`L+YaAm5@-c#+))po}kQ%SRc0c8(<Rgf`4(J$Pt;PR!O0%}BZ2A0| zsmn6#>7#z7y~?I1IFPq_mdX6fl$Y`INjQ3{<+LX=S#O=O1B%cK=Qi{8cTSL*;`k7T znNNJJ;Um;tnPPyu5}E2Y{}LCEGZ08TFPHcfL+pQh%HXwI$M_s&&_URZlzNwsEI@vT zs+n{rnqR=W!$%W^5QE9?>!NV4aZFihH~Mxd=L&y=@mBV?*Us)Qc~0Ett%em;v7OIH z?J-m%A84QuvM{B2`#X~k^JbjH{=ddk4JmoiyWd;xFP+p)TP?U?_iuhwq`hy>BkoCl zvp`|3m<que4-v2ghOZ$#*DYYOa9dLGR)()j*8R*g`+NK35V$0-azMo|dVB0cP;mja zK!K^@7DfUiJu~f{MeM(!@MgTJ-<Gs8h4ZLQW4MuaIb4C0=D2=w=V%ac7aG!TAUDfs z$|TAR%MfKrQyRdOn;39jB@s))e43b;*uc$N;|U<hG4y!a>^QOc^v)%j?n{8(w>T6o z9hZswfa6J(EqnA<oCt+2Xph;WYkKD3C7BsxH6Sitzi`7Li1^p>-9~oZ^#6NCORIo~ zk3x87=n4^vrJ=6*dQ+ZtFGe+G2^QbrIvNSvtFAJ?t48TC&*9^{oQEw@=s%+AzwY*S zK7kFL&U0VIE;ZEEZ*CHn_DXwR`oyou81$9X*A@08EwoOGGVAnUJ841pUPU1{T5dh? z<sQwm@7<O|J2J{_F=AI(M&#ia-=F0}Gz&born7C41e^XO&6_J;UG>DM*oENxoeMow z@U<Pb!@xH=<#Tw-{@8z9M%0b^E=w&bMBdeZo+-714B%X5-!P0DzI|Ifos?Szo|}m= z9<eHMRZvhU<KokVJ%NS#<ldcUVbz#lhkCBx|2QvS$c{cd`abmBIlV-?zy)w{4J}d( zl;aKBf0hu>*i6z6YAqa1vP|8aQkc@7vWmRhBJ$|{0=amSleF^7COcavf4DNoK6tpB ze_k5&ht?KLL))DdljQdy87C^`5%A{wpnWD7AJ`@~IN?uWP0(JXCL^|<o}NLJEEV`8 z@0xL1*eA{x;Yx8^GW=A3-rEE|fE!rZk1T&Ve(`LKM`=JG!Kl@?wzPorPk`SgBkw22 zXc<x|kkiyPHhQwKumIA6mEgcU>2I<$Vl)2QqzM05=1Xk~ZdMQ7`C>1Vo-d8ZSGEux ze8D`JS7>PPd8s{d+VMe9LG7(oNAtTqia!j-vZdY05Jm!ArIeG;c=F~N!RCU#ws&4i z>ej1AY(PR;%Oe}Z%DTvuuZY-bEuj58@76BHmooXEIHugE0_T0cr4~F@>rk9mx36A5 zQ=eYAF;^rMRO($Q!G0PJ-pP>Vw4hCOg%!XmV0Ew-ST}6Ifcc?OHMQ-{AaKH%*kB9` zCM#3*iJufypDa&zwM+x!7^LkYQSSv+aaYNbN<#J=QTIF4%{|2{u-Jv$sZY@0J%-Uj z#3uCrR@~pWgblzZVDlYZgZFISKJ6H%EuA@N<%16lARyV_i^(tz$zcrcFnVDYh-E41 z9<i$BujAf_OjJ)ECuKagJ}xAlmWW-PPx}-YIyPC*j8DZRYu4PXl9i`mdJS*!DIbj! z#~?6|`CJL9UjB^eSqG*JGxEwFbdvUB>%mSj*$gBYPA@Esam2V>S>-=Q%(s7LFumK4 zGx#Z@^Xzm8^k)3Vtewqlb=cDd#^3DPHOLzY^XO&Hie20tswHW1SsD%@cJyoxIAeTu zMreL>tC42xZIt2$zfwF=OtC^GcDwjI=`q3qkxjbSKWi{AFUXGe`QD<2LZPs%*b0H& zBOv=EOoeg7Y<A8@9SdoHN^LExGkgtBxTr~G-#J*QHLg=}pjl*;^ra#C@lj#txR;|D zTdgoc3oJJ|ce4yxG<6hr!jt;0C~$t~Nc<vvB;bp<H~BX$gdM^jv9O5t&a$2-mw6hU z0R&WqM5qnn<9%g5D#a4<a{Qg7<}8)b*A=pyiB4d*LTr`7ZV4s6dZ&U8BvU(M&_r92 zHtPFbyN&n}3mSFaW<8E44E1glD|J&B1r4HxpIv4vkUsJ(crl4(u4O9E970Q?c8Di; zBNK(!KI?U8G<917I@xw6GzWU`W#zl<SU&jdHL!2u^yvr9#R*UoZ<pRQehMVRLRLS2 zL*cthzJa?%7-NbQ+8P_b<37XBE;8{{3JMgq{o+t%h0Uw7z)#jVga;-o_h&n``HTLJ ztJ&l52FYYaTg3fRTGVEzh9Yd>50>lG4=v6@nZFE(h~oV4o#E%2zvPY&ESaLGokT1! zc7~AhBIVjk{9%N-bbpuEV)*SN-nnq`ePj_Wez`27&@j6~(N=L*BV-X(qM4`oQ0XJ) z-g&Y2RbcCW*0m*1_j@eFbE?qIm3UL}N9!Yr&c$dv^4%9E{xfI(I%LtXB~vw9ljs-o zWpnI3FQuIK2Pd4=?UL_wyl0yS!<U%9skgPAn`X^uqKHtZM8((A@;Ps_podXk>|c0~ zASWfZhC1y&AAOVbEO8wYlF*pZom!qfP(>h{AT=vnTrPNoeH%O>$3pedKmqo{s@PCU z<4^^iiF2NcOB#eSE$LOAkt<KTu#|{$*%mW`TTkH^kOKT$AkyB4c=m<tZC4z_na3!n zW=+nWztfQIpDy-oKIa<G*7+WJ=>#P~%ZyEy(O)@Z`dvCQ14!^@Ys;fxvx4j_PM<}s zMe4<0Q={JAGRo<UKoQz|;Hox%-FJY#dYICV+n$L_6*}NC^unA(mmk`MRec&3c}hZ{ z+1^d2c3_q<b_e4Xx468Xa3*NW(>OJift5S`bDrFen!4b(S@a#)l(}-*$*u7nV01bC z&FCJl{5X{w6Gi}UQBB?|^@XmPK3YE&CuW(5*0rpgLmQV+g&)Fx@i^=-ijymj%rRo0 zcf=;eA#AH~yi1oE5r~9o&qdCtn=%`x$b35c07w+(`za+*fZl%1g5DCrxL<kP;c8sW zA}0<+<T4tjT*T0@=1R<4e1EHs-%HF@eEwQrz<!H(lC=~NN1jSl&u)Lm{R|Hj(lS?W z2+ZjCA#Iq;UP}I~>;iXK|Lx&)(<spqP%PF|u=uPSuuWlG;{7)<szjp3RA6py;ZUD+ z5;CdboUcNxJ4SGiiq7?D;=p`Qa7<0(;7)RRDwP~|Z>sm>mPx=7pG=V&L7npk&y=EI z`Pyf7eL^9@HpPT<&cqqbKFn{)o8Q)T%&rDob!{}Gv-yCJc}hIp6=mN6Tlh7F#8}Un z+nkir(D|A%w@}*JQPd!~RorJ)#Td63Cn2RNCjU_|S6xR3rfMo&^W#X0<vie}j@|Xb zOl+D+&zamgN$(;murepD@<>`PeY-jN_8%JZR5Ze3PN@mpYGcGK_HumkAq-Von6cHP zAqnjonU5yF^XVC+!ul1xR8?T-JTAIvr?F!Xg8(De=I8+w(NdMRgjwsMmMvI1GMC3n zU@_tl&Rt2(J008n`sT4Xv2F8SUj7$yM5wHrhrTS@`Fp?NwMdIXl_<=zL1Tdk+8#I2 zD`8uuRXs7uC~7ZTXp}IkT97?Z7XKxBS~AJ`Jw_WL==(XvB-fNGj3Sl6Emv%U+9uJ7 zUp|3KV#XA9t1~i8Zt>xvD8hL{8e*YY&X0e#Rzkg8tnj)-<sp6G4Xfo({bP4^XVig` zxek@(#qdtr@>;LTJD*RD$`W=R)rOkw`j(FvYMrFR+r^i@87M;MR35US7)$qTY@o*C zg)nP&Pn_6qjxAO%zl8a|K%Zs6H!EVRRw?^KD2+GJoPK<^{}anqqVT$??P1^Wt^wl> z<(6-1B;xDeqOy1@W4JSS-iY|p75{L`F4K`86nf;9nc7;*fW=_sw;&4%G6swvk>CoW z4h@d;Lns$Iws;<&>C>RoBWQePfAJEQ4)0gw(O`+wx^@4bmV%xdQP==m^dJMq1fCQ( zozl*B6@VF7RUh|);R&0aa_9X^3wMWwc=`3=wJI*NUHsDaKAC=ZaK(|P;)VBFr?pZB zGj2kqLt>s1SyjkmBWS>4q{egpl2fS_sYMM`s_^s7JA(A+$)GQEuq|`sTZe4z4n^J8 z8(#aV;S%1r%U3R)QZ_qDT4Cp$(F^^5SZkS{&+RLNw`3=SPLw}k&zSf~S%)+xNGdDJ ztOcv46yYJMRqU?|X>P0b-2B*dlqWG_PE+^9ULnzt5zE%2p=ie5*&H9DHO^u;WE$6h zTJa@pIJRP@EcTpIAR$-bv+bSG4oW<Fl^jb-8=<XeW*gl@!NTw}R|a0J%JxE}>-M|X ziGAIs68#bVAxbaAiywRBwP}XwE_2sdx{^?NJ$Oz6uaP(_wH7QJE1e(rQ<9q5cDCp4 zveW=_nQrCzCj9ujXPx#rvSz#=+wO<OpRy37)w+%x#pw(=(6lBcx^@+FGU0(HSF&>4 z)6PQb1$>I1pGB7oe;`kc5CNMXg!PXSM7HcF`#>U%GGCEBhkOh(TvhEH&5r3xiBO>x zQ_kwf2dpMGDFHPu<$kfqDfw`Al=Jq|BW{-Q#{qx`K)S2zP%-y3!R+iJa`UN@&jpFQ z|M*G6xXD5);p0W{hs^ZkQ+k5Xmic5uX$siPj~O`H%r&j!X)GLHwq!!yy{MVvJmi=% z2|k|p&x8Sk8UE3;im_44A)bCWdF%D0w?fxpp6d6e?)m7t5Cw0CS5bA#1zl5+(pQ_M zlKT@c<W&u41)=<pUsZQLOjBZ@wJ~Ir^f{(lxMowgU{0;G@>T*s>~CRtX)Ra3u&<+E z5*D)n{PltL_nLM~RMNaVV6!EIk(<Y7yDt-hqty(4Tnp7yF_M2fd|}bKKKZ`X*mU`$ zO6+y<LTH2;7`ShYQ^^=s;&!r%ooce|m_9ZAN~GJ4{j;qn!bLG8t!QqNYxV?=-m0!y zanKN~<gOhX1Sb%zbuG^CZz{I_u76c_F}d_|xsXE-4=#shBB!2&>2p;AK%%TRxB&(H z{YS)B1uNFhbF_YJISSIuJMRyLr%aq&=E<fX8gikV!<kVUR==Jg?*ZXGI<+*`KH`T` zyLtpPB<#rY!+4EtsO7a3Q_?r4xiS=?E@d@dQc*GMJH|Jlj@rsM#*ja9ZpQ7JO#1*Q zfL$uYlBT`(2Ti$)%$EIm=ZNM}mxd4VbkU^1>&d5CiPMG_6&XDEn&N<MzzdQMumG=6 zWZx=%^|jR3ly{A#nGv1(`94dNY*fsblZhR8{;W~z^-8acx1yyB$3qUMf?)V$!^vgu zv9j93+X7I2XK=-m)IR5g9cL%gibcJA_Zth%0EbUk{@JpBx#T~E0y^IzO#Uq012eqs zr(iLEp=%H(7bSlWFT%(ee4DEz3dV0?3~lk<S8ASN@6@Pe>(pqI+X|3nqg^OFZdF-< z>9rS9if*#oE0o4w(?IRGPR^8$m@X5qi$Hhew^x%%dW_Sy4o{U)Zw=HTvn6G*TTKtI zepJL}FW&m7;{JU}P%aV5?^TtWR!sZPIGcvVSL&qAyJTF{6d&j{j(yC`$)^l74of`c zAlwB;lD;2iKp&Y!=)P8p7!3?g+xgAbxk^UWb|qJq@~Vs2F&hbp9bg->qBl85(>)>7 z*!l3dtLaT&>8nLkURmm};b2}8!AHd+U15{F3{*Bp5B#5}#HxVdW+gi`s4TH$6VjHo z)AUcR91g&^xa;EUnieTt{3EW}yf^o6Sc1A&M&6KCj{cm~0Dkr?z7BNEF_@=p?I!VX zci`xJj{r}iI@&@LK+5ECDIL)P13LZ2{fG6lles>sezz7~AJ&+VQsB`>ri?8GZQ5bU zr#V2wp<sfD#=0PM3hq-HE;UL4_%bBogGfwpOW}V*cnIohQNS`rM5s_lTw*L<I_=GN z1S!$<`l{Qttx!NCIYcZijRH+b%R2Bt>w{94Gk#{7j5P~z?`Li1Z5b%xf-;DpycLZk z!FXJ=L2S{zBCRZSS8B{0b4~>M1f||UN~0Xf#ODFcy;>maGq`@kw91y~KP*&j9CwbJ zw9-10LM-ST&FO5gHIL`_n%V%#2*&?MwU&GXYm%a(gd)%iWUln!-+pB^Wq;dBdMQ|5 z%TN3dQtP||i_V(b=$k$8zij^iz<u^OGqdpyL7v|!MukFm&;X9MDpeIMVkstH%zBXs zHH^;^X}no{%_e%Kbh+$0I<+#Rm}2gT<rP`w$rC9-=wsQQM7F7gn8^8$`Pc$FqVX>j zEE{JQ4}jK27pv_AZRhB}4@s#0N?kHNC?OwAT%!+UM+1UF1zR%<qvXp$9-AhlKx;+t zh`CVU-oJBZ7g>ieAw85A_UU*SPv0L2^yfY_R{W48U)%9~sGg36hOG=L@#V|x8`^uI ziJ3gmg^c+N5XAiL?@jiO=Ky_8!4h9i?3HIRVHV)wF56EnQkT;*_)lsb8bXT7^ym(G zb?4}EGI1vwork@-H|{bfW{J25vRk4PvbY6obVqu{!3oygX`UTsSH)drM+*khtpEk% zt9DKrI8uWA<95vsjV{GH5^+B1%k$3svkI#<h7Y6Qa*zX^Imv&TGhOaxFYPA^f;=)R z!L{rfngddNtl1-8T}T=Dtj^q7mijszpeFo3z21h>khc|Z!W>E1bCEIZ4qD9H2ks@; zEAy{~jUXx2eSlQdLRxxf2i+LHM)(HQ@E6B_J1uPAP}M4J^XLFni&50?AbJg)pv}j3 zMukzWAiTkKBhTemZ+t(Ea({xC{X-N%-tN24Sq@mhrhhdvODG#`PAWd%*#x10%WI>H zpKx0uzw=q52ePtv;OFiIZFEEoasXgTFaVUNX6B>A$GntR*WZgc7&CwW^0h%uxu1pR z|2`A$IPwb33;s_?w_F~AVA)J@fuewod1Is~GT^1?tTI7<3pj;<A%5{sf&VO9#3(P# z%uL3O550t2Mr}Q#yc9Ub(l`~Ju#{6iK6P2c>oR~<r9LHYU^RTMXVhwBm7ZE0!`9ki zXwiAQ+wzwyEIhjf4;-T*xu_lvalvBM-)GPsWgakGw84yYDgcO9?G}b_7Q7*;Jb5*I zq#RaEkye>84VBh<xlUx&&U@OFZ~A_Q4{ErkU5-R10S3ujcHV=&H;g_tRUT-xWUh_% z>28lKgHQlm<XoUdcihXN$CG_0J1((65oYC39E_JE#0e6-Z_Utg^9a~XEXsN}Wy4By z=quSfKOj%jUD7$W0Wj7f|2aETdw-3Ld=I6f<S|O6QF^Dr6tlBC6S@T|xXC-ap#T?$ zEc{@qSL5b|w9b|#Go_Hy)J-nM#|IL$5!E>@rBPo?Q%s!;RQ?%3iGMuY|68KlU>m&$ z;(Sr*#WRSwYankCM>A>7iT#x>?vQlK(lD3}T-T9+S<tCj=3Fl8l)Gj#aoX;!XeSb6 z4&05)GFZ)Md?@k-uoxF<<ru~E@5&Mb1JCxAcNcnS^=1Uyrw_A%%d2Nu0B^cW1_Cy| z<`KF;LjOsP0o~zEmp4fHXuy8H%k#sez|y6lb8T+)SPzq2;==Ef;$VFN8?N`!_X!Rj zKSu-}#rxfa{0n3W7Mjmuyr5BsOu)4$YYYb(Y8j@aWsR;=7TD-2Km$U35HFOF7@kkO z)81`*%x^QX?v3%V^u$vpg2dK;ml>ELaLHTlL5HH2@{I=^)yHuvGYe<RL+)?%GM07# z7zId7nW(rN0?yo*^~n<8VgPO#+SI;(DtJv}v*I<LP3NxEaTAYr?=furP@lgoZ_zCW zu2?xmoltk({>P{YpnIKcr-pgLPX=1DMCi~i7C8LPkuYJpvoI{ha2-iyG2VWGC}@7> zi#nAqJ&@igskV<}&IoDU%~62w$I7Qq)6KG0SnSGHwr1){B*s?&zC!meX0t8|mIDrS zvwNt{ai$lBttT^W+U3*tRuXR$EA*+NyfpXn`$>b((Jgy$<d<x~4{Cj&eB&>2imLla z^B85E;MFC#;Nd%bRu<cJ*5*$%B?Tnr0Sm*YLOpODu%_X}(+{?;KfpH)X_LE4u{FAi zX9B*fOAiX@yz6lzKG=!nKK1zL9)Iho>f}I^vy30OGk_BSjC`EfX(libLLTKR+V4(= z4sIKTzS$si9Q4D?oQ#PXJP3Pv`Z{EmxT5=z&z}cfpPI0^kk;sPE4-!8m#km0^2_;` zrAo;n4$7Z>y}8n_M9S#)C!)g>$N(RlQo%qYVIe@{=EMtKhmv{e%N;M?T003;w&1#q zeR!vROpi-@MN881F{LbNCF&Jx!#`000E)`+9B)d8lo|^fmdy9TKY0gvvaogPjT^>7 z#^M>O56OPfmA;xQqce48sA)C(H)BYUm<5kcjW;S;%TWjru9!APZ+d=gbMsw-n9?-S zT8Bulu*No!c@zV2#Y@%i&)7~`_^!bCJA_wq-T~Mk+v$&!F{Hb)*T$JvMr>2w!Bs!~ zfR(!hZYizOButfff4ZdHybDBkyFJDkTl9_{54o+z>v`}ED>U-!FE5PR5H>;Y96Dfs zk<6-S>8O~GB$YE|#)X~AUOoW}CAl}e)3u|!a~T6$C9c?p8#GnArj%pl{a=~p<^$yP z2j>zMt`h(T;ONxMs^MUdemNJtdKSFLXWR4yzampH7R;^wWA?R)Xuv141U!62Zpzf> z9XLgJW-9U>N~!77h_Zvouglhb{8}osgCmzG@qYoF=7e{~kQ~2RzJSCke^|ilA?*p8 zV*%#l*{t)1(%7aKVh~Gsu&b2mme{1n7qEWVt5YUEq5tAOah7HQ`FZd_V=Wr3R#f;i z>&SgX*9yC`<$kswCGpuiRf@p;xcxf0IG+|U9&iP%6&kP$!viXhwA|BxQbO3+(qTkr z*Res@yJGhUxPBH+cj;pm&tby+R9vYf*zD~$ifQRBW-lOevG*Z&KbGUr@}5gok-%Oe zX15tOUsOoWrQ~uJlbWw!-Cy3A@hIIh;q5x)GYcFOmow}LNea=*R9KYDSM1AtRX-aB z>`3jowdL$h-<Vjgd(R=PFZ_9Krmi%Q1GwRzl`!qF{ORi<-}w7)6sTc=^(`&f;bB=2 za?%v0n$;6Sdi-kAYo9QnKo2yB3o=yR#{9_Lrfb(j6ngy2S4dL>`HCBEc^1>q%}E0w zU&7I#7Vu<%x~sS#M9T3IlbF~U;#ntI^jT?qM%O!ytvy6j3{Lm`DH-AVmKa(79`uQ6 z;Wj2UhJnr|78z6D)UzACGW#%y$SSrTGNGv;crN>s)rh&8$;g?)h1rW-{P+lJv*2?^ z&C(!HfDAAN5)fu36Y{N2BN7%#A2Yq=wu*Zm9#?ufPVaS36Ey~-n+@_URwmJro;Uwx z;7owjlSY_!Tz9tVZzMsKkWi0khD65txN<7zV>51kAW!P@6vY1&N2>aw%Yhp66`O{L z%(&BGf%TRE8GtUO7Mvl!-k)3K?vyrJTjCdTF0DKnJv~~-_3=%kJVp^Fso%M}xmUJQ z_u*BCD86}zN4o#ZABGQ2NgcAQZ3W8r%ON_(!Eq&}u7wXYFFEbdifoN*lIIVPwM5;+ zk7D`u{V7;1X|2w@|7KjQ?0MhqFG^D;kcR&}%ESvyTBDDdW&FN5tf&68C7!Nk<dRAl z8XDqOYH2`}d_&PZ395>Up<%o790*{hwz6|2TzTIeERy2gBF5c@ChY)nqvRuQ8CI!V zD09lsNyGXyum%S1Wua5};GYiG)_POiFU;q3>bG-!BknZpt0Kniqdo}T(W$1R_(@y- zpYVwKJF4qO0RJz*rvFQ9m<%`dxjD?yxEi4<k6ajaGUgZ?zqEzC6Sr}68%Rmw{dD@# zC3|Y23`oS6wj^mK;W-u9S~UU#v%@%S3-3T#?CrTH?6+DlECCJut?s$d`HjI|vUhJJ zAsi74!w=5>n%n0z0}gnM64%n@rMzO%AND=!h@0y#ac!rdY~|TKg5gLK^l>AFOdWpP zyk$1I0b6%>xqUo)x1Cz^M?aFKzFNG&ux?K7qO&8Q@+#o8y)6A)sXULlYkHF_PL$xm z@k+^i01>O1@dO{S9oil`I!<BLFfR|sb5^!%SXl*J%lxyw>T}k}9icivfeMd&Pkq+L zN4%x>9>rqVuIg9gcD&=9d2@C)&vmFtGynN=Y`VbFA1(XCXI}!fFP>fY%3ZK(yepQM zH^ZdVh%HZUEKam)*LXYU6+EV}?jO~6XVqC3ypCQiF(+b?$(O=R`m`V|Yi1TxuGz$x z(y8BcD!^PPvgT@bYKRoWR5@?g6kRWi&j0s57-tDfqVH8Wr-Fn}$%QK|7(U}pV^i6! zu>pq5#k(olTqwgo#ouMS9yKiC%I|5YK5#OVw=8v8f4a|zU%oRV=bFCM61dH~dH#iX zCD)NV{JD^&)4AV|*CC53PCa2nCez4(x2M`Dref^^o1|thNr`62%iCBKeM}zYu$t<} zhui3zOhU!14wKV;IyJ%QE-r(&&)gS3?N8bor>m78eo?HCXfmxVtqyuMIBu2|57sjP zDo2h8CxmktsKB~^_Oa~BHH8P>uZDzKYH4ge6bvK2&#-UZs|~(SrS`WJQIXX5GqwD` z@{PjdX?|#aFj${6WsBy<{Mc4MHR2vy%?QZkHf$cY2E)Vtz|OqI<AJ_w*0P{ShIq;P z=(>n5M574FP8w<6y~*M!>{PE7A_rrg{syQjFo5;vdvl9-(}Z>w7B&MBaY|@otb79n z3dklkHDau>z~&WI6+?A!rH$|P8LPn^g)G%v`Il)JSr4EqIvOzH#dDtNTC`DQdg=&U z1kRX-*1_I7i+lrGo(Kd!E}QAp0gZq`avsDAI62w*8-E7H>mzJ{p>~tKQ`1u$k$2N! zA{KHZ&RpqJ&73_4XRBXA*GpZ=OdK9_T4h<yTX8yj98&&~ow+W)^ZE0)AY`T27W86} z{p`hjkS_;N_o@3l7!{OPIvF?>JB6D1kH4wu$h$T)>>fT~{VWFQ_?V{A*k~N*EvJMc zr)SSHOvUqFI?M-9&0pZe8upm8;va@P$uV;M{-E9Q+T(;rN)nHsr&_{eU6X?==!Lm3 zwwT4!*{(ng0-BtYeRMUe`y0{;gsj>L&NDM3>C+km;<v>cH%Y6Oe?4t3(E1HJs2KAE zM7Li?ZJ(XI=^bD*oBS;6RH#i*2VJl8CF?nBI%@-kf_QMsQ=|Ma1}B$tdSMcbW&6)} z&a*zi*uS9@2A{4hIl%fkKhHWoH*S(NuqhZY<8HZBtwGD_`ymi{&-rp(C9WEW#?|8F zq;zE6GJ$gGljpqYw{{LHpL|oyP7dXmw#d&(d{#l<e+y%XF}<3G4*jcz272qz4!-qz zO>1LEJ^fRu^PZOk>hO%XfmSR>^wE_mpEFQtE>my+IR(|>B-_}Yv%MU7KrWs=^M_;k z+Qx$kN;p2#iLH@VX`HN3zV))z;0NgLDy|TbWlCWUt83elk$>5g_4mUM;h5#giRY5( z5JOqd$nEKA6uy$x?s7+CSd`>I9bt+vLzp8hfL8&n5Ul-?84Wpl0?>=sd!*0MJUK7U zTHEZ^Drz6*rV|wJApe^FpHlqtpZGF$;2n$?I~=K;`%g7%mK(Aa?IECN^ffJJS=)rO z0<SQ>TW?+5m*!>lHX(i0yZxjHnPzO84_O#@le82eR+T=)Erwvz;m~F28462)y@6%H z3cM*2xgH311Mhc*WM$dAKY<T;&lwjGY^Wp}b_*7r3jLAPWkY2<Wi;gyJ~7xb1xk8! zpib74Po$Fmt_Z|p*ilH>v0`xDoK)!coBE?AaH<MM9&;C?j8Vt*>|?L3e~EqIIdLew z*RIYdvWH7vkO-l(hj`NT18r0@bHutS|N26dC_U<hx<@e&_&gXm`Qpva4KFkIu08ws zXXttVcyr37C!?9N<;-jhlWtEir+F4<qt*`b49j9hv|5`W%5j|5rk-a#{%F>;Z)xYq z;b(P2x<Mltq?-e`UrAE`A1y~M8a%n(2@L3ZV$q2g{n<%LhIMXALD>Ptnr`+(#aUSB z85y4~%p7J9bA`eGHDth|)o7J{-4|MwE0Au~z#I{YN^a`7Bd=!o$I=ZSP!-U#B7p(g zH^4A`nE5Wa7c2dbaTbg~AH$HiEr~c#1l8YbYjJ<NYTGa)O9)wC^09+8+%+OkPs63+ zUPrh}`6Im9a9pY8uZ-y2ucMc+KZKV#5Bghs3RC6TlCi!omjnIJ4ngVZIHU7GpU>RU zxO6z2Yc=s)kwZq`g$U)Rz<+Sozq%yuKs66JT%5ob%if-=>*BHPcrocd88{g`i8@_j z<HATUvE3gQ%CW4aw5b<gM=ilZ`cxd@*L@=D!!#fHGKCt{J?JM2)I?|@v=Jpv$G+6% z|8@6(pM_7~hIJ^q4Lo@s(J$L7v#U7`1*RAZ*uJl%uSG7t5SzD}bYI<Jn?y|(&nAOO ze>?Px#!%TDFfH>Q_`u;}-r;wolw&GGy6^i4CZv)&NI7`fjbfQG>=<qgKSl^6k_R&T zDhzZeRSo|1)>zM>&2|L(yK_3$(kg}H*}f7DvsT}>eT)#~X)-hATm^YB0a)_VFRLU% zEe(Zb?p=@ZL}X=c0F6n0b!@YoI^BzEM^3eIF?(ux*gS=mZp&`W|G!rrUk)vM5gM$Y ze)Ilgjp<k#TTX>XG^f>aeHtwGck;F-$yWV}u-6+HG3<d}I-P|m!&+*ak#r`OG;mpL zC4olv7hRwc>+W2)dts@#;~9*2Z6XRL026y5#xz*=UsKj0$NHP+xxyCxxCJ>l9$(Nv zML2Ep5f>X6;HrrJHYQ&KcSb-%#bP7j0tQqckvVcAJ_5{c=W)-X_87GadYbn>x2Rx^ zt=7M-b`g=^FOYd!h0jc>x!MV<p0*uL{-`Ja<`Fj<sNXOwi^OZ!WYx6&skZO0Fl1bd zX-)baA5vvi@Y+PL85@v<w@Yz8G2t|^H_3vEkugo`^S^m}6IztR7FqAv{bed>>9O%{ zw$BVV`FJfPyVNO&DqP-;=<Ne#hxz8-MT}3)vw!TtsJw5)6X`1_Ox_MW*#PolLKpB| zjFzehm5s@4)-X?!9eX=ns*mEX+PvR}UCBhzYm;DHta_XRoJ2oiPM&)fv!WM7&Al)< z7bPsU6BZC7QS#>eLCtK$-{kh(K8oq`2VUlqd{n!eVFd08Ug`zL&r$Duw41L1UA#iY zfX#Vr;@KP<Ac4=E##F%_W}2&?1?@0an|QYyDLQ29%ay1uq@0ACK5Ab140J*6+R!IC zZ5oz`*yPqu`eXqOw4&Ey3gOl>=uq=fr-}awWkx>YlPz5yNaicwA4(fZ+yTe3bi5B> zzYx{Gu#eG@{9xZa?N%Ob5C_l-sy<WR;=Y67-1|FCM@QG(8&E|o^xLkVuZYGbLr9ko zfpge5uGqtsoafj_6v>Mv>}WNp=tIO6gtV46Ii@h>aI2Z>Y8S_W=HdWEU8Jy{VBNNK ziq@{QM4+n60Oc~|8_>^4XC6&;6l<CCaqMtq14WzW1R1R6Gp=vxKRBqJUxwAowzN7m zbhTLl&24o{lx`no$uW&#@=3Y(=n@uOPl^3i`1Ld)A62W`)~3r3yEB;~j-L8;Wt;C? zeO`e2iB-FrXB@Nk0~Et|E?@O*3+Fl)Ggl7_W&YtkdfevwcK7;M6++8eKv!`g$4wgt zho`=-tyf|1{l3s{7|rBWYdOAYRtQMivYV2xfWIeL36~kXzd002x+{j|!u+6LR{Hk; zm2l^QP<3qp!0%YHg?KF)d)vz(6ixOmR0tW8UCK5LWr@Zz%1~sfgpe%R##))NWFOg+ zAv;O-eF<at9q;$&9CMy~&OPT|Gk2cncTG3k`H4|;q_bdq1Ng1}S^)>lpxL>niMV-r zc?|Z>XL}3&Q$BC5Zs{)_0|x0U#XFvK?S&xgyR(;#*$%s6R;x2#?$eAv9_j3V7BbeU zfh6Mb@h(J*>4TNwc4wYT6S{?4m1x6$J2{K9vxXNudxk?L;`$x#9zFeQH7V4ya|V># zBWi-<-eO$W>brJ7VKB>0eV@kLN%fnXmoW)$A}$taVu5kK#diZH9plkzKMrB9Ok;`Y zDvj1^m-Zvl`Ce`OapE@lg32zfnrl0I#R(&`%8-LHR-;Oai-jY|qcE%%DlQ}OEJk?* z29gUtD3EocuTReno*+zRoyUGBj>09Xd*>BLf_?~ML1cPt5fW85mL*u!Ie5ZL=IQyQ ze|HQmFHR|{54t?jL8=Nmmdn7u!(VpFfIGy=;#S%oBhSbEiL&-T+iV>y;d$^~v>Q6o zSOTR<u)O@Tz2^$+g!k$h53USOZ->1VwZf$618=1|>K>+4!z3Ku3-bG}V9D7rb?3al z&kj-KKca8+#}!FgJ6voGN5^>Lnf=@Dm&=pOt6ti@&?%OS_(rfr&4=ZRr#zXYigp_q zdtXKkD|<PT1x+%L)wsWT{Re!B7A-~U+)>%I2kqPk8e;|;Upm>jH#FW*C;|S0^{+wJ zPb(3Jps=nO20wA)%GjVXn=xBhyd1$uCk3tq*IItf;{Uf~g6Zx;!6GpoeXT#Dh_ANE zW47qLwMiw(JBp*oNW(B$Ar+gF!y#GG6o2$PNuAr}r@jANb>=Df_inMqu0tZIBpH=~ zMVS*5cnrQDatn6~*h;U>&g}99pHeU~7M2Y<%GaM<lWKTajBoVk@24l|AT{3~kScl~ zis~i5(lP!XbyF_+Rq`}N3?HG%!?0@hrY0CKL2-VYo$#&r%en^x!b<iYK0@jBgL1_L z9X5$6V;UZUKC-E|w+K6IgzV1!^%!Yf_$9CG4036rZ+IXhmiVcnUbOps&fxggy<vR5 zaS6er_{;5Wq0l*Xo)X&5$1}e$URShFV+Z3G%2lCJb6#B&4JAy$vyiWO^VL}4Qc@xt zg3n+NBr0l_5C%&Gqn4be#?+&7qLEklD0EL9Nc(sDYMp=OFgCxLkuKMPCNL?8wV$yV z!o#jdEJ^OU78W*_^z`(WYllm<(Ig`)3zICZ#NlNT#|ceSgZehrQ_b}^6A>pm#d&pC zr+nxlC%kZfpZD8q6NhFCWer60#L?k?ff&p#hg7t>IM0%07Ka30A~1(&A-9V;faljJ zS+qu(a~9yd{K@g`i3*D&HgO!1rX6I`{!29dfOt(sB?lXS=|=3wZ>wv4Y`fS4rNZ&< z>wVLG@xy$nbL4<-xinuJTPt(Vvv-(H;qQMYf7C+Wo8m2|F6Puw4YFxMSo}SO#^<fp z6`!7mf7{KJVko!4{WXbo{i;s<t*de-*sHH39Mh3$EX7|+a_btphmsH;v&?_MqgfK6 zo6i+zz-UGeIr>Ffq-N*Y@)XnZ%9l|j#KDs(Chm{t+QfJ~VU=qHx#Xx4j8lC%J>}Jy zUcMQ+oFZ}Hg_j*IM~)sjwR?Y6LC9$BMstPoE>Y7&1n>u)sIZHt*sa8Tr7yZ+DkzAW zvv&PLR}}cFUn|GhF=d+9h5Fu7kY9|dn#N4RMJZ9(fI_pfenI+Dn;3q7_=cd^06~ub zyC6EBD|FNP<jC{hu0RI;IU1993$t&zT<F>pmx$(S<3liCU8LjVuym<HvmSaW1MOb@ z;L@7r;@)-pf?l_GDiT`A+Zl6Oc&RISDN&t?-syy1P+G%$n`mLBZ*D2Ss?mRFX)uZ~ z_fh<*B$H(oWmD0dlM-WhqkS96%=e8Z+kJMcNXXD2s?W8T<gxou#tR+Xfa;B}9$b*R zHoC!QP$9#Ma!>5F#IY1QCMFdwU0oL|vb5bFBKQ$}TW)=-lLW<H!EWysG{0p;*}XHe zXCGLnT4~jJd()1c#NMwEZ^Yv%rKSRPO=R2^S!;~k=-~58>f=hm9@F3RzSh49PehEY zE7W`4lCt;Q+zodIYv&!(5o|@AhRv>}5nasZrbGP0^S!qIp+8;ES@><YT8g0}8aF9s z^v&e%gnfIt^~70fo{wcxX9U%udkn2;-1B_Rsn~Wo%7A~fGI^`V+jf60=OEyHiuIkK z!eR4VW|QJjgvi4o;(^u$hMs5I0`-^D|16hHA+7Y(A9<5wsJ-i%mx3bnKFkY|aySi6 zkFJYqS}@;FTJHYgprs8yfUEGAml_Qpl&3{b6UC}sB31bfjvNGyCm3>Fm!<TV6nOf- zZT%z!KU5FK7!)>{6zCtp@+!wB-Ih!#H1W#r`>g}v<*B$yaYkpA*a&vG4|22jq>26J zuBTk(`Q`78CDpj2hu;g=*3}Y@6tD-WbJSf-D0+jhO?f9XY#wu;@ay!^hV?!3_~~EE z`7wn#k0N`lWqdw8DmAw@$q8a6l4Z5keWSzNVt<4apPEhsEhz-eeEq{y&Em53!U?V4 zu8S#WSj0c_(7aVG)2*=Tc!c#7Pm}y<5mcP>X@gtXY_xw(db!zNICtC*`wX?GaN1Hp zh(Sa6scWWTS&(FCl^?k?cbb|IF(2f9SB0>0Rq}4_QPQBzi13Q8{NbK(nDtLIG5tkz zx|||XRl9AAr*35A!_1f!g{F_gGWE-pW08c&LfsVkTEK}^O@qt&=`IKZnB=(x%F~q) zJsTp$(#M!l`%rkej+<3K@OKQ=xG3-2ku7hKYo>VHnz|2Z?f2By_9ZPfABEjdOf1`I z`=h)e=THQj*~P`VBN_O+b(loz{Li0G0tJcdw2w6I{`RLa`&{GL-rgh~z##K4GyMH6 zstOh<-+&t4{y8k8--`cO>M$vGzl72`y;eKM=E{3TsP2y)S4<qMj&A2k-LuBbsGu(! zGOzp8(9Au7TZ!|u>-oZ;)r+`g?W$ed4c#k}(I;Dk)rih}wT09}rLwJ7gJHb**H7YA zV){!J#@UqRzeFb8lCVr`O1M3NVyJ*I)Tk7{fs|KB+_R8@O#IK>53!PHyTa-;?6E(c zDPiRVL8zvrwd9D@j$x0*MR$0{Q?nAp7Dv%#2trJ~QVz@adckbf8pECS8V1#M+zP`6 zi*hO0xg@Tltvo>A24q9^2i)6`95?@0Xf_CUTM53rgXegg*1aOHFnU24f;#l6+tOT1 zHnw57tUL0;GrP1<TAUD)ejq;mB}}i>`p~6F14t87rL8-!u0CKl#~;u@P@O-wFLWM3 zNib>=y#_&^x9~PJsBD~Ri?$~O{Z657Xx29Jyj^6Wpi|WTRD$CEE^le+jVt=bOtoC@ z*P7T?01?pB>M1Q<dg+AZ()zA{ks$;z&;89-S0ddJw`;weZKqTNj`MgWDEnk^Y;L1% zcLFQc0SFgdIppfETx|j+x8BZ0(m-jGH3G|{bOkhi-SIQ!j>o#eOoPf-xeVq2mn?Hh z20#<MU7spN&=r8ak6Yk{a8^%5M)H}=={FLkq#v@<La`FXp;+%Z)OkCX{6JPU-|~16 zgnsmmn$yiEjn=4i>D%EtmrRdk1?AJr-?O+(7u)xXq;$)(VNjyPXr!?CU*Qs0m>xOy zgRGl6GgMbq5QcqN+Y#K7`_y(ZnG_JRaI8CQ2LSQG0!5LgCS>2|PXZHx48yRVbK2a8 zujL~)PxLE7&^tde%K1cI8%$5BIcO*@fbBGd6POLhVjkQwdx1E_3(FtM8v|1<m4U2M zcIixw*I&=x8gJmCc*~8TCGa?+id)4^pn}Banwn6iuF^>wvaBp0lNi^3R%d5O$8nU9 zR9prsVG!=4vgy<w0*3zAJ!lKj3zkzqnlCuk>j6iJ?@-CqKVg|5YGR*jc>)G7L$|!X zEymD8b(7vJ`*Bju`8FJM1zy;`5;YzOdh<kPL`wn`OpQlQ@&PObXYJo4b5)~m=)3{9 zP!2eBdD_HyDibh2N{rYc9809_DF-r!rtcc!h#(H!nbiu<VmcW5-jL*y>~VKNAr|WO zQzhA3@|HFqL3dA^lT@8kC63*nrrwZJJ_3n?`|+ibf0O_)Bf;b`ye>=DRE5-i3d6AD z!^dqmELx~cx0Rr@r;6i3Eg8NvW{A|XDH{;lNY<8(st(wbzvWUf|2ZgKDl57uT9^dO zm`qhFGbC1<)1bmmSI`mACu0R(7M~rrVW2U~F8#e?5_Xvd22EvR`G;jsl}t~^trZuf zW`Mzr46E1OyfSq}9Jh0Z)YsfYvG6aK=?bj;TDRvkAt++8XFT-;jTr_gWW2ycQP*(! z&}?f0G#2G<RZ#&P6C?&7{dySfDq=S^_}JY)))ib9jnBq^(bJeMtJt+?0>4CLMu?Rv z3HX@_k0>=W1_-D8M0q4%9r!dqhS$%5wQG6zFIk0|uAre?mbUhoFHu-SlR2)^6>!e- z*A};Y0XC9dr?>PjsvH@DGxbw!pZljtIE4wSQx;&Np6TQTD9+7*eMo7N2Kv19dfXfd zB$L9y^*bbiEHFbp1^BWG`q=a7FeBqTy+t7rcijdRr?&(VJJXQVGpdf}+icMFA7938 z$SvHk{M&NuhsdyJ@-V1%&!@b(WI6(8dgIBPi8mx=ZLl>t*HPtVt0WOve)pfi&sV{G zWb&KID)_)6Msr5*`!I%Uv{3eD;`lZ{4NvEdaEz+H{QvQxguD}=WhKb6MhCR`&Y}O$ z6aD!LE--M`tQxDwQ-N3HJEz-JOWz<t0?ZLujm9x!U@GY^-ysG3jPC(wLZnM&T$-$M z%wpYO@II`NY6hNFZWY+0b?-=YlII4}uGXG{2n&qtBjA;xR)b>6-vrPP)mhx2f;)c} zFkI@*LIG{@x`3a_J-N9f5C|T}pQy2KcXDxqjCRB4e}t(D1IB5R$?|Xr<W&LN$w_Ks z&CuxBc3M%eRK3@p4C2K#C8v7?M}w1S9~8}ZWY!u98o7Q&rW>m1n1X9A*n_`zZp7iZ zvCko0Iyf1;Q&g?=^zDE1f`>e-*dVS}o!PIoZhrnRaQKlYZ6zFn5SMn<s|^#dR4h9= z6?^Q%>n8!_Q?aQ(n}63syG}Et0Dcs)LJLApcG9ND*kl21NGzMdeJ63;j~;4y){|}+ z6blZMzMU!`bb8km41M~7P0gC*F;(SXWxwC#Bl(dIxA;w3q*?=v(MWYIACy5wob`o7 z0znqZ0~SGm<^5(N$B9B8mwLPURsVaJU@^a%)%WEUD7LCuQ+QADINE08*V+ZsaT8#h zbu6N0O$Oh-$I+L6Usk>E_XRj&Yi_f8gOl%o6PF?v<PraapQnzt?%Wm4v7Jv{34o8Q zYMyA4ZVPw;B(BKYwma!vkB>RCSe^&2KMMBT=S&2lRiT3}-Jr&jSV6GR1OCbasU26J zb3vqUN}ts_w8VgsMLbJ+1NUfzazEdiWf{~x1y;K+Pua~pnLe%3=F)JcnX(%IvaY$v z=`FC<*k4_Tsmr^Cs;-<3o8km3p>5%@`dlj5vE2(0aGcnS8$L0o`z%6m_?H4@Q^la7 z!JgL-ntFuTa1Z9*Rm9=&9V#yE3@5|i(TIhA2vc3UaSXF{3=^b25T~Tw{G;X}BlTju z>Y`V=AppEuqIjchi&x9SCVU|-vc>F|L&*($kegTW5uX+VS8F`v>V=6kr!oijW`=1m zf)x`14|#h?`xW+Ds?g55>uj>B;4T6jwc!Q3=7E49A5K7dJMiMh4Y#C|pu|ly=<*DV zyekO}TCfEEV9>NZ#sVvEEo&$$Kwf_3*w}C-(BL~k-dH@rF2eeQFaBQ;E23M|WGSxT za!s_6AjIi;ZbLlyfnWWx(KljjsS0m12KryzL5pp749NIQIcojuK&sO8fd=q*G(m>N zgg=3nPV)DDDsc*Y3yiXVE?iep4c~pv9<E9Yq`<EmrE8HWHJ5f-0xfRM=|s?_4OYYd z48fkgw`((@flcxKd2DH~ZP)|r7_>eBN)#8W%%^xq(4!tMl?JzAazA{Q&JH9^b!Kmo zd^YFa%E*`@u>Wb|x-X|k1z#=oXLA6iT+8!ciu((z_wz@Slu%4Y&hNY8)lLsx*_#N# z(RaM!sdg=!mUyYgHX&z9!__v|<y*|?z9WAOHHxU+;tNj8SDXW`TM7d9Xvxnx|4t|g z8-aj<sKu<7LHGAz#ZTTRTfYwpP=F6jGiX|lrriV@V}y3G7dm`K4H@LlAgCZy%kbet z7-^e>Y{mknowfRU4Xt#sY^ps>UP#@{hQ)n(z3YT+#&O3sr{4JHm%(~-nWX*h)h*H3 z(p%1!!mRE~pSrei-N0ePVd<?Ne2{uP^k^V{A(zL_b%<+&X@Xk#$K3{X+x@D5&8pf} zkh<o|Wa=$LSNPSf;@G^r>2avS_mtZyEaOqpK(GRgB~g7e-qO9|Y`}8XO&o^-buR6J z6skXZVevB?jaeO+sHgpbC#|u|;G@X3p6AS#5-4_rvs9?K!I28u{3~P1KQNYo>Q3_k z*YFabJKJt1Dq^3aPGzT_{NUzAV?}2YTDK_oI*X%FNo&B>_`FyvExsX6hah+FnEae+ zhD(ZATV(nquc1kp3vTJS^m&DeGPADhQkDn+f{HwrbN4f0v|zSo$mN{u;uqByH&^jU zJOmerdc6Rn#=`WeD)TbD{{W-Le8s7PU;ojdMm&Y5KBF+mml#p&X7?Q*Ml(d!4zLn& zH%|+XR@Rq{@i=A@O{C#EdBA<d2^Pr)c`q1=z4RQ{<VuSU%iut;$pRV^^0hxQAE=~w zn%(Q2E2ur|kUoFTlm_^<9Yul-eXI`kkuKjJSqQS+mJT!gwLuFKvIbfO#j>{zN@f7g zn$67~#(A`jAP|g7Ph0n3o14H_1$CJ0Q~EedCAvbi)(hIxnbv=aiacpHtW$~h_nE~& z20hM|!n&7b)MTx|Ln>k(*ITH%nAyg;Nm^UT1Vf9#6OeiEv=K0O`D(tl%QM@LnJp9G zN7H|L$D8{@YsYkZmpq5Ik=r)x_Vb&o)Ev!R8nB6=8O{1MA(}*;O1VE-95U`nm7EQU zR(zUwU%dzSIDKXV#aZ)YFV~H^`-~=p9MuGt<Fj&C+^FhOeQ>Zl;{%Jo+|uQ0{nyR+ z$KLBn&{eP~ICj=5`5y_S%L@5(8jKdE&eYN#_Zi`1m;3>4yo5g)9vt9vyWJ%Y@ZTOf j9>V{%UsUT6uN`!2(~)rDUCR#m6LdrS*0mx{yQlvH2d6cR diff --git a/elasticsearch-persistence/examples/music/assets/form.css b/elasticsearch-persistence/examples/music/assets/form.css deleted file mode 100644 index 3a937e310..000000000 --- a/elasticsearch-persistence/examples/music/assets/form.css +++ /dev/null @@ -1,113 +0,0 @@ -/* Based on https://github.com/plataformatec/simple_form/wiki/CSS-for-simple_form */ - -body.edit h1, -body.new h1 { - color: #999; - font-size: 100%; - text-transform: uppercase; - margin: 0 0 1em 5.5em; -} - -body.edit a[href^="/artists"], -body.new a[href^="/artists"], -body.edit a[href^="/music/artists"], -body.new a[href^="/music/artists"] { - color: #222; - background: #ccc; - text-decoration: none; - border-radius: 0.3em; - padding: 0.25em 0.5em; - margin: 2em 0 0 5.5em; - display: inline-block; -} - -body.edit a[href^="/artists"]:hover, -body.new a[href^="/artists"]:hover, -body.edit a[href^="/music/artists"]:hover, -body.new a[href^="/music/artists"]:hover { - color: #fff; - background: #333; -} - -body.edit a[href^="/artists"]:last-child, -body.new a[href^="/artists"]:last-child, -body.edit a[href^="/music/artists"]:last-child, -body.new a[href^="/music/artists"]:last-child { - margin-left: 0; -} - -.simple_form div.input { - margin-bottom: 1em; - clear: both; -} - -.simple_form label { - color: #878787; - font-size: 80%; - text-transform: uppercase; - font-weight: 200; - float: left; - width: 5em; - text-align: right; - margin: 0.25em 1em; -} - -div.boolean, .simple_form input[type='submit'] { - margin-left: 8.5em; -} - -.field_with_errors input { - border: 2px solid #c70008 !important; -} - -.simple_form .error { - color: #fff !important; - background: #c70008; - font-weight: bold; - clear: left; - display: block; - padding: 0.25em 0.5em; - margin-left: 5.6em; - width: 27.45em; -} - -.simple_form .hint { - color: #878787; - font-size: 80%; - font-style: italic; - display: block; - margin: 0.25em 0 0 7em; - clear: left; -} - -input { - margin: 0; -} - -input.radio { - margin-right: 5px; - vertical-align: -3px; -} - -input.check_boxes { - margin-left: 3px; - vertical-align: -3px; -} - -label.collection_check_boxes { - float: none; - margin: 0; - vertical-align: -2px; - margin-left: 2px; -} - -input.string, -textarea.text { - padding: 0.5em; - min-width: 40em; - border: 1px solid #ccc; -} - -textarea.text { - min-height: 5em; -} diff --git a/elasticsearch-persistence/examples/music/index_manager.rb b/elasticsearch-persistence/examples/music/index_manager.rb deleted file mode 100644 index cbf9e09ee..000000000 --- a/elasticsearch-persistence/examples/music/index_manager.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'open-uri' - -class IndexManager - def self.create_index(options={}) - client = Artist.gateway.client - index_name = Artist.index_name - - client.indices.delete index: index_name rescue nil if options[:force] - - settings = Artist.settings.to_hash.merge(Album.settings.to_hash) - mappings = Artist.mappings.to_hash.merge(Album.mappings.to_hash) - - client.indices.create index: index_name, - body: { - settings: settings.to_hash, - mappings: mappings.to_hash } - end - - def self.import_from_yaml(source, options={}) - create_index force: true if options[:force] - - input = open(source) - artists = YAML.load_documents input - - artists.each do |artist| - Artist.create artist.update( - 'album_count' => artist['releases'].size, - 'members_combined' => artist['members'].join(', '), - 'suggest' => { - 'name' => { - 'input' => { 'input' => artist['namevariations'].unshift(artist['name']).reject { |d| d.to_s.empty? } }, - 'output' => artist['name'], - 'payload' => { - 'url' => "/artists/#{artist['id']}" - } - }, - 'member' => { - 'input' => { 'input' => artist['members'] }, - 'output' => artist['name'], - 'payload' => { - 'url' => "/artists/#{artist['id']}" - } - } - } - ) - - artist['releases'].each do |album| - album.update( - 'suggest' => { - 'title' => { - 'input' => { 'input' => album['title'] }, - 'output' => album['title'], - 'payload' => { - 'url' => "/artists/#{artist['id']}#album_#{album['id']}" - } - }, - 'track' => { - 'input' => { 'input' => album['tracklist'].map { |d| d['title'] }.reject { |d| d.to_s.empty? } }, - 'output' => album['title'], - 'payload' => { - 'url' => "/artists/#{artist['id']}#album_#{album['id']}" - } - } - } - ) - album['notes'] = album['notes'].to_s.gsub(/<.+?>/, '').gsub(/ {2,}/, '') - album['released'] = nil if album['released'] < 1 - - Album.create album, id: album['id'], parent: artist['id'] - end - end - end -end diff --git a/elasticsearch-persistence/examples/music/search/index.html.erb b/elasticsearch-persistence/examples/music/search/index.html.erb deleted file mode 100644 index 098f626e5..000000000 --- a/elasticsearch-persistence/examples/music/search/index.html.erb +++ /dev/null @@ -1,95 +0,0 @@ -<header> - <h1> - <span class="back"><%= link_to "〈".html_safe, :back, title: "Back" %></span> - Artists & Albums - </h1> -</header> - -<section id="searchbox"> -<%= form_tag search_path, method: 'get' do %> - <input type="text" name="q" value="<%= params[:q] %>" id="q" autofocus="autofocus" placeholder="start typing to search..." tabindex="0" /> -<% end %> -</section> - -<section class="artists"> - <% @artists.each do |artist| %> - <%= content_tag :div, class: 'artist search result clearfix' do %> - <h2> - <%= image_tag "http://ruby.elastic.co.s3-website-us-east-1.amazonaws.com/demo/music/bands/#{artist.id}.jpeg", height: '45px', class: 'band' %> - <%= link_to artist do %> - <span class="name"><%= highlighted(artist, :name) %></span> - <small><%= pluralize artist.album_count, 'album' %></small> - <% end %> - </h2> - <% if highlight = highlight(artist, :members_combined) %> - <p class="highlight small"> - <span class="label">Members</span> - <%= highlight.first.html_safe %> - </p> - <% end %> - <% if highlight = highlight(artist, :profile) %> - <p class="highlight small"> - <span class="label">Profile</span> - <%= highlight.join('…').html_safe %> - </p> - <% end %> - <% end %> - <% end %> -</section> - -<section class="albums"> - <% @albums.each do |album| %> - <%= content_tag :div, class: 'album search result clearfix' do %> - <h2> - <%= image_tag "http://ruby.elastic.co.s3-website-us-east-1.amazonaws.com/demo/music/covers/#{album.id}.jpeg", width: '45px', class: 'cover' %> - <%= link_to artist_path(album.artist_id, anchor: "album_#{album.id}") do %> - <span class="name"><%= highlighted(album, :title) %></span> - <small><%= album.artist %></small> - <small>(<%= [album.meta.formats.first, album.released].compact.join(' ') %>)</small> - <% end %> - </h2> - - <% if highlight = highlight(album, 'tracklist.title') %> - <p class="highlight small"> - <span class="label">Tracks</span> - <%= highlight.join('…').html_safe %> - </p> - <% end %> - - <% if highlight = highlight(album, :notes) %> - <p class="highlight small"> - <span class="label">Notes</span> - <%= highlight.map { |d| d.gsub(/^\.\s?/, '') }.join('…').html_safe %> - </p> - <% end %> - <% end %> - <% end %> -</section> - -<% if @artists.empty? && @albums.empty? %> -<section class="no-results"> - <p>The search hasn't returned any results...</p> -</section> -<% end %> - -<script> -$.widget( "custom.suggest", $.ui.autocomplete, { - _renderMenu: function( ul, items ) { - $.each( items, function( index, item ) { - var category = ul.append( "<li class='ui-autocomplete-category'>" + item.label + "</li>" ); - - $.each( item.value, function( index, item ) { - var li = $('<li class="ui-autocomplete-item"><a href="<%= Rails.application.config.relative_url_root %>'+ item.url +'">'+ item.text +'</a></li>').data('ui-autocomplete-item', item ) - category.append(li) - } ) - }); - } - }); - -$( "#q" ).suggest({ - source: '<%= suggest_path %>', - select: function(event, ui) { - document.location.href = '<%= Rails.application.config.relative_url_root %>'+ui.item.url - } -}); -</script> diff --git a/elasticsearch-persistence/examples/music/search/search_controller.rb b/elasticsearch-persistence/examples/music/search/search_controller.rb deleted file mode 100644 index bb845c5b6..000000000 --- a/elasticsearch-persistence/examples/music/search/search_controller.rb +++ /dev/null @@ -1,41 +0,0 @@ -class SearchController < ApplicationController - - def index - tags = { pre_tags: '<em class="hl">', post_tags: '</em>' } - @artists = Artist.search \ - query: { - multi_match: { - query: params[:q], - fields: ['name^10','members^2','profile'] - } - }, - highlight: { - tags_schema: 'styled', - fields: { - name: { number_of_fragments: 0 }, - members_combined: { number_of_fragments: 0 }, - profile: { fragment_size: 50 } - } - } - - @albums = Album.search \ - query: { - multi_match: { - query: params[:q], - fields: ['title^100','tracklist.title^10','notes^1'] - } - }, - highlight: { - tags_schema: 'styled', - fields: { - title: { number_of_fragments: 0 }, - 'tracklist.title' => { number_of_fragments: 0 }, - notes: { fragment_size: 50 } - } - } - end - - def suggest - render json: Suggester.new(params) - end -end diff --git a/elasticsearch-persistence/examples/music/search/search_controller_test.rb b/elasticsearch-persistence/examples/music/search/search_controller_test.rb deleted file mode 100644 index a1c95cd0c..000000000 --- a/elasticsearch-persistence/examples/music/search/search_controller_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'test_helper' - -class SearchControllerTest < ActionController::TestCase - setup do - IndexManager.create_index force: true - end - - test "should get suggest" do - get :suggest, term: 'foo' - assert_response :success - end -end diff --git a/elasticsearch-persistence/examples/music/search/search_helper.rb b/elasticsearch-persistence/examples/music/search/search_helper.rb deleted file mode 100644 index 65a57c322..000000000 --- a/elasticsearch-persistence/examples/music/search/search_helper.rb +++ /dev/null @@ -1,15 +0,0 @@ -module SearchHelper - - def highlight(object, field) - object.try(:hit).try(:highlight).try(field) - end - - def highlighted(object, field) - if h = object.try(:hit).try(:highlight).try(field).try(:first) - h.html_safe - else - field.to_s.split('.').reduce(object) { |result,item| result.try(item) } - end - end - -end diff --git a/elasticsearch-persistence/examples/music/suggester.rb b/elasticsearch-persistence/examples/music/suggester.rb deleted file mode 100644 index 5438cf11a..000000000 --- a/elasticsearch-persistence/examples/music/suggester.rb +++ /dev/null @@ -1,69 +0,0 @@ -class Suggester - attr_reader :response - - def initialize(params={}) - @term = params[:term] - end - - def response - @response ||= begin - Elasticsearch::Persistence.client.search \ - index: Artist.index_name, - body: { - suggest: { - artists: { - text: @term, - completion: { field: 'suggest.name.input', size: 25 } - }, - members: { - text: @term, - completion: { field: 'suggest.member.input', size: 25 } - }, - albums: { - text: @term, - completion: { field: 'suggest.title.input', size: 25 } - }, - tracks: { - text: @term, - completion: { field: 'suggest.track.input', size: 25 } - } - }, - _source: ['suggest.*'] - } - end - end - - def as_json(options={}) - return [] unless response['suggest'] - - output = [ - { label: 'Bands', - value: response['suggest']['artists'][0]['options'].map do |d| - { text: d['_source']['suggest']['name']['output'], - url: d['_source']['suggest']['name']['payload']['url'] } - end - }, - - { label: 'Albums', - value: response['suggest']['albums'][0]['options'].map do |d| - { text: d['_source']['suggest']['title']['output'], - url: d['_source']['suggest']['title']['payload']['url'] } - end - }, - - { label: 'Band Members', - value: response['suggest']['members'][0]['options'].map do |d| - { text: "#{d['text']} (#{d['_source']['suggest']['member']['output']})", - url: d['_source']['suggest']['member']['payload']['url'] } - end - }, - - { label: 'Album Tracks', - value: response['suggest']['tracks'][0]['options'].map do |d| - { text: "#{d['text']} (#{d['_source']['suggest']['track']['output']})", - url: d['_source']['suggest']['track']['payload']['url'] } - end - } - ] - end -end diff --git a/elasticsearch-persistence/examples/music/template.rb b/elasticsearch-persistence/examples/music/template.rb deleted file mode 100644 index 4759b642d..000000000 --- a/elasticsearch-persistence/examples/music/template.rb +++ /dev/null @@ -1,430 +0,0 @@ -# ====================================================================================== -# Template for generating a Rails application with support for Elasticsearch persistence -# ====================================================================================== -# -# This file creates a fully working Rails application with support for storing and retrieving models -# in Elasticsearch, using the `elasticsearch-persistence` gem -# (https://github.com/elasticsearch/elasticsearch-rails/tree/master/elasticsearch-persistence). -# -# Requirements: -# ------------- -# -# * Git -# * Ruby >= 1.9.3 -# * Rails >= 5 -# * Java >= 8 (for Elasticsearch) -# -# Usage: -# ------ -# -# $ time rails new music --force --skip --skip-bundle --skip-active-record --template https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-persistence/examples/music/template.rb -# -# ===================================================================================================== - -STDOUT.sync = true -STDERR.sync = true - -require 'uri' -require 'json' -require 'net/http' - -at_exit do - pid = File.read("#{destination_root}/tmp/pids/elasticsearch.pid") rescue nil - if pid - say_status "Stop", "Elasticsearch", :yellow - run "kill #{pid}" - end -end - -$elasticsearch_url = ENV.fetch('ELASTICSEARCH_URL', 'http://localhost:9200') - -# ----- Check & download Elasticsearch ------------------------------------------------------------ - -cluster_info = Net::HTTP.get(URI.parse($elasticsearch_url)) rescue nil -cluster_info = JSON.parse(cluster_info) if cluster_info - -if cluster_info.nil? || cluster_info['version']['number'] < '5' - # Change the port when incompatible Elasticsearch version is running on localhost:9200 - if $elasticsearch_url == 'http://localhost:9200' && cluster_info && cluster_info['version']['number'] < '5' - $change_port = '9280' - $elasticsearch_url = "http://localhost:#{$change_port}" - end - - COMMAND = <<-COMMAND.gsub(/^ /, '') - curl -# -O "https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.2.1.tar.gz" - tar -zxf elasticsearch-5.2.1.tar.gz - rm -f elasticsearch-5.2.1.tar.gz - ./elasticsearch-5.2.1/bin/elasticsearch -d -p #{destination_root}/tmp/pids/elasticsearch.pid #{$change_port.nil? ? '' : "-E http.port=#{$change_port}" } - COMMAND - - puts "\n" - say_status "ERROR", "Elasticsearch not running!\n", :red - puts '-'*80 - say_status '', "It appears that Elasticsearch 5 is not running on this machine." - say_status '', "Is it installed? Do you want me to install and run it for you with this command?\n\n" - COMMAND.each_line { |l| say_status '', "$ #{l}" } - puts - say_status '', "(To uninstall, just remove the generated application directory.)" - puts '-'*80, '' - - if yes?("Install Elasticsearch?", :bold) - puts - say_status "Install", "Elasticsearch", :yellow - - java_info = `java -version 2>&1` - - unless java_info.match /1\.[8-9]/ - puts - say_status "ERROR", "Required Java version (1.8) not found, exiting...", :red - exit(1) - end - - commands = COMMAND.split("\n") - exec = commands.pop - inside("vendor") do - commands.each { |command| run command } - run "(#{exec})" # Launch Elasticsearch in subshell - end - - # Wait for Elasticsearch to be up... - # - system <<-COMMAND - until $(curl --silent --head --fail #{$elasticsearch_url} > /dev/null 2>&1); do - printf '.'; sleep 1 - done - COMMAND - end -end unless ENV['RAILS_NO_ES_INSTALL'] - -# ----- Application skeleton ---------------------------------------------------------------------- - -run "touch tmp/.gitignore" - -append_to_file ".gitignore", "vendor/elasticsearch-5.2.1/\n" - -git :init -git add: "." -git commit: "-m 'Initial commit: Clean application'" - -# ----- Add README -------------------------------------------------------------------------------- - -puts -say_status "README", "Adding Readme...\n", :yellow -puts '-'*80, ''; sleep 0.25 - -remove_file 'README.md' - -create_file 'README.md', <<-README -= Ruby on Rails and Elasticsearch persistence: Example application - -README - - -git add: "." -git commit: "-m 'Added README for the application'" - -# ----- Use Pry as the Rails console -------------------------------------------------------------- - -puts -say_status "Rubygems", "Adding Pry into Gemfile...\n", :yellow -puts '-'*80, ''; - -gem_group :development do - gem 'pry' - gem 'pry-rails' -end - -git add: "Gemfile*" -git commit: "-m 'Added Pry into the Gemfile'" - -# ----- Auxiliary gems ---------------------------------------------------------------------------- - -puts -say_status "Rubygems", "Adding libraries into the Gemfile...\n", :yellow -puts '-'*80, ''; sleep 0.75 - -gem "simple_form" - -git add: "Gemfile*" -git commit: "-m 'Added auxiliary libraries into the Gemfile'" - -# ----- Remove CoffeeScript, Sass and "all that jazz" --------------------------------------------- - -comment_lines 'Gemfile', /gem 'coffee/ -comment_lines 'Gemfile', /gem 'sass/ -comment_lines 'Gemfile', /gem 'uglifier/ -uncomment_lines 'Gemfile', /gem 'therubyracer/ - -# ----- Add gems into Gemfile --------------------------------------------------------------------- - -puts -say_status "Rubygems", "Adding Elasticsearch libraries into Gemfile...\n", :yellow -puts '-'*80, ''; sleep 0.75 - -gem 'elasticsearch', git: 'https://github.com/elastic/elasticsearch-ruby.git' -gem 'elasticsearch-model', git: 'https://github.com/elastic/elasticsearch-rails.git', require: 'elasticsearch/model' -gem 'elasticsearch-persistence', git: 'https://github.com/elastic/elasticsearch-rails.git', require: 'elasticsearch/persistence/model' -gem 'elasticsearch-rails', git: 'https://github.com/elastic/elasticsearch-rails.git' - -git add: "Gemfile*" -git commit: "-m 'Added the Elasticsearch libraries into the Gemfile'" - -# ----- Install gems ------------------------------------------------------------------------------ - -puts -say_status "Rubygems", "Installing Rubygems...", :yellow -puts '-'*80, '' - -run "bundle install" - -# ----- Autoload ./lib ---------------------------------------------------------------------------- - -puts -say_status "Application", "Adding autoloading of ./lib...", :yellow -puts '-'*80, '' - -insert_into_file 'config/application.rb', - ' - config.autoload_paths += %W(#{config.root}/lib) - -', - after: 'class Application < Rails::Application' - -git commit: "-a -m 'Added autoloading of the ./lib folder'" - -# ----- Add jQuery UI ---------------------------------------------------------------------------- - -puts -say_status "Assets", "Adding jQuery UI...", :yellow -puts '-'*80, ''; sleep 0.25 - -if ENV['LOCAL'] - copy_file File.expand_path('../vendor/assets/jquery-ui-1.10.4.custom.min.js', __FILE__), - 'vendor/assets/javascripts/jquery-ui-1.10.4.custom.min.js' - copy_file File.expand_path('../vendor/assets/jquery-ui-1.10.4.custom.min.css', __FILE__), - 'vendor/assets/stylesheets/ui-lightness/jquery-ui-1.10.4.custom.min.css' - copy_file File.expand_path('../vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png', __FILE__), - 'vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png' -else - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.js', - 'vendor/assets/javascripts/jquery-ui-1.10.4.custom.min.js' - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.css', - 'vendor/assets/stylesheets/ui-lightness/jquery-ui-1.10.4.custom.min.css' - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png', - 'vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png' -end - -append_to_file 'app/assets/javascripts/application.js', "//= require jquery-ui-1.10.4.custom.min.js" - -git commit: "-a -m 'Added jQuery UI'" - -# ----- Generate Artist scaffold ------------------------------------------------------------------ - -puts -say_status "Model", "Generating the Artist scaffold...", :yellow -puts '-'*80, ''; sleep 0.25 - -generate :scaffold, "Artist name:String --orm=elasticsearch" -route "root to: 'artists#index'" - -git add: "." -git commit: "-m 'Added the generated Artist scaffold'" - -# ----- Generate Album model ---------------------------------------------------------------------- - -puts -say_status "Model", "Generating the Album model...", :yellow -puts '-'*80, ''; sleep 0.25 - -generate :model, "Album --orm=elasticsearch" - -git add: "." -git commit: "-m 'Added the generated Album model'" - -# ----- Add proper model classes ------------------------------------------------------------------ - -puts -say_status "Model", "Adding Album, Artist and Suggester models implementation...", :yellow -puts '-'*80, ''; sleep 0.25 - -if ENV['LOCAL'] - copy_file File.expand_path('../artist.rb', __FILE__), 'app/models/artist.rb' - copy_file File.expand_path('../album.rb', __FILE__), 'app/models/album.rb' - copy_file File.expand_path('../suggester.rb', __FILE__), 'app/models/suggester.rb' -else - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/artist.rb', - 'app/models/artist.rb' - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/album.rb', - 'app/models/album.rb' - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/suggester.rb', - 'app/models/suggester.rb' -end - -git add: "./app/models" -git commit: "-m 'Added Album, Artist and Suggester models implementation'" - -# ----- Add controllers and views ----------------------------------------------------------------- - -puts -say_status "Views", "Adding ArtistsController and views...", :yellow -puts '-'*80, ''; sleep 0.25 - -if ENV['LOCAL'] - copy_file File.expand_path('../artists/artists_controller.rb', __FILE__), 'app/controllers/artists_controller.rb' - copy_file File.expand_path('../artists/index.html.erb', __FILE__), 'app/views/artists/index.html.erb' - copy_file File.expand_path('../artists/show.html.erb', __FILE__), 'app/views/artists/show.html.erb' - copy_file File.expand_path('../artists/_form.html.erb', __FILE__), 'app/views/artists/_form.html.erb' - copy_file File.expand_path('../artists/artists_controller_test.rb', __FILE__), - 'test/controllers/artists_controller_test.rb' -else - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/artists/artists_controller.rb', - 'app/controllers/artists_controller.rb' - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/artists/index.html.erb', - 'app/views/artists/index.html.erb' - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/artists/show.html.erb', - 'app/views/artists/show.html.erb' - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/artists/_form.html.erb', - 'app/views/artists/_form.html.erb' - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/artists/artists_controller_test.rb', - 'test/controllers/artists_controller_test.rb' -end - -git commit: "-a -m 'Added ArtistsController and related views'" - -puts -say_status "Views", "Adding SearchController and views...", :yellow -puts '-'*80, ''; sleep 0.25 - -if ENV['LOCAL'] - copy_file File.expand_path('../search/search_controller.rb', __FILE__), 'app/controllers/search_controller.rb' - copy_file File.expand_path('../search/search_helper.rb', __FILE__), 'app/helpers/search_helper.rb' - copy_file File.expand_path('../search/index.html.erb', __FILE__), 'app/views/search/index.html.erb' - copy_file File.expand_path('../search/search_controller_test.rb', __FILE__), - 'test/controllers/search_controller_test.rb' -else - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/search/search_controller.rb', - 'app/controllers/search_controller.rb' - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/search/search_helper.rb', - 'app/helpers/search_helper.rb' - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/search/index.html.erb', - 'app/views/search/index.html.erb' - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/search/search_controller_test.rb', - 'test/controllers/search_controller_test.rb' -end - -route "get 'search', to: 'search#index'" -route "get 'suggest', to: 'search#suggest'" - -comment_lines 'test/test_helper.rb', /fixtures \:all/ - -git add: "." -git commit: "-m 'Added SearchController and related views'" - -# ----- Add assets ----------------------------------------------------------------- - -puts -say_status "Views", "Adding application assets...", :yellow -puts '-'*80, ''; sleep 0.25 - -git rm: 'app/assets/stylesheets/scaffold.css' - -gsub_file 'app/views/layouts/application.html.erb', /<body>/, '<body class="<%= controller.action_name %>">' - -if ENV['LOCAL'] - copy_file File.expand_path('../assets/application.css', __FILE__), 'app/assets/stylesheets/application.css' - copy_file File.expand_path('../assets/autocomplete.css', __FILE__), 'app/assets/stylesheets/autocomplete.css' - copy_file File.expand_path('../assets/form.css', __FILE__), 'app/assets/stylesheets/form.css' - copy_file File.expand_path('../assets/blank_cover.png', __FILE__), 'public/images/blank_cover.png' - copy_file File.expand_path('../assets/blank_artist.png', __FILE__), 'public/images/blank_artist.png' -else - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/assets/application.css', - 'app/assets/stylesheets/application.css' - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/assets/autocomplete.css', - 'app/assets/stylesheets/autocomplete.css' - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/assets/form.css', - 'app/assets/stylesheets/form.css' - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/assets/blank_cover.png', - 'public/images/blank_cover.png' - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/assets/blank_artist.png', - 'public/images/blank_artist.png' -end - -git add: "." -git commit: "-m 'Added application assets'" - -# ----- Add an Elasticsearch initializer ---------------------------------------------------------- - -puts -say_status "Initializer", "Adding an Elasticsearch initializer...", :yellow -puts '-'*80, ''; sleep 0.25 - -initializer 'elasticsearch.rb', %q{ - Elasticsearch::Persistence.client = Elasticsearch::Client.new host: ENV['ELASTICSEARCH_URL'] || 'localhost:9200' - - if Rails.env.development? - logger = ActiveSupport::Logger.new(STDERR) - logger.level = Logger::INFO - logger.formatter = proc { |s, d, p, m| "\e[2m#{m}\n\e[0m" } - Elasticsearch::Persistence.client.transport.logger = logger - end -}.gsub(/^ /, '') - -git add: "./config" -git commit: "-m 'Added an Elasticsearch initializer'" - -# ----- Add IndexManager ----------------------------------------------------------------- - -puts -say_status "Application", "Adding the IndexManager class...", :yellow -puts '-'*80, ''; sleep 0.25 - -if ENV['LOCAL'] - copy_file File.expand_path('../index_manager.rb', __FILE__), 'lib/index_manager.rb' -else - get 'https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/master/elasticsearch-persistence/examples/music/index_manager.rb', - 'lib/index_manager.rb' -end - -# TODO: get 'https://raw.github.com/...', '...' - -git add: "." -git commit: "-m 'Added the IndexManager class'" - -# ----- Import the data --------------------------------------------------------------------------- - -puts -say_status "Data", "Import the data...", :yellow -puts '-'*80, ''; sleep 0.25 - -source = ENV.fetch('DATA_SOURCE', 'https://github.com/elastic/elasticsearch-rails/releases/download/dischord.yml/dischord.yml') - -run "ELASTICSEARCH_URL=#{$elasticsearch_url} rails runner 'IndexManager.import_from_yaml(\"#{source}\", force: true)'" - -# ----- Print Git log ----------------------------------------------------------------------------- - -puts -say_status "Git", "Details about the application:", :yellow -puts '-'*80, '' - -run "git --no-pager log --reverse --oneline" - -# ----- Start the application --------------------------------------------------------------------- - -unless ENV['RAILS_NO_SERVER_START'] - require 'net/http' - if (begin; Net::HTTP.get(URI('http://localhost:3000')); rescue Errno::ECONNREFUSED; false; rescue Exception; true; end) - puts "\n" - say_status "ERROR", "Some other application is running on port 3000!\n", :red - puts '-'*80 - - port = ask("Please provide free port:", :bold) - else - port = '3000' - end - - puts "", "="*80 - say_status "DONE", "\e[1mStarting the application.\e[0m", :yellow - puts "="*80, "" - - run "ELASTICSEARCH_URL=#{$elasticsearch_url} rails server --port=#{port}" -end diff --git a/elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.css b/elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.css deleted file mode 100755 index 672cea658..000000000 --- a/elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! jQuery UI - v1.10.4 - 2014-06-04 -* http://jqueryui.com -* Includes: jquery.ui.core.css, jquery.ui.autocomplete.css, jquery.ui.menu.css, jquery.ui.theme.css -* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS%2CTahoma%2CVerdana%2CArial%2Csans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=gloss_wave&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=highlight_soft&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=glass&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=glass&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=highlight_soft&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=diagonals_thick&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=diagonals_thick&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=flat&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px -* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ - -.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-menu{list-style:none;padding:2px;margin:0;display:block;outline:none}.ui-menu .ui-menu{margin-top:-3px;position:absolute}.ui-menu .ui-menu-item{margin:0;padding:0;width:100%;list-style-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)}.ui-menu .ui-menu-divider{margin:5px -2px 5px -2px;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:2px .4em;line-height:1.5;min-height:0;font-weight:normal}.ui-menu .ui-menu-item a.ui-state-focus,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px}.ui-menu .ui-state-disabled{font-weight:normal;margin:.4em 0 .2em;line-height:1.5}.ui-menu .ui-state-disabled a{cursor:default}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item a{position:relative;padding-left:2em}.ui-menu .ui-icon{position:absolute;top:.2em;left:.2em}.ui-menu .ui-menu-icon{position:static;float:right}.ui-widget{font-family:Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #ddd;background:#eee url("images/ui-bg_highlight-soft_100_eeeeee_1x100.png") 50% top repeat-x;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #e78f08;background:#f6a828 url("images/ui-bg_gloss-wave_35_f6a828_500x100.png") 50% 50% repeat-x;color:#fff;font-weight:bold}.ui-widget-header a{color:#fff}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #ccc;background:#f6f6f6 url("images/ui-bg_glass_100_f6f6f6_1x400.png") 50% 50% repeat-x;font-weight:bold;color:#1c94c4}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#1c94c4;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #fbcb09;background:#fdf5ce url("images/ui-bg_glass_100_fdf5ce_1x400.png") 50% 50% repeat-x;font-weight:bold;color:#c77405}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited{color:#c77405;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #fbd850;background:#fff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;font-weight:bold;color:#eb8f00}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#eb8f00;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fed22f;background:#ffe45c url("images/ui-bg_highlight-soft_75_ffe45c_1x100.png") 50% top repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#b81900 url("images/ui-bg_diagonals-thick_18_b81900_40x40.png") 50% 50% repeat;color:#fff}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#fff}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#fff}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_222222_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_ffffff_256x240.png")}.ui-state-default .ui-icon{background-image:url("images/ui-icons_ef8c08_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url("images/ui-icons_ef8c08_256x240.png")}.ui-state-active .ui-icon{background-image:url("images/ui-icons_ef8c08_256x240.png")}.ui-state-highlight .ui-icon{background-image:url("images/ui-icons_228ef1_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_ffd27a_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:4px}.ui-widget-overlay{background:#666 url("images/ui-bg_diagonals-thick_20_666666_40x40.png") 50% 50% repeat;opacity:.5;filter:Alpha(Opacity=50)}.ui-widget-shadow{margin:-5px 0 0 -5px;padding:5px;background:#000 url("images/ui-bg_flat_10_000000_40x100.png") 50% 50% repeat-x;opacity:.2;filter:Alpha(Opacity=20);border-radius:5px} \ No newline at end of file diff --git a/elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.js b/elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.js deleted file mode 100755 index 8af84cb1e..000000000 --- a/elasticsearch-persistence/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/*! jQuery UI - v1.10.4 - 2014-06-05 -* http://jqueryui.com -* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.position.js, jquery.ui.autocomplete.js, jquery.ui.menu.js, jquery.ui.effect.js, jquery.ui.effect-highlight.js -* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ - -(function(e,t){function i(t,i){var s,a,o,r=t.nodeName.toLowerCase();return"area"===r?(s=t.parentNode,a=s.name,t.href&&a&&"map"===s.nodeName.toLowerCase()?(o=e("img[usemap=#"+a+"]")[0],!!o&&n(o)):!1):(/input|select|textarea|button|object/.test(r)?!t.disabled:"a"===r?t.href||i:i)&&n(t)}function n(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}var s=0,a=/^ui-id-\d+$/;e.ui=e.ui||{},e.extend(e.ui,{version:"1.10.4",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({focus:function(t){return function(i,n){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),n&&n.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),scrollParent:function(){var t;return t=e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(i){if(i!==t)return this.css("zIndex",i);if(this.length)for(var n,s,a=e(this[0]);a.length&&a[0]!==document;){if(n=a.css("position"),("absolute"===n||"relative"===n||"fixed"===n)&&(s=parseInt(a.css("zIndex"),10),!isNaN(s)&&0!==s))return s;a=a.parent()}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++s)})},removeUniqueId:function(){return this.each(function(){a.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,n){return!!e.data(t,n[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var n=e.attr(t,"tabindex"),s=isNaN(n);return(s||n>=0)&&i(t,!s)}}),e("<a>").outerWidth(1).jquery||e.each(["Width","Height"],function(i,n){function s(t,i,n,s){return e.each(a,function(){i-=parseFloat(e.css(t,"padding"+this))||0,n&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var a="Width"===n?["Left","Right"]:["Top","Bottom"],o=n.toLowerCase(),r={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+n]=function(i){return i===t?r["inner"+n].call(this):this.each(function(){e(this).css(o,s(this,i)+"px")})},e.fn["outer"+n]=function(t,i){return"number"!=typeof t?r["outer"+n].call(this,t):this.each(function(){e(this).css(o,s(this,t,!0,i)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("<a>").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart="onselectstart"in document.createElement("div"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,i,n){var s,a=e.ui[t].prototype;for(s in n)a.plugins[s]=a.plugins[s]||[],a.plugins[s].push([i,n[s]])},call:function(e,t,i){var n,s=e.plugins[t];if(s&&e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType)for(n=0;s.length>n;n++)e.options[s[n][0]]&&s[n][1].apply(e.element,i)}},hasScroll:function(t,i){if("hidden"===e(t).css("overflow"))return!1;var n=i&&"left"===i?"scrollLeft":"scrollTop",s=!1;return t[n]>0?!0:(t[n]=1,s=t[n]>0,t[n]=0,s)}})})(jQuery);(function(t,e){var i=0,s=Array.prototype.slice,n=t.cleanData;t.cleanData=function(e){for(var i,s=0;null!=(i=e[s]);s++)try{t(i).triggerHandler("remove")}catch(o){}n(e)},t.widget=function(i,s,n){var o,a,r,h,l={},c=i.split(".")[0];i=i.split(".")[1],o=c+"-"+i,n||(n=s,s=t.Widget),t.expr[":"][o.toLowerCase()]=function(e){return!!t.data(e,o)},t[c]=t[c]||{},a=t[c][i],r=t[c][i]=function(t,i){return this._createWidget?(arguments.length&&this._createWidget(t,i),e):new r(t,i)},t.extend(r,a,{version:n.version,_proto:t.extend({},n),_childConstructors:[]}),h=new s,h.options=t.widget.extend({},h.options),t.each(n,function(i,n){return t.isFunction(n)?(l[i]=function(){var t=function(){return s.prototype[i].apply(this,arguments)},e=function(t){return s.prototype[i].apply(this,t)};return function(){var i,s=this._super,o=this._superApply;return this._super=t,this._superApply=e,i=n.apply(this,arguments),this._super=s,this._superApply=o,i}}(),e):(l[i]=n,e)}),r.prototype=t.widget.extend(h,{widgetEventPrefix:a?h.widgetEventPrefix||i:i},l,{constructor:r,namespace:c,widgetName:i,widgetFullName:o}),a?(t.each(a._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,r,i._proto)}),delete a._childConstructors):s._childConstructors.push(r),t.widget.bridge(i,r)},t.widget.extend=function(i){for(var n,o,a=s.call(arguments,1),r=0,h=a.length;h>r;r++)for(n in a[r])o=a[r][n],a[r].hasOwnProperty(n)&&o!==e&&(i[n]=t.isPlainObject(o)?t.isPlainObject(i[n])?t.widget.extend({},i[n],o):t.widget.extend({},o):o);return i},t.widget.bridge=function(i,n){var o=n.prototype.widgetFullName||i;t.fn[i]=function(a){var r="string"==typeof a,h=s.call(arguments,1),l=this;return a=!r&&h.length?t.widget.extend.apply(null,[a].concat(h)):a,r?this.each(function(){var s,n=t.data(this,o);return n?t.isFunction(n[a])&&"_"!==a.charAt(0)?(s=n[a].apply(n,h),s!==n&&s!==e?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):e):t.error("no such method '"+a+"' for "+i+" widget instance"):t.error("cannot call methods on "+i+" prior to initialization; "+"attempted to call method '"+a+"'")}):this.each(function(){var e=t.data(this,o);e?e.option(a||{})._init():t.data(this,o,new n(a,this))}),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{disabled:!1,create:null},_createWidget:function(e,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this.bindings=t(),this.hoverable=t(),this.focusable=t(),s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:t.noop,_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(t.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:t.noop,widget:function(){return this.element},option:function(i,s){var n,o,a,r=i;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof i)if(r={},n=i.split("."),i=n.shift(),n.length){for(o=r[i]=t.widget.extend({},this.options[i]),a=0;n.length-1>a;a++)o[n[a]]=o[n[a]]||{},o=o[n[a]];if(i=n.pop(),1===arguments.length)return o[i]===e?null:o[i];o[i]=s}else{if(1===arguments.length)return this.options[i]===e?null:this.options[i];r[i]=s}return this._setOptions(r),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return this.options[t]=e,"disabled"===t&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!e).attr("aria-disabled",e),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,s,n){var o,a=this;"boolean"!=typeof i&&(n=s,s=i,i=!1),n?(s=o=t(s),this.bindings=this.bindings.add(s)):(n=s,s=this.element,o=this.widget()),t.each(n,function(n,r){function h(){return i||a.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof r?a[r]:r).apply(a,arguments):e}"string"!=typeof r&&(h.guid=r.guid=r.guid||h.guid||t.guid++);var l=n.match(/^(\w+)\s*(.*)$/),c=l[1]+a.eventNamespace,u=l[2];u?o.delegate(u,c,h):s.bind(c,h)})},_off:function(t,e){e=(e||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(e).undelegate(e)},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){t(e.currentTarget).addClass("ui-state-hover")},mouseleave:function(e){t(e.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){t(e.currentTarget).addClass("ui-state-focus")},focusout:function(e){t(e.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}})})(jQuery);(function(t,e){function i(t,e,i){return[parseFloat(t[0])*(p.test(t[0])?e/100:1),parseFloat(t[1])*(p.test(t[1])?i/100:1)]}function s(e,i){return parseInt(t.css(e,i),10)||0}function n(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}t.ui=t.ui||{};var a,o=Math.max,r=Math.abs,l=Math.round,h=/left|center|right/,c=/top|center|bottom/,u=/[\+\-]\d+(\.[\d]+)?%?/,d=/^\w+/,p=/%$/,f=t.fn.position;t.position={scrollbarWidth:function(){if(a!==e)return a;var i,s,n=t("<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>"),o=n.children()[0];return t("body").append(n),i=o.offsetWidth,n.css("overflow","scroll"),s=o.offsetWidth,i===s&&(s=n[0].clientWidth),n.remove(),a=i-s},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.width<e.element[0].scrollWidth,a="scroll"===s||"auto"===s&&e.height<e.element[0].scrollHeight;return{width:a?t.position.scrollbarWidth():0,height:n?t.position.scrollbarWidth():0}},getWithinInfo:function(e){var i=t(e||window),s=t.isWindow(i[0]),n=!!i[0]&&9===i[0].nodeType;return{element:i,isWindow:s,isDocument:n,offset:i.offset()||{left:0,top:0},scrollLeft:i.scrollLeft(),scrollTop:i.scrollTop(),width:s?i.width():i.outerWidth(),height:s?i.height():i.outerHeight()}}},t.fn.position=function(e){if(!e||!e.of)return f.apply(this,arguments);e=t.extend({},e);var a,p,g,m,v,_,b=t(e.of),y=t.position.getWithinInfo(e.within),k=t.position.getScrollInfo(y),w=(e.collision||"flip").split(" "),D={};return _=n(b),b[0].preventDefault&&(e.at="left top"),p=_.width,g=_.height,m=_.offset,v=t.extend({},m),t.each(["my","at"],function(){var t,i,s=(e[this]||"").split(" ");1===s.length&&(s=h.test(s[0])?s.concat(["center"]):c.test(s[0])?["center"].concat(s):["center","center"]),s[0]=h.test(s[0])?s[0]:"center",s[1]=c.test(s[1])?s[1]:"center",t=u.exec(s[0]),i=u.exec(s[1]),D[this]=[t?t[0]:0,i?i[0]:0],e[this]=[d.exec(s[0])[0],d.exec(s[1])[0]]}),1===w.length&&(w[1]=w[0]),"right"===e.at[0]?v.left+=p:"center"===e.at[0]&&(v.left+=p/2),"bottom"===e.at[1]?v.top+=g:"center"===e.at[1]&&(v.top+=g/2),a=i(D.at,p,g),v.left+=a[0],v.top+=a[1],this.each(function(){var n,h,c=t(this),u=c.outerWidth(),d=c.outerHeight(),f=s(this,"marginLeft"),_=s(this,"marginTop"),x=u+f+s(this,"marginRight")+k.width,C=d+_+s(this,"marginBottom")+k.height,M=t.extend({},v),T=i(D.my,c.outerWidth(),c.outerHeight());"right"===e.my[0]?M.left-=u:"center"===e.my[0]&&(M.left-=u/2),"bottom"===e.my[1]?M.top-=d:"center"===e.my[1]&&(M.top-=d/2),M.left+=T[0],M.top+=T[1],t.support.offsetFractions||(M.left=l(M.left),M.top=l(M.top)),n={marginLeft:f,marginTop:_},t.each(["left","top"],function(i,s){t.ui.position[w[i]]&&t.ui.position[w[i]][s](M,{targetWidth:p,targetHeight:g,elemWidth:u,elemHeight:d,collisionPosition:n,collisionWidth:x,collisionHeight:C,offset:[a[0]+T[0],a[1]+T[1]],my:e.my,at:e.at,within:y,elem:c})}),e.using&&(h=function(t){var i=m.left-M.left,s=i+p-u,n=m.top-M.top,a=n+g-d,l={target:{element:b,left:m.left,top:m.top,width:p,height:g},element:{element:c,left:M.left,top:M.top,width:u,height:d},horizontal:0>s?"left":i>0?"right":"center",vertical:0>a?"top":n>0?"bottom":"middle"};u>p&&p>r(i+s)&&(l.horizontal="center"),d>g&&g>r(n+a)&&(l.vertical="middle"),l.important=o(r(i),r(s))>o(r(n),r(a))?"horizontal":"vertical",e.using.call(this,t,l)}),c.offset(t.extend(M,{using:h}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,l=n-r,h=r+e.collisionWidth-a-n;e.collisionWidth>a?l>0&&0>=h?(i=t.left+l+e.collisionWidth-a-n,t.left+=l-i):t.left=h>0&&0>=l?n:l>h?n+a-e.collisionWidth:n:l>0?t.left+=l:h>0?t.left-=h:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,l=n-r,h=r+e.collisionHeight-a-n;e.collisionHeight>a?l>0&&0>=h?(i=t.top+l+e.collisionHeight-a-n,t.top+=l-i):t.top=h>0&&0>=l?n:l>h?n+a-e.collisionHeight:n:l>0?t.top+=l:h>0?t.top-=h:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,a=n.offset.left+n.scrollLeft,o=n.width,l=n.isWindow?n.scrollLeft:n.offset.left,h=t.left-e.collisionPosition.marginLeft,c=h-l,u=h+e.collisionWidth-o-l,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-o-a,(0>i||r(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-l,(s>0||u>r(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,a=n.offset.top+n.scrollTop,o=n.height,l=n.isWindow?n.scrollTop:n.offset.top,h=t.top-e.collisionPosition.marginTop,c=h-l,u=h+e.collisionHeight-o-l,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-o-a,t.top+p+f+g>c&&(0>s||r(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-l,t.top+p+f+g>u&&(i>0||u>r(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}},function(){var e,i,s,n,a,o=document.getElementsByTagName("body")[0],r=document.createElement("div");e=document.createElement(o?"div":"body"),s={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},o&&t.extend(s,{position:"absolute",left:"-1000px",top:"-1000px"});for(a in s)e.style[a]=s[a];e.appendChild(r),i=o||document.documentElement,i.insertBefore(e,i.firstChild),r.style.cssText="position: absolute; left: 10.7432222px;",n=t(r).offset().left,t.support.offsetFractions=n>10&&11>n,e.innerHTML="",i.removeChild(e)}()})(jQuery);(function(e){e.widget("ui.autocomplete",{version:"1.10.4",defaultElement:"<input>",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var t,i,s,n=this.element[0].nodeName.toLowerCase(),a="textarea"===n,o="input"===n;this.isMultiLine=a?!0:o?!1:this.element.prop("isContentEditable"),this.valueMethod=this.element[a||o?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return t=!0,s=!0,i=!0,undefined;t=!1,s=!1,i=!1;var a=e.ui.keyCode;switch(n.keyCode){case a.PAGE_UP:t=!0,this._move("previousPage",n);break;case a.PAGE_DOWN:t=!0,this._move("nextPage",n);break;case a.UP:t=!0,this._keyEvent("previous",n);break;case a.DOWN:t=!0,this._keyEvent("next",n);break;case a.ENTER:case a.NUMPAD_ENTER:this.menu.active&&(t=!0,n.preventDefault(),this.menu.select(n));break;case a.TAB:this.menu.active&&this.menu.select(n);break;case a.ESCAPE:this.menu.element.is(":visible")&&(this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(t)return t=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),undefined;if(!i){var n=e.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(e){return s?(s=!1,e.preventDefault(),undefined):(this._searchTimeout(e),undefined)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){return this.cancelBlur?(delete this.cancelBlur,undefined):(clearTimeout(this.searching),this.close(e),this._change(e),undefined)}}),this._initSource(),this.menu=e("<ul>").addClass("ui-autocomplete ui-front").appendTo(this._appendTo()).menu({role:null}).hide().data("ui-menu"),this._on(this.menu.element,{mousedown:function(t){t.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur});var i=this.menu.element[0];e(t.target).closest(".ui-menu-item").length||this._delay(function(){var t=this;this.document.one("mousedown",function(s){s.target===t.element[0]||s.target===i||e.contains(i,s.target)||t.close()})})},menufocus:function(t,i){if(this.isNewMenu&&(this.isNewMenu=!1,t.originalEvent&&/^mouse/.test(t.originalEvent.type)))return this.menu.blur(),this.document.one("mousemove",function(){e(t.target).trigger(t.originalEvent)}),undefined;var s=i.item.data("ui-autocomplete-item");!1!==this._trigger("focus",t,{item:s})?t.originalEvent&&/^key/.test(t.originalEvent.type)&&this._value(s.value):this.liveRegion.text(s.value)},menuselect:function(e,t){var i=t.item.data("ui-autocomplete-item"),s=this.previous;this.element[0]!==this.document[0].activeElement&&(this.element.focus(),this.previous=s,this._delay(function(){this.previous=s,this.selectedItem=i})),!1!==this._trigger("select",e,{item:i})&&this._value(i.value),this.term=this._value(),this.close(e),this.selectedItem=i}}),this.liveRegion=e("<span>",{role:"status","aria-live":"polite"}).addClass("ui-helper-hidden-accessible").insertBefore(this.element),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(e,t){this._super(e,t),"source"===e&&this._initSource(),"appendTo"===e&&this.menu.element.appendTo(this._appendTo()),"disabled"===e&&t&&this.xhr&&this.xhr.abort()},_appendTo:function(){var t=this.options.appendTo;return t&&(t=t.jquery||t.nodeType?e(t):this.document.find(t).eq(0)),t||(t=this.element.closest(".ui-front")),t.length||(t=this.document[0].body),t},_initSource:function(){var t,i,s=this;e.isArray(this.options.source)?(t=this.options.source,this.source=function(i,s){s(e.ui.autocomplete.filter(t,i.term))}):"string"==typeof this.options.source?(i=this.options.source,this.source=function(t,n){s.xhr&&s.xhr.abort(),s.xhr=e.ajax({url:i,data:t,dataType:"json",success:function(e){n(e)},error:function(){n([])}})}):this.source=this.options.source},_searchTimeout:function(e){clearTimeout(this.searching),this.searching=this._delay(function(){this.term!==this._value()&&(this.selectedItem=null,this.search(null,e))},this.options.delay)},search:function(e,t){return e=null!=e?e:this._value(),this.term=this._value(),e.length<this.options.minLength?this.close(t):this._trigger("search",t)!==!1?this._search(e):undefined},_search:function(e){this.pending++,this.element.addClass("ui-autocomplete-loading"),this.cancelSearch=!1,this.source({term:e},this._response())},_response:function(){var t=++this.requestIndex;return e.proxy(function(e){t===this.requestIndex&&this.__response(e),this.pending--,this.pending||this.element.removeClass("ui-autocomplete-loading")},this)},__response:function(e){e&&(e=this._normalize(e)),this._trigger("response",null,{content:e}),!this.options.disabled&&e&&e.length&&!this.cancelSearch?(this._suggest(e),this._trigger("open")):this._close()},close:function(e){this.cancelSearch=!0,this._close(e)},_close:function(e){this.menu.element.is(":visible")&&(this.menu.element.hide(),this.menu.blur(),this.isNewMenu=!0,this._trigger("close",e))},_change:function(e){this.previous!==this._value()&&this._trigger("change",e,{item:this.selectedItem})},_normalize:function(t){return t.length&&t[0].label&&t[0].value?t:e.map(t,function(t){return"string"==typeof t?{label:t,value:t}:e.extend({label:t.label||t.value,value:t.value||t.label},t)})},_suggest:function(t){var i=this.menu.element.empty();this._renderMenu(i,t),this.isNewMenu=!0,this.menu.refresh(),i.show(),this._resizeMenu(),i.position(e.extend({of:this.element},this.options.position)),this.options.autoFocus&&this.menu.next()},_resizeMenu:function(){var e=this.menu.element;e.outerWidth(Math.max(e.width("").outerWidth()+1,this.element.outerWidth()))},_renderMenu:function(t,i){var s=this;e.each(i,function(e,i){s._renderItemData(t,i)})},_renderItemData:function(e,t){return this._renderItem(e,t).data("ui-autocomplete-item",t)},_renderItem:function(t,i){return e("<li>").append(e("<a>").text(i.label)).appendTo(t)},_move:function(e,t){return this.menu.element.is(":visible")?this.menu.isFirstItem()&&/^previous/.test(e)||this.menu.isLastItem()&&/^next/.test(e)?(this._value(this.term),this.menu.blur(),undefined):(this.menu[e](t),undefined):(this.search(null,t),undefined)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(e,t){(!this.isMultiLine||this.menu.element.is(":visible"))&&(this._move(e,t),t.preventDefault())}}),e.extend(e.ui.autocomplete,{escapeRegex:function(e){return e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(t,i){var s=RegExp(e.ui.autocomplete.escapeRegex(i),"i");return e.grep(t,function(e){return s.test(e.label||e.value||e)})}}),e.widget("ui.autocomplete",e.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(e){return e+(e>1?" results are":" result is")+" available, use up and down arrow keys to navigate."}}},__response:function(e){var t;this._superApply(arguments),this.options.disabled||this.cancelSearch||(t=e&&e.length?this.options.messages.results(e.length):this.options.messages.noResults,this.liveRegion.text(t))}})})(jQuery);(function(t){t.widget("ui.menu",{version:"1.10.4",defaultElement:"<ul>",delay:300,options:{icons:{submenu:"ui-icon-carat-1-e"},menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().addClass("ui-menu ui-widget ui-widget-content ui-corner-all").toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length).attr({role:this.options.role,tabIndex:0}).bind("click"+this.eventNamespace,t.proxy(function(t){this.options.disabled&&t.preventDefault()},this)),this.options.disabled&&this.element.addClass("ui-state-disabled").attr("aria-disabled","true"),this._on({"mousedown .ui-menu-item > a":function(t){t.preventDefault()},"click .ui-state-disabled > a":function(t){t.preventDefault()},"click .ui-menu-item:has(a)":function(e){var i=t(e.target).closest(".ui-menu-item");!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&t(this.document[0].activeElement).closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){var i=t(e.currentTarget);i.siblings().children(".ui-state-active").removeClass("ui-state-active"),this.focus(e,i)},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.children(".ui-menu-item").eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){t.contains(this.element[0],this.document[0].activeElement)||this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(e){t(e.target).closest(".ui-menu").length||this.collapseAll(e),this.mouseHandled=!1}})},_destroy:function(){this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeClass("ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons").removeAttr("role").removeAttr("tabIndex").removeAttr("aria-labelledby").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-disabled").removeUniqueId().show(),this.element.find(".ui-menu-item").removeClass("ui-menu-item").removeAttr("role").removeAttr("aria-disabled").children("a").removeUniqueId().removeClass("ui-corner-all ui-state-hover").removeAttr("tabIndex").removeAttr("role").removeAttr("aria-haspopup").children().each(function(){var e=t(this);e.data("ui-menu-submenu-carat")&&e.remove()}),this.element.find(".ui-menu-divider").removeClass("ui-menu-divider ui-widget-content")},_keydown:function(e){function i(t){return t.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}var s,n,a,o,r,l=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:l=!1,n=this.previousFilter||"",a=String.fromCharCode(e.keyCode),o=!1,clearTimeout(this.filterTimer),a===n?o=!0:a=n+a,r=RegExp("^"+i(a),"i"),s=this.activeMenu.children(".ui-menu-item").filter(function(){return r.test(t(this).children("a").text())}),s=o&&-1!==s.index(this.active.next())?this.active.nextAll(".ui-menu-item"):s,s.length||(a=String.fromCharCode(e.keyCode),r=RegExp("^"+i(a),"i"),s=this.activeMenu.children(".ui-menu-item").filter(function(){return r.test(t(this).children("a").text())})),s.length?(this.focus(e,s),s.length>1?(this.previousFilter=a,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter):delete this.previousFilter}l&&e.preventDefault()},_activate:function(t){this.active.is(".ui-state-disabled")||(this.active.children("a[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i=this.options.icons.submenu,s=this.element.find(this.options.menus);this.element.toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length),s.filter(":not(.ui-menu)").addClass("ui-menu ui-widget ui-widget-content ui-corner-all").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),s=e.prev("a"),n=t("<span>").addClass("ui-menu-icon ui-icon "+i).data("ui-menu-submenu-carat",!0);s.attr("aria-haspopup","true").prepend(n),e.attr("aria-labelledby",s.attr("id"))}),e=s.add(this.element),e.children(":not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","presentation").children("a").uniqueId().addClass("ui-corner-all").attr({tabIndex:-1,role:this._itemRole()}),e.children(":not(.ui-menu-item)").each(function(){var e=t(this);/[^\-\u2014\u2013\s]/.test(e.text())||e.addClass("ui-widget-content ui-menu-divider")}),e.children(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){"icons"===t&&this.element.find(".ui-menu-icon").removeClass(this.options.icons.submenu).addClass(e.submenu),this._super(t,e)},focus:function(t,e){var i,s;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children("a").addClass("ui-state-focus"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),this.active.parent().closest(".ui-menu-item").children("a:first").addClass("ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,a,o,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,a=this.activeMenu.scrollTop(),o=this.activeMenu.height(),r=e.height(),0>n?this.activeMenu.scrollTop(a+n):n+r>o&&this.activeMenu.scrollTop(a+n-o+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this.active.children("a").removeClass("ui-state-focus"),this.active=null,this._trigger("blur",t,{item:this.active}))},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false").end().find("a.ui-state-active").removeClass("ui-state-active")},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").children(".ui-menu-item").first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.children(".ui-menu-item")[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.children(".ui-menu-item")[this.active?"last":"first"]())),undefined):(this.next(e),undefined)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.children(".ui-menu-item").first())),undefined):(this.next(e),undefined)},_hasScroll:function(){return this.element.outerHeight()<this.element.prop("scrollHeight")},select:function(e){this.active=this.active||t(e.target).closest(".ui-menu-item");var i={item:this.active};this.active.has(".ui-menu").length||this.collapseAll(e,!0),this._trigger("select",e,i)}})})(jQuery);(function(t,e){var i="ui-effects-";t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:t>s.max?s.max:t)}function s(i){var s=h(),n=s._rgba=[];return i=i.toLowerCase(),f(l,function(t,a){var o,r=a.re.exec(i),l=r&&a.parse(r),h=a.space||"rgba";return l?(o=s[h](l),s[c[h].cache]=o[c[h].cache],n=s._rgba=o._rgba,!1):e}),n.length?("0,0,0,0"===n.join()&&t.extend(n,a.transparent),s):a[i]}function n(t,e,i){return i=(i+1)%1,1>6*i?t+6*(e-t)*i:1>2*i?e:2>3*i?t+6*(e-t)*(2/3-i):t}var a,o="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,l=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],h=t.Color=function(e,i,s,n){return new t.Color.fn.parse(e,i,s,n)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},d=h.support={},p=t("<p>")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(c,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),h.fn=t.extend(h.prototype,{parse:function(n,o,r,l){if(n===e)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=t(n).css(o),o=e);var u=this,d=t.type(n),p=this._rgba=[];return o!==e&&(n=[n,o,r,l],d="array"),"string"===d?this.parse(s(n)||a._default):"array"===d?(f(c.rgba.props,function(t,e){p[e.idx]=i(n[e.idx],e)}),this):"object"===d?(n instanceof h?f(c,function(t,e){n[e.cache]&&(u[e.cache]=n[e.cache].slice())}):f(c,function(e,s){var a=s.cache;f(s.props,function(t,e){if(!u[a]&&s.to){if("alpha"===t||null==n[t])return;u[a]=s.to(u._rgba)}u[a][e.idx]=i(n[t],e,!0)}),u[a]&&0>t.inArray(null,u[a].slice(0,3))&&(u[a][3]=1,s.from&&(u._rgba=s.from(u[a])))}),this):e},is:function(t){var i=h(t),s=!0,n=this;return f(c,function(t,a){var o,r=i[a.cache];return r&&(o=n[a.cache]||a.to&&a.to(n._rgba)||[],f(a.props,function(t,i){return null!=r[i.idx]?s=r[i.idx]===o[i.idx]:e})),s}),s},_space:function(){var t=[],e=this;return f(c,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=h(t),n=s._space(),a=c[n],o=0===this.alpha()?h("transparent"):this,r=o[a.cache]||a.to(o._rgba),l=r.slice();return s=s[a.cache],f(a.props,function(t,n){var a=n.idx,o=r[a],h=s[a],c=u[n.type]||{};null!==h&&(null===o?l[a]=h:(c.mod&&(h-o>c.mod/2?o+=c.mod:o-h>c.mod/2&&(o-=c.mod)),l[a]=i((h-o)*e+o,n)))}),this[n](l)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=h(e)._rgba;return h(t.map(i,function(t,e){return(1-s)*n[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&3>e&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),h.fn.parse.prototype=h.fn,c.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,n=t[1]/255,a=t[2]/255,o=t[3],r=Math.max(s,n,a),l=Math.min(s,n,a),h=r-l,c=r+l,u=.5*c;return e=l===r?0:s===r?60*(n-a)/h+360:n===r?60*(a-s)/h+120:60*(s-n)/h+240,i=0===h?0:.5>=u?h/c:h/(2-c),[Math.round(e)%360,i,u,null==o?1:o]},c.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],a=t[3],o=.5>=s?s*(1+i):s+i-s*i,r=2*s-o;return[Math.round(255*n(r,o,e+1/3)),Math.round(255*n(r,o,e)),Math.round(255*n(r,o,e-1/3)),a]},f(c,function(s,n){var a=n.props,o=n.cache,l=n.to,c=n.from;h.fn[s]=function(s){if(l&&!this[o]&&(this[o]=l(this._rgba)),s===e)return this[o].slice();var n,r=t.type(s),u="array"===r||"object"===r?s:arguments,d=this[o].slice();return f(a,function(t,e){var s=u["object"===r?t:e.idx];null==s&&(s=d[e.idx]),d[e.idx]=i(s,e)}),c?(n=h(c(d)),n[o]=d,n):h(d)},f(a,function(e,i){h.fn[e]||(h.fn[e]=function(n){var a,o=t.type(n),l="alpha"===e?this._hsla?"hsla":"rgba":s,h=this[l](),c=h[i.idx];return"undefined"===o?c:("function"===o&&(n=n.call(this,c),o=t.type(n)),null==n&&i.empty?this:("string"===o&&(a=r.exec(n),a&&(n=c+parseFloat(a[2])*("+"===a[1]?1:-1))),h[i.idx]=n,this[l](h)))})})}),h.hook=function(e){var i=e.split(" ");f(i,function(e,i){t.cssHooks[i]={set:function(e,n){var a,o,r="";if("transparent"!==n&&("string"!==t.type(n)||(a=s(n)))){if(n=h(a||n),!d.rgba&&1!==n._rgba[3]){for(o="backgroundColor"===i?e.parentNode:e;(""===r||"transparent"===r)&&o&&o.style;)try{r=t.css(o,"backgroundColor"),o=o.parentNode}catch(l){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{e.style[i]=n}catch(l){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=h(e.elem,i),e.end=h(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},h.hook(o),t.cssHooks.borderColor={expand:function(t){var e={};return f(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},a=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(jQuery),function(){function i(e){var i,s,n=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,a={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(a[t.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(a[i]=n[i]);return a}function s(e,i){var s,n,o={};for(s in i)n=i[s],e[s]!==n&&(a[s]||(t.fx.step[s]||!isNaN(parseFloat(n)))&&(o[s]=n));return o}var n=["add","remove","toggle"],a={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(jQuery.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(e,a,o,r){var l=t.speed(a,o,r);return this.queue(function(){var a,o=t(this),r=o.attr("class")||"",h=l.children?o.find("*").addBack():o;h=h.map(function(){var e=t(this);return{el:e,start:i(this)}}),a=function(){t.each(n,function(t,i){e[i]&&o[i+"Class"](e[i])})},a(),h=h.map(function(){return this.end=i(this.el[0]),this.diff=s(this.start,this.end),this}),o.attr("class",r),h=h.map(function(){var e=this,i=t.Deferred(),s=t.extend({},l,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,h.get()).done(function(){a(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),l.complete.call(o[0])})})},t.fn.extend({addClass:function(e){return function(i,s,n,a){return s?t.effects.animateClass.call(this,{add:i},s,n,a):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,n,a){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,n,a):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(i){return function(s,n,a,o,r){return"boolean"==typeof n||n===e?a?t.effects.animateClass.call(this,n?{add:s}:{remove:s},a,o,r):i.apply(this,arguments):t.effects.animateClass.call(this,{toggle:s},n,a,o)}}(t.fn.toggleClass),switchClass:function(e,i,s,n,a){return t.effects.animateClass.call(this,{add:i,remove:e},s,n,a)}})}(),function(){function s(e,i,s,n){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(n=s,s=i,i={}),t.isFunction(s)&&(n=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=n||i.complete,e}function n(e){return!e||"number"==typeof e||t.fx.speeds[e]?!0:"string"!=typeof e||t.effects.effect[e]?t.isFunction(e)?!0:"object"!=typeof e||e.effect?!1:!0:!0}t.extend(t.effects,{version:"1.10.4",save:function(t,e){for(var s=0;e.length>s;s++)null!==e[s]&&t.data(i+e[s],t[0].style[e[s]])},restore:function(t,s){var n,a;for(a=0;s.length>a;a++)null!==s[a]&&(n=t.data(i+s[a]),n===e&&(n=""),t.css(s[a],n))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),"float":e.css("float")},s=t("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},a=document.activeElement;try{a.id}catch(o){a=document.body}return e.wrap(s),(e[0]===a||t.contains(e[0],a))&&t(a).focus(),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).focus()),e},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var a=e.cssUnit(i);a[0]>0&&(n[i]=a[0]*s+a[1])}),n}}),t.fn.extend({effect:function(){function e(e){function s(){t.isFunction(a)&&a.call(n[0]),t.isFunction(e)&&e()}var n=t(this),a=i.complete,r=i.mode;(n.is(":hidden")?"hide"===r:"show"===r)?(n[r](),s()):o.call(n[0],i,s)}var i=s.apply(this,arguments),n=i.mode,a=i.queue,o=t.effects.effect[i.effect];return t.fx.off||!o?n?this[n](i.duration,i.complete):this.each(function(){i.complete&&i.complete.call(this)}):a===!1?this.each(e):this.queue(a||"fx",e)},show:function(t){return function(e){if(n(e))return t.apply(this,arguments);var i=s.apply(this,arguments);return i.mode="show",this.effect.call(this,i)}}(t.fn.show),hide:function(t){return function(e){if(n(e))return t.apply(this,arguments);var i=s.apply(this,arguments);return i.mode="hide",this.effect.call(this,i)}}(t.fn.hide),toggle:function(t){return function(e){if(n(e)||"boolean"==typeof e)return t.apply(this,arguments);var i=s.apply(this,arguments);return i.mode="toggle",this.effect.call(this,i)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s}})}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}()})(jQuery);(function(t){t.effects.effect.highlight=function(e,i){var s=t(this),n=["backgroundImage","backgroundColor","opacity"],a=t.effects.setMode(s,e.mode||"show"),o={backgroundColor:s.css("backgroundColor")};"hide"===a&&(o.opacity=0),t.effects.save(s,n),s.show().css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(o,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){"hide"===a&&s.hide(),t.effects.restore(s,n),i()}})}})(jQuery); \ No newline at end of file diff --git a/elasticsearch-persistence/examples/music/vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png b/elasticsearch-persistence/examples/music/vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png deleted file mode 100644 index f1273672d253263b7564e9e21d69d7d9d0b337d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^j6j^i!3HGVb)pi0l%l7LV~E7m<R2d&AFo$qV`FPm oboulDzr6KyA+fs7hb{~ZQx+&qVC9&67pR!Q)78&qol`;+0H8b=ng9R* From 058d810dcf83758174af2ea7dfee9d1527cd3838 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Wed, 15 Aug 2018 16:26:37 +0200 Subject: [PATCH 393/582] [STORE] Update Sinatra app --- elasticsearch-persistence/examples/notes/application.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/elasticsearch-persistence/examples/notes/application.rb b/elasticsearch-persistence/examples/notes/application.rb index 03ffa5d10..ed6af1b8a 100644 --- a/elasticsearch-persistence/examples/notes/application.rb +++ b/elasticsearch-persistence/examples/notes/application.rb @@ -54,11 +54,12 @@ def __truncate_text class NoteRepository include Elasticsearch::Persistence::Repository + include Elasticsearch::Persistence::Repository::DSL client Elasticsearch::Client.new url: ENV['ELASTICSEARCH_URL'], log: true - index :notes - type :note + index_name :notes + document_type :note mapping do indexes :text, analyzer: 'snowball' @@ -66,8 +67,6 @@ class NoteRepository indexes :created_at, type: 'date' end - create_index! - def deserialize(document) Note.new document['_source'].merge('id' => document['_id']) end From 8affe4b3e338a0a517827ed137143c473af2993d Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Thu, 16 Aug 2018 11:51:50 +0200 Subject: [PATCH 394/582] [STORE] Update README --- elasticsearch-persistence/README.md | 46 ++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index 637313d1f..728ec2250 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -42,7 +42,7 @@ The library provides the Repository pattern for adding persistence to your Ruby The `Elasticsearch::Persistence::Repository` module provides an implementation of the [repository pattern](http://martinfowler.com/eaaCatalog/repository.html) and allows -to save, delete, find and search objects stored in Elasticsearch, as well as configure +you to save, delete, find and search objects stored in Elasticsearch, as well as configure mappings and settings for the index. It's an unobtrusive and decoupled way of adding persistence to your Ruby objects. @@ -257,9 +257,9 @@ puts repository.find(1).attributes['image'] Each of the following configurations can be set for a repository instance. If you have included the `Elasticsearch::Persistence::Repository::DSL` mixin, then you can use the class-level DSL -methods to set each configuration. You can override the configuration for any instance by passing options to the +methods to set each value. You can still override the configuration for any instance by passing options to the `#initialize` method. -If you don't use the DSL mixin, you can set also the instance configuration with options passed the `#initialize` method. +Even if you don't use the DSL mixin, you can set the instance configuration with options passed the `#initialize` method. ##### Client @@ -269,6 +269,9 @@ The repository uses the standard Elasticsearch [client](https://github.com/elast client = Elasticsearch::Client.new(url: 'http://search.server.org') repository = NoteRepository.new(client: client) repository.client.transport.logger = Logger.new(STDERR) +repository.client +# => Elasticsearch::Client + ``` or with the DSL mixin: @@ -282,6 +285,8 @@ class NoteRepository end repository = NoteRepository.new +repository.client +# => Elasticsearch::Client ``` @@ -292,6 +297,9 @@ is 'repository'. ```ruby repository = NoteRepository.new(index_name: 'notes_development') +repository.index_name +# => 'notes_development' + ``` or with the DSL mixin: @@ -305,15 +313,20 @@ class NoteRepository end repository = NoteRepository.new +repository.index_name +# => 'notes_development' ``` -The `type` method specifies the Elasticsearch document type to use for storage, lookup and search. The default value is +The `document_type` method specifies the Elasticsearch document type to use for storage, lookup and search. The default value is '_doc'. Keep in mind that future versions of Elasticsearch will not allow you to set this yourself and will use the type, '_doc'. ```ruby repository = NoteRepository.new(document_type: 'note') +repository.document_type +# => 'note' + ``` or with the DSL mixin: @@ -327,6 +340,8 @@ class NoteRepository end repository = NoteRepository.new +repository.document_type +# => 'note' ``` @@ -336,6 +351,9 @@ returned instead. ```ruby repository = NoteRepository.new(klass: Note) +repository.klass +# => Note + ``` or with the DSL mixin: @@ -349,6 +367,8 @@ class NoteRepository end repository = NoteRepository.new +repository.klass +# => Note ``` @@ -383,8 +403,10 @@ repository = NoteRepository.new ``` -You can also use the `#create` method defined on the repository class to create and set the mappings and settings -on an instance with a block in one call: +##### Create a Repository and set its configuration with a block + +You can also use the `#create` method to instantiate and set the mappings and settings on an instance +with a block in one call: ```ruby repository = NoteRepository.create(index_name: 'notes_development') do @@ -399,13 +421,15 @@ repository = NoteRepository.create(index_name: 'notes_development') do end ``` +##### Index Management + The convenience methods `create_index!`, `delete_index!` and `refresh_index!` allow you to manage the index lifecycle. These methods can only be called on repository instances and are not implemented at the class level. ##### Serialization -The `serialize` and `deserialize` methods allow you to customize the serialization of the document when passing it -to the storage, and the initialization procedure when loading it from the storage: +The `serialize` and `deserialize` methods allow you to customize the serialization of the document when it +is persisted to Elasticsearch, and define the initialization procedure when loading it from the storage: ```ruby class NoteRepository @@ -447,7 +471,7 @@ repository.update 1, script: 'if (!ctx._source.tags.contains(t)) { ctx._source.t ``` -The `delete` method allows to remove objects from the repository (pass either the object itself or its ID): +The `delete` method allows you to remove objects from the repository (pass either the object itself or its ID): ```ruby repository.delete(note) @@ -456,7 +480,7 @@ repository.delete(1) ##### Finding -The `find` method allows to find one or many documents in the storage and returns them as deserialized Ruby objects: +The `find` method allows you to find one or many documents in the storage and returns them as deserialized Ruby objects: ```ruby repository.save Note.new(id: 2, title: 'Fast White Dog') @@ -479,7 +503,7 @@ Handle the missing objects in the application code, or call `compact` on the res ##### Search -The `search` method to retrieve objects from the repository by a query string or definition in the Elasticsearch DSL: +The `search` method is used to retrieve objects from the repository by a query string or definition in the Elasticsearch DSL: ```ruby repository.search('fox or dog').to_a From 58402fbc3e349d8c8f1b16bb378b45f4aa154e9c Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Thu, 16 Aug 2018 11:56:16 +0200 Subject: [PATCH 395/582] [STORE] Change document type references to _doc --- elasticsearch-persistence/README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index 728ec2250..cf4856536 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -76,7 +76,7 @@ We can save a `Note` instance into the repository... note = Note.new id: 1, text: 'Test' repository.save(note) -# PUT http://localhost:9200/repository/note/1 [status:201, request:0.210s, query:n/a] +# PUT http://localhost:9200/repository/_doc/1 [status:201, request:0.210s, query:n/a] # > {"id":1,"text":"Test"} # < {"_index":"repository","_type":"note","_id":"1","_version":1,"created":true} ``` @@ -85,7 +85,7 @@ repository.save(note) ```ruby n = repository.find(1) -# GET http://localhost:9200/repository/_all/1 [status:200, request:0.003s, query:n/a] +# GET http://localhost:9200/repository/_doc/1 [status:200, request:0.003s, query:n/a] # < {"_index":"repository","_type":"note","_id":"1","_version":2,"found":true, "_source" : {"id":1,"text":"Test"}} => <Note:0x007fcbfc0c4980 @attributes={"id"=>1, "text"=>"Test"}> ``` @@ -104,7 +104,7 @@ repository.search(query: { match: { text: 'test' } }).first ```ruby repository.delete(note) -# DELETE http://localhost:9200/repository/note/1 [status:200, request:0.014s, query:n/a] +# DELETE http://localhost:9200/repository/_doc/1 [status:200, request:0.014s, query:n/a] # < {"found":true,"_index":"repository","_type":"note","_id":"1","_version":3} => {"found"=>true, "_index"=>"repository", "_type"=>"note", "_id"=>"1", "_version"=>2} ``` @@ -145,7 +145,7 @@ class MyRepository end client = Elasticsearch::Client.new(url: ENV['ELASTICSEARCH_URL'], log: true) -repository = MyRepository.new(client: client, index_name: :my_notes, type: :my_note, klass: Note) +repository = MyRepository.new(client: client, index_name: :my_notes, type: :note, klass: Note) repository.settings number_of_shards: 1 do mapping do indexes :text, analyzer: 'snowball' @@ -168,7 +168,7 @@ Save the document with extra properties added by the `serialize` method: ```ruby repository.save(note) -# PUT http://localhost:9200/my_notes/my_note/1 +# PUT http://localhost:9200/my_notes/note/1 # > {"id":1,"text":"Test","my_special_key":"my_special_stuff"} {"_index"=>"my_notes", "_type"=>"my_note", "_id"=>"1", "_version"=>4, ... } ``` @@ -177,7 +177,7 @@ And `deserialize` it: ```ruby repository.find(1) -# ***** CUSTOM DESERIALIZE LOGIC KICKING IN... ***** +# ***** CUSTOM DESERIALIZE LOGIC... ***** <Note:0x007f9bd782b7a0 @attributes={... "my_special_key"=>"my_special_stuff"}> ``` @@ -245,10 +245,10 @@ repository.create_index!(force: true) note = Note.new('id' => 1, 'text' => 'Document with image', 'image' => '... BINARY DATA ...') repository.save(note) -# PUT http://localhost:9200/notes_development/note/1 +# PUT http://localhost:9200/notes_development/_doc/1 # > {"id":1,"text":"Document with image","image":"Li4uIEJJTkFSWSBEQVRBIC4uLg==\n"} puts repository.find(1).attributes['image'] -# GET http://localhost:9200/notes_development/note/1 +# GET http://localhost:9200/notes_development/_doc/1 # < {... "_source" : { ... "image":"Li4uIEJJTkFSWSBEQVRBIC4uLg==\n"}} # => ... BINARY DATA ... ``` @@ -452,7 +452,7 @@ The `save` method allows you to store a domain object in the repository: ```ruby note = Note.new id: 1, title: 'Quick Brown Fox' repository.save(note) -# => {"_index"=>"notes_development", "_type"=>"my_note", "_id"=>"1", "_version"=>1, "created"=>true} +# => {"_index"=>"notes_development", "_type"=>"_doc", "_id"=>"1", "_version"=>1, "created"=>true} ``` The `update` method allows you to perform a partial update of a document in the repository. @@ -460,14 +460,14 @@ Use either a partial document: ```ruby repository.update id: 1, title: 'UPDATED', tags: [] -# => {"_index"=>"notes_development", "_type"=>"note", "_id"=>"1", "_version"=>2} +# => {"_index"=>"notes_development", "_type"=>"_doc", "_id"=>"1", "_version"=>2} ``` Or a script (optionally with parameters): ```ruby repository.update 1, script: 'if (!ctx._source.tags.contains(t)) { ctx._source.tags += t }', params: { t: 'foo' } -# => {"_index"=>"notes_development", "_type"=>"note", "_id"=>"1", "_version"=>3} +# => {"_index"=>"notes_development", "_type"=>"_doc", "_id"=>"1", "_version"=>3} ``` @@ -507,11 +507,11 @@ The `search` method is used to retrieve objects from the repository by a query s ```ruby repository.search('fox or dog').to_a -# GET http://localhost:9200/notes_development/my_note/_search?q=fox +# GET http://localhost:9200/notes_development/_doc/_search?q=fox # => [<MyNote ... FOX ...>, <MyNote ... DOG ...>] repository.search(query: { match: { title: 'fox dog' } }).to_a -# GET http://localhost:9200/notes_development/my_note/_search +# GET http://localhost:9200/notes_development/_doc/_search # > {"query":{"match":{"title":"fox dog"}}} # => [<MyNote ... FOX ...>, <MyNote ... DOG ...>] ``` From eb476aae1ffe5e01db7561f52ceced68b4844869 Mon Sep 17 00:00:00 2001 From: Philip Yu <ht.yu@me.com> Date: Mon, 3 Sep 2018 18:58:13 +0800 Subject: [PATCH 396/582] [MODEL] Support scope, query and preprocess importing options in Mongoid Adapter (#830) [MODEL] Support scope, query and preprocess importing options in Mongoid Adapter in master --- .../elasticsearch/model/adapters/mongoid.rb | 13 +++- .../test/integration/mongoid_basic_test.rb | 64 +++++++++++++++++++ .../test/unit/adapter_mongoid_test.rb | 59 ++++++++++++++++- 3 files changed, 132 insertions(+), 4 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb index 5117dbf58..d4aff56f9 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb @@ -63,10 +63,17 @@ module Importing # @see https://github.com/karmi/retire/pull/724 # def __find_in_batches(options={}, &block) - options[:batch_size] ||= 1_000 + batch_size = options[:batch_size] || 1_000 + query = options[:query] + named_scope = options[:scope] + preprocess = options[:preprocess] + + scope = all + scope = scope.send(named_scope) if named_scope + scope = query.is_a?(Proc) ? scope.class_exec(&query) : scope.where(query) if query - all.no_timeout.each_slice(options[:batch_size]) do |items| - yield items + scope.no_timeout.each_slice(batch_size) do |items| + yield (preprocess ? self.__send__(preprocess, items) : items) end end diff --git a/elasticsearch-model/test/integration/mongoid_basic_test.rb b/elasticsearch-model/test/integration/mongoid_basic_test.rb index c8b44dda0..292c1f1a5 100644 --- a/elasticsearch-model/test/integration/mongoid_basic_test.rb +++ b/elasticsearch-model/test/integration/mongoid_basic_test.rb @@ -167,6 +167,70 @@ def as_indexed_json(options={}) assert response.results.any?, "Search has not returned results: #{response.to_a}" end end + + context "importing when the model has a default scope" do + class ::MongoidArticleWithDefaultScope + include Mongoid::Document + include Elasticsearch::Model + + default_scope -> { where(status: 'active') } + + field :id, type: String + field :title, type: String + field :status, type: String, default: 'active' + + attr_accessible :title if respond_to? :attr_accessible + attr_accessible :status if respond_to? :attr_accessible + + settings index: { number_of_shards: 1, number_of_replicas: 0 } do + mapping do + indexes :title, type: 'text', analyzer: 'snowball' + indexes :status, type: 'text' + indexes :created_at, type: 'date' + end + end + + def as_indexed_json(options={}) + as_json(except: [:id, :_id]) + end + end + + setup do + Elasticsearch::Model::Adapter.register \ + Elasticsearch::Model::Adapter::Mongoid, + lambda { |klass| !!defined?(::Mongoid::Document) && klass.respond_to?(:ancestors) && klass.ancestors.include?(::Mongoid::Document) } + + MongoidArticleWithDefaultScope.__elasticsearch__.create_index! force: true + + MongoidArticleWithDefaultScope.delete_all + + MongoidArticleWithDefaultScope.create! title: 'Test' + MongoidArticleWithDefaultScope.create! title: 'Testing Coding' + MongoidArticleWithDefaultScope.create! title: 'Coding' + MongoidArticleWithDefaultScope.create! title: 'Test legacy code', status: 'removed' + + MongoidArticleWithDefaultScope.__elasticsearch__.refresh_index! + MongoidArticleWithDefaultScope.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' + end + + should "import only documents from the default scope" do + assert_equal 3, MongoidArticleWithDefaultScope.count + + assert_equal 0, MongoidArticleWithDefaultScope.import + + MongoidArticleWithDefaultScope.__elasticsearch__.refresh_index! + assert_equal 3, MongoidArticleWithDefaultScope.search('*').results.total + end + + should "import only documents from a specific query combined with the default scope" do + assert_equal 3, MongoidArticleWithDefaultScope.count + + assert_equal 0, MongoidArticleWithDefaultScope.import(query: -> { where(title: /^Test/) }) + + MongoidArticleWithDefaultScope.__elasticsearch__.refresh_index! + assert_equal 2, MongoidArticleWithDefaultScope.search('*').results.total + end + end end end diff --git a/elasticsearch-model/test/unit/adapter_mongoid_test.rb b/elasticsearch-model/test/unit/adapter_mongoid_test.rb index ca9b0d20b..0074df097 100644 --- a/elasticsearch-model/test/unit/adapter_mongoid_test.rb +++ b/elasticsearch-model/test/unit/adapter_mongoid_test.rb @@ -98,7 +98,64 @@ def ids assert_equal @transform.call(model), { index: { _id: "1", data: {} } } end end - end + should "limit the relation to a specific scope" do + relation = mock() + relation.stubs(:no_timeout).returns(relation) + relation.expects(:published).returns(relation) + relation.expects(:each_slice).returns([]) + DummyClassForMongoid.expects(:all).returns(relation) + + DummyClassForMongoid.__send__ :extend, Elasticsearch::Model::Adapter::Mongoid::Importing + DummyClassForMongoid.__find_in_batches(scope: :published) do; end + end + + context "when limit the relation with proc" do + setup do + @query = Proc.new { where(color: "red") } + end + should "query with a specific criteria" do + relation = mock() + relation.stubs(:no_timeout).returns(relation) + relation.expects(:class_exec).returns(relation) + relation.expects(:each_slice).returns([]) + DummyClassForMongoid.expects(:all).returns(relation) + + DummyClassForMongoid.__find_in_batches(query: @query) do; end + end + end + + context "when limit the relation with hash" do + setup do + @query = { color: "red" } + end + should "query with a specific criteria" do + relation = mock() + relation.stubs(:no_timeout).returns(relation) + relation.expects(:where).with(@query).returns(relation) + relation.expects(:each_slice).returns([]) + DummyClassForMongoid.expects(:all).returns(relation) + + DummyClassForMongoid.__find_in_batches(query: @query) do; end + end + end + + should "preprocess the batch if option provided" do + class << DummyClassForMongoid + # Updates/transforms the batch while fetching it from the database + # (eg. with information from an external system) + # + def update_batch(batch) + batch.collect { |b| b.to_s + '!' } + end + end + + DummyClassForMongoid.expects(:__find_in_batches).returns( [:a, :b] ) + + DummyClassForMongoid.__find_in_batches(preprocess: :update_batch) do |batch| + assert_same_elements ["a!", "b!"], batch + end + end + end end end From 19e953aac12fbc5606e75697a29dcb152bb44262 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Mon, 3 Sep 2018 13:32:23 +0200 Subject: [PATCH 397/582] [CI] Update to test against Elasticsearch 6.4 --- Rakefile | 4 ++-- elasticsearch-rails/lib/rails/templates/01-basic.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Rakefile b/Rakefile index c953c5c48..bfca05bfd 100644 --- a/Rakefile +++ b/Rakefile @@ -70,10 +70,10 @@ namespace :test do --env "cluster.routing.allocation.disk.threshold_enabled=false" \ --publish 9250:9200 \ --rm \ - docker.elastic.co/elasticsearch/elasticsearch:6.3.0 + docker.elastic.co/elasticsearch/elasticsearch:6.4.0 COMMAND require 'elasticsearch/extensions/test/cluster' - Elasticsearch::Extensions::Test::Cluster::Cluster.new(version: '6.3.0', + Elasticsearch::Extensions::Test::Cluster::Cluster.new(version: '6.4.0', number_of_nodes: 1).wait_for_green rescue end diff --git a/elasticsearch-rails/lib/rails/templates/01-basic.rb b/elasticsearch-rails/lib/rails/templates/01-basic.rb index e9c378f84..5e9637544 100644 --- a/elasticsearch-rails/lib/rails/templates/01-basic.rb +++ b/elasticsearch-rails/lib/rails/templates/01-basic.rb @@ -37,7 +37,7 @@ --env "cluster.name=elasticsearch-rails" \ --env "cluster.routing.allocation.disk.threshold_enabled=false" \ --rm \ - docker.elastic.co/elasticsearch/elasticsearch-oss:6.3.0 + docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.0 CMD begin From 0143aa75257a8d747ffa1687fb65726b3aae2460 Mon Sep 17 00:00:00 2001 From: Thomas Haratyk <Thomas.haratyk@gmail.com> Date: Tue, 4 Sep 2018 12:38:45 +0100 Subject: [PATCH 398/582] Fix sort order on ActiveRecord >= 5. re issue #546 (#831) --- .../lib/elasticsearch/model/adapters/active_record.rb | 5 ++++- .../test/integration/active_record_basic_test.rb | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb index 23c4267fa..0a39b861b 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb @@ -56,7 +56,10 @@ def order(*args) # Redefine the `to_a` method to the original one # sql_records.instance_exec do - define_singleton_method(:to_a) do + ar_records_method_name = :to_a + ar_records_method_name = :records if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 5 + + define_singleton_method(ar_records_method_name) do if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4 self.load else diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index 02c7b24c3..38cb10301 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -223,8 +223,10 @@ def as_indexed_json(options = {}) if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4 assert_equal 'Testing Coding', response.records.order(title: :desc).first.title + assert_equal 'Testing Coding', response.records.order(title: :desc)[0].title else assert_equal 'Testing Coding', response.records.order('title DESC').first.title + assert_equal 'Testing Coding', response.records.order('title DESC')[0].title end end From aa97dc009c0693b22f37ad38620346780246cdf3 Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Wed, 5 Sep 2018 12:11:40 +0200 Subject: [PATCH 399/582] [MODEL] Use default scope on ActiveRecord model when importing (#827) --- .../integration/active_record_import_test.rb | 77 ++++++++++++++++--- 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/elasticsearch-model/test/integration/active_record_import_test.rb b/elasticsearch-model/test/integration/active_record_import_test.rb index ed8b85c1d..ca6446646 100644 --- a/elasticsearch-model/test/integration/active_record_import_test.rb +++ b/elasticsearch-model/test/integration/active_record_import_test.rb @@ -10,21 +10,22 @@ module Elasticsearch module Model class ActiveRecordImportIntegrationTest < Elasticsearch::Test::IntegrationTestCase - class ::ImportArticle < ActiveRecord::Base - include Elasticsearch::Model + context "ActiveRecord importing" do + setup do + Object.send(:remove_const, :ImportArticle) if defined?(ImportArticle) + class ::ImportArticle < ActiveRecord::Base + include Elasticsearch::Model - scope :popular, -> { where('views >= 50') } + scope :popular, -> { where('views >= 50') } - mapping do - indexes :title, type: 'text' - indexes :views, type: 'integer' - indexes :numeric, type: 'integer' - indexes :created_at, type: 'date' - end - end + mapping do + indexes :title, type: 'text' + indexes :views, type: 'integer' + indexes :numeric, type: 'integer' + indexes :created_at, type: 'date' + end + end - context "ActiveRecord importing" do - setup do ActiveRecord::Schema.define(:version => 1) do create_table :import_articles do |t| t.string :title @@ -110,6 +111,58 @@ class ::ImportArticle < ActiveRecord::Base end end + context "ActiveRecord importing when the model has a default scope" do + + setup do + Object.send(:remove_const, :ImportArticle) if defined?(ImportArticle) + class ::ImportArticle < ActiveRecord::Base + include Elasticsearch::Model + + default_scope { where('views >= 8') } + + mapping do + indexes :title, type: 'text' + indexes :views, type: 'integer' + indexes :numeric, type: 'integer' + indexes :created_at, type: 'date' + end + end + + ActiveRecord::Schema.define(:version => 1) do + create_table :import_articles do |t| + t.string :title + t.integer :views + t.string :numeric # For the sake of invalid data sent to Elasticsearch + t.datetime :created_at, :default => 'NOW()' + end + end + + ImportArticle.delete_all + ImportArticle.__elasticsearch__.delete_index! force: true + ImportArticle.__elasticsearch__.create_index! force: true + ImportArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' + + 10.times { |i| ImportArticle.create! title: "Test #{i}", views: i } + end + + should "import only documents from the default scope" do + assert_equal 2, ImportArticle.count + + assert_equal 0, ImportArticle.import + + ImportArticle.__elasticsearch__.refresh_index! + assert_equal 2, ImportArticle.search('*').results.total + end + + should "import only documents from a specific query combined with the default scope" do + assert_equal 2, ImportArticle.count + + assert_equal 0, ImportArticle.import(query: -> { where("title = 'Test 9'") }) + + ImportArticle.__elasticsearch__.refresh_index! + assert_equal 1, ImportArticle.search('*').results.total + end + end end end end From f852bc588e4d769699d02cd076d307c37e29fc87 Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Wed, 5 Sep 2018 14:32:05 +0200 Subject: [PATCH 400/582] [STORE] [MODEL] Address performance of HashWrapper in Response objects (#825) * [STORE] Lazily wrap response in HashWrapper and provide #raw_response access * [MODEL] Lazily wrap response in HashWrapper and provide #raw_response access * [MODEL] Opimitize response changes * [STORE] Opimitize response changes --- .../lib/elasticsearch/model/response.rb | 21 +++++++++-------- .../lib/elasticsearch/model/response/base.rb | 5 ++-- .../test/unit/response_results_test.rb | 3 +++ .../repository/response/results.rb | 17 +++++++------- .../spec/repository/response/results_spec.rb | 23 +++++++++++++++++++ 5 files changed, 49 insertions(+), 20 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response.rb b/elasticsearch-model/lib/elasticsearch/model/response.rb index d15c24a04..9726ae2c5 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response.rb @@ -10,8 +10,7 @@ module Response # Implements Enumerable and forwards its methods to the {#results} object. # class Response - attr_reader :klass, :search, :response, - :took, :timed_out, :shards + attr_reader :klass, :search include Enumerable @@ -27,9 +26,7 @@ def initialize(klass, search, options={}) # @return [Hash] # def response - @response ||= begin - HashWrapper.new(search.execute!) - end + @response ||= HashWrapper.new(search.execute!) end # Returns the collection of "hits" from Elasticsearch @@ -51,31 +48,35 @@ def records(options = {}) # Returns the "took" time # def took - response['took'] + raw_response['took'] end # Returns whether the response timed out # def timed_out - response['timed_out'] + raw_response['timed_out'] end # Returns the statistics on shards # def shards - HashWrapper.new(response['_shards']) + @shards ||= HashWrapper.new(raw_response['_shards']) end # Returns a Hashie::Mash of the aggregations # def aggregations - Aggregations.new(response['aggregations']) + @aggregations ||= Aggregations.new(raw_response['aggregations']) end # Returns a Hashie::Mash of the suggestions # def suggestions - Suggestions.new(response['suggest']) + @suggestions ||= Suggestions.new(raw_response['suggest']) + end + + def raw_response + @raw_response ||= search.execute! end end end diff --git a/elasticsearch-model/lib/elasticsearch/model/response/base.rb b/elasticsearch-model/lib/elasticsearch/model/response/base.rb index 3bb8005b6..827c52e35 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/base.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/base.rb @@ -4,7 +4,7 @@ module Response # Common funtionality for classes in the {Elasticsearch::Model::Response} module # module Base - attr_reader :klass, :response + attr_reader :klass, :response, :raw_response # @param klass [Class] The name of the model class # @param response [Hash] The full response returned from Elasticsearch client @@ -12,7 +12,8 @@ module Base # def initialize(klass, response, options={}) @klass = klass - @response = response + @raw_response = response + @response = response end # @abstract Implement this method in specific class diff --git a/elasticsearch-model/test/unit/response_results_test.rb b/elasticsearch-model/test/unit/response_results_test.rb index e97539ecd..900094bf5 100644 --- a/elasticsearch-model/test/unit/response_results_test.rb +++ b/elasticsearch-model/test/unit/response_results_test.rb @@ -27,5 +27,8 @@ def self.document_type; 'bar'; end assert_equal 'bar', @results.first.foo end + should "provide access to the raw response" do + assert_equal RESPONSE, @response.raw_response + end end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb index 169ecd42e..9de0059f3 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb @@ -11,6 +11,7 @@ class Results include Enumerable attr_reader :repository + attr_reader :raw_response # The key for accessing the results in an Elasticsearch query response. # @@ -30,8 +31,8 @@ class Results # def initialize(repository, response, options={}) @repository = repository - @response = Elasticsearch::Model::HashWrapper.new(response) - @options = options + @raw_response = response + @options = options end def method_missing(method_name, *arguments, &block) @@ -45,25 +46,25 @@ def respond_to?(method_name, include_private = false) # The number of total hits for a query # def total - response[HITS][TOTAL] + raw_response[HITS][TOTAL] end # The maximum score for a query # def max_score - response[HITS][MAX_SCORE] + raw_response[HITS][MAX_SCORE] end # Yields [object, hit] pairs to the block # def each_with_hit(&block) - results.zip(response[HITS][HITS]).each(&block) + results.zip(raw_response[HITS][HITS]).each(&block) end # Yields [object, hit] pairs and returns the result # def map_with_hit(&block) - results.zip(response[HITS][HITS]).map(&block) + results.zip(raw_response[HITS][HITS]).map(&block) end # Return the collection of domain objects @@ -76,7 +77,7 @@ def map_with_hit(&block) # @return [Array] # def results - @results ||= response[HITS][HITS].map do |document| + @results ||= raw_response[HITS][HITS].map do |document| repository.deserialize(document.to_hash) end end @@ -93,7 +94,7 @@ def results # @return [Elasticsearch::Model::HashWrapper] # def response - @response + @response ||= Elasticsearch::Model::HashWrapper.new(raw_response) end end end diff --git a/elasticsearch-persistence/spec/repository/response/results_spec.rb b/elasticsearch-persistence/spec/repository/response/results_spec.rb index 66a2cc6c0..f41f4b81d 100644 --- a/elasticsearch-persistence/spec/repository/response/results_spec.rb +++ b/elasticsearch-persistence/spec/repository/response/results_spec.rb @@ -66,6 +66,22 @@ def deserialize(document) it 'wraps the response in a HashWrapper' do expect(results.response._shards.total).to eq(5) end + + context 'when the response method is not called' do + + it 'does not create an instance of HashWrapper' do + expect(Elasticsearch::Model::HashWrapper).not_to receive(:new) + results + end + end + + context 'when the response method is called' do + + it 'does create an instance of HashWrapper' do + expect(Elasticsearch::Model::HashWrapper).to receive(:new) + results.response + end + end end describe '#total' do @@ -102,4 +118,11 @@ def deserialize(document) expect(results.map_with_hit { |pair| pair[0] }).to eq(['Object', 'Object']) end end + + describe '#raw_response' do + + it 'returns the raw response from Elasticsearch' do + expect(results.raw_response).to eq(response) + end + end end From 08daac5d9bec4b57d528ef94c8bf7c1b2c18d6a4 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Wed, 5 Sep 2018 14:46:47 +0200 Subject: [PATCH 401/582] [MODEL] Inherit from HashWrapper for disabling warnings --- .../lib/elasticsearch/model/response/aggregations.rb | 2 +- .../lib/elasticsearch/model/response/suggestions.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb b/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb index 43dfd23e8..c2dd23c67 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb @@ -2,7 +2,7 @@ module Elasticsearch module Model module Response - class Aggregations < Hashie::Mash + class Aggregations < HashWrapper disable_warnings if respond_to?(:disable_warnings) def initialize(attributes={}) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb b/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb index 1b1cc6598..b2809bb12 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb @@ -2,7 +2,7 @@ module Elasticsearch module Model module Response - class Suggestions < Hashie::Mash + class Suggestions < HashWrapper disable_warnings if respond_to?(:disable_warnings) def terms From 52f649bd40d112fb4576acdbd58940099c03d008 Mon Sep 17 00:00:00 2001 From: Hideki Igarashi <hideki.develop@gmail.com> Date: Wed, 5 Sep 2018 22:03:52 +0900 Subject: [PATCH 402/582] [MODEL] Fix import method to pass index name on refresh (#692) Fixes #506 --- .../lib/elasticsearch/model/importing.rb | 2 +- .../test/unit/importing_test.rb | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/importing.rb b/elasticsearch-model/lib/elasticsearch/model/importing.rb index 7c42545d2..764413acf 100644 --- a/elasticsearch-model/lib/elasticsearch/model/importing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/importing.rb @@ -130,7 +130,7 @@ def import(options={}, &block) errors += response['items'].select { |k, v| k.values.first['error'] } end - self.refresh_index! if refresh + self.refresh_index! index: target_index if refresh case return_value when 'errors' diff --git a/elasticsearch-model/test/unit/importing_test.rb b/elasticsearch-model/test/unit/importing_test.rb index 6f739acec..2f43ed4a6 100644 --- a/elasticsearch-model/test/unit/importing_test.rb +++ b/elasticsearch-model/test/unit/importing_test.rb @@ -146,6 +146,27 @@ def importing_mixin end end + context "with the refresh option" do + should "refresh the index" do + DummyImportingModel.expects(:__find_in_batches).with do |options| + assert_equal 'bar', options[:foo] + assert_nil options[:refresh] + true + end + + DummyImportingModel.expects(:refresh_index!).with do |options| + assert_equal 'foo', options[:index] + true + end + + DummyImportingModel.expects(:index_name).returns('foo') + DummyImportingModel.expects(:document_type).returns('foo') + DummyImportingModel.stubs(:index_exists?).returns(true) + + DummyImportingModel.import refresh: true, foo: 'bar' + end + end + should "allow passing a different index / type" do Elasticsearch::Model::Adapter.expects(:from_class) .with(DummyImportingModel) From 3afbded9ff2be3a8fbb179ce7fa8c0892ae8385d Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Thu, 6 Sep 2018 17:21:55 +0200 Subject: [PATCH 403/582] Update compatibility matrix for 6.0 release --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 283f18e02..ba6ff4814 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ is compatible with the Elasticsearch `master` branch, therefore, with the next m | 0.1 | → | 1.x | | 2.x | → | 2.x | | 5.x | → | 5.x | +| 6.x | → | 6.x | | master | → | master | ## Installation From b72da17ba21104f9aeb401a2a900e18c302a0ed2 Mon Sep 17 00:00:00 2001 From: Tim Bleck <mail@tim-bleck.de> Date: Fri, 7 Sep 2018 13:15:55 +0200 Subject: [PATCH 404/582] [MODEL] Fix import when preprocess returns empty collection (#720) Fixes #719 --- .../model/adapters/active_record.rb | 3 +- .../test/unit/adapter_active_record_test.rb | 37 ++++++++++++++----- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb index 0a39b861b..ae1b38f56 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb @@ -106,7 +106,8 @@ def __find_in_batches(options={}, &block) scope = scope.instance_exec(&query) if query scope.find_in_batches(options) do |batch| - yield (preprocess ? self.__send__(preprocess, batch) : batch) + batch = self.__send__(preprocess, batch) if preprocess + yield(batch) if batch.present? end end diff --git a/elasticsearch-model/test/unit/adapter_active_record_test.rb b/elasticsearch-model/test/unit/adapter_active_record_test.rb index 335e3bd10..152526e47 100644 --- a/elasticsearch-model/test/unit/adapter_active_record_test.rb +++ b/elasticsearch-model/test/unit/adapter_active_record_test.rb @@ -121,20 +121,37 @@ def ids DummyClassForActiveRecord.__find_in_batches(query: -> { where(color: "red") }) do; end end - should "preprocess the batch if option provided" do - class << DummyClassForActiveRecord - # Updates/transforms the batch while fetching it from the database - # (eg. with information from an external system) - # - def update_batch(batch) - batch.collect { |b| b.to_s + '!' } + context "when preprocessing batches" do + setup do + class << DummyClassForActiveRecord + def find_in_batches(options = {}, &block) + yield [:a, :b] + end + end + end + + should "preprocess the batch if option provided" do + class << DummyClassForActiveRecord + def update_batch(batch) + batch.collect { |b| b.to_s + '!' } + end + end + + DummyClassForActiveRecord.__find_in_batches(preprocess: :update_batch) do |batch| + assert_same_elements ["a!", "b!"], batch end end - DummyClassForActiveRecord.expects(:__find_in_batches).returns( [:a, :b] ) + should "skip batch if preprocess option provided and returns empty collection" do + class << DummyClassForActiveRecord + def update_batch(batch) + [] + end + end - DummyClassForActiveRecord.__find_in_batches(preprocess: :update_batch) do |batch| - assert_same_elements ["a!", "b!"], batch + DummyClassForActiveRecord.__find_in_batches(preprocess: :update_batch) do |batch| + flunk("block should not have been called on empty batch") + end end end From 3a50717f8287202aa7c7b376c7cfd6d1f7d1bac2 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Fri, 7 Sep 2018 13:27:57 +0200 Subject: [PATCH 405/582] [MODEL] Add test for not importing when ActiveRecord query is empty --- .../integration/active_record_import_test.rb | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/elasticsearch-model/test/integration/active_record_import_test.rb b/elasticsearch-model/test/integration/active_record_import_test.rb index ca6446646..1e9d9957e 100644 --- a/elasticsearch-model/test/integration/active_record_import_test.rb +++ b/elasticsearch-model/test/integration/active_record_import_test.rb @@ -163,6 +163,36 @@ class ::ImportArticle < ActiveRecord::Base assert_equal 1, ImportArticle.search('*').results.total end end + + context 'ActiveRecord importing when the batch is empty' do + + setup do + Object.send(:remove_const, :ImportArticle) if defined?(ImportArticle) + class ::ImportArticle < ActiveRecord::Base + include Elasticsearch::Model + mapping { indexes :title, type: 'text' } + end + + ActiveRecord::Schema.define(:version => 1) do + create_table :import_articles do |t| + t.string :title + end + end + + ImportArticle.delete_all + ImportArticle.__elasticsearch__.delete_index! force: true + ImportArticle.__elasticsearch__.create_index! force: true + ImportArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' + end + + should 'not make any requests to create documents to Elasticsearch' do + assert_equal 0, ImportArticle.count + assert_equal 0, ImportArticle.import + + ImportArticle.__elasticsearch__.refresh_index! + assert_equal 0, ImportArticle.search('*').results.total + end + end end end end From 8a77b8b166409e243cf017856bf4f6bc151c7f49 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Fri, 7 Sep 2018 14:20:56 +0200 Subject: [PATCH 406/582] [MODEL] Validate that #first called on response records equals #[] with 0 --- elasticsearch-model/test/integration/active_record_basic_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index 38cb10301..90abeb47a 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -111,6 +111,7 @@ def as_indexed_json(options = {}) response = Article.search query: { match: { title: 'code' }}, sort: { clicks: :desc } assert_equal response.records[0].clicks, 3 + assert_equal response.records[0], response.records.first assert_equal response.records[1].clicks, 2 response.records.each_with_hit do |r, h| From 78bb69c96c106b88b0b10d6300acbcd479ef04c3 Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Tue, 11 Sep 2018 16:28:14 +0200 Subject: [PATCH 407/582] [MODEL] Port basic response tests to rspec (#833) --- elasticsearch-model/Gemfile | 5 + elasticsearch-model/Rakefile | 9 +- elasticsearch-model/gemfiles/3.0.gemfile | 5 + elasticsearch-model/gemfiles/4.0.gemfile | 5 + elasticsearch-model/gemfiles/5.0.gemfile | 5 + .../model/response/pagination.rb | 194 +------- .../model/response/pagination/kaminari.rb | 109 +++++ .../response/pagination/will_paginate.rb | 95 ++++ .../model/response/aggregations_spec.rb | 66 +++ .../elasticsearch/model/response/base_spec.rb | 91 ++++ .../response/pagination/kaminari_spec.rb | 411 +++++++++++++++++ .../response/pagination/will_paginate_spec.rb | 263 +++++++++++ .../model/response/records_spec.rb | 118 +++++ .../model/response/response_spec.rb | 131 ++++++ .../model/response/result_spec.rb | 114 +++++ .../model/response/results_spec.rb | 56 +++ elasticsearch-model/spec/spec_helper.rb | 10 + .../test/unit/response_aggregations_test.rb | 46 -- .../test/unit/response_base_test.rb | 40 -- .../unit/response_pagination_kaminari_test.rb | 433 ------------------ .../response_pagination_will_paginate_test.rb | 398 ---------------- .../test/unit/response_records_test.rb | 91 ---- .../test/unit/response_result_test.rb | 90 ---- .../test/unit/response_results_test.rb | 34 -- .../test/unit/response_test.rb | 104 ----- 25 files changed, 1492 insertions(+), 1431 deletions(-) create mode 100644 elasticsearch-model/lib/elasticsearch/model/response/pagination/kaminari.rb create mode 100644 elasticsearch-model/lib/elasticsearch/model/response/pagination/will_paginate.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb create mode 100644 elasticsearch-model/spec/spec_helper.rb delete mode 100644 elasticsearch-model/test/unit/response_aggregations_test.rb delete mode 100644 elasticsearch-model/test/unit/response_base_test.rb delete mode 100644 elasticsearch-model/test/unit/response_pagination_kaminari_test.rb delete mode 100644 elasticsearch-model/test/unit/response_pagination_will_paginate_test.rb delete mode 100644 elasticsearch-model/test/unit/response_records_test.rb delete mode 100644 elasticsearch-model/test/unit/response_result_test.rb delete mode 100644 elasticsearch-model/test/unit/response_results_test.rb delete mode 100644 elasticsearch-model/test/unit/response_test.rb diff --git a/elasticsearch-model/Gemfile b/elasticsearch-model/Gemfile index a54f5084e..ee48fe61b 100644 --- a/elasticsearch-model/Gemfile +++ b/elasticsearch-model/Gemfile @@ -2,3 +2,8 @@ source 'https://rubygems.org' # Specify your gem's dependencies in elasticsearch-model.gemspec gemspec + +group :development, :testing do + gem 'rspec' + gem 'pry-nav' +end \ No newline at end of file diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index f80c46b23..bda261913 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -17,6 +17,7 @@ end require 'rake/testtask' namespace :test do + Rake::TestTask.new(:run_unit) do |test| test.libs << 'lib' << 'test' test.test_files = FileList["test/unit/**/*_test.rb"] @@ -33,9 +34,11 @@ namespace :test do desc "Run unit tests against ActiveModel 3, 4 and 5" task :unit do - sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/3.0.gemfile', __FILE__)}' bundle exec rake test:run_unit" - sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/4.0.gemfile', __FILE__)}' bundle exec rake test:run_unit" - sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/5.0.gemfile', __FILE__)}' bundle exec rake test:run_unit" + ['3.0.gemfile', '4.0.gemfile', '5.0.gemfile'].each do |gemfile| + ['bundle exec rake test:run_unit', 'bundle exec rspec'].each do |cmd| + sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/'+gemfile, __FILE__)}' #{cmd}" + end + end end desc "Run integration tests against latest stable ActiveModel (5)" diff --git a/elasticsearch-model/gemfiles/3.0.gemfile b/elasticsearch-model/gemfiles/3.0.gemfile index 97ea5f60a..b0141ef48 100644 --- a/elasticsearch-model/gemfiles/3.0.gemfile +++ b/elasticsearch-model/gemfiles/3.0.gemfile @@ -11,3 +11,8 @@ gem 'activemodel', '>= 3.0' gem 'activerecord', '~> 3.2' gem 'mongoid', '>= 3.0' gem 'sqlite3' unless defined?(JRUBY_VERSION) + +group :development, :testing do + gem 'rspec' + gem 'pry-nav' +end \ No newline at end of file diff --git a/elasticsearch-model/gemfiles/4.0.gemfile b/elasticsearch-model/gemfiles/4.0.gemfile index fa0cc73e9..eea429e12 100644 --- a/elasticsearch-model/gemfiles/4.0.gemfile +++ b/elasticsearch-model/gemfiles/4.0.gemfile @@ -10,3 +10,8 @@ gemspec path: '../' gem 'activemodel', '~> 4' gem 'activerecord', '~> 4' gem 'sqlite3' unless defined?(JRUBY_VERSION) + +group :development, :testing do + gem 'rspec' + gem 'pry-nav' +end \ No newline at end of file diff --git a/elasticsearch-model/gemfiles/5.0.gemfile b/elasticsearch-model/gemfiles/5.0.gemfile index 77e10c7bb..aa8e77655 100644 --- a/elasticsearch-model/gemfiles/5.0.gemfile +++ b/elasticsearch-model/gemfiles/5.0.gemfile @@ -10,3 +10,8 @@ gemspec path: '../' gem 'activemodel', '~> 5' gem 'activerecord', '~> 5' gem 'sqlite3' unless defined?(JRUBY_VERSION) + +group :development, :testing do + gem 'rspec' + gem 'pry-nav' +end diff --git a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb index c8e74b793..82f1301a5 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb @@ -1,192 +1,2 @@ -module Elasticsearch - module Model - module Response - - # Pagination for search results/records - # - module Pagination - # Allow models to be paginated with the "kaminari" gem [https://github.com/amatsuda/kaminari] - # - module Kaminari - def self.included(base) - # Include the Kaminari configuration and paging method in response - # - base.__send__ :include, ::Kaminari::ConfigurationMethods::ClassMethods - base.__send__ :include, ::Kaminari::PageScopeMethods - - # Include the Kaminari paging methods in results and records - # - Elasticsearch::Model::Response::Results.__send__ :include, ::Kaminari::ConfigurationMethods::ClassMethods - Elasticsearch::Model::Response::Results.__send__ :include, ::Kaminari::PageScopeMethods - Elasticsearch::Model::Response::Records.__send__ :include, ::Kaminari::PageScopeMethods - - Elasticsearch::Model::Response::Results.__send__ :delegate, :limit_value, :offset_value, :total_count, :max_pages, to: :response - Elasticsearch::Model::Response::Records.__send__ :delegate, :limit_value, :offset_value, :total_count, :max_pages, to: :response - - base.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - # Define the `page` Kaminari method - # - def #{::Kaminari.config.page_method_name}(num=nil) - @results = nil - @records = nil - @response = nil - @page = [num.to_i, 1].max - @per_page ||= __default_per_page - - self.search.definition.update size: @per_page, - from: @per_page * (@page - 1) - - self - end - RUBY - end - - # Returns the current "limit" (`size`) value - # - def limit_value - case - when search.definition[:size] - search.definition[:size] - else - __default_per_page - end - end - - # Returns the current "offset" (`from`) value - # - def offset_value - case - when search.definition[:from] - search.definition[:from] - else - 0 - end - end - - # Set the "limit" (`size`) value - # - def limit(value) - return self if value.to_i <= 0 - @results = nil - @records = nil - @response = nil - @per_page = value.to_i - - search.definition.update :size => @per_page - search.definition.update :from => @per_page * (@page - 1) if @page - self - end - - # Set the "offset" (`from`) value - # - def offset(value) - return self if value.to_i < 0 - @results = nil - @records = nil - @response = nil - @page = nil - search.definition.update :from => value.to_i - self - end - - # Returns the total number of results - # - def total_count - results.total - end - - # Returns the models's `per_page` value or the default - # - # @api private - # - def __default_per_page - klass.respond_to?(:default_per_page) && klass.default_per_page || ::Kaminari.config.default_per_page - end - end - - # Allow models to be paginated with the "will_paginate" gem [https://github.com/mislav/will_paginate] - # - module WillPaginate - def self.included(base) - base.__send__ :include, ::WillPaginate::CollectionMethods - - # Include the paging methods in results and records - # - methods = [:current_page, :offset, :length, :per_page, :total_entries, :total_pages, :previous_page, :next_page, :out_of_bounds?] - Elasticsearch::Model::Response::Results.__send__ :delegate, *methods, to: :response - Elasticsearch::Model::Response::Records.__send__ :delegate, *methods, to: :response - end - - def offset - (current_page - 1) * per_page - end - - def length - search.definition[:size] - end - - # Main pagination method - # - # @example - # - # Article.search('foo').paginate(page: 1, per_page: 30) - # - def paginate(options) - param_name = options[:param_name] || :page - page = [options[param_name].to_i, 1].max - per_page = (options[:per_page] || __default_per_page).to_i - - search.definition.update size: per_page, - from: (page - 1) * per_page - self - end - - # Return the current page - # - def current_page - search.definition[:from] / per_page + 1 if search.definition[:from] && per_page - end - - # Pagination method - # - # @example - # - # Article.search('foo').page(2) - # - def page(num) - paginate(page: num, per_page: per_page) # shorthand - end - - # Return or set the "size" value - # - # @example - # - # Article.search('foo').per_page(15).page(2) - # - def per_page(num = nil) - if num.nil? - search.definition[:size] - else - paginate(page: current_page, per_page: num) # shorthand - end - end - - # Returns the total number of results - # - def total_entries - results.total - end - - # Returns the models's `per_page` value or the default - # - # @api private - # - def __default_per_page - klass.respond_to?(:per_page) && klass.per_page || ::WillPaginate.per_page - end - end - end - - end - end -end +require 'elasticsearch/model/response/pagination/kaminari' +require 'elasticsearch/model/response/pagination/will_paginate' diff --git a/elasticsearch-model/lib/elasticsearch/model/response/pagination/kaminari.rb b/elasticsearch-model/lib/elasticsearch/model/response/pagination/kaminari.rb new file mode 100644 index 000000000..5b1acfd9b --- /dev/null +++ b/elasticsearch-model/lib/elasticsearch/model/response/pagination/kaminari.rb @@ -0,0 +1,109 @@ +module Elasticsearch + module Model + module Response + + # Pagination for search results/records + # + module Pagination + # Allow models to be paginated with the "kaminari" gem [https://github.com/amatsuda/kaminari] + # + module Kaminari + def self.included(base) + # Include the Kaminari configuration and paging method in response + # + base.__send__ :include, ::Kaminari::ConfigurationMethods::ClassMethods + base.__send__ :include, ::Kaminari::PageScopeMethods + + # Include the Kaminari paging methods in results and records + # + Elasticsearch::Model::Response::Results.__send__ :include, ::Kaminari::ConfigurationMethods::ClassMethods + Elasticsearch::Model::Response::Results.__send__ :include, ::Kaminari::PageScopeMethods + Elasticsearch::Model::Response::Records.__send__ :include, ::Kaminari::PageScopeMethods + + Elasticsearch::Model::Response::Results.__send__ :delegate, :limit_value, :offset_value, :total_count, :max_pages, to: :response + Elasticsearch::Model::Response::Records.__send__ :delegate, :limit_value, :offset_value, :total_count, :max_pages, to: :response + + base.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + # Define the `page` Kaminari method + # + def #{::Kaminari.config.page_method_name}(num=nil) + @results = nil + @records = nil + @response = nil + @page = [num.to_i, 1].max + @per_page ||= __default_per_page + + self.search.definition.update size: @per_page, + from: @per_page * (@page - 1) + + self + end + RUBY + end + + # Returns the current "limit" (`size`) value + # + def limit_value + case + when search.definition[:size] + search.definition[:size] + else + __default_per_page + end + end + + # Returns the current "offset" (`from`) value + # + def offset_value + case + when search.definition[:from] + search.definition[:from] + else + 0 + end + end + + # Set the "limit" (`size`) value + # + def limit(value) + return self if value.to_i <= 0 + @results = nil + @records = nil + @response = nil + @per_page = value.to_i + + search.definition.update :size => @per_page + search.definition.update :from => @per_page * (@page - 1) if @page + self + end + + # Set the "offset" (`from`) value + # + def offset(value) + return self if value.to_i < 0 + @results = nil + @records = nil + @response = nil + @page = nil + search.definition.update :from => value.to_i + self + end + + # Returns the total number of results + # + def total_count + results.total + end + + # Returns the models's `per_page` value or the default + # + # @api private + # + def __default_per_page + klass.respond_to?(:default_per_page) && klass.default_per_page || ::Kaminari.config.default_per_page + end + end + end + end + end +end diff --git a/elasticsearch-model/lib/elasticsearch/model/response/pagination/will_paginate.rb b/elasticsearch-model/lib/elasticsearch/model/response/pagination/will_paginate.rb new file mode 100644 index 000000000..7cfc36d0c --- /dev/null +++ b/elasticsearch-model/lib/elasticsearch/model/response/pagination/will_paginate.rb @@ -0,0 +1,95 @@ +module Elasticsearch + module Model + module Response + + # Pagination for search results/records + # + module Pagination + + + # Allow models to be paginated with the "will_paginate" gem [https://github.com/mislav/will_paginate] + # + module WillPaginate + def self.included(base) + base.__send__ :include, ::WillPaginate::CollectionMethods + + # Include the paging methods in results and records + # + methods = [:current_page, :offset, :length, :per_page, :total_entries, :total_pages, :previous_page, :next_page, :out_of_bounds?] + Elasticsearch::Model::Response::Results.__send__ :delegate, *methods, to: :response + Elasticsearch::Model::Response::Records.__send__ :delegate, *methods, to: :response + end + + def offset + (current_page - 1) * per_page + end + + def length + search.definition[:size] + end + + # Main pagination method + # + # @example + # + # Article.search('foo').paginate(page: 1, per_page: 30) + # + def paginate(options) + param_name = options[:param_name] || :page + page = [options[param_name].to_i, 1].max + per_page = (options[:per_page] || __default_per_page).to_i + + search.definition.update size: per_page, + from: (page - 1) * per_page + self + end + + # Return the current page + # + def current_page + search.definition[:from] / per_page + 1 if search.definition[:from] && per_page + end + + # Pagination method + # + # @example + # + # Article.search('foo').page(2) + # + def page(num) + paginate(page: num, per_page: per_page) # shorthand + end + + # Return or set the "size" value + # + # @example + # + # Article.search('foo').per_page(15).page(2) + # + def per_page(num = nil) + if num.nil? + search.definition[:size] + else + paginate(page: current_page, per_page: num) # shorthand + end + end + + # Returns the total number of results + # + def total_entries + results.total + end + + # Returns the models's `per_page` value or the default + # + # @api private + # + def __default_per_page + klass.respond_to?(:per_page) && klass.per_page || ::WillPaginate.per_page + end + end + end + + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb new file mode 100644 index 000000000..9d20730da --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Response::Aggregations do + + before(:all) do + class OriginClass + def self.index_name; 'foo'; end + def self.document_type; 'bar'; end + end + end + + after(:all) do + Object.send(:remove_const, :OriginClass) if defined?(OriginClass) + end + + let(:response_document) do + { + 'aggregations' => { + 'foo' => {'bar' => 10 }, + 'price' => { 'doc_count' => 123, + 'min' => { 'value' => 1.0}, + 'max' => { 'value' => 99 } + } + } + } + end + + let(:search) do + Elasticsearch::Model::Searching::SearchRequest.new(OriginClass, '*').tap do |request| + allow(request).to receive(:execute!).and_return(response_document) + end + end + + let(:aggregations) do + Elasticsearch::Model::Response::Response.new(OriginClass, search).aggregations + end + + describe 'method delegation' do + + it 'delegates methods to the response document' do + expect(aggregations.foo).to be_a(Hashie::Mash) + expect(aggregations.foo.bar).to be(10) + end + end + + describe '#doc_count' do + + it 'returns the doc count value from the response document' do + expect(aggregations.price.doc_count).to eq(123) + end + end + + describe '#min' do + + it 'returns the min value from the response document' do + expect(aggregations.price.min.value).to eq(1) + end + end + + describe '#max' do + + it 'returns the max value from the response document' do + expect(aggregations.price.max.value).to eq(99) + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb new file mode 100644 index 000000000..eae7a8a67 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb @@ -0,0 +1,91 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Response::Base do + + before(:all) do + class DummyBaseClass + include Elasticsearch::Model::Response::Base + end + + class OriginClass + def self.index_name; 'foo'; end + def self.document_type; 'bar'; end + end + end + + after(:all) do + Object.send(:remove_const, :DummyBaseClass) if defined?(DummyBaseClass) + Object.send(:remove_const, :OriginClass) if defined?(OriginClass) + end + + let(:response_document) do + { 'hits' => { 'total' => 123, 'max_score' => 456, 'hits' => [] } } + end + + let(:search) do + Elasticsearch::Model::Searching::SearchRequest.new(OriginClass, '*').tap do |request| + allow(request).to receive(:execute!).and_return(response_document) + end + end + + let(:response) do + Elasticsearch::Model::Response::Response.new(OriginClass, search) + end + + let(:response_base) do + DummyBaseClass.new(OriginClass, response) + end + + describe '#klass' do + + it 'returns the class' do + expect(response.klass).to be(OriginClass) + end + end + + describe '#response' do + + it 'returns the response object' do + expect(response_base.response).to eq(response) + end + end + + describe 'response document' do + + it 'returns the response document' do + expect(response_base.response.response).to eq(response_document) + end + end + + describe '#total' do + + it 'returns the total' do + expect(response_base.total).to eq(123) + end + end + + describe '#max_score' do + + it 'returns the total' do + expect(response_base.max_score).to eq(456) + end + end + + describe '#results' do + + it 'raises a NotImplemented error' do + expect { + response_base.results + }.to raise_exception(Elasticsearch::Model::NotImplemented) + end + end + + describe '#records' do + + it 'raises a NotImplemented error' do + expect { + response_base.records + }.to raise_exception(Elasticsearch::Model::NotImplemented) + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb new file mode 100644 index 000000000..6198fafac --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb @@ -0,0 +1,411 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Response::Response do + + before(:all) do + class ModelClass + include ::Kaminari::ConfigurationMethods + def self.index_name; 'foo'; end + def self.document_type; 'bar'; end + end + end + + after(:all) do + Object.send(:remove_const, :ModelClass) if defined?(ModelClass) + end + + let(:response_document) do + { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, + 'hits' => { 'total' => 100, 'hits' => (1..100).to_a.map { |i| { _id: i } } } } + end + + let(:search) do + Elasticsearch::Model::Searching::SearchRequest.new(model, '*') + end + + let(:response) do + allow(model).to receive(:client).and_return(client) + Elasticsearch::Model::Response::Response.new(model, search, response_document).tap do |resp| + allow(resp).to receive(:client).and_return(client) + end + + end + + let(:client) do + double('client') + end + + shared_examples_for 'a search request that can be paginated' do + + describe '#page' do + + it 'does not set an initial from and size on the search definition' do + expect(response.search.definition[:from]).to be(nil) + expect(response.search.definition[:size]).to be(nil) + end + + context 'when page is called once' do + + let(:search_request) do + { index: index_field, from: 25, size: 25, q: '*', type: type_field} + end + + before do + expect(client).to receive(:search).with(search_request).and_return(response_document) + response.page(2).to_a + end + + it 'advances the from/size in the search request' do + expect(response.search.definition[:from]).to be(25) + expect(response.search.definition[:size]).to be(25) + end + end + + context 'when page is called more than once' do + + let(:search_request_one) do + { index: index_field, from: 25, size: 25, q: '*', type: type_field} + end + + let(:search_request_two) do + { index: index_field, from: 75, size: 25, q: '*', type: type_field} + end + + before do + expect(client).to receive(:search).with(search_request_one).and_return(response_document) + response.page(2).to_a + expect(client).to receive(:search).with(search_request_two).and_return(response_document) + response.page(4).to_a + end + + it 'advances the from/size in the search request' do + expect(response.search.definition[:from]).to be(75) + expect(response.search.definition[:size]).to be(25) + end + end + + context 'when limit is also set' do + + before do + response.records + response.results + end + + context 'when page is called before limit' do + + before do + response.page(3).limit(35) + end + + it 'sets the correct values' do + expect(response.search.definition[:size]).to eq(35) + expect(response.search.definition[:from]).to eq(70) + end + + it 'resets the instance variables' do + expect(response.instance_variable_get(:@response)).to be(nil) + expect(response.instance_variable_get(:@records)).to be(nil) + expect(response.instance_variable_get(:@results)).to be(nil) + end + end + + context 'when limit is called before page' do + + before do + response.limit(35).page(3) + end + + it 'sets the correct values' do + expect(response.search.definition[:size]).to eq(35) + expect(response.search.definition[:from]).to eq(70) + end + + it 'resets the instance variables' do + expect(response.instance_variable_get(:@response)).to be(nil) + expect(response.instance_variable_get(:@records)).to be(nil) + expect(response.instance_variable_get(:@results)).to be(nil) + end + end + end + end + + describe '#limit_value' do + + context 'when there is no default set' do + + it 'uses the limit value from the Kaminari configuration' do + expect(response.limit_value).to eq(Kaminari.config.default_per_page) + end + end + + context 'when there is a limit in the search definition' do + + let(:search) do + Elasticsearch::Model::Searching::SearchRequest.new(model, '*', size: 10) + end + + it 'gets the limit from the search definition' do + expect(response.limit_value).to eq(10) + end + end + + context 'when there is a limit in the search body' do + + let(:search) do + Elasticsearch::Model::Searching::SearchRequest.new(model, { query: { match_all: {} }, size: 999 }) + end + + it 'does not use the limit' do + expect(response.limit_value).to be(Kaminari.config.default_per_page) + end + end + end + + describe '#offset_value' do + + context 'when there is no default set' do + + it 'uses an offset of 0' do + expect(response.offset_value).to eq(0) + end + end + + context 'when there is an offset in the search definition' do + + let(:search) do + Elasticsearch::Model::Searching::SearchRequest.new(model, '*', from: 50) + end + + it 'gets the limit from the search definition' do + expect(response.offset_value).to eq(50) + end + end + + context 'when there is an offset in the search body' do + + let(:search) do + Elasticsearch::Model::Searching::SearchRequest.new(model, { query: { match_all: {} }, from: 333 }) + end + + it 'does not use the offset' do + expect(response.offset_value).to be(0) + end + end + end + + describe '#limit' do + + context 'when a limit is set' do + + before do + response.records + response.results + response.limit(35) + end + + it 'sets the limit on the search defintiion' do + expect(response.search.definition[:size]).to eq(35) + end + + it 'resets the instance variables' do + expect(response.instance_variable_get(:@response)).to be(nil) + expect(response.instance_variable_get(:@records)).to be(nil) + expect(response.instance_variable_get(:@results)).to be(nil) + end + + context 'when the limit is provided as a string' do + + before do + response.limit('35') + end + + it 'coerces the string to an integer' do + expect(response.search.definition[:size]).to eq(35) + end + end + + context 'when the limit is an invalid type' do + + before do + response.limit('asdf') + end + + it 'does not apply the setting' do + expect(response.search.definition[:size]).to eq(35) + end + end + end + end + + describe '#offset' do + + context 'when an offset is set' do + + before do + response.records + response.results + response.offset(15) + end + + it 'sets the limit on the search defintiion' do + expect(response.search.definition[:from]).to eq(15) + end + + it 'resets the instance variables' do + expect(response.instance_variable_get(:@response)).to be(nil) + expect(response.instance_variable_get(:@records)).to be(nil) + expect(response.instance_variable_get(:@results)).to be(nil) + end + + context 'when the offset is provided as a string' do + + before do + response.offset('15') + end + + it 'coerces the string to an integer' do + expect(response.search.definition[:from]).to eq(15) + end + end + + context 'when the offset is an invalid type' do + + before do + response.offset('asdf') + end + + it 'does not apply the setting' do + expect(response.search.definition[:from]).to eq(0) + end + end + end + end + + describe '#total' do + + before do + allow(response.results).to receive(:total).and_return(100) + end + + it 'returns the total number of hits' do + expect(response.total_count).to eq(100) + end + end + + context 'results' do + + before do + allow(search).to receive(:execute!).and_return(response_document) + end + + describe '#current_page' do + + it 'returns the current page' do + expect(response.results.current_page).to eq(1) + end + + context 'when a particular page is accessed' do + + it 'returns the correct current page' do + expect(response.page(5).results.current_page).to eq(5) + end + end + end + + describe '#prev_page' do + + it 'returns the previous page' do + expect(response.page(1).results.prev_page).to be(nil) + expect(response.page(2).results.prev_page).to be(1) + expect(response.page(3).results.prev_page).to be(2) + expect(response.page(4).results.prev_page).to be(3) + end + end + + describe '#next_page' do + + it 'returns the previous page' do + expect(response.page(1).results.next_page).to be(2) + expect(response.page(2).results.next_page).to be(3) + expect(response.page(3).results.next_page).to be(4) + expect(response.page(4).results.next_page).to be(nil) + end + end + end + + context 'records' do + + before do + allow(search).to receive(:execute!).and_return(response_document) + end + + describe '#current_page' do + + it 'returns the current page' do + expect(response.records.current_page).to eq(1) + end + + context 'when a particular page is accessed' do + + it 'returns the correct current page' do + expect(response.page(5).records.current_page).to eq(5) + end + end + end + + describe '#prev_page' do + + it 'returns the previous page' do + expect(response.page(1).records.prev_page).to be(nil) + expect(response.page(2).records.prev_page).to be(1) + expect(response.page(3).records.prev_page).to be(2) + expect(response.page(4).records.prev_page).to be(3) + end + end + + describe '#next_page' do + + it 'returns the previous page' do + expect(response.page(1).records.next_page).to be(2) + expect(response.page(2).records.next_page).to be(3) + expect(response.page(3).records.next_page).to be(4) + expect(response.page(4).records.next_page).to be(nil) + end + end + end + end + + context 'when the model is a single one' do + + let(:model) do + ModelClass + end + + let(:type_field) do + 'bar' + end + + let(:index_field) do + 'foo' + end + + it_behaves_like 'a search request that can be paginated' + end + + context 'when the model is a multimodel' do + + let(:model) do + Elasticsearch::Model::Multimodel.new(ModelClass) + end + + let(:type_field) do + ['bar'] + end + + let(:index_field) do + ['foo'] + end + + it_behaves_like 'a search request that can be paginated' + end +end \ No newline at end of file diff --git a/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb new file mode 100644 index 000000000..1e12ebb95 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb @@ -0,0 +1,263 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Response::Response do + + before(:all) do + class ModelClass + def self.index_name; 'foo'; end + def self.document_type; 'bar'; end + + def self.per_page + 33 + end + end + + # Subclass Response so we can include WillPaginate module without conflicts with Kaminari. + class WillPaginateResponse < Elasticsearch::Model::Response::Response + include Elasticsearch::Model::Response::Pagination::WillPaginate + end + end + + after(:all) do + Object.send(:remove_const, :ModelClass) if defined?(ModelClass) + Object.send(:remove_const, :WillPaginateResponse) if defined?(WillPaginateResponse) + end + + let(:response_document) do + { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, + 'hits' => { 'total' => 100, 'hits' => (1..100).to_a.map { |i| { _id: i } } } } + end + + let(:search) do + Elasticsearch::Model::Searching::SearchRequest.new(model, '*') + end + + let(:response) do + allow(model).to receive(:client).and_return(client) + WillPaginateResponse.new(model, search, response_document).tap do |resp| + allow(resp).to receive(:client).and_return(client) + end + end + + let(:client) do + double('client') + end + + shared_examples_for 'a search request that can be paginated' do + + describe '#offset' do + + context 'when per_page and page are set' do + + before do + response.per_page(3).page(3) + end + + it 'sets the correct offset' do + expect(response.offset).to eq(6) + end + end + end + + describe '#length' do + + context 'when per_page and page are set' do + + before do + response.per_page(3).page(3) + end + + it 'sets the correct offset' do + expect(response.length).to eq(3) + end + end + end + + describe '#paginate' do + + context 'when there are no settings' do + + context 'when page is set to nil' do + + before do + response.paginate(page: nil) + end + + it 'uses the defaults' do + expect(response.search.definition[:size]).to eq(default_per_page) + expect(response.search.definition[:from]).to eq(0) + end + end + + context 'when page is set to a value' do + + before do + response.paginate(page: 2) + end + + it 'uses the defaults' do + expect(response.search.definition[:size]).to eq(default_per_page) + expect(response.search.definition[:from]).to eq(default_per_page) + end + end + + context 'when a custom page and per_page is set' do + + before do + response.paginate(page: 3, per_page: 9) + end + + it 'uses the custom values' do + expect(response.search.definition[:size]).to eq(9) + expect(response.search.definition[:from]).to eq(18) + end + end + + context 'fall back to first page if invalid value is provided' do + + before do + response.paginate(page: -1) + end + + it 'uses the custom values' do + expect(response.search.definition[:size]).to eq(default_per_page) + expect(response.search.definition[:from]).to eq(0) + end + end + end + end + + describe '#page' do + + context 'when a value is provided for page' do + + before do + response.page(5) + end + + it 'calculates the correct :size and :from' do + expect(response.search.definition[:size]).to eq(default_per_page) + expect(response.search.definition[:from]).to eq(default_per_page * 4) + end + end + + context 'when a value is provided for page and per_page' do + + before do + response.page(5).per_page(3) + end + + it 'calculates the correct :size and :from' do + expect(response.search.definition[:size]).to eq(3) + expect(response.search.definition[:from]).to eq(12) + end + end + + context 'when a value is provided for per_page and page' do + + before do + response.per_page(3).page(5) + end + + it 'calculates the correct :size and :from' do + expect(response.search.definition[:size]).to eq(3) + expect(response.search.definition[:from]).to eq(12) + end + end + end + + describe '#current_page' do + + context 'when no values are set' do + + before do + response.paginate({}) + end + + it 'returns the first page' do + expect(response.current_page).to eq(1) + end + end + + context 'when values are provided for per_page and page' do + + before do + response.paginate(page: 3, per_page: 9) + end + + it 'calculates the correct current page' do + expect(response.current_page).to eq(3) + end + end + + context 'when #paginate has not been called on the response' do + + it 'returns nil' do + expect(response.current_page).to be_nil + end + end + end + + describe '#per_page' do + + context 'when a value is set via the #paginate method' do + + before do + response.paginate(per_page: 8) + end + + it 'returns the per_page value' do + expect(response.per_page).to eq(8) + end + end + + context 'when a value is set via the #per_page method' do + + before do + response.per_page(8) + end + + it 'returns the per_page value' do + expect(response.per_page).to eq(8) + end + end + end + + describe '#total_entries' do + + before do + allow(response).to receive(:results).and_return(double('results', total: 100)) + end + + it 'returns the total results' do + expect(response.total_entries).to eq(100) + end + end + end + + context 'when the model is a single one' do + + let(:model) do + ModelClass + end + + let(:default_per_page) do + 33 + end + + it_behaves_like 'a search request that can be paginated' + end + + context 'when the model is a multimodel' do + + let(:model) do + Elasticsearch::Model::Multimodel.new(ModelClass) + end + + let(:default_per_page) do + ::WillPaginate.per_page + end + + it_behaves_like 'a search request that can be paginated' + end +end \ No newline at end of file diff --git a/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb new file mode 100644 index 000000000..a378cc56a --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb @@ -0,0 +1,118 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Response::Records do + + before(:all) do + class DummyCollection + include Enumerable + + def each(&block); ['FOO'].each(&block); end + def size; ['FOO'].size; end + def empty?; ['FOO'].empty?; end + def foo; 'BAR'; end + end + + class DummyModel + def self.index_name; 'foo'; end + def self.document_type; 'bar'; end + + def self.find(*args) + DummyCollection.new + end + end + end + + after(:all) do + Object.send(:remove_const, :DummyCollection) if defined?(DummyCollection) + Object.send(:remove_const, :DummyModel) if defined?(DummyModel) + end + + let(:response_document) do + { 'hits' => { 'total' => 123, 'max_score' => 456, 'hits' => [{'_id' => '1', 'foo' => 'bar'}] } } + end + + let(:results) do + Elasticsearch::Model::Response::Results.new(DummyModel, response_document) + end + + let(:search) do + Elasticsearch::Model::Searching::SearchRequest.new(DummyModel, '*').tap do |request| + allow(request).to receive(:execute!).and_return(response_document) + end + end + + let(:response) do + Elasticsearch::Model::Response::Response.new(DummyModel, search) + end + + let(:records) do + described_class.new(DummyModel, response) + end + + context 'when the records are accessed' do + + it 'returns the records' do + expect(records.records.size).to eq(1) + expect(records.records.first).to eq('FOO') + end + + it 'delegates methods to records' do + expect(records.foo).to eq('BAR') + end + end + + describe '#each_with_hit' do + + it 'returns each record with its Elasticsearch hit' do + records.each_with_hit do |record, hit| + expect(record).to eq('FOO') + expect(hit.foo).to eq('bar') + end + end + end + + describe '#map_with_hit' do + + let(:value) do + records.map_with_hit { |record, hit| "#{record}---#{hit.foo}" } + end + + it 'returns each record with its Elasticsearch hit' do + expect(value).to eq(['FOO---bar']) + end + end + + describe '#ids' do + + it 'returns the ids' do + expect(records.ids).to eq(['1']) + end + end + + context 'when an adapter is used' do + + before do + module DummyAdapter + module RecordsMixin + def records + ['FOOBAR'] + end + end + + def records_mixin + RecordsMixin + end; module_function :records_mixin + end + + allow(Elasticsearch::Model::Adapter).to receive(:from_class).and_return(DummyAdapter) + end + + after do + Object.send(:remove_const, :DummyAdapter) if defined?(DummyAdapter) + end + + it 'delegates the records method to the adapter' do + expect(records.records).to eq(['FOOBAR']) + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb new file mode 100644 index 000000000..2bb96fb8a --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb @@ -0,0 +1,131 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Response::Response do + + before(:all) do + class OriginClass + def self.index_name; 'foo'; end + def self.document_type; 'bar'; end + end + end + + after(:all) do + Object.send(:remove_const, :OriginClass) if defined?(OriginClass) + end + + let(:response_document) do + { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, 'hits' => { 'hits' => [] }, + 'aggregations' => {'foo' => {'bar' => 10}}, + 'suggest' => {'my_suggest' => [ { 'text' => 'foo', 'options' => [ { 'text' => 'Foo', 'score' => 2.0 }, + { 'text' => 'Bar', 'score' => 1.0 } ] } ]}} + + end + + let(:search) do + Elasticsearch::Model::Searching::SearchRequest.new(OriginClass, '*').tap do |request| + allow(request).to receive(:execute!).and_return(response_document) + end + end + + let(:response) do + Elasticsearch::Model::Response::Response.new(OriginClass, search) + end + + it 'performs the Elasticsearch request lazily' do + expect(search).not_to receive(:execute!) + response + end + + describe '#klass' do + + it 'returns the class' do + expect(response.klass).to be(OriginClass) + end + end + + describe '#search' do + + it 'returns the search object' do + expect(response.search).to eq(search) + end + end + + describe '#took' do + + it 'returns the took field' do + expect(response.took).to eq('5') + end + end + + describe '#timed_out' do + + it 'returns the timed_out field' do + expect(response.timed_out).to eq(false) + end + end + + describe '#shards' do + + it 'returns a Hashie::Mash' do + expect(response.shards.one).to eq('OK') + end + end + + describe '#response' do + + it 'returns the response document' do + expect(response.response).to eq(response_document) + end + end + + describe '#results' do + + it 'provides access to the results' do + expect(response.results).to be_a(Elasticsearch::Model::Response::Results) + expect(response.size).to be(0) + end + end + + describe '#records' do + + it 'provides access to the records' do + expect(response.records).to be_a(Elasticsearch::Model::Response::Records) + expect(response.size).to be(0) + end + end + + describe 'enumerable methods' do + + it 'delegates the methods to the results' do + expect(response.empty?).to be(true) + end + end + + describe 'aggregations' do + + it 'provides access to the aggregations' do + expect(response.aggregations).to be_a(Hashie::Mash) + expect(response.aggregations.foo.bar).to eq(10) + end + end + + describe 'suggestions' do + + it 'provides access to the suggestions' do + expect(response.suggestions).to be_a(Hashie::Mash) + expect(response.suggestions.my_suggest.first.options.first.text).to eq('Foo') + expect(response.suggestions.terms).to eq([ 'Foo', 'Bar' ]) + end + + context 'when there are no suggestions' do + + let(:response_document) do + { } + end + + it 'returns an empty list' do + expect(response.suggestions.terms).to eq([ ]) + end + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb new file mode 100644 index 000000000..c998b81b8 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb @@ -0,0 +1,114 @@ +require 'spec_helper' +require 'active_support/json/encoding' + +describe Elasticsearch::Model::Response::Result do + + let(:result) do + described_class.new(foo: 'bar', bar: { bam: 'baz' }) + end + + it 'provides access to the properties' do + expect(result.foo).to eq('bar') + expect(result.bar.bam).to eq('baz') + expect { result.xoxo }.to raise_exception(NoMethodError) + end + + describe '#id' do + + let(:result) do + described_class.new(foo: 'bar', _id: 42, _source: { id: 12 }) + end + + it 'returns the _id field' do + expect(result.id).to eq(42) + end + + it 'provides access to the source id field' do + expect(result._source.id).to eq(12) + end + end + + describe '#type' do + + let(:result) do + described_class.new(foo: 'bar', _type: 'baz', _source: { type: 'BAM' }) + end + + it 'returns the _type field' do + expect(result.type).to eq('baz') + end + + it 'provides access to the source type field' do + expect(result._source.type).to eq('BAM') + end + end + + describe 'method delegation' do + + let(:result) do + described_class.new(foo: 'bar', _source: { bar: { bam: 'baz' } }) + end + + it 'provides access to the _source field via a method' do + expect(result._source).to eq('bar' => { 'bam' => 'baz' }) + end + + context 'when methods map to keys in subdocuments of the response from Elasticsearch' do + + it 'provides access to top level fields via a method' do + expect(result.foo).to eq('bar') + expect(result.fetch(:foo)).to eq('bar') + expect(result.fetch(:does_not_exist, 'moo')).to eq('moo') + end + + it 'responds to hash methods' do + expect(result.keys).to eq(['foo', '_source']) + expect(result.to_hash).to eq('foo' => 'bar', '_source' => { 'bar' => { 'bam' => 'baz' } }) + end + + it 'provides access to fields in the _source subdocument via a method' do + expect(result.bar).to eq('bam' => 'baz') + expect(result.bar.bam).to eq('baz') + expect(result._source.bar).to eq('bam' => 'baz') + expect(result._source.bar.bam).to eq('baz') + end + + context 'when boolean methods are called' do + + it 'provides access to top level fields via a method' do + expect(result.foo?).to eq(true) + expect(result.boo?).to eq(false) + end + + it 'delegates to fields in the _source subdocument via a method' do + expect(result.bar?).to eq(true) + expect(result.bar.bam?).to eq(true) + expect(result.boo?).to eq(false) + expect(result.bar.boo?).to eq(false) + expect(result._source.bar?).to eq(true) + expect(result._source.bar.bam?).to eq(true) + expect(result._source.boo?).to eq(false) + expect(result._source.bar.boo?).to eq(false) + end + end + end + + context 'when methods do not map to keys in subdocuments of the response from Elasticsearch' do + + it 'raises a NoMethodError' do + expect { result.does_not_exist }.to raise_exception(NoMethodError) + end + end + end + + describe '#as_json' do + + let(:result) do + described_class.new(foo: 'bar', _source: { bar: { bam: 'baz' } }) + end + + it 'returns a json string' do + expect(result.as_json(except: 'foo')).to eq({'_source'=>{'bar'=>{'bam'=>'baz'}}}) + end + end +end \ No newline at end of file diff --git a/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb new file mode 100644 index 000000000..5168022b3 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Response::Results do + + before(:all) do + class OriginClass + def self.index_name; 'foo'; end + def self.document_type; 'bar'; end + end + end + + after(:all) do + Object.send(:remove_const, :OriginClass) if defined?(OriginClass) + end + + let(:response_document) do + { 'hits' => { 'total' => 123, 'max_score' => 456, 'hits' => [{'foo' => 'bar'}] } } + end + + let(:search) do + Elasticsearch::Model::Searching::SearchRequest.new(OriginClass, '*').tap do |request| + allow(request).to receive(:execute!).and_return(response_document) + end + end + + let(:response) do + Elasticsearch::Model::Response::Response.new(OriginClass, search) + end + + let(:results) do + response.results + end + + describe '#results' do + + it 'provides access to the results' do + expect(results.results.size).to be(1) + expect(results.results.first.foo).to eq('bar') + end + end + + describe 'Enumerable' do + + it 'deletebates enumerable methods to the results' do + expect(results.empty?).to be(false) + expect(results.first.foo).to eq('bar') + end + end + + describe '#raw_response' do + + it 'returns the raw response document' do + expect(response.raw_response).to eq(response_document) + end + end +end diff --git a/elasticsearch-model/spec/spec_helper.rb b/elasticsearch-model/spec/spec_helper.rb new file mode 100644 index 000000000..ec55da253 --- /dev/null +++ b/elasticsearch-model/spec/spec_helper.rb @@ -0,0 +1,10 @@ +require 'pry-nav' +require 'kaminari' +require 'will_paginate' +require 'will_paginate/collection' +require 'elasticsearch/model' + +RSpec.configure do |config| + config.formatter = 'documentation' + config.color = true +end diff --git a/elasticsearch-model/test/unit/response_aggregations_test.rb b/elasticsearch-model/test/unit/response_aggregations_test.rb deleted file mode 100644 index cac17759d..000000000 --- a/elasticsearch-model/test/unit/response_aggregations_test.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::ResponseAggregationsTest < Test::Unit::TestCase - context "Response aggregations" do - class OriginClass - def self.index_name; 'foo'; end - def self.document_type; 'bar'; end - end - - RESPONSE = { - 'aggregations' => { - 'foo' => {'bar' => 10 }, - 'price' => { 'doc_count' => 123, - 'min' => { 'value' => 1.0}, - 'max' => { 'value' => 99 } - } - } - } - - setup do - @search = Elasticsearch::Model::Searching::SearchRequest.new OriginClass, '*' - @search.stubs(:execute!).returns(RESPONSE) - end - - should "access the aggregations" do - @search.expects(:execute!).returns(RESPONSE) - - response = Elasticsearch::Model::Response::Response.new OriginClass, @search - assert_respond_to response, :aggregations - assert_kind_of Elasticsearch::Model::Response::Aggregations, response.aggregations - assert_kind_of Hashie::Mash, response.aggregations.foo - assert_equal 10, response.aggregations.foo.bar - end - - should "properly return min and max values" do - @search.expects(:execute!).returns(RESPONSE) - - response = Elasticsearch::Model::Response::Response.new OriginClass, @search - - assert_equal 123, response.aggregations.price.doc_count - assert_equal 1, response.aggregations.price.min.value - assert_equal 99, response.aggregations.price.max.value - end - - end -end diff --git a/elasticsearch-model/test/unit/response_base_test.rb b/elasticsearch-model/test/unit/response_base_test.rb deleted file mode 100644 index aa9b4244d..000000000 --- a/elasticsearch-model/test/unit/response_base_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::BaseTest < Test::Unit::TestCase - context "Response base module" do - class OriginClass - def self.index_name; 'foo'; end - def self.document_type; 'bar'; end - end - - class DummyBaseClass - include Elasticsearch::Model::Response::Base - end - - RESPONSE = { 'hits' => { 'total' => 123, 'max_score' => 456, 'hits' => [] } } - - setup do - @search = Elasticsearch::Model::Searching::SearchRequest.new OriginClass, '*' - @response = Elasticsearch::Model::Response::Response.new OriginClass, @search - @search.stubs(:execute!).returns(RESPONSE) - end - - should "access klass, response, total and max_score" do - r = DummyBaseClass.new OriginClass, @response - - assert_equal OriginClass, r.klass - assert_equal @response, r.response - assert_equal RESPONSE, r.response.response - assert_equal 123, r.total - assert_equal 456, r.max_score - end - - should "have abstract methods results and records" do - r = DummyBaseClass.new OriginClass, @response - - assert_raise(Elasticsearch::Model::NotImplemented) { |e| r.results } - assert_raise(Elasticsearch::Model::NotImplemented) { |e| r.records } - end - - end -end diff --git a/elasticsearch-model/test/unit/response_pagination_kaminari_test.rb b/elasticsearch-model/test/unit/response_pagination_kaminari_test.rb deleted file mode 100644 index 1fc9b2f3c..000000000 --- a/elasticsearch-model/test/unit/response_pagination_kaminari_test.rb +++ /dev/null @@ -1,433 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::ResponsePaginationKaminariTest < Test::Unit::TestCase - class ModelClass - include ::Kaminari::ConfigurationMethods - - def self.index_name; 'foo'; end - def self.document_type; 'bar'; end - end - - RESPONSE = { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, - 'hits' => { 'total' => 100, 'hits' => (1..100).to_a.map { |i| { _id: i } } } } - - context "Response pagination" do - - setup do - @search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, '*' - @response = Elasticsearch::Model::Response::Response.new ModelClass, @search, RESPONSE - @response.klass.stubs(:client).returns mock('client') - end - - should "have pagination methods" do - assert_respond_to @response, :page - assert_respond_to @response, :limit_value - assert_respond_to @response, :offset_value - assert_respond_to @response, :limit - assert_respond_to @response, :offset - assert_respond_to @response, :total_count - end - - context "#page method" do - should "advance the from/size" do - @response.klass.client - .expects(:search) - .with do |definition| - assert_equal 25, definition[:from] - assert_equal 25, definition[:size] - true - end - .returns(RESPONSE) - - assert_nil @response.search.definition[:from] - assert_nil @response.search.definition[:size] - - @response.page(2).to_a - assert_equal 25, @response.search.definition[:from] - assert_equal 25, @response.search.definition[:size] - end - - should "advance the from/size further" do - @response.klass.client - .expects(:search) - .with do |definition| - assert_equal 75, definition[:from] - assert_equal 25, definition[:size] - true - end - .returns(RESPONSE) - - @response.page(4).to_a - assert_equal 75, @response.search.definition[:from] - assert_equal 25, @response.search.definition[:size] - end - end - - context "limit/offset readers" do - should "return the default" do - assert_equal Kaminari.config.default_per_page, @response.limit_value - assert_equal 0, @response.offset_value - end - - should "return the value from URL parameters" do - search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, '*', size: 10, from: 50 - @response = Elasticsearch::Model::Response::Response.new ModelClass, search, RESPONSE - - assert_equal 10, @response.limit_value - assert_equal 50, @response.offset_value - end - - should "ignore the value from request body" do - search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, - { query: { match_all: {} }, from: 333, size: 999 } - @response = Elasticsearch::Model::Response::Response.new ModelClass, search, RESPONSE - - assert_equal Kaminari.config.default_per_page, @response.limit_value - assert_equal 0, @response.offset_value - end - end - - context "limit setter" do - setup do - @response.records - @response.results - end - - should "set the values" do - @response.limit(35) - assert_equal 35, @response.search.definition[:size] - end - - should "reset the variables" do - @response.limit(35) - - assert_nil @response.instance_variable_get(:@response) - assert_nil @response.instance_variable_get(:@records) - assert_nil @response.instance_variable_get(:@results) - end - - should 'coerce string parameters' do - @response.limit("35") - assert_equal 35, @response.search.definition[:size] - end - - should 'ignore invalid string parameters' do - @response.limit(35) - @response.limit("asdf") - assert_equal 35, @response.search.definition[:size] - end - end - - context "with the page() and limit() methods" do - setup do - @response.records - @response.results - end - - should "set the values" do - @response.page(3).limit(35) - assert_equal 35, @response.search.definition[:size] - assert_equal 70, @response.search.definition[:from] - end - - should "set the values when limit is called first" do - @response.limit(35).page(3) - assert_equal 35, @response.search.definition[:size] - assert_equal 70, @response.search.definition[:from] - end - - should "reset the instance variables" do - @response.page(3).limit(35) - - assert_nil @response.instance_variable_get(:@response) - assert_nil @response.instance_variable_get(:@records) - assert_nil @response.instance_variable_get(:@results) - end - end - - context "offset setter" do - setup do - @response.records - @response.results - end - - should "set the values" do - @response.offset(15) - assert_equal 15, @response.search.definition[:from] - end - - should "reset the variables" do - @response.offset(35) - - assert_nil @response.instance_variable_get(:@response) - assert_nil @response.instance_variable_get(:@records) - assert_nil @response.instance_variable_get(:@results) - end - - should 'coerce string parameters' do - @response.offset("35") - assert_equal 35, @response.search.definition[:from] - end - - should 'coerce invalid string parameters' do - @response.offset(35) - @response.offset("asdf") - assert_equal 0, @response.search.definition[:from] - end - end - - context "total" do - should "return the number of hits" do - @response.expects(:results).returns(mock('results', total: 100)) - assert_equal 100, @response.total_count - end - end - - context "results" do - setup do - @search.stubs(:execute!).returns RESPONSE - end - - should "return current page and total count" do - assert_equal 1, @response.page(1).results.current_page - assert_equal 100, @response.results.total_count - - assert_equal 5, @response.page(5).results.current_page - end - - should "return previous page and next page" do - assert_equal nil, @response.page(1).results.prev_page - assert_equal 2, @response.page(1).results.next_page - - assert_equal 3, @response.page(4).results.prev_page - assert_equal nil, @response.page(4).results.next_page - - assert_equal 2, @response.page(3).results.prev_page - assert_equal 4, @response.page(3).results.next_page - end - end - - context "records" do - setup do - @search.stubs(:execute!).returns RESPONSE - end - - should "return current page and total count" do - assert_equal 1, @response.page(1).records.current_page - assert_equal 100, @response.records.total_count - - assert_equal 5, @response.page(5).records.current_page - end - - should "return previous page and next page" do - assert_equal nil, @response.page(1).records.prev_page - assert_equal 2, @response.page(1).records.next_page - - assert_equal 3, @response.page(4).records.prev_page - assert_equal nil, @response.page(4).records.next_page - - assert_equal 2, @response.page(3).records.prev_page - assert_equal 4, @response.page(3).records.next_page - end - end - end - - context "Multimodel response pagination" do - setup do - @multimodel = Elasticsearch::Model::Multimodel.new(ModelClass) - @search = Elasticsearch::Model::Searching::SearchRequest.new @multimodel, '*' - @response = Elasticsearch::Model::Response::Response.new @multimodel, @search, RESPONSE - @response.klass.stubs(:client).returns mock('client') - end - - should "have pagination methods" do - assert_respond_to @response, :page - assert_respond_to @response, :limit_value - assert_respond_to @response, :offset_value - assert_respond_to @response, :limit - assert_respond_to @response, :offset - assert_respond_to @response, :total_count - end - - context "#page method" do - should "advance the from/size" do - @response.klass.client - .expects(:search) - .with do |definition| - assert_equal 25, definition[:from] - assert_equal 25, definition[:size] - true - end - .returns(RESPONSE) - - assert_nil @response.search.definition[:from] - assert_nil @response.search.definition[:size] - - @response.page(2).to_a - assert_equal 25, @response.search.definition[:from] - assert_equal 25, @response.search.definition[:size] - end - - should "advance the from/size further" do - @response.klass.client - .expects(:search) - .with do |definition| - assert_equal 75, definition[:from] - assert_equal 25, definition[:size] - true - end - .returns(RESPONSE) - - @response.page(4).to_a - assert_equal 75, @response.search.definition[:from] - assert_equal 25, @response.search.definition[:size] - end - end - - context "limit/offset readers" do - should "return the default" do - assert_equal Kaminari.config.default_per_page, @response.limit_value - assert_equal 0, @response.offset_value - end - - should "return the value from URL parameters" do - search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, '*', size: 10, from: 50 - @response = Elasticsearch::Model::Response::Response.new ModelClass, search, RESPONSE - - assert_equal 10, @response.limit_value - assert_equal 50, @response.offset_value - end - - should "ignore the value from request body" do - search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, - { query: { match_all: {} }, from: 333, size: 999 } - @response = Elasticsearch::Model::Response::Response.new ModelClass, search, RESPONSE - - assert_equal Kaminari.config.default_per_page, @response.limit_value - assert_equal 0, @response.offset_value - end - end - - context "limit setter" do - setup do - @response.records - @response.results - end - - should "set the values" do - @response.limit(35) - assert_equal 35, @response.search.definition[:size] - end - - should "reset the variables" do - @response.limit(35) - - assert_nil @response.instance_variable_get(:@response) - assert_nil @response.instance_variable_get(:@records) - assert_nil @response.instance_variable_get(:@results) - end - end - - context "with the page() and limit() methods" do - setup do - @response.records - @response.results - end - - should "set the values" do - @response.page(3).limit(35) - assert_equal 35, @response.search.definition[:size] - assert_equal 70, @response.search.definition[:from] - end - - should "set the values when limit is called first" do - @response.limit(35).page(3) - assert_equal 35, @response.search.definition[:size] - assert_equal 70, @response.search.definition[:from] - end - - should "reset the instance variables" do - @response.page(3).limit(35) - - assert_nil @response.instance_variable_get(:@response) - assert_nil @response.instance_variable_get(:@records) - assert_nil @response.instance_variable_get(:@results) - end - end - - context "offset setter" do - setup do - @response.records - @response.results - end - - should "set the values" do - @response.offset(15) - assert_equal 15, @response.search.definition[:from] - end - - should "reset the variables" do - @response.offset(35) - - assert_nil @response.instance_variable_get(:@response) - assert_nil @response.instance_variable_get(:@records) - assert_nil @response.instance_variable_get(:@results) - end - end - - context "total" do - should "return the number of hits" do - @response.expects(:results).returns(mock('results', total: 100)) - assert_equal 100, @response.total_count - end - end - - context "results" do - setup do - @search.stubs(:execute!).returns RESPONSE - end - - should "return current page and total count" do - assert_equal 1, @response.page(1).results.current_page - assert_equal 100, @response.results.total_count - - assert_equal 5, @response.page(5).results.current_page - end - - should "return previous page and next page" do - assert_equal nil, @response.page(1).results.prev_page - assert_equal 2, @response.page(1).results.next_page - - assert_equal 3, @response.page(4).results.prev_page - assert_equal nil, @response.page(4).results.next_page - - assert_equal 2, @response.page(3).results.prev_page - assert_equal 4, @response.page(3).results.next_page - end - end - - context "records" do - setup do - @search.stubs(:execute!).returns RESPONSE - end - - should "return current page and total count" do - assert_equal 1, @response.page(1).records.current_page - assert_equal 100, @response.records.total_count - - assert_equal 5, @response.page(5).records.current_page - end - - should "return previous page and next page" do - assert_equal nil, @response.page(1).records.prev_page - assert_equal 2, @response.page(1).records.next_page - - assert_equal 3, @response.page(4).records.prev_page - assert_equal nil, @response.page(4).records.next_page - - assert_equal 2, @response.page(3).records.prev_page - assert_equal 4, @response.page(3).records.next_page - end - end - end -end diff --git a/elasticsearch-model/test/unit/response_pagination_will_paginate_test.rb b/elasticsearch-model/test/unit/response_pagination_will_paginate_test.rb deleted file mode 100644 index 6c9383525..000000000 --- a/elasticsearch-model/test/unit/response_pagination_will_paginate_test.rb +++ /dev/null @@ -1,398 +0,0 @@ -require 'test_helper' -require 'will_paginate' -require 'will_paginate/collection' - -class Elasticsearch::Model::ResponsePaginationWillPaginateTest < Test::Unit::TestCase - class ModelClass - def self.index_name; 'foo'; end - def self.document_type; 'bar'; end - - # WillPaginate adds this method to models (see WillPaginate::PerPage module) - def self.per_page - 33 - end - end - - # Subsclass Response so we can include WillPaginate module without conflicts with Kaminari. - class WillPaginateResponse < Elasticsearch::Model::Response::Response - include Elasticsearch::Model::Response::Pagination::WillPaginate - end - - RESPONSE = { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, - 'hits' => { 'total' => 100, 'hits' => (1..100).to_a.map { |i| { _id: i } } } } - - context "Response pagination" do - - setup do - @search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, '*' - @response = WillPaginateResponse.new ModelClass, @search, RESPONSE - @response.klass.stubs(:client).returns mock('client') - - @expected_methods = [ - # methods needed by WillPaginate::CollectionMethods - :current_page, - :offset, - :per_page, - :total_entries, - :length, - - # methods defined by WillPaginate::CollectionMethods - :total_pages, - :previous_page, - :next_page, - :out_of_bounds?, - ] - end - - should "have pagination methods" do - assert_respond_to @response, :paginate - - @expected_methods.each do |method| - assert_respond_to @response, method - end - end - - context "response.results" do - should "have pagination methods" do - @expected_methods.each do |method| - assert_respond_to @response.results, method - end - end - end - - context "response.records" do - should "have pagination methods" do - @expected_methods.each do |method| - @response.klass.stubs(:find).returns([]) - assert_respond_to @response.records, method - end - end - end - - context "#offset method" do - should "calculate offset using current_page and per_page" do - @response.per_page(3).page(3) - assert_equal 6, @response.offset - end - end - context "#length method" do - should "return count of paginated results" do - @response.per_page(3).page(3) - assert_equal 3, @response.length - end - end - - context "#paginate method" do - should "set from/size using defaults" do - @response.klass.client - .expects(:search) - .with do |definition| - assert_equal 0, definition[:from] - assert_equal 33, definition[:size] - true - end - .returns(RESPONSE) - - assert_nil @response.search.definition[:from] - assert_nil @response.search.definition[:size] - - @response.paginate(page: nil).to_a - assert_equal 0, @response.search.definition[:from] - assert_equal 33, @response.search.definition[:size] - end - - should "set from/size using default per_page" do - @response.klass.client - .expects(:search) - .with do |definition| - assert_equal 33, definition[:from] - assert_equal 33, definition[:size] - true - end - .returns(RESPONSE) - - assert_nil @response.search.definition[:from] - assert_nil @response.search.definition[:size] - - @response.paginate(page: 2).to_a - assert_equal 33, @response.search.definition[:from] - assert_equal 33, @response.search.definition[:size] - end - - should "set from/size using custom page and per_page" do - @response.klass.client - .expects(:search) - .with do |definition| - assert_equal 18, definition[:from] - assert_equal 9, definition[:size] - true - end - .returns(RESPONSE) - - assert_nil @response.search.definition[:from] - assert_nil @response.search.definition[:size] - - @response.paginate(page: 3, per_page: 9).to_a - assert_equal 18, @response.search.definition[:from] - assert_equal 9, @response.search.definition[:size] - end - - should "search for first page if specified page is < 1" do - @response.klass.client - .expects(:search) - .with do |definition| - assert_equal 0, definition[:from] - assert_equal 33, definition[:size] - true - end - .returns(RESPONSE) - - assert_nil @response.search.definition[:from] - assert_nil @response.search.definition[:size] - - @response.paginate(page: "-1").to_a - assert_equal 0, @response.search.definition[:from] - assert_equal 33, @response.search.definition[:size] - end - - should "use the param_name" do - @response.klass.client - .expects(:search) - .with do |definition| - assert_equal 10, definition[:from] - true - end - .returns(RESPONSE) - - @response.paginate(my_page: 2, per_page: 10, param_name: :my_page).to_a - end - end - - context "#page and #per_page shorthand methods" do - should "set from/size using default per_page" do - @response.page(5) - assert_equal 132, @response.search.definition[:from] - assert_equal 33, @response.search.definition[:size] - end - - should "set from/size when calling #page then #per_page" do - @response.page(5).per_page(3) - assert_equal 12, @response.search.definition[:from] - assert_equal 3, @response.search.definition[:size] - end - - should "set from/size when calling #per_page then #page" do - @response.per_page(3).page(5) - assert_equal 12, @response.search.definition[:from] - assert_equal 3, @response.search.definition[:size] - end - end - - context "#current_page method" do - should "return 1 by default" do - @response.paginate({}) - assert_equal 1, @response.current_page - end - - should "return current page number" do - @response.paginate(page: 3, per_page: 9) - assert_equal 3, @response.current_page - end - - should "return nil if not pagination set" do - assert_equal nil, @response.current_page - end - end - - context "#per_page method" do - should "return value set in paginate call" do - @response.paginate(per_page: 8) - assert_equal 8, @response.per_page - end - end - - context "#total_entries method" do - should "return total from response" do - @response.expects(:results).returns(mock('results', total: 100)) - assert_equal 100, @response.total_entries - end - end - end - - context "Multimodel response pagination" do - setup do - @multimodel = Elasticsearch::Model::Multimodel.new ModelClass - @search = Elasticsearch::Model::Searching::SearchRequest.new @multimodel, '*' - @response = WillPaginateResponse.new @multimodel, @search, RESPONSE - @response.klass.stubs(:client).returns mock('client') - - @expected_methods = [ - # methods needed by WillPaginate::CollectionMethods - :current_page, - :offset, - :per_page, - :total_entries, - :length, - - # methods defined by WillPaginate::CollectionMethods - :total_pages, - :previous_page, - :next_page, - :out_of_bounds?, - ] - end - - should "have pagination methods" do - assert_respond_to @response, :paginate - - @expected_methods.each do |method| - assert_respond_to @response, method - end - end - - context "response.results" do - should "have pagination methods" do - @expected_methods.each do |method| - assert_respond_to @response.results, method - end - end - end - - context "#offset method" do - should "calculate offset using current_page and per_page" do - @response.per_page(3).page(3) - assert_equal 6, @response.offset - end - end - context "#length method" do - should "return count of paginated results" do - @response.per_page(3).page(3) - assert_equal 3, @response.length - end - end - - context "#paginate method" do - should "set from/size using WillPaginate defaults, ignoring aggregated models configuration" do - @response.klass.client - .expects(:search) - .with do |definition| - assert_equal 0, definition[:from] - assert_equal ::WillPaginate.per_page, definition[:size] - true - end - .returns(RESPONSE) - - assert_nil @response.search.definition[:from] - assert_nil @response.search.definition[:size] - - @response.paginate(page: nil).to_a - assert_equal 0, @response.search.definition[:from] - assert_equal ::WillPaginate.per_page, @response.search.definition[:size] - end - - should "set from/size using default per_page, ignoring aggregated models' configuration" do - @response.klass.client - .expects(:search) - .with do |definition| - assert_equal ::WillPaginate.per_page, definition[:from] - assert_equal ::WillPaginate.per_page, definition[:size] - true - end - .returns(RESPONSE) - - assert_nil @response.search.definition[:from] - assert_nil @response.search.definition[:size] - - @response.paginate(page: 2).to_a - assert_equal ::WillPaginate.per_page, @response.search.definition[:from] - assert_equal ::WillPaginate.per_page, @response.search.definition[:size] - end - - should "set from/size using custom page and per_page" do - @response.klass.client - .expects(:search) - .with do |definition| - assert_equal 18, definition[:from] - assert_equal 9, definition[:size] - true - end - .returns(RESPONSE) - - assert_nil @response.search.definition[:from] - assert_nil @response.search.definition[:size] - - @response.paginate(page: 3, per_page: 9).to_a - assert_equal 18, @response.search.definition[:from] - assert_equal 9, @response.search.definition[:size] - end - - should "search for first page if specified page is < 1" do - @response.klass.client - .expects(:search) - .with do |definition| - assert_equal 0, definition[:from] - assert_equal ::WillPaginate.per_page, definition[:size] - true - end - .returns(RESPONSE) - - assert_nil @response.search.definition[:from] - assert_nil @response.search.definition[:size] - - @response.paginate(page: "-1").to_a - assert_equal 0, @response.search.definition[:from] - assert_equal ::WillPaginate.per_page, @response.search.definition[:size] - end - end - - context "#page and #per_page shorthand methods" do - should "set from/size using default per_page" do - @response.page(5) - assert_equal 120, @response.search.definition[:from] - assert_equal ::WillPaginate.per_page, @response.search.definition[:size] - end - - should "set from/size when calling #page then #per_page" do - @response.page(5).per_page(3) - assert_equal 12, @response.search.definition[:from] - assert_equal 3, @response.search.definition[:size] - end - - should "set from/size when calling #per_page then #page" do - @response.per_page(3).page(5) - assert_equal 12, @response.search.definition[:from] - assert_equal 3, @response.search.definition[:size] - end - end - - context "#current_page method" do - should "return 1 by default" do - @response.paginate({}) - assert_equal 1, @response.current_page - end - - should "return current page number" do - @response.paginate(page: 3, per_page: 9) - assert_equal 3, @response.current_page - end - - should "return nil if not pagination set" do - assert_equal nil, @response.current_page - end - end - - context "#per_page method" do - should "return value set in paginate call" do - @response.paginate(per_page: 8) - assert_equal 8, @response.per_page - end - end - - context "#total_entries method" do - should "return total from response" do - @response.expects(:results).returns(mock('results', total: 100)) - assert_equal 100, @response.total_entries - end - end - end -end diff --git a/elasticsearch-model/test/unit/response_records_test.rb b/elasticsearch-model/test/unit/response_records_test.rb deleted file mode 100644 index 8a78255d7..000000000 --- a/elasticsearch-model/test/unit/response_records_test.rb +++ /dev/null @@ -1,91 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::RecordsTest < Test::Unit::TestCase - context "Response records" do - class DummyCollection - include Enumerable - - def each(&block); ['FOO'].each(&block); end - def size; ['FOO'].size; end - def empty?; ['FOO'].empty?; end - def foo; 'BAR'; end - end - - class DummyModel - def self.index_name; 'foo'; end - def self.document_type; 'bar'; end - - def self.find(*args) - DummyCollection.new - end - end - - RESPONSE = { 'hits' => { 'total' => 123, 'max_score' => 456, 'hits' => [{'_id' => '1', 'foo' => 'bar'}] } } - RESULTS = Elasticsearch::Model::Response::Results.new DummyModel, RESPONSE - - setup do - search = Elasticsearch::Model::Searching::SearchRequest.new DummyModel, '*' - search.stubs(:execute!).returns RESPONSE - - response = Elasticsearch::Model::Response::Response.new DummyModel, search - @records = Elasticsearch::Model::Response::Records.new DummyModel, response - end - - should "access the records" do - assert_respond_to @records, :records - assert_equal 1, @records.records.size - assert_equal 'FOO', @records.records.first - end - - should "delegate Enumerable methods to records" do - assert ! @records.empty? - assert_equal 'FOO', @records.first - end - - should "delegate methods to records" do - assert_respond_to @records, :foo - assert_equal 'BAR', @records.foo - end - - should "have each_with_hit method" do - @records.each_with_hit do |record, hit| - assert_equal 'FOO', record - assert_equal 'bar', hit.foo - end - end - - should "have map_with_hit method" do - assert_equal ['FOO---bar'], @records.map_with_hit { |record, hit| "#{record}---#{hit.foo}" } - end - - should "return the IDs" do - assert_equal ['1'], @records.ids - end - - context "with adapter" do - module DummyAdapter - module RecordsMixin - def records - ['FOOBAR'] - end - end - - def records_mixin - RecordsMixin - end; module_function :records_mixin - end - - should "delegate the records method to the adapter" do - Elasticsearch::Model::Adapter.expects(:from_class) - .with(DummyModel) - .returns(DummyAdapter) - - @records = Elasticsearch::Model::Response::Records.new DummyModel, - RESPONSE - - assert_equal ['FOOBAR'], @records.records - end - end - - end -end diff --git a/elasticsearch-model/test/unit/response_result_test.rb b/elasticsearch-model/test/unit/response_result_test.rb deleted file mode 100644 index c357d46ba..000000000 --- a/elasticsearch-model/test/unit/response_result_test.rb +++ /dev/null @@ -1,90 +0,0 @@ -require 'test_helper' -require 'active_support/json/encoding' - -class Elasticsearch::Model::ResultTest < Test::Unit::TestCase - context "Response result" do - - should "have method access to properties" do - result = Elasticsearch::Model::Response::Result.new foo: 'bar', bar: { bam: 'baz' } - - assert_respond_to result, :foo - assert_respond_to result, :bar - - assert_equal 'bar', result.foo - assert_equal 'baz', result.bar.bam - - assert_raise(NoMethodError) { result.xoxo } - end - - should "return _id as #id" do - result = Elasticsearch::Model::Response::Result.new foo: 'bar', _id: 42, _source: { id: 12 } - - assert_equal 42, result.id - assert_equal 12, result._source.id - end - - should "return _type as #type" do - result = Elasticsearch::Model::Response::Result.new foo: 'bar', _type: 'baz', _source: { type: 'BAM' } - - assert_equal 'baz', result.type - assert_equal 'BAM', result._source.type - end - - should "delegate method calls to `_source` when available" do - result = Elasticsearch::Model::Response::Result.new foo: 'bar', _source: { bar: 'baz' } - - assert_respond_to result, :foo - assert_respond_to result, :_source - assert_respond_to result, :bar - - assert_equal 'bar', result.foo - assert_equal 'baz', result._source.bar - assert_equal 'baz', result.bar - end - - should "delegate existence method calls to `_source`" do - result = Elasticsearch::Model::Response::Result.new foo: 'bar', _source: { bar: { bam: 'baz' } } - - assert_respond_to result._source, :bar? - assert_respond_to result, :bar? - - assert_equal true, result._source.bar? - assert_equal true, result.bar? - assert_equal false, result.boo? - - assert_equal true, result.bar.bam? - assert_equal false, result.bar.boo? - end - - should "delegate methods to @result" do - result = Elasticsearch::Model::Response::Result.new foo: 'bar' - - assert_equal 'bar', result.foo - assert_equal 'bar', result.fetch('foo') - assert_equal 'moo', result.fetch('NOT_EXIST', 'moo') - assert_equal ['foo'], result.keys - - assert_respond_to result, :to_hash - assert_equal({'foo' => 'bar'}, result.to_hash) - - assert_raise(NoMethodError) { result.does_not_exist } - end - - should "delegate existence method calls to @result" do - result = Elasticsearch::Model::Response::Result.new foo: 'bar', _source: { bar: 'bam' } - assert_respond_to result, :foo? - - assert_equal true, result.foo? - assert_equal false, result.boo? - assert_equal false, result._source.foo? - assert_equal false, result._source.boo? - end - - should "delegate as_json to @result even when ActiveSupport changed half of Ruby" do - result = Elasticsearch::Model::Response::Result.new foo: 'bar' - - result.instance_variable_get(:@result).expects(:as_json) - result.as_json(except: 'foo') - end - end -end diff --git a/elasticsearch-model/test/unit/response_results_test.rb b/elasticsearch-model/test/unit/response_results_test.rb deleted file mode 100644 index 900094bf5..000000000 --- a/elasticsearch-model/test/unit/response_results_test.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::ResultsTest < Test::Unit::TestCase - context "Response results" do - class OriginClass - def self.index_name; 'foo'; end - def self.document_type; 'bar'; end - end - - RESPONSE = { 'hits' => { 'total' => 123, 'max_score' => 456, 'hits' => [{'foo' => 'bar'}] } } - - setup do - @search = Elasticsearch::Model::Searching::SearchRequest.new OriginClass, '*' - @response = Elasticsearch::Model::Response::Response.new OriginClass, @search - @results = Elasticsearch::Model::Response::Results.new OriginClass, @response - @search.stubs(:execute!).returns(RESPONSE) - end - - should "access the results" do - assert_respond_to @results, :results - assert_equal 1, @results.results.size - assert_equal 'bar', @results.results.first.foo - end - - should "delegate Enumerable methods to results" do - assert ! @results.empty? - assert_equal 'bar', @results.first.foo - end - - should "provide access to the raw response" do - assert_equal RESPONSE, @response.raw_response - end - end -end diff --git a/elasticsearch-model/test/unit/response_test.rb b/elasticsearch-model/test/unit/response_test.rb deleted file mode 100644 index e18f6c7ef..000000000 --- a/elasticsearch-model/test/unit/response_test.rb +++ /dev/null @@ -1,104 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::ResponseTest < Test::Unit::TestCase - context "Response" do - class OriginClass - def self.index_name; 'foo'; end - def self.document_type; 'bar'; end - end - - RESPONSE = { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, 'hits' => { 'hits' => [] }, - 'aggregations' => {'foo' => {'bar' => 10}}, - 'suggest' => {'my_suggest' => [ { 'text' => 'foo', 'options' => [ { 'text' => 'Foo', 'score' => 2.0 }, { 'text' => 'Bar', 'score' => 1.0 } ] } ]}} - - setup do - @search = Elasticsearch::Model::Searching::SearchRequest.new OriginClass, '*' - @search.stubs(:execute!).returns(RESPONSE) - end - - should "access klass, response, took, timed_out, shards" do - response = Elasticsearch::Model::Response::Response.new OriginClass, @search - - assert_equal OriginClass, response.klass - assert_equal @search, response.search - assert_equal RESPONSE, response.response - assert_equal '5', response.took - assert_equal false, response.timed_out - assert_equal 'OK', response.shards.one - end - - should "wrap the raw Hash response in a HashWrapper" do - @search = Elasticsearch::Model::Searching::SearchRequest.new OriginClass, '*' - @search.stubs(:execute!).returns({'hits' => { 'hits' => [] }, 'aggregations' => { 'dates' => 'FOO' }}) - - response = Elasticsearch::Model::Response::Response.new OriginClass, @search - - assert_respond_to response.response, :aggregations - assert_equal 'FOO', response.response.aggregations.dates - end - - should "load and access the results" do - @search.expects(:execute!).returns(RESPONSE) - - response = Elasticsearch::Model::Response::Response.new OriginClass, @search - assert_instance_of Elasticsearch::Model::Response::Results, response.results - assert_equal 0, response.size - end - - should "load and access the records" do - @search.expects(:execute!).returns(RESPONSE) - - response = Elasticsearch::Model::Response::Response.new OriginClass, @search - assert_instance_of Elasticsearch::Model::Response::Records, response.records - assert_equal 0, response.size - end - - should "delegate Enumerable methods to results" do - @search.expects(:execute!).returns(RESPONSE) - - response = Elasticsearch::Model::Response::Response.new OriginClass, @search - assert response.empty? - end - - should "be initialized lazily" do - @search.expects(:execute!).never - - Elasticsearch::Model::Response::Response.new OriginClass, @search - end - - should "access the aggregations" do - @search.expects(:execute!).returns(RESPONSE) - - response = Elasticsearch::Model::Response::Response.new OriginClass, @search - assert_respond_to response, :aggregations - assert_kind_of Hashie::Mash, response.aggregations.foo - assert_equal 10, response.aggregations.foo.bar - end - - should "access the suggest" do - @search.expects(:execute!).returns(RESPONSE) - - response = Elasticsearch::Model::Response::Response.new OriginClass, @search - - assert_respond_to response, :suggestions - assert_kind_of Hashie::Mash, response.suggestions - assert_equal 'Foo', response.suggestions.my_suggest.first.options.first.text - end - - should "return array of terms from the suggestions" do - @search.expects(:execute!).returns(RESPONSE) - response = Elasticsearch::Model::Response::Response.new OriginClass, @search - - assert_not_empty response.suggestions - assert_equal [ 'Foo', 'Bar' ], response.suggestions.terms - end - - should "return empty array as suggest terms when there are no suggestions" do - @search.expects(:execute!).returns({}) - response = Elasticsearch::Model::Response::Response.new OriginClass, @search - - assert_empty response.suggestions - assert_equal [], response.suggestions.terms - end - end -end From f3fde1c4d46f7a3bf2944d1c4ef1646d5cc31773 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Tue, 11 Sep 2018 16:36:20 +0200 Subject: [PATCH 408/582] [MODEL] Add newlines at the end of files that are missing it --- elasticsearch-model/Gemfile | 2 +- .../elasticsearch/model/response/pagination/kaminari_spec.rb | 3 +-- .../model/response/pagination/will_paginate_spec.rb | 2 +- .../spec/elasticsearch/model/response/result_spec.rb | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/elasticsearch-model/Gemfile b/elasticsearch-model/Gemfile index ee48fe61b..c016d096d 100644 --- a/elasticsearch-model/Gemfile +++ b/elasticsearch-model/Gemfile @@ -6,4 +6,4 @@ gemspec group :development, :testing do gem 'rspec' gem 'pry-nav' -end \ No newline at end of file +end diff --git a/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb index 6198fafac..d288e2dc8 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb @@ -28,7 +28,6 @@ def self.document_type; 'bar'; end Elasticsearch::Model::Response::Response.new(model, search, response_document).tap do |resp| allow(resp).to receive(:client).and_return(client) end - end let(:client) do @@ -408,4 +407,4 @@ def self.document_type; 'bar'; end it_behaves_like 'a search request that can be paginated' end -end \ No newline at end of file +end diff --git a/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb index 1e12ebb95..7cd4fdfd9 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb @@ -260,4 +260,4 @@ class WillPaginateResponse < Elasticsearch::Model::Response::Response it_behaves_like 'a search request that can be paginated' end -end \ No newline at end of file +end diff --git a/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb index c998b81b8..2b19c4ab3 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb @@ -111,4 +111,4 @@ expect(result.as_json(except: 'foo')).to eq({'_source'=>{'bar'=>{'bam'=>'baz'}}}) end end -end \ No newline at end of file +end From ff6c23d0a7d9fa7b5ff0bcb3ed6093e2cf283a5b Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Wed, 12 Sep 2018 13:57:41 +0200 Subject: [PATCH 409/582] [MODEL] Port adapter tests to rspec (#834) * [MODEL] Port adapter tests to rspec * [MODEL] Fix spacing in new specs --- .../spec/elasticsearch/model/adapter_spec.rb | 121 +++++++++ .../model/adapters/active_record_spec.rb | 238 ++++++++++++++++++ .../model/adapters/default_spec.rb | 41 +++ .../model/adapters/mongoid_spec.rb | 235 +++++++++++++++++ .../model/adapters/multiple_spec.rb | 126 ++++++++++ .../model/response/records_spec.rb | 1 + .../test/unit/adapter_active_record_test.rb | 174 ------------- .../test/unit/adapter_default_test.rb | 41 --- .../test/unit/adapter_mongoid_test.rb | 161 ------------ .../test/unit/adapter_multiple_test.rb | 106 -------- elasticsearch-model/test/unit/adapter_test.rb | 69 ----- 11 files changed, 762 insertions(+), 551 deletions(-) create mode 100644 elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/adapters/default_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb delete mode 100644 elasticsearch-model/test/unit/adapter_active_record_test.rb delete mode 100644 elasticsearch-model/test/unit/adapter_default_test.rb delete mode 100644 elasticsearch-model/test/unit/adapter_mongoid_test.rb delete mode 100644 elasticsearch-model/test/unit/adapter_multiple_test.rb delete mode 100644 elasticsearch-model/test/unit/adapter_test.rb diff --git a/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb new file mode 100644 index 000000000..01526c12b --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb @@ -0,0 +1,121 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Adapter do + + before(:all) do + class ::DummyAdapterClass; end + class ::DummyAdapterClassWithAdapter; end + class ::DummyAdapter + Records = Module.new + Callbacks = Module.new + Importing = Module.new + end + end + + after(:all) do + Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyAdapterClassWithAdapter) + Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyAdapterClass) + Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyAdapter) + Object.send(:remove_const, :DummyAdapterClass) if defined?(DummyAdapterClass) + Object.send(:remove_const, :DummyAdapterClassWithAdapter) if defined?(DummyAdapterClassWithAdapter) + Object.send(:remove_const, :DummyAdapter) if defined?(DummyAdapter) + end + + describe '#from_class' do + + it 'should return an Adapter instance' do + expect(Elasticsearch::Model::Adapter.from_class(DummyAdapterClass)).to be_a(Elasticsearch::Model::Adapter::Adapter) + end + end + + describe 'register' do + + before do + expect(Elasticsearch::Model::Adapter::Adapter).to receive(:register).and_call_original + Elasticsearch::Model::Adapter.register(:foo, lambda { |c| false }) + end + + it 'should register an adapter' do + expect(Elasticsearch::Model::Adapter::Adapter.adapters[:foo]).to be_a(Proc) + end + + context 'when a specific adapter class is set' do + + before do + expect(Elasticsearch::Model::Adapter::Adapter).to receive(:register).and_call_original + Elasticsearch::Model::Adapter::Adapter.register(DummyAdapter, + lambda { |c| c == DummyAdapterClassWithAdapter }) + end + + let(:adapter) do + Elasticsearch::Model::Adapter::Adapter.new(DummyAdapterClassWithAdapter) + end + + it 'should register the adapter' do + expect(adapter.adapter).to eq(DummyAdapter) + end + end + end + + describe 'default adapter' do + + let(:adapter) do + Elasticsearch::Model::Adapter::Adapter.new(DummyAdapterClass) + end + + it 'sets a default adapter' do + expect(adapter.adapter).to eq(Elasticsearch::Model::Adapter::Default) + end + end + + describe '#records_mixin' do + + before do + Elasticsearch::Model::Adapter::Adapter.register(DummyAdapter, + lambda { |c| c == DummyAdapterClassWithAdapter }) + + end + + let(:adapter) do + Elasticsearch::Model::Adapter::Adapter.new(DummyAdapterClassWithAdapter) + end + + it 'returns a Module' do + expect(adapter.records_mixin).to be_a(Module) + end + end + + describe '#callbacks_mixin' do + + before do + Elasticsearch::Model::Adapter::Adapter.register(DummyAdapter, + lambda { |c| c == DummyAdapterClassWithAdapter }) + + end + + let(:adapter) do + Elasticsearch::Model::Adapter::Adapter.new(DummyAdapterClassWithAdapter) + end + + it 'returns a Module' do + expect(adapter.callbacks_mixin).to be_a(Module) + end + end + + describe '#importing_mixin' do + + before do + Elasticsearch::Model::Adapter::Adapter.register(DummyAdapter, + lambda { |c| c == DummyAdapterClassWithAdapter }) + + end + + let(:adapter) do + Elasticsearch::Model::Adapter::Adapter.new(DummyAdapterClassWithAdapter) + end + + it 'returns a Module' do + expect(adapter.importing_mixin).to be_a(Module) + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb new file mode 100644 index 000000000..19ad6f9e9 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb @@ -0,0 +1,238 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Adapter::ActiveRecord do + + before(:all) do + class DummyClassForActiveRecord; end + end + + after(:all) do + Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyClassForActiveRecord) + Object.send(:remove_const, :DummyClassForActiveRecord) if defined?(DummyClassForActiveRecord) + end + + let(:model) do + DummyClassForActiveRecord.new.tap do |m| + allow(m).to receive(:response).and_return(double('response', response: response)) + allow(m).to receive(:ids).and_return(ids) + end + end + + let(:response) do + { 'hits' => {'hits' => [ {'_id' => 2 }, {'_id' => 1 } ]} } + end + + let(:ids) do + [2, 1] + end + + let(:record_1) do + double('record').tap do |rec| + allow(rec).to receive(:id).and_return(1) + end + end + + let(:record_2) do + double('record').tap do |rec| + allow(rec).to receive(:id).and_return(2) + end + end + + let(:records) do + [record_1, record_2].tap do |r| + allow(r).to receive(:load).and_return(true) + allow(r).to receive(:exec_queries).and_return(true) + end + end + + describe 'adapter registration' do + + before(:all) do + DummyClassForActiveRecord.__send__ :include, Elasticsearch::Model::Adapter::ActiveRecord::Records + end + + it 'can register an adapater' do + expect(Elasticsearch::Model::Adapter.adapters[Elasticsearch::Model::Adapter::ActiveRecord]).not_to be_nil + expect(Elasticsearch::Model::Adapter.adapters[Elasticsearch::Model::Adapter::ActiveRecord].call(DummyClassForActiveRecord)).to be(false) + end + end + + describe '#records' do + + before(:all) do + DummyClassForActiveRecord.__send__ :include, Elasticsearch::Model::Adapter::ActiveRecord::Records + end + + let(:instance) do + model.tap do |inst| + allow(inst).to receive(:klass).and_return(double('class', primary_key: :some_key, where: records)).at_least(:once) + end + end + + it 'returns the list of records' do + expect(instance.records).to eq(records) + end + + it 'loads the records' do + expect(instance.load).to eq(true) + end + + context 'when :includes is specified' do + + before do + expect(records).to receive(:includes).with([:submodel]).once.and_return(records) + instance.options[:includes] = [:submodel] + end + + it 'incorporates the includes option in the query' do + expect(instance.records).to eq(records) + end + end + + context 'when an order is not defined for the ActiveRecord query' do + + context 'when the records have a different order than the hits' do + + before do + records.instance_variable_set(:@records, records) + end + + it 'reorders the records based on hits order' do + expect(records.collect(&:id)).to eq([1, 2]) + expect(instance.records.to_a.collect(&:id)).to eq([2, 1]) + end + end + end + + context 'when an order is defined for the ActiveRecord query' do + + context 'when the records have a different order than the hits' do + + before do + records.instance_variable_set(:@records, records) + expect(instance.records).to receive(:order).and_return(records) + end + + it 'reorders the records based on hits order' do + expect(records.collect(&:id)).to eq([1, 2]) + expect(instance.records.to_a.collect(&:id)).to eq([2, 1]) + expect(instance.order(:foo).to_a.collect(&:id)).to eq([1, 2]) + end + end + end + end + + describe 'callbacks registration' do + + before do + expect(DummyClassForActiveRecord).to receive(:after_commit).exactly(3).times + end + + it 'should register the model class for callbacks' do + Elasticsearch::Model::Adapter::ActiveRecord::Callbacks.included(DummyClassForActiveRecord) + end + end + + describe 'importing' do + + before do + DummyClassForActiveRecord.__send__ :extend, Elasticsearch::Model::Adapter::ActiveRecord::Importing + end + + context 'when an invalid scope is specified' do + + it 'raises a NoMethodError' do + expect { + DummyClassForActiveRecord.__find_in_batches(scope: :not_found_method) + }.to raise_exception(NoMethodError) + end + end + + context 'when a valid scope is specified' do + + before do + expect(DummyClassForActiveRecord).to receive(:find_in_batches).once.and_return([]) + expect(DummyClassForActiveRecord).to receive(:published).once.and_return(DummyClassForActiveRecord) + end + + it 'uses the scope' do + expect(DummyClassForActiveRecord.__find_in_batches(scope: :published)).to eq([]) + end + end + + context 'allow query criteria to be specified' do + + before do + expect(DummyClassForActiveRecord).to receive(:find_in_batches).once.and_return([]) + expect(DummyClassForActiveRecord).to receive(:where).with(color: 'red').once.and_return(DummyClassForActiveRecord) + end + + it 'uses the scope' do + expect(DummyClassForActiveRecord.__find_in_batches(query: -> { where(color: 'red') })).to eq([]) + end + end + + context 'when preprocessing batches' do + + context 'if the query returns results' do + + before do + class << DummyClassForActiveRecord + def find_in_batches(options = {}, &block) + yield [:a, :b] + end + + def update_batch(batch) + batch.collect { |b| b.to_s + '!' } + end + end + end + + it 'applies the preprocessing method' do + DummyClassForActiveRecord.__find_in_batches(preprocess: :update_batch) do |batch| + expect(batch).to match(['a!', 'b!']) + end + end + end + + context 'if the query does not return results' do + + before do + class << DummyClassForActiveRecord + def find_in_batches(options = {}, &block) + yield [:a, :b] + end + + def update_batch(batch) + [] + end + end + end + + it 'applies the preprocessing method' do + DummyClassForActiveRecord.__find_in_batches(preprocess: :update_batch) do |batch| + expect(batch).to match([]) + end + end + end + end + + context 'when transforming models' do + + let(:instance) do + model.tap do |inst| + allow(inst).to receive(:id).and_return(1) + allow(inst).to receive(:__elasticsearch__).and_return(double('object', id: 1, as_indexed_json: {})) + end + end + + it 'returns an proc' do + expect(DummyClassForActiveRecord.__transform.respond_to?(:call)).to be(true) + end + + it 'provides a default transformation' do + expect(DummyClassForActiveRecord.__transform.call(instance)).to eq(index: { _id: 1, data: {} }) + end + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/default_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/default_spec.rb new file mode 100644 index 000000000..b0d108538 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/default_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Adapter::Default do + + before(:all) do + class DummyClassForDefaultAdapter; end + DummyClassForDefaultAdapter.__send__ :include, Elasticsearch::Model::Adapter::Default::Records + DummyClassForDefaultAdapter.__send__ :include, Elasticsearch::Model::Adapter::Default::Importing + end + + after(:all) do + Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyClassForDefaultAdapter) + Object.send(:remove_const, :DummyClassForDefaultAdapter) if defined?(DummyClassForDefaultAdapter) + end + + let(:instance) do + DummyClassForDefaultAdapter.new.tap do |m| + allow(m).to receive(:klass).and_return(double('class', primary_key: :some_key, find: [1])).at_least(:once) + end + end + + it 'should have the default records implementation' do + expect(instance.records).to eq([1]) + end + + it 'should have the default Callback implementation' do + expect(Elasticsearch::Model::Adapter::Default::Callbacks).to be_a(Module) + end + + it 'should have the default Importing implementation' do + expect { + DummyClassForDefaultAdapter.new.__find_in_batches + }.to raise_exception(Elasticsearch::Model::NotImplemented) + end + + it 'should have the default transform implementation' do + expect { + DummyClassForDefaultAdapter.new.__transform + }.to raise_exception(Elasticsearch::Model::NotImplemented) + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb new file mode 100644 index 000000000..cb3459341 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb @@ -0,0 +1,235 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Adapter::Mongoid do + + before(:all) do + class DummyClassForMongoid; end + ::Symbol.class_eval { def in; self; end } + end + + after(:all) do + Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyClassForMongoid) + Object.send(:remove_const, :DummyClassForMongoid) if defined?(DummyClassForMongoid) + end + + let(:response) do + { 'hits' => {'hits' => [ {'_id' => 2}, {'_id' => 1} ]} } + end + + let(:ids) do + [2, 1] + end + + let(:record_1) do + double('record').tap do |rec| + allow(rec).to receive(:id).and_return(1) + end + end + + let(:record_2) do + double('record').tap do |rec| + allow(rec).to receive(:load).and_return(true) + allow(rec).to receive(:id).and_return(2) + end + end + + let(:records) do + [record_1, record_2] + end + + let(:model) do + DummyClassForMongoid.new.tap do |m| + allow(m).to receive(:response).and_return(double('response', response: response)) + allow(m).to receive(:ids).and_return(ids) + end + end + + describe 'adapter registration' do + + it 'registers an adapater' do + expect(Elasticsearch::Model::Adapter.adapters[Elasticsearch::Model::Adapter::Mongoid]).not_to be_nil + expect(Elasticsearch::Model::Adapter.adapters[Elasticsearch::Model::Adapter::Mongoid].call(DummyClassForMongoid)).to be(false) + end + + it 'registers the records module' do + expect(Elasticsearch::Model::Adapter::Mongoid::Records).to be_a(Module) + end + end + + describe '#records' do + + before(:all) do + DummyClassForMongoid.__send__ :include, Elasticsearch::Model::Adapter::Mongoid::Records + end + + let(:instance) do + model.tap do |inst| + allow(inst).to receive(:klass).and_return(double('class', where: records)).at_least(:once) + end + end + + it 'returns the records' do + expect(instance.records).to eq(records) + end + + context 'when an order is not defined for the Mongoid query' do + + context 'when the records have a different order than the hits' do + + before do + records.instance_variable_set(:@records, records) + end + + it 'reorders the records based on hits order' do + expect(records.collect(&:id)).to eq([1, 2]) + expect(instance.records.to_a.collect(&:id)).to eq([2, 1]) + end + end + + context 'when an order is defined for the Mongoid query' do + + context 'when the records have a different order than the hits' do + + before do + records.instance_variable_set(:@records, records) + expect(instance.records).to receive(:asc).and_return(records) + end + + it 'reorders the records based on hits order' do + expect(records.collect(&:id)).to eq([1, 2]) + expect(instance.records.to_a.collect(&:id)).to eq([2, 1]) + expect(instance.asc.to_a.collect(&:id)).to eq([1, 2]) + end + end + end + end + + describe 'callbacks registration' do + + before do + expect(DummyClassForMongoid).to receive(:after_create).once + expect(DummyClassForMongoid).to receive(:after_update).once + expect(DummyClassForMongoid).to receive(:after_destroy).once + end + + it 'should register the model class for callbacks' do + Elasticsearch::Model::Adapter::Mongoid::Callbacks.included(DummyClassForMongoid) + end + end + end + + describe 'importing' do + + before(:all) do + DummyClassForMongoid.__send__ :extend, Elasticsearch::Model::Adapter::Mongoid::Importing + end + + let(:relation) do + double('relation', each_slice: []).tap do |rel| + allow(rel).to receive(:published).and_return(rel) + allow(rel).to receive(:no_timeout).and_return(rel) + allow(rel).to receive(:class_exec).and_return(rel) + end + end + + before do + allow(DummyClassForMongoid).to receive(:all).and_return(relation) + end + + context 'when a scope is specified' do + + it 'applies the scope' do + expect(DummyClassForMongoid.__find_in_batches(scope: :published) do; end).to eq([]) + end + end + + context 'query criteria specified as a proc' do + + let(:query) do + Proc.new { where(color: "red") } + end + + it 'execites the query' do + expect(DummyClassForMongoid.__find_in_batches(query: query) do; end).to eq([]) + end + end + + context 'query criteria specified as a hash' do + + before do + expect(relation).to receive(:where).with(color: 'red').and_return(relation) + end + + let(:query) do + { color: "red" } + end + + it 'execites the query' do + expect(DummyClassForMongoid.__find_in_batches(query: query) do; end).to eq([]) + end + end + + context 'when preprocessing batches' do + + context 'if the query returns results' do + + before do + class << DummyClassForMongoid + def find_in_batches(options = {}, &block) + yield [:a, :b] + end + + def update_batch(batch) + batch.collect { |b| b.to_s + '!' } + end + end + end + + it 'applies the preprocessing method' do + DummyClassForMongoid.__find_in_batches(preprocess: :update_batch) do |batch| + expect(batch).to match(['a!', 'b!']) + end + end + end + + context 'if the query does not return results' do + + before do + class << DummyClassForMongoid + def find_in_batches(options = {}, &block) + yield [:a, :b] + end + + def update_batch(batch) + [] + end + end + end + + it 'applies the preprocessing method' do + DummyClassForMongoid.__find_in_batches(preprocess: :update_batch) do |batch| + expect(batch).to match([]) + end + end + end + end + + context 'when transforming models' do + + let(:instance) do + model.tap do |inst| + allow(inst).to receive(:as_indexed_json).and_return({}) + allow(inst).to receive(:id).and_return(1) + end + end + + it 'returns an proc' do + expect(DummyClassForMongoid.__transform.respond_to?(:call)).to be(true) + end + + it 'provides a default transformation' do + expect(DummyClassForMongoid.__transform.call(instance)).to eq(index: { _id: '1', data: {} }) + end + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb new file mode 100644 index 000000000..83a8e6f68 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb @@ -0,0 +1,126 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Adapter::Multiple do + + before(:all) do + class DummyOne + include Elasticsearch::Model + + index_name 'dummy' + document_type 'dummy_one' + + def self.find(ids) + ids.map { |id| new(id) } + end + + attr_reader :id + + def initialize(id) + @id = id.to_i + end + end + + module Namespace + class DummyTwo + include Elasticsearch::Model + + index_name 'dummy' + document_type 'dummy_two' + + def self.find(ids) + ids.map { |id| new(id) } + end + + attr_reader :id + + def initialize(id) + @id = id.to_i + end + end + end + + class DummyTwo + include Elasticsearch::Model + + index_name 'other_index' + document_type 'dummy_two' + + def self.find(ids) + ids.map { |id| new(id) } + end + + attr_reader :id + + def initialize(id) + @id = id.to_i + end + end + end + + after(:all) do + Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyOne) + Elasticsearch::Model::Adapter::Adapter.adapters.delete(Namespace::DummyTwo) + Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyTwo) + Object.send(:remove_const, :DummyOne) if defined?(DummyOne) + Object.send(:remove_const, :Namespace) if defined?(Namespace::DummyTwo) + Object.send(:remove_const, :DummyTwo) if defined?(DummyTwo) + end + + let(:hits) do + [ + { + _index: 'dummy', + _type: 'dummy_two', + _id: '2' + }, + { + _index: 'dummy', + _type: 'dummy_one', + _id: '2' + }, + { + _index: 'other_index', + _type: 'dummy_two', + _id: '1' + }, + { + _index: 'dummy', + _type: 'dummy_two', + _id: '1' + }, + { + _index: 'dummy', + _type: 'dummy_one', + _id: '3' + } + ] + end + + let(:response) do + double('response', response: { 'hits' => { 'hits' => hits } }) + end + + let(:multimodel) do + Elasticsearch::Model::Multimodel.new(DummyOne, DummyTwo, Namespace::DummyTwo) + end + + describe '#records' do + + before do + multimodel.class.send :include, Elasticsearch::Model::Adapter::Multiple::Records + expect(multimodel).to receive(:response).at_least(:once).and_return(response) + end + + it 'instantiates the correct types of instances' do + expect(multimodel.records[0]).to be_a(Namespace::DummyTwo) + expect(multimodel.records[1]).to be_a(DummyOne) + expect(multimodel.records[2]).to be_a(DummyTwo) + expect(multimodel.records[3]).to be_a(Namespace::DummyTwo) + expect(multimodel.records[4]).to be_a(DummyOne) + end + + it 'returns the results in the correct order' do + expect(multimodel.records.map(&:id)).to eq([2, 2, 1, 1, 3]) + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb index a378cc56a..8687a92b4 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb @@ -108,6 +108,7 @@ def records_mixin end after do + Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyAdapter) Object.send(:remove_const, :DummyAdapter) if defined?(DummyAdapter) end diff --git a/elasticsearch-model/test/unit/adapter_active_record_test.rb b/elasticsearch-model/test/unit/adapter_active_record_test.rb deleted file mode 100644 index 152526e47..000000000 --- a/elasticsearch-model/test/unit/adapter_active_record_test.rb +++ /dev/null @@ -1,174 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::AdapterActiveRecordTest < Test::Unit::TestCase - context "Adapter ActiveRecord module: " do - class ::DummyClassForActiveRecord - RESPONSE = Struct.new('DummyActiveRecordResponse') do - def response - { 'hits' => {'hits' => [ {'_id' => 2}, {'_id' => 1} ]} } - end - end.new - - def response - RESPONSE - end - - def ids - [2, 1] - end - end - - RESPONSE = { 'hits' => { 'total' => 123, 'max_score' => 456, 'hits' => [] } } - - setup do - @records = [ stub(id: 1, inspect: '<Model-1>'), stub(id: 2, inspect: '<Model-2>') ] - @records.stubs(:load).returns(true) - @records.stubs(:exec_queries).returns(true) - end - - should "have the register condition" do - assert_not_nil Elasticsearch::Model::Adapter.adapters[Elasticsearch::Model::Adapter::ActiveRecord] - assert_equal false, Elasticsearch::Model::Adapter.adapters[Elasticsearch::Model::Adapter::ActiveRecord].call(DummyClassForActiveRecord) - end - - context "Records" do - setup do - DummyClassForActiveRecord.__send__ :include, Elasticsearch::Model::Adapter::ActiveRecord::Records - end - - should "have the implementation" do - assert_instance_of Module, Elasticsearch::Model::Adapter::ActiveRecord::Records - - instance = DummyClassForActiveRecord.new - instance.expects(:klass).returns(mock('class', primary_key: :some_key, where: @records)).at_least_once - - assert_equal @records, instance.records - end - - should "load the records" do - instance = DummyClassForActiveRecord.new - instance.expects(:records).returns(@records) - instance.load - end - - should "load the records with its submodels when using :includes" do - klass = mock('class', primary_key: :some_key, where: @records) - @records.expects(:includes).with([:submodel]).at_least_once - - instance = DummyClassForActiveRecord.new - instance.expects(:klass).returns(klass).at_least_once - instance.options[:includes] = [:submodel] - instance.records - end - - should "reorder the records based on hits order" do - @records.instance_variable_set(:@records, @records) - - instance = DummyClassForActiveRecord.new - instance.expects(:klass).returns(mock('class', primary_key: :some_key, where: @records)).at_least_once - - assert_equal [1, 2], @records. to_a.map(&:id) - assert_equal [2, 1], instance.records.to_a.map(&:id) - end - - should "not reorder records when SQL order is present" do - @records.instance_variable_set(:@records, @records) - - instance = DummyClassForActiveRecord.new - instance.expects(:klass).returns(stub('class', primary_key: :some_key, where: @records)).at_least_once - instance.records.expects(:order).returns(@records) - - assert_equal [2, 1], instance.records. to_a.map(&:id) - assert_equal [1, 2], instance.order(:foo).to_a.map(&:id) - end - end - - context "Callbacks" do - should "register hooks for automatically updating the index" do - DummyClassForActiveRecord.expects(:after_commit).times(3) - - Elasticsearch::Model::Adapter::ActiveRecord::Callbacks.included(DummyClassForActiveRecord) - end - end - - context "Importing" do - setup do - DummyClassForActiveRecord.__send__ :extend, Elasticsearch::Model::Adapter::ActiveRecord::Importing - end - - should "raise an exception when passing an invalid scope" do - assert_raise NoMethodError do - DummyClassForActiveRecord.__find_in_batches(scope: :not_found_method) do; end - end - end - - should "implement the __find_in_batches method" do - DummyClassForActiveRecord.expects(:find_in_batches).returns([]) - DummyClassForActiveRecord.__find_in_batches do; end - end - - should "limit the relation to a specific scope" do - DummyClassForActiveRecord.expects(:find_in_batches).returns([]) - DummyClassForActiveRecord.expects(:published).returns(DummyClassForActiveRecord) - - DummyClassForActiveRecord.__find_in_batches(scope: :published) do; end - end - - should "limit the relation to a specific query" do - DummyClassForActiveRecord.expects(:find_in_batches).returns([]) - DummyClassForActiveRecord.expects(:where).returns(DummyClassForActiveRecord) - - DummyClassForActiveRecord.__find_in_batches(query: -> { where(color: "red") }) do; end - end - - context "when preprocessing batches" do - setup do - class << DummyClassForActiveRecord - def find_in_batches(options = {}, &block) - yield [:a, :b] - end - end - end - - should "preprocess the batch if option provided" do - class << DummyClassForActiveRecord - def update_batch(batch) - batch.collect { |b| b.to_s + '!' } - end - end - - DummyClassForActiveRecord.__find_in_batches(preprocess: :update_batch) do |batch| - assert_same_elements ["a!", "b!"], batch - end - end - - should "skip batch if preprocess option provided and returns empty collection" do - class << DummyClassForActiveRecord - def update_batch(batch) - [] - end - end - - DummyClassForActiveRecord.__find_in_batches(preprocess: :update_batch) do |batch| - flunk("block should not have been called on empty batch") - end - end - end - - context "when transforming models" do - setup do - @transform = DummyClassForActiveRecord.__transform - end - - should "provide an object that responds to #call" do - assert_respond_to @transform, :call - end - - should "provide default transformation" do - model = mock("model", id: 1, __elasticsearch__: stub(as_indexed_json: {})) - assert_equal @transform.call(model), { index: { _id: 1, data: {} } } - end - end - end - end -end diff --git a/elasticsearch-model/test/unit/adapter_default_test.rb b/elasticsearch-model/test/unit/adapter_default_test.rb deleted file mode 100644 index 48edd205d..000000000 --- a/elasticsearch-model/test/unit/adapter_default_test.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::AdapterDefaultTest < Test::Unit::TestCase - context "Adapter default module" do - class ::DummyClassForDefaultAdapter; end - - should "have the default Records implementation" do - assert_instance_of Module, Elasticsearch::Model::Adapter::Default::Records - - DummyClassForDefaultAdapter.__send__ :include, Elasticsearch::Model::Adapter::Default::Records - - instance = DummyClassForDefaultAdapter.new - klass = mock('class', find: [1]) - instance.expects(:klass).returns(klass) - instance.records - end - - should "have the default Callbacks implementation" do - assert_instance_of Module, Elasticsearch::Model::Adapter::Default::Callbacks - end - - context "concerning abstract methods" do - setup do - DummyClassForDefaultAdapter.__send__ :include, Elasticsearch::Model::Adapter::Default::Importing - end - - should "have the default Importing implementation" do - assert_raise Elasticsearch::Model::NotImplemented do - DummyClassForDefaultAdapter.new.__find_in_batches - end - end - - should "have the default transform implementation" do - assert_raise Elasticsearch::Model::NotImplemented do - DummyClassForDefaultAdapter.new.__transform - end - end - end - - end -end diff --git a/elasticsearch-model/test/unit/adapter_mongoid_test.rb b/elasticsearch-model/test/unit/adapter_mongoid_test.rb deleted file mode 100644 index 0074df097..000000000 --- a/elasticsearch-model/test/unit/adapter_mongoid_test.rb +++ /dev/null @@ -1,161 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::AdapterMongoidTest < Test::Unit::TestCase - context "Adapter Mongoid module: " do - class ::DummyClassForMongoid - RESPONSE = Struct.new('DummyMongoidResponse') do - def response - { 'hits' => {'hits' => [ {'_id' => 2}, {'_id' => 1} ]} } - end - end.new - - def response - RESPONSE - end - - def ids - [2, 1] - end - end - - setup do - @records = [ stub(id: 1, inspect: '<Model-1>'), stub(id: 2, inspect: '<Model-2>') ] - ::Symbol.any_instance.stubs(:in).returns(@records) - end - - should "have the register condition" do - assert_not_nil Elasticsearch::Model::Adapter.adapters[Elasticsearch::Model::Adapter::Mongoid] - assert_equal false, Elasticsearch::Model::Adapter.adapters[Elasticsearch::Model::Adapter::Mongoid].call(DummyClassForMongoid) - end - - context "Records" do - setup do - DummyClassForMongoid.__send__ :include, Elasticsearch::Model::Adapter::Mongoid::Records - end - - should "have the implementation" do - assert_instance_of Module, Elasticsearch::Model::Adapter::Mongoid::Records - - instance = DummyClassForMongoid.new - instance.expects(:klass).returns(mock('class', where: @records)) - - assert_equal @records, instance.records - end - - should "reorder the records based on hits order" do - @records.instance_variable_set(:@records, @records) - - instance = DummyClassForMongoid.new - instance.expects(:klass).returns(mock('class', where: @records)) - - assert_equal [1, 2], @records. to_a.map(&:id) - assert_equal [2, 1], instance.records.to_a.map(&:id) - end - - should "not reorder records when SQL order is present" do - @records.instance_variable_set(:@records, @records) - - instance = DummyClassForMongoid.new - instance.expects(:klass).returns(stub('class', where: @records)).at_least_once - instance.records.expects(:asc).returns(@records) - - assert_equal [2, 1], instance.records.to_a.map(&:id) - assert_equal [1, 2], instance.asc.to_a.map(&:id) - end - end - - context "Callbacks" do - should "register hooks for automatically updating the index" do - DummyClassForMongoid.expects(:after_create) - DummyClassForMongoid.expects(:after_update) - DummyClassForMongoid.expects(:after_destroy) - - Elasticsearch::Model::Adapter::Mongoid::Callbacks.included(DummyClassForMongoid) - end - end - - context "Importing" do - should "implement the __find_in_batches method" do - relation = mock() - relation.stubs(:no_timeout).returns([]) - DummyClassForMongoid.expects(:all).returns(relation) - - DummyClassForMongoid.__send__ :extend, Elasticsearch::Model::Adapter::Mongoid::Importing - DummyClassForMongoid.__find_in_batches do; end - end - - context "when transforming models" do - setup do - @transform = DummyClassForMongoid.__transform - end - - should "provide an object that responds to #call" do - assert_respond_to @transform, :call - end - - should "provide basic transformation" do - model = mock("model", id: 1, as_indexed_json: {}) - assert_equal @transform.call(model), { index: { _id: "1", data: {} } } - end - end - - should "limit the relation to a specific scope" do - relation = mock() - relation.stubs(:no_timeout).returns(relation) - relation.expects(:published).returns(relation) - relation.expects(:each_slice).returns([]) - DummyClassForMongoid.expects(:all).returns(relation) - - DummyClassForMongoid.__send__ :extend, Elasticsearch::Model::Adapter::Mongoid::Importing - DummyClassForMongoid.__find_in_batches(scope: :published) do; end - end - - context "when limit the relation with proc" do - setup do - @query = Proc.new { where(color: "red") } - end - should "query with a specific criteria" do - relation = mock() - relation.stubs(:no_timeout).returns(relation) - relation.expects(:class_exec).returns(relation) - relation.expects(:each_slice).returns([]) - DummyClassForMongoid.expects(:all).returns(relation) - - DummyClassForMongoid.__find_in_batches(query: @query) do; end - end - end - - context "when limit the relation with hash" do - setup do - @query = { color: "red" } - end - should "query with a specific criteria" do - relation = mock() - relation.stubs(:no_timeout).returns(relation) - relation.expects(:where).with(@query).returns(relation) - relation.expects(:each_slice).returns([]) - DummyClassForMongoid.expects(:all).returns(relation) - - DummyClassForMongoid.__find_in_batches(query: @query) do; end - end - end - - should "preprocess the batch if option provided" do - class << DummyClassForMongoid - # Updates/transforms the batch while fetching it from the database - # (eg. with information from an external system) - # - def update_batch(batch) - batch.collect { |b| b.to_s + '!' } - end - end - - DummyClassForMongoid.expects(:__find_in_batches).returns( [:a, :b] ) - - DummyClassForMongoid.__find_in_batches(preprocess: :update_batch) do |batch| - assert_same_elements ["a!", "b!"], batch - end - end - end - end -end diff --git a/elasticsearch-model/test/unit/adapter_multiple_test.rb b/elasticsearch-model/test/unit/adapter_multiple_test.rb deleted file mode 100644 index b848286fb..000000000 --- a/elasticsearch-model/test/unit/adapter_multiple_test.rb +++ /dev/null @@ -1,106 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::MultipleTest < Test::Unit::TestCase - - context "Adapter for multiple models" do - - class ::DummyOne - include Elasticsearch::Model - - index_name 'dummy' - document_type 'dummy_one' - - def self.find(ids) - ids.map { |id| new(id) } - end - - attr_reader :id - - def initialize(id) - @id = id.to_i - end - end - - module ::Namespace - class DummyTwo - include Elasticsearch::Model - - index_name 'dummy' - document_type 'dummy_two' - - def self.find(ids) - ids.map { |id| new(id) } - end - - attr_reader :id - - def initialize(id) - @id = id.to_i - end - end - end - - class ::DummyTwo - include Elasticsearch::Model - - index_name 'other_index' - document_type 'dummy_two' - - def self.find(ids) - ids.map { |id| new(id) } - end - - attr_reader :id - - def initialize(id) - @id = id.to_i - end - end - - HITS = [{_index: 'dummy', - _type: 'dummy_two', - _id: '2', - }, { - _index: 'dummy', - _type: 'dummy_one', - _id: '2', - }, { - _index: 'other_index', - _type: 'dummy_two', - _id: '1', - }, { - _index: 'dummy', - _type: 'dummy_two', - _id: '1', - }, { - _index: 'dummy', - _type: 'dummy_one', - _id: '3'}] - - setup do - @multimodel = Elasticsearch::Model::Multimodel.new(DummyOne, DummyTwo, Namespace::DummyTwo) - end - - context "when returning records" do - setup do - @multimodel.class.send :include, Elasticsearch::Model::Adapter::Multiple::Records - @multimodel.expects(:response).at_least_once.returns(stub(response: { 'hits' => { 'hits' => HITS } })) - end - - should "keep the order from response" do - assert_instance_of Module, Elasticsearch::Model::Adapter::Multiple::Records - records = @multimodel.records - - assert_equal 5, records.count - - assert_kind_of ::Namespace::DummyTwo, records[0] - assert_kind_of ::DummyOne, records[1] - assert_kind_of ::DummyTwo, records[2] - assert_kind_of ::Namespace::DummyTwo, records[3] - assert_kind_of ::DummyOne, records[4] - - assert_equal [2, 2, 1, 1, 3], records.map(&:id) - end - end - end -end diff --git a/elasticsearch-model/test/unit/adapter_test.rb b/elasticsearch-model/test/unit/adapter_test.rb deleted file mode 100644 index 71b4e7cea..000000000 --- a/elasticsearch-model/test/unit/adapter_test.rb +++ /dev/null @@ -1,69 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::AdapterTest < Test::Unit::TestCase - context "Adapter module" do - class ::DummyAdapterClass; end - class ::DummyAdapterClassWithAdapter; end - class ::DummyAdapter - Records = Module.new - Callbacks = Module.new - Importing = Module.new - end - - should "return an Adapter instance" do - assert_instance_of Elasticsearch::Model::Adapter::Adapter, - Elasticsearch::Model::Adapter.from_class(DummyAdapterClass) - end - - should "return a list of adapters" do - Elasticsearch::Model::Adapter::Adapter.expects(:adapters) - Elasticsearch::Model::Adapter.adapters - end - - should "register an adapter" do - begin - Elasticsearch::Model::Adapter::Adapter.expects(:register) - Elasticsearch::Model::Adapter.register(:foo, lambda { |c| false }) - ensure - Elasticsearch::Model::Adapter::Adapter.instance_variable_set(:@adapters, {}) - end - end - end - - context "Adapter class" do - should "register an adapter" do - begin - Elasticsearch::Model::Adapter::Adapter.register(:foo, lambda { |c| false }) - assert Elasticsearch::Model::Adapter::Adapter.adapters[:foo] - ensure - Elasticsearch::Model::Adapter::Adapter.instance_variable_set(:@adapters, {}) - end - end - - should "return the default adapter" do - adapter = Elasticsearch::Model::Adapter::Adapter.new(DummyAdapterClass) - assert_equal Elasticsearch::Model::Adapter::Default, adapter.adapter - end - - should "return a specific adapter" do - Elasticsearch::Model::Adapter::Adapter.register(DummyAdapter, - lambda { |c| c == DummyAdapterClassWithAdapter }) - - adapter = Elasticsearch::Model::Adapter::Adapter.new(DummyAdapterClassWithAdapter) - assert_equal DummyAdapter, adapter.adapter - end - - should "return the modules" do - assert_nothing_raised do - Elasticsearch::Model::Adapter::Adapter.register(DummyAdapter, - lambda { |c| c == DummyAdapterClassWithAdapter }) - - adapter = Elasticsearch::Model::Adapter::Adapter.new(DummyAdapterClassWithAdapter) - - assert_instance_of Module, adapter.records_mixin - assert_instance_of Module, adapter.callbacks_mixin - assert_instance_of Module, adapter.importing_mixin - end - end - end -end From c9f05e5af77d8a2ab81cba9cdea424e789198d0b Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Thu, 13 Sep 2018 17:10:24 +0200 Subject: [PATCH 410/582] Update CHANGELOG to include 6.0 changes --- CHANGELOG.md | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d81c0e69e..bccab3d9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,81 @@ +## 6.0.0 + +* Update to test against Elasticsearch 6.4 +* Fix sort order on ActiveRecord >= 5. re issue #546 (#831) + +### ActiveModel + +* Inherit from HashWrapper for disabling warnings +* Fix import method to pass index name on refresh (#692) +* Use default scope on ActiveRecord model when importing (#827) +* Support scope, query and preprocess importing options in Mongoid Adapter in 6.x (#829) +* Address performance of HashWrapper in Response objects (#825) + +### Persistence + +* Address performance of HashWrapper in Response objects (#825) +* Minor refactor in Repository::Search +* Remove example music app that demonstrates the AR pattern +* Update Sinatra app +* Update README +* Change document type references to _doc + +## 6.0.0.pre + +* Added the "Compatibility" chapter to the READMEs +* Updated the Bundler instructions and Github URLs in the READMEs +* Updated the version on the `master` branch to `6.0.0.alpha1` +* Update versions to 6.0.0.beta +* minor: Fix spacing +* Update various gemspecs to conditionally depend on gems incompatible with JRuby (#810) +* Update versions +* Use local as source for gem dependencies when possible +* Only require 'oj' gem if not using JRuby +* Update versions to .pre + +### ActiveModel + +* Added an example with a custom "pattern" analyzer +* Added a "trigram" custom analyzer to the example +* Fix README typo (s/situation/situations) +* Fix reference to @ids in example and README +* Add Callbacks to the example datamapper adapter +* Fix `Asynchronous Callbacks` example +* Fixed a typo in the README +* Improved the custom analyzer example +* Removed left-overs from previous implementation in the "completion suggester" example +* Updated the `changes` method name in `Indexing` to `changes_to_save` for compatibility with Rails 5.1 +* Fixed the handling of changed attributes in `Indexing` to work with older Rails versions +* Update child-parent integration test to use single index type for ES 6.3 (#805) +* Use default doc type: _doc (#814) +* Avoid making an update when no attributes are changed (#762) + +### Persistence + +* Updated the failing integration tests for Elasticsearch 5.x +* Updated the dependency for "elasticsearch" and "elasticsearch-model" to `5.x` +* Documentation for Model should include Model and not Repository +* Depend on version >= 6 of elasticsearch gems +* Undo last commit; depend on version 5 of elasticsearch gems +* Reduce repeated string instantiation (#813) +* Make default doc type '_doc' in preparation for deprecation of mapping types (#816) +* Remove Elasticsearch::Persistence::Model (ActiveRecord persistence pattern) (#812) +* Deprecate _all field in ES 6.x (#820) +* Remove development dependency on virtus, include explicitly in Gemfile for integration test +* Refactor Repository as mixin (#824) +* Add missing Repository::Response::Results spec +* Update README for Repository mixin refactor +* Minor typo in README +* Add #inspect method for Repository +* Update references to Elasticsearch::Client + +### Ruby on Rails + +* Fixed typo in README +* Fix typo in rake import task +* Updated the templates for example Rails applications +* Add 'oj' back as a development dependency in gemspec + ## 6.0.0.alpha1 * Updated the Rake dependency to 11.1 From d92efd84be4e6f338a9bdf53a796bbd6d3bdb428 Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Mon, 17 Sep 2018 10:22:35 +0200 Subject: [PATCH 411/582] [MODEL] Ensure that specified ActiveRecord order is not overwritten by Elasticsearch search results order (#835) * [MODEL] Ensure that specified ActiveRecord order is not overwritten by Elasticsearch query order * [MODEL] Remove interception of #order method, as ordering is handled by checking order_values in #to_a --- .../model/adapters/active_record.rb | 30 ++++--------------- .../model/adapters/active_record_spec.rb | 8 ++--- .../integration/active_record_basic_test.rb | 7 +++++ 3 files changed, 16 insertions(+), 29 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb index ae1b38f56..3fb3f987e 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb @@ -35,7 +35,11 @@ def records else self.__send__(:exec_queries) end - @records.sort_by { |record| hits.index { |hit| hit['_id'].to_s == record.id.to_s } } + if !self.order_values.present? + @records.sort_by { |record| hits.index { |hit| hit['_id'].to_s == record.id.to_s } } + else + @records + end end if self end @@ -47,30 +51,6 @@ def records def load records.__send__(:load) end - - # Intercept call to the `order` method, so we can ignore the order from Elasticsearch - # - def order(*args) - sql_records = records.__send__ :order, *args - - # Redefine the `to_a` method to the original one - # - sql_records.instance_exec do - ar_records_method_name = :to_a - ar_records_method_name = :records if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 5 - - define_singleton_method(ar_records_method_name) do - if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4 - self.load - else - self.__send__(:exec_queries) - end - @records - end - end - - sql_records - end end module Callbacks diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb index 19ad6f9e9..7d18f0b72 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb @@ -66,6 +66,7 @@ class DummyClassForActiveRecord; end let(:instance) do model.tap do |inst| allow(inst).to receive(:klass).and_return(double('class', primary_key: :some_key, where: records)).at_least(:once) + allow(inst).to receive(:order).and_return(double('class', primary_key: :some_key, where: records)).at_least(:once) end end @@ -95,6 +96,7 @@ class DummyClassForActiveRecord; end before do records.instance_variable_set(:@records, records) + allow(records).to receive(:order_values).and_return([]) end it 'reorders the records based on hits order' do @@ -109,14 +111,12 @@ class DummyClassForActiveRecord; end context 'when the records have a different order than the hits' do before do - records.instance_variable_set(:@records, records) - expect(instance.records).to receive(:order).and_return(records) + records.instance_variable_set(:@records, [record_2, record_1]) + allow(records).to receive(:order_values).and_return([double('order_definition')]) end it 'reorders the records based on hits order' do - expect(records.collect(&:id)).to eq([1, 2]) expect(instance.records.to_a.collect(&:id)).to eq([2, 1]) - expect(instance.order(:foo).to_a.collect(&:id)).to eq([1, 2]) end end end diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb index 90abeb47a..d7ecb0586 100644 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ b/elasticsearch-model/test/integration/active_record_basic_test.rb @@ -231,6 +231,13 @@ def as_indexed_json(options = {}) end end + should "allow ordering following any method chain in SQL" do + if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4 + response = Article.search query: { match: { title: { query: 'test' } } } + assert_equal 'Testing Coding', response.records.distinct.order(id: :desc).first.title + end + end + should "allow dot access to response" do response = Article.search query: { match: { title: { query: 'test' } } }, aggregations: { From d930a935545b51e127f4a112a9fe3dba37231993 Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Mon, 17 Sep 2018 11:04:45 +0200 Subject: [PATCH 412/582] [MODEL] Port remainder of Elasticsearch::Model unit tests to rspec (#836) * [MODEL] Port remainder of Elasticsearch::Model unit tests to rspec * [MODEL] Minor spacing fixes --- .../elasticsearch/model/callbacks_spec.rb | 34 + .../spec/elasticsearch/model/client_spec.rb | 66 ++ .../elasticsearch/model/hash_wrapper_spec.rb | 12 + .../elasticsearch/model/importing_spec.rb | 215 +++++ .../spec/elasticsearch/model/indexing_spec.rb | 862 ++++++++++++++++++ .../spec/elasticsearch/model/module_spec.rb | 79 ++ .../elasticsearch/model/multimodel_spec.rb | 56 ++ .../model/naming_inheritance_spec.rb | 104 +++ .../spec/elasticsearch/model/naming_spec.rb | 187 ++++ .../spec/elasticsearch/model/proxy_spec.rb | 110 +++ .../model/searching_search_request_spec.rb | 114 +++ .../elasticsearch/model/searching_spec.rb | 49 + .../elasticsearch/model/serializing_spec.rb | 22 + elasticsearch-model/spec/spec_helper.rb | 3 + elasticsearch-model/spec/support/model.json | 1 + .../{test => spec}/support/model.yml | 0 elasticsearch-model/test/support/model.json | 1 - .../test/unit/callbacks_test.rb | 31 - elasticsearch-model/test/unit/client_test.rb | 27 - .../test/unit/hash_wrapper_test.rb | 13 - .../test/unit/importing_test.rb | 224 ----- .../test/unit/indexing_test.rb | 720 --------------- elasticsearch-model/test/unit/module_test.rb | 68 -- .../test/unit/multimodel_test.rb | 38 - .../test/unit/naming_inheritance_test.rb | 94 -- elasticsearch-model/test/unit/naming_test.rb | 103 --- elasticsearch-model/test/unit/proxy_test.rb | 98 -- .../unit/searching_search_request_test.rb | 78 -- .../test/unit/searching_test.rb | 41 - .../test/unit/serializing_test.rb | 17 - 30 files changed, 1914 insertions(+), 1553 deletions(-) create mode 100644 elasticsearch-model/spec/elasticsearch/model/callbacks_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/client_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/hash_wrapper_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/importing_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/module_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/naming_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/searching_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/serializing_spec.rb create mode 100644 elasticsearch-model/spec/support/model.json rename elasticsearch-model/{test => spec}/support/model.yml (100%) delete mode 100644 elasticsearch-model/test/support/model.json delete mode 100644 elasticsearch-model/test/unit/callbacks_test.rb delete mode 100644 elasticsearch-model/test/unit/client_test.rb delete mode 100644 elasticsearch-model/test/unit/hash_wrapper_test.rb delete mode 100644 elasticsearch-model/test/unit/importing_test.rb delete mode 100644 elasticsearch-model/test/unit/indexing_test.rb delete mode 100644 elasticsearch-model/test/unit/module_test.rb delete mode 100644 elasticsearch-model/test/unit/multimodel_test.rb delete mode 100644 elasticsearch-model/test/unit/naming_inheritance_test.rb delete mode 100644 elasticsearch-model/test/unit/naming_test.rb delete mode 100644 elasticsearch-model/test/unit/proxy_test.rb delete mode 100644 elasticsearch-model/test/unit/searching_search_request_test.rb delete mode 100644 elasticsearch-model/test/unit/searching_test.rb delete mode 100644 elasticsearch-model/test/unit/serializing_test.rb diff --git a/elasticsearch-model/spec/elasticsearch/model/callbacks_spec.rb b/elasticsearch-model/spec/elasticsearch/model/callbacks_spec.rb new file mode 100644 index 000000000..0bbb494e4 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/callbacks_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Callbacks do + + before(:all) do + class ::DummyCallbacksModel + end + + module DummyCallbacksAdapter + module CallbacksMixin + end + + def callbacks_mixin + CallbacksMixin + end; module_function :callbacks_mixin + end + end + + after(:all) do + Object.send(:remove_const, :DummyCallbacksModel) if defined?(DummyCallbacksModel) + Object.send(:remove_const, :DummyCallbacksAdapter) if defined?(DummyCallbacksAdapter) + end + + context 'when a model includes the Callbacks module' do + + before do + Elasticsearch::Model::Callbacks.included(DummyCallbacksModel) + end + + it 'includes the callbacks mixin from the model adapter' do + expect(DummyCallbacksModel.ancestors).to include(Elasticsearch::Model::Adapter::Default::Callbacks) + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/client_spec.rb b/elasticsearch-model/spec/elasticsearch/model/client_spec.rb new file mode 100644 index 000000000..2aee4f7b2 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/client_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Client do + + before(:all) do + class ::DummyClientModel + extend Elasticsearch::Model::Client::ClassMethods + include Elasticsearch::Model::Client::InstanceMethods + end + end + + after(:all) do + Object.send(:remove_const, :DummyClientModel) if defined?(DummyClientModel) + end + + context 'when a class includes the client module class methods' do + + it 'defines the client module class methods on the model' do + expect(DummyClientModel.client).to be_a(Elasticsearch::Transport::Client) + end + end + + context 'when a class includes the client module instance methods' do + + it 'defines the client module class methods on the model' do + expect(DummyClientModel.new.client).to be_a(Elasticsearch::Transport::Client) + end + end + + context 'when the client is set on the class' do + + around do |example| + original_client = DummyClientModel.client + DummyClientModel.client = 'foobar' + example.run + DummyClientModel.client = original_client + end + + it 'sets the client on the class' do + expect(DummyClientModel.client).to eq('foobar') + end + + it 'sets the client on an instance' do + expect(DummyClientModel.new.client).to eq('foobar') + end + end + + context 'when the client is set on an instance' do + + before do + model_instance.client = 'foo' + end + + let(:model_instance) do + DummyClientModel.new + end + + it 'sets the client on an instance' do + expect(model_instance.client).to eq('foo') + end + + it 'does not set the client on the class' do + expect(DummyClientModel.client).to be_a(Elasticsearch::Transport::Client) + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/hash_wrapper_spec.rb b/elasticsearch-model/spec/elasticsearch/model/hash_wrapper_spec.rb new file mode 100644 index 000000000..53f018726 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/hash_wrapper_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +describe Elasticsearch::Model::HashWrapper, if: Hashie::VERSION >= '3.5.3' do + + before do + expect(Hashie.logger).to receive(:warn).never + end + + it 'does not print a warning for re-defined methods' do + Elasticsearch::Model::HashWrapper.new(:foo => 'bar', :sort => true) + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb new file mode 100644 index 000000000..0b629ff22 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb @@ -0,0 +1,215 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Importing do + + before(:all) do + class DummyImportingModel + end + + module DummyImportingAdapter + module ImportingMixin + def __find_in_batches(options={}, &block) + yield if block_given? + end + def __transform + lambda {|a|} + end + end + + def importing_mixin + ImportingMixin + end; module_function :importing_mixin + end + end + + after(:all) do + Object.send(:remove_const, :DummyImportingModel) if defined?(DummyImportingModel) + Object.send(:remove_const, :DummyImportingAdapter) if defined?(DummyImportingAdapter) + end + + before do + allow(Elasticsearch::Model::Adapter).to receive(:from_class).with(DummyImportingModel).and_return(DummyImportingAdapter) + DummyImportingModel.__send__ :include, Elasticsearch::Model::Importing + end + + context 'when a model includes the Importing module' do + + it 'provides importing methods' do + expect(DummyImportingModel.respond_to?(:import)).to be(true) + expect(DummyImportingModel.respond_to?(:__find_in_batches)).to be(true) + end + end + + describe '#import' do + + before do + allow(DummyImportingModel).to receive(:index_name).and_return('foo') + allow(DummyImportingModel).to receive(:document_type).and_return('foo') + allow(DummyImportingModel).to receive(:index_exists?).and_return(true) + allow(DummyImportingModel).to receive(:__batch_to_bulk) + allow(client).to receive(:bulk).and_return(response) + end + + let(:client) do + double('client') + end + + let(:response) do + { 'items' => [] } + end + + context 'when no options are provided' do + + before do + expect(DummyImportingModel).to receive(:client).and_return(client) + allow(DummyImportingModel).to receive(:index_exists?).and_return(true) + end + + it 'uses the client to import documents' do + expect(DummyImportingModel.import).to eq(0) + end + end + + context 'when there is an error' do + + before do + expect(DummyImportingModel).to receive(:client).and_return(client) + allow(DummyImportingModel).to receive(:index_exists?).and_return(true) + end + + let(:response) do + { 'items' => [{ 'index' => { } }, { 'index' => { 'error' => 'FAILED' } }] } + end + + it 'returns the number of errors' do + expect(DummyImportingModel.import).to eq(1) + end + + context 'when the method is called with the option to return the errors' do + + it 'returns the errors' do + expect(DummyImportingModel.import(return: 'errors')).to eq([{ 'index' => { 'error' => 'FAILED' } }]) + end + end + + context 'when the method is called with a block' do + + it 'yields the response to the block' do + DummyImportingModel.import do |response| + expect(response['items'].size).to eq(2) + end + end + end + end + + context 'when the index does not exist' do + + before do + allow(DummyImportingModel).to receive(:index_exists?).and_return(false) + end + + it 'raises an exception' do + expect { + DummyImportingModel.import + }.to raise_exception(ArgumentError) + end + end + + context 'when the method is called with the force option' do + + before do + expect(DummyImportingModel).to receive(:create_index!).with(force: true, index: 'foo').and_return(true) + expect(DummyImportingModel).to receive(:__find_in_batches).with(foo: 'bar').and_return(true) + end + + it 'deletes and creates the index' do + expect(DummyImportingModel.import(force: true, foo: 'bar')).to eq(0) + end + end + + context 'when the method is called with the refresh option' do + + before do + expect(DummyImportingModel).to receive(:refresh_index!).with(index: 'foo').and_return(true) + expect(DummyImportingModel).to receive(:__find_in_batches).with(foo: 'bar').and_return(true) + end + + it 'refreshes the index' do + expect(DummyImportingModel.import(refresh: true, foo: 'bar')).to eq(0) + end + end + + context 'when a different index name is provided' do + + before do + expect(DummyImportingModel).to receive(:client).and_return(client) + expect(client).to receive(:bulk).with(body: nil, index: 'my-new-index', type: 'foo').and_return(response) + end + + it 'uses the alternate index name' do + expect(DummyImportingModel.import(index: 'my-new-index')).to eq(0) + end + end + + context 'when a different document type is provided' do + + before do + expect(DummyImportingModel).to receive(:client).and_return(client) + expect(client).to receive(:bulk).with(body: nil, index: 'foo', type: 'my-new-type').and_return(response) + end + + it 'uses the alternate index name' do + expect(DummyImportingModel.import(type: 'my-new-type')).to eq(0) + end + end + + context 'the transform method' do + + before do + expect(DummyImportingModel).to receive(:client).and_return(client) + expect(DummyImportingModel).to receive(:__transform).and_return(transform) + expect(DummyImportingModel).to receive(:__batch_to_bulk).with(anything, transform) + end + + let(:transform) do + lambda {|a|} + end + + it 'applies the transform method to the results' do + expect(DummyImportingModel.import).to eq(0) + end + end + + context 'when a transform is provided as an option' do + + context 'when the transform option is not a lambda' do + + let(:transform) do + 'not_callable' + end + + it 'raises an error' do + expect { + DummyImportingModel.import(transform: transform) + }.to raise_exception(ArgumentError) + end + end + + context 'when the transform option is a lambda' do + + before do + expect(DummyImportingModel).to receive(:client).and_return(client) + expect(DummyImportingModel).to receive(:__batch_to_bulk).with(anything, transform) + end + + let(:transform) do + lambda {|a|} + end + + it 'applies the transform lambda to the results' do + expect(DummyImportingModel.import(transform: transform)).to eq(0) + end + end + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb new file mode 100644 index 000000000..5daadcf15 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb @@ -0,0 +1,862 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Indexing do + + before(:all) do + class ::DummyIndexingModel + extend ActiveModel::Naming + extend Elasticsearch::Model::Naming::ClassMethods + extend Elasticsearch::Model::Indexing::ClassMethods + + def self.foo + 'bar' + end + end + + class NotFound < Exception; end + end + + after(:all) do + Object.send(:remove_const, :DummyIndexingModel) if defined?(DummyIndexingModel) + Object.send(:remove_const, :NotFound) if defined?(NotFound) + end + + describe 'the Settings class' do + + it 'should be convertible to a hash' do + expect(Elasticsearch::Model::Indexing::Settings.new(foo: 'bar').to_hash).to eq(foo: 'bar') + end + + it 'should be convertible to json' do + expect(Elasticsearch::Model::Indexing::Settings.new(foo: 'bar').as_json).to eq(foo: 'bar') + end + end + + describe '#settings' do + + it 'returns an instance of the Settings class' do + expect(DummyIndexingModel.settings).to be_a(Elasticsearch::Model::Indexing::Settings) + end + + context 'when the settings are updated' do + + before do + DummyIndexingModel.settings(foo: 'boo') + DummyIndexingModel.settings(bar: 'bam') + end + + it 'updates the settings on the class' do + expect(DummyIndexingModel.settings.to_hash).to eq(foo: 'boo', bar: 'bam') + end + end + + context 'when the settings are updated with a yml file' do + + before do + DummyIndexingModel.settings File.open('spec/support/model.yml') + DummyIndexingModel.settings bar: 'bam' + end + + it 'updates the settings on the class' do + expect(DummyIndexingModel.settings.to_hash).to eq(foo: 'boo', bar: 'bam', 'baz' => 'qux') + end + end + + context 'when the settings are updated with a json file' do + + before do + DummyIndexingModel.settings File.open('spec/support/model.json') + DummyIndexingModel.settings bar: 'bam' + end + + it 'updates the settings on the class' do + expect(DummyIndexingModel.settings.to_hash).to eq(foo: 'boo', bar: 'bam', 'baz' => 'qux', 'laz' => 'qux') + end + end + end + + describe '#mappings' do + + let(:expected_mapping_hash) do + { :mytype => { foo: 'bar', :properties => {} } } + end + + it 'returns an instance of the Mappings class' do + expect(DummyIndexingModel.mappings).to be_a(Elasticsearch::Model::Indexing::Mappings) + end + + it 'raises an exception when there is no type passed to the #initialize method' do + expect { + Elasticsearch::Model::Indexing::Mappings.new + }.to raise_exception(ArgumentError) + end + + it 'should be convertible to a hash' do + expect(Elasticsearch::Model::Indexing::Mappings.new(:mytype, { foo: 'bar' }).to_hash).to eq(expected_mapping_hash) + end + + it 'should be convertible to json' do + expect(Elasticsearch::Model::Indexing::Mappings.new(:mytype, { foo: 'bar' }).as_json).to eq(expected_mapping_hash) + end + + context 'when specific mappings are defined' do + + let(:mappings) do + Elasticsearch::Model::Indexing::Mappings.new(:mytype) + end + + before do + mappings.indexes :foo, { type: 'boolean', include_in_all: false } + mappings.indexes :bar + end + + it 'creates the correct mapping definition' do + expect(mappings.to_hash[:mytype][:properties][:foo][:type]).to eq('boolean') + end + + it 'uses text as the default type' do + expect(mappings.to_hash[:mytype][:properties][:bar][:type]).to eq('text') + end + + context 'when mappings are defined for multiple fields' do + + before do + mappings.indexes :my_field, type: 'text' do + indexes :raw, type: 'keyword' + end + end + + it 'defines the mapping for all the fields' do + expect(mappings.to_hash[:mytype][:properties][:my_field][:type]).to eq('text') + expect(mappings.to_hash[:mytype][:properties][:my_field][:fields][:raw][:type]).to eq('keyword') + expect(mappings.to_hash[:mytype][:properties][:my_field][:fields][:raw][:properties]).to be_nil + end + end + + context 'when embedded properties are defined' do + + before do + mappings.indexes :foo do + indexes :bar + end + + mappings.indexes :foo_object, type: 'object' do + indexes :bar + end + + mappings.indexes :foo_nested, type: 'nested' do + indexes :bar + end + + mappings.indexes :foo_nested_as_symbol, type: :nested do + indexes :bar + end + end + + it 'defines mappings for the embedded properties' do + expect(mappings.to_hash[:mytype][:properties][:foo][:type]).to eq('object') + expect(mappings.to_hash[:mytype][:properties][:foo][:properties][:bar][:type]).to eq('text') + expect(mappings.to_hash[:mytype][:properties][:foo][:fields]).to be_nil + + expect(mappings.to_hash[:mytype][:properties][:foo_object][:type]).to eq('object') + expect(mappings.to_hash[:mytype][:properties][:foo_object][:properties][:bar][:type]).to eq('text') + expect(mappings.to_hash[:mytype][:properties][:foo_object][:fields]).to be_nil + + expect(mappings.to_hash[:mytype][:properties][:foo_nested][:type]).to eq('nested') + expect(mappings.to_hash[:mytype][:properties][:foo_nested][:properties][:bar][:type]).to eq('text') + expect(mappings.to_hash[:mytype][:properties][:foo_nested][:fields]).to be_nil + + expect(mappings.to_hash[:mytype][:properties][:foo_nested_as_symbol][:type]).to eq(:nested) + expect(mappings.to_hash[:mytype][:properties][:foo_nested_as_symbol][:properties]).not_to be_nil + expect(mappings.to_hash[:mytype][:properties][:foo_nested_as_symbol][:fields]).to be_nil + end + end + end + + context 'when the method is called on a class' do + + before do + DummyIndexingModel.mappings(foo: 'boo') + DummyIndexingModel.mappings(bar: 'bam') + end + + let(:expected_mappings_hash) do + { _doc: { foo: "boo", bar: "bam", properties: {} } } + end + + it 'sets the mappings' do + expect(DummyIndexingModel.mappings.to_hash).to eq(expected_mappings_hash) + end + + context 'when the method is called with a block' do + + before do + DummyIndexingModel.mapping do + indexes :foo, type: 'boolean' + end + end + + it 'sets the mappings' do + expect(DummyIndexingModel.mapping.to_hash[:_doc][:properties][:foo][:type]).to eq('boolean') + end + end + end + end + + describe 'instance methods' do + + before(:all) do + class ::DummyIndexingModelWithCallbacks + extend Elasticsearch::Model::Indexing::ClassMethods + include Elasticsearch::Model::Indexing::InstanceMethods + + def self.before_save(&block) + (@callbacks ||= {})[block.hash] = block + end + + def changes_to_save + {:foo => ['One', 'Two']} + end + end + + class ::DummyIndexingModelWithNoChanges + extend Elasticsearch::Model::Indexing::ClassMethods + include Elasticsearch::Model::Indexing::InstanceMethods + + def self.before_save(&block) + (@callbacks ||= {})[block.hash] = block + end + + def changes_to_save + {} + end + end + + class ::DummyIndexingModelWithCallbacksAndCustomAsIndexedJson + extend Elasticsearch::Model::Indexing::ClassMethods + include Elasticsearch::Model::Indexing::InstanceMethods + + def self.before_save(&block) + (@callbacks ||= {})[block.hash] = block + end + + def changes_to_save + {:foo => ['A', 'B'], :bar => ['C', 'D']} + end + + def as_indexed_json(options={}) + { :foo => 'B' } + end + end + + class ::DummyIndexingModelWithOldDirty + extend Elasticsearch::Model::Indexing::ClassMethods + include Elasticsearch::Model::Indexing::InstanceMethods + + def self.before_save(&block) + (@callbacks ||= {})[block.hash] = block + end + + def changes + {:foo => ['One', 'Two']} + end + end + end + + after(:all) do + Object.send(:remove_const, :DummyIndexingModelWithCallbacks) if defined?(DummyIndexingModelWithCallbacks) + Object.send(:remove_const, :DummyIndexingModelWithNoChanges) if defined?(DummyIndexingModelWithNoChanges) + Object.send(:remove_const, :DummyIndexingModelWithCallbacksAndCustomAsIndexedJson) if defined?(DummyIndexingModelWithCallbacksAndCustomAsIndexedJson) + Object.send(:remove_const, :DummyIndexingModelWithOldDirty) if defined?(DummyIndexingModelWithOldDirty) + end + + context 'when the module is included' do + + context 'when the model uses the old ActiveModel::Dirty' do + + before do + DummyIndexingModelWithOldDirty.__send__ :include, Elasticsearch::Model::Indexing::InstanceMethods + end + + it 'registers callbacks' do + expect(DummyIndexingModelWithOldDirty.instance_variable_get(:@callbacks)).not_to be_empty + end + + let(:instance) do + DummyIndexingModelWithOldDirty.new + end + + it 'sets the @__changed_model_attributes variable before the callback' do + DummyIndexingModelWithOldDirty.instance_variable_get(:@callbacks).each do |n, callback| + instance.instance_eval(&callback) + expect(instance.instance_variable_get(:@__changed_model_attributes)).to eq(foo: 'Two') + end + end + end + + context 'when the model users the current ActiveModel::Dirty' do + + before do + DummyIndexingModelWithCallbacks.__send__ :include, Elasticsearch::Model::Indexing::InstanceMethods + end + + it 'registers callbacks' do + expect(DummyIndexingModelWithCallbacks.instance_variable_get(:@callbacks)).not_to be_empty + end + + let(:instance) do + DummyIndexingModelWithCallbacks.new + end + + it 'sets the @__changed_model_attributes variable before the callback' do + DummyIndexingModelWithCallbacks.instance_variable_get(:@callbacks).each do |n, callback| + instance.instance_eval(&callback) + expect(instance.instance_variable_get(:@__changed_model_attributes)).to eq(foo: 'Two') + end + end + end + end + + describe '#index_document' do + + before do + expect(instance).to receive(:client).and_return(client) + expect(instance).to receive(:as_indexed_json).and_return('JSON') + expect(instance).to receive(:index_name).and_return('foo') + expect(instance).to receive(:document_type).and_return('bar') + expect(instance).to receive(:id).and_return('1') + end + + let(:client) do + double('client') + end + + let(:instance) do + DummyIndexingModelWithCallbacks.new + end + + context 'when no options are passed to the method' do + + before do + expect(client).to receive(:index).with(index: 'foo', type: 'bar', id: '1', body: 'JSON').and_return(true) + end + + it 'provides the method on an instance' do + expect(instance.index_document).to be(true) + end + end + + context 'when extra options are passed to the method' do + + before do + expect(client).to receive(:index).with(index: 'foo', type: 'bar', id: '1', body: 'JSON', parent: 'A').and_return(true) + end + + it 'passes the extra options to the method call on the client' do + expect(instance.index_document(parent: 'A')).to be(true) + end + end + end + + describe '#delete_document' do + + before do + expect(instance).to receive(:client).and_return(client) + expect(instance).to receive(:index_name).and_return('foo') + expect(instance).to receive(:document_type).and_return('bar') + expect(instance).to receive(:id).and_return('1') + end + + let(:client) do + double('client') + end + + let(:instance) do + DummyIndexingModelWithCallbacks.new + end + + context 'when no options are passed to the method' do + + before do + expect(client).to receive(:delete).with(index: 'foo', type: 'bar', id: '1').and_return(true) + end + + it 'provides the method on an instance' do + expect(instance.delete_document).to be(true) + end + end + + context 'when extra options are passed to the method' do + + before do + expect(client).to receive(:delete).with(index: 'foo', type: 'bar', id: '1', parent: 'A').and_return(true) + end + + it 'passes the extra options to the method call on the client' do + expect(instance.delete_document(parent: 'A')).to be(true) + end + end + end + + describe '#update_document' do + + let(:client) do + double('client') + end + + let(:instance) do + DummyIndexingModelWithCallbacks.new + end + + context 'when no changes are present' do + + before do + expect(instance).to receive(:index_document).and_return(true) + expect(client).to receive(:update).never + instance.instance_variable_set(:@__changed_model_attributes, nil) + end + + it 'updates the document' do + expect(instance.update_document).to be(true) + end + end + + context 'when changes are present' do + + before do + expect(instance).to receive(:client).and_return(client) + expect(instance).to receive(:index_name).and_return('foo') + expect(instance).to receive(:document_type).and_return('bar') + expect(instance).to receive(:id).and_return('1') + end + + context 'when the changes are included in the as_indexed_json representation' do + + before do + instance.instance_variable_set(:@__changed_model_attributes, { foo: 'bar' }) + expect(client).to receive(:update).with(index: 'foo', type: 'bar', id: '1', body: { doc: { foo: 'bar' } }).and_return(true) + end + + it 'updates the document' do + expect(instance.update_document).to be(true) + end + end + + context 'when the changes are not all included in the as_indexed_json representation' do + + let(:instance) do + DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new + end + + before do + instance.instance_variable_set(:@__changed_model_attributes, {'foo' => 'B', 'bar' => 'D' }) + expect(client).to receive(:update).with(index: 'foo', type: 'bar', id: '1', body: { doc: { foo: 'B' } }).and_return(true) + end + + it 'updates the document' do + expect(instance.update_document).to be(true) + end + end + + context 'when there are partial updates' do + + let(:instance) do + DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new + end + + before do + instance.instance_variable_set(:@__changed_model_attributes, { 'foo' => { 'bar' => 'BAR'} }) + expect(instance).to receive(:as_indexed_json).and_return('foo' => 'BAR') + expect(client).to receive(:update).with(index: 'foo', type: 'bar', id: '1', body: { doc: { 'foo' => 'BAR' } }).and_return(true) + end + + it 'updates the document' do + expect(instance.update_document).to be(true) + end + end + end + end + + describe '#update_document_attributes' do + + let(:client) do + double('client') + end + + let(:instance) do + DummyIndexingModelWithCallbacks.new + end + + context 'when changes are present' do + + before do + expect(instance).to receive(:client).and_return(client) + expect(instance).to receive(:index_name).and_return('foo') + expect(instance).to receive(:document_type).and_return('bar') + expect(instance).to receive(:id).and_return('1') + instance.instance_variable_set(:@__changed_model_attributes, { author: 'john' }) + end + + context 'when no options are specified' do + + before do + expect(client).to receive(:update).with(index: 'foo', type: 'bar', id: '1', body: { doc: { title: 'green' } }).and_return(true) + end + + it 'updates the document' do + expect(instance.update_document_attributes(title: 'green')).to be(true) + end + end + + context 'when extra options are provided' do + + before do + expect(client).to receive(:update).with(index: 'foo', type: 'bar', id: '1', body: { doc: { title: 'green' } }, refresh: true).and_return(true) + end + + it 'updates the document' do + expect(instance.update_document_attributes({ title: 'green' }, refresh: true)).to be(true) + end + end + end + end + end + + describe '#index_exists?' do + + before do + expect(DummyIndexingModel).to receive(:client).and_return(client) + end + + context 'when the index exists' do + + let(:client) do + double('client', indices: double('indices', exists: true)) + end + + it 'returns true' do + expect(DummyIndexingModel.index_exists?).to be(true) + end + end + + context 'when the index does not exists' do + + let(:client) do + double('client', indices: double('indices', exists: false)) + end + + it 'returns false' do + expect(DummyIndexingModel.index_exists?).to be(false) + end + end + + context 'when the index API raises an error' do + + let(:client) do + double('client').tap do |cl| + expect(cl).to receive(:indices).and_raise(StandardError) + end + end + + it 'returns false' do + expect(DummyIndexingModel.index_exists?).to be(false) + end + end + + context 'when the indices.exists API raises an error' do + + let(:client) do + double('client', indices: indices) + end + + let(:indices) do + double('indices').tap do |ind| + expect(ind).to receive(:exists).and_raise(StandardError) + end + end + + it 'returns false' do + expect(DummyIndexingModel.index_exists?).to be(false) + end + end + end + + describe '#delete_index!' do + + before(:all) do + class ::DummyIndexingModelForRecreate + extend ActiveModel::Naming + extend Elasticsearch::Model::Naming::ClassMethods + extend Elasticsearch::Model::Indexing::ClassMethods + end + end + + after(:all) do + Object.send(:remove_const, :DummyIndexingModelForRecreate) if defined?(DummyIndexingModelForRecreate) + end + + context 'when the index is not found' do + + let(:client) do + double('client', indices: indices) + end + + let(:indices) do + double('indices').tap do |ind| + expect(ind).to receive(:delete).and_raise(NotFound) + end + end + + before do + expect(DummyIndexingModelForRecreate).to receive(:client).and_return(client) + end + + context 'when the force option is true' do + + it 'deletes the index without raising an exception' do + expect(DummyIndexingModelForRecreate.delete_index!(force: true)).to be_nil + end + end + + context 'when the force option is not provided' do + + it 'raises an exception' do + expect { + DummyIndexingModelForRecreate.delete_index! + }.to raise_exception(NotFound) + end + end + + context 'when the exception is not NotFound' do + + let(:indices) do + double('indices').tap do |ind| + expect(ind).to receive(:delete).and_raise(Exception) + end + end + + it 'raises an exception' do + expect { + DummyIndexingModelForRecreate.delete_index! + }.to raise_exception(Exception) + end + end + end + + context 'when an index name is provided in the options' do + + before do + expect(DummyIndexingModelForRecreate).to receive(:client).and_return(client) + expect(indices).to receive(:delete).with(index: 'custom-foo') + end + + let(:client) do + double('client', indices: indices) + end + + let(:indices) do + double('indices', delete: true) + end + + it 'uses the index name' do + expect(DummyIndexingModelForRecreate.delete_index!(index: 'custom-foo')) + end + end + end + + describe '#create_index' do + + before(:all) do + class ::DummyIndexingModelForCreate + extend ActiveModel::Naming + extend Elasticsearch::Model::Naming::ClassMethods + extend Elasticsearch::Model::Indexing::ClassMethods + + index_name 'foo' + + settings index: { number_of_shards: 1 } do + mappings do + indexes :foo, analyzer: 'keyword' + end + end + end + end + + after(:all) do + Object.send(:remove_const, :DummyIndexingModelForCreate) if defined?(DummyIndexingModelForCreate) + end + + let(:client) do + double('client', indices: indices) + end + + let(:indices) do + double('indices') + end + + context 'when the index does not exist' do + + before do + expect(DummyIndexingModelForCreate).to receive(:client).and_return(client) + expect(DummyIndexingModelForCreate).to receive(:index_exists?).and_return(false) + end + + context 'when options are not provided' do + + let(:expected_body) do + { mappings: { _doc: { properties: { foo: { analyzer: 'keyword', + type: 'text' } } } }, + settings: { index: { number_of_shards: 1 } } } + end + + before do + expect(indices).to receive(:create).with(index: 'foo', body: expected_body).and_return(true) + end + + it 'creates the index' do + expect(DummyIndexingModelForCreate.create_index!).to be(true) + end + end + + context 'when options are provided' do + + let(:expected_body) do + { mappings: { foobar: { properties: { foo: { analyzer: 'bar' } } } }, + settings: { index: { number_of_shards: 3 } } } + end + + before do + expect(indices).to receive(:create).with(index: 'foobar', body: expected_body).and_return(true) + end + + it 'creates the index' do + expect(DummyIndexingModelForCreate.create_index! \ + index: 'foobar', + settings: { index: { number_of_shards: 3 } }, + mappings: { foobar: { properties: { foo: { analyzer: 'bar' } } } } + ).to be(true) + end + end + end + + context 'when the index exists' do + + before do + expect(DummyIndexingModelForCreate).to receive(:index_exists?).and_return(true) + expect(indices).to receive(:create).never + end + + it 'does not create the index' do + expect(DummyIndexingModelForCreate.create_index!).to be_nil + end + end + + context 'when creating the index raises an exception' do + + before do + expect(DummyIndexingModelForCreate).to receive(:client).and_return(client) + expect(DummyIndexingModelForCreate).to receive(:index_exists?).and_return(false) + expect(DummyIndexingModelForCreate).to receive(:delete_index!).and_return(true) + expect(indices).to receive(:create).and_raise(Exception) + end + + it 'raises the exception' do + expect { + DummyIndexingModelForCreate.create_index!(force: true) + }.to raise_exception(Exception) + end + end + + context 'when an index name is provided in the options' do + + before do + expect(DummyIndexingModelForCreate).to receive(:client).and_return(client).twice + expect(indices).to receive(:exists).and_return(false) + expect(indices).to receive(:create).with(index: 'custom-foo', body: expected_body) + end + + let(:expected_body) do + { mappings: { _doc: { properties: { foo: { analyzer: 'keyword', + type: 'text' } } } }, + settings: { index: { number_of_shards: 1 } } } + end + + it 'uses the index name' do + expect(DummyIndexingModelForCreate.create_index!(index: 'custom-foo')) + end + end + end + + describe '#refresh_index!' do + + before(:all) do + class ::DummyIndexingModelForRefresh + extend ActiveModel::Naming + extend Elasticsearch::Model::Naming::ClassMethods + extend Elasticsearch::Model::Indexing::ClassMethods + + index_name 'foo' + + settings index: { number_of_shards: 1 } do + mappings do + indexes :foo, analyzer: 'keyword' + end + end + end + end + + after(:all) do + Object.send(:remove_const, :DummyIndexingModelForRefresh) if defined?(DummyIndexingModelForRefresh) + end + + let(:client) do + double('client', indices: indices) + end + + let(:indices) do + double('indices') + end + + before do + expect(DummyIndexingModelForRefresh).to receive(:client).and_return(client) + end + + context 'when the force option is true' do + + context 'when the operation raises a NotFound exception' do + + before do + expect(indices).to receive(:refresh).and_raise(NotFound) + end + + it 'does not raise an exception' do + expect(DummyIndexingModelForRefresh.refresh_index!(force: true)).to be_nil + end + end + + context 'when the operation raises another type of exception' do + + before do + expect(indices).to receive(:refresh).and_raise(Exception) + end + + it 'does not raise an exception' do + expect { + DummyIndexingModelForRefresh.refresh_index!(force: true) + }.to raise_exception(Exception) + end + end + end + + context 'when an index name is provided in the options' do + + before do + expect(indices).to receive(:refresh).with(index: 'custom-foo') + end + + it 'uses the index name' do + expect(DummyIndexingModelForRefresh.refresh_index!(index: 'custom-foo')) + end + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/module_spec.rb b/elasticsearch-model/spec/elasticsearch/model/module_spec.rb new file mode 100644 index 000000000..c3a394534 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/module_spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' + +describe Elasticsearch::Model do + + describe '#client' do + + it 'should have a default' do + expect(Elasticsearch::Model.client).to be_a(Elasticsearch::Transport::Client) + end + end + + describe '#client=' do + + before do + Elasticsearch::Model.client = 'Foobar' + end + + it 'should allow the client to be set' do + expect(Elasticsearch::Model.client).to eq('Foobar') + end + end + + describe 'mixin' do + + before(:all) do + class ::DummyIncludingModel; end + class ::DummyIncludingModelWithSearchMethodDefined + def self.search(query, options={}) + "SEARCH" + end + end + end + + after(:all) do + Object.send(:remove_const, :DummyIncludingModel) if defined?(DummyIncludingModel) + Object.send(:remove_const, :DummyIncludingModelWithSearchMethodDefined) if defined?(DummyIncludingModelWithSearchMethodDefined) + end + + before do + DummyIncludingModel.__send__ :include, Elasticsearch::Model + end + + it 'should include and set up the proxy' do + expect(DummyIncludingModel).to respond_to(:__elasticsearch__) + expect(DummyIncludingModel.new).to respond_to(:__elasticsearch__) + end + + it 'should delegate methods to the proxy' do + expect(DummyIncludingModel).to respond_to(:search) + expect(DummyIncludingModel).to respond_to(:mapping) + expect(DummyIncludingModel).to respond_to(:settings) + expect(DummyIncludingModel).to respond_to(:index_name) + expect(DummyIncludingModel).to respond_to(:document_type) + expect(DummyIncludingModel).to respond_to(:import) + end + + it 'should not interfere with existing methods' do + expect(DummyIncludingModelWithSearchMethodDefined.search('foo')).to eq('SEARCH') + end + end + + describe '#settings' do + + it 'allows access to the settings' do + expect(Elasticsearch::Model.settings).to eq({}) + end + + context 'when settings are changed' do + + before do + Elasticsearch::Model.settings[:foo] = 'bar' + end + + it 'persists the changes' do + expect(Elasticsearch::Model.settings[:foo]).to eq('bar') + end + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb b/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb new file mode 100644 index 000000000..6fa274ccc --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Multimodel do + + let(:multimodel) do + Elasticsearch::Model::Multimodel.new(model_1, model_2) + end + + let(:model_1) do + double('Foo', index_name: 'foo_index', document_type: 'foo', to_ary: nil) + end + + let(:model_2) do + double('Bar', index_name: 'bar_index', document_type: 'bar', to_ary: nil) + end + + it 'has an index name' do + expect(multimodel.index_name).to eq(['foo_index', 'bar_index']) + end + + it 'has an document type' do + expect(multimodel.document_type).to eq(['foo', 'bar']) + end + + it 'has a client' do + expect(multimodel.client).to eq(Elasticsearch::Model.client) + end + + describe 'the model registry' do + + before(:all) do + + class JustAModel + include Elasticsearch::Model + end + + class JustAnotherModel + include Elasticsearch::Model + end + end + + after(:all) do + Object.send(:remove_const, :JustAModel) if defined?(JustAModel) + Object.send(:remove_const, :JustAnotherModel) if defined?(JustAnotherModel) + end + + let(:multimodel) do + Elasticsearch::Model::Multimodel.new + end + + it 'includes model in the registry' do + expect(multimodel.models).to include(JustAModel) + expect(multimodel.models).to include(JustAnotherModel) + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb b/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb new file mode 100644 index 000000000..daf7fb47d --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb @@ -0,0 +1,104 @@ +require 'spec_helper' + +describe 'naming inheritance' do + + before(:all) do + Elasticsearch::Model.settings[:inheritance_enabled] = true + + class ::TestBase + extend ActiveModel::Naming + + extend Elasticsearch::Model::Naming::ClassMethods + include Elasticsearch::Model::Naming::InstanceMethods + end + + class ::Animal < ::TestBase + extend ActiveModel::Naming + + extend Elasticsearch::Model::Naming::ClassMethods + include Elasticsearch::Model::Naming::InstanceMethods + + index_name "mammals" + document_type "mammal" + end + + class ::Dog < ::Animal + end + + module ::MyNamespace + class Dog < ::Animal + end + end + + class ::Cat < ::Animal + extend ActiveModel::Naming + + extend Elasticsearch::Model::Naming::ClassMethods + include Elasticsearch::Model::Naming::InstanceMethods + + index_name "cats" + document_type "cat" + end + + end + + after(:all) do + Elasticsearch::Model.settings[:inheritance_enabled] = false + Object.send(:remove_const, :TestBase) if defined?(TestBase) + Object.send(:remove_const, :Animal) if defined?(Animal) + Object.send(:remove_const, :MyNamespace) if defined?(MyNamespace) + Object.send(:remove_const, :Cat) if defined?(Cat) + end + + describe '#index_name' do + + it 'returns the default index name' do + expect(TestBase.index_name).to eq('test_bases') + expect(TestBase.new.index_name).to eq('test_bases') + end + + it 'returns the explicit index name' do + expect(Animal.index_name).to eq('mammals') + expect(Animal.new.index_name).to eq('mammals') + + expect(Cat.index_name).to eq('cats') + expect(Cat.new.index_name).to eq('cats') + end + + it 'returns the ancestor index name' do + expect(Dog.index_name).to eq('mammals') + expect(Dog.new.index_name).to eq('mammals') + end + + it 'returns the ancestor index name for namespaced models' do + expect(::MyNamespace::Dog.index_name).to eq('mammals') + expect(::MyNamespace::Dog.new.index_name).to eq('mammals') + end + end + + describe '#document_type' do + + it 'returns the default document type' do + expect(TestBase.document_type).to eq('_doc') + expect(TestBase.new.document_type).to eq('_doc') + end + + it 'returns the explicit document type' do + expect(Animal.document_type).to eq('mammal') + expect(Animal.new.document_type).to eq('mammal') + + expect(Cat.document_type).to eq('cat') + expect(Cat.new.document_type).to eq('cat') + end + + it 'returns the ancestor document type' do + expect(Dog.document_type).to eq('mammal') + expect(Dog.new.document_type).to eq('mammal') + end + + it 'returns the ancestor document type for namespaced models' do + expect(::MyNamespace::Dog.document_type).to eq('mammal') + expect(::MyNamespace::Dog.new.document_type).to eq('mammal') + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb b/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb new file mode 100644 index 000000000..32b9905d1 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb @@ -0,0 +1,187 @@ +require 'spec_helper' + +describe 'naming' do + + before(:all) do + class ::DummyNamingModel + extend ActiveModel::Naming + + extend Elasticsearch::Model::Naming::ClassMethods + include Elasticsearch::Model::Naming::InstanceMethods + end + + module ::MyNamespace + class DummyNamingModelInNamespace + extend ActiveModel::Naming + + extend Elasticsearch::Model::Naming::ClassMethods + include Elasticsearch::Model::Naming::InstanceMethods + end + end + end + + after(:all) do + Object.send(:remove_const, :DummyNamingModel) if defined?(DummyNamingModel) + Object.send(:remove_const, :MyNamespace) if defined?(MyNamespace) + end + + it 'returns the default index name' do + expect(DummyNamingModel.index_name).to eq('dummy_naming_models') + expect(DummyNamingModel.new.index_name).to eq('dummy_naming_models') + end + + it 'returns the sanitized defualt index name for namespaced models' do + expect(::MyNamespace::DummyNamingModelInNamespace.index_name).to eq('my_namespace-dummy_naming_model_in_namespaces') + expect(::MyNamespace::DummyNamingModelInNamespace.new.index_name).to eq('my_namespace-dummy_naming_model_in_namespaces') + end + + it 'returns the document type' do + expect(DummyNamingModel.document_type).to eq('_doc') + expect(DummyNamingModel.new.document_type).to eq('_doc') + end + + describe '#index_name' do + + context 'when the index name is set on the class' do + + before do + DummyNamingModel.index_name 'foobar' + end + + it 'sets the index_name' do + expect(DummyNamingModel.index_name).to eq('foobar') + end + end + + context 'when the index name is set on an instance' do + + before do + instance.index_name 'foobar_d' + end + + let(:instance) do + DummyNamingModel.new + end + + it 'sets the index name on the instance' do + expect(instance.index_name).to eq('foobar_d') + end + + context 'when the index name is set with a proc' do + + before do + modifier = 'r' + instance.index_name Proc.new{ "foobar_#{modifier}" } + end + + it 'sets the index name on the instance' do + expect(instance.index_name).to eq('foobar_r') + end + end + end + end + + describe '#index_name=' do + + before do + DummyNamingModel.index_name = 'foobar_index_S' + end + + it 'changes the index name' do + expect(DummyNamingModel.index_name).to eq('foobar_index_S') + end + + context 'when the method is called on an instance' do + + let(:instance) do + DummyNamingModel.new + end + + before do + instance.index_name = 'foobar_index_s' + end + + it 'changes the index name' do + expect(instance.index_name).to eq('foobar_index_s') + end + + it 'does not change the index name on the class' do + expect(DummyNamingModel.index_name).to eq('foobar_index_S') + end + end + + context 'when the index name is changed with a proc' do + + before do + modifier2 = 'y' + DummyNamingModel.index_name = Proc.new{ "foobar_index_#{modifier2}" } + end + + it 'changes the index name' do + expect(DummyNamingModel.index_name).to eq('foobar_index_y') + end + end + end + + describe '#document_type' do + + it 'returns the document type' do + expect(DummyNamingModel.document_type).to eq('_doc') + end + + context 'when the method is called with an argument' do + + before do + DummyNamingModel.document_type 'foo' + end + + it 'changes the document type' do + expect(DummyNamingModel.document_type).to eq('foo') + end + end + + context 'when the method is called on an instance' do + + let(:instance) do + DummyNamingModel.new + end + + before do + instance.document_type 'foobar_d' + end + + it 'changes the document type' do + expect(instance.document_type).to eq('foobar_d') + end + end + end + + describe '#document_type=' do + + context 'when the method is called on the class' do + + before do + DummyNamingModel.document_type = 'foo_z' + end + + it 'changes the document type' do + expect(DummyNamingModel.document_type).to eq('foo_z') + end + end + + context 'when the method is called on an instance' do + + let(:instance) do + DummyNamingModel.new + end + + before do + instance.document_type = 'foobar_b' + end + + it 'changes the document type' do + expect(instance.document_type).to eq('foobar_b') + end + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb b/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb new file mode 100644 index 000000000..ea5553af0 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb @@ -0,0 +1,110 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Proxy do + + before(:all) do + class ::DummyProxyModel + include Elasticsearch::Model::Proxy + + def self.foo + 'classy foo' + end + + def bar + 'insta barr' + end + + def as_json(options) + {foo: 'bar'} + end + end + + class ::DummyProxyModelWithCallbacks + def self.before_save(&block) + (@callbacks ||= {})[block.hash] = block + end + + def changes_to_save + {:foo => ['One', 'Two']} + end + end + end + + after(:all) do + Object.send(:remove_const, :DummyProxyModel) if defined?(DummyProxyModel) + Object.send(:remove_const, :DummyProxyModelWithCallbacks) if defined?(DummyProxyModelWithCallbacks) + end + + before do + DummyProxyModelWithCallbacks.__send__ :include, Elasticsearch::Model::Proxy + end + + it 'sets up a proxy method on the class' do + expect(DummyProxyModel).to respond_to(:__elasticsearch__) + end + + it 'sets up a proxy method on instances' do + expect(DummyProxyModel.new).to respond_to(:__elasticsearch__) + end + + it 'sets up hooks for before_save callbacks' do + expect(DummyProxyModelWithCallbacks).to respond_to(:before_save) + end + + it 'delegates methods to the target' do + expect(DummyProxyModel.__elasticsearch__).to respond_to(:foo) + expect(DummyProxyModel.__elasticsearch__.foo).to eq('classy foo') + expect(DummyProxyModel.new.__elasticsearch__).to respond_to(:bar) + expect(DummyProxyModel.new.__elasticsearch__.bar).to eq('insta barr') + + expect { + DummyProxyModel.__elasticsearch__.xoxo + }.to raise_exception(NoMethodError) + + expect { + DummyProxyModel.new.__elasticsearch__.xoxo + }.to raise_exception(NoMethodError) + end + + it 'returns the proxy class from an instance proxy' do + expect(DummyProxyModel.new.__elasticsearch__.class.class).to eq(Elasticsearch::Model::Proxy::ClassMethodsProxy) + end + + it 'returns the origin class from an instance proxy' do + expect(DummyProxyModel.new.__elasticsearch__.klass).to eq(DummyProxyModel) + end + + it 'delegates #as_json from the proxy to the target' do + expect(DummyProxyModel.new.__elasticsearch__.as_json).to eq(foo: 'bar') + end + + it 'includes the proxy in the inspect string' do + expect(DummyProxyModel.__elasticsearch__.inspect).to match(/PROXY/) + expect(DummyProxyModel.new.__elasticsearch__.inspect).to match(/PROXY/) + end + + context 'when instances are cloned' do + + let!(:model) do + DummyProxyModel.new + end + + let!(:model_target) do + model.__elasticsearch__.target + end + + let!(:duplicate) do + model.dup + end + + let!(:duplicate_target) do + duplicate.__elasticsearch__.target + end + + it 'resets the proxy target' do + expect(model).not_to eq(duplicate) + expect(model).to eq(model_target) + expect(duplicate).to eq(duplicate_target) + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb b/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb new file mode 100644 index 000000000..f93dbce01 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb @@ -0,0 +1,114 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Serializing do + + before(:all) do + class ::DummySearchingModel + extend Elasticsearch::Model::Searching::ClassMethods + + def self.index_name; 'foo'; end + def self.document_type; 'bar'; end + + end + end + + after(:all) do + Object.send(:remove_const, :DummySearchingModel) if defined?(DummySearchingModel) + end + + before do + allow(DummySearchingModel).to receive(:client).and_return(client) + end + + let(:client) do + double('client') + end + + describe '#initialize' do + + context 'when the search definition is a simple query' do + + before do + expect(client).to receive(:search).with(index: 'foo', type: 'bar', q: 'foo').and_return({}) + end + + let(:search) do + Elasticsearch::Model::Searching::SearchRequest.new(DummySearchingModel, 'foo') + end + + it 'passes the query to the client' do + expect(search.execute!).to eq({}) + end + end + + context 'when the search definition is a hash' do + + before do + expect(client).to receive(:search).with(index: 'foo', type: 'bar', body: { foo: 'bar' }).and_return({}) + end + + let(:search) do + Elasticsearch::Model::Searching::SearchRequest.new(DummySearchingModel, foo: 'bar') + end + + it 'passes the hash to the client' do + expect(search.execute!).to eq({}) + end + end + + context 'when the search definition is a json string' do + + before do + expect(client).to receive(:search).with(index: 'foo', type: 'bar', body: '{"foo":"bar"}').and_return({}) + end + + let(:search) do + Elasticsearch::Model::Searching::SearchRequest.new(DummySearchingModel, '{"foo":"bar"}') + end + + it 'passes the json string to the client' do + expect(search.execute!).to eq({}) + end + end + + context 'when the search definition is a custom object' do + + before(:all) do + class MySpecialQueryBuilder + def to_hash; {foo: 'bar'}; end + end + end + + after(:all) do + Object.send(:remove_const, :MySpecialQueryBuilder) if defined?(MySpecialQueryBuilder) + end + + before do + expect(client).to receive(:search).with(index: 'foo', type: 'bar', body: {foo: 'bar'}).and_return({}) + end + + let(:search) do + Elasticsearch::Model::Searching::SearchRequest.new(DummySearchingModel, MySpecialQueryBuilder.new) + end + + it 'passes the query builder to the client and calls #to_hash on it' do + expect(search.execute!).to eq({}) + end + end + + context 'when extra options are specified' do + + before do + expect(client).to receive(:search).with(index: 'foo', type: 'bar', q: 'foo', size: 15).and_return({}) + end + + let(:search) do + Elasticsearch::Model::Searching::SearchRequest.new(DummySearchingModel, 'foo', size: 15) + end + + it 'passes the extra options to the client as part of the request' do + expect(search.execute!).to eq({}) + end + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/searching_spec.rb b/elasticsearch-model/spec/elasticsearch/model/searching_spec.rb new file mode 100644 index 000000000..66ee516a9 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/searching_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Searching::ClassMethods do + + before(:all) do + class ::DummySearchingModel + extend Elasticsearch::Model::Searching::ClassMethods + + def self.index_name; 'foo'; end + def self.document_type; 'bar'; end + end + end + + after(:all) do + Object.send(:remove_const, :DummySearchingModel) if defined?(DummySearchingModel) + end + + it 'has the search method' do + expect(DummySearchingModel).to respond_to(:search) + end + + describe '#search' do + + let(:response) do + double('search', execute!: { 'hits' => {'hits' => [ {'_id' => 2 }, {'_id' => 1 } ]} }) + end + + before do + expect(Elasticsearch::Model::Searching::SearchRequest).to receive(:new).with(DummySearchingModel, 'foo', { default_operator: 'AND' }).and_return(response) + end + + it 'creates a search object' do + expect(DummySearchingModel.search('foo', default_operator: 'AND')).to be_a(Elasticsearch::Model::Response::Response) + end + end + + describe 'lazy execution' do + + let(:response) do + double('search').tap do |r| + expect(r).to receive(:execute!).never + end + end + + it 'does not execute the search until the results are accessed' do + DummySearchingModel.search('foo') + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/serializing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/serializing_spec.rb new file mode 100644 index 000000000..6b440e02e --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/serializing_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Serializing do + + before(:all) do + class DummyClass + include Elasticsearch::Model::Serializing::InstanceMethods + + def as_json(options={}) + 'HASH' + end + end + end + + after(:all) do + Object.send(:remove_const, :DummyClass) if defined?(DummyClass) + end + + it 'delegates to #as_json by default' do + expect(DummyClass.new.as_indexed_json).to eq('HASH') + end +end diff --git a/elasticsearch-model/spec/spec_helper.rb b/elasticsearch-model/spec/spec_helper.rb index ec55da253..9c692211c 100644 --- a/elasticsearch-model/spec/spec_helper.rb +++ b/elasticsearch-model/spec/spec_helper.rb @@ -3,6 +3,9 @@ require 'will_paginate' require 'will_paginate/collection' require 'elasticsearch/model' +require 'hashie/version' +require 'active_model' +require 'yaml' RSpec.configure do |config| config.formatter = 'documentation' diff --git a/elasticsearch-model/spec/support/model.json b/elasticsearch-model/spec/support/model.json new file mode 100644 index 000000000..0c3e4a388 --- /dev/null +++ b/elasticsearch-model/spec/support/model.json @@ -0,0 +1 @@ +{ "laz": "qux" } diff --git a/elasticsearch-model/test/support/model.yml b/elasticsearch-model/spec/support/model.yml similarity index 100% rename from elasticsearch-model/test/support/model.yml rename to elasticsearch-model/spec/support/model.yml diff --git a/elasticsearch-model/test/support/model.json b/elasticsearch-model/test/support/model.json deleted file mode 100644 index fcf3a6473..000000000 --- a/elasticsearch-model/test/support/model.json +++ /dev/null @@ -1 +0,0 @@ -{ "baz": "qux" } diff --git a/elasticsearch-model/test/unit/callbacks_test.rb b/elasticsearch-model/test/unit/callbacks_test.rb deleted file mode 100644 index 95617a414..000000000 --- a/elasticsearch-model/test/unit/callbacks_test.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::CallbacksTest < Test::Unit::TestCase - context "Callbacks module" do - class ::DummyCallbacksModel - end - - module DummyCallbacksAdapter - module CallbacksMixin - end - - def callbacks_mixin - CallbacksMixin - end; module_function :callbacks_mixin - end - - should "include the callbacks mixin from adapter" do - Elasticsearch::Model::Adapter.expects(:from_class) - .with(DummyCallbacksModel) - .returns(DummyCallbacksAdapter) - - ::DummyCallbacksModel.expects(:__send__).with do |method, parameter| - assert_equal :include, method - assert_equal DummyCallbacksAdapter::CallbacksMixin, parameter - true - end - - Elasticsearch::Model::Callbacks.included(DummyCallbacksModel) - end - end -end diff --git a/elasticsearch-model/test/unit/client_test.rb b/elasticsearch-model/test/unit/client_test.rb deleted file mode 100644 index 315a3ab44..000000000 --- a/elasticsearch-model/test/unit/client_test.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::ClientTest < Test::Unit::TestCase - context "Client module" do - class ::DummyClientModel - extend Elasticsearch::Model::Client::ClassMethods - include Elasticsearch::Model::Client::InstanceMethods - end - - should "have the default client method" do - assert_instance_of Elasticsearch::Transport::Client, DummyClientModel.client - assert_instance_of Elasticsearch::Transport::Client, DummyClientModel.new.client - end - - should "set the client for the model" do - DummyClientModel.client = 'foobar' - assert_equal 'foobar', DummyClientModel.client - assert_equal 'foobar', DummyClientModel.new.client - end - - should "set the client for a model instance" do - instance = DummyClientModel.new - instance.client = 'moobam' - assert_equal 'moobam', instance.client - end - end -end diff --git a/elasticsearch-model/test/unit/hash_wrapper_test.rb b/elasticsearch-model/test/unit/hash_wrapper_test.rb deleted file mode 100644 index b7b1989d5..000000000 --- a/elasticsearch-model/test/unit/hash_wrapper_test.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'test_helper' - -require 'hashie/version' - -class Elasticsearch::Model::HashWrapperTest < Test::Unit::TestCase - context "The HashWrapper class" do - should "not print the warning for re-defined methods" do - Hashie.logger.expects(:warn).never - - subject = Elasticsearch::Model::HashWrapper.new(:foo => 'bar', :sort => true) - end if Hashie::VERSION >= '3.5.3' - end -end diff --git a/elasticsearch-model/test/unit/importing_test.rb b/elasticsearch-model/test/unit/importing_test.rb deleted file mode 100644 index 2f43ed4a6..000000000 --- a/elasticsearch-model/test/unit/importing_test.rb +++ /dev/null @@ -1,224 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::ImportingTest < Test::Unit::TestCase - context "Importing module" do - class ::DummyImportingModel - end - - module ::DummyImportingAdapter - module ImportingMixin - def __find_in_batches(options={}, &block) - yield if block_given? - end - def __transform - lambda {|a|} - end - end - - def importing_mixin - ImportingMixin - end; module_function :importing_mixin - end - - should "include methods from the module and adapter" do - Elasticsearch::Model::Adapter.expects(:from_class) - .with(DummyImportingModel) - .returns(DummyImportingAdapter) - - DummyImportingModel.__send__ :include, Elasticsearch::Model::Importing - - assert_respond_to DummyImportingModel, :import - assert_respond_to DummyImportingModel, :__find_in_batches - end - - should "call the client when importing" do - Elasticsearch::Model::Adapter.expects(:from_class) - .with(DummyImportingModel) - .returns(DummyImportingAdapter) - - DummyImportingModel.__send__ :include, Elasticsearch::Model::Importing - - client = mock('client') - client.expects(:bulk).returns({'items' => []}) - - DummyImportingModel.expects(:client).returns(client) - DummyImportingModel.expects(:index_name).returns('foo') - DummyImportingModel.expects(:document_type).returns('foo') - DummyImportingModel.stubs(:index_exists?).returns(true) - DummyImportingModel.stubs(:__batch_to_bulk) - assert_equal 0, DummyImportingModel.import - end - - should "return the number of errors" do - Elasticsearch::Model::Adapter.expects(:from_class) - .with(DummyImportingModel) - .returns(DummyImportingAdapter) - - DummyImportingModel.__send__ :include, Elasticsearch::Model::Importing - - client = mock('client') - client.expects(:bulk).returns({'items' => [ {'index' => {}}, {'index' => {'error' => 'FAILED'}} ]}) - - DummyImportingModel.stubs(:client).returns(client) - DummyImportingModel.stubs(:index_name).returns('foo') - DummyImportingModel.stubs(:document_type).returns('foo') - DummyImportingModel.stubs(:index_exists?).returns(true) - DummyImportingModel.stubs(:__batch_to_bulk) - - assert_equal 1, DummyImportingModel.import - end - - should "return an array of error elements" do - Elasticsearch::Model::Adapter.expects(:from_class) - .with(DummyImportingModel) - .returns(DummyImportingAdapter) - - DummyImportingModel.__send__ :include, Elasticsearch::Model::Importing - - client = mock('client') - client.expects(:bulk).returns({'items' => [ {'index' => {}}, {'index' => {'error' => 'FAILED'}} ]}) - - DummyImportingModel.stubs(:client).returns(client) - DummyImportingModel.stubs(:index_name).returns('foo') - DummyImportingModel.stubs(:document_type).returns('foo') - DummyImportingModel.stubs(:index_exists?).returns(true) - DummyImportingModel.stubs(:__batch_to_bulk) - - assert_equal [{'index' => {'error' => 'FAILED'}}], DummyImportingModel.import(return: 'errors') - end - - should "yield the response" do - Elasticsearch::Model::Adapter.expects(:from_class) - .with(DummyImportingModel) - .returns(DummyImportingAdapter) - - DummyImportingModel.__send__ :include, Elasticsearch::Model::Importing - - client = mock('client') - client.expects(:bulk).returns({'items' => [ {'index' => {}}, {'index' => {'error' => 'FAILED'}} ]}) - - DummyImportingModel.stubs(:client).returns(client) - DummyImportingModel.stubs(:index_name).returns('foo') - DummyImportingModel.stubs(:document_type).returns('foo') - DummyImportingModel.stubs(:index_exists?).returns(true) - DummyImportingModel.stubs(:__batch_to_bulk) - - DummyImportingModel.import do |response| - assert_equal 2, response['items'].size - end - end - - context "when the index does not exist" do - should "raise an exception" do - Elasticsearch::Model::Adapter.expects(:from_class) - .with(DummyImportingModel) - .returns(DummyImportingAdapter) - - DummyImportingModel.__send__ :include, Elasticsearch::Model::Importing - - DummyImportingModel.expects(:index_name).returns('foo') - DummyImportingModel.expects(:document_type).returns('foo') - DummyImportingModel.expects(:index_exists?).returns(false) - - assert_raise ArgumentError do - DummyImportingModel.import - end - end - end - - context "with the force option" do - should "delete and create the index" do - DummyImportingModel.expects(:__find_in_batches).with do |options| - assert_equal 'bar', options[:foo] - assert_nil options[:force] - true - end - - DummyImportingModel.expects(:create_index!).with do |options| - assert_equal true, options[:force] - true - end - - DummyImportingModel.expects(:index_name).returns('foo') - DummyImportingModel.expects(:document_type).returns('foo') - - DummyImportingModel.import force: true, foo: 'bar' - end - end - - context "with the refresh option" do - should "refresh the index" do - DummyImportingModel.expects(:__find_in_batches).with do |options| - assert_equal 'bar', options[:foo] - assert_nil options[:refresh] - true - end - - DummyImportingModel.expects(:refresh_index!).with do |options| - assert_equal 'foo', options[:index] - true - end - - DummyImportingModel.expects(:index_name).returns('foo') - DummyImportingModel.expects(:document_type).returns('foo') - DummyImportingModel.stubs(:index_exists?).returns(true) - - DummyImportingModel.import refresh: true, foo: 'bar' - end - end - - should "allow passing a different index / type" do - Elasticsearch::Model::Adapter.expects(:from_class) - .with(DummyImportingModel) - .returns(DummyImportingAdapter) - - DummyImportingModel.__send__ :include, Elasticsearch::Model::Importing - - client = mock('client') - - client - .expects(:bulk) - .with do |options| - assert_equal 'my-new-index', options[:index] - assert_equal 'my-other-type', options[:type] - true - end - .returns({'items' => [ {'index' => {} }]}) - - DummyImportingModel.stubs(:client).returns(client) - DummyImportingModel.stubs(:index_exists?).returns(true) - DummyImportingModel.stubs(:__batch_to_bulk) - - DummyImportingModel.import index: 'my-new-index', type: 'my-other-type' - end - - should "use the default transform from adapter" do - client = mock('client', bulk: {'items' => []}) - transform = lambda {|a|} - - DummyImportingModel.stubs(:client).returns(client) - DummyImportingModel.stubs(:index_exists?).returns(true) - DummyImportingModel.expects(:__transform).returns(transform) - DummyImportingModel.expects(:__batch_to_bulk).with(anything, transform) - - DummyImportingModel.import index: 'foo', type: 'bar' - end - - should "use the transformer from options" do - client = mock('client', bulk: {'items' => []}) - transform = lambda {|a|} - - DummyImportingModel.stubs(:client).returns(client) - DummyImportingModel.stubs(:index_exists?).returns(true) - DummyImportingModel.expects(:__batch_to_bulk).with(anything, transform) - - DummyImportingModel.import index: 'foo', type: 'bar', transform: transform - end - - should "raise an ArgumentError if transform doesn't respond to the call method" do - assert_raise ArgumentError do - DummyImportingModel.import index: 'foo', type: 'bar', transform: "not_callable" - end - end - end -end diff --git a/elasticsearch-model/test/unit/indexing_test.rb b/elasticsearch-model/test/unit/indexing_test.rb deleted file mode 100644 index f09186a93..000000000 --- a/elasticsearch-model/test/unit/indexing_test.rb +++ /dev/null @@ -1,720 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase - context "Indexing module: " do - class ::DummyIndexingModel - extend ActiveModel::Naming - extend Elasticsearch::Model::Naming::ClassMethods - extend Elasticsearch::Model::Indexing::ClassMethods - - def self.foo - 'bar' - end - end - - class NotFound < Exception; end - - context "Settings class" do - should "be convertible to hash" do - hash = { foo: 'bar' } - settings = Elasticsearch::Model::Indexing::Settings.new hash - assert_equal hash, settings.to_hash - assert_equal settings.to_hash, settings.as_json - end - end - - context "Settings method" do - should "initialize the index settings" do - assert_instance_of Elasticsearch::Model::Indexing::Settings, DummyIndexingModel.settings - end - - should "update and return the index settings from a hash" do - DummyIndexingModel.settings foo: 'boo' - DummyIndexingModel.settings bar: 'bam' - - assert_equal( {foo: 'boo', bar: 'bam'}, DummyIndexingModel.settings.to_hash) - end - - should "update and return the index settings from a yml file" do - DummyIndexingModel.settings File.open("test/support/model.yml") - DummyIndexingModel.settings bar: 'bam' - - assert_equal( {foo: 'boo', bar: 'bam', 'baz' => 'qux'}, DummyIndexingModel.settings.to_hash) - end - - should "update and return the index settings from a json file" do - DummyIndexingModel.settings File.open("test/support/model.json") - DummyIndexingModel.settings bar: 'bam' - - assert_equal( {foo: 'boo', bar: 'bam', 'baz' => 'qux'}, DummyIndexingModel.settings.to_hash) - end - - should "evaluate the block" do - DummyIndexingModel.expects(:foo) - - DummyIndexingModel.settings do - foo - end - end - end - - context "Mappings class" do - should "initialize the index mappings" do - assert_instance_of Elasticsearch::Model::Indexing::Mappings, DummyIndexingModel.mappings - end - - should "raise an exception when not passed type" do - assert_raise ArgumentError do - Elasticsearch::Model::Indexing::Mappings.new - end - end - - should "be convertible to hash" do - mappings = Elasticsearch::Model::Indexing::Mappings.new :mytype, { foo: 'bar' } - assert_equal( { :mytype => { foo: 'bar', :properties => {} } }, mappings.to_hash ) - assert_equal mappings.to_hash, mappings.as_json - end - - should "define properties" do - mappings = Elasticsearch::Model::Indexing::Mappings.new :mytype - assert_respond_to mappings, :indexes - - mappings.indexes :foo, { type: 'boolean', include_in_all: false } - assert_equal 'boolean', mappings.to_hash[:mytype][:properties][:foo][:type] - end - - should "define type as 'text' by default" do - mappings = Elasticsearch::Model::Indexing::Mappings.new :mytype - - mappings.indexes :bar - assert_equal 'text', mappings.to_hash[:mytype][:properties][:bar][:type] - end - - should "define multiple fields" do - mappings = Elasticsearch::Model::Indexing::Mappings.new :mytype - - mappings.indexes :my_field, type: 'text' do - indexes :raw, type: 'keyword' - end - - assert_equal 'text', mappings.to_hash[:mytype][:properties][:my_field][:type] - assert_equal 'keyword', mappings.to_hash[:mytype][:properties][:my_field][:fields][:raw][:type] - assert_nil mappings.to_hash[:mytype][:properties][:my_field][:properties] - end - - should "define embedded properties" do - mappings = Elasticsearch::Model::Indexing::Mappings.new :mytype - - mappings.indexes :foo do - indexes :bar - end - - mappings.indexes :foo_object, type: 'object' do - indexes :bar - end - - mappings.indexes :foo_nested, type: 'nested' do - indexes :bar - end - - mappings.indexes :foo_nested_as_symbol, type: :nested do - indexes :bar - end - - # Object is the default when `type` is missing and there's a block passed - # - assert_equal 'object', mappings.to_hash[:mytype][:properties][:foo][:type] - assert_equal 'text', mappings.to_hash[:mytype][:properties][:foo][:properties][:bar][:type] - assert_nil mappings.to_hash[:mytype][:properties][:foo][:fields] - - assert_equal 'object', mappings.to_hash[:mytype][:properties][:foo_object][:type] - assert_equal 'text', mappings.to_hash[:mytype][:properties][:foo_object][:properties][:bar][:type] - assert_nil mappings.to_hash[:mytype][:properties][:foo_object][:fields] - - assert_equal 'nested', mappings.to_hash[:mytype][:properties][:foo_nested][:type] - assert_equal 'text', mappings.to_hash[:mytype][:properties][:foo_nested][:properties][:bar][:type] - assert_nil mappings.to_hash[:mytype][:properties][:foo_nested][:fields] - - assert_equal :nested, mappings.to_hash[:mytype][:properties][:foo_nested_as_symbol][:type] - assert_not_nil mappings.to_hash[:mytype][:properties][:foo_nested_as_symbol][:properties] - assert_nil mappings.to_hash[:mytype][:properties][:foo_nested_as_symbol][:fields] - end - end - - context "Mappings method" do - should "initialize the index mappings" do - assert_instance_of Elasticsearch::Model::Indexing::Mappings, DummyIndexingModel.mappings - end - - should "update and return the index mappings" do - DummyIndexingModel.mappings foo: 'boo' - DummyIndexingModel.mappings bar: 'bam' - assert_equal( { _doc: { foo: "boo", bar: "bam", properties: {} } }, - DummyIndexingModel.mappings.to_hash ) - end - - should "evaluate the block" do - DummyIndexingModel.mappings.expects(:indexes).with(:foo).returns(true) - - DummyIndexingModel.mappings do - indexes :foo - end - end - end - - context "Instance methods" do - class ::DummyIndexingModelWithCallbacks - extend Elasticsearch::Model::Indexing::ClassMethods - include Elasticsearch::Model::Indexing::InstanceMethods - - def self.before_save(&block) - (@callbacks ||= {})[block.hash] = block - end - - def changes_to_save - {:foo => ['One', 'Two']} - end - end - - class ::DummyIndexingModelWithNoChanges - extend Elasticsearch::Model::Indexing::ClassMethods - include Elasticsearch::Model::Indexing::InstanceMethods - - def self.before_save(&block) - (@callbacks ||= {})[block.hash] = block - end - - def changes_to_save - {} - end - end - - class ::DummyIndexingModelWithCallbacksAndCustomAsIndexedJson - extend Elasticsearch::Model::Indexing::ClassMethods - include Elasticsearch::Model::Indexing::InstanceMethods - - def self.before_save(&block) - (@callbacks ||= {})[block.hash] = block - end - - def changes_to_save - {:foo => ['A', 'B'], :bar => ['C', 'D']} - end - - def as_indexed_json(options={}) - { :foo => 'B' } - end - end - - class ::DummyIndexingModelWithOldDirty - extend Elasticsearch::Model::Indexing::ClassMethods - include Elasticsearch::Model::Indexing::InstanceMethods - - def self.before_save(&block) - (@callbacks ||= {})[block.hash] = block - end - - def changes - {:foo => ['One', 'Two']} - end - end - - should "register before_save callback when included" do - ::DummyIndexingModelWithCallbacks.expects(:before_save).returns(true) - ::DummyIndexingModelWithCallbacks.__send__ :include, Elasticsearch::Model::Indexing::InstanceMethods - end - - should "set the @__changed_model_attributes variable before save" do - instance = ::DummyIndexingModelWithCallbacks.new - instance.expects(:instance_variable_set).with do |name, value| - assert_equal :@__changed_model_attributes, name - assert_equal({foo: 'Two'}, value) - true - end - - ::DummyIndexingModelWithCallbacks.__send__ :include, Elasticsearch::Model::Indexing::InstanceMethods - - ::DummyIndexingModelWithCallbacks.instance_variable_get(:@callbacks).each do |n,b| - instance.instance_eval(&b) - end - end - - # https://github.com/elastic/elasticsearch-rails/issues/714 - # https://github.com/rails/rails/pull/25337#issuecomment-225166796 - should "set the @__changed_model_attributes variable before save for old ActiveModel::Dirty" do - instance = ::DummyIndexingModelWithOldDirty.new - instance.expects(:instance_variable_set).with do |name, value| - assert_equal :@__changed_model_attributes, name - assert_equal({foo: 'Two'}, value) - true - end - - ::DummyIndexingModelWithOldDirty.__send__ :include, Elasticsearch::Model::Indexing::InstanceMethods - - ::DummyIndexingModelWithOldDirty.instance_variable_get(:@callbacks).each do |n,b| - instance.instance_eval(&b) - end - end - - should "have the index_document method" do - client = mock('client') - instance = ::DummyIndexingModelWithCallbacks.new - - client.expects(:index).with do |payload| - assert_equal 'foo', payload[:index] - assert_equal 'bar', payload[:type] - assert_equal '1', payload[:id] - assert_equal 'JSON', payload[:body] - true - end - - instance.expects(:client).returns(client) - instance.expects(:as_indexed_json).returns('JSON') - instance.expects(:index_name).returns('foo') - instance.expects(:document_type).returns('bar') - instance.expects(:id).returns('1') - - instance.index_document - end - - should "pass extra options to the index_document method to client.index" do - client = mock('client') - instance = ::DummyIndexingModelWithCallbacks.new - - client.expects(:index).with do |payload| - assert_equal 'A', payload[:parent] - true - end - - instance.expects(:client).returns(client) - instance.expects(:as_indexed_json).returns('JSON') - instance.expects(:index_name).returns('foo') - instance.expects(:document_type).returns('bar') - instance.expects(:id).returns('1') - - instance.index_document(parent: 'A') - end - - should "have the delete_document method" do - client = mock('client') - instance = ::DummyIndexingModelWithCallbacks.new - - client.expects(:delete).with do |payload| - assert_equal 'foo', payload[:index] - assert_equal 'bar', payload[:type] - assert_equal '1', payload[:id] - true - end - - instance.expects(:client).returns(client) - instance.expects(:index_name).returns('foo') - instance.expects(:document_type).returns('bar') - instance.expects(:id).returns('1') - - instance.delete_document() - end - - should "pass extra options to the delete_document method to client.delete" do - client = mock('client') - instance = ::DummyIndexingModelWithCallbacks.new - - client.expects(:delete).with do |payload| - assert_equal 'A', payload[:parent] - true - end - - instance.expects(:client).returns(client) - instance.expects(:id).returns('1') - instance.expects(:index_name).returns('foo') - instance.expects(:document_type).returns('bar') - - instance.delete_document(parent: 'A') - end - - should "update the document by re-indexing when no changes are present" do - client = mock('client') - instance = ::DummyIndexingModelWithCallbacks.new - - # Reset the fake `changes` - instance.instance_variable_set(:@__changed_model_attributes, nil) - - instance.expects(:index_document) - instance.update_document - end - - should "update the document by partial update when changes are present" do - client = mock('client') - instance = ::DummyIndexingModelWithCallbacks.new - - # Set the fake `changes` hash - instance.instance_variable_set(:@__changed_model_attributes, {foo: 'bar'}) - - client.expects(:update).with do |payload| - assert_equal 'foo', payload[:index] - assert_equal 'bar', payload[:type] - assert_equal '1', payload[:id] - assert_equal({foo: 'bar'}, payload[:body][:doc]) - true - end - - instance.expects(:client).returns(client) - instance.expects(:index_name).returns('foo') - instance.expects(:document_type).returns('bar') - instance.expects(:id).returns('1') - - instance.update_document - end - - should "exclude attributes not contained in custom as_indexed_json during partial update" do - client = mock('client') - instance = ::DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new - - # Set the fake `changes` hash - instance.instance_variable_set(:@__changed_model_attributes, {'foo' => 'B', 'bar' => 'D' }) - - client.expects(:update).with do |payload| - assert_equal({:foo => 'B'}, payload[:body][:doc]) - true - end - - instance.expects(:client).returns(client) - instance.expects(:index_name).returns('foo') - instance.expects(:document_type).returns('bar') - instance.expects(:id).returns('1') - - instance.update_document - end - - should "get attributes from as_indexed_json during partial update" do - client = mock('client') - instance = ::DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new - - instance.instance_variable_set(:@__changed_model_attributes, { 'foo' => { 'bar' => 'BAR'} }) - # Overload as_indexed_json - instance.expects(:as_indexed_json).returns({ 'foo' => 'BAR' }) - - client.expects(:update).with do |payload| - assert_equal({'foo' => 'BAR'}, payload[:body][:doc]) - true - end - - instance.expects(:client).returns(client) - instance.expects(:index_name).returns('foo') - instance.expects(:document_type).returns('bar') - instance.expects(:id).returns('1') - - instance.update_document - end - - should "index instead of update when nothing was changed" do - client = mock('client') - instance = ::DummyIndexingModelWithNoChanges.new - - # Set the fake `changes` hash - instance.instance_variable_set(:@__changed_model_attributes, {}) - # Overload as_indexed_json for running index - instance.expects(:as_indexed_json).returns({ 'foo' => 'BAR' }) - - client.expects(:index) - client.expects(:update).never - - instance.expects(:client).returns(client) - instance.expects(:index_name).returns('foo') - instance.expects(:document_type).returns('bar') - instance.expects(:id).returns('1') - - instance.update_document({}) - end - - should "update only the specific attributes" do - client = mock('client') - instance = ::DummyIndexingModelWithCallbacks.new - - # Set the fake `changes` hash - instance.instance_variable_set(:@__changed_model_attributes, {author: 'john'}) - - client.expects(:update).with do |payload| - assert_equal 'foo', payload[:index] - assert_equal 'bar', payload[:type] - assert_equal '1', payload[:id] - assert_equal({title: 'green'}, payload[:body][:doc]) - true - end - - instance.expects(:client).returns(client) - instance.expects(:index_name).returns('foo') - instance.expects(:document_type).returns('bar') - instance.expects(:id).returns('1') - - instance.update_document_attributes title: "green" - end - - should "pass options to the update_document_attributes method" do - client = mock('client') - instance = ::DummyIndexingModelWithCallbacks.new - - client.expects(:update).with do |payload| - assert_equal 'foo', payload[:index] - assert_equal 'bar', payload[:type] - assert_equal '1', payload[:id] - assert_equal({title: 'green'}, payload[:body][:doc]) - assert_equal true, payload[:refresh] - true - end - - instance.expects(:client).returns(client) - instance.expects(:index_name).returns('foo') - instance.expects(:document_type).returns('bar') - instance.expects(:id).returns('1') - - instance.update_document_attributes( { title: "green" }, { refresh: true } ) - end - end - - context "Checking for index existence" do - context "when the index exists" do - should "return true" do - indices = mock('indices', exists: true) - client = stub('client', indices: indices) - - DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once - - assert_equal true, DummyIndexingModelForRecreate.index_exists? - end - end - - context "when the index does not exists" do - should "return false" do - indices = mock('indices', exists: false) - client = stub('client', indices: indices) - - DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once - - assert_equal false, DummyIndexingModelForRecreate.index_exists? - end - end - - context "when the indices API raises an error" do - should "return false" do - client = stub('client') - client.expects(:indices).raises(StandardError) - - DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once - - assert_equal false, DummyIndexingModelForRecreate.index_exists? - end - end - - context "the indices.exists API raises an error" do - should "return false" do - indices = stub('indices') - client = stub('client') - client.expects(:indices).returns(indices) - - indices.expects(:exists).raises(StandardError) - - DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once - - assert_equal false, DummyIndexingModelForRecreate.index_exists? - end - end - end - - context "Re-creating the index" do - class ::DummyIndexingModelForRecreate - extend ActiveModel::Naming - extend Elasticsearch::Model::Naming::ClassMethods - extend Elasticsearch::Model::Indexing::ClassMethods - - settings index: { number_of_shards: 1 } do - mappings do - indexes :foo, analyzer: 'keyword' - end - end - end - - should "delete the index without raising exception when the index is not found" do - client = stub('client') - indices = stub('indices') - client.stubs(:indices).returns(indices) - - indices.expects(:delete).returns({}).then.raises(NotFound).at_least_once - - DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once - - assert_nothing_raised { DummyIndexingModelForRecreate.delete_index! force: true } - end - - should "raise an exception without the force option" do - client = stub('client') - indices = stub('indices') - client.stubs(:indices).returns(indices) - - indices.expects(:delete).raises(NotFound) - - DummyIndexingModelForRecreate.expects(:client).returns(client) - - assert_raise(NotFound) { DummyIndexingModelForRecreate.delete_index! } - end - - should "raise a regular exception when deleting the index" do - client = stub('client') - - indices = stub('indices') - indices.expects(:delete).raises(Exception) - client.stubs(:indices).returns(indices) - - DummyIndexingModelForRecreate.expects(:client).returns(client) - - assert_raise(Exception) { DummyIndexingModelForRecreate.delete_index! force: true } - end - - should "create the index with correct settings and mappings when it doesn't exist" do - client = stub('client') - indices = stub('indices') - client.stubs(:indices).returns(indices) - - indices.expects(:create).with do |payload| - assert_equal 'dummy_indexing_model_for_recreates', payload[:index] - assert_equal 1, payload[:body][:settings][:index][:number_of_shards] - assert_equal 'keyword', payload[:body][:mappings][:_doc][:properties][:foo][:analyzer] - true - end.returns({}) - - DummyIndexingModelForRecreate.expects(:index_exists?).returns(false) - DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once - - assert_nothing_raised { DummyIndexingModelForRecreate.create_index! } - end - - should "get the index settings and mappings from options" do - client = stub('client') - indices = stub('indices') - client.stubs(:indices).returns(indices) - - indices.expects(:create).with do |payload| - assert_equal 'foobar', payload[:index] - assert_equal 3, payload[:body][:settings][:index][:number_of_shards] - assert_equal 'bar', payload[:body][:mappings][:foobar][:properties][:foo][:analyzer] - true - end.returns({}) - - DummyIndexingModelForRecreate.expects(:index_exists?).returns(false) - DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once - - DummyIndexingModelForRecreate.create_index! \ - index: 'foobar', - settings: { index: { number_of_shards: 3 } }, - mappings: { foobar: { properties: { foo: { analyzer: 'bar' } } } } - end - - should "not create the index when it exists" do - client = stub('client') - indices = stub('indices') - client.stubs(:indices).returns(indices) - - indices.expects(:create).never - - DummyIndexingModelForRecreate.expects(:index_exists?).returns(true) - DummyIndexingModelForRecreate.expects(:client).returns(client).never - - assert_nothing_raised { DummyIndexingModelForRecreate.create_index! } - end - - should "raise exception during index creation" do - client = stub('client') - indices = stub('indices') - client.stubs(:indices).returns(indices) - - indices.expects(:delete).returns({}) - indices.expects(:create).raises(Exception).at_least_once - - DummyIndexingModelForRecreate.expects(:index_exists?).returns(false) - DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once - - assert_raise(Exception) { DummyIndexingModelForRecreate.create_index! force: true } - end - - should "delete the index first with the force option" do - client = stub('client') - indices = stub('indices') - client.stubs(:indices).returns(indices) - - indices.expects(:delete).returns({}) - indices.expects(:create).returns({}).at_least_once - - DummyIndexingModelForRecreate.expects(:index_exists?).returns(false) - DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once - - assert_nothing_raised do - DummyIndexingModelForRecreate.create_index! force: true - end - end - - should "refresh the index without raising exception with the force option" do - client = stub('client') - indices = stub('indices') - client.stubs(:indices).returns(indices) - - indices.expects(:refresh).returns({}).then.raises(NotFound).at_least_once - - DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once - - assert_nothing_raised { DummyIndexingModelForRecreate.refresh_index! force: true } - end - - should "raise a regular exception when refreshing the index" do - client = stub('client') - indices = stub('indices') - client.stubs(:indices).returns(indices) - - indices.expects(:refresh).returns({}).then.raises(Exception).at_least_once - - DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once - - assert_nothing_raised { DummyIndexingModelForRecreate.refresh_index! force: true } - end - - context "with a custom index name" do - setup do - @client = stub('client') - @indices = stub('indices') - @client.stubs(:indices).returns(@indices) - DummyIndexingModelForRecreate.expects(:client).returns(@client).at_least_once - end - - should "create the custom index" do - @indices.expects(:create).with do |arguments| - assert_equal 'custom-foo', arguments[:index] - true - end - DummyIndexingModelForRecreate.expects(:index_exists?).with do |arguments| - assert_equal 'custom-foo', arguments[:index] - true - end - - DummyIndexingModelForRecreate.create_index! index: 'custom-foo' - end - - should "delete the custom index" do - @indices.expects(:delete).with do |arguments| - assert_equal 'custom-foo', arguments[:index] - true - end - - DummyIndexingModelForRecreate.delete_index! index: 'custom-foo' - end - - should "refresh the custom index" do - @indices.expects(:refresh).with do |arguments| - assert_equal 'custom-foo', arguments[:index] - true - end - - DummyIndexingModelForRecreate.refresh_index! index: 'custom-foo' - end - end - end - - end -end diff --git a/elasticsearch-model/test/unit/module_test.rb b/elasticsearch-model/test/unit/module_test.rb deleted file mode 100644 index fb8e0ba61..000000000 --- a/elasticsearch-model/test/unit/module_test.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::ModuleTest < Test::Unit::TestCase - context "The main module" do - - context "client" do - should "have a default" do - client = Elasticsearch::Model.client - assert_not_nil client - assert_instance_of Elasticsearch::Transport::Client, client - end - - should "be settable" do - begin - Elasticsearch::Model.client = "Foobar" - assert_equal "Foobar", Elasticsearch::Model.client - ensure - Elasticsearch::Model.client = nil - end - end - end - - context "when included in module/class, " do - class ::DummyIncludingModel; end - class ::DummyIncludingModelWithSearchMethodDefined - def self.search(query, options={}) - "SEARCH" - end - end - - should "include and set up the proxy" do - DummyIncludingModel.__send__ :include, Elasticsearch::Model - - assert_respond_to DummyIncludingModel, :__elasticsearch__ - assert_respond_to DummyIncludingModel.new, :__elasticsearch__ - end - - should "delegate important methods to the proxy" do - DummyIncludingModel.__send__ :include, Elasticsearch::Model - - assert_respond_to DummyIncludingModel, :search - assert_respond_to DummyIncludingModel, :mappings - assert_respond_to DummyIncludingModel, :settings - assert_respond_to DummyIncludingModel, :index_name - assert_respond_to DummyIncludingModel, :document_type - assert_respond_to DummyIncludingModel, :import - end - - should "not override existing method" do - DummyIncludingModelWithSearchMethodDefined.__send__ :include, Elasticsearch::Model - - assert_equal 'SEARCH', DummyIncludingModelWithSearchMethodDefined.search('foo') - end - end - - context "settings" do - should "access the settings" do - assert_not_nil Elasticsearch::Model.settings - end - - should "allow to set settings" do - assert_nothing_raised { Elasticsearch::Model.settings[:foo] = 'bar' } - assert_equal 'bar', Elasticsearch::Model.settings[:foo] - end - end - - end -end diff --git a/elasticsearch-model/test/unit/multimodel_test.rb b/elasticsearch-model/test/unit/multimodel_test.rb deleted file mode 100644 index daf9f4043..000000000 --- a/elasticsearch-model/test/unit/multimodel_test.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::MultimodelTest < Test::Unit::TestCase - - context "Multimodel class" do - setup do - title = stub('Foo', index_name: 'foo_index', document_type: 'foo', to_ary: nil) - series = stub('Bar', index_name: 'bar_index', document_type: 'bar', to_ary: nil) - @multimodel = Elasticsearch::Model::Multimodel.new(title, series) - end - - should "have an index_name" do - assert_equal ['foo_index', 'bar_index'], @multimodel.index_name - end - - should "have a document_type" do - assert_equal ['foo', 'bar'], @multimodel.document_type - end - - should "have a client" do - assert_equal Elasticsearch::Model.client, @multimodel.client - end - - should "include models in the registry" do - class ::JustAModel - include Elasticsearch::Model - end - - class ::JustAnotherModel - include Elasticsearch::Model - end - - multimodel = Elasticsearch::Model::Multimodel.new - assert multimodel.models.include?(::JustAModel) - assert multimodel.models.include?(::JustAnotherModel) - end - end -end diff --git a/elasticsearch-model/test/unit/naming_inheritance_test.rb b/elasticsearch-model/test/unit/naming_inheritance_test.rb deleted file mode 100644 index 1b40e9cfd..000000000 --- a/elasticsearch-model/test/unit/naming_inheritance_test.rb +++ /dev/null @@ -1,94 +0,0 @@ -require "test_helper" - -class Elasticsearch::Model::NamingInheritanceTest < Test::Unit::TestCase - def setup - Elasticsearch::Model.settings[:inheritance_enabled] = true - end - - def teardown - Elasticsearch::Model.settings[:inheritance_enabled] = false - end - - context "Naming module with inheritance" do - class ::TestBase - extend ActiveModel::Naming - - extend Elasticsearch::Model::Naming::ClassMethods - include Elasticsearch::Model::Naming::InstanceMethods - end - - class ::Animal < ::TestBase - extend ActiveModel::Naming - - extend Elasticsearch::Model::Naming::ClassMethods - include Elasticsearch::Model::Naming::InstanceMethods - - index_name "mammals" - document_type "mammal" - end - - class ::Dog < ::Animal - end - - module ::MyNamespace - class Dog < ::Animal - end - end - - class ::Cat < ::Animal - extend ActiveModel::Naming - - extend Elasticsearch::Model::Naming::ClassMethods - include Elasticsearch::Model::Naming::InstanceMethods - - index_name "cats" - document_type "cat" - end - - should "return the default index_name" do - assert_equal "test_bases", TestBase.index_name - assert_equal "test_bases", TestBase.new.index_name - end - - should "return the explicit index_name" do - assert_equal "mammals", Animal.index_name - assert_equal "mammals", Animal.new.index_name - - assert_equal "cats", Cat.index_name - assert_equal "cats", Cat.new.index_name - end - - should "return the ancestor index_name" do - assert_equal "mammals", Dog.index_name - assert_equal "mammals", Dog.new.index_name - end - - should "return the ancestor index_name for namespaced model" do - assert_equal "mammals", ::MyNamespace::Dog.index_name - assert_equal "mammals", ::MyNamespace::Dog.new.index_name - end - - should "return the default document_type" do - assert_equal "_doc", TestBase.document_type - assert_equal "_doc", TestBase.new.document_type - end - - should "return the explicit document_type" do - assert_equal "mammal", Animal.document_type - assert_equal "mammal", Animal.new.document_type - - assert_equal "cat", Cat.document_type - assert_equal "cat", Cat.new.document_type - end - - should "return the ancestor document_type" do - assert_equal "mammal", Dog.document_type - assert_equal "mammal", Dog.new.document_type - end - - should "return the ancestor document_type for namespaced model" do - assert_equal "mammal", ::MyNamespace::Dog.document_type - assert_equal "mammal", ::MyNamespace::Dog.new.document_type - end - end -end diff --git a/elasticsearch-model/test/unit/naming_test.rb b/elasticsearch-model/test/unit/naming_test.rb deleted file mode 100644 index 3f2368891..000000000 --- a/elasticsearch-model/test/unit/naming_test.rb +++ /dev/null @@ -1,103 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::NamingTest < Test::Unit::TestCase - context "Naming module" do - class ::DummyNamingModel - extend ActiveModel::Naming - - extend Elasticsearch::Model::Naming::ClassMethods - include Elasticsearch::Model::Naming::InstanceMethods - end - - module ::MyNamespace - class DummyNamingModelInNamespace - extend ActiveModel::Naming - - extend Elasticsearch::Model::Naming::ClassMethods - include Elasticsearch::Model::Naming::InstanceMethods - end - end - - should "return the default index_name" do - assert_equal 'dummy_naming_models', DummyNamingModel.index_name - assert_equal 'dummy_naming_models', DummyNamingModel.new.index_name - end - - should "return the sanitized default index_name for namespaced model" do - assert_equal 'my_namespace-dummy_naming_model_in_namespaces', ::MyNamespace::DummyNamingModelInNamespace.index_name - assert_equal 'my_namespace-dummy_naming_model_in_namespaces', ::MyNamespace::DummyNamingModelInNamespace.new.index_name - end - - should "return the default document_type" do - assert_equal '_doc', DummyNamingModel.document_type - assert_equal '_doc', DummyNamingModel.new.document_type - end - - should "set and return the index_name" do - DummyNamingModel.index_name 'foobar' - assert_equal 'foobar', DummyNamingModel.index_name - - d = DummyNamingModel.new - d.index_name 'foobar_d' - assert_equal 'foobar_d', d.index_name - - modifier = 'r' - d.index_name Proc.new{ "foobar_#{modifier}" } - assert_equal 'foobar_r', d.index_name - - modifier = 'z' - assert_equal 'foobar_z', d.index_name - - modifier = 'f' - d.index_name { "foobar_#{modifier}" } - assert_equal 'foobar_f', d.index_name - - modifier = 't' - assert_equal 'foobar_t', d.index_name - end - - should "set the index_name with setter" do - DummyNamingModel.index_name = 'foobar_index_S' - assert_equal 'foobar_index_S', DummyNamingModel.index_name - - d = DummyNamingModel.new - d.index_name = 'foobar_index_s' - assert_equal 'foobar_index_s', d.index_name - - assert_equal 'foobar_index_S', DummyNamingModel.index_name - - modifier2 = 'y' - DummyNamingModel.index_name = Proc.new{ "foobar_index_#{modifier2}" } - assert_equal 'foobar_index_y', DummyNamingModel.index_name - - modifier = 'r' - d.index_name = Proc.new{ "foobar_index_#{modifier}" } - assert_equal 'foobar_index_r', d.index_name - - modifier = 'z' - assert_equal 'foobar_index_z', d.index_name - - assert_equal 'foobar_index_y', DummyNamingModel.index_name - end - - should "set and return the document_type" do - DummyNamingModel.document_type 'foobar' - assert_equal 'foobar', DummyNamingModel.document_type - - d = DummyNamingModel.new - d.document_type 'foobar_d' - assert_equal 'foobar_d', d.document_type - end - - should "set the document_type with setter" do - DummyNamingModel.document_type = 'foobar_type_S' - assert_equal 'foobar_type_S', DummyNamingModel.document_type - - d = DummyNamingModel.new - d.document_type = 'foobar_type_s' - assert_equal 'foobar_type_s', d.document_type - - assert_equal 'foobar_type_S', DummyNamingModel.document_type - end - end -end diff --git a/elasticsearch-model/test/unit/proxy_test.rb b/elasticsearch-model/test/unit/proxy_test.rb deleted file mode 100644 index a64b5b175..000000000 --- a/elasticsearch-model/test/unit/proxy_test.rb +++ /dev/null @@ -1,98 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::SearchTest < Test::Unit::TestCase - context "Searching module" do - class ::DummyProxyModel - include Elasticsearch::Model::Proxy - - def self.foo - 'classy foo' - end - - def bar - 'insta barr' - end - - def as_json(options) - {foo: 'bar'} - end - end - - class ::DummyProxyModelWithCallbacks - def self.before_save(&block) - (@callbacks ||= {})[block.hash] = block - end - - def changes_to_save - {:foo => ['One', 'Two']} - end - end - - should "setup the class proxy method" do - assert_respond_to DummyProxyModel, :__elasticsearch__ - end - - should "setup the instance proxy method" do - assert_respond_to DummyProxyModel.new, :__elasticsearch__ - end - - should "register the hook for before_save callback" do - ::DummyProxyModelWithCallbacks.expects(:before_save).returns(true) - DummyProxyModelWithCallbacks.__send__ :include, Elasticsearch::Model::Proxy - end - - should "set the @__changed_model_attributes variable before save" do - instance = ::DummyProxyModelWithCallbacks.new - instance.__elasticsearch__.expects(:instance_variable_set).with do |name, value| - assert_equal :@__changed_model_attributes, name - assert_equal({foo: 'Two'}, value) - true - end - - ::DummyProxyModelWithCallbacks.__send__ :include, Elasticsearch::Model::Proxy - - ::DummyProxyModelWithCallbacks.instance_variable_get(:@callbacks).each do |n,b| - instance.instance_eval(&b) - end - end - - should "delegate methods to the target" do - assert_respond_to DummyProxyModel.__elasticsearch__, :foo - assert_respond_to DummyProxyModel.new.__elasticsearch__, :bar - - assert_raise(NoMethodError) { DummyProxyModel.__elasticsearch__.xoxo } - assert_raise(NoMethodError) { DummyProxyModel.new.__elasticsearch__.xoxo } - - assert_equal 'classy foo', DummyProxyModel.__elasticsearch__.foo - assert_equal 'insta barr', DummyProxyModel.new.__elasticsearch__.bar - end - - should "reset the proxy target for duplicates" do - model = DummyProxyModel.new - model_target = model.__elasticsearch__.target - duplicate = model.dup - duplicate_target = duplicate.__elasticsearch__.target - - assert_not_equal model, duplicate - assert_equal model, model_target - assert_equal duplicate, duplicate_target - end - - should "return the proxy class from instance proxy" do - assert_equal Elasticsearch::Model::Proxy::ClassMethodsProxy, DummyProxyModel.new.__elasticsearch__.class.class - end - - should "return the origin class from instance proxy" do - assert_equal DummyProxyModel, DummyProxyModel.new.__elasticsearch__.klass - end - - should "delegate as_json from the proxy to target" do - assert_equal({foo: 'bar'}, DummyProxyModel.new.__elasticsearch__.as_json) - end - - should "have inspect method indicating the proxy" do - assert_match /PROXY/, DummyProxyModel.__elasticsearch__.inspect - assert_match /PROXY/, DummyProxyModel.new.__elasticsearch__.inspect - end - end -end diff --git a/elasticsearch-model/test/unit/searching_search_request_test.rb b/elasticsearch-model/test/unit/searching_search_request_test.rb deleted file mode 100644 index b2e84aecc..000000000 --- a/elasticsearch-model/test/unit/searching_search_request_test.rb +++ /dev/null @@ -1,78 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::SearchRequestTest < Test::Unit::TestCase - context "SearchRequest class" do - class ::DummySearchingModel - extend Elasticsearch::Model::Searching::ClassMethods - - def self.index_name; 'foo'; end - def self.document_type; 'bar'; end - - end - - setup do - @client = mock('client') - DummySearchingModel.stubs(:client).returns(@client) - end - - should "pass the search definition as a simple query" do - @client.expects(:search).with do |params| - assert_equal 'foo', params[:q] - true - end - .returns({}) - - s = Elasticsearch::Model::Searching::SearchRequest.new ::DummySearchingModel, 'foo' - s.execute! - end - - should "pass the search definition as a Hash" do - @client.expects(:search).with do |params| - assert_equal( {foo: 'bar'}, params[:body] ) - true - end - .returns({}) - - s = Elasticsearch::Model::Searching::SearchRequest.new ::DummySearchingModel, foo: 'bar' - s.execute! - end - - should "pass the search definition as a JSON string" do - @client.expects(:search).with do |params| - assert_equal( '{"foo":"bar"}', params[:body] ) - true - end - .returns({}) - - s = Elasticsearch::Model::Searching::SearchRequest.new ::DummySearchingModel, '{"foo":"bar"}' - s.execute! - end - - should "pass the search definition as an object which responds to to_hash" do - class MySpecialQueryBuilder - def to_hash; {foo: 'bar'}; end - end - - @client.expects(:search).with do |params| - assert_equal( {foo: 'bar'}, params[:body] ) - true - end - .returns({}) - - s = Elasticsearch::Model::Searching::SearchRequest.new ::DummySearchingModel, MySpecialQueryBuilder.new - s.execute! - end - - should "pass the options to the client" do - @client.expects(:search).with do |params| - assert_equal 'foo', params[:q] - assert_equal 15, params[:size] - true - end - .returns({}) - - s = Elasticsearch::Model::Searching::SearchRequest.new ::DummySearchingModel, 'foo', size: 15 - s.execute! - end - end -end diff --git a/elasticsearch-model/test/unit/searching_test.rb b/elasticsearch-model/test/unit/searching_test.rb deleted file mode 100644 index f6cb78136..000000000 --- a/elasticsearch-model/test/unit/searching_test.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::SearchingTest < Test::Unit::TestCase - context "Searching module" do - class ::DummySearchingModel - extend Elasticsearch::Model::Searching::ClassMethods - - def self.index_name; 'foo'; end - def self.document_type; 'bar'; end - end - - setup do - @client = mock('client') - DummySearchingModel.stubs(:client).returns(@client) - end - - should "have the search method" do - assert_respond_to DummySearchingModel, :search - end - - should "initialize the search object" do - Elasticsearch::Model::Searching::SearchRequest - .expects(:new).with do |klass, query, options| - assert_equal DummySearchingModel, klass - assert_equal 'foo', query - assert_equal({default_operator: 'AND'}, options) - true - end - .returns( stub('search') ) - - DummySearchingModel.search 'foo', default_operator: 'AND' - end - - should "not execute the search" do - Elasticsearch::Model::Searching::SearchRequest - .expects(:new).returns( mock('search').expects(:execute!).never ) - - DummySearchingModel.search 'foo' - end - end -end diff --git a/elasticsearch-model/test/unit/serializing_test.rb b/elasticsearch-model/test/unit/serializing_test.rb deleted file mode 100644 index 201329257..000000000 --- a/elasticsearch-model/test/unit/serializing_test.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'test_helper' - -class Elasticsearch::Model::SerializingTest < Test::Unit::TestCase - context "Serializing module" do - class DummyClass - include Elasticsearch::Model::Serializing::InstanceMethods - - def as_json(options={}) - 'HASH' - end - end - - should "delegate to as_json by default" do - assert_equal 'HASH', DummyClass.new.as_indexed_json - end - end -end From e72e02ca3eefef1e6af673456ec28b90e0e5c873 Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Wed, 19 Sep 2018 17:00:48 +0200 Subject: [PATCH 413/582] [MODEL] Port all integration tests to rspec (#837) --- elasticsearch-model/Rakefile | 11 +- elasticsearch-model/gemfiles/4.0.gemfile | 1 + elasticsearch-model/gemfiles/5.0.gemfile | 1 + .../spec/elasticsearch/model/adapter_spec.rb | 10 +- .../active_record/associations_spec.rb | 334 +++++++++++++++++ .../adapters/active_record/basic_spec.rb | 340 ++++++++++++++++++ .../active_record/dynamic_index_name_spec.rb | 18 + .../adapters/active_record/import_spec.rb | 187 ++++++++++ .../active_record/multi_model_spec.rb | 110 ++++++ .../active_record/namespaced_model_spec.rb | 38 ++ .../adapters/active_record/pagination_spec.rb | 315 ++++++++++++++++ .../active_record/parent_child_spec.rb | 75 ++++ .../active_record/serialization_spec.rb | 61 ++++ .../model/adapters/active_record_spec.rb | 33 +- .../model/adapters/default_spec.rb | 2 +- .../model/adapters/mongoid/basic_spec.rb | 267 ++++++++++++++ .../adapters/mongoid/multi_model_spec.rb | 66 ++++ .../model/adapters/mongoid_spec.rb | 2 +- .../model/adapters/multiple_spec.rb | 11 +- .../elasticsearch/model/callbacks_spec.rb | 3 +- .../spec/elasticsearch/model/client_spec.rb | 2 +- .../elasticsearch/model/importing_spec.rb | 3 +- .../spec/elasticsearch/model/indexing_spec.rb | 3 +- .../spec/elasticsearch/model/module_spec.rb | 9 +- .../elasticsearch/model/multimodel_spec.rb | 3 +- .../model/naming_inheritance_spec.rb | 16 +- .../spec/elasticsearch/model/naming_spec.rb | 3 +- .../spec/elasticsearch/model/proxy_spec.rb | 9 +- .../model/response/aggregations_spec.rb | 2 +- .../elasticsearch/model/response/base_spec.rb | 3 +- .../response/pagination/kaminari_spec.rb | 4 +- .../response/pagination/will_paginate_spec.rb | 5 +- .../model/response/records_spec.rb | 3 +- .../model/response/response_spec.rb | 2 +- .../model/response/results_spec.rb | 2 +- .../model/searching_search_request_spec.rb | 4 +- .../elasticsearch/model/searching_spec.rb | 2 +- .../elasticsearch/model/serializing_spec.rb | 2 +- elasticsearch-model/spec/spec_helper.rb | 144 ++++++++ elasticsearch-model/spec/support/app.rb | 21 ++ .../spec/support/app/answer.rb | 33 ++ .../spec/support/app/article.rb | 22 ++ .../support/app/article_for_pagination.rb | 12 + .../app/article_with_custom_serialization.rb | 13 + .../app/article_with_dynamic_index_name.rb | 15 + .../spec/support/app/author.rb | 9 + .../spec/support/app/authorship.rb | 4 + .../spec/support/app/category.rb | 3 + .../spec/support/app/comment.rb | 3 + .../spec/support/app/episode.rb | 11 + elasticsearch-model/spec/support/app/image.rb | 19 + .../spec/support/app/import_article.rb | 12 + .../spec/support/app/mongoid_article.rb | 21 ++ .../spec/support/app/namespaced_book.rb | 10 + .../app/parent_and_child_searchable.rb | 24 ++ elasticsearch-model/spec/support/app/post.rb | 14 + .../spec/support/app/question.rb | 27 ++ .../spec/support/app/searchable.rb | 48 +++ .../spec/support/app/series.rb | 11 + ...e_record_associations_parent_child_test.rb | 188 ---------- .../active_record_associations_test.rb | 339 ----------------- .../integration/active_record_basic_test.rb | 263 -------------- ...active_record_custom_serialization_test.rb | 67 ---- .../integration/active_record_import_test.rb | 198 ---------- .../active_record_namespaced_model_test.rb | 56 --- .../active_record_pagination_test.rb | 149 -------- .../integration/dynamic_index_name_test.rb | 52 --- .../test/integration/mongoid_basic_test.rb | 240 ------------- .../test/integration/multiple_models_test.rb | 176 --------- elasticsearch-model/test/test_helper.rb | 92 ----- 70 files changed, 2338 insertions(+), 1920 deletions(-) create mode 100644 elasticsearch-model/spec/elasticsearch/model/adapters/active_record/associations_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb create mode 100644 elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb create mode 100644 elasticsearch-model/spec/support/app.rb create mode 100644 elasticsearch-model/spec/support/app/answer.rb create mode 100644 elasticsearch-model/spec/support/app/article.rb create mode 100644 elasticsearch-model/spec/support/app/article_for_pagination.rb create mode 100644 elasticsearch-model/spec/support/app/article_with_custom_serialization.rb create mode 100644 elasticsearch-model/spec/support/app/article_with_dynamic_index_name.rb create mode 100644 elasticsearch-model/spec/support/app/author.rb create mode 100644 elasticsearch-model/spec/support/app/authorship.rb create mode 100644 elasticsearch-model/spec/support/app/category.rb create mode 100644 elasticsearch-model/spec/support/app/comment.rb create mode 100644 elasticsearch-model/spec/support/app/episode.rb create mode 100644 elasticsearch-model/spec/support/app/image.rb create mode 100644 elasticsearch-model/spec/support/app/import_article.rb create mode 100644 elasticsearch-model/spec/support/app/mongoid_article.rb create mode 100644 elasticsearch-model/spec/support/app/namespaced_book.rb create mode 100644 elasticsearch-model/spec/support/app/parent_and_child_searchable.rb create mode 100644 elasticsearch-model/spec/support/app/post.rb create mode 100644 elasticsearch-model/spec/support/app/question.rb create mode 100644 elasticsearch-model/spec/support/app/searchable.rb create mode 100644 elasticsearch-model/spec/support/app/series.rb delete mode 100644 elasticsearch-model/test/integration/active_record_associations_parent_child_test.rb delete mode 100644 elasticsearch-model/test/integration/active_record_associations_test.rb delete mode 100644 elasticsearch-model/test/integration/active_record_basic_test.rb delete mode 100644 elasticsearch-model/test/integration/active_record_custom_serialization_test.rb delete mode 100644 elasticsearch-model/test/integration/active_record_import_test.rb delete mode 100644 elasticsearch-model/test/integration/active_record_namespaced_model_test.rb delete mode 100644 elasticsearch-model/test/integration/active_record_pagination_test.rb delete mode 100755 elasticsearch-model/test/integration/dynamic_index_name_test.rb delete mode 100644 elasticsearch-model/test/integration/mongoid_basic_test.rb delete mode 100644 elasticsearch-model/test/integration/multiple_models_test.rb delete mode 100644 elasticsearch-model/test/test_helper.rb diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index bda261913..e479d3599 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -34,6 +34,10 @@ namespace :test do desc "Run unit tests against ActiveModel 3, 4 and 5" task :unit do + end + + desc "Run integration tests against latest stable ActiveModel (5)" + task :integration do ['3.0.gemfile', '4.0.gemfile', '5.0.gemfile'].each do |gemfile| ['bundle exec rake test:run_unit', 'bundle exec rspec'].each do |cmd| sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/'+gemfile, __FILE__)}' #{cmd}" @@ -41,13 +45,6 @@ namespace :test do end end - desc "Run integration tests against latest stable ActiveModel (5)" - task :integration do - #sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/3.0.gemfile', __FILE__)}' bundle exec rake test:run_integration" - #sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/4.0.gemfile', __FILE__)}' bundle exec rake test:run_integration" - sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/5.0.gemfile', __FILE__)}' bundle exec rake test:run_integration" - end - desc "Run unit and integration tests" task :all do Rake::Task['test:unit'].invoke diff --git a/elasticsearch-model/gemfiles/4.0.gemfile b/elasticsearch-model/gemfiles/4.0.gemfile index eea429e12..f8acebc38 100644 --- a/elasticsearch-model/gemfiles/4.0.gemfile +++ b/elasticsearch-model/gemfiles/4.0.gemfile @@ -10,6 +10,7 @@ gemspec path: '../' gem 'activemodel', '~> 4' gem 'activerecord', '~> 4' gem 'sqlite3' unless defined?(JRUBY_VERSION) +gem 'mongoid', '~> 5' group :development, :testing do gem 'rspec' diff --git a/elasticsearch-model/gemfiles/5.0.gemfile b/elasticsearch-model/gemfiles/5.0.gemfile index aa8e77655..612f2bbd9 100644 --- a/elasticsearch-model/gemfiles/5.0.gemfile +++ b/elasticsearch-model/gemfiles/5.0.gemfile @@ -10,6 +10,7 @@ gemspec path: '../' gem 'activemodel', '~> 5' gem 'activerecord', '~> 5' gem 'sqlite3' unless defined?(JRUBY_VERSION) +gem 'mongoid', '~> 6' group :development, :testing do gem 'rspec' diff --git a/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb index 01526c12b..71fd2e6b5 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb @@ -13,12 +13,10 @@ class ::DummyAdapter end after(:all) do - Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyAdapterClassWithAdapter) - Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyAdapterClass) - Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyAdapter) - Object.send(:remove_const, :DummyAdapterClass) if defined?(DummyAdapterClass) - Object.send(:remove_const, :DummyAdapterClassWithAdapter) if defined?(DummyAdapterClassWithAdapter) - Object.send(:remove_const, :DummyAdapter) if defined?(DummyAdapter) + [DummyAdapterClassWithAdapter, DummyAdapterClass, DummyAdapter].each do |adapter| + Elasticsearch::Model::Adapter::Adapter.adapters.delete(adapter) + end + remove_classes(DummyAdapterClass, DummyAdapterClassWithAdapter, DummyAdapter) end describe '#from_class' do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/associations_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/associations_spec.rb new file mode 100644 index 000000000..0cfed6432 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/associations_spec.rb @@ -0,0 +1,334 @@ +require 'spec_helper' + +describe 'Elasticsearch::Model::Adapter::ActiveRecord Associations' do + + before(:all) do + ActiveRecord::Schema.define(version: 1) do + create_table :categories do |t| + t.string :title + t.timestamps null: false + end + + create_table :categories_posts do |t| + t.references :post, :category + end + + create_table :authors do |t| + t.string :first_name, :last_name + t.timestamps null: false + end + + create_table :authorships do |t| + t.string :first_name, :last_name + t.references :post + t.references :author + t.timestamps null: false + end + + create_table :comments do |t| + t.string :text + t.string :author + t.references :post + t.timestamps null: false + end + + add_index(:comments, :post_id) unless index_exists?(:comments, :post_id) + + create_table :posts do |t| + t.string :title + t.text :text + t.boolean :published + t.timestamps null: false + end + end + + Comment.__send__ :include, Elasticsearch::Model + Comment.__send__ :include, Elasticsearch::Model::Callbacks + end + + before do + clear_tables(:categories, :categories_posts, :authors, :authorships, :comments, :posts) + clear_indices(Post) + Post.__elasticsearch__.create_index!(force: true) + Comment.__elasticsearch__.create_index!(force: true) + end + + after do + clear_tables(Post, Category) + clear_indices(Post) + end + + context 'when a document is created' do + + before do + Post.create!(title: 'Test') + Post.create!(title: 'Testing Coding') + Post.create!(title: 'Coding') + Post.__elasticsearch__.refresh_index! + end + + let(:search_result) do + Post.search('title:test') + end + + it 'indexes the document' do + expect(search_result.results.size).to eq(2) + expect(search_result.results.first.title).to eq('Test') + expect(search_result.records.size).to eq(2) + expect(search_result.records.first.title).to eq('Test') + end + end + + describe 'has_many_and_belongs_to association' do + + context 'when an association is updated' do + + before do + post.categories = [category_a, category_b] + Post.__elasticsearch__.refresh_index! + end + + let(:category_a) do + Category.where(title: "One").first_or_create! + end + + let(:category_b) do + Category.where(title: "Two").first_or_create! + end + + let(:post) do + Post.create! title: "First Post", text: "This is the first post..." + end + + let(:search_result) do + Post.search(query: { + bool: { + must: { + multi_match: { + fields: ['title'], + query: 'first' + } + }, + filter: { + terms: { + categories: ['One'] + } + } + } + } ) + end + + it 'applies the update with' do + expect(search_result.results.size).to eq(1) + expect(search_result.results.first.title).to eq('First Post') + expect(search_result.records.size).to eq(1) + expect(search_result.records.first.title).to eq('First Post') + end + end + + context 'when an association is deleted' do + + before do + post.categories = [category_a, category_b] + post.categories = [category_b] + Post.__elasticsearch__.refresh_index! + end + + let(:category_a) do + Category.where(title: "One").first_or_create! + end + + let(:category_b) do + Category.where(title: "Two").first_or_create! + end + + let(:post) do + Post.create! title: "First Post", text: "This is the first post..." + end + + let(:search_result) do + Post.search(query: { + bool: { + must: { + multi_match: { + fields: ['title'], + query: 'first' + } + }, + filter: { + terms: { + categories: ['One'] + } + } + } + } ) + end + + it 'applies the update with a reindex' do + expect(search_result.results.size).to eq(0) + expect(search_result.records.size).to eq(0) + end + end + end + + describe 'has_many through association' do + + context 'when the association is updated' do + + before do + author_a = Author.where(first_name: "John", last_name: "Smith").first_or_create! + author_b = Author.where(first_name: "Mary", last_name: "Smith").first_or_create! + author_c = Author.where(first_name: "Kobe", last_name: "Griss").first_or_create! + + # Create posts + post_1 = Post.create!(title: "First Post", text: "This is the first post...") + post_2 = Post.create!(title: "Second Post", text: "This is the second post...") + post_3 = Post.create!(title: "Third Post", text: "This is the third post...") + + # Assign authors + post_1.authors = [author_a, author_b] + post_2.authors = [author_a] + post_3.authors = [author_c] + + Post.__elasticsearch__.refresh_index! + end + + context 'if active record is at least 4' do + + let(:search_result) do + Post.search('authors.full_name:john') + end + + it 'applies the update', if: active_record_at_least_4? do + expect(search_result.results.size).to eq(2) + expect(search_result.records.size).to eq(2) + end + end + + context 'if active record is less than 4' do + + let(:search_result) do + Post.search('authors.author.full_name:john') + end + + it 'applies the update', if: !active_record_at_least_4? do + expect(search_result.results.size).to eq(2) + expect(search_result.records.size).to eq(2) + end + end + end + + context 'when an association is added', if: active_record_at_least_4? do + + before do + author_a = Author.where(first_name: "John", last_name: "Smith").first_or_create! + author_b = Author.where(first_name: "Mary", last_name: "Smith").first_or_create! + + # Create posts + post_1 = Post.create!(title: "First Post", text: "This is the first post...") + + # Assign authors + post_1.authors = [author_a] + post_1.authors << author_b + Post.__elasticsearch__.refresh_index! + end + + let(:search_result) do + Post.search('authors.full_name:john') + end + + it 'adds the association' do + expect(search_result.results.size).to eq(1) + expect(search_result.records.size).to eq(1) + end + end + end + + describe 'has_many association' do + + context 'when an association is added', if: active_record_at_least_4? do + + before do + # Create posts + post_1 = Post.create!(title: "First Post", text: "This is the first post...") + post_2 = Post.create!(title: "Second Post", text: "This is the second post...") + + # Add comments + post_1.comments.create!(author: 'John', text: 'Excellent') + post_1.comments.create!(author: 'Abby', text: 'Good') + + post_2.comments.create!(author: 'John', text: 'Terrible') + + post_1.comments.create!(author: 'John', text: 'Or rather just good...') + Post.__elasticsearch__.refresh_index! + end + + let(:search_result) do + Post.search(query: { + nested: { + path: 'comments', + query: { + bool: { + must: [ + { match: { 'comments.author' => 'john' } }, + { match: { 'comments.text' => 'good' } } + ] + } + } + } + }) + end + + it 'adds the association' do + expect(search_result.results.size).to eq(1) + end + end + end + + describe '#touch' do + + context 'when a touch callback is defined on the model' do + + before do + # Create categories + category_a = Category.where(title: "One").first_or_create! + + # Create post + post = Post.create!(title: "First Post", text: "This is the first post...") + + # Assign category + post.categories << category_a + category_a.update_attribute(:title, "Updated") + category_a.posts.each { |p| p.touch } + + Post.__elasticsearch__.refresh_index! + end + + it 'executes the callback after #touch' do + expect(Post.search('categories:One').size).to eq(0) + expect(Post.search('categories:Updated').size).to eq(1) + end + end + end + + describe '#includes' do + + before do + post_1 = Post.create(title: 'One') + post_2 = Post.create(title: 'Two') + post_1.comments.create(text: 'First comment') + post_2.comments.create(text: 'Second comment') + + Comment.__elasticsearch__.refresh_index! + end + + let(:search_result) do + Comment.search('first').records(includes: :post) + end + + it 'eager loads associations' do + expect(search_result.first.association(:post)).to be_loaded + expect(search_result.first.post.title).to eq('One') + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb new file mode 100644 index 000000000..a4d9c05c5 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb @@ -0,0 +1,340 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Adapter::ActiveRecord do + + before(:all) do + ActiveRecord::Schema.define(:version => 1) do + create_table :articles do |t| + t.string :title + t.string :body + t.integer :clicks, :default => 0 + t.datetime :created_at, :default => 'NOW()' + end + end + + Article.delete_all + Article.__elasticsearch__.create_index!(force: true) + + Article.create!(title: 'Test', body: '', clicks: 1) + Article.create!(title: 'Testing Coding', body: '', clicks: 2) + Article.create!(title: 'Coding', body: '', clicks: 3) + + Article.__elasticsearch__.refresh_index! + end + + describe 'indexing a document' do + + let(:search_result) do + Article.search('title:test') + end + + it 'allows searching for documents' do + expect(search_result.results.size).to be(2) + expect(search_result.records.size).to be(2) + end + end + + describe '#results' do + + let(:search_result) do + Article.search('title:test') + end + + it 'returns an instance of Response::Result' do + expect(search_result.results.first).to be_a(Elasticsearch::Model::Response::Result) + end + + it 'prooperly loads the document' do + expect(search_result.results.first.title).to eq('Test') + end + + context 'when the result contains other data' do + + let(:search_result) do + Article.search(query: { match: { title: 'test' } }, highlight: { fields: { title: {} } }) + end + + it 'allows access to the Elasticsearch result' do + expect(search_result.results.first.title).to eq('Test') + expect(search_result.results.first.title?).to be(true) + expect(search_result.results.first.boo?).to be(false) + expect(search_result.results.first.highlight?).to be(true) + expect(search_result.results.first.highlight.title?).to be(true) + expect(search_result.results.first.highlight.boo?).to be(false) + end + end + end + + describe '#records' do + + let(:search_result) do + Article.search('title:test') + end + + it 'returns an instance of the model' do + expect(search_result.records.first).to be_a(Article) + end + + it 'prooperly loads the document' do + expect(search_result.records.first.title).to eq('Test') + end + end + + describe 'Enumerable' do + + let(:search_result) do + Article.search('title:test') + end + + it 'allows iteration over results' do + expect(search_result.results.map(&:_id)).to eq(['1', '2']) + end + + it 'allows iteration over records' do + expect(search_result.records.map(&:id)).to eq([1, 2]) + end + end + + describe '#id' do + + let(:search_result) do + Article.search('title:test') + end + + it 'returns the id' do + expect(search_result.results.first.id).to eq('1') + end + end + + describe '#id' do + + let(:search_result) do + Article.search('title:test') + end + + it 'returns the type' do + expect(search_result.results.first.type).to eq('article') + end + end + + describe '#each_with_hit' do + + let(:search_result) do + Article.search('title:test') + end + + it 'returns the record with the Elasticsearch hit' do + search_result.records.each_with_hit do |r, h| + expect(h._score).not_to be_nil + expect(h._source.title).not_to be_nil + end + end + end + + describe 'search results order' do + + let(:search_result) do + Article.search(query: { match: { title: 'code' }}, sort: { clicks: :desc }) + end + + it 'preserves the search results order when accessing a single record' do + expect(search_result.records[0].clicks).to be(3) + expect(search_result.records[1].clicks).to be(2) + expect(search_result.records.first).to eq(search_result.records[0]) + end + + it 'preserves the search results order for the list of records' do + search_result.records.each_with_hit do |r, h| + expect(r.id.to_s).to eq(h._id) + end + + search_result.records.map_with_hit do |r, h| + expect(r.id.to_s).to eq(h._id) + end + end + end + + describe 'a paged collection' do + + let(:search_result) do + Article.search(query: { match: { title: { query: 'test' } } }, + size: 2, + from: 1) + end + + it 'applies the paged options to the search' do + expect(search_result.results.size).to eq(1) + expect(search_result.results.first.title).to eq('Testing Coding') + expect(search_result.records.size).to eq(1) + expect(search_result.records.first.title).to eq('Testing Coding') + end + end + + describe '#destroy' do + + before do + Article.create!(title: 'destroy', body: '', clicks: 1) + Article.__elasticsearch__.refresh_index! + Article.where(title: 'destroy').first.destroy + + Article.__elasticsearch__.refresh_index! + end + + let(:search_result) do + Article.search('title:test') + end + + it 'removes the document from the index' do + expect(Article.count).to eq(3) + expect(search_result.results.size).to eq(2) + expect(search_result.records.size).to eq(2) + end + end + + describe 'full document updates' do + + before do + article = Article.create!(title: 'update', body: '', clicks: 1) + Article.__elasticsearch__.refresh_index! + article.title = 'Writing' + article.save + + Article.__elasticsearch__.refresh_index! + end + + let(:search_result) do + Article.search('title:write') + end + + it 'applies the update' do + expect(search_result.results.size).to eq(1) + expect(search_result.records.size).to eq(1) + end + end + + describe 'attribute updates' do + + before do + article = Article.create!(title: 'update', body: '', clicks: 1) + Article.__elasticsearch__.refresh_index! + article.title = 'special' + article.save + + Article.__elasticsearch__.refresh_index! + end + + let(:search_result) do + Article.search('title:special') + end + + it 'applies the update' do + expect(search_result.results.size).to eq(1) + expect(search_result.records.size).to eq(1) + end + end + + describe '#save' do + + before do + article = Article.create!(title: 'save', body: '', clicks: 1) + + ActiveRecord::Base.transaction do + article.body = 'dummy' + article.save + + article.title = 'special' + article.save + end + + article.__elasticsearch__.update_document + Article.__elasticsearch__.refresh_index! + end + + let(:search_result) do + Article.search('body:dummy') + end + + it 'applies the save' do + expect(search_result.results.size).to eq(1) + expect(search_result.records.size).to eq(1) + end + end + + describe 'a DSL search' do + + let(:search_result) do + Article.search(query: { match: { title: { query: 'test' } } }) + end + + it 'returns the results' do + expect(search_result.results.size).to eq(2) + expect(search_result.records.size).to eq(2) + end + end + + describe 'chaining SQL queries on response.records' do + + let(:search_result) do + Article.search(query: { match: { title: { query: 'test' } } }) + end + + it 'executes the SQL request with the chained query criteria' do + expect(search_result.records.size).to eq(2) + expect(search_result.records.where(title: 'Test').size).to eq(1) + expect(search_result.records.where(title: 'Test').first.title).to eq('Test') + end + end + + describe 'ordering of SQL queries' do + + context 'when order is called on the ActiveRecord query' do + + let(:search_result) do + Article.search query: { match: { title: { query: 'test' } } } + end + + it 'allows the SQL query to be ordered independent of the Elasticsearch results order', unless: active_record_at_least_4? do + expect(search_result.records.order('title DESC').first.title).to eq('Testing Coding') + expect(search_result.records.order('title DESC')[0].title).to eq('Testing Coding') + end + + it 'allows the SQL query to be ordered independent of the Elasticsearch results order', if: active_record_at_least_4? do + expect(search_result.records.order(title: :desc).first.title).to eq('Testing Coding') + expect(search_result.records.order(title: :desc)[0].title).to eq('Testing Coding') + end + end + + context 'when more methods are chained on the ActiveRecord query' do + + let(:search_result) do + Article.search query: {match: {title: {query: 'test'}}} + end + + it 'allows the SQL query to be ordered independent of the Elasticsearch results order', if: active_record_at_least_4? do + expect(search_result.records.distinct.order(title: :desc).first.title).to eq('Testing Coding') + expect(search_result.records.distinct.order(title: :desc)[0].title).to eq('Testing Coding') + end + end + end + + describe 'access to the response via methods' do + + let(:search_result) do + Article.search(query: { match: { title: { query: 'test' } } }, + aggregations: { + dates: { date_histogram: { field: 'created_at', interval: 'hour' } }, + clicks: { global: {}, aggregations: { min: { min: { field: 'clicks' } } } } + }, + suggest: { text: 'tezt', title: { term: { field: 'title', suggest_mode: 'always' } } }) + end + + it 'allows document keys to be access via methods' do + expect(search_result.aggregations.dates.buckets.first.doc_count).to eq(2) + expect(search_result.aggregations.clicks.doc_count).to eq(6) + expect(search_result.aggregations.clicks.min.value).to eq(1.0) + expect(search_result.aggregations.clicks.max).to be_nil + expect(search_result.suggestions.title.first.options.size).to eq(1) + expect(search_result.suggestions.terms).to eq(['test']) + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb new file mode 100644 index 000000000..1a116ec7d --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe 'Elasticsearch::Model::Adapter::ActiveRecord Dynamic Index naming' do + + before do + ArticleWithDynamicIndexName.counter = 0 + end + + it 'exavlues the index_name value' do + expect(ArticleWithDynamicIndexName.index_name).to eq('articles-1') + end + + it 'revaluates the index name with each call' do + expect(ArticleWithDynamicIndexName.index_name).to eq('articles-1') + expect(ArticleWithDynamicIndexName.index_name).to eq('articles-2') + expect(ArticleWithDynamicIndexName.index_name).to eq('articles-3') + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb new file mode 100644 index 000000000..52301b01a --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb @@ -0,0 +1,187 @@ +require 'spec_helper' + +describe 'Elasticsearch::Model::Adapter::ActiveRecord Importing' do + + before(:all) do + ActiveRecord::Schema.define(:version => 1) do + create_table :import_articles do |t| + t.string :title + t.integer :views + t.string :numeric # For the sake of invalid data sent to Elasticsearch + t.datetime :created_at, :default => 'NOW()' + end + end + + ImportArticle.delete_all + ImportArticle.__elasticsearch__.client.cluster.health(wait_for_status: 'yellow') + end + + before do + ImportArticle.__elasticsearch__.create_index! + end + + after do + clear_indices(ImportArticle) + clear_tables(ImportArticle) + end + + describe '#import' do + + context 'when no search criteria is specified' do + + before do + 10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" } + ImportArticle.import + ImportArticle.__elasticsearch__.refresh_index! + end + + it 'imports all documents' do + expect(ImportArticle.search('*').results.total).to eq(10) + end + end + + context 'when batch size is specified' do + + before do + 10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" } + end + + let!(:batch_count) do + batches = 0 + errors = ImportArticle.import(batch_size: 5) do |response| + batches += 1 + end + ImportArticle.__elasticsearch__.refresh_index! + batches + end + + it 'imports using the batch size' do + expect(batch_count).to eq(2) + end + + it 'imports all the documents' do + expect(ImportArticle.search('*').results.total).to eq(10) + end + end + + context 'when a scope is specified' do + + before do + 10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" } + ImportArticle.import(scope: 'popular', force: true) + ImportArticle.__elasticsearch__.refresh_index! + end + + it 'applies the scope' do + expect(ImportArticle.search('*').results.total).to eq(5) + end + end + + context 'when a query is specified' do + + before do + 10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" } + ImportArticle.import(query: -> { where('views >= 3') }) + ImportArticle.__elasticsearch__.refresh_index! + end + + it 'applies the query' do + expect(ImportArticle.search('*').results.total).to eq(7) + end + end + + context 'when there are invalid documents' do + + let!(:result) do + 10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" } + new_article + batches = 0 + errors = ImportArticle.__elasticsearch__.import(batch_size: 5) do |response| + batches += 1 + end + ImportArticle.__elasticsearch__.refresh_index! + { batch_size: batches, errors: errors} + end + + let(:new_article) do + ImportArticle.create!(title: "Test INVALID", numeric: "INVALID") + end + + it 'does not import them' do + expect(ImportArticle.search('*').results.total).to eq(10) + expect(result[:batch_size]).to eq(3) + expect(result[:errors]).to eq(1) + end + end + + context 'when a transform proc is specified' do + + before do + 10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" } + ImportArticle.import( transform: ->(a) {{ index: { data: { name: a.title, foo: 'BAR' } }}} ) + ImportArticle.__elasticsearch__.refresh_index! + end + + it 'transforms the documents' do + expect(ImportArticle.search('*').results.first._source.keys).to include('name') + expect(ImportArticle.search('*').results.first._source.keys).to include('foo') + end + + it 'imports all documents' do + expect(ImportArticle.search('test').results.total).to eq(10) + expect(ImportArticle.search('bar').results.total).to eq(10) + end + end + + context 'when the model has a default scope' do + + around(:all) do |example| + 10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" } + ImportArticle.instance_eval { default_scope { where('views > 3') } } + example.run + ImportArticle.default_scopes.pop + end + + before do + ImportArticle.__elasticsearch__.import + ImportArticle.__elasticsearch__.refresh_index! + end + + it 'uses the default scope' do + expect(ImportArticle.search('*').results.total).to eq(6) + end + end + + context 'when there is a default scope and a query specified' do + + around(:all) do |example| + 10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" } + ImportArticle.instance_eval { default_scope { where('views > 3') } } + example.run + ImportArticle.default_scopes.pop + end + + before do + ImportArticle.import(query: -> { where('views <= 4') }) + ImportArticle.__elasticsearch__.refresh_index! + end + + it 'combines the query and the default scope' do + expect(ImportArticle.search('*').results.total).to eq(1) + end + end + + context 'when the batch is empty' do + + before do + ImportArticle.delete_all + ImportArticle.import + ImportArticle.__elasticsearch__.refresh_index! + end + + it 'does not make any requests to create documents' do + expect(ImportArticle.search('*').results.total).to eq(0) + end + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb new file mode 100644 index 000000000..96c65fc5c --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb @@ -0,0 +1,110 @@ +require 'spec_helper' + +describe 'Elasticsearch::Model::Adapter::ActiveRecord MultiModel' do + + before(:all) do + ActiveRecord::Schema.define do + create_table Episode.table_name do |t| + t.string :name + t.datetime :created_at, :default => 'NOW()' + end + + create_table Series.table_name do |t| + t.string :name + t.datetime :created_at, :default => 'NOW()' + end + end + end + + before do + models = [ Episode, Series ] + clear_tables(models) + models.each do |model| + model.__elasticsearch__.create_index! force: true + model.create name: "The #{model.name}" + model.create name: "A great #{model.name}" + model.create name: "The greatest #{model.name}" + model.__elasticsearch__.refresh_index! + end + end + + after do + clear_indices(Episode, Series) + clear_tables(Episode, Series) + end + + context 'when the search is across multimodels' do + + let(:search_result) do + Elasticsearch::Model.search(%q<"The greatest Episode"^2 OR "The greatest Series">, [Series, Episode]) + end + + it 'executes the search across models' do + expect(search_result.results.size).to eq(2) + expect(search_result.records.size).to eq(2) + end + + describe '#results' do + + it 'returns an instance of Elasticsearch::Model::Response::Result' do + expect(search_result.results[0]).to be_a(Elasticsearch::Model::Response::Result) + expect(search_result.results[1]).to be_a(Elasticsearch::Model::Response::Result) + end + + it 'returns the correct model instance' do + expect(search_result.results[0].name).to eq('The greatest Episode') + expect(search_result.results[1].name).to eq('The greatest Series') + end + + it 'provides access to the results' do + expect(search_result.results[0].name).to eq('The greatest Episode') + expect(search_result.results[0].name?).to be(true) + expect(search_result.results[0].boo?).to be(false) + + expect(search_result.results[1].name).to eq('The greatest Series') + expect(search_result.results[1].name?).to be(true) + expect(search_result.results[1].boo?).to be(false) + end + end + + describe '#records' do + + it 'returns an instance of Elasticsearch::Model::Response::Result' do + expect(search_result.records[0]).to be_a(Episode) + expect(search_result.records[1]).to be_a(Series) + end + + it 'returns the correct model instance' do + expect(search_result.records[0].name).to eq('The greatest Episode') + expect(search_result.records[1].name).to eq('The greatest Series') + end + + context 'when the data store is changed' do + + before do + Series.find_by_name("The greatest Series").delete + Series.__elasticsearch__.refresh_index! + end + + it 'only returns matching records' do + expect(search_result.results.size).to eq(2) + expect(search_result.records.size).to eq(1 ) + expect(search_result.records[0].name).to eq('The greatest Episode') + end + end + end + + describe 'pagination' do + + let(:search_result) do + Elasticsearch::Model.search('series OR episode', [Series, Episode]) + end + + it 'properly paginates the results' do + expect(search_result.page(1).per(3).results.size).to eq(3) + expect(search_result.page(2).per(3).results.size).to eq(3) + expect(search_result.page(3).per(3).results.size).to eq(0) + end + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb new file mode 100644 index 000000000..ea426d3f2 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'Elasticsearch::Model::Adapter::ActiveRecord Namespaced Model' do + + before(:all) do + ActiveRecord::Schema.define(:version => 1) do + create_table :books do |t| + t.string :title + end + end + + MyNamespace::Book.delete_all + MyNamespace::Book.__elasticsearch__.create_index!(force: true) + MyNamespace::Book.create!(title: 'Test') + MyNamespace::Book.__elasticsearch__.refresh_index! + end + + after do + clear_indices(MyNamespace::Book) + clear_tables(MyNamespace::Book) + end + + context 'when the model is namespaced' do + + it 'has the proper index name' do + expect(MyNamespace::Book.index_name).to eq('my_namespace-books') + end + + it 'has the proper document type' do + expect(MyNamespace::Book.document_type).to eq('book') + end + + it 'saves the document into the index' do + expect(MyNamespace::Book.search('title:test').results.size).to eq(1) + expect(MyNamespace::Book.search('title:test').results.first.title).to eq('Test') + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb new file mode 100644 index 000000000..9427fae48 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb @@ -0,0 +1,315 @@ +require 'spec_helper' + +describe 'Elasticsearch::Model::Adapter::ActiveRecord Pagination' do + + before(:all) do + ActiveRecord::Schema.define(:version => 1) do + create_table ArticleForPagination.table_name do |t| + t.string :title + t.datetime :created_at, :default => 'NOW()' + t.boolean :published + end + end + + Kaminari::Hooks.init if defined?(Kaminari::Hooks) + + ArticleForPagination.__elasticsearch__.create_index! force: true + + 68.times do |i| + ArticleForPagination.create! title: "Test #{i}", published: (i % 2 == 0) + end + + ArticleForPagination.import + ArticleForPagination.__elasticsearch__.refresh_index! + end + + context 'when no other page is specified' do + + let(:records) do + ArticleForPagination.search('title:test').page(1).records + end + + describe '#size' do + + it 'returns the correct size' do + expect(records.size).to eq(25) + end + end + + describe '#current_page' do + + it 'returns the correct current page' do + expect(records.current_page).to eq(1) + end + end + + describe '#prev_page' do + + it 'returns the correct previous page' do + expect(records.prev_page).to be_nil + end + end + + describe '#next_page' do + + it 'returns the correct next page' do + expect(records.next_page).to eq(2) + end + end + + describe '#total_pages' do + + it 'returns the correct total pages' do + expect(records.total_pages).to eq(3) + end + end + + describe '#first_page?' do + + it 'returns the correct first page' do + expect(records.first_page?).to be(true) + end + end + + describe '#last_page?' do + + it 'returns the correct last page' do + expect(records.last_page?).to be(false) + end + end + + describe '#out_of_range?' do + + it 'returns whether the pagination is out of range' do + expect(records.out_of_range?).to be(false) + end + end + end + + context 'when a specific page is specified' do + + let(:records) do + ArticleForPagination.search('title:test').page(2).records + end + + describe '#size' do + + it 'returns the correct size' do + expect(records.size).to eq(25) + end + end + + describe '#current_page' do + + it 'returns the correct current page' do + expect(records.current_page).to eq(2) + end + end + + describe '#prev_page' do + + it 'returns the correct previous page' do + expect(records.prev_page).to eq(1) + end + end + + describe '#next_page' do + + it 'returns the correct next page' do + expect(records.next_page).to eq(3) + end + end + + describe '#total_pages' do + + it 'returns the correct total pages' do + expect(records.total_pages).to eq(3) + end + end + + describe '#first_page?' do + + it 'returns the correct first page' do + expect(records.first_page?).to be(false) + end + end + + describe '#last_page?' do + + it 'returns the correct last page' do + expect(records.last_page?).to be(false) + end + end + + describe '#out_of_range?' do + + it 'returns whether the pagination is out of range' do + expect(records.out_of_range?).to be(false) + end + end + end + + context 'when a the last page is specified' do + + let(:records) do + ArticleForPagination.search('title:test').page(3).records + end + + describe '#size' do + + it 'returns the correct size' do + expect(records.size).to eq(18) + end + end + + describe '#current_page' do + + it 'returns the correct current page' do + expect(records.current_page).to eq(3) + end + end + + describe '#prev_page' do + + it 'returns the correct previous page' do + expect(records.prev_page).to eq(2) + end + end + + describe '#next_page' do + + it 'returns the correct next page' do + expect(records.next_page).to be_nil + end + end + + describe '#total_pages' do + + it 'returns the correct total pages' do + expect(records.total_pages).to eq(3) + end + end + + describe '#first_page?' do + + it 'returns the correct first page' do + expect(records.first_page?).to be(false) + end + end + + describe '#last_page?' do + + it 'returns the correct last page' do + expect(records.last_page?).to be(true) + end + end + + describe '#out_of_range?' do + + it 'returns whether the pagination is out of range' do + expect(records.out_of_range?).to be(false) + end + end + end + + context 'when an invalid page is specified' do + + let(:records) do + ArticleForPagination.search('title:test').page(6).records + end + + describe '#size' do + + it 'returns the correct size' do + expect(records.size).to eq(0) + end + end + + describe '#current_page' do + + it 'returns the correct current page' do + expect(records.current_page).to eq(6) + end + end + + describe '#next_page' do + + it 'returns the correct next page' do + expect(records.next_page).to be_nil + end + end + + describe '#total_pages' do + + it 'returns the correct total pages' do + expect(records.total_pages).to eq(3) + end + end + + describe '#first_page?' do + + it 'returns the correct first page' do + expect(records.first_page?).to be(false) + end + end + + describe '#last_page?' do + + it 'returns whether it is the last page', if: !(Kaminari::VERSION < '1') do + expect(records.last_page?).to be(false) + end + + it 'returns whether it is the last page', if: Kaminari::VERSION < '1' do + expect(records.last_page?).to be(true) # Kaminari returns current_page >= total_pages in version < 1.0 + end + end + + describe '#out_of_range?' do + + it 'returns whether the pagination is out of range' do + expect(records.out_of_range?).to be(true) + end + end + end + + context 'when a scope is also specified' do + + let(:records) do + ArticleForPagination.search('title:test').page(2).records.published + end + + describe '#size' do + + it 'returns the correct size' do + expect(records.size).to eq(12) + end + end + end + + context 'when a sorting is specified' do + + let(:search) do + ArticleForPagination.search({ query: { match: { title: 'test' } }, sort: [ { id: 'desc' } ] }) + end + + it 'applies the sort' do + expect(search.page(2).records.first.id).to eq(43) + expect(search.page(3).records.first.id).to eq(18) + expect(search.page(2).per(5).records.first.id).to eq(63) + end + end + + context 'when the model has a specific default per page set' do + + around do |example| + original_default = ArticleForPagination.instance_variable_get(:@_default_per_page) + ArticleForPagination.paginates_per 50 + example.run + ArticleForPagination.paginates_per original_default + end + + it 'uses the default per page setting' do + expect(ArticleForPagination.search('*').page(1).records.size).to eq(50) + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb new file mode 100644 index 000000000..647cb6dde --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' + +describe 'Elasticsearch::Model::Adapter::ActiveRecord Parent-Child' do + + before(:all) do + ActiveRecord::Schema.define(version: 1) do + create_table :questions do |t| + t.string :title + t.text :text + t.string :author + t.timestamps null: false + end + + create_table :answers do |t| + t.text :text + t.string :author + t.references :question + t.timestamps null: false + end + + add_index(:answers, :question_id) unless index_exists?(:answers, :question_id) + + clear_tables(Question) + ParentChildSearchable.create_index!(force: true) + + q_1 = Question.create!(title: 'First Question', author: 'John') + q_2 = Question.create!(title: 'Second Question', author: 'Jody') + + q_1.answers.create!(text: 'Lorem Ipsum', author: 'Adam') + q_1.answers.create!(text: 'Dolor Sit', author: 'Ryan') + + q_2.answers.create!(text: 'Amet Et', author: 'John') + + Question.__elasticsearch__.refresh_index! + end + end + + describe 'has_child search' do + + let(:search_result) do + Question.search(query: { has_child: { type: 'answer', query: { match: { author: 'john' } } } }) + end + + it 'finds parents by matching on child search criteria' do + expect(search_result.records.first.title).to eq('Second Question') + end + end + + describe 'hash_parent search' do + + let(:search_result) do + Answer.search(query: { has_parent: { parent_type: 'question', query: { match: { author: 'john' } } } }) + end + + it 'finds children by matching in parent criteria' do + expect(search_result.records.map(&:author)).to match(['Adam', 'Ryan']) + end + end + + context 'when a parent is deleted' do + + before do + Question.where(title: 'First Question').each(&:destroy) + Question.__elasticsearch__.refresh_index! + end + + let(:search_result) do + Answer.search(query: { has_parent: { parent_type: 'question', query: { match_all: {} } } }) + end + + it 'deletes the children' do + expect(search_result.results.total).to eq(1) + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb new file mode 100644 index 000000000..e5b2072be --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +describe 'Elasticsearch::Model::Adapter::ActiveRecord Serialization' do + + before(:all) do + ActiveRecord::Schema.define(:version => 1) do + create_table ArticleWithCustomSerialization.table_name do |t| + t.string :title + t.string :status + end + end + + ArticleWithCustomSerialization.delete_all + ArticleWithCustomSerialization.__elasticsearch__.create_index!(force: true) + end + + context 'when the model has a custom serialization defined' do + + before do + ArticleWithCustomSerialization.create!(title: 'Test', status: 'green') + ArticleWithCustomSerialization.__elasticsearch__.refresh_index! + end + + context 'when a document is indexed' do + + let(:search_result) do + ArticleWithCustomSerialization.__elasticsearch__.client.get(index: 'article_with_custom_serializations', + type: '_doc', + id: '1') + end + + it 'applies the serialization when indexing' do + expect(search_result['_source']).to eq('title' => 'Test') + end + end + + context 'when a document is updated' do + + before do + article.update_attributes(title: 'UPDATED', status: 'yellow') + ArticleWithCustomSerialization.__elasticsearch__.refresh_index! + end + + let!(:article) do + art = ArticleWithCustomSerialization.create!(title: 'Test', status: 'red') + ArticleWithCustomSerialization.__elasticsearch__.refresh_index! + art + end + + let(:search_result) do + ArticleWithCustomSerialization.__elasticsearch__.client.get(index: 'article_with_custom_serializations', + type: '_doc', + id: article.id) + end + + it 'applies the serialization when updating' do + expect(search_result['_source']).to eq('title' => 'UPDATED') + end + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb index 7d18f0b72..6e0cb7d64 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb @@ -8,7 +8,7 @@ class DummyClassForActiveRecord; end after(:all) do Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyClassForActiveRecord) - Object.send(:remove_const, :DummyClassForActiveRecord) if defined?(DummyClassForActiveRecord) + remove_classes(DummyClassForActiveRecord) end let(:model) do @@ -89,37 +89,6 @@ class DummyClassForActiveRecord; end expect(instance.records).to eq(records) end end - - context 'when an order is not defined for the ActiveRecord query' do - - context 'when the records have a different order than the hits' do - - before do - records.instance_variable_set(:@records, records) - allow(records).to receive(:order_values).and_return([]) - end - - it 'reorders the records based on hits order' do - expect(records.collect(&:id)).to eq([1, 2]) - expect(instance.records.to_a.collect(&:id)).to eq([2, 1]) - end - end - end - - context 'when an order is defined for the ActiveRecord query' do - - context 'when the records have a different order than the hits' do - - before do - records.instance_variable_set(:@records, [record_2, record_1]) - allow(records).to receive(:order_values).and_return([double('order_definition')]) - end - - it 'reorders the records based on hits order' do - expect(instance.records.to_a.collect(&:id)).to eq([2, 1]) - end - end - end end describe 'callbacks registration' do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/default_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/default_spec.rb index b0d108538..08064df0f 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/default_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/default_spec.rb @@ -10,7 +10,7 @@ class DummyClassForDefaultAdapter; end after(:all) do Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyClassForDefaultAdapter) - Object.send(:remove_const, :DummyClassForDefaultAdapter) if defined?(DummyClassForDefaultAdapter) + remove_classes(DummyClassForDefaultAdapter) end let(:instance) do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb new file mode 100644 index 000000000..f4aefc740 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb @@ -0,0 +1,267 @@ +require 'spec_helper' + +describe Elasticsearch::Model::Adapter::Mongoid, if: test_mongoid? do + + before(:all) do + connect_mongoid('mongoid_test') + Elasticsearch::Model::Adapter.register \ + Elasticsearch::Model::Adapter::Mongoid, + lambda { |klass| !!defined?(::Mongoid::Document) && klass.respond_to?(:ancestors) && klass.ancestors.include?(::Mongoid::Document) } + + MongoidArticle.__elasticsearch__.create_index! force: true + + MongoidArticle.delete_all + + MongoidArticle.__elasticsearch__.refresh_index! + MongoidArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' + end + + after do + clear_indices(MongoidArticle) + clear_tables(MongoidArticle) + end + + describe 'searching' do + + before do + MongoidArticle.create! title: 'Test' + MongoidArticle.create! title: 'Testing Coding' + MongoidArticle.create! title: 'Coding' + MongoidArticle.__elasticsearch__.refresh_index! + end + + let(:search_result) do + MongoidArticle.search('title:test') + end + + it 'find the documents successfully' do + expect(search_result.results.size).to eq(2) + expect(search_result.records.size).to eq(2) + end + + describe '#results' do + + it 'returns a Elasticsearch::Model::Response::Result' do + expect(search_result.results.first).to be_a(Elasticsearch::Model::Response::Result) + end + + it 'retrieves the document from Elasticsearch' do + expect(search_result.results.first.title).to eq('Test') + end + + it 'retrieves all results' do + expect(search_result.results.collect(&:title)).to match(['Test', 'Testing Coding']) + end + end + + describe '#records' do + + it 'returns an instance of the model' do + expect(search_result.records.first).to be_a(MongoidArticle) + end + + it 'retrieves the document from Elasticsearch' do + expect(search_result.records.first.title).to eq('Test') + end + + it 'iterates over the records' do + expect(search_result.records.first.title).to eq('Test') + end + + it 'retrieves all records' do + expect(search_result.records.collect(&:title)).to match(['Test', 'Testing Coding']) + end + + describe '#each_with_hit' do + + it 'yields each hit with the model object' do + search_result.records.each_with_hit do |r, h| + expect(h._source).not_to be_nil + expect(h._source.title).not_to be_nil + end + end + + it 'preserves the search order' do + search_result.records.each_with_hit do |r, h| + expect(r.id.to_s).to eq(h._id) + end + end + end + + describe '#map_with_hit' do + + it 'yields each hit with the model object' do + search_result.records.map_with_hit do |r, h| + expect(h._source).not_to be_nil + expect(h._source.title).not_to be_nil + end + end + + it 'preserves the search order' do + search_result.records.map_with_hit do |r, h| + expect(r.id.to_s).to eq(h._id) + end + end + end + end + end + + describe '#destroy' do + + let(:article) do + MongoidArticle.create!(title: 'Test') + end + + before do + article + MongoidArticle.create!(title: 'Coding') + article.destroy + MongoidArticle.__elasticsearch__.refresh_index! + end + + it 'removes documents from the index' do + expect(MongoidArticle.search('title:test').results.total).to eq(0) + expect(MongoidArticle.search('title:code').results.total).to eq(1) + end + end + + describe 'updates to the document' do + + let(:article) do + MongoidArticle.create!(title: 'Test') + end + + before do + article.title = 'Writing' + article.save + MongoidArticle.__elasticsearch__.refresh_index! + end + + it 'indexes updates' do + expect(MongoidArticle.search('title:write').results.total).to eq(1) + expect(MongoidArticle.search('title:test').results.total).to eq(0) + end + end + + describe 'DSL search' do + + before do + MongoidArticle.create! title: 'Test' + MongoidArticle.create! title: 'Testing Coding' + MongoidArticle.create! title: 'Coding' + MongoidArticle.__elasticsearch__.refresh_index! + end + + let(:search_result) do + MongoidArticle.search(query: { match: { title: { query: 'test' } } }) + end + + it 'finds the matching documents' do + expect(search_result.results.size).to eq(2) + expect(search_result.records.size).to eq(2) + end + end + + describe 'paging a collection' do + + before do + MongoidArticle.create! title: 'Test' + MongoidArticle.create! title: 'Testing Coding' + MongoidArticle.create! title: 'Coding' + MongoidArticle.__elasticsearch__.refresh_index! + end + + let(:search_result) do + MongoidArticle.search(query: { match: { title: { query: 'test' } } }, + size: 2, + from: 1) + end + + it 'applies the size and from parameters' do + expect(search_result.results.size).to eq(1) + expect(search_result.results.first.title).to eq('Testing Coding') + expect(search_result.records.size).to eq(1) + expect(search_result.records.first.title).to eq('Testing Coding') + end + end + + describe 'importing' do + + before do + 97.times { |i| MongoidArticle.create! title: "Test #{i}" } + MongoidArticle.__elasticsearch__.create_index! force: true + MongoidArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' + end + + context 'when there is no default scope' do + + let!(:batch_count) do + batches = 0 + errors = MongoidArticle.import(batch_size: 10) do |response| + batches += 1 + end + MongoidArticle.__elasticsearch__.refresh_index! + batches + end + + it 'imports all the documents' do + expect(MongoidArticle.search('*').results.total).to eq(97) + end + + it 'uses the specified batch size' do + expect(batch_count).to eq(10) + end + end + + context 'when there is a default scope' do + + around(:all) do |example| + 10.times { |i| MongoidArticle.create! title: 'Test', views: "#{i}" } + MongoidArticle.default_scope -> { MongoidArticle.gt(views: 3) } + example.run + MongoidArticle.default_scoping = nil + end + + before do + MongoidArticle.__elasticsearch__.import + MongoidArticle.__elasticsearch__.refresh_index! + end + + it 'uses the default scope' do + expect(MongoidArticle.search('*').results.total).to eq(6) + end + end + + context 'when there is a default scope and a query specified' do + + around(:all) do |example| + 10.times { |i| MongoidArticle.create! title: 'Test', views: "#{i}" } + MongoidArticle.default_scope -> { MongoidArticle.gt(views: 3) } + example.run + MongoidArticle.default_scoping = nil + end + + before do + MongoidArticle.import(query: -> { lte(views: 4) }) + MongoidArticle.__elasticsearch__.refresh_index! + end + + it 'combines the query and the default scope' do + expect(MongoidArticle.search('*').results.total).to eq(1) + end + end + + context 'when the batch is empty' do + + before do + MongoidArticle.delete_all + MongoidArticle.import + MongoidArticle.__elasticsearch__.refresh_index! + end + + it 'does not make any requests to create documents' do + expect(MongoidArticle.search('*').results.total).to eq(0) + end + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb new file mode 100644 index 000000000..e4308ab98 --- /dev/null +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe 'Elasticsearch::Model::Adapter::ActiveRecord Multimodel', if: test_mongoid? do + + before(:all) do + connect_mongoid('mongoid_test') + + begin + ActiveRecord::Schema.define(:version => 1) do + create_table Episode.table_name do |t| + t.string :name + t.datetime :created_at, :default => 'NOW()' + end + end + rescue + end + end + + before do + clear_tables(Episode, Image) + Episode.__elasticsearch__.create_index! force: true + Episode.create name: "TheEpisode" + Episode.create name: "A great Episode" + Episode.create name: "The greatest Episode" + Episode.__elasticsearch__.refresh_index! + + Image.__elasticsearch__.create_index! force: true + Image.create! name: "The Image" + Image.create! name: "A great Image" + Image.create! name: "The greatest Image" + Image.__elasticsearch__.refresh_index! + Image.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' + end + + after do + [Episode, Image].each do |model| + model.__elasticsearch__.client.delete_by_query(index: model.index_name, q: '*') + model.delete_all + model.__elasticsearch__.refresh_index! + end + end + + context 'when the search is across multimodels with different adapters' do + + let(:search_result) do + Elasticsearch::Model.search(%q<"greatest Episode" OR "greatest Image"^2>, [Episode, Image]) + end + + it 'executes the search across models' do + expect(search_result.results.size).to eq(2) + expect(search_result.records.size).to eq(2) + end + + it 'returns the correct type of model instance' do + expect(search_result.records[0]).to be_a(Image) + expect(search_result.records[1]).to be_a(Episode) + end + + it 'creates the model instances with the correct attributes' do + expect(search_result.results[0].name).to eq('The greatest Image') + expect(search_result.records[0].name).to eq('The greatest Image') + expect(search_result.results[1].name).to eq('The greatest Episode') + expect(search_result.records[1].name).to eq('The greatest Episode') + end + end +end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb index cb3459341..3a01a6635 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb @@ -9,7 +9,7 @@ class DummyClassForMongoid; end after(:all) do Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyClassForMongoid) - Object.send(:remove_const, :DummyClassForMongoid) if defined?(DummyClassForMongoid) + remove_classes(DummyClassForMongoid) end let(:response) do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb index 83a8e6f68..947df1717 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb @@ -58,12 +58,11 @@ def initialize(id) end after(:all) do - Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyOne) - Elasticsearch::Model::Adapter::Adapter.adapters.delete(Namespace::DummyTwo) - Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyTwo) - Object.send(:remove_const, :DummyOne) if defined?(DummyOne) - Object.send(:remove_const, :Namespace) if defined?(Namespace::DummyTwo) - Object.send(:remove_const, :DummyTwo) if defined?(DummyTwo) + [DummyOne, Namespace::DummyTwo, DummyTwo].each do |adapter| + Elasticsearch::Model::Adapter::Adapter.adapters.delete(adapter) + end + Namespace.send(:remove_const, :DummyTwo) if defined?(Namespace::DummyTwo) + remove_classes(DummyOne, DummyTwo, Namespace) end let(:hits) do diff --git a/elasticsearch-model/spec/elasticsearch/model/callbacks_spec.rb b/elasticsearch-model/spec/elasticsearch/model/callbacks_spec.rb index 0bbb494e4..d10ce656c 100644 --- a/elasticsearch-model/spec/elasticsearch/model/callbacks_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/callbacks_spec.rb @@ -17,8 +17,7 @@ def callbacks_mixin end after(:all) do - Object.send(:remove_const, :DummyCallbacksModel) if defined?(DummyCallbacksModel) - Object.send(:remove_const, :DummyCallbacksAdapter) if defined?(DummyCallbacksAdapter) + remove_classes(DummyCallbacksModel, DummyCallbacksAdapter) end context 'when a model includes the Callbacks module' do diff --git a/elasticsearch-model/spec/elasticsearch/model/client_spec.rb b/elasticsearch-model/spec/elasticsearch/model/client_spec.rb index 2aee4f7b2..ea273af73 100644 --- a/elasticsearch-model/spec/elasticsearch/model/client_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/client_spec.rb @@ -10,7 +10,7 @@ class ::DummyClientModel end after(:all) do - Object.send(:remove_const, :DummyClientModel) if defined?(DummyClientModel) + remove_classes(DummyClientModel) end context 'when a class includes the client module class methods' do diff --git a/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb index 0b629ff22..c46f00ba6 100644 --- a/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb @@ -23,8 +23,7 @@ def importing_mixin end after(:all) do - Object.send(:remove_const, :DummyImportingModel) if defined?(DummyImportingModel) - Object.send(:remove_const, :DummyImportingAdapter) if defined?(DummyImportingAdapter) + remove_classes(DummyImportingModel, DummyImportingAdapter) end before do diff --git a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb index 5daadcf15..beaf0dafd 100644 --- a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb @@ -17,8 +17,7 @@ class NotFound < Exception; end end after(:all) do - Object.send(:remove_const, :DummyIndexingModel) if defined?(DummyIndexingModel) - Object.send(:remove_const, :NotFound) if defined?(NotFound) + remove_classes(DummyIndexingModel, NotFound) end describe 'the Settings class' do diff --git a/elasticsearch-model/spec/elasticsearch/model/module_spec.rb b/elasticsearch-model/spec/elasticsearch/model/module_spec.rb index c3a394534..3162e2ec5 100644 --- a/elasticsearch-model/spec/elasticsearch/model/module_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/module_spec.rb @@ -29,15 +29,12 @@ def self.search(query, options={}) "SEARCH" end end - end - after(:all) do - Object.send(:remove_const, :DummyIncludingModel) if defined?(DummyIncludingModel) - Object.send(:remove_const, :DummyIncludingModelWithSearchMethodDefined) if defined?(DummyIncludingModelWithSearchMethodDefined) + DummyIncludingModel.__send__ :include, Elasticsearch::Model end - before do - DummyIncludingModel.__send__ :include, Elasticsearch::Model + after(:all) do + remove_classes(DummyIncludingModel, DummyIncludingModelWithSearchMethodDefined) end it 'should include and set up the proxy' do diff --git a/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb b/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb index 6fa274ccc..44ee0bd84 100644 --- a/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb @@ -40,8 +40,7 @@ class JustAnotherModel end after(:all) do - Object.send(:remove_const, :JustAModel) if defined?(JustAModel) - Object.send(:remove_const, :JustAnotherModel) if defined?(JustAnotherModel) + remove_classes(JustAModel, JustAnotherModel) end let(:multimodel) do diff --git a/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb b/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb index daf7fb47d..287fc7d42 100644 --- a/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb @@ -3,8 +3,6 @@ describe 'naming inheritance' do before(:all) do - Elasticsearch::Model.settings[:inheritance_enabled] = true - class ::TestBase extend ActiveModel::Naming @@ -43,13 +41,17 @@ class ::Cat < ::Animal end after(:all) do - Elasticsearch::Model.settings[:inheritance_enabled] = false - Object.send(:remove_const, :TestBase) if defined?(TestBase) - Object.send(:remove_const, :Animal) if defined?(Animal) - Object.send(:remove_const, :MyNamespace) if defined?(MyNamespace) - Object.send(:remove_const, :Cat) if defined?(Cat) + remove_classes(TestBase, Animal, MyNamespace, Cat) + end + + around(:all) do |example| + original_value = Elasticsearch::Model.settings[:inheritance_enabled] + Elasticsearch::Model.settings[:inheritance_enabled] = true + example.run + Elasticsearch::Model.settings[:inheritance_enabled] = original_value end + describe '#index_name' do it 'returns the default index name' do diff --git a/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb b/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb index 32b9905d1..8e0d8d54d 100644 --- a/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb @@ -21,8 +21,7 @@ class DummyNamingModelInNamespace end after(:all) do - Object.send(:remove_const, :DummyNamingModel) if defined?(DummyNamingModel) - Object.send(:remove_const, :MyNamespace) if defined?(MyNamespace) + remove_classes(DummyNamingModel, MyNamespace) end it 'returns the default index name' do diff --git a/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb b/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb index ea5553af0..6e484f896 100644 --- a/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb @@ -28,15 +28,12 @@ def changes_to_save {:foo => ['One', 'Two']} end end - end - after(:all) do - Object.send(:remove_const, :DummyProxyModel) if defined?(DummyProxyModel) - Object.send(:remove_const, :DummyProxyModelWithCallbacks) if defined?(DummyProxyModelWithCallbacks) + DummyProxyModelWithCallbacks.__send__ :include, Elasticsearch::Model::Proxy end - before do - DummyProxyModelWithCallbacks.__send__ :include, Elasticsearch::Model::Proxy + after(:all) do + remove_classes(DummyProxyModel, DummyProxyModelWithCallbacks) end it 'sets up a proxy method on the class' do diff --git a/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb index 9d20730da..2d1f8f509 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb @@ -10,7 +10,7 @@ def self.document_type; 'bar'; end end after(:all) do - Object.send(:remove_const, :OriginClass) if defined?(OriginClass) + remove_classes(OriginClass) end let(:response_document) do diff --git a/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb index eae7a8a67..cfd77d7c7 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb @@ -14,8 +14,7 @@ def self.document_type; 'bar'; end end after(:all) do - Object.send(:remove_const, :DummyBaseClass) if defined?(DummyBaseClass) - Object.send(:remove_const, :OriginClass) if defined?(OriginClass) + remove_classes(DummyBaseClass, OriginClass) end let(:response_document) do diff --git a/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb index d288e2dc8..e8fe1f86b 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Elasticsearch::Model::Response::Response do +describe 'Elasticsearch::Model::Response::Response Kaminari' do before(:all) do class ModelClass @@ -11,7 +11,7 @@ def self.document_type; 'bar'; end end after(:all) do - Object.send(:remove_const, :ModelClass) if defined?(ModelClass) + remove_classes(ModelClass) end let(:response_document) do diff --git a/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb index 7cd4fdfd9..0666b0f28 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Elasticsearch::Model::Response::Response do +describe 'Elasticsearch::Model::Response::Response WillPaginate' do before(:all) do class ModelClass @@ -19,8 +19,7 @@ class WillPaginateResponse < Elasticsearch::Model::Response::Response end after(:all) do - Object.send(:remove_const, :ModelClass) if defined?(ModelClass) - Object.send(:remove_const, :WillPaginateResponse) if defined?(WillPaginateResponse) + remove_classes(ModelClass, WillPaginateResponse) end let(:response_document) do diff --git a/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb index 8687a92b4..882c763f3 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb @@ -23,8 +23,7 @@ def self.find(*args) end after(:all) do - Object.send(:remove_const, :DummyCollection) if defined?(DummyCollection) - Object.send(:remove_const, :DummyModel) if defined?(DummyModel) + remove_classes(DummyCollection, DummyModel) end let(:response_document) do diff --git a/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb index 2bb96fb8a..32f96a4d2 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb @@ -10,7 +10,7 @@ def self.document_type; 'bar'; end end after(:all) do - Object.send(:remove_const, :OriginClass) if defined?(OriginClass) + remove_classes(OriginClass) end let(:response_document) do diff --git a/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb index 5168022b3..f7149003d 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb @@ -10,7 +10,7 @@ def self.document_type; 'bar'; end end after(:all) do - Object.send(:remove_const, :OriginClass) if defined?(OriginClass) + remove_classes(OriginClass) end let(:response_document) do diff --git a/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb b/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb index f93dbce01..db3ac6dab 100644 --- a/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb @@ -5,15 +5,13 @@ before(:all) do class ::DummySearchingModel extend Elasticsearch::Model::Searching::ClassMethods - def self.index_name; 'foo'; end def self.document_type; 'bar'; end - end end after(:all) do - Object.send(:remove_const, :DummySearchingModel) if defined?(DummySearchingModel) + remove_classes(DummySearchingModel) end before do diff --git a/elasticsearch-model/spec/elasticsearch/model/searching_spec.rb b/elasticsearch-model/spec/elasticsearch/model/searching_spec.rb index 66ee516a9..ca95f282b 100644 --- a/elasticsearch-model/spec/elasticsearch/model/searching_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/searching_spec.rb @@ -12,7 +12,7 @@ def self.document_type; 'bar'; end end after(:all) do - Object.send(:remove_const, :DummySearchingModel) if defined?(DummySearchingModel) + remove_classes(DummySearchingModel) end it 'has the search method' do diff --git a/elasticsearch-model/spec/elasticsearch/model/serializing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/serializing_spec.rb index 6b440e02e..ac4a850f3 100644 --- a/elasticsearch-model/spec/elasticsearch/model/serializing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/serializing_spec.rb @@ -13,7 +13,7 @@ def as_json(options={}) end after(:all) do - Object.send(:remove_const, :DummyClass) if defined?(DummyClass) + remove_classes(DummyClass) end it 'delegates to #as_json by default' do diff --git a/elasticsearch-model/spec/spec_helper.rb b/elasticsearch-model/spec/spec_helper.rb index 9c692211c..178f9c9fe 100644 --- a/elasticsearch-model/spec/spec_helper.rb +++ b/elasticsearch-model/spec/spec_helper.rb @@ -1,13 +1,157 @@ require 'pry-nav' require 'kaminari' +require 'kaminari/version' require 'will_paginate' require 'will_paginate/collection' require 'elasticsearch/model' require 'hashie/version' require 'active_model' +require 'mongoid' require 'yaml' +require 'active_record' RSpec.configure do |config| config.formatter = 'documentation' config.color = true + + config.before(:suite) do + require 'ansi' + tracer = ::Logger.new(STDERR) + tracer.formatter = lambda { |s, d, p, m| "#{m.gsub(/^.*$/) { |n| ' ' + n }.ansi(:faint)}\n" } + Elasticsearch::Model.client = Elasticsearch::Client.new host: "localhost:#{(ENV['TEST_CLUSTER_PORT'] || 9250)}", + tracer: (ENV['QUIET'] ? nil : tracer) + + unless ActiveRecord::Base.connected? + ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) + end + require 'support/app' + + if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' + ::ActiveRecord::Base.raise_in_transactional_callbacks = true + end + end + + config.after(:all) do + drop_all_tables! + delete_all_indices! + end +end + +# Is the ActiveRecord version at least 4.0? +# +# @return [ true, false ] Whether the ActiveRecord version is at least 4.0. +# +# @since 6.0.1 +def active_record_at_least_4? + defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4 +end + +# Delete all documents from the indices of the provided list of models. +# +# @param [ Array<ActiveRecord::Base> ] models The list of models. +# +# @return [ true ] +# +# @since 6.0.1 +def clear_indices(*models) + models.each do |model| + begin; Elasticsearch::Model.client.delete_by_query(index: model.index_name, q: '*'); rescue; end + end and true +end + +# Delete all documents from the tables of the provided list of models. +# +# @param [ Array<ActiveRecord::Base> ] models The list of models. +# +# @return [ true ] +# +# @since 6.0.1 +def clear_tables(*models) + begin; models.map(&:delete_all); rescue; end and true +end + +# Drop all tables of models registered as subclasses of ActiveRecord::Base. +# +# @return [ true ] +# +# @since 6.0.1 +def drop_all_tables! + ActiveRecord::Base.descendants.each do |model| + begin + ActiveRecord::Schema.define do + drop_table model + end if model.table_exists? + rescue + end + end and true +end + +# Drop all indices of models registered as subclasses of ActiveRecord::Base. +# +# @return [ true ] +# +# @since 6.0.1 +def delete_all_indices! + client = Elasticsearch::Model.client + ActiveRecord::Base.descendants.each do |model| + begin + client.indices.delete(index: model.index_name) if model.__elasticsearch__.index_exists? + rescue + end + end and true +end + +# Remove all classes. +# +# @param [ Array<Class> ] classes The list of classes to remove. +# +# @return [ true ] +# +# @since 6.0.1 +def remove_classes(*classes) + classes.each do |_class| + Object.send(:remove_const, _class.name.to_sym) if defined?(_class) + end and true +end + +# Determine whether the tests with Mongoid should be run. +# Depends on whether MongoDB is running on the default host and port, `localhost:27017`. +# +# @return [ true, false ] +# +# @since 6.0.1 +def test_mongoid? + $mongoid_available ||= begin + require 'mongoid' + if defined?(Mongo) # older versions of Mongoid use the driver, Moped + client = Mongo::Client.new(['localhost:27017']) + Timeout.timeout(1) do + client.database.command(ping: 1) && true + end + end and true + rescue Timeout::Error, LoadError, Mongo::Error => e + client.close + $stderr.puts("MongoDB not installed or running: #{e}") + end +end + +# Connect Mongoid and set up its Logger if Mongoid tests should be run. +# +# @since 6.0.1 +def connect_mongoid(source) + if test_mongoid? + $stderr.puts "Mongoid #{Mongoid::VERSION}", '-'*80 + + if !ENV['QUIET'] == 'true' + logger = ::Logger.new($stderr) + logger.formatter = lambda { |s, d, p, m| " #{m.ansi(:faint, :cyan)}\n" } + logger.level = ::Logger::DEBUG + Mongoid.logger = logger + Mongo::Logger.logger = logger + else + Mongo::Logger.logger.level = ::Logger::WARN + end + + Mongoid.connect_to(source) + end end diff --git a/elasticsearch-model/spec/support/app.rb b/elasticsearch-model/spec/support/app.rb new file mode 100644 index 000000000..c06ca9ca5 --- /dev/null +++ b/elasticsearch-model/spec/support/app.rb @@ -0,0 +1,21 @@ +require 'active_record' + +require 'support/app/question' +require 'support/app/answer' +require 'support/app/parent_and_child_searchable' +require 'support/app/article_with_custom_serialization' +require 'support/app/import_article' +require 'support/app/namespaced_book' +require 'support/app/article_for_pagination' +require 'support/app/article_with_dynamic_index_name' +require 'support/app/episode' +require 'support/app/image' +require 'support/app/series' +require 'support/app/mongoid_article' +require 'support/app/article' +require 'support/app/searchable' +require 'support/app/category' +require 'support/app/author' +require 'support/app/authorship' +require 'support/app/comment' +require 'support/app/post' diff --git a/elasticsearch-model/spec/support/app/answer.rb b/elasticsearch-model/spec/support/app/answer.rb new file mode 100644 index 000000000..7de32dc7b --- /dev/null +++ b/elasticsearch-model/spec/support/app/answer.rb @@ -0,0 +1,33 @@ +class Answer < ActiveRecord::Base + include Elasticsearch::Model + + belongs_to :question + + JOIN_TYPE = 'answer'.freeze + + index_name 'questions_and_answers'.freeze + document_type 'doc'.freeze + + before_create :randomize_id + + def randomize_id + begin + self.id = SecureRandom.random_number(1_000_000) + end while Answer.where(id: self.id).exists? + end + + mapping do + indexes :text + indexes :author + end + + def as_indexed_json(options={}) + # This line is necessary for differences between ActiveModel::Serializers::JSON#as_json versions + json = as_json(options)[JOIN_TYPE] || as_json(options) + json.merge(join_field: { name: JOIN_TYPE, parent: question_id }) + end + + after_commit lambda { __elasticsearch__.index_document(routing: (question_id || 1)) }, on: :create + after_commit lambda { __elasticsearch__.update_document(routing: (question_id || 1)) }, on: :update + after_commit lambda {__elasticsearch__.delete_document(routing: (question_id || 1)) }, on: :destroy +end \ No newline at end of file diff --git a/elasticsearch-model/spec/support/app/article.rb b/elasticsearch-model/spec/support/app/article.rb new file mode 100644 index 000000000..ddff706ef --- /dev/null +++ b/elasticsearch-model/spec/support/app/article.rb @@ -0,0 +1,22 @@ +class ::Article < ActiveRecord::Base + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + document_type 'article' + + settings index: {number_of_shards: 1, number_of_replicas: 0} do + mapping do + indexes :title, type: 'text', analyzer: 'snowball' + indexes :body, type: 'text' + indexes :clicks, type: 'integer' + indexes :created_at, type: 'date' + end + end + + def as_indexed_json(options = {}) + attributes + .symbolize_keys + .slice(:title, :body, :clicks, :created_at) + .merge(suggest_title: title) + end +end diff --git a/elasticsearch-model/spec/support/app/article_for_pagination.rb b/elasticsearch-model/spec/support/app/article_for_pagination.rb new file mode 100644 index 000000000..8bea633c1 --- /dev/null +++ b/elasticsearch-model/spec/support/app/article_for_pagination.rb @@ -0,0 +1,12 @@ +class ::ArticleForPagination < ActiveRecord::Base + include Elasticsearch::Model + + scope :published, -> { where(published: true) } + + settings index: { number_of_shards: 1, number_of_replicas: 0 } do + mapping do + indexes :title, type: 'text', analyzer: 'snowball' + indexes :created_at, type: 'date' + end + end +end diff --git a/elasticsearch-model/spec/support/app/article_with_custom_serialization.rb b/elasticsearch-model/spec/support/app/article_with_custom_serialization.rb new file mode 100644 index 000000000..c03b19ea5 --- /dev/null +++ b/elasticsearch-model/spec/support/app/article_with_custom_serialization.rb @@ -0,0 +1,13 @@ +class ::ArticleWithCustomSerialization < ActiveRecord::Base + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + mapping do + indexes :title + end + + def as_indexed_json(options={}) + # as_json(options.merge root: false).slice('title') + { title: self.title } + end +end diff --git a/elasticsearch-model/spec/support/app/article_with_dynamic_index_name.rb b/elasticsearch-model/spec/support/app/article_with_dynamic_index_name.rb new file mode 100644 index 000000000..7c53d04bf --- /dev/null +++ b/elasticsearch-model/spec/support/app/article_with_dynamic_index_name.rb @@ -0,0 +1,15 @@ +class ::ArticleWithDynamicIndexName < ActiveRecord::Base + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + def self.counter=(value) + @counter = 0 + end + + def self.counter + (@counter ||= 0) && @counter += 1 + end + + mapping { indexes :title } + index_name { "articles-#{counter}" } +end diff --git a/elasticsearch-model/spec/support/app/author.rb b/elasticsearch-model/spec/support/app/author.rb new file mode 100644 index 000000000..ff1664af7 --- /dev/null +++ b/elasticsearch-model/spec/support/app/author.rb @@ -0,0 +1,9 @@ +class Author < ActiveRecord::Base + has_many :authorships + + after_update { self.authorships.each(&:touch) } + + def full_name + [first_name, last_name].compact.join(' ') + end +end diff --git a/elasticsearch-model/spec/support/app/authorship.rb b/elasticsearch-model/spec/support/app/authorship.rb new file mode 100644 index 000000000..70bc2458f --- /dev/null +++ b/elasticsearch-model/spec/support/app/authorship.rb @@ -0,0 +1,4 @@ +class Authorship < ActiveRecord::Base + belongs_to :author + belongs_to :post, touch: true +end diff --git a/elasticsearch-model/spec/support/app/category.rb b/elasticsearch-model/spec/support/app/category.rb new file mode 100644 index 000000000..751413c0d --- /dev/null +++ b/elasticsearch-model/spec/support/app/category.rb @@ -0,0 +1,3 @@ +class Category < ActiveRecord::Base + has_and_belongs_to_many :posts +end diff --git a/elasticsearch-model/spec/support/app/comment.rb b/elasticsearch-model/spec/support/app/comment.rb new file mode 100644 index 000000000..49a25832c --- /dev/null +++ b/elasticsearch-model/spec/support/app/comment.rb @@ -0,0 +1,3 @@ +class Comment < ActiveRecord::Base + belongs_to :post, touch: true +end diff --git a/elasticsearch-model/spec/support/app/episode.rb b/elasticsearch-model/spec/support/app/episode.rb new file mode 100644 index 000000000..6cd159c26 --- /dev/null +++ b/elasticsearch-model/spec/support/app/episode.rb @@ -0,0 +1,11 @@ +class Episode < ActiveRecord::Base + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + settings index: {number_of_shards: 1, number_of_replicas: 0} do + mapping do + indexes :name, type: 'text', analyzer: 'snowball' + indexes :created_at, type: 'date' + end + end +end diff --git a/elasticsearch-model/spec/support/app/image.rb b/elasticsearch-model/spec/support/app/image.rb new file mode 100644 index 000000000..8bddcd08b --- /dev/null +++ b/elasticsearch-model/spec/support/app/image.rb @@ -0,0 +1,19 @@ +class Image + include Mongoid::Document + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + field :name, type: String + attr_accessible :name if respond_to? :attr_accessible + + settings index: {number_of_shards: 1, number_of_replicas: 0} do + mapping do + indexes :name, type: 'text', analyzer: 'snowball' + indexes :created_at, type: 'date' + end + end + + def as_indexed_json(options={}) + as_json(except: [:_id]) + end +end diff --git a/elasticsearch-model/spec/support/app/import_article.rb b/elasticsearch-model/spec/support/app/import_article.rb new file mode 100644 index 000000000..d25580786 --- /dev/null +++ b/elasticsearch-model/spec/support/app/import_article.rb @@ -0,0 +1,12 @@ +class ImportArticle < ActiveRecord::Base + include Elasticsearch::Model + + scope :popular, -> { where('views >= 5') } + + mapping do + indexes :title, type: 'text' + indexes :views, type: 'integer' + indexes :numeric, type: 'integer' + indexes :created_at, type: 'date' + end +end diff --git a/elasticsearch-model/spec/support/app/mongoid_article.rb b/elasticsearch-model/spec/support/app/mongoid_article.rb new file mode 100644 index 000000000..cf3a67a84 --- /dev/null +++ b/elasticsearch-model/spec/support/app/mongoid_article.rb @@ -0,0 +1,21 @@ +class ::MongoidArticle + include Mongoid::Document + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + field :id, type: String + field :title, type: String + field :views, type: Integer + attr_accessible :title if respond_to? :attr_accessible + + settings index: { number_of_shards: 1, number_of_replicas: 0 } do + mapping do + indexes :title, type: 'text', analyzer: 'snowball' + indexes :created_at, type: 'date' + end + end + + def as_indexed_json(options={}) + as_json(except: [:id, :_id]) + end +end diff --git a/elasticsearch-model/spec/support/app/namespaced_book.rb b/elasticsearch-model/spec/support/app/namespaced_book.rb new file mode 100644 index 000000000..07a500928 --- /dev/null +++ b/elasticsearch-model/spec/support/app/namespaced_book.rb @@ -0,0 +1,10 @@ +module MyNamespace + class Book < ActiveRecord::Base + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + document_type 'book' + + mapping { indexes :title } + end +end diff --git a/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb b/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb new file mode 100644 index 000000000..fd2f4417a --- /dev/null +++ b/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb @@ -0,0 +1,24 @@ +module ParentChildSearchable + INDEX_NAME = 'questions_and_answers'.freeze + JOIN = 'join'.freeze + + def create_index!(options={}) + client = Question.__elasticsearch__.client + client.indices.delete index: INDEX_NAME rescue nil if options[:force] + + settings = Question.settings.to_hash.merge Answer.settings.to_hash + mapping_properties = { join_field: { type: JOIN, + relations: { Question::JOIN_TYPE => Answer::JOIN_TYPE } } } + + merged_properties = mapping_properties.merge(Question.mappings.to_hash[:doc][:properties]).merge( + Answer.mappings.to_hash[:doc][:properties]) + mappings = { doc: { properties: merged_properties }} + + client.indices.create index: INDEX_NAME, + body: { + settings: settings.to_hash, + mappings: mappings } + end + + extend self +end diff --git a/elasticsearch-model/spec/support/app/post.rb b/elasticsearch-model/spec/support/app/post.rb new file mode 100644 index 000000000..0cdbba7bb --- /dev/null +++ b/elasticsearch-model/spec/support/app/post.rb @@ -0,0 +1,14 @@ +class Post < ActiveRecord::Base + include Searchable + + has_and_belongs_to_many :categories, after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ], + after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ] + has_many :authorships + has_many :authors, through: :authorships, + after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ], + after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ] + has_many :comments, after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ], + after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ] + + after_touch() { __elasticsearch__.index_document } +end diff --git a/elasticsearch-model/spec/support/app/question.rb b/elasticsearch-model/spec/support/app/question.rb new file mode 100644 index 000000000..f64a97a92 --- /dev/null +++ b/elasticsearch-model/spec/support/app/question.rb @@ -0,0 +1,27 @@ +class Question < ActiveRecord::Base + include Elasticsearch::Model + + has_many :answers, dependent: :destroy + + JOIN_TYPE = 'question'.freeze + JOIN_METADATA = { join_field: JOIN_TYPE}.freeze + + index_name 'questions_and_answers'.freeze + document_type 'doc'.freeze + + mapping do + indexes :title + indexes :text + indexes :author + end + + def as_indexed_json(options={}) + # This line is necessary for differences between ActiveModel::Serializers::JSON#as_json versions + json = as_json(options)[JOIN_TYPE] || as_json(options) + json.merge(JOIN_METADATA) + end + + after_commit lambda { __elasticsearch__.index_document }, on: :create + after_commit lambda { __elasticsearch__.update_document }, on: :update + after_commit lambda { __elasticsearch__.delete_document }, on: :destroy +end diff --git a/elasticsearch-model/spec/support/app/searchable.rb b/elasticsearch-model/spec/support/app/searchable.rb new file mode 100644 index 000000000..826a64875 --- /dev/null +++ b/elasticsearch-model/spec/support/app/searchable.rb @@ -0,0 +1,48 @@ +module Searchable + extend ActiveSupport::Concern + + included do + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + # Set up the mapping + # + settings index: { number_of_shards: 1, number_of_replicas: 0 } do + mapping do + indexes :title, analyzer: 'snowball' + indexes :created_at, type: 'date' + + indexes :authors do + indexes :first_name + indexes :last_name + indexes :full_name, type: 'text' do + indexes :raw, type: 'keyword' + end + end + + indexes :categories, type: 'keyword' + + indexes :comments, type: 'nested' do + indexes :text + indexes :author + end + end + end + + # Customize the JSON serialization for Elasticsearch + # + def as_indexed_json(options={}) + { + title: title, + text: text, + categories: categories.map(&:title), + authors: authors.as_json(methods: [:full_name], only: [:full_name, :first_name, :last_name]), + comments: comments.as_json(only: [:text, :author]) + } + end + + # Update document in the index after touch + # + after_touch() { __elasticsearch__.index_document } + end +end diff --git a/elasticsearch-model/spec/support/app/series.rb b/elasticsearch-model/spec/support/app/series.rb new file mode 100644 index 000000000..d10a04748 --- /dev/null +++ b/elasticsearch-model/spec/support/app/series.rb @@ -0,0 +1,11 @@ +class Series < ActiveRecord::Base + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + settings index: {number_of_shards: 1, number_of_replicas: 0} do + mapping do + indexes :name, type: 'text', analyzer: 'snowball' + indexes :created_at, type: 'date' + end + end +end diff --git a/elasticsearch-model/test/integration/active_record_associations_parent_child_test.rb b/elasticsearch-model/test/integration/active_record_associations_parent_child_test.rb deleted file mode 100644 index fb3b07e1d..000000000 --- a/elasticsearch-model/test/integration/active_record_associations_parent_child_test.rb +++ /dev/null @@ -1,188 +0,0 @@ -require 'test_helper' -require 'active_record' - -# Needed for ActiveRecord 3.x ? -ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? - -::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' - -class Question < ActiveRecord::Base - include Elasticsearch::Model - - has_many :answers, dependent: :destroy - - JOIN_TYPE = 'question'.freeze - JOIN_METADATA = { join_field: JOIN_TYPE}.freeze - - index_name 'questions_and_answers'.freeze - document_type 'doc'.freeze - - mapping do - indexes :title - indexes :text - indexes :author - end - - def as_indexed_json(options={}) - # This line is necessary for differences between ActiveModel::Serializers::JSON#as_json versions - json = as_json(options)[JOIN_TYPE] || as_json(options) - json.merge(JOIN_METADATA) - end - - after_commit lambda { __elasticsearch__.index_document }, on: :create - after_commit lambda { __elasticsearch__.update_document }, on: :update - after_commit lambda { __elasticsearch__.delete_document }, on: :destroy -end - -class Answer < ActiveRecord::Base - include Elasticsearch::Model - - belongs_to :question - - JOIN_TYPE = 'answer'.freeze - - index_name 'questions_and_answers'.freeze - document_type 'doc'.freeze - - before_create :randomize_id - - def randomize_id - begin - self.id = SecureRandom.random_number(1_000_000) - end while Answer.where(id: self.id).exists? - end - - mapping do - indexes :text - indexes :author - end - - def as_indexed_json(options={}) - # This line is necessary for differences between ActiveModel::Serializers::JSON#as_json versions - json = as_json(options)[JOIN_TYPE] || as_json(options) - json.merge(join_field: { name: JOIN_TYPE, parent: question_id }) - end - - after_commit lambda { __elasticsearch__.index_document(routing: (question_id || 1)) }, on: :create - after_commit lambda { __elasticsearch__.update_document(routing: (question_id || 1)) }, on: :update - after_commit lambda {__elasticsearch__.delete_document(routing: (question_id || 1)) }, on: :destroy -end - -module ParentChildSearchable - INDEX_NAME = 'questions_and_answers'.freeze - JOIN = 'join'.freeze - - def create_index!(options={}) - client = Question.__elasticsearch__.client - client.indices.delete index: INDEX_NAME rescue nil if options[:force] - - settings = Question.settings.to_hash.merge Answer.settings.to_hash - mapping_properties = { join_field: { type: JOIN, - relations: { Question::JOIN_TYPE => Answer::JOIN_TYPE } } } - - merged_properties = mapping_properties.merge(Question.mappings.to_hash[:doc][:properties]).merge( - Answer.mappings.to_hash[:doc][:properties]) - mappings = { doc: { properties: merged_properties }} - - client.indices.create index: INDEX_NAME, - body: { - settings: settings.to_hash, - mappings: mappings } - end - - extend self -end - -module Elasticsearch - module Model - class ActiveRecordAssociationsParentChildIntegrationTest < Elasticsearch::Test::IntegrationTestCase - - context "ActiveRecord associations with parent/child modelling" do - setup do - ActiveRecord::Schema.define(version: 1) do - create_table :questions do |t| - t.string :title - t.text :text - t.string :author - t.timestamps null: false - end - - create_table :answers do |t| - t.text :text - t.string :author - t.references :question - t.timestamps null: false - end - - add_index(:answers, :question_id) unless index_exists?(:answers, :question_id) - end - - Question.delete_all - ParentChildSearchable.create_index! force: true - - q_1 = Question.create! title: 'First Question', author: 'John' - q_2 = Question.create! title: 'Second Question', author: 'Jody' - - q_1.answers.create! text: 'Lorem Ipsum', author: 'Adam' - q_1.answers.create! text: 'Dolor Sit', author: 'Ryan' - - q_2.answers.create! text: 'Amet Et', author: 'John' - - Question.__elasticsearch__.refresh_index! - end - - should "find questions by matching answers" do - response = Question.search( - { query: { - has_child: { - type: 'answer', - query: { - match: { - author: 'john' - } - } - } - } - }) - - assert_equal 'Second Question', response.records.first.title - end - - should "find answers for matching questions" do - response = Answer.search( - { query: { - has_parent: { - parent_type: 'question', - query: { - match: { - author: 'john' - } - } - } - } - }) - - assert_same_elements ['Adam', 'Ryan'], response.records.map(&:author) - end - - should "delete answers when the question is deleted" do - Question.where(title: 'First Question').each(&:destroy) - Question.__elasticsearch__.refresh_index! - - response = Answer.search( - { query: { - has_parent: { - parent_type: 'question', - query: { - match_all: {} - } - } - } - }) - - assert_equal 1, response.results.total - end - end - end - end -end diff --git a/elasticsearch-model/test/integration/active_record_associations_test.rb b/elasticsearch-model/test/integration/active_record_associations_test.rb deleted file mode 100644 index 87a1301d8..000000000 --- a/elasticsearch-model/test/integration/active_record_associations_test.rb +++ /dev/null @@ -1,339 +0,0 @@ -require 'test_helper' -require 'active_record' - -# Needed for ActiveRecord 3.x ? -ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? - -::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' - -module Elasticsearch - module Model - class ActiveRecordAssociationsIntegrationTest < Elasticsearch::Test::IntegrationTestCase - - # ----- Search integration via Concern module ----------------------------------------------------- - - module Searchable - extend ActiveSupport::Concern - - included do - include Elasticsearch::Model - include Elasticsearch::Model::Callbacks - - # Set up the mapping - # - settings index: { number_of_shards: 1, number_of_replicas: 0 } do - mapping do - indexes :title, analyzer: 'snowball' - indexes :created_at, type: 'date' - - indexes :authors do - indexes :first_name - indexes :last_name - indexes :full_name, type: 'text' do - indexes :raw, type: 'keyword' - end - end - - indexes :categories, type: 'keyword' - - indexes :comments, type: 'nested' do - indexes :text - indexes :author - end - end - end - - # Customize the JSON serialization for Elasticsearch - # - def as_indexed_json(options={}) - { - title: title, - text: text, - categories: categories.map(&:title), - authors: authors.as_json(methods: [:full_name], only: [:full_name, :first_name, :last_name]), - comments: comments.as_json(only: [:text, :author]) - } - end - - # Update document in the index after touch - # - after_touch() { __elasticsearch__.index_document } - end - end - - context "ActiveRecord associations" do - setup do - - # ----- Schema definition --------------------------------------------------------------- - - ActiveRecord::Schema.define(version: 1) do - create_table :categories do |t| - t.string :title - t.timestamps null: false - end - - create_table :categories_posts, id: false do |t| - t.references :post, :category - end - - create_table :authors do |t| - t.string :first_name, :last_name - t.timestamps null: false - end - - create_table :authorships do |t| - t.string :first_name, :last_name - t.references :post - t.references :author - t.timestamps null: false - end - - create_table :comments do |t| - t.string :text - t.string :author - t.references :post - t.timestamps null: false - end - - add_index(:comments, :post_id) unless index_exists?(:comments, :post_id) - - create_table :posts do |t| - t.string :title - t.text :text - t.boolean :published - t.timestamps null: false - end - end - - # ----- Models definition ------------------------------------------------------------------------- - - class Category < ActiveRecord::Base - has_and_belongs_to_many :posts - end - - class Author < ActiveRecord::Base - has_many :authorships - - after_update { self.authorships.each(&:touch) } - - def full_name - [first_name, last_name].compact.join(' ') - end - end - - class Authorship < ActiveRecord::Base - belongs_to :author - belongs_to :post, touch: true - end - - class Comment < ActiveRecord::Base - belongs_to :post, touch: true - end - - class Post < ActiveRecord::Base - has_and_belongs_to_many :categories, after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ], - after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ] - has_many :authorships - has_many :authors, through: :authorships, - after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ], - after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ] - has_many :comments, after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ], - after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ] - - after_touch() { __elasticsearch__.index_document } - end - - # Include the search integration - # - Post.__send__ :include, Searchable - Comment.__send__ :include, Elasticsearch::Model - Comment.__send__ :include, Elasticsearch::Model::Callbacks - - # ----- Reset the indices ----------------------------------------------------------------- - - Post.delete_all - Post.__elasticsearch__.create_index! force: true - - Comment.delete_all - Comment.__elasticsearch__.create_index! force: true - end - - should "index and find a document" do - Post.create! title: 'Test' - Post.create! title: 'Testing Coding' - Post.create! title: 'Coding' - Post.__elasticsearch__.refresh_index! - - response = Post.search('title:test') - - assert_equal 2, response.results.size - assert_equal 2, response.records.size - - assert_equal 'Test', response.results.first.title - assert_equal 'Test', response.records.first.title - end - - should "reindex a document after categories are changed" do - # Create categories - category_a = Category.where(title: "One").first_or_create! - category_b = Category.where(title: "Two").first_or_create! - - # Create post - post = Post.create! title: "First Post", text: "This is the first post..." - - # Assign categories - post.categories = [category_a, category_b] - - Post.__elasticsearch__.refresh_index! - - query = { query: { - bool: { - must: { - multi_match: { - fields: ['title'], - query: 'first' - } - }, - filter: { - terms: { - categories: ['One'] - } - } - } - } - } - - response = Post.search query - - assert_equal 1, response.results.size - assert_equal 1, response.records.size - - # Remove category "One" - post.categories = [category_b] - - Post.__elasticsearch__.refresh_index! - response = Post.search query - - assert_equal 0, response.results.size - assert_equal 0, response.records.size - end - - should "reindex a document after authors are changed" do - # Create authors - author_a = Author.where(first_name: "John", last_name: "Smith").first_or_create! - author_b = Author.where(first_name: "Mary", last_name: "Smith").first_or_create! - author_c = Author.where(first_name: "Kobe", last_name: "Griss").first_or_create! - - # Create posts - post_1 = Post.create! title: "First Post", text: "This is the first post..." - post_2 = Post.create! title: "Second Post", text: "This is the second post..." - post_3 = Post.create! title: "Third Post", text: "This is the third post..." - - # Assign authors - post_1.authors = [author_a, author_b] - post_2.authors = [author_a] - post_3.authors = [author_c] - - Post.__elasticsearch__.refresh_index! - - response = Post.search 'authors.full_name:john' - - assert_equal 2, response.results.size - assert_equal 2, response.records.size - - post_3.authors << author_a - - Post.__elasticsearch__.refresh_index! - - response = Post.search 'authors.full_name:john' - - assert_equal 3, response.results.size - assert_equal 3, response.records.size - end if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4 - - should "reindex a document after comments are added" do - # Create posts - post_1 = Post.create! title: "First Post", text: "This is the first post..." - post_2 = Post.create! title: "Second Post", text: "This is the second post..." - - # Add comments - post_1.comments.create! author: 'John', text: 'Excellent' - post_1.comments.create! author: 'Abby', text: 'Good' - - post_2.comments.create! author: 'John', text: 'Terrible' - - Post.__elasticsearch__.refresh_index! - - response = Post.search 'comments.author:john AND comments.text:good' - assert_equal 0, response.results.size - - # Add comment - post_1.comments.create! author: 'John', text: 'Or rather just good...' - - Post.__elasticsearch__.refresh_index! - - response = Post.search 'comments.author:john AND comments.text:good' - assert_equal 0, response.results.size - - response = Post.search \ - query: { - nested: { - path: 'comments', - query: { - bool: { - must: [ - { match: { 'comments.author' => 'john' } }, - { match: { 'comments.text' => 'good' } } - ] - } - } - } - } - - assert_equal 1, response.results.size - end if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4 - - should "reindex a document after Post#touch" do - # Create categories - category_a = Category.where(title: "One").first_or_create! - - # Create post - post = Post.create! title: "First Post", text: "This is the first post..." - - # Assign category - post.categories << category_a - - Post.__elasticsearch__.refresh_index! - - assert_equal 1, Post.search('categories:One').size - - # Update category - category_a.update_attribute :title, "Updated" - - # Trigger touch on posts in category - category_a.posts.each { |p| p.touch } - - Post.__elasticsearch__.refresh_index! - - assert_equal 0, Post.search('categories:One').size - assert_equal 1, Post.search('categories:Updated').size - end - - should "eagerly load associated records" do - post_1 = Post.create(title: 'One') - post_2 = Post.create(title: 'Two') - post_1.comments.create text: 'First comment' - post_1.comments.create text: 'Second comment' - - Comment.__elasticsearch__.refresh_index! - - records = Comment.search('first').records(includes: :post) - - assert records.first.association(:post).loaded?, "The associated Post should be eagerly loaded" - assert_equal 'One', records.first.post.title - end - end - - end - end -end diff --git a/elasticsearch-model/test/integration/active_record_basic_test.rb b/elasticsearch-model/test/integration/active_record_basic_test.rb deleted file mode 100644 index d7ecb0586..000000000 --- a/elasticsearch-model/test/integration/active_record_basic_test.rb +++ /dev/null @@ -1,263 +0,0 @@ -require 'test_helper' -require 'active_record' - -puts "ActiveRecord #{ActiveRecord::VERSION::STRING}", '-'*80 - -# Needed for ActiveRecord 3.x ? -ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? - -::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' - -module Elasticsearch - module Model - class ActiveRecordBasicIntegrationTest < Elasticsearch::Test::IntegrationTestCase - - class ::Article < ActiveRecord::Base - include Elasticsearch::Model - include Elasticsearch::Model::Callbacks - - document_type 'article' - - settings index: { number_of_shards: 1, number_of_replicas: 0 } do - mapping do - indexes :title, type: 'text', analyzer: 'snowball' - indexes :body, type: 'text' - indexes :clicks, type: 'integer' - indexes :created_at, type: 'date' - end - end - - def as_indexed_json(options = {}) - attributes - .symbolize_keys - .slice(:title, :body, :clicks, :created_at) - .merge(suggest_title: title) - end - end - - context "ActiveRecord basic integration" do - setup do - ActiveRecord::Schema.define(:version => 1) do - create_table :articles do |t| - t.string :title - t.string :body - t.integer :clicks, :default => 0 - t.datetime :created_at, :default => 'NOW()' - end - end - - Article.delete_all - Article.__elasticsearch__.create_index! force: true - - ::Article.create! title: 'Test', body: '', clicks: 1 - ::Article.create! title: 'Testing Coding', body: '', clicks: 2 - ::Article.create! title: 'Coding', body: '', clicks: 3 - - Article.__elasticsearch__.refresh_index! - end - - should "index and find a document" do - response = Article.search('title:test') - - assert response.any?, "Response should not be empty: #{response.to_a.inspect}" - - assert_equal 2, response.results.size - assert_equal 2, response.records.size - - assert_instance_of Elasticsearch::Model::Response::Result, response.results.first - assert_instance_of Article, response.records.first - - assert_equal 'Test', response.results.first.title - assert_equal 'Test', response.records.first.title - end - - should "provide access to result" do - response = Article.search query: { match: { title: 'test' } }, highlight: { fields: { title: {} } } - - assert_equal 'Test', response.results.first.title - - assert_equal true, response.results.first.title? - assert_equal false, response.results.first.boo? - - assert_equal true, response.results.first.highlight? - assert_equal true, response.results.first.highlight.title? - assert_equal false, response.results.first.highlight.boo? - end - - should "iterate over results" do - response = Article.search('title:test') - - assert_equal ['1', '2'], response.results.map(&:_id) - assert_equal [1, 2], response.records.map(&:id) - end - - should "return _id and _type as #id and #type" do - response = Article.search('title:test') - - assert_equal '1', response.results.first.id - assert_equal 'article', response.results.first.type - end - - should "access results from records" do - response = Article.search('title:test') - - response.records.each_with_hit do |r, h| - assert_not_nil h._score - assert_not_nil h._source.title - end - end - - should "preserve the search results order for records" do - response = Article.search query: { match: { title: 'code' }}, sort: { clicks: :desc } - - assert_equal response.records[0].clicks, 3 - assert_equal response.records[0], response.records.first - assert_equal response.records[1].clicks, 2 - - response.records.each_with_hit do |r, h| - assert_equal h._id, r.id.to_s - end - - response.records.map_with_hit do |r, h| - assert_equal h._id, r.id.to_s - end - end - - should "remove document from index on destroy" do - article = Article.first - - article.destroy - assert_equal 2, Article.count - - Article.__elasticsearch__.refresh_index! - - response = Article.search 'title:test' - - assert_equal 1, response.results.size - assert_equal 1, response.records.size - end - - should "index updates to the document" do - article = Article.first - - article.title = 'Writing' - article.save - - Article.__elasticsearch__.refresh_index! - - response = Article.search 'title:write' - - assert_equal 1, response.results.size - assert_equal 1, response.records.size - end - - should "update specific attributes" do - article = Article.first - - response = Article.search 'title:special' - - assert_equal 0, response.results.size - assert_equal 0, response.records.size - - article.__elasticsearch__.update_document_attributes title: 'special' - - Article.__elasticsearch__.refresh_index! - - response = Article.search 'title:special' - - assert_equal 1, response.results.size - assert_equal 1, response.records.size - end - - should "update document when save is called multiple times in a transaction" do - article = Article.first - response = Article.search 'body:dummy' - - assert_equal 0, response.results.size - assert_equal 0, response.records.size - - ActiveRecord::Base.transaction do - article.body = 'dummy' - article.save - - article.title = 'special' - article.save - end - - article.__elasticsearch__.update_document - Article.__elasticsearch__.refresh_index! - - response = Article.search 'body:dummy' - assert_equal 1, response.results.size - assert_equal 1, response.records.size - end - - should "return results for a DSL search" do - response = Article.search query: { match: { title: { query: 'test' } } } - - assert_equal 2, response.results.size - assert_equal 2, response.records.size - end - - should "return a paged collection" do - response = Article.search query: { match: { title: { query: 'test' } } }, - size: 2, - from: 1 - - assert_equal 1, response.results.size - assert_equal 1, response.records.size - - assert_equal 'Testing Coding', response.results.first.title - assert_equal 'Testing Coding', response.records.first.title - end - - should "allow chaining SQL commands on response.records" do - response = Article.search query: { match: { title: { query: 'test' } } } - - assert_equal 2, response.records.size - assert_equal 1, response.records.where(title: 'Test').size - assert_equal 'Test', response.records.where(title: 'Test').first.title - end - - should "allow ordering response.records in SQL" do - response = Article.search query: { match: { title: { query: 'test' } } } - - if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4 - assert_equal 'Testing Coding', response.records.order(title: :desc).first.title - assert_equal 'Testing Coding', response.records.order(title: :desc)[0].title - else - assert_equal 'Testing Coding', response.records.order('title DESC').first.title - assert_equal 'Testing Coding', response.records.order('title DESC')[0].title - end - end - - should "allow ordering following any method chain in SQL" do - if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4 - response = Article.search query: { match: { title: { query: 'test' } } } - assert_equal 'Testing Coding', response.records.distinct.order(id: :desc).first.title - end - end - - should "allow dot access to response" do - response = Article.search query: { match: { title: { query: 'test' } } }, - aggregations: { - dates: { date_histogram: { field: 'created_at', interval: 'hour' } }, - clicks: { global: {}, aggregations: { min: { min: { field: 'clicks' } } } } - }, - suggest: { text: 'tezt', title: { term: { field: 'title', suggest_mode: 'always' } } } - - response.response.respond_to?(:aggregations) - assert_equal 2, response.aggregations.dates.buckets.first.doc_count - assert_equal 3, response.aggregations.clicks.doc_count - assert_equal 1.0, response.aggregations.clicks.min.value - assert_nil response.aggregations.clicks.max - - response.response.respond_to?(:suggest) - assert_equal 1, response.suggestions.title.first.options.size - assert_equal ['test'], response.suggestions.terms - end - end - - end - end -end diff --git a/elasticsearch-model/test/integration/active_record_custom_serialization_test.rb b/elasticsearch-model/test/integration/active_record_custom_serialization_test.rb deleted file mode 100644 index 9847967e5..000000000 --- a/elasticsearch-model/test/integration/active_record_custom_serialization_test.rb +++ /dev/null @@ -1,67 +0,0 @@ -require 'test_helper' -require 'active_record' - -# Needed for ActiveRecord 3.x ? -ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? - -::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' - -module Elasticsearch - module Model - class ActiveRecordCustomSerializationTest < Elasticsearch::Test::IntegrationTestCase - context "ActiveRecord model with custom JSON serialization" do - setup do - class ::ArticleWithCustomSerialization < ActiveRecord::Base - include Elasticsearch::Model - include Elasticsearch::Model::Callbacks - - mapping do - indexes :title - end - - def as_indexed_json(options={}) - # as_json(options.merge root: false).slice('title') - { title: self.title } - end - end - - ActiveRecord::Schema.define(:version => 1) do - create_table ArticleWithCustomSerialization.table_name do |t| - t.string :title - t.string :status - end - end - - ArticleWithCustomSerialization.delete_all - ArticleWithCustomSerialization.__elasticsearch__.create_index! force: true - end - - should "index only the title attribute when creating" do - ArticleWithCustomSerialization.create! title: 'Test', status: 'green' - - a = ArticleWithCustomSerialization.__elasticsearch__.client.get \ - index: 'article_with_custom_serializations', - type: '_doc', - id: '1' - - assert_equal( { 'title' => 'Test' }, a['_source'] ) - end - - should "index only the title attribute when updating" do - ArticleWithCustomSerialization.create! title: 'Test', status: 'green' - - article = ArticleWithCustomSerialization.first - article.update_attributes title: 'UPDATED', status: 'red' - - a = ArticleWithCustomSerialization.__elasticsearch__.client.get \ - index: 'article_with_custom_serializations', - type: '_doc', - id: '1' - - assert_equal( { 'title' => 'UPDATED' }, a['_source'] ) - end - end - - end - end -end diff --git a/elasticsearch-model/test/integration/active_record_import_test.rb b/elasticsearch-model/test/integration/active_record_import_test.rb deleted file mode 100644 index 1e9d9957e..000000000 --- a/elasticsearch-model/test/integration/active_record_import_test.rb +++ /dev/null @@ -1,198 +0,0 @@ -require 'test_helper' -require 'active_record' - -# Needed for ActiveRecord 3.x ? -ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? - -::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' - -module Elasticsearch - module Model - class ActiveRecordImportIntegrationTest < Elasticsearch::Test::IntegrationTestCase - - context "ActiveRecord importing" do - setup do - Object.send(:remove_const, :ImportArticle) if defined?(ImportArticle) - class ::ImportArticle < ActiveRecord::Base - include Elasticsearch::Model - - scope :popular, -> { where('views >= 50') } - - mapping do - indexes :title, type: 'text' - indexes :views, type: 'integer' - indexes :numeric, type: 'integer' - indexes :created_at, type: 'date' - end - end - - ActiveRecord::Schema.define(:version => 1) do - create_table :import_articles do |t| - t.string :title - t.integer :views - t.string :numeric # For the sake of invalid data sent to Elasticsearch - t.datetime :created_at, :default => 'NOW()' - end - end - - ImportArticle.delete_all - ImportArticle.__elasticsearch__.create_index! force: true - ImportArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' - - 100.times { |i| ImportArticle.create! title: "Test #{i}", views: i } - end - - should "import all the documents" do - assert_equal 100, ImportArticle.count - - ImportArticle.__elasticsearch__.refresh_index! - assert_equal 0, ImportArticle.search('*').results.total - - batches = 0 - errors = ImportArticle.import(batch_size: 10) do |response| - batches += 1 - end - - assert_equal 0, errors - assert_equal 10, batches - - ImportArticle.__elasticsearch__.refresh_index! - assert_equal 100, ImportArticle.search('*').results.total - end - - should "import only documents from a specific scope" do - assert_equal 100, ImportArticle.count - - assert_equal 0, ImportArticle.import(scope: 'popular') - - ImportArticle.__elasticsearch__.refresh_index! - assert_equal 50, ImportArticle.search('*').results.total - end - - should "import only documents from a specific query" do - assert_equal 100, ImportArticle.count - - assert_equal 0, ImportArticle.import(query: -> { where('views >= 30') }) - - ImportArticle.__elasticsearch__.refresh_index! - assert_equal 70, ImportArticle.search('*').results.total - end - - should "report and not store/index invalid documents" do - ImportArticle.create! title: "Test INVALID", numeric: "INVALID" - - assert_equal 101, ImportArticle.count - - ImportArticle.__elasticsearch__.refresh_index! - assert_equal 0, ImportArticle.search('*').results.total - - batches = 0 - errors = ImportArticle.__elasticsearch__.import(batch_size: 10) do |response| - batches += 1 - end - - assert_equal 1, errors - assert_equal 11, batches - - ImportArticle.__elasticsearch__.refresh_index! - assert_equal 100, ImportArticle.search('*').results.total - end - - should "transform documents with the option" do - assert_equal 100, ImportArticle.count - - assert_equal 0, ImportArticle.import( transform: ->(a) {{ index: { data: { name: a.title, foo: 'BAR' } }}} ) - - ImportArticle.__elasticsearch__.refresh_index! - assert_contains ImportArticle.search('*').results.first._source.keys, 'name' - assert_contains ImportArticle.search('*').results.first._source.keys, 'foo' - assert_equal 100, ImportArticle.search('test').results.total - assert_equal 100, ImportArticle.search('bar').results.total - end - end - - context "ActiveRecord importing when the model has a default scope" do - - setup do - Object.send(:remove_const, :ImportArticle) if defined?(ImportArticle) - class ::ImportArticle < ActiveRecord::Base - include Elasticsearch::Model - - default_scope { where('views >= 8') } - - mapping do - indexes :title, type: 'text' - indexes :views, type: 'integer' - indexes :numeric, type: 'integer' - indexes :created_at, type: 'date' - end - end - - ActiveRecord::Schema.define(:version => 1) do - create_table :import_articles do |t| - t.string :title - t.integer :views - t.string :numeric # For the sake of invalid data sent to Elasticsearch - t.datetime :created_at, :default => 'NOW()' - end - end - - ImportArticle.delete_all - ImportArticle.__elasticsearch__.delete_index! force: true - ImportArticle.__elasticsearch__.create_index! force: true - ImportArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' - - 10.times { |i| ImportArticle.create! title: "Test #{i}", views: i } - end - - should "import only documents from the default scope" do - assert_equal 2, ImportArticle.count - - assert_equal 0, ImportArticle.import - - ImportArticle.__elasticsearch__.refresh_index! - assert_equal 2, ImportArticle.search('*').results.total - end - - should "import only documents from a specific query combined with the default scope" do - assert_equal 2, ImportArticle.count - - assert_equal 0, ImportArticle.import(query: -> { where("title = 'Test 9'") }) - - ImportArticle.__elasticsearch__.refresh_index! - assert_equal 1, ImportArticle.search('*').results.total - end - end - - context 'ActiveRecord importing when the batch is empty' do - - setup do - Object.send(:remove_const, :ImportArticle) if defined?(ImportArticle) - class ::ImportArticle < ActiveRecord::Base - include Elasticsearch::Model - mapping { indexes :title, type: 'text' } - end - - ActiveRecord::Schema.define(:version => 1) do - create_table :import_articles do |t| - t.string :title - end - end - - ImportArticle.delete_all - ImportArticle.__elasticsearch__.delete_index! force: true - ImportArticle.__elasticsearch__.create_index! force: true - ImportArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' - end - - should 'not make any requests to create documents to Elasticsearch' do - assert_equal 0, ImportArticle.count - assert_equal 0, ImportArticle.import - - ImportArticle.__elasticsearch__.refresh_index! - assert_equal 0, ImportArticle.search('*').results.total - end - end - end - end -end diff --git a/elasticsearch-model/test/integration/active_record_namespaced_model_test.rb b/elasticsearch-model/test/integration/active_record_namespaced_model_test.rb deleted file mode 100644 index 75e7aa745..000000000 --- a/elasticsearch-model/test/integration/active_record_namespaced_model_test.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'test_helper' -require 'active_record' - -# Needed for ActiveRecord 3.x ? -ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? - -::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' - -module Elasticsearch - module Model - class ActiveRecordNamespacedModelIntegrationTest < Elasticsearch::Test::IntegrationTestCase - context "Namespaced ActiveRecord model integration" do - setup do - ActiveRecord::Schema.define(:version => 1) do - create_table :articles do |t| - t.string :title - end - end - - module ::MyNamespace - class Article < ActiveRecord::Base - include Elasticsearch::Model - include Elasticsearch::Model::Callbacks - - document_type 'article' - - mapping { indexes :title } - end - end - - MyNamespace::Article.delete_all - MyNamespace::Article.__elasticsearch__.create_index! force: true - - MyNamespace::Article.create! title: 'Test' - - MyNamespace::Article.__elasticsearch__.refresh_index! - end - - should "have proper index name and document type" do - assert_equal "my_namespace-articles", MyNamespace::Article.index_name - assert_equal "article", MyNamespace::Article.document_type - end - - should "save document into index on save and find it" do - response = MyNamespace::Article.search 'title:test' - - assert response.any?, "No results returned: #{response.inspect}" - assert_equal 1, response.size - - assert_equal 'Test', response.results.first.title - end - end - - end - end -end diff --git a/elasticsearch-model/test/integration/active_record_pagination_test.rb b/elasticsearch-model/test/integration/active_record_pagination_test.rb deleted file mode 100644 index 0b5e259ee..000000000 --- a/elasticsearch-model/test/integration/active_record_pagination_test.rb +++ /dev/null @@ -1,149 +0,0 @@ -require 'test_helper' -require 'active_record' - -# Needed for ActiveRecord 3.x ? -ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? - -::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' - -module Elasticsearch - module Model - class ActiveRecordPaginationTest < Elasticsearch::Test::IntegrationTestCase - class ::ArticleForPagination < ActiveRecord::Base - include Elasticsearch::Model - - scope :published, -> { where(published: true) } - - settings index: { number_of_shards: 1, number_of_replicas: 0 } do - mapping do - indexes :title, type: 'text', analyzer: 'snowball' - indexes :created_at, type: 'date' - end - end - end - - context "ActiveRecord pagination" do - setup do - ActiveRecord::Schema.define(:version => 1) do - create_table ::ArticleForPagination.table_name do |t| - t.string :title - t.datetime :created_at, :default => 'NOW()' - t.boolean :published - end - end - - Kaminari::Hooks.init if defined?(Kaminari::Hooks) - - ArticleForPagination.delete_all - ArticleForPagination.__elasticsearch__.create_index! force: true - - 68.times do |i| - ::ArticleForPagination.create! title: "Test #{i}", published: (i % 2 == 0) - end - - ArticleForPagination.import - ArticleForPagination.__elasticsearch__.refresh_index! - end - - should "be on the first page by default" do - records = ArticleForPagination.search('title:test').page(1).records - - assert_equal 25, records.size - assert_equal 1, records.current_page - assert_equal nil, records.prev_page - assert_equal 2, records.next_page - assert_equal 3, records.total_pages - - assert records.first_page?, "Should be the first page" - assert ! records.last_page?, "Should NOT be the last page" - assert ! records.out_of_range?, "Should NOT be out of range" - end - - should "load next page" do - records = ArticleForPagination.search('title:test').page(2).records - - assert_equal 25, records.size - assert_equal 2, records.current_page - assert_equal 1, records.prev_page - assert_equal 3, records.next_page - assert_equal 3, records.total_pages - - assert ! records.first_page?, "Should NOT be the first page" - assert ! records.last_page?, "Should NOT be the last page" - assert ! records.out_of_range?, "Should NOT be out of range" - end - - should "load last page" do - records = ArticleForPagination.search('title:test').page(3).records - - assert_equal 18, records.size - assert_equal 3, records.current_page - assert_equal 2, records.prev_page - assert_equal nil, records.next_page - assert_equal 3, records.total_pages - - assert ! records.first_page?, "Should NOT be the first page" - assert records.last_page?, "Should be the last page" - assert ! records.out_of_range?, "Should NOT be out of range" - end - - should "not load invalid page" do - records = ArticleForPagination.search('title:test').page(6).records - - assert_equal 0, records.size - assert_equal 6, records.current_page - - assert_equal nil, records.next_page - assert_equal 3, records.total_pages - - assert ! records.first_page?, "Should NOT be the first page" - assert records.out_of_range?, "Should be out of range" - end - - should "be combined with scopes" do - records = ArticleForPagination.search('title:test').page(2).records.published - assert records.all? { |r| r.published? } - assert_equal 12, records.size - end - - should "respect sort" do - search = ArticleForPagination.search({ query: { match: { title: 'test' } }, sort: [ { id: 'desc' } ] }) - - records = search.page(2).records - assert_equal 43, records.first.id # 68 - 25 = 42 - - records = search.page(3).records - assert_equal 18, records.first.id # 68 - (2 * 25) = 18 - - records = search.page(2).per(5).records - assert_equal 63, records.first.id # 68 - 5 = 63 - end - - should "set the limit per request" do - records = ArticleForPagination.search('title:test').limit(50).page(2).records - - assert_equal 18, records.size - assert_equal 2, records.current_page - assert_equal 1, records.prev_page - assert_equal nil, records.next_page - assert_equal 2, records.total_pages - - assert records.last_page?, "Should be the last page" - end - - context "with specific model settings" do - teardown do - ArticleForPagination.instance_variable_set(:@_default_per_page, nil) - end - - should "respect paginates_per" do - ArticleForPagination.paginates_per 50 - - assert_equal 50, ArticleForPagination.search('*').page(1).records.size - end - end - end - - end - end -end diff --git a/elasticsearch-model/test/integration/dynamic_index_name_test.rb b/elasticsearch-model/test/integration/dynamic_index_name_test.rb deleted file mode 100755 index f87db7978..000000000 --- a/elasticsearch-model/test/integration/dynamic_index_name_test.rb +++ /dev/null @@ -1,52 +0,0 @@ -require 'test_helper' -require 'active_record' - -# Needed for ActiveRecord 3.x ? -ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? - -::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' - -module Elasticsearch - module Model - class DynamicIndexNameTest < Elasticsearch::Test::IntegrationTestCase - context "Dynamic index name" do - setup do - class ::ArticleWithDynamicIndexName < ActiveRecord::Base - include Elasticsearch::Model - include Elasticsearch::Model::Callbacks - - def self.counter=(value) - @counter = 0 - end - - def self.counter - (@counter ||= 0) && @counter += 1 - end - - mapping { indexes :title } - index_name { "articles-#{counter}" } - end - - ::ActiveRecord::Schema.define(:version => 1) do - create_table ::ArticleWithDynamicIndexName.table_name do |t| - t.string :title - end - end - - ::ArticleWithDynamicIndexName.counter = 0 - end - - should 'evaluate the index_name value' do - assert_equal ArticleWithDynamicIndexName.index_name, "articles-1" - end - - should 're-evaluate the index_name value each time' do - assert_equal ArticleWithDynamicIndexName.index_name, "articles-1" - assert_equal ArticleWithDynamicIndexName.index_name, "articles-2" - assert_equal ArticleWithDynamicIndexName.index_name, "articles-3" - end - end - - end - end -end diff --git a/elasticsearch-model/test/integration/mongoid_basic_test.rb b/elasticsearch-model/test/integration/mongoid_basic_test.rb deleted file mode 100644 index 292c1f1a5..000000000 --- a/elasticsearch-model/test/integration/mongoid_basic_test.rb +++ /dev/null @@ -1,240 +0,0 @@ -require 'test_helper' -MongoDB.setup! - -if MongoDB.available? - MongoDB.connect_to 'mongoid_articles' - - module Elasticsearch - module Model - class MongoidBasicIntegrationTest < Elasticsearch::Test::IntegrationTestCase - - class ::MongoidArticle - include Mongoid::Document - include Elasticsearch::Model - include Elasticsearch::Model::Callbacks - - field :id, type: String - field :title, type: String - attr_accessible :title if respond_to? :attr_accessible - - settings index: { number_of_shards: 1, number_of_replicas: 0 } do - mapping do - indexes :title, type: 'text', analyzer: 'snowball' - indexes :created_at, type: 'date' - end - end - - def as_indexed_json(options={}) - as_json(except: [:id, :_id]) - end - end - - context "Mongoid integration" do - setup do - Elasticsearch::Model::Adapter.register \ - Elasticsearch::Model::Adapter::Mongoid, - lambda { |klass| !!defined?(::Mongoid::Document) && klass.respond_to?(:ancestors) && klass.ancestors.include?(::Mongoid::Document) } - - MongoidArticle.__elasticsearch__.create_index! force: true - - MongoidArticle.delete_all - - MongoidArticle.create! title: 'Test' - MongoidArticle.create! title: 'Testing Coding' - MongoidArticle.create! title: 'Coding' - - MongoidArticle.__elasticsearch__.refresh_index! - MongoidArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' - end - - should "index and find a document" do - response = MongoidArticle.search('title:test') - - assert response.any? - - assert_equal 2, response.results.size - assert_equal 2, response.records.size - - assert_instance_of Elasticsearch::Model::Response::Result, response.results.first - assert_instance_of MongoidArticle, response.records.first - - assert_equal 'Test', response.results.first.title - assert_equal 'Test', response.records.first.title - end - - should "iterate over results" do - response = MongoidArticle.search('title:test') - - assert_equal ['Test', 'Testing Coding'], response.results.map(&:title) - assert_equal ['Test', 'Testing Coding'], response.records.map(&:title) - end - - should "access results from records" do - response = MongoidArticle.search('title:test') - - response.records.each_with_hit do |r, h| - assert_not_nil h._score - assert_not_nil h._source.title - end - end - - should "preserve the search results order for records" do - response = MongoidArticle.search('title:code') - - response.records.each_with_hit do |r, h| - assert_equal h._id, r.id.to_s - end - - response.records.map_with_hit do |r, h| - assert_equal h._id, r.id.to_s - end - end - - should "remove document from index on destroy" do - article = MongoidArticle.first - - article.destroy - assert_equal 2, MongoidArticle.count - - MongoidArticle.__elasticsearch__.refresh_index! - - response = MongoidArticle.search 'title:test' - - assert_equal 1, response.results.size - assert_equal 1, response.records.size - end - - should "index updates to the document" do - article = MongoidArticle.first - - article.title = 'Writing' - article.save - - MongoidArticle.__elasticsearch__.refresh_index! - - response = MongoidArticle.search 'title:write' - - assert_equal 1, response.results.size - assert_equal 1, response.records.size - end - - should "return results for a DSL search" do - response = MongoidArticle.search query: { match: { title: { query: 'test' } } } - - assert_equal 2, response.results.size - assert_equal 2, response.records.size - end - - should "return a paged collection" do - response = MongoidArticle.search query: { match: { title: { query: 'test' } } }, - size: 2, - from: 1 - - assert_equal 1, response.results.size - assert_equal 1, response.records.size - - assert_equal 'Testing Coding', response.results.first.title - assert_equal 'Testing Coding', response.records.first.title - end - - - context "importing" do - setup do - MongoidArticle.delete_all - 97.times { |i| MongoidArticle.create! title: "Test #{i}" } - MongoidArticle.__elasticsearch__.create_index! force: true - MongoidArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' - end - - should "import all the documents" do - assert_equal 97, MongoidArticle.count - - MongoidArticle.__elasticsearch__.refresh_index! - assert_equal 0, MongoidArticle.search('*').results.total - - batches = 0 - errors = MongoidArticle.import(batch_size: 10) do |response| - batches += 1 - end - - assert_equal 0, errors - assert_equal 10, batches - - MongoidArticle.__elasticsearch__.refresh_index! - assert_equal 97, MongoidArticle.search('*').results.total - - response = MongoidArticle.search('test') - assert response.results.any?, "Search has not returned results: #{response.to_a}" - end - end - - context "importing when the model has a default scope" do - class ::MongoidArticleWithDefaultScope - include Mongoid::Document - include Elasticsearch::Model - - default_scope -> { where(status: 'active') } - - field :id, type: String - field :title, type: String - field :status, type: String, default: 'active' - - attr_accessible :title if respond_to? :attr_accessible - attr_accessible :status if respond_to? :attr_accessible - - settings index: { number_of_shards: 1, number_of_replicas: 0 } do - mapping do - indexes :title, type: 'text', analyzer: 'snowball' - indexes :status, type: 'text' - indexes :created_at, type: 'date' - end - end - - def as_indexed_json(options={}) - as_json(except: [:id, :_id]) - end - end - - setup do - Elasticsearch::Model::Adapter.register \ - Elasticsearch::Model::Adapter::Mongoid, - lambda { |klass| !!defined?(::Mongoid::Document) && klass.respond_to?(:ancestors) && klass.ancestors.include?(::Mongoid::Document) } - - MongoidArticleWithDefaultScope.__elasticsearch__.create_index! force: true - - MongoidArticleWithDefaultScope.delete_all - - MongoidArticleWithDefaultScope.create! title: 'Test' - MongoidArticleWithDefaultScope.create! title: 'Testing Coding' - MongoidArticleWithDefaultScope.create! title: 'Coding' - MongoidArticleWithDefaultScope.create! title: 'Test legacy code', status: 'removed' - - MongoidArticleWithDefaultScope.__elasticsearch__.refresh_index! - MongoidArticleWithDefaultScope.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' - end - - should "import only documents from the default scope" do - assert_equal 3, MongoidArticleWithDefaultScope.count - - assert_equal 0, MongoidArticleWithDefaultScope.import - - MongoidArticleWithDefaultScope.__elasticsearch__.refresh_index! - assert_equal 3, MongoidArticleWithDefaultScope.search('*').results.total - end - - should "import only documents from a specific query combined with the default scope" do - assert_equal 3, MongoidArticleWithDefaultScope.count - - assert_equal 0, MongoidArticleWithDefaultScope.import(query: -> { where(title: /^Test/) }) - - MongoidArticleWithDefaultScope.__elasticsearch__.refresh_index! - assert_equal 2, MongoidArticleWithDefaultScope.search('*').results.total - end - end - end - - end - end - end - -end diff --git a/elasticsearch-model/test/integration/multiple_models_test.rb b/elasticsearch-model/test/integration/multiple_models_test.rb deleted file mode 100644 index 02022b60f..000000000 --- a/elasticsearch-model/test/integration/multiple_models_test.rb +++ /dev/null @@ -1,176 +0,0 @@ -require 'test_helper' -require 'active_record' - -# Needed for ActiveRecord 3.x ? -ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) unless ActiveRecord::Base.connected? - -::ActiveRecord::Base.raise_in_transactional_callbacks = true if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' - -MongoDB.setup! - -module Elasticsearch - module Model - class MultipleModelsIntegration < Elasticsearch::Test::IntegrationTestCase - module ::NameSearch - extend ActiveSupport::Concern - - included do - include Elasticsearch::Model - include Elasticsearch::Model::Callbacks - - settings index: {number_of_shards: 1, number_of_replicas: 0} do - mapping do - indexes :name, type: 'text', analyzer: 'snowball' - indexes :created_at, type: 'date' - end - end - end - end - - class ::Episode < ActiveRecord::Base - include NameSearch - end - - class ::Series < ActiveRecord::Base - include NameSearch - end - - context "Multiple models" do - setup do - ActiveRecord::Schema.define(:version => 1) do - create_table :episodes do |t| - t.string :name - t.datetime :created_at, :default => 'NOW()' - end - - create_table :series do |t| - t.string :name - t.datetime :created_at, :default => 'NOW()' - end - end - - [::Episode, ::Series].each do |model| - model.delete_all - model.__elasticsearch__.create_index! force: true - model.create name: "The #{model.name}" - model.create name: "A great #{model.name}" - model.create name: "The greatest #{model.name}" - model.__elasticsearch__.refresh_index! - end - end - - should "find matching documents across multiple models" do - response = Elasticsearch::Model.search(%q<"The greatest Episode"^2 OR "The greatest Series">, [Series, Episode]) - - assert response.any?, "Response should not be empty: #{response.to_a.inspect}" - - assert_equal 2, response.results.size - assert_equal 2, response.records.size - - assert_instance_of Elasticsearch::Model::Response::Result, response.results.first - assert_instance_of Episode, response.records.first - assert_instance_of Series, response.records.last - - assert_equal 'The greatest Episode', response.results[0].name - assert_equal 'The greatest Episode', response.records[0].name - - assert_equal 'The greatest Series', response.results[1].name - assert_equal 'The greatest Series', response.records[1].name - end - - should "provide access to results" do - response = Elasticsearch::Model.search(%q<"A great Episode"^2 OR "A great Series">, [Series, Episode]) - - assert_equal 'A great Episode', response.results[0].name - assert_equal true, response.results[0].name? - assert_equal false, response.results[0].boo? - - assert_equal 'A great Series', response.results[1].name - assert_equal true, response.results[1].name? - assert_equal false, response.results[1].boo? - end - - should "only retrieve records for existing results" do - ::Series.find_by_name("The greatest Series").delete - ::Series.__elasticsearch__.refresh_index! - response = Elasticsearch::Model.search(%q<"The greatest Episode"^2 OR "The greatest Series">, [Series, Episode]) - - assert response.any?, "Response should not be empty: #{response.to_a.inspect}" - - assert_equal 2, response.results.size - assert_equal 1, response.records.size - - assert_instance_of Elasticsearch::Model::Response::Result, response.results.first - assert_instance_of Episode, response.records.first - - assert_equal 'The greatest Episode', response.results[0].name - assert_equal 'The greatest Episode', response.records[0].name - end - - should "paginate the results" do - response = Elasticsearch::Model.search('series OR episode', [Series, Episode]) - - assert_equal 3, response.page(1).per(3).results.size - assert_equal 3, response.page(2).per(3).results.size - assert_equal 0, response.page(3).per(3).results.size - end - - if MongoDB.available? - MongoDB.connect_to 'mongoid_collections' - - context "Across mongoid models" do - setup do - class ::Image - include Mongoid::Document - include Elasticsearch::Model - include Elasticsearch::Model::Callbacks - - field :name, type: String - attr_accessible :name if respond_to? :attr_accessible - - settings index: {number_of_shards: 1, number_of_replicas: 0} do - mapping do - indexes :name, type: 'text', analyzer: 'snowball' - indexes :created_at, type: 'date' - end - end - - def as_indexed_json(options={}) - as_json(except: [:_id]) - end - end - - Image.delete_all - Image.__elasticsearch__.create_index! force: true - Image.create! name: "The Image" - Image.create! name: "A great Image" - Image.create! name: "The greatest Image" - Image.__elasticsearch__.refresh_index! - Image.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' - end - - should "find matching documents across multiple models" do - response = Elasticsearch::Model.search(%q<"greatest Episode" OR "greatest Image"^2>, [Episode, Image]) - - assert response.any?, "Response should not be empty: #{response.to_a.inspect}" - - assert_equal 2, response.results.size - assert_equal 2, response.records.size - - assert_instance_of Elasticsearch::Model::Response::Result, response.results.first - assert_instance_of Image, response.records.first - assert_instance_of Episode, response.records.last - - assert_equal 'The greatest Image', response.results[0].name - assert_equal 'The greatest Image', response.records[0].name - - assert_equal 'The greatest Episode', response.results[1].name - assert_equal 'The greatest Episode', response.records[1].name - end - end - end - - end - end - end -end diff --git a/elasticsearch-model/test/test_helper.rb b/elasticsearch-model/test/test_helper.rb deleted file mode 100644 index 5a727e2b2..000000000 --- a/elasticsearch-model/test/test_helper.rb +++ /dev/null @@ -1,92 +0,0 @@ -RUBY_1_8 = defined?(RUBY_VERSION) && RUBY_VERSION < '1.9' - -exit(0) if RUBY_1_8 - -require 'simplecov' and SimpleCov.start { add_filter "/test|test_/" } if ENV["COVERAGE"] - -# Register `at_exit` handler for integration tests shutdown. -# MUST be called before requiring `test/unit`. -at_exit { Elasticsearch::Test::IntegrationTestCase.__run_at_exit_hooks } - -puts '-'*80 - -if defined?(RUBY_VERSION) && RUBY_VERSION > '2.2' - require 'test-unit' - require 'mocha/test_unit' -else - require 'minitest/autorun' - require 'mocha/mini_test' -end - -require 'shoulda-context' - -require 'turn' unless ENV["TM_FILEPATH"] || ENV["NOTURN"] || defined?(RUBY_VERSION) && RUBY_VERSION > '2.2' - -require 'ansi' -require 'oj' unless defined?(JRUBY_VERSION) - -require 'active_model' - -require 'kaminari' - -require 'elasticsearch/model' - -require 'elasticsearch/extensions/test/cluster' -require 'elasticsearch/extensions/test/startup_shutdown' - -module Elasticsearch - module Test - class IntegrationTestCase < ::Test::Unit::TestCase - extend Elasticsearch::Extensions::Test::StartupShutdown - - startup { Elasticsearch::Extensions::Test::Cluster.start(nodes: 1) if ENV['SERVER'] and not Elasticsearch::Extensions::Test::Cluster.running? } - shutdown { Elasticsearch::Extensions::Test::Cluster.stop if ENV['SERVER'] && started? } - context "IntegrationTest" do; should "noop on Ruby 1.8" do; end; end if RUBY_1_8 - - def setup - ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) - logger = ::Logger.new(STDERR) - logger.formatter = lambda { |s, d, p, m| "\e[2;36m#{m}\e[0m\n" } - ActiveRecord::Base.logger = logger unless ENV['QUIET'] - - ActiveRecord::LogSubscriber.colorize_logging = false - ActiveRecord::Migration.verbose = false - - tracer = ::Logger.new(STDERR) - tracer.formatter = lambda { |s, d, p, m| "#{m.gsub(/^.*$/) { |n| ' ' + n }.ansi(:faint)}\n" } - - Elasticsearch::Model.client = Elasticsearch::Client.new host: "localhost:#{(ENV['TEST_CLUSTER_PORT'] || 9250)}", - tracer: (ENV['QUIET'] ? nil : tracer) - end - end - end -end - -class MongoDB - def self.setup! - begin - require 'mongoid' - Mongo::Client.new(["localhost:27017"]) - ENV['MONGODB_AVAILABLE'] = 'yes' - rescue LoadError, Mongo::Error => e - $stderr.puts "MongoDB not installed or running: #{e}" - end - end - - def self.available? - !!ENV['MONGODB_AVAILABLE'] - end - - def self.connect_to(source) - $stderr.puts "Mongoid #{Mongoid::VERSION}", '-'*80 - - logger = ::Logger.new($stderr) - logger.formatter = lambda { |s, d, p, m| " #{m.ansi(:faint, :cyan)}\n" } - logger.level = ::Logger::DEBUG - - Mongoid.logger = logger unless ENV['QUIET'] - Mongo::Logger.logger = logger unless ENV['QUIET'] - - Mongoid.connect_to source - end -end From e590db91747c01b6851e58045f23397794ebf4b3 Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Fri, 21 Sep 2018 15:50:50 +0200 Subject: [PATCH 414/582] Update test tasks and travis (#840) * [CI] Update Travis testing structure * [RAILS] Update test tasks * [STORE] Update test tasks * [MODEL] Update test tasks --- .travis.yml | 26 +++++--- Rakefile | 41 +++++++++---- elasticsearch-model/Rakefile | 61 ++++++++----------- elasticsearch-model/spec/spec_helper.rb | 6 +- elasticsearch-persistence/Rakefile | 17 +++--- elasticsearch-persistence/spec/spec_helper.rb | 6 +- elasticsearch-rails/Rakefile | 11 ++++ elasticsearch-rails/test/test_helper.rb | 5 +- 8 files changed, 108 insertions(+), 65 deletions(-) diff --git a/.travis.yml b/.travis.yml index 426ccf85a..79ae349f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ language: ruby services: - mongodb + - elasticsearch branches: only: @@ -23,29 +24,40 @@ matrix: include: - rvm: 2.2 jdk: oraclejdk8 - env: TEST_SUITE=unit + env: RAILS_VERSIONS=3.0 - rvm: 2.3 jdk: oraclejdk8 - env: TEST_SUITE=unit + env: RAILS_VERSIONS=5.0 - rvm: 2.4 jdk: oraclejdk8 - env: TEST_SUITE=unit + env: RAILS_VERSIONS=5.0 - rvm: 2.5 jdk: oraclejdk8 - env: TEST_SUITE=unit + env: RAILS_VERSIONS=5.0 - rvm: jruby-9.1 jdk: oraclejdk8 - env: TEST_SUITE=unit + env: RAILS_VERSIONS=5.0 - rvm: 2.5 jdk: oraclejdk8 - env: TEST_SUITE=integration QUIET=y + env: RAILS_VERSIONS=4.0,5.0 + +env: + global: + - ELASTICSEARCH_VERSION=6.4.0 + - QUIET=true + before_install: + - wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ELASTICSEARCH_VERSION}.deb + - wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ELASTICSEARCH_VERSION}.deb.sha512 + - shasum -a 512 -c elasticsearch-${ELASTICSEARCH_VERSION}.deb.sha512 + - sudo dpkg -i --force-confnew elasticsearch-${ELASTICSEARCH_VERSION}.deb + - sudo service elasticsearch restart - gem update --system -q - gem update bundler -q - gem --version @@ -57,7 +69,7 @@ install: - rake bundle:install script: - - rake test:$TEST_SUITE + - rake test:all notifications: disable: true diff --git a/Rakefile b/Rakefile index bfca05bfd..6020f304d 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,7 @@ require 'pathname' -subprojects = %w| elasticsearch-rails elasticsearch-persistence elasticsearch-model | +subprojects = [ 'elasticsearch-rails', 'elasticsearch-persistence' ] +subprojects << 'elasticsearch-model' unless defined?(JRUBY_VERSION) __current__ = Pathname( File.expand_path('..', __FILE__) ) @@ -25,15 +26,9 @@ namespace :bundle do task :install do subprojects.each do |project| puts '-'*80 - sh "bundle install --gemfile #{__current__.join(project)}/Gemfile" + sh "cd #{__current__.join(project)} && bundle exec rake bundle:install" puts end - puts '-'*80 - sh "bundle install --gemfile #{__current__.join('elasticsearch-model/gemfiles')}/3.0.gemfile" - puts '-'*80 - sh "bundle install --gemfile #{__current__.join('elasticsearch-model/gemfiles')}/4.0.gemfile" - puts '-'*80 - sh "bundle install --gemfile #{__current__.join('elasticsearch-model/gemfiles')}/5.0.gemfile" end desc "Remove Gemfile.lock in all subprojects" @@ -60,7 +55,7 @@ namespace :test do end desc "Run Elasticsearch (Docker)" - task :setup_elasticsearch do + task :setup_elasticsearch_docker do begin sh <<-COMMAND.gsub(/^\s*/, '').gsub(/\s{1,}/, ' ') docker run -d=true \ @@ -70,15 +65,30 @@ namespace :test do --env "cluster.routing.allocation.disk.threshold_enabled=false" \ --publish 9250:9200 \ --rm \ - docker.elastic.co/elasticsearch/elasticsearch:6.4.0 + docker.elastic.co/elasticsearch/elasticsearch:${ELASTICSEARCH_VERSION} COMMAND require 'elasticsearch/extensions/test/cluster' - Elasticsearch::Extensions::Test::Cluster::Cluster.new(version: '6.4.0', + Elasticsearch::Extensions::Test::Cluster::Cluster.new(version: ENV['ELASTICSEARCH_VERSION'], number_of_nodes: 1).wait_for_green rescue end end + desc "Setup MongoDB (Docker)" + task :setup_mongodb_docker do + begin + if ENV['MONGODB_VERSION'] + sh <<-COMMAND.gsub(/^\s*/, '').gsub(/\s{1,}/, ' ') + wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-${MONGODB_VERSION}.tgz -O /tmp/mongodb.tgz && + tar -xvf /tmp/mongodb.tgz && + mkdir /tmp/data && + ${PWD}/mongodb-linux-x86_64-${MONGODB_VERSION}/bin/mongod --setParameter enableTestCommands=1 --dbpath /tmp/data --bind_ip 127.0.0.1 --auth &> /dev/null & + COMMAND + end + rescue + end + end + desc "Run integration tests in all subprojects" task :integration => :setup_elasticsearch do # 1/ elasticsearch-model @@ -106,8 +116,13 @@ namespace :test do desc "Run all tests in all subprojects" task :all do - Rake::Task['test:unit'].invoke - Rake::Task['test:integration'].invoke + subprojects.each do |project| + puts '-'*80 + sh "cd #{project} && " + + "unset BUNDLE_GEMFILE && " + + "bundle exec rake test:all" + puts "\n" + end end namespace :cluster do diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index e479d3599..6969c1d4a 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -4,12 +4,25 @@ desc "Run unit tests" task :default => 'test:unit' task :test => 'test:unit' -namespace :bundler do - desc "Install dependencies for all the Gemfiles" +if RUBY_VERSION < '2.3' + GEMFILES = ['3.0.gemfile', '4.0.gemfile', '5.0.gemfile'] +else + GEMFILES = ['4.0.gemfile', '5.0.gemfile'] +end + +namespace :bundle do + desc 'Install dependencies for all the Gemfiles in /gemfiles. Optionally define env variable RAILS_VERSIONS. E.g. RAILS_VERSIONS=3.0,5.0' task :install do - sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/3.0.gemfile', __FILE__)}' bundle install" - sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/4.0.gemfile', __FILE__)}' bundle install" - sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/5.0.gemfile', __FILE__)}' bundle install" + unless defined?(JRUBY_VERSION) + puts '-'*80 + gemfiles = ENV['RAILS_VERSIONS'] ? ENV['RAILS_VERSIONS'].split(',').map { |v| "#{v}.gemfile"} : GEMFILES + gemfiles.each do |gemfile| + Bundler.with_clean_env do + sh "bundle install --gemfile #{File.expand_path('../gemfiles/'+gemfile, __FILE__)}" + end + puts '-'*80 + end + end end end @@ -18,38 +31,16 @@ end require 'rake/testtask' namespace :test do - Rake::TestTask.new(:run_unit) do |test| - test.libs << 'lib' << 'test' - test.test_files = FileList["test/unit/**/*_test.rb"] - test.verbose = false - test.warning = false - end - - Rake::TestTask.new(:run_integration) do |test| - test.libs << 'lib' << 'test' - test.test_files = FileList["test/integration/**/*_test.rb"] - test.verbose = false - test.warning = false - end - - desc "Run unit tests against ActiveModel 3, 4 and 5" - task :unit do - end - - desc "Run integration tests against latest stable ActiveModel (5)" - task :integration do - ['3.0.gemfile', '4.0.gemfile', '5.0.gemfile'].each do |gemfile| - ['bundle exec rake test:run_unit', 'bundle exec rspec'].each do |cmd| - sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/'+gemfile, __FILE__)}' #{cmd}" - end + desc 'Run all tests. Optionally define env variable RAILS_VERSIONS. E.g. RAILS_VERSIONS=3.0,5.0' + task :all, [:rails_versions] do |task, args| + gemfiles = ENV['RAILS_VERSIONS'] ? ENV['RAILS_VERSIONS'].split(',').map {|v| "#{v}.gemfile"} : GEMFILES + puts '-' * 80 + gemfiles.each do |gemfile| + sh "BUNDLE_GEMFILE='#{File.expand_path("../gemfiles/#{gemfile}", __FILE__)}' " + + " bundle exec rspec" + puts '-' * 80 end end - - desc "Run unit and integration tests" - task :all do - Rake::Task['test:unit'].invoke - Rake::Task['test:integration'].invoke - end end # ----- Documentation tasks --------------------------------------------------- diff --git a/elasticsearch-model/spec/spec_helper.rb b/elasticsearch-model/spec/spec_helper.rb index 178f9c9fe..9e46a27de 100644 --- a/elasticsearch-model/spec/spec_helper.rb +++ b/elasticsearch-model/spec/spec_helper.rb @@ -10,6 +10,10 @@ require 'yaml' require 'active_record' +unless defined?(ELASTICSEARCH_URL) + ELASTICSEARCH_URL = ENV['ELASTICSEARCH_URL'] || "localhost:#{(ENV['TEST_CLUSTER_PORT'] || 9200)}" +end + RSpec.configure do |config| config.formatter = 'documentation' config.color = true @@ -18,7 +22,7 @@ require 'ansi' tracer = ::Logger.new(STDERR) tracer.formatter = lambda { |s, d, p, m| "#{m.gsub(/^.*$/) { |n| ' ' + n }.ansi(:faint)}\n" } - Elasticsearch::Model.client = Elasticsearch::Client.new host: "localhost:#{(ENV['TEST_CLUSTER_PORT'] || 9250)}", + Elasticsearch::Model.client = Elasticsearch::Client.new host: ELASTICSEARCH_URL, tracer: (ENV['QUIET'] ? nil : tracer) unless ActiveRecord::Base.connected? diff --git a/elasticsearch-persistence/Rakefile b/elasticsearch-persistence/Rakefile index 660e57211..0942fbdcf 100644 --- a/elasticsearch-persistence/Rakefile +++ b/elasticsearch-persistence/Rakefile @@ -12,19 +12,22 @@ require 'rspec/core/rake_task' namespace :test do RSpec::Core::RakeTask.new(:spec) - Rake::TestTask.new(:unit) do |test| - end - Rake::TestTask.new(:integration) do |test| + Rake::TestTask.new(:all) do |test| test.verbose = false test.warning = false test.deps = [ :spec ] end +end - Rake::TestTask.new(:all) do |test| - test.verbose = false - test.warning = false - test.deps = [ :spec ] +namespace :bundle do + desc 'Install gem dependencies' + task :install do + puts '-'*80 + Bundler.with_clean_env do + sh 'bundle install' + end + puts '-'*80 end end diff --git a/elasticsearch-persistence/spec/spec_helper.rb b/elasticsearch-persistence/spec/spec_helper.rb index 9f2f0f5df..6b8ea8182 100644 --- a/elasticsearch-persistence/spec/spec_helper.rb +++ b/elasticsearch-persistence/spec/spec_helper.rb @@ -1,6 +1,10 @@ require 'pry-nav' require 'elasticsearch/persistence' +unless defined?(ELASTICSEARCH_URL) + ELASTICSEARCH_URL = ENV['ELASTICSEARCH_URL'] || "localhost:#{(ENV['TEST_CLUSTER_PORT'] || 9200)}" +end + RSpec.configure do |config| config.formatter = 'documentation' config.color = true @@ -13,7 +17,7 @@ # The default client to be used by the repositories. # # @since 6.0.0 -DEFAULT_CLIENT = Elasticsearch::Client.new(host: "localhost:#{(ENV['TEST_CLUSTER_PORT'] || 9250)}", +DEFAULT_CLIENT = Elasticsearch::Client.new(host: ELASTICSEARCH_URL, tracer: (ENV['QUIET'] ? nil : ::Logger.new(STDERR))) class MyTestRepository diff --git a/elasticsearch-rails/Rakefile b/elasticsearch-rails/Rakefile index 622731c66..f555aaf42 100644 --- a/elasticsearch-rails/Rakefile +++ b/elasticsearch-rails/Rakefile @@ -28,6 +28,17 @@ namespace :test do end end +namespace :bundle do + desc 'Install gem dependencies' + task :install do + puts '-'*80 + Bundler.with_clean_env do + sh 'bundle install' + end + puts '-'*80 + end +end + # ----- Documentation tasks --------------------------------------------------- require 'yard' diff --git a/elasticsearch-rails/test/test_helper.rb b/elasticsearch-rails/test/test_helper.rb index e06554fc0..bc96002c5 100644 --- a/elasticsearch-rails/test/test_helper.rb +++ b/elasticsearch-rails/test/test_helper.rb @@ -1,4 +1,7 @@ RUBY_1_8 = defined?(RUBY_VERSION) && RUBY_VERSION < '1.9' +unless defined?(ELASTICSEARCH_URL) + ELASTICSEARCH_URL = ENV['ELASTICSEARCH_URL'] || "localhost:#{(ENV['TEST_CLUSTER_PORT'] || 9200)}" +end exit(0) if RUBY_1_8 @@ -56,7 +59,7 @@ def setup tracer = ::Logger.new(STDERR) tracer.formatter = lambda { |s, d, p, m| "#{m.gsub(/^.*$/) { |n| ' ' + n }.ansi(:faint)}\n" } - Elasticsearch::Model.client = Elasticsearch::Client.new host: "localhost:#{(ENV['TEST_CLUSTER_PORT'] || 9250)}", + Elasticsearch::Model.client = Elasticsearch::Client.new host: ELASTICSEARCH_URL, tracer: (ENV['QUIET'] ? nil : tracer) end end From dfcc664dd31d686bcd799c6a07180e70ffa3147a Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Mon, 24 Sep 2018 11:19:43 +0200 Subject: [PATCH 415/582] [RAILS] Convert tests to rspec (#842) --- elasticsearch-rails/Gemfile | 14 ++-- elasticsearch-rails/Rakefile | 20 ++--- .../spec/instrumentation_spec.rb | 81 +++++++++++++++++++ elasticsearch-rails/spec/lograge_spec.rb | 17 ++++ elasticsearch-rails/spec/spec_helper.rb | 45 +++++++++++ elasticsearch-rails/test/test_helper.rb | 67 --------------- .../instrumentation/instrumentation_test.rb | 61 -------------- .../test/unit/instrumentation/lograge_test.rb | 21 ----- 8 files changed, 156 insertions(+), 170 deletions(-) create mode 100644 elasticsearch-rails/spec/instrumentation_spec.rb create mode 100644 elasticsearch-rails/spec/lograge_spec.rb create mode 100644 elasticsearch-rails/spec/spec_helper.rb delete mode 100644 elasticsearch-rails/test/test_helper.rb delete mode 100644 elasticsearch-rails/test/unit/instrumentation/instrumentation_test.rb delete mode 100644 elasticsearch-rails/test/unit/instrumentation/lograge_test.rb diff --git a/elasticsearch-rails/Gemfile b/elasticsearch-rails/Gemfile index 81fc68ac4..407f94341 100644 --- a/elasticsearch-rails/Gemfile +++ b/elasticsearch-rails/Gemfile @@ -3,12 +3,12 @@ source 'https://rubygems.org' # Specify your gem's dependencies in elasticsearch-rails.gemspec gemspec -# TODO: Figure out how to specify dependency on local elasticsearch-model without endless "Resolving dependencies" -# if File.exists? File.expand_path("../../elasticsearch-model", __FILE__) -# gem 'elasticsearch-model', :path => File.expand_path("../../elasticsearch-model", __FILE__), :require => true -# end - - - gem 'elasticsearch-model', :path => File.expand_path("../../elasticsearch-model", __FILE__), :require => false gem 'elasticsearch-persistence', :path => File.expand_path("../../elasticsearch-persistence", __FILE__), :require => false + + +group :development, :testing do + gem 'rspec' + gem 'pry-nav' + gem 'sqlite3' unless defined?(JRUBY_VERSION) +end diff --git a/elasticsearch-rails/Rakefile b/elasticsearch-rails/Rakefile index f555aaf42..0d87185d4 100644 --- a/elasticsearch-rails/Rakefile +++ b/elasticsearch-rails/Rakefile @@ -7,24 +7,16 @@ task :test => 'test:unit' # ----- Test tasks ------------------------------------------------------------ require 'rake/testtask' +require 'rspec/core/rake_task' + namespace :test do - Rake::TestTask.new(:unit) do |test| - test.libs << 'lib' << 'test' - test.test_files = FileList["test/unit/**/*_test.rb"] - test.verbose = false - test.warning = false - end - Rake::TestTask.new(:integration) do |test| - test.libs << 'lib' << 'test' - test.test_files = FileList["test/integration/**/*_test.rb"] - test.verbose = false - test.warning = false - end + RSpec::Core::RakeTask.new(:spec) Rake::TestTask.new(:all) do |test| - test.libs << 'lib' << 'test' - test.test_files = FileList["test/unit/**/*_test.rb", "test/integration/**/*_test.rb"] + test.verbose = false + test.warning = false + test.deps = [ :spec ] unless defined?(JRUBY_VERSION) end end diff --git a/elasticsearch-rails/spec/instrumentation_spec.rb b/elasticsearch-rails/spec/instrumentation_spec.rb new file mode 100644 index 000000000..2327800d1 --- /dev/null +++ b/elasticsearch-rails/spec/instrumentation_spec.rb @@ -0,0 +1,81 @@ +require 'spec_helper' + +describe 'ActiveSupport::Instrumentation integration' do + + before(:all) do + class DummyInstrumentationModel + extend Elasticsearch::Model::Searching::ClassMethods + + def self.index_name; 'foo'; end + def self.document_type; 'bar'; end + end + end + + after(:all) do + remove_classes(DummyInstrumentationModel) + end + + let(:response_document) do + { 'took' => '5ms', + 'hits' => { 'total' => 123, + 'max_score' => 456, + 'hits' => [] } } + end + + let(:search) do + Elasticsearch::Model::Searching::SearchRequest.new(DummyInstrumentationModel, 'foo') + end + + let(:client) do + double('client', search: response_document) + end + + before do + allow(DummyInstrumentationModel).to receive(:client).and_return(client) + Elasticsearch::Rails::Instrumentation::Railtie.run_initializers + end + + context 'SearchRequest#execute!' do + + it 'wraps the method with instrumentation' do + expect(search).to respond_to(:execute_without_instrumentation!) + expect(search).to respond_to(:execute_with_instrumentation!) + end + end + + context 'Model#search' do + + before do + expect(ActiveSupport::Notifications).to receive(:instrument).with('search.elasticsearch', + { klass: 'DummyInstrumentationModel', + name: 'Search', + search: { body: query, + index: 'foo', + type: 'bar' } }).and_return({}) + end + + let(:query) do + { query: { match: { foo: 'bar' } } } + end + + let(:logged_message) do + @logger.logged(:debug).first + end + + it 'publishes a notification' do + expect(DummyInstrumentationModel.search(query).response).to eq({}) + end + + context 'when a message is logged', unless: defined?(RUBY_VERSION) && RUBY_VERSION > '2.2' do + + let(:query) do + { query: { match: { moo: 'bam' } } } + end + + it 'prints the debug information to the log' do + expect(logged_message).to match(/DummyInstrumentationModel Search \(\d+\.\d+ms\)/) + expect(logged_message).to match(/body\: \{query\: \{match\: \{moo\: "bam"\}\}\}\}/) + end + end + end +end diff --git a/elasticsearch-rails/spec/lograge_spec.rb b/elasticsearch-rails/spec/lograge_spec.rb new file mode 100644 index 000000000..3192d4089 --- /dev/null +++ b/elasticsearch-rails/spec/lograge_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' +require 'action_pack' +require 'lograge' +require 'elasticsearch/rails/lograge' + +describe 'ActiveSupport::Instrumentation integration' do + + before do + Elasticsearch::Rails::Lograge::Railtie.run_initializers + end + + it 'customizes the Lograge configuration' do + expect(Elasticsearch::Rails::Lograge::Railtie.initializers + .select { |i| i.name == 'elasticsearch.lograge' } + .first).not_to be_nil + end +end diff --git a/elasticsearch-rails/spec/spec_helper.rb b/elasticsearch-rails/spec/spec_helper.rb new file mode 100644 index 000000000..d92dc5233 --- /dev/null +++ b/elasticsearch-rails/spec/spec_helper.rb @@ -0,0 +1,45 @@ +require 'pry-nav' +require 'active_record' +require 'elasticsearch/model' +require 'elasticsearch/rails' +require 'rails/railtie' +require 'elasticsearch/rails/instrumentation' + + +unless defined?(ELASTICSEARCH_URL) + ELASTICSEARCH_URL = ENV['ELASTICSEARCH_URL'] || "localhost:#{(ENV['TEST_CLUSTER_PORT'] || 9200)}" +end + +RSpec.configure do |config| + config.formatter = 'documentation' + config.color = true + + config.before(:suite) do + require 'ansi' + tracer = ::Logger.new(STDERR) + tracer.formatter = lambda { |s, d, p, m| "#{m.gsub(/^.*$/) { |n| ' ' + n }.ansi(:faint)}\n" } + Elasticsearch::Model.client = Elasticsearch::Client.new host: ELASTICSEARCH_URL, + tracer: (ENV['QUIET'] ? nil : tracer) + + unless ActiveRecord::Base.connected? + ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) + end + + if ::ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks) && ::ActiveRecord::VERSION::MAJOR.to_s < '5' + ::ActiveRecord::Base.raise_in_transactional_callbacks = true + end + end +end + +# Remove all classes. +# +# @param [ Array<Class> ] classes The list of classes to remove. +# +# @return [ true ] +# +# @since 6.0.1 +def remove_classes(*classes) + classes.each do |_class| + Object.send(:remove_const, _class.name.to_sym) if defined?(_class) + end and true +end diff --git a/elasticsearch-rails/test/test_helper.rb b/elasticsearch-rails/test/test_helper.rb deleted file mode 100644 index bc96002c5..000000000 --- a/elasticsearch-rails/test/test_helper.rb +++ /dev/null @@ -1,67 +0,0 @@ -RUBY_1_8 = defined?(RUBY_VERSION) && RUBY_VERSION < '1.9' -unless defined?(ELASTICSEARCH_URL) - ELASTICSEARCH_URL = ENV['ELASTICSEARCH_URL'] || "localhost:#{(ENV['TEST_CLUSTER_PORT'] || 9200)}" -end - -exit(0) if RUBY_1_8 - -require 'simplecov' and SimpleCov.start { add_filter "/test|test_/" } if ENV["COVERAGE"] - -# Register `at_exit` handler for integration tests shutdown. -# MUST be called before requiring `test/unit`. -at_exit { Elasticsearch::Test::IntegrationTestCase.__run_at_exit_hooks } - -puts '-'*80 - -if defined?(RUBY_VERSION) && RUBY_VERSION > '2.2' - require 'test-unit' - require 'mocha/test_unit' -else - require 'minitest/autorun' - require 'mocha/mini_test' -end - -require 'shoulda-context' - -require 'turn' unless ENV["TM_FILEPATH"] || ENV["NOTURN"] || defined?(RUBY_VERSION) && RUBY_VERSION > '2.2' - -require 'ansi' -require 'oj' unless defined?(JRUBY_VERSION) - -require 'rails/version' -require 'active_record' -require 'active_model' - -require 'elasticsearch/model' -require 'elasticsearch/rails' - -require 'elasticsearch/extensions/test/cluster' -require 'elasticsearch/extensions/test/startup_shutdown' - -module Elasticsearch - module Test - class IntegrationTestCase < ::Test::Unit::TestCase - extend Elasticsearch::Extensions::Test::StartupShutdown - - startup { Elasticsearch::Extensions::Test::Cluster.start(nodes: 1) if ENV['SERVER'] and not Elasticsearch::Extensions::Test::Cluster.running? } - shutdown { Elasticsearch::Extensions::Test::Cluster.stop if ENV['SERVER'] && started? } - context "IntegrationTest" do; should "noop on Ruby 1.8" do; end; end if RUBY_1_8 - - def setup - ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) - logger = ::Logger.new(STDERR) - logger.formatter = lambda { |s, d, p, m| "#{m.ansi(:faint, :cyan)}\n" } - ActiveRecord::Base.logger = logger unless ENV['QUIET'] - - ActiveRecord::LogSubscriber.colorize_logging = false - ActiveRecord::Migration.verbose = false - - tracer = ::Logger.new(STDERR) - tracer.formatter = lambda { |s, d, p, m| "#{m.gsub(/^.*$/) { |n| ' ' + n }.ansi(:faint)}\n" } - - Elasticsearch::Model.client = Elasticsearch::Client.new host: ELASTICSEARCH_URL, - tracer: (ENV['QUIET'] ? nil : tracer) - end - end - end -end diff --git a/elasticsearch-rails/test/unit/instrumentation/instrumentation_test.rb b/elasticsearch-rails/test/unit/instrumentation/instrumentation_test.rb deleted file mode 100644 index 51cd3c669..000000000 --- a/elasticsearch-rails/test/unit/instrumentation/instrumentation_test.rb +++ /dev/null @@ -1,61 +0,0 @@ -require 'test_helper' - -require 'rails/railtie' -require 'active_support/log_subscriber/test_helper' - -require 'elasticsearch/rails/instrumentation' - -class Elasticsearch::Rails::InstrumentationTest < Test::Unit::TestCase - include ActiveSupport::LogSubscriber::TestHelper - - context "ActiveSupport::Instrumentation integration" do - class ::DummyInstrumentationModel - extend Elasticsearch::Model::Searching::ClassMethods - - def self.index_name; 'foo'; end - def self.document_type; 'bar'; end - end - - RESPONSE = { 'took' => '5ms', 'hits' => { 'total' => 123, 'max_score' => 456, 'hits' => [] } } - - setup do - @search = Elasticsearch::Model::Searching::SearchRequest.new ::DummyInstrumentationModel, '*' - - @client = stub('client', search: RESPONSE) - DummyInstrumentationModel.stubs(:client).returns(@client) - - Elasticsearch::Rails::Instrumentation::Railtie.run_initializers - end - - should "wrap SearchRequest#execute! with instrumentation" do - s = Elasticsearch::Model::Searching::SearchRequest.new ::DummyInstrumentationModel, 'foo' - assert_respond_to s, :execute_without_instrumentation! - assert_respond_to s, :execute_with_instrumentation! - end - - should "publish the notification" do - @query = { query: { match: { foo: 'bar' } } } - - ActiveSupport::Notifications.expects(:instrument).with do |name, payload| - assert_equal "search.elasticsearch", name - assert_equal 'DummyInstrumentationModel', payload[:klass] - assert_equal @query, payload[:search][:body] - true - end - - s = ::DummyInstrumentationModel.search @query - s.response - end - - should "print the debug information to the Rails log" do - s = ::DummyInstrumentationModel.search query: { match: { moo: 'bam' } } - s.response - - logged = @logger.logged(:debug).first - - assert_not_nil logged - assert_match /DummyInstrumentationModel Search \(\d+\.\d+ms\)/, logged - assert_match /body\: \{query\: \{match\: \{moo\: "bam"\}\}\}\}/, logged - end unless defined?(RUBY_VERSION) && RUBY_VERSION > '2.2' - end -end diff --git a/elasticsearch-rails/test/unit/instrumentation/lograge_test.rb b/elasticsearch-rails/test/unit/instrumentation/lograge_test.rb deleted file mode 100644 index f7984daef..000000000 --- a/elasticsearch-rails/test/unit/instrumentation/lograge_test.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'test_helper' - -require 'rails/railtie' -require 'action_pack' -require 'lograge' - -require 'elasticsearch/rails/lograge' - -class Elasticsearch::Rails::LogrageTest < Test::Unit::TestCase - context "Lograge integration" do - setup do - Elasticsearch::Rails::Lograge::Railtie.run_initializers - end - - should "customize the Lograge configuration" do - assert_not_nil Elasticsearch::Rails::Lograge::Railtie.initializers - .select { |i| i.name == 'elasticsearch.lograge' } - .first - end - end -end From 6f0860899486ce4baaafad60a8c23dd7ad98c262 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Mon, 24 Sep 2018 11:21:24 +0200 Subject: [PATCH 416/582] [CI] Consolidate travis matrix --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 79ae349f7..b2d590256 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,10 +34,6 @@ matrix: jdk: oraclejdk8 env: RAILS_VERSIONS=5.0 - - rvm: 2.5 - jdk: oraclejdk8 - env: RAILS_VERSIONS=5.0 - - rvm: jruby-9.1 jdk: oraclejdk8 env: RAILS_VERSIONS=5.0 From ad6c2e03c69adada97aa4165b67d87bbbee552bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hrvoje=20=C5=A0imi=C4=87?= <hrvoje@twobucks.co> Date: Mon, 24 Sep 2018 11:37:33 +0200 Subject: [PATCH 417/582] [RAILS] Fix seeds file to stop using outdated YAML method (#843) The expert template was using the outdated YAML.load_documents, which is no longer present in the newer versions of Ruby. --- elasticsearch-rails/lib/rails/templates/seeds.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/elasticsearch-rails/lib/rails/templates/seeds.rb b/elasticsearch-rails/lib/rails/templates/seeds.rb index 85bcd509b..b9e2ecbd8 100644 --- a/elasticsearch-rails/lib/rails/templates/seeds.rb +++ b/elasticsearch-rails/lib/rails/templates/seeds.rb @@ -3,7 +3,8 @@ Zlib::GzipReader.open(File.expand_path('../articles.yml.gz', __FILE__)) do |gzip| puts "Reading articles from gzipped YAML..." - @documents = YAML.load_documents(gzip.read) + @documents = YAML.respond_to?(:load_documents) ? YAML.load_documents(gzip.read) : + YAML.load_stream(gzip.read) end # Truncate the default ActiveRecord logger output From dec858e7b5097a3e522d835097fbbeb5908246fa Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Mon, 24 Sep 2018 12:02:39 +0200 Subject: [PATCH 418/582] [CI] Update Travis matrix to test against 7.0.0.alpha1 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b2d590256..2819babaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,7 @@ matrix: env: global: - - ELASTICSEARCH_VERSION=6.4.0 + - ELASTICSEARCH_VERSION=7.0.0.alpha - QUIET=true From e55e64cb3e11e51697c5eba565616a2e84f54ced Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Mon, 24 Sep 2018 12:12:41 +0200 Subject: [PATCH 419/582] [CI] Try another endpoint for elasticsearh 7.0.0.alpha1 --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2819babaa..b27a21a42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,7 +49,7 @@ env: before_install: - - wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ELASTICSEARCH_VERSION}.deb + - wget https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/deb/elasticsearch/7.0.0/elasticsearch-${ELASTICSEARCH_VERSION}.deb - wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ELASTICSEARCH_VERSION}.deb.sha512 - shasum -a 512 -c elasticsearch-${ELASTICSEARCH_VERSION}.deb.sha512 - sudo dpkg -i --force-confnew elasticsearch-${ELASTICSEARCH_VERSION}.deb @@ -60,6 +60,7 @@ before_install: - bundle version install: + - bundle install - bundle install - rake bundle:clean - rake bundle:install From c0866ed4f3dc84d7294d818893a591464961376a Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Mon, 24 Sep 2018 12:35:16 +0200 Subject: [PATCH 420/582] [CI] Use docker for now to install elasticsearch in travis --- .travis.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index b27a21a42..35e89f730 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,23 +45,24 @@ matrix: env: global: - ELASTICSEARCH_VERSION=7.0.0.alpha + - TEST_CLUSTER_PORT=9250 - QUIET=true before_install: - - wget https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/deb/elasticsearch/7.0.0/elasticsearch-${ELASTICSEARCH_VERSION}.deb - - wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ELASTICSEARCH_VERSION}.deb.sha512 - - shasum -a 512 -c elasticsearch-${ELASTICSEARCH_VERSION}.deb.sha512 - - sudo dpkg -i --force-confnew elasticsearch-${ELASTICSEARCH_VERSION}.deb - - sudo service elasticsearch restart + #- wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ELASTICSEARCH_VERSION}.deb + #- wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ELASTICSEARCH_VERSION}.deb.sha512 + #- shasum -a 512 -c elasticsearch-${ELASTICSEARCH_VERSION}.deb.sha512 + #- sudo dpkg -i --force-confnew elasticsearch-${ELASTICSEARCH_VERSION}.deb + #- sudo service elasticsearch restart - gem update --system -q - gem update bundler -q - gem --version - bundle version install: - - bundle install - - bundle install + - bundle install test:setup_elasticsearch_docker + - bundle exec rake test:set - rake bundle:clean - rake bundle:install From 5067eda3bfcaad2e34a975ff4f5f4131365c3aa2 Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Wed, 26 Sep 2018 16:38:03 +0200 Subject: [PATCH 421/582] [CI] Download master snapshot of Elasticsearch and setup on Travis (#844) --- .travis.yml | 13 +++---------- elasticsearch-model/spec/spec_helper.rb | 1 + elasticsearch-persistence/spec/spec_helper.rb | 1 + elasticsearch-rails/spec/spec_helper.rb | 1 + travis_before_script.sh | 15 +++++++++++++++ 5 files changed, 21 insertions(+), 10 deletions(-) create mode 100644 travis_before_script.sh diff --git a/.travis.yml b/.travis.yml index 35e89f730..a7b121b10 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,6 @@ language: ruby services: - mongodb - - elasticsearch branches: only: @@ -44,25 +43,19 @@ matrix: env: global: - - ELASTICSEARCH_VERSION=7.0.0.alpha + - ELASTICSEARCH_VERSION=7.0.0-alpha1 - TEST_CLUSTER_PORT=9250 - QUIET=true - before_install: - #- wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ELASTICSEARCH_VERSION}.deb - #- wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ELASTICSEARCH_VERSION}.deb.sha512 - #- shasum -a 512 -c elasticsearch-${ELASTICSEARCH_VERSION}.deb.sha512 - #- sudo dpkg -i --force-confnew elasticsearch-${ELASTICSEARCH_VERSION}.deb - #- sudo service elasticsearch restart + - ELASTICSEARCH_VERSION=7.0.0-alpha1 TEST_CLUSTER_PORT=9250 source ./travis_before_script.sh - gem update --system -q - gem update bundler -q - gem --version - bundle version install: - - bundle install test:setup_elasticsearch_docker - - bundle exec rake test:set + - bundle install - rake bundle:clean - rake bundle:install diff --git a/elasticsearch-model/spec/spec_helper.rb b/elasticsearch-model/spec/spec_helper.rb index 9e46a27de..86c257969 100644 --- a/elasticsearch-model/spec/spec_helper.rb +++ b/elasticsearch-model/spec/spec_helper.rb @@ -24,6 +24,7 @@ tracer.formatter = lambda { |s, d, p, m| "#{m.gsub(/^.*$/) { |n| ' ' + n }.ansi(:faint)}\n" } Elasticsearch::Model.client = Elasticsearch::Client.new host: ELASTICSEARCH_URL, tracer: (ENV['QUIET'] ? nil : tracer) + puts "Elasticsearch Version: #{Elasticsearch::Model.client.info['version']}" unless ActiveRecord::Base.connected? ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) diff --git a/elasticsearch-persistence/spec/spec_helper.rb b/elasticsearch-persistence/spec/spec_helper.rb index 6b8ea8182..9160c8a41 100644 --- a/elasticsearch-persistence/spec/spec_helper.rb +++ b/elasticsearch-persistence/spec/spec_helper.rb @@ -9,6 +9,7 @@ config.formatter = 'documentation' config.color = true + config.before(:suite) { puts "Elasticsearch Version: #{DEFAULT_CLIENT.info['version']}" } config.after(:suite) do DEFAULT_CLIENT.indices.delete(index: '_all') end diff --git a/elasticsearch-rails/spec/spec_helper.rb b/elasticsearch-rails/spec/spec_helper.rb index d92dc5233..b12e8468e 100644 --- a/elasticsearch-rails/spec/spec_helper.rb +++ b/elasticsearch-rails/spec/spec_helper.rb @@ -20,6 +20,7 @@ tracer.formatter = lambda { |s, d, p, m| "#{m.gsub(/^.*$/) { |n| ' ' + n }.ansi(:faint)}\n" } Elasticsearch::Model.client = Elasticsearch::Client.new host: ELASTICSEARCH_URL, tracer: (ENV['QUIET'] ? nil : tracer) + puts "Elasticsearch Version: #{Elasticsearch::Model.client.info['version']}" unless ActiveRecord::Base.connected? ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" ) diff --git a/travis_before_script.sh b/travis_before_script.sh new file mode 100644 index 000000000..fdca40263 --- /dev/null +++ b/travis_before_script.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +metadata_url="https://artifacts-api.elastic.co/v1/branches/master/builds/latest/projects/elasticsearch/packages/elasticsearch-${ELASTICSEARCH_VERSION}-SNAPSHOT.zip/file" +echo "Getting snapshot location from $metadata_url" + +url=$(curl -v $metadata_url 2>&1 | grep -Pio 'location: \K(.*)' | tr -d '\r') + +echo "Downloading Elasticsearch from $url" +curl $url -o /tmp/elasticsearch.zip + +echo 'Unzipping file' +unzip -q /tmp/elasticsearch.zip + +echo "Starting elasticsearch on port ${TEST_CLUSTER_PORT}" +${PWD}/elasticsearch-7.0.0-alpha1-SNAPSHOT/bin/elasticsearch -E http.port=${TEST_CLUSTER_PORT} &> /dev/null & From 93a0da088e434f2f78636877fa306777629e066c Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Tue, 16 Oct 2018 18:51:42 +0200 Subject: [PATCH 422/582] [MODEL] Avoid executing search twice; Reuse response in Response#raw_response (#850) --- elasticsearch-model/lib/elasticsearch/model/response.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response.rb b/elasticsearch-model/lib/elasticsearch/model/response.rb index 9726ae2c5..acb25669e 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response.rb @@ -60,7 +60,7 @@ def timed_out # Returns the statistics on shards # def shards - @shards ||= HashWrapper.new(raw_response['_shards']) + @shards ||= response['_shards'] end # Returns a Hashie::Mash of the aggregations @@ -76,7 +76,7 @@ def suggestions end def raw_response - @raw_response ||= search.execute! + @raw_response ||= @response ? @response.to_hash : search.execute! end end end From 3f2c03f36b17216a71c4ba56bdaa43347ca50a81 Mon Sep 17 00:00:00 2001 From: Jon Roberts <jon@emptyflask.net> Date: Wed, 17 Oct 2018 10:26:00 -0500 Subject: [PATCH 423/582] `respond_to_missing?` to silence Ruby 2.4 warnings (#838) I ran into a bunch of warnings when using Elastic results with Ruby 2.4+ and trailblazer/cells, because `#method_missing?` without `#respond_to_missing?` makes Forwardable think all of the Result attributes are private methods. https://robots.thoughtbot.com/always-define-respond-to-missing-when-overriding --- .../lib/elasticsearch/model/response/result.rb | 2 +- .../spec/elasticsearch/model/response/result_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/result.rb b/elasticsearch-model/lib/elasticsearch/model/response/result.rb index 01481d0e1..267ca6300 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/result.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/result.rb @@ -46,7 +46,7 @@ def method_missing(name, *arguments) # Respond to methods from `@result` or `@result._source` # - def respond_to?(method_name, include_private = false) + def respond_to_missing?(method_name, include_private = false) @result.respond_to?(method_name.to_sym) || \ @result._source && @result._source.respond_to?(method_name.to_sym) || \ super diff --git a/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb index 2b19c4ab3..6f1298a2b 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb @@ -53,6 +53,14 @@ expect(result._source).to eq('bar' => { 'bam' => 'baz' }) end + it 'is recognized by #method' do + expect(result.method :bar).to be_a Method + end + + it 'respond_to? still works' do + expect(result.respond_to? :bar).to be true + end + context 'when methods map to keys in subdocuments of the response from Elasticsearch' do it 'provides access to top level fields via a method' do From 55369738d49a513ea4eb11fb628c40543bd61c33 Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Wed, 17 Oct 2018 17:34:22 +0200 Subject: [PATCH 424/582] [STORE] Ensure that arguments are passed to super (#853) --- .../lib/elasticsearch/persistence/repository.rb | 4 +++- elasticsearch-persistence/spec/repository_spec.rb | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index 23e34c3e5..c76be8310 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -211,7 +211,9 @@ def settings(*args) # # @since 6.0.0 def index_exists?(*args) - super(index_name: index_name) + params = { index_name: index_name } + params.merge!(args.first) unless args.empty? + super(params) end # Get the nicer formatted string for use in inspection. diff --git a/elasticsearch-persistence/spec/repository_spec.rb b/elasticsearch-persistence/spec/repository_spec.rb index 292eb5d06..bf0968b32 100644 --- a/elasticsearch-persistence/spec/repository_spec.rb +++ b/elasticsearch-persistence/spec/repository_spec.rb @@ -353,6 +353,13 @@ class RepositoryWithDSL it 'determines if the index exists' do expect(repository.index_exists?).to be(true) end + + context 'when arguments are passed in' do + + it 'passes the arguments to the request' do + expect(repository.index_exists?(index: 'other')).to be(false) + end + end end context 'when the method is called on the class' do From fb84196ae1369784c561234ed492c6abba6910c1 Mon Sep 17 00:00:00 2001 From: Timo Schilling <timo@schilling.io> Date: Wed, 17 Oct 2018 17:51:52 +0200 Subject: [PATCH 425/582] [RAILS] Fixed 03-expert.rb set tracer only in dev env (#621) The variable `tracer` is only defined in Rails development env, so we only can set them in that env. --- elasticsearch-rails/lib/rails/templates/03-expert.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/elasticsearch-rails/lib/rails/templates/03-expert.rb b/elasticsearch-rails/lib/rails/templates/03-expert.rb index d892b4c85..f7f9040c7 100644 --- a/elasticsearch-rails/lib/rails/templates/03-expert.rb +++ b/elasticsearch-rails/lib/rails/templates/03-expert.rb @@ -270,9 +270,8 @@ def index if Rails.env.development? tracer = ActiveSupport::Logger.new('log/elasticsearch.log') tracer.level = Logger::DEBUG + Elasticsearch::Model.client.transport.tracer = tracer end - -Elasticsearch::Model.client.transport.tracer = tracer CODE git add: "config/initializers" From 8d1cbf1bf1cd11be2949d718b814fed3f8ad1466 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emily.s@elastic.co> Date: Mon, 22 Oct 2018 16:31:21 +0200 Subject: [PATCH 426/582] [STORE] Index name option is handled by super, no need to pass options expicitly --- .../lib/elasticsearch/persistence/repository.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index c76be8310..10e156ea4 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -211,9 +211,7 @@ def settings(*args) # # @since 6.0.0 def index_exists?(*args) - params = { index_name: index_name } - params.merge!(args.first) unless args.empty? - super(params) + super end # Get the nicer formatted string for use in inspection. From f6f36dc7a3310fa5e58033bc9d979f61eb4a9caa Mon Sep 17 00:00:00 2001 From: Loren Siebert <loren@siebert.org> Date: Fri, 2 Nov 2018 09:11:25 -0400 Subject: [PATCH 427/582] Update README.md to link to migration blog post (#857) --- elasticsearch-persistence/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index cf4856536..b58e9f026 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -568,7 +568,7 @@ and demonstrates a rich set of features: ### The ActiveRecord Pattern The ActiveRecord pattern has been deprecated as of version 6.0.0 of this gem. Please use the -[Repository Pattern](#the-repository-pattern) instead. +[Repository Pattern](#the-repository-pattern) instead. For more information on migrating 5.x ActiveRecord-based applications to use the Repository Pattern, please see [this blog post](https://www.elastic.co/blog/activerecord-to-repository-changing-persistence-patterns-with-the-elasticsearch-rails-gem). ## License From 4429fd9c27b4808a721e90481fd8814a0b7486d4 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Thu, 8 Nov 2018 11:34:32 +0100 Subject: [PATCH 428/582] [MODEL] Update example to account for deprecation of _suggest endpoint in favor of _search --- .../examples/activerecord_mapping_completion.rb | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/elasticsearch-model/examples/activerecord_mapping_completion.rb b/elasticsearch-model/examples/activerecord_mapping_completion.rb index b8270313c..d15390525 100644 --- a/elasticsearch-model/examples/activerecord_mapping_completion.rb +++ b/elasticsearch-model/examples/activerecord_mapping_completion.rb @@ -50,20 +50,7 @@ def as_indexed_json(options={}) puts "Article search:".ansi(:bold), response_1.to_a.map { |d| "Title: #{d.title}" }.inspect.ansi(:bold, :yellow) -response_2 = Article.__elasticsearch__.client.suggest \ - index: Article.index_name, - body: { - articles: { - text: 'foo', - completion: { field: 'title.suggest' } - } - }; - -puts "Article suggest:".ansi(:bold), - response_2['articles'].first['options'].map { |d| "#{d['text']} -> #{d['_source']['url']}" }. - inspect.ansi(:bold, :green) - -response_3 = Article.search \ +response_2 = Article.search \ query: { match: { title: 'foo' } }, @@ -76,7 +63,7 @@ def as_indexed_json(options={}) _source: ['title', 'url'] puts "Article search with suggest:".ansi(:bold), - response_3.response['suggest']['articles'].first['options'].map { |d| "#{d['text']} -> #{d['_source']['url']}" }. + response_2.response['suggest']['articles'].first['options'].map { |d| "#{d['text']} -> #{d['_source']['url']}" }. inspect.ansi(:bold, :blue) require 'pry'; binding.pry; From 6069226bf25d7be91bc088e90d59bc37d496e9bf Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Thu, 29 Nov 2018 15:49:02 +0100 Subject: [PATCH 429/582] Add license headers, LICENSE and NOTICE files (#861) --- Gemfile | 17 ++ LICENSE.txt | 202 +++++++++++++++++ Rakefile | 17 ++ elasticsearch-model/Gemfile | 17 ++ elasticsearch-model/LICENSE.txt | 209 +++++++++++++++++- elasticsearch-model/Rakefile | 17 ++ .../elasticsearch-model.gemspec | 17 ++ .../examples/activerecord_article.rb | 17 ++ .../examples/activerecord_associations.rb | 17 ++ .../examples/activerecord_custom_analyzer.rb | 17 ++ .../activerecord_mapping_completion.rb | 17 ++ .../activerecord_mapping_edge_ngram.rb | 17 ++ .../examples/couchbase_article.rb | 17 ++ .../examples/datamapper_article.rb | 17 ++ .../examples/mongoid_article.rb | 17 ++ elasticsearch-model/examples/ohm_article.rb | 17 ++ elasticsearch-model/examples/riak_article.rb | 17 ++ elasticsearch-model/gemfiles/3.0.gemfile | 17 ++ elasticsearch-model/gemfiles/4.0.gemfile | 17 ++ elasticsearch-model/gemfiles/5.0.gemfile | 17 ++ .../lib/elasticsearch/model.rb | 17 ++ .../lib/elasticsearch/model/adapter.rb | 17 ++ .../model/adapters/active_record.rb | 17 ++ .../elasticsearch/model/adapters/default.rb | 17 ++ .../elasticsearch/model/adapters/mongoid.rb | 17 ++ .../elasticsearch/model/adapters/multiple.rb | 17 ++ .../lib/elasticsearch/model/callbacks.rb | 17 ++ .../lib/elasticsearch/model/client.rb | 17 ++ .../elasticsearch/model/ext/active_record.rb | 17 ++ .../lib/elasticsearch/model/hash_wrapper.rb | 17 ++ .../lib/elasticsearch/model/importing.rb | 17 ++ .../lib/elasticsearch/model/indexing.rb | 17 ++ .../lib/elasticsearch/model/multimodel.rb | 17 ++ .../lib/elasticsearch/model/naming.rb | 17 ++ .../lib/elasticsearch/model/proxy.rb | 17 ++ .../lib/elasticsearch/model/response.rb | 17 ++ .../model/response/aggregations.rb | 17 ++ .../lib/elasticsearch/model/response/base.rb | 17 ++ .../model/response/pagination.rb | 17 ++ .../model/response/pagination/kaminari.rb | 17 ++ .../response/pagination/will_paginate.rb | 17 ++ .../elasticsearch/model/response/records.rb | 17 ++ .../elasticsearch/model/response/result.rb | 17 ++ .../elasticsearch/model/response/results.rb | 17 ++ .../model/response/suggestions.rb | 17 ++ .../lib/elasticsearch/model/searching.rb | 17 ++ .../lib/elasticsearch/model/serializing.rb | 17 ++ .../lib/elasticsearch/model/version.rb | 17 ++ .../spec/elasticsearch/model/adapter_spec.rb | 17 ++ .../active_record/associations_spec.rb | 17 ++ .../adapters/active_record/basic_spec.rb | 17 ++ .../active_record/dynamic_index_name_spec.rb | 17 ++ .../adapters/active_record/import_spec.rb | 17 ++ .../active_record/multi_model_spec.rb | 17 ++ .../active_record/namespaced_model_spec.rb | 17 ++ .../adapters/active_record/pagination_spec.rb | 17 ++ .../active_record/parent_child_spec.rb | 17 ++ .../active_record/serialization_spec.rb | 17 ++ .../model/adapters/active_record_spec.rb | 17 ++ .../model/adapters/default_spec.rb | 17 ++ .../model/adapters/mongoid/basic_spec.rb | 17 ++ .../adapters/mongoid/multi_model_spec.rb | 17 ++ .../model/adapters/mongoid_spec.rb | 17 ++ .../model/adapters/multiple_spec.rb | 17 ++ .../elasticsearch/model/callbacks_spec.rb | 17 ++ .../spec/elasticsearch/model/client_spec.rb | 17 ++ .../elasticsearch/model/hash_wrapper_spec.rb | 17 ++ .../elasticsearch/model/importing_spec.rb | 17 ++ .../spec/elasticsearch/model/indexing_spec.rb | 17 ++ .../spec/elasticsearch/model/module_spec.rb | 17 ++ .../elasticsearch/model/multimodel_spec.rb | 17 ++ .../model/naming_inheritance_spec.rb | 17 ++ .../spec/elasticsearch/model/naming_spec.rb | 17 ++ .../spec/elasticsearch/model/proxy_spec.rb | 17 ++ .../model/response/aggregations_spec.rb | 17 ++ .../elasticsearch/model/response/base_spec.rb | 17 ++ .../response/pagination/kaminari_spec.rb | 17 ++ .../response/pagination/will_paginate_spec.rb | 17 ++ .../model/response/records_spec.rb | 17 ++ .../model/response/response_spec.rb | 17 ++ .../model/response/result_spec.rb | 17 ++ .../model/response/results_spec.rb | 17 ++ .../model/searching_search_request_spec.rb | 17 ++ .../elasticsearch/model/searching_spec.rb | 17 ++ .../elasticsearch/model/serializing_spec.rb | 17 ++ elasticsearch-model/spec/spec_helper.rb | 17 ++ elasticsearch-model/spec/support/app.rb | 17 ++ .../spec/support/app/answer.rb | 17 ++ .../spec/support/app/article.rb | 17 ++ .../support/app/article_for_pagination.rb | 17 ++ .../app/article_with_custom_serialization.rb | 17 ++ .../app/article_with_dynamic_index_name.rb | 17 ++ .../spec/support/app/author.rb | 17 ++ .../spec/support/app/authorship.rb | 17 ++ .../spec/support/app/category.rb | 17 ++ .../spec/support/app/comment.rb | 17 ++ .../spec/support/app/episode.rb | 17 ++ elasticsearch-model/spec/support/app/image.rb | 17 ++ .../spec/support/app/import_article.rb | 17 ++ .../spec/support/app/mongoid_article.rb | 17 ++ .../spec/support/app/namespaced_book.rb | 17 ++ .../app/parent_and_child_searchable.rb | 17 ++ elasticsearch-model/spec/support/app/post.rb | 17 ++ .../spec/support/app/question.rb | 17 ++ .../spec/support/app/searchable.rb | 17 ++ .../spec/support/app/series.rb | 17 ++ elasticsearch-persistence/Gemfile | 17 ++ elasticsearch-persistence/LICENSE.txt | 209 +++++++++++++++++- elasticsearch-persistence/Rakefile | 17 ++ .../elasticsearch-persistence.gemspec | 17 ++ .../examples/notes/Gemfile | 17 ++ .../examples/notes/application.rb | 17 ++ .../examples/notes/config.ru | 17 ++ .../examples/notes/test.rb | 17 ++ .../lib/elasticsearch/persistence.rb | 17 ++ .../elasticsearch/persistence/repository.rb | 17 ++ .../persistence/repository/dsl.rb | 17 ++ .../persistence/repository/find.rb | 17 ++ .../repository/response/results.rb | 17 ++ .../persistence/repository/search.rb | 17 ++ .../persistence/repository/serialize.rb | 17 ++ .../persistence/repository/store.rb | 17 ++ .../lib/elasticsearch/persistence/version.rb | 17 ++ .../spec/repository/find_spec.rb | 17 ++ .../spec/repository/response/results_spec.rb | 17 ++ .../spec/repository/search_spec.rb | 17 ++ .../spec/repository/serialize_spec.rb | 17 ++ .../spec/repository/store_spec.rb | 17 ++ .../spec/repository_spec.rb | 17 ++ elasticsearch-persistence/spec/spec_helper.rb | 17 ++ elasticsearch-rails/Gemfile | 17 ++ elasticsearch-rails/LICENSE.txt | 209 +++++++++++++++++- elasticsearch-rails/Rakefile | 17 ++ .../elasticsearch-rails.gemspec | 17 ++ .../lib/elasticsearch/rails.rb | 17 ++ .../elasticsearch/rails/instrumentation.rb | 17 ++ .../instrumentation/controller_runtime.rb | 17 ++ .../rails/instrumentation/log_subscriber.rb | 17 ++ .../rails/instrumentation/publishers.rb | 17 ++ .../rails/instrumentation/railtie.rb | 17 ++ .../lib/elasticsearch/rails/lograge.rb | 17 ++ .../lib/elasticsearch/rails/tasks/import.rb | 17 ++ .../lib/elasticsearch/rails/version.rb | 17 ++ .../lib/rails/templates/01-basic.rb | 17 ++ .../lib/rails/templates/02-pretty.rb | 17 ++ .../lib/rails/templates/03-expert.rb | 17 ++ .../lib/rails/templates/04-dsl.rb | 17 ++ .../lib/rails/templates/05-settings-files.rb | 17 ++ .../lib/rails/templates/indexer.rb | 17 ++ .../templates/search_controller_test.dsl.rb | 17 ++ .../rails/templates/search_controller_test.rb | 17 ++ .../lib/rails/templates/searchable.dsl.rb | 17 ++ .../lib/rails/templates/searchable.rb | 17 ++ .../lib/rails/templates/seeds.rb | 17 ++ .../spec/instrumentation_spec.rb | 17 ++ elasticsearch-rails/spec/lograge_spec.rb | 17 ++ elasticsearch-rails/spec/spec_helper.rb | 17 ++ 157 files changed, 3400 insertions(+), 30 deletions(-) create mode 100644 LICENSE.txt diff --git a/Gemfile b/Gemfile index 536ee1c11..005d12640 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + source 'https://rubygems.org' gem "bundler", "~> 1" diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Rakefile b/Rakefile index 6020f304d..99e4e2c8d 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'pathname' subprojects = [ 'elasticsearch-rails', 'elasticsearch-persistence' ] diff --git a/elasticsearch-model/Gemfile b/elasticsearch-model/Gemfile index c016d096d..6a70a3206 100644 --- a/elasticsearch-model/Gemfile +++ b/elasticsearch-model/Gemfile @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + source 'https://rubygems.org' # Specify your gem's dependencies in elasticsearch-model.gemspec diff --git a/elasticsearch-model/LICENSE.txt b/elasticsearch-model/LICENSE.txt index 7dc94b3e5..d64569567 100644 --- a/elasticsearch-model/LICENSE.txt +++ b/elasticsearch-model/LICENSE.txt @@ -1,13 +1,202 @@ -Copyright (c) 2014 Elasticsearch -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ - http://www.apache.org/licenses/LICENSE-2.0 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index 6969c1d4a..06af970f7 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require "bundler/gem_tasks" desc "Run unit tests" diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index 9e52c400a..33bc169e7 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) diff --git a/elasticsearch-model/examples/activerecord_article.rb b/elasticsearch-model/examples/activerecord_article.rb index 428c4dcc1..2bd2e1e27 100644 --- a/elasticsearch-model/examples/activerecord_article.rb +++ b/elasticsearch-model/examples/activerecord_article.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # ActiveRecord and Elasticsearch # ============================== # diff --git a/elasticsearch-model/examples/activerecord_associations.rb b/elasticsearch-model/examples/activerecord_associations.rb index b086283a0..4d7125cbe 100644 --- a/elasticsearch-model/examples/activerecord_associations.rb +++ b/elasticsearch-model/examples/activerecord_associations.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # ActiveRecord associations and Elasticsearch # =========================================== # diff --git a/elasticsearch-model/examples/activerecord_custom_analyzer.rb b/elasticsearch-model/examples/activerecord_custom_analyzer.rb index 07371508a..e2a79ef23 100644 --- a/elasticsearch-model/examples/activerecord_custom_analyzer.rb +++ b/elasticsearch-model/examples/activerecord_custom_analyzer.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Custom Analyzer for ActiveRecord integration with Elasticsearch # =============================================================== diff --git a/elasticsearch-model/examples/activerecord_mapping_completion.rb b/elasticsearch-model/examples/activerecord_mapping_completion.rb index d15390525..901d93232 100644 --- a/elasticsearch-model/examples/activerecord_mapping_completion.rb +++ b/elasticsearch-model/examples/activerecord_mapping_completion.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'ansi' require 'active_record' require 'elasticsearch/model' diff --git a/elasticsearch-model/examples/activerecord_mapping_edge_ngram.rb b/elasticsearch-model/examples/activerecord_mapping_edge_ngram.rb index 7a6a1e8c6..b3af42959 100644 --- a/elasticsearch-model/examples/activerecord_mapping_edge_ngram.rb +++ b/elasticsearch-model/examples/activerecord_mapping_edge_ngram.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'ansi' require 'sqlite3' require 'active_record' diff --git a/elasticsearch-model/examples/couchbase_article.rb b/elasticsearch-model/examples/couchbase_article.rb index 57cc421b0..b7372a2d0 100644 --- a/elasticsearch-model/examples/couchbase_article.rb +++ b/elasticsearch-model/examples/couchbase_article.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Couchbase and Elasticsearch # =========================== # diff --git a/elasticsearch-model/examples/datamapper_article.rb b/elasticsearch-model/examples/datamapper_article.rb index 291df7d3d..0c483a005 100644 --- a/elasticsearch-model/examples/datamapper_article.rb +++ b/elasticsearch-model/examples/datamapper_article.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # DataMapper and Elasticsearch # ============================ # diff --git a/elasticsearch-model/examples/mongoid_article.rb b/elasticsearch-model/examples/mongoid_article.rb index 5cd12ca4f..01d6e5295 100644 --- a/elasticsearch-model/examples/mongoid_article.rb +++ b/elasticsearch-model/examples/mongoid_article.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Mongoid and Elasticsearch # ========================= # diff --git a/elasticsearch-model/examples/ohm_article.rb b/elasticsearch-model/examples/ohm_article.rb index 3145085e7..52091083c 100644 --- a/elasticsearch-model/examples/ohm_article.rb +++ b/elasticsearch-model/examples/ohm_article.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Ohm for Redis and Elasticsearch # =============================== # diff --git a/elasticsearch-model/examples/riak_article.rb b/elasticsearch-model/examples/riak_article.rb index 8013cda7e..2ff12d9bd 100644 --- a/elasticsearch-model/examples/riak_article.rb +++ b/elasticsearch-model/examples/riak_article.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Riak and Elasticsearch # ====================== # diff --git a/elasticsearch-model/gemfiles/3.0.gemfile b/elasticsearch-model/gemfiles/3.0.gemfile index b0141ef48..53fd4c66a 100644 --- a/elasticsearch-model/gemfiles/3.0.gemfile +++ b/elasticsearch-model/gemfiles/3.0.gemfile @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Usage: # # $ BUNDLE_GEMFILE=./gemfiles/3.0.gemfile bundle install diff --git a/elasticsearch-model/gemfiles/4.0.gemfile b/elasticsearch-model/gemfiles/4.0.gemfile index f8acebc38..8c53765ac 100644 --- a/elasticsearch-model/gemfiles/4.0.gemfile +++ b/elasticsearch-model/gemfiles/4.0.gemfile @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Usage: # # $ BUNDLE_GEMFILE=./gemfiles/4.0.gemfile bundle install diff --git a/elasticsearch-model/gemfiles/5.0.gemfile b/elasticsearch-model/gemfiles/5.0.gemfile index 612f2bbd9..8b1930961 100644 --- a/elasticsearch-model/gemfiles/5.0.gemfile +++ b/elasticsearch-model/gemfiles/5.0.gemfile @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Usage: # # $ BUNDLE_GEMFILE=./gemfiles/5.0.gemfile bundle install diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index 2c395bd84..23e121896 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'hashie/mash' require 'active_support/core_ext/module/delegation' diff --git a/elasticsearch-model/lib/elasticsearch/model/adapter.rb b/elasticsearch-model/lib/elasticsearch/model/adapter.rb index 3a25e5d97..5fa2cd974 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapter.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapter.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb index 3fb3f987e..87a9e2ea7 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model module Adapter diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/default.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/default.rb index e58cf4ceb..62cec409c 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/default.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/default.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model module Adapter diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb index d4aff56f9..fb671158b 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model module Adapter diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb index 9a0bc4e8e..7cd8456ac 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model module Adapter diff --git a/elasticsearch-model/lib/elasticsearch/model/callbacks.rb b/elasticsearch-model/lib/elasticsearch/model/callbacks.rb index 1b72cb2a0..4f0397e29 100644 --- a/elasticsearch-model/lib/elasticsearch/model/callbacks.rb +++ b/elasticsearch-model/lib/elasticsearch/model/callbacks.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model diff --git a/elasticsearch-model/lib/elasticsearch/model/client.rb b/elasticsearch-model/lib/elasticsearch/model/client.rb index c1a9b4ed9..8a49d98f4 100644 --- a/elasticsearch-model/lib/elasticsearch/model/client.rb +++ b/elasticsearch-model/lib/elasticsearch/model/client.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model diff --git a/elasticsearch-model/lib/elasticsearch/model/ext/active_record.rb b/elasticsearch-model/lib/elasticsearch/model/ext/active_record.rb index ffa6cc385..543329191 100644 --- a/elasticsearch-model/lib/elasticsearch/model/ext/active_record.rb +++ b/elasticsearch-model/lib/elasticsearch/model/ext/active_record.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Prevent `MyModel.inspect` failing with `ActiveRecord::ConnectionNotEstablished` # (triggered by elasticsearch-model/lib/elasticsearch/model.rb:79:in `included') # diff --git a/elasticsearch-model/lib/elasticsearch/model/hash_wrapper.rb b/elasticsearch-model/lib/elasticsearch/model/hash_wrapper.rb index 484d292a6..50a5727f4 100644 --- a/elasticsearch-model/lib/elasticsearch/model/hash_wrapper.rb +++ b/elasticsearch-model/lib/elasticsearch/model/hash_wrapper.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model diff --git a/elasticsearch-model/lib/elasticsearch/model/importing.rb b/elasticsearch-model/lib/elasticsearch/model/importing.rb index 764413acf..ed89de433 100644 --- a/elasticsearch-model/lib/elasticsearch/model/importing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/importing.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index 0763f1c2b..c4433ae7e 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model diff --git a/elasticsearch-model/lib/elasticsearch/model/multimodel.rb b/elasticsearch-model/lib/elasticsearch/model/multimodel.rb index 8831d4fd0..2c2306274 100644 --- a/elasticsearch-model/lib/elasticsearch/model/multimodel.rb +++ b/elasticsearch-model/lib/elasticsearch/model/multimodel.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model diff --git a/elasticsearch-model/lib/elasticsearch/model/naming.rb b/elasticsearch-model/lib/elasticsearch/model/naming.rb index cef23e810..961959d61 100644 --- a/elasticsearch-model/lib/elasticsearch/model/naming.rb +++ b/elasticsearch-model/lib/elasticsearch/model/naming.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model diff --git a/elasticsearch-model/lib/elasticsearch/model/proxy.rb b/elasticsearch-model/lib/elasticsearch/model/proxy.rb index a1c89ca95..31ad5c3d3 100644 --- a/elasticsearch-model/lib/elasticsearch/model/proxy.rb +++ b/elasticsearch-model/lib/elasticsearch/model/proxy.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model diff --git a/elasticsearch-model/lib/elasticsearch/model/response.rb b/elasticsearch-model/lib/elasticsearch/model/response.rb index acb25669e..718c06987 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model diff --git a/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb b/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb index c2dd23c67..c5f42ff54 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model module Response diff --git a/elasticsearch-model/lib/elasticsearch/model/response/base.rb b/elasticsearch-model/lib/elasticsearch/model/response/base.rb index 827c52e35..4684704e1 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/base.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/base.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model module Response diff --git a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb index 82f1301a5..60830c75b 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb @@ -1,2 +1,19 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'elasticsearch/model/response/pagination/kaminari' require 'elasticsearch/model/response/pagination/will_paginate' diff --git a/elasticsearch-model/lib/elasticsearch/model/response/pagination/kaminari.rb b/elasticsearch-model/lib/elasticsearch/model/response/pagination/kaminari.rb index 5b1acfd9b..755ed567a 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/pagination/kaminari.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/pagination/kaminari.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model module Response diff --git a/elasticsearch-model/lib/elasticsearch/model/response/pagination/will_paginate.rb b/elasticsearch-model/lib/elasticsearch/model/response/pagination/will_paginate.rb index 7cfc36d0c..88023f12b 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/pagination/will_paginate.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/pagination/will_paginate.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model module Response diff --git a/elasticsearch-model/lib/elasticsearch/model/response/records.rb b/elasticsearch-model/lib/elasticsearch/model/response/records.rb index 4638ca689..0cb832cb2 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/records.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/records.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model module Response diff --git a/elasticsearch-model/lib/elasticsearch/model/response/result.rb b/elasticsearch-model/lib/elasticsearch/model/response/result.rb index 267ca6300..83e07e917 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/result.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/result.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model module Response diff --git a/elasticsearch-model/lib/elasticsearch/model/response/results.rb b/elasticsearch-model/lib/elasticsearch/model/response/results.rb index 006e66a46..b0060f3fe 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/results.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/results.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model module Response diff --git a/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb b/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb index b2809bb12..d02b1dcf5 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model module Response diff --git a/elasticsearch-model/lib/elasticsearch/model/searching.rb b/elasticsearch-model/lib/elasticsearch/model/searching.rb index 604657d5e..1ee103344 100644 --- a/elasticsearch-model/lib/elasticsearch/model/searching.rb +++ b/elasticsearch-model/lib/elasticsearch/model/searching.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model diff --git a/elasticsearch-model/lib/elasticsearch/model/serializing.rb b/elasticsearch-model/lib/elasticsearch/model/serializing.rb index 659a58bb2..43c9afbbf 100644 --- a/elasticsearch-model/lib/elasticsearch/model/serializing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/serializing.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model diff --git a/elasticsearch-model/lib/elasticsearch/model/version.rb b/elasticsearch-model/lib/elasticsearch/model/version.rb index 3b98efb5c..4d6e36d2e 100644 --- a/elasticsearch-model/lib/elasticsearch/model/version.rb +++ b/elasticsearch-model/lib/elasticsearch/model/version.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Model VERSION = "7.0.0" diff --git a/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb index 71fd2e6b5..a02a633d6 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Adapter do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/associations_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/associations_spec.rb index 0cfed6432..f451f5d49 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/associations_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/associations_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe 'Elasticsearch::Model::Adapter::ActiveRecord Associations' do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb index a4d9c05c5..669b28bfc 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Adapter::ActiveRecord do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb index 1a116ec7d..02c2bb59d 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe 'Elasticsearch::Model::Adapter::ActiveRecord Dynamic Index naming' do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb index 52301b01a..39ff5095f 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe 'Elasticsearch::Model::Adapter::ActiveRecord Importing' do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb index 96c65fc5c..08dc869d0 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe 'Elasticsearch::Model::Adapter::ActiveRecord MultiModel' do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb index ea426d3f2..9f4422050 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe 'Elasticsearch::Model::Adapter::ActiveRecord Namespaced Model' do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb index 9427fae48..fdf296261 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe 'Elasticsearch::Model::Adapter::ActiveRecord Pagination' do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb index 647cb6dde..60d642103 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe 'Elasticsearch::Model::Adapter::ActiveRecord Parent-Child' do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb index e5b2072be..6e30c64b7 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe 'Elasticsearch::Model::Adapter::ActiveRecord Serialization' do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb index 6e0cb7d64..02068bc54 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Adapter::ActiveRecord do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/default_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/default_spec.rb index 08064df0f..612e8a784 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/default_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/default_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Adapter::Default do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb index f4aefc740..1f12c0afd 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Adapter::Mongoid, if: test_mongoid? do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb index e4308ab98..952cda672 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe 'Elasticsearch::Model::Adapter::ActiveRecord Multimodel', if: test_mongoid? do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb index 3a01a6635..ac8e73551 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Adapter::Mongoid do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb index 947df1717..c0bf116e6 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Adapter::Multiple do diff --git a/elasticsearch-model/spec/elasticsearch/model/callbacks_spec.rb b/elasticsearch-model/spec/elasticsearch/model/callbacks_spec.rb index d10ce656c..325467bbe 100644 --- a/elasticsearch-model/spec/elasticsearch/model/callbacks_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/callbacks_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Callbacks do diff --git a/elasticsearch-model/spec/elasticsearch/model/client_spec.rb b/elasticsearch-model/spec/elasticsearch/model/client_spec.rb index ea273af73..9fa9a5943 100644 --- a/elasticsearch-model/spec/elasticsearch/model/client_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/client_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Client do diff --git a/elasticsearch-model/spec/elasticsearch/model/hash_wrapper_spec.rb b/elasticsearch-model/spec/elasticsearch/model/hash_wrapper_spec.rb index 53f018726..d300e7bff 100644 --- a/elasticsearch-model/spec/elasticsearch/model/hash_wrapper_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/hash_wrapper_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::HashWrapper, if: Hashie::VERSION >= '3.5.3' do diff --git a/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb index c46f00ba6..f42166a25 100644 --- a/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Importing do diff --git a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb index beaf0dafd..15c8f05e8 100644 --- a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Indexing do diff --git a/elasticsearch-model/spec/elasticsearch/model/module_spec.rb b/elasticsearch-model/spec/elasticsearch/model/module_spec.rb index 3162e2ec5..3f7d682d0 100644 --- a/elasticsearch-model/spec/elasticsearch/model/module_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/module_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model do diff --git a/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb b/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb index 44ee0bd84..b0cfcb1c2 100644 --- a/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Multimodel do diff --git a/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb b/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb index 287fc7d42..93b7ed95e 100644 --- a/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe 'naming inheritance' do diff --git a/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb b/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb index 8e0d8d54d..9e2365bcd 100644 --- a/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe 'naming' do diff --git a/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb b/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb index 6e484f896..f8bed3fbc 100644 --- a/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Proxy do diff --git a/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb index 2d1f8f509..2d3339991 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Response::Aggregations do diff --git a/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb index cfd77d7c7..c4d4499c9 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Response::Base do diff --git a/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb index e8fe1f86b..3824a0d44 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe 'Elasticsearch::Model::Response::Response Kaminari' do diff --git a/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb index 0666b0f28..fa78c0784 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe 'Elasticsearch::Model::Response::Response WillPaginate' do diff --git a/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb index 882c763f3..72b46182b 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Response::Records do diff --git a/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb index 32f96a4d2..9406b773a 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Response::Response do diff --git a/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb index 6f1298a2b..7cf701660 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' require 'active_support/json/encoding' diff --git a/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb index f7149003d..dca4c658c 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Response::Results do diff --git a/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb b/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb index db3ac6dab..a49fa8e28 100644 --- a/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Serializing do diff --git a/elasticsearch-model/spec/elasticsearch/model/searching_spec.rb b/elasticsearch-model/spec/elasticsearch/model/searching_spec.rb index ca95f282b..ced7257d9 100644 --- a/elasticsearch-model/spec/elasticsearch/model/searching_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/searching_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Searching::ClassMethods do diff --git a/elasticsearch-model/spec/elasticsearch/model/serializing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/serializing_spec.rb index ac4a850f3..f18079dcc 100644 --- a/elasticsearch-model/spec/elasticsearch/model/serializing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/serializing_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Model::Serializing do diff --git a/elasticsearch-model/spec/spec_helper.rb b/elasticsearch-model/spec/spec_helper.rb index 86c257969..94e3e5dfd 100644 --- a/elasticsearch-model/spec/spec_helper.rb +++ b/elasticsearch-model/spec/spec_helper.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'pry-nav' require 'kaminari' require 'kaminari/version' diff --git a/elasticsearch-model/spec/support/app.rb b/elasticsearch-model/spec/support/app.rb index c06ca9ca5..d5c2c8ace 100644 --- a/elasticsearch-model/spec/support/app.rb +++ b/elasticsearch-model/spec/support/app.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'active_record' require 'support/app/question' diff --git a/elasticsearch-model/spec/support/app/answer.rb b/elasticsearch-model/spec/support/app/answer.rb index 7de32dc7b..ec94c4b60 100644 --- a/elasticsearch-model/spec/support/app/answer.rb +++ b/elasticsearch-model/spec/support/app/answer.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + class Answer < ActiveRecord::Base include Elasticsearch::Model diff --git a/elasticsearch-model/spec/support/app/article.rb b/elasticsearch-model/spec/support/app/article.rb index ddff706ef..82e863b1c 100644 --- a/elasticsearch-model/spec/support/app/article.rb +++ b/elasticsearch-model/spec/support/app/article.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + class ::Article < ActiveRecord::Base include Elasticsearch::Model include Elasticsearch::Model::Callbacks diff --git a/elasticsearch-model/spec/support/app/article_for_pagination.rb b/elasticsearch-model/spec/support/app/article_for_pagination.rb index 8bea633c1..b785b40eb 100644 --- a/elasticsearch-model/spec/support/app/article_for_pagination.rb +++ b/elasticsearch-model/spec/support/app/article_for_pagination.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + class ::ArticleForPagination < ActiveRecord::Base include Elasticsearch::Model diff --git a/elasticsearch-model/spec/support/app/article_with_custom_serialization.rb b/elasticsearch-model/spec/support/app/article_with_custom_serialization.rb index c03b19ea5..784f0338e 100644 --- a/elasticsearch-model/spec/support/app/article_with_custom_serialization.rb +++ b/elasticsearch-model/spec/support/app/article_with_custom_serialization.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + class ::ArticleWithCustomSerialization < ActiveRecord::Base include Elasticsearch::Model include Elasticsearch::Model::Callbacks diff --git a/elasticsearch-model/spec/support/app/article_with_dynamic_index_name.rb b/elasticsearch-model/spec/support/app/article_with_dynamic_index_name.rb index 7c53d04bf..d72341d96 100644 --- a/elasticsearch-model/spec/support/app/article_with_dynamic_index_name.rb +++ b/elasticsearch-model/spec/support/app/article_with_dynamic_index_name.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + class ::ArticleWithDynamicIndexName < ActiveRecord::Base include Elasticsearch::Model include Elasticsearch::Model::Callbacks diff --git a/elasticsearch-model/spec/support/app/author.rb b/elasticsearch-model/spec/support/app/author.rb index ff1664af7..da8de2070 100644 --- a/elasticsearch-model/spec/support/app/author.rb +++ b/elasticsearch-model/spec/support/app/author.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + class Author < ActiveRecord::Base has_many :authorships diff --git a/elasticsearch-model/spec/support/app/authorship.rb b/elasticsearch-model/spec/support/app/authorship.rb index 70bc2458f..0ef2c1786 100644 --- a/elasticsearch-model/spec/support/app/authorship.rb +++ b/elasticsearch-model/spec/support/app/authorship.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + class Authorship < ActiveRecord::Base belongs_to :author belongs_to :post, touch: true diff --git a/elasticsearch-model/spec/support/app/category.rb b/elasticsearch-model/spec/support/app/category.rb index 751413c0d..66518361b 100644 --- a/elasticsearch-model/spec/support/app/category.rb +++ b/elasticsearch-model/spec/support/app/category.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + class Category < ActiveRecord::Base has_and_belongs_to_many :posts end diff --git a/elasticsearch-model/spec/support/app/comment.rb b/elasticsearch-model/spec/support/app/comment.rb index 49a25832c..c2fcd16eb 100644 --- a/elasticsearch-model/spec/support/app/comment.rb +++ b/elasticsearch-model/spec/support/app/comment.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + class Comment < ActiveRecord::Base belongs_to :post, touch: true end diff --git a/elasticsearch-model/spec/support/app/episode.rb b/elasticsearch-model/spec/support/app/episode.rb index 6cd159c26..66f37a989 100644 --- a/elasticsearch-model/spec/support/app/episode.rb +++ b/elasticsearch-model/spec/support/app/episode.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + class Episode < ActiveRecord::Base include Elasticsearch::Model include Elasticsearch::Model::Callbacks diff --git a/elasticsearch-model/spec/support/app/image.rb b/elasticsearch-model/spec/support/app/image.rb index 8bddcd08b..e605ae942 100644 --- a/elasticsearch-model/spec/support/app/image.rb +++ b/elasticsearch-model/spec/support/app/image.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + class Image include Mongoid::Document include Elasticsearch::Model diff --git a/elasticsearch-model/spec/support/app/import_article.rb b/elasticsearch-model/spec/support/app/import_article.rb index d25580786..4bb228e16 100644 --- a/elasticsearch-model/spec/support/app/import_article.rb +++ b/elasticsearch-model/spec/support/app/import_article.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + class ImportArticle < ActiveRecord::Base include Elasticsearch::Model diff --git a/elasticsearch-model/spec/support/app/mongoid_article.rb b/elasticsearch-model/spec/support/app/mongoid_article.rb index cf3a67a84..2dacd62b6 100644 --- a/elasticsearch-model/spec/support/app/mongoid_article.rb +++ b/elasticsearch-model/spec/support/app/mongoid_article.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + class ::MongoidArticle include Mongoid::Document include Elasticsearch::Model diff --git a/elasticsearch-model/spec/support/app/namespaced_book.rb b/elasticsearch-model/spec/support/app/namespaced_book.rb index 07a500928..32a486ee2 100644 --- a/elasticsearch-model/spec/support/app/namespaced_book.rb +++ b/elasticsearch-model/spec/support/app/namespaced_book.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module MyNamespace class Book < ActiveRecord::Base include Elasticsearch::Model diff --git a/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb b/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb index fd2f4417a..e747bec2e 100644 --- a/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb +++ b/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module ParentChildSearchable INDEX_NAME = 'questions_and_answers'.freeze JOIN = 'join'.freeze diff --git a/elasticsearch-model/spec/support/app/post.rb b/elasticsearch-model/spec/support/app/post.rb index 0cdbba7bb..15aa0e041 100644 --- a/elasticsearch-model/spec/support/app/post.rb +++ b/elasticsearch-model/spec/support/app/post.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + class Post < ActiveRecord::Base include Searchable diff --git a/elasticsearch-model/spec/support/app/question.rb b/elasticsearch-model/spec/support/app/question.rb index f64a97a92..eec7d3b04 100644 --- a/elasticsearch-model/spec/support/app/question.rb +++ b/elasticsearch-model/spec/support/app/question.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + class Question < ActiveRecord::Base include Elasticsearch::Model diff --git a/elasticsearch-model/spec/support/app/searchable.rb b/elasticsearch-model/spec/support/app/searchable.rb index 826a64875..bfa7416a6 100644 --- a/elasticsearch-model/spec/support/app/searchable.rb +++ b/elasticsearch-model/spec/support/app/searchable.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Searchable extend ActiveSupport::Concern diff --git a/elasticsearch-model/spec/support/app/series.rb b/elasticsearch-model/spec/support/app/series.rb index d10a04748..6028650b4 100644 --- a/elasticsearch-model/spec/support/app/series.rb +++ b/elasticsearch-model/spec/support/app/series.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + class Series < ActiveRecord::Base include Elasticsearch::Model include Elasticsearch::Model::Callbacks diff --git a/elasticsearch-persistence/Gemfile b/elasticsearch-persistence/Gemfile index de011df05..72fbae110 100644 --- a/elasticsearch-persistence/Gemfile +++ b/elasticsearch-persistence/Gemfile @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + source 'https://rubygems.org' # Specify your gem's dependencies in elasticsearch-persistence.gemspec diff --git a/elasticsearch-persistence/LICENSE.txt b/elasticsearch-persistence/LICENSE.txt index 489007102..d64569567 100644 --- a/elasticsearch-persistence/LICENSE.txt +++ b/elasticsearch-persistence/LICENSE.txt @@ -1,13 +1,202 @@ -Copyright (c) 2014 Elasticsearch <http://www.elasticsearch.org> -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ - http://www.apache.org/licenses/LICENSE-2.0 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/elasticsearch-persistence/Rakefile b/elasticsearch-persistence/Rakefile index 0942fbdcf..a49e40051 100644 --- a/elasticsearch-persistence/Rakefile +++ b/elasticsearch-persistence/Rakefile @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require "bundler/gem_tasks" desc "Run unit tests" diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index 34a99b690..3b36ebd07 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) diff --git a/elasticsearch-persistence/examples/notes/Gemfile b/elasticsearch-persistence/examples/notes/Gemfile index c70894e27..1f98fc7f2 100644 --- a/elasticsearch-persistence/examples/notes/Gemfile +++ b/elasticsearch-persistence/examples/notes/Gemfile @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + source 'https://rubygems.org' gem 'rake' diff --git a/elasticsearch-persistence/examples/notes/application.rb b/elasticsearch-persistence/examples/notes/application.rb index ed6af1b8a..27f796abb 100644 --- a/elasticsearch-persistence/examples/notes/application.rb +++ b/elasticsearch-persistence/examples/notes/application.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + $LOAD_PATH.unshift File.expand_path('../../../lib/', __FILE__) require 'sinatra/base' diff --git a/elasticsearch-persistence/examples/notes/config.ru b/elasticsearch-persistence/examples/notes/config.ru index 98f8403ad..8dced717b 100644 --- a/elasticsearch-persistence/examples/notes/config.ru +++ b/elasticsearch-persistence/examples/notes/config.ru @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + #\ --port 3000 --server thin require File.expand_path('../application', __FILE__) diff --git a/elasticsearch-persistence/examples/notes/test.rb b/elasticsearch-persistence/examples/notes/test.rb index cb9528747..c99fff3f4 100644 --- a/elasticsearch-persistence/examples/notes/test.rb +++ b/elasticsearch-persistence/examples/notes/test.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + ENV['RACK_ENV'] = 'test' at_exit { Elasticsearch::Test::IntegrationTestCase.__run_at_exit_hooks } if ENV['SERVER'] diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence.rb b/elasticsearch-persistence/lib/elasticsearch/persistence.rb index ce0d25b60..cf4337a25 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'hashie/mash' require 'elasticsearch' diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index 10e156ea4..e330ffb5c 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'elasticsearch/persistence/repository/dsl' require 'elasticsearch/persistence/repository/find' require 'elasticsearch/persistence/repository/store' diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb index f997e63ce..7373f9acd 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Persistence module Repository diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb index cbd264ae2..e2714ce4d 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Persistence module Repository diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb index 9de0059f3..0b37433b6 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Persistence module Repository diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb index c3893d74f..4fad6a20a 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Persistence module Repository diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb index 067a7daad..04ec367aa 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Persistence module Repository diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb index 6571e1078..96f68637f 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Persistence module Repository diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb index 393bf1343..2953d86a6 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Persistence VERSION = '7.0.0' diff --git a/elasticsearch-persistence/spec/repository/find_spec.rb b/elasticsearch-persistence/spec/repository/find_spec.rb index b630d45a2..ab00e8d8b 100644 --- a/elasticsearch-persistence/spec/repository/find_spec.rb +++ b/elasticsearch-persistence/spec/repository/find_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Persistence::Repository::Find do diff --git a/elasticsearch-persistence/spec/repository/response/results_spec.rb b/elasticsearch-persistence/spec/repository/response/results_spec.rb index f41f4b81d..981773515 100644 --- a/elasticsearch-persistence/spec/repository/response/results_spec.rb +++ b/elasticsearch-persistence/spec/repository/response/results_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Persistence::Repository::Response::Results do diff --git a/elasticsearch-persistence/spec/repository/search_spec.rb b/elasticsearch-persistence/spec/repository/search_spec.rb index 939f09731..6986830c2 100644 --- a/elasticsearch-persistence/spec/repository/search_spec.rb +++ b/elasticsearch-persistence/spec/repository/search_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Persistence::Repository::Search do diff --git a/elasticsearch-persistence/spec/repository/serialize_spec.rb b/elasticsearch-persistence/spec/repository/serialize_spec.rb index bf0d7175e..7d6dfdfdc 100644 --- a/elasticsearch-persistence/spec/repository/serialize_spec.rb +++ b/elasticsearch-persistence/spec/repository/serialize_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Persistence::Repository::Serialize do diff --git a/elasticsearch-persistence/spec/repository/store_spec.rb b/elasticsearch-persistence/spec/repository/store_spec.rb index c42c811d2..bc9b7ba61 100644 --- a/elasticsearch-persistence/spec/repository/store_spec.rb +++ b/elasticsearch-persistence/spec/repository/store_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Persistence::Repository::Store do diff --git a/elasticsearch-persistence/spec/repository_spec.rb b/elasticsearch-persistence/spec/repository_spec.rb index bf0968b32..06d32ef0a 100644 --- a/elasticsearch-persistence/spec/repository_spec.rb +++ b/elasticsearch-persistence/spec/repository_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe Elasticsearch::Persistence::Repository do diff --git a/elasticsearch-persistence/spec/spec_helper.rb b/elasticsearch-persistence/spec/spec_helper.rb index 9160c8a41..2caf7bcee 100644 --- a/elasticsearch-persistence/spec/spec_helper.rb +++ b/elasticsearch-persistence/spec/spec_helper.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'pry-nav' require 'elasticsearch/persistence' diff --git a/elasticsearch-rails/Gemfile b/elasticsearch-rails/Gemfile index 407f94341..ac2583628 100644 --- a/elasticsearch-rails/Gemfile +++ b/elasticsearch-rails/Gemfile @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + source 'https://rubygems.org' # Specify your gem's dependencies in elasticsearch-rails.gemspec diff --git a/elasticsearch-rails/LICENSE.txt b/elasticsearch-rails/LICENSE.txt index 7dc94b3e5..d64569567 100644 --- a/elasticsearch-rails/LICENSE.txt +++ b/elasticsearch-rails/LICENSE.txt @@ -1,13 +1,202 @@ -Copyright (c) 2014 Elasticsearch -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ - http://www.apache.org/licenses/LICENSE-2.0 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/elasticsearch-rails/Rakefile b/elasticsearch-rails/Rakefile index 0d87185d4..36cd53074 100644 --- a/elasticsearch-rails/Rakefile +++ b/elasticsearch-rails/Rakefile @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require "bundler/gem_tasks" desc "Run unit tests" diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index e4ad351ba..d6026ae2f 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) diff --git a/elasticsearch-rails/lib/elasticsearch/rails.rb b/elasticsearch-rails/lib/elasticsearch/rails.rb index f425f7276..a2f85b1e0 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require "elasticsearch/rails/version" module Elasticsearch diff --git a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation.rb b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation.rb index 081791ab5..61a23093d 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'elasticsearch/rails/instrumentation/railtie' require 'elasticsearch/rails/instrumentation/publishers' diff --git a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/controller_runtime.rb b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/controller_runtime.rb index 461387c80..09ca20ca7 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/controller_runtime.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/controller_runtime.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'active_support/core_ext/module/attr_internal' module Elasticsearch diff --git a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/log_subscriber.rb b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/log_subscriber.rb index c02bc0704..cf313a8c2 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/log_subscriber.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/log_subscriber.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Rails module Instrumentation diff --git a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/publishers.rb b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/publishers.rb index e054d5371..ec4e4e743 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/publishers.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/publishers.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Rails module Instrumentation diff --git a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/railtie.rb b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/railtie.rb index 6aeb6a866..adb1e494a 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/railtie.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/railtie.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Rails module Instrumentation diff --git a/elasticsearch-rails/lib/elasticsearch/rails/lograge.rb b/elasticsearch-rails/lib/elasticsearch/rails/lograge.rb index aa0f4f1f3..562756e81 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/lograge.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/lograge.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Rails module Lograge diff --git a/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb b/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb index 9b0fa2039..87fef3daa 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # A collection of Rake tasks to facilitate importing data from your models into Elasticsearch. # # Add this e.g. into the `lib/tasks/elasticsearch.rake` file in your Rails application: diff --git a/elasticsearch-rails/lib/elasticsearch/rails/version.rb b/elasticsearch-rails/lib/elasticsearch/rails/version.rb index eaf50e581..6d5c428e3 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/version.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/version.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Elasticsearch module Rails VERSION = "7.0.0" diff --git a/elasticsearch-rails/lib/rails/templates/01-basic.rb b/elasticsearch-rails/lib/rails/templates/01-basic.rb index 5e9637544..2de5f1436 100644 --- a/elasticsearch-rails/lib/rails/templates/01-basic.rb +++ b/elasticsearch-rails/lib/rails/templates/01-basic.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # ===================================================================================================== # Template for generating a no-frills Rails application with support for Elasticsearch full-text search # ===================================================================================================== diff --git a/elasticsearch-rails/lib/rails/templates/02-pretty.rb b/elasticsearch-rails/lib/rails/templates/02-pretty.rb index 2c3318304..4a70e5f39 100644 --- a/elasticsearch-rails/lib/rails/templates/02-pretty.rb +++ b/elasticsearch-rails/lib/rails/templates/02-pretty.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # $ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/02-pretty.rb unless File.read('README.md').include? '## [1] Basic' diff --git a/elasticsearch-rails/lib/rails/templates/03-expert.rb b/elasticsearch-rails/lib/rails/templates/03-expert.rb index f7f9040c7..176da41df 100644 --- a/elasticsearch-rails/lib/rails/templates/03-expert.rb +++ b/elasticsearch-rails/lib/rails/templates/03-expert.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # $ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/03-expert.rb unless File.read('README.md').include? '## [2] Pretty' diff --git a/elasticsearch-rails/lib/rails/templates/04-dsl.rb b/elasticsearch-rails/lib/rails/templates/04-dsl.rb index 163160b34..1325d57c1 100644 --- a/elasticsearch-rails/lib/rails/templates/04-dsl.rb +++ b/elasticsearch-rails/lib/rails/templates/04-dsl.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # $ rails new searchapp --skip --skip-bundle --template https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/04-dsl.rb unless File.read('README.md').include? '## [3] Expert' diff --git a/elasticsearch-rails/lib/rails/templates/05-settings-files.rb b/elasticsearch-rails/lib/rails/templates/05-settings-files.rb index 4d09f4847..5c5e4bbeb 100644 --- a/elasticsearch-rails/lib/rails/templates/05-settings-files.rb +++ b/elasticsearch-rails/lib/rails/templates/05-settings-files.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # $ rails new searchapp --skip --skip-bundle --template https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/05-settings-files.rb # (See: 01-basic.rb, 02-pretty.rb, 03-expert.rb, 04-dsl.rb) diff --git a/elasticsearch-rails/lib/rails/templates/indexer.rb b/elasticsearch-rails/lib/rails/templates/indexer.rb index 407c06109..912b960ca 100644 --- a/elasticsearch-rails/lib/rails/templates/indexer.rb +++ b/elasticsearch-rails/lib/rails/templates/indexer.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Indexer class for <http://sidekiq.org> # # Run me with: diff --git a/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb b/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb index d4c9cac05..a73119e66 100644 --- a/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb +++ b/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'test_helper' require 'sidekiq/api' diff --git a/elasticsearch-rails/lib/rails/templates/search_controller_test.rb b/elasticsearch-rails/lib/rails/templates/search_controller_test.rb index ca5ca29ba..d195772c6 100644 --- a/elasticsearch-rails/lib/rails/templates/search_controller_test.rb +++ b/elasticsearch-rails/lib/rails/templates/search_controller_test.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'test_helper' class SearchControllerTest < ActionController::TestCase diff --git a/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb b/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb index 9ba6a0bb0..fb3ff03d2 100644 --- a/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb +++ b/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Searchable extend ActiveSupport::Concern diff --git a/elasticsearch-rails/lib/rails/templates/searchable.rb b/elasticsearch-rails/lib/rails/templates/searchable.rb index 45fbf75d4..d101826ef 100644 --- a/elasticsearch-rails/lib/rails/templates/searchable.rb +++ b/elasticsearch-rails/lib/rails/templates/searchable.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + module Searchable extend ActiveSupport::Concern diff --git a/elasticsearch-rails/lib/rails/templates/seeds.rb b/elasticsearch-rails/lib/rails/templates/seeds.rb index b9e2ecbd8..74bf4a379 100644 --- a/elasticsearch-rails/lib/rails/templates/seeds.rb +++ b/elasticsearch-rails/lib/rails/templates/seeds.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'zlib' require 'yaml' diff --git a/elasticsearch-rails/spec/instrumentation_spec.rb b/elasticsearch-rails/spec/instrumentation_spec.rb index 2327800d1..ff70d4266 100644 --- a/elasticsearch-rails/spec/instrumentation_spec.rb +++ b/elasticsearch-rails/spec/instrumentation_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'spec_helper' describe 'ActiveSupport::Instrumentation integration' do diff --git a/elasticsearch-rails/spec/lograge_spec.rb b/elasticsearch-rails/spec/lograge_spec.rb index 3192d4089..4503b538e 100644 --- a/elasticsearch-rails/spec/lograge_spec.rb +++ b/elasticsearch-rails/spec/lograge_spec.rb @@ -1,3 +1,20 @@ + +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. require 'spec_helper' require 'action_pack' require 'lograge' diff --git a/elasticsearch-rails/spec/spec_helper.rb b/elasticsearch-rails/spec/spec_helper.rb index b12e8468e..9c2522eb9 100644 --- a/elasticsearch-rails/spec/spec_helper.rb +++ b/elasticsearch-rails/spec/spec_helper.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + require 'pry-nav' require 'active_record' require 'elasticsearch/model' From d7237b115619f96a3a70252ee4aa3bef2e57429b Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Thu, 29 Nov 2018 16:32:56 +0100 Subject: [PATCH 430/582] [CI] Update elasticsearch snapshot version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a7b121b10..5c2cb0d32 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,7 +43,7 @@ matrix: env: global: - - ELASTICSEARCH_VERSION=7.0.0-alpha1 + - ELASTICSEARCH_VERSION=7.0.0 - TEST_CLUSTER_PORT=9250 - QUIET=true From f7b29e4e8e040ca2d16ee178462d2e721d47cca5 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Thu, 29 Nov 2018 16:35:06 +0100 Subject: [PATCH 431/582] [CI] Update elasticsearch snapshot version also in travis before script ENV var --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5c2cb0d32..52b8e4bec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,7 +48,7 @@ env: - QUIET=true before_install: - - ELASTICSEARCH_VERSION=7.0.0-alpha1 TEST_CLUSTER_PORT=9250 source ./travis_before_script.sh + - ELASTICSEARCH_VERSION=7.0.0 TEST_CLUSTER_PORT=9250 source ./travis_before_script.sh - gem update --system -q - gem update bundler -q - gem --version From 6baae485f213a6bffa812d3e98fa3a6663644ede Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Thu, 29 Nov 2018 21:56:38 +0100 Subject: [PATCH 432/582] [CI] Elasticsearch version should be ENV variable in each travis script reference --- travis_before_script.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/travis_before_script.sh b/travis_before_script.sh index fdca40263..b949c53e6 100644 --- a/travis_before_script.sh +++ b/travis_before_script.sh @@ -12,4 +12,4 @@ echo 'Unzipping file' unzip -q /tmp/elasticsearch.zip echo "Starting elasticsearch on port ${TEST_CLUSTER_PORT}" -${PWD}/elasticsearch-7.0.0-alpha1-SNAPSHOT/bin/elasticsearch -E http.port=${TEST_CLUSTER_PORT} &> /dev/null & +${PWD}/elasticsearch-${ELASTICSEARCH_VERSION}-SNAPSHOT/bin/elasticsearch -E http.port=${TEST_CLUSTER_PORT} &> /dev/null & From ed9f449abb95f4a6791d60633c071c99bd7f23b1 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Fri, 14 Dec 2018 17:18:38 +0100 Subject: [PATCH 433/582] [STORE] Handle total hits as an object in search response --- .../persistence/repository/response/results.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb index 0b37433b6..4eae5b913 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb @@ -38,6 +38,10 @@ class Results # TOTAL = 'total'.freeze + # The key for accessing the value field in an Elasticsearch query response when 'total' is an object. + # + VALUE = 'value'.freeze + # The key for accessing the maximum score in an Elasticsearch query response. # MAX_SCORE = 'max_score'.freeze @@ -63,7 +67,11 @@ def respond_to?(method_name, include_private = false) # The number of total hits for a query # def total - raw_response[HITS][TOTAL] + if raw_response[HITS][TOTAL].respond_to?(:keys) + raw_response[HITS][TOTAL][VALUE] + else + raw_response[HITS][TOTAL] + end end # The maximum score for a query From 9c40f630e1b549f0b7889fe33dcd826b485af6fc Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Fri, 14 Dec 2018 17:18:50 +0100 Subject: [PATCH 434/582] [MODEL] Handle total hits as an object in search response --- .../lib/elasticsearch/model/response/base.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/base.rb b/elasticsearch-model/lib/elasticsearch/model/response/base.rb index 4684704e1..2afa1193e 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/base.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/base.rb @@ -48,7 +48,11 @@ def records # Returns the total number of hits # def total - response.response['hits']['total'] + if response.response['hits']['total'].respond_to?(:keys) + response.response['hits']['total']['value'] + else + response.response['hits']['total'] + end end # Returns the max_score From 266b59c5e296abfc7a1a393fd130deb57d88e9ed Mon Sep 17 00:00:00 2001 From: jonbwhite <jonbwhite@users.noreply.github.com> Date: Mon, 17 Dec 2018 05:33:48 -0800 Subject: [PATCH 435/582] Only execute update if document attributes is not empty (#862) * Only execute update if document attributes is not empty * Adds test for skipping empty elasticsearch update --- .../lib/elasticsearch/model/indexing.rb | 2 +- .../spec/elasticsearch/model/indexing_spec.rb | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index c4433ae7e..e3f598b81 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -426,7 +426,7 @@ def update_document(options={}) type: document_type, id: self.id, body: { doc: attributes } }.merge(options) - ) + ) unless attributes.empty? else index_document(options) end diff --git a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb index 15c8f05e8..cfb078e17 100644 --- a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb @@ -440,10 +440,10 @@ def changes context 'when changes are present' do before do - expect(instance).to receive(:client).and_return(client) - expect(instance).to receive(:index_name).and_return('foo') - expect(instance).to receive(:document_type).and_return('bar') - expect(instance).to receive(:id).and_return('1') + allow(instance).to receive(:client).and_return(client) + allow(instance).to receive(:index_name).and_return('foo') + allow(instance).to receive(:document_type).and_return('bar') + allow(instance).to receive(:id).and_return('1') end context 'when the changes are included in the as_indexed_json representation' do @@ -474,6 +474,21 @@ def changes end end + context 'when none of the changes are included in the as_indexed_json representation' do + + let(:instance) do + DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new + end + + before do + instance.instance_variable_set(:@__changed_model_attributes, {'bar' => 'D' }) + end + + it 'does not update the document' do + expect(instance.update_document).to_not be(true) + end + end + context 'when there are partial updates' do let(:instance) do From 00596345ec1c22d177603dcdde7d6422a7cf8de7 Mon Sep 17 00:00:00 2001 From: Emily S <emily.s@elastic.co> Date: Mon, 4 Mar 2019 09:17:20 +0100 Subject: [PATCH 436/582] [MODEL] Use logger to log index not found message (#868) * [MODEL] Use logger to log index not found message * [CI] Remove Gemfile.lock on Travis * [CI] Update Travis matrix --- .travis.yml | 16 +++--- .../lib/elasticsearch/model/indexing.rb | 6 ++- .../spec/elasticsearch/model/indexing_spec.rb | 50 +++++++++++++++++-- 3 files changed, 56 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 52b8e4bec..451da6cf3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,22 +25,18 @@ matrix: jdk: oraclejdk8 env: RAILS_VERSIONS=3.0 - - rvm: 2.3 + - rvm: 2.3.8 jdk: oraclejdk8 env: RAILS_VERSIONS=5.0 - - rvm: 2.4 + - rvm: 2.6.1 jdk: oraclejdk8 - env: RAILS_VERSIONS=5.0 + env: RAILS_VERSIONS=4.0,5.0 - - rvm: jruby-9.1 + - rvm: jruby-9.2.5.0 jdk: oraclejdk8 env: RAILS_VERSIONS=5.0 - - rvm: 2.5 - jdk: oraclejdk8 - env: RAILS_VERSIONS=4.0,5.0 - env: global: - ELASTICSEARCH_VERSION=7.0.0 @@ -49,8 +45,8 @@ env: before_install: - ELASTICSEARCH_VERSION=7.0.0 TEST_CLUSTER_PORT=9250 source ./travis_before_script.sh - - gem update --system -q - - gem update bundler -q + - gem update --system + - gem update bundler - gem --version - bundle version diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index e3f598b81..da3c7f79b 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -286,7 +286,8 @@ def delete_index!(options={}) self.client.indices.delete index: target_index rescue Exception => e if e.class.to_s =~ /NotFound/ && options[:force] - STDERR.puts "[!!!] Index does not exist (#{e.class})" + client.transport.logger.debug("[!!!] Index does not exist (#{e.class})") if client.transport.logger + nil else raise e end @@ -312,7 +313,8 @@ def refresh_index!(options={}) self.client.indices.refresh index: target_index rescue Exception => e if e.class.to_s =~ /NotFound/ && options[:force] - STDERR.puts "[!!!] Index does not exist (#{e.class})" + client.transport.logger.debug("[!!!] Index does not exist (#{e.class})") if client.transport.logger + nil else raise e end diff --git a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb index cfb078e17..f625b538e 100644 --- a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb @@ -629,7 +629,7 @@ class ::DummyIndexingModelForRecreate context 'when the index is not found' do let(:client) do - double('client', indices: indices) + double('client', indices: indices, transport: double('transport', { logger: nil })) end let(:indices) do @@ -639,7 +639,7 @@ class ::DummyIndexingModelForRecreate end before do - expect(DummyIndexingModelForRecreate).to receive(:client).and_return(client) + expect(DummyIndexingModelForRecreate).to receive(:client).at_most(3).times.and_return(client) end context 'when the force option is true' do @@ -647,6 +647,26 @@ class ::DummyIndexingModelForRecreate it 'deletes the index without raising an exception' do expect(DummyIndexingModelForRecreate.delete_index!(force: true)).to be_nil end + + context 'when the client has a logger' do + + let(:logger) do + Logger.new(STDOUT).tap { |l| l.level = Logger::DEBUG } + end + + let(:client) do + double('client', indices: indices, transport: double('transport', { logger: logger })) + end + + it 'deletes the index without raising an exception' do + expect(DummyIndexingModelForRecreate.delete_index!(force: true)).to be_nil + end + + it 'logs the message that the index is not found' do + expect(logger).to receive(:debug) + expect(DummyIndexingModelForRecreate.delete_index!(force: true)).to be_nil + end + end end context 'when the force option is not provided' do @@ -816,6 +836,8 @@ class ::DummyIndexingModelForCreate expect(DummyIndexingModelForCreate.create_index!(index: 'custom-foo')) end end + + context 'when the logging level is debug' end describe '#refresh_index!' do @@ -841,7 +863,7 @@ class ::DummyIndexingModelForRefresh end let(:client) do - double('client', indices: indices) + double('client', indices: indices, transport: double('transport', { logger: nil })) end let(:indices) do @@ -849,7 +871,7 @@ class ::DummyIndexingModelForRefresh end before do - expect(DummyIndexingModelForRefresh).to receive(:client).and_return(client) + expect(DummyIndexingModelForRefresh).to receive(:client).at_most(3).times.and_return(client) end context 'when the force option is true' do @@ -863,6 +885,26 @@ class ::DummyIndexingModelForRefresh it 'does not raise an exception' do expect(DummyIndexingModelForRefresh.refresh_index!(force: true)).to be_nil end + + context 'when the client has a logger' do + + let(:logger) do + Logger.new(STDOUT).tap { |l| l.level = Logger::DEBUG } + end + + let(:client) do + double('client', indices: indices, transport: double('transport', { logger: logger })) + end + + it 'does not raise an exception' do + expect(DummyIndexingModelForRefresh.refresh_index!(force: true)).to be_nil + end + + it 'logs the message that the index is not found' do + expect(logger).to receive(:debug) + expect(DummyIndexingModelForRefresh.refresh_index!(force: true)).to be_nil + end + end end context 'when the operation raises another type of exception' do From fd10aa74b65793b9a141adc9ba8b0455c77e0524 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Tue, 26 Mar 2019 16:32:05 +0100 Subject: [PATCH 437/582] Remove bundler requirement from Gemfile --- Gemfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Gemfile b/Gemfile index 005d12640..466fb946e 100644 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,6 @@ source 'https://rubygems.org' -gem "bundler", "~> 1" gem "rake", "~> 11.1" gem 'elasticsearch-extensions' From 3e100395cb6be25d4cc5f032ce219ad8e7eb2411 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Tue, 26 Mar 2019 16:58:08 +0100 Subject: [PATCH 438/582] Remove bundler version requirement in gemspec files --- elasticsearch-model/elasticsearch-model.gemspec | 2 +- elasticsearch-persistence/elasticsearch-persistence.gemspec | 2 +- elasticsearch-rails/elasticsearch-rails.gemspec | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index 33bc169e7..fdfeac1fa 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -44,7 +44,7 @@ Gem::Specification.new do |s| s.add_dependency "activesupport", '> 3' s.add_dependency "hashie" - s.add_development_dependency "bundler", "~> 1.3" + s.add_development_dependency "bundler" s.add_development_dependency "rake", "~> 11.1" s.add_development_dependency "elasticsearch-extensions" diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index 3b36ebd07..f20190052 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -46,7 +46,7 @@ Gem::Specification.new do |s| s.add_dependency "activemodel", '> 4' s.add_dependency "hashie" - s.add_development_dependency "bundler", "~> 1.5" + s.add_development_dependency "bundler" s.add_development_dependency "rake", "~> 11.1" s.add_development_dependency "oj" unless defined?(JRUBY_VERSION) diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index d6026ae2f..0911b111c 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -40,7 +40,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 1.9.3" - s.add_development_dependency "bundler", "~> 1.3" + s.add_development_dependency "bundler" s.add_development_dependency "rake", "~> 11.1" s.add_development_dependency "elasticsearch-extensions" From e5d8bf926718df0177952496ae17096fffdb5c74 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Wed, 27 Mar 2019 17:25:03 +0100 Subject: [PATCH 439/582] Fix Travis before script --- .travis.yml | 4 ++-- travis_before_script.sh | 13 ++----------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 451da6cf3..4a37abea8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,12 +39,12 @@ matrix: env: global: - - ELASTICSEARCH_VERSION=7.0.0 + - ELASTICSEARCH_VERSION=7.0.0-beta1 - TEST_CLUSTER_PORT=9250 - QUIET=true before_install: - - ELASTICSEARCH_VERSION=7.0.0 TEST_CLUSTER_PORT=9250 source ./travis_before_script.sh + - TEST_CLUSTER_PORT=9250 source ./travis_before_script.sh - gem update --system - gem update bundler - gem --version diff --git a/travis_before_script.sh b/travis_before_script.sh index b949c53e6..014864175 100644 --- a/travis_before_script.sh +++ b/travis_before_script.sh @@ -1,15 +1,6 @@ #!/bin/bash -metadata_url="https://artifacts-api.elastic.co/v1/branches/master/builds/latest/projects/elasticsearch/packages/elasticsearch-${ELASTICSEARCH_VERSION}-SNAPSHOT.zip/file" -echo "Getting snapshot location from $metadata_url" - -url=$(curl -v $metadata_url 2>&1 | grep -Pio 'location: \K(.*)' | tr -d '\r') - -echo "Downloading Elasticsearch from $url" -curl $url -o /tmp/elasticsearch.zip - -echo 'Unzipping file' -unzip -q /tmp/elasticsearch.zip +curl https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ELASTICSEARCH_VERSION}-linux-x86_64.tar.gz | tar xz -C /tmp echo "Starting elasticsearch on port ${TEST_CLUSTER_PORT}" -${PWD}/elasticsearch-${ELASTICSEARCH_VERSION}-SNAPSHOT/bin/elasticsearch -E http.port=${TEST_CLUSTER_PORT} &> /dev/null & +/tmp/elasticsearch-${ELASTICSEARCH_VERSION}/bin/elasticsearch -E http.port=${TEST_CLUSTER_PORT} &> /dev/null & From cff4848908a2ed67ecd6fe5e1f03126cbc9014f7 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Mon, 29 Apr 2019 18:52:13 +0900 Subject: [PATCH 440/582] [MODEL] Test against Rails 6.0.rc1 --- .travis.yml | 2 +- elasticsearch-model/.gitignore | 4 +-- elasticsearch-model/Rakefile | 2 +- elasticsearch-model/gemfiles/3.0.gemfile | 2 +- elasticsearch-model/gemfiles/4.0.gemfile | 2 +- elasticsearch-model/gemfiles/6.0.gemfile | 35 ++++++++++++++++++++++++ 6 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 elasticsearch-model/gemfiles/6.0.gemfile diff --git a/.travis.yml b/.travis.yml index 4a37abea8..17c17c020 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,7 @@ matrix: - rvm: 2.6.1 jdk: oraclejdk8 - env: RAILS_VERSIONS=4.0,5.0 + env: RAILS_VERSIONS=4.0,5.0,6.0 - rvm: jruby-9.2.5.0 jdk: oraclejdk8 diff --git a/elasticsearch-model/.gitignore b/elasticsearch-model/.gitignore index 8a8ab4613..52f8d0334 100644 --- a/elasticsearch-model/.gitignore +++ b/elasticsearch-model/.gitignore @@ -16,6 +16,4 @@ test/tmp test/version_tmp tmp -gemfiles/3.0.gemfile.lock -gemfiles/4.0.gemfile.lock -gemfiles/5.0.gemfile.lock +gemfiles/*.gemfile.lock diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index 06af970f7..76291b92a 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -24,7 +24,7 @@ task :test => 'test:unit' if RUBY_VERSION < '2.3' GEMFILES = ['3.0.gemfile', '4.0.gemfile', '5.0.gemfile'] else - GEMFILES = ['4.0.gemfile', '5.0.gemfile'] + GEMFILES = ['4.0.gemfile', '5.0.gemfile', '6.0.gemfile'] end namespace :bundle do diff --git a/elasticsearch-model/gemfiles/3.0.gemfile b/elasticsearch-model/gemfiles/3.0.gemfile index 53fd4c66a..1641023d7 100644 --- a/elasticsearch-model/gemfiles/3.0.gemfile +++ b/elasticsearch-model/gemfiles/3.0.gemfile @@ -27,7 +27,7 @@ gemspec path: '../' gem 'activemodel', '>= 3.0' gem 'activerecord', '~> 3.2' gem 'mongoid', '>= 3.0' -gem 'sqlite3' unless defined?(JRUBY_VERSION) +gem 'sqlite3', '> 1.3', '< 1.4' unless defined?(JRUBY_VERSION) group :development, :testing do gem 'rspec' diff --git a/elasticsearch-model/gemfiles/4.0.gemfile b/elasticsearch-model/gemfiles/4.0.gemfile index 8c53765ac..e7244994a 100644 --- a/elasticsearch-model/gemfiles/4.0.gemfile +++ b/elasticsearch-model/gemfiles/4.0.gemfile @@ -26,7 +26,7 @@ gemspec path: '../' gem 'activemodel', '~> 4' gem 'activerecord', '~> 4' -gem 'sqlite3' unless defined?(JRUBY_VERSION) +gem 'sqlite3', '> 1.3', '< 1.4' unless defined?(JRUBY_VERSION) gem 'mongoid', '~> 5' group :development, :testing do diff --git a/elasticsearch-model/gemfiles/6.0.gemfile b/elasticsearch-model/gemfiles/6.0.gemfile new file mode 100644 index 000000000..035a754cc --- /dev/null +++ b/elasticsearch-model/gemfiles/6.0.gemfile @@ -0,0 +1,35 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Usage: +# +# $ BUNDLE_GEMFILE=./gemfiles/6.0.gemfile bundle install +# $ BUNDLE_GEMFILE=./gemfiles/6.0.gemfile bundle exec rake test:integration + +source 'https://rubygems.org' + +gemspec path: '../' + +gem 'activemodel', '6.0.0.rc1' +gem 'activerecord', '6.0.0.rc1' +gem 'sqlite3' unless defined?(JRUBY_VERSION) +gem 'mongoid', '~> 6' + +group :development, :testing do + gem 'rspec' + gem 'pry-nav' +end From a0f14d96fab54b64cb3b8cbacd6476aba2dfa78d Mon Sep 17 00:00:00 2001 From: Emily S <emily.stolfo@elastic.co> Date: Tue, 30 Apr 2019 13:11:53 +0900 Subject: [PATCH 441/582] 7.0 support (#875) * [MODEL] Updates for 7.0 * [STORE] Updates for 7.0 * [MODEL] Test against Rails 6.0.beta3 * Delete all gemfile.lock files in bundle clean task * [STORE] Account for no types being accepted for mappings in 7.0 * Update travis config * Update script to download and start elasticsearch * Use wait_for_green script before running tests * Handle downloading and installing ES 6.7.1 nad 7.0.0 on Travis * Update travis script * Leave testing against ES 6.x to the 6.x branch * [MODEL] Add test for handling different total hits format in search response * Correct merge conflicts * Update dependency versions for 7.0 release of client --- .travis.yml | 10 +- Rakefile | 59 ++- elasticsearch-model/.gitignore | 4 +- elasticsearch-model/gemfiles/6.0.gemfile | 1 + .../elasticsearch/model/adapters/multiple.rb | 16 +- .../lib/elasticsearch/model/indexing.rb | 20 +- .../lib/elasticsearch/model/naming.rb | 5 +- .../adapters/active_record/basic_spec.rb | 490 ++++++++++-------- .../active_record/namespaced_model_spec.rb | 2 +- .../active_record/parent_child_spec.rb | 2 +- .../spec/elasticsearch/model/indexing_spec.rb | 103 +++- .../model/naming_inheritance_spec.rb | 6 +- .../spec/elasticsearch/model/naming_spec.rb | 10 +- .../response/pagination/kaminari_spec.rb | 91 +++- elasticsearch-model/spec/support/app.rb | 1 + .../spec/support/app/article_no_type.rb | 37 ++ .../app/parent_and_child_searchable.rb | 8 +- .../elasticsearch-persistence.gemspec | 2 +- .../elasticsearch/persistence/repository.rb | 13 +- .../persistence/repository/dsl.rb | 2 +- .../spec/repository/store_spec.rb | 2 +- .../spec/repository_spec.rb | 48 +- elasticsearch-persistence/spec/spec_helper.rb | 9 + travis_before_script.sh | 11 +- 24 files changed, 620 insertions(+), 332 deletions(-) create mode 100644 elasticsearch-model/spec/support/app/article_no_type.rb diff --git a/.travis.yml b/.travis.yml index 17c17c020..d164a0b0c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,10 +14,7 @@ services: branches: only: - master - - travis - - 5.x - 6.x - - 2.x matrix: include: @@ -29,7 +26,7 @@ matrix: jdk: oraclejdk8 env: RAILS_VERSIONS=5.0 - - rvm: 2.6.1 + - rvm: 2.6.2 jdk: oraclejdk8 env: RAILS_VERSIONS=4.0,5.0,6.0 @@ -39,12 +36,13 @@ matrix: env: global: - - ELASTICSEARCH_VERSION=7.0.0-beta1 + - ELASTICSEARCH_VERSION=7.0.0 + - TEST_ES_SERVER=http://localhost:9250 - TEST_CLUSTER_PORT=9250 - QUIET=true before_install: - - TEST_CLUSTER_PORT=9250 source ./travis_before_script.sh + - source ./travis_before_script.sh - gem update --system - gem update bundler - gem --version diff --git a/Rakefile b/Rakefile index 99e4e2c8d..07dbe81ee 100644 --- a/Rakefile +++ b/Rakefile @@ -22,6 +22,33 @@ subprojects << 'elasticsearch-model' unless defined?(JRUBY_VERSION) __current__ = Pathname( File.expand_path('..', __FILE__) ) +def admin_client + $admin_client ||= begin + transport_options = {} + test_suite = ENV['TEST_SUITE'].freeze + + if hosts = ENV['TEST_ES_SERVER'] || ENV['ELASTICSEARCH_HOSTS'] + split_hosts = hosts.split(',').map do |host| + /(http\:\/\/)?(\S+)/.match(host)[2] + end + + host, port = split_hosts.first.split(':') + end + + if test_suite == 'security' + transport_options.merge!(:ssl => { verify: false, + ca_path: CERT_DIR }) + + password = ENV['ELASTIC_PASSWORD'] + user = ENV['ELASTIC_USER'] || 'elastic' + url = "https://#{user}:#{password}@#{host}:#{port}" + else + url = "http://#{host || 'localhost'}:#{port || 9200}" + end + Elasticsearch::Client.new(host: url, transport_options: transport_options) + end +end + task :default do system "rake --tasks" end @@ -53,9 +80,7 @@ namespace :bundle do subprojects.each do |project| sh "rm -f #{__current__.join(project)}/Gemfile.lock" end - sh "rm -f #{__current__.join('elasticsearch-model/gemfiles')}/3.0.gemfile.lock" - sh "rm -f #{__current__.join('elasticsearch-model/gemfiles')}/4.0.gemfile.lock" - sh "rm -f #{__current__.join('elasticsearch-model/gemfiles')}/5.0.gemfile.lock" + sh "rm -f #{__current__.join('elasticsearch-model/gemfiles')}/*.lock" end end @@ -132,7 +157,7 @@ namespace :test do end desc "Run all tests in all subprojects" - task :all do + task :all => :wait_for_green do subprojects.each do |project| puts '-'*80 sh "cd #{project} && " + @@ -163,6 +188,32 @@ namespace :test do end end + +desc "Wait for elasticsearch cluster to be in green state" +task :wait_for_green do + require 'elasticsearch' + + ready = nil + 5.times do |i| + begin + puts "Attempting to wait for green status: #{i + 1}" + if admin_client.cluster.health(wait_for_status: 'green', timeout: '50s') + ready = true + break + end + rescue Elasticsearch::Transport::Transport::Errors::RequestTimeout => ex + puts "Couldn't confirm green status.\n#{ex.inspect}." + rescue Faraday::ConnectionFailed => ex + puts "Couldn't connect to Elasticsearch.\n#{ex.inspect}." + sleep(30) + end + end + unless ready + puts "Couldn't connect to Elasticsearch, aborting program." + exit(1) + end +end + desc "Generate documentation for all subprojects" task :doc do subprojects.each do |project| diff --git a/elasticsearch-model/.gitignore b/elasticsearch-model/.gitignore index 52f8d0334..37746eee1 100644 --- a/elasticsearch-model/.gitignore +++ b/elasticsearch-model/.gitignore @@ -16,4 +16,6 @@ test/tmp test/version_tmp tmp -gemfiles/*.gemfile.lock + +gemfiles/*.lock + diff --git a/elasticsearch-model/gemfiles/6.0.gemfile b/elasticsearch-model/gemfiles/6.0.gemfile index 035a754cc..484a9df3b 100644 --- a/elasticsearch-model/gemfiles/6.0.gemfile +++ b/elasticsearch-model/gemfiles/6.0.gemfile @@ -20,6 +20,7 @@ # $ BUNDLE_GEMFILE=./gemfiles/6.0.gemfile bundle install # $ BUNDLE_GEMFILE=./gemfiles/6.0.gemfile bundle exec rake test:integration + source 'https://rubygems.org' gemspec path: '../' diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb index 7cd8456ac..b4df631f4 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb @@ -75,8 +75,8 @@ def __records_for_klass(klass, ids) klass.where(klass.primary_key => ids) when Elasticsearch::Model::Adapter::Mongoid.equal?(adapter) klass.where(:id.in => ids) - else - klass.find(ids) + else + klass.find(ids) end end @@ -108,13 +108,21 @@ def __ids_by_type def __type_for_hit(hit) @@__types ||= {} - @@__types[ "#{hit[:_index]}::#{hit[:_type]}" ] ||= begin + key = "#{hit[:_index]}::#{hit[:_type]}" if hit[:_type] && hit[:_type] != '_doc' + key = hit[:_index] unless key + + @@__types[key] ||= begin Registry.all.detect do |model| - model.index_name == hit[:_index] && model.document_type == hit[:_type] + (model.index_name == hit[:_index] && __no_type?(hit)) || + (model.index_name == hit[:_index] && model.document_type == hit[:_type]) end end end + def __no_type?(hit) + hit[:_type].nil? || hit[:_type] == '_doc' + end + # Returns the adapter registered for a particular `klass` or `nil` if not available # # @api private diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index da3c7f79b..69baf597e 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -56,9 +56,7 @@ class Mappings # @private TYPES_WITH_EMBEDDED_PROPERTIES = %w(object nested) - def initialize(type, options={}) - raise ArgumentError, "`type` is missing" if type.nil? - + def initialize(type = nil, options={}) @type = type @options = options @mapping = {} @@ -89,7 +87,11 @@ def indexes(name, options={}, &block) end def to_hash - { @type.to_sym => @options.merge( properties: @mapping ) } + if @type + { @type.to_sym => @options.merge( properties: @mapping ) } + else + @options.merge( properties: @mapping ) + end end def as_json(options={}) @@ -246,10 +248,12 @@ def create_index!(options={}) delete_index!(options.merge index: target_index) if options[:force] unless index_exists?(index: target_index) - self.client.indices.create index: target_index, - body: { - settings: settings, - mappings: mappings } + options.delete(:force) + self.client.indices.create({ index: target_index, + body: { + settings: settings, + mappings: mappings } + }.merge(options)) end end diff --git a/elasticsearch-model/lib/elasticsearch/model/naming.rb b/elasticsearch-model/lib/elasticsearch/model/naming.rb index 961959d61..9db0a9b08 100644 --- a/elasticsearch-model/lib/elasticsearch/model/naming.rb +++ b/elasticsearch-model/lib/elasticsearch/model/naming.rb @@ -108,10 +108,7 @@ def default_index_name self.model_name.collection.gsub(/\//, '-') end - def default_document_type - DEFAULT_DOC_TYPE - end - + def default_document_type; end end module InstanceMethods diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb index 669b28bfc..eef1bd42c 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb @@ -19,339 +19,377 @@ describe Elasticsearch::Model::Adapter::ActiveRecord do - before(:all) do - ActiveRecord::Schema.define(:version => 1) do - create_table :articles do |t| - t.string :title - t.string :body - t.integer :clicks, :default => 0 - t.datetime :created_at, :default => 'NOW()' + context 'when a document_type is not defined for the Model' do + + before do + ActiveRecord::Schema.define(:version => 1) do + create_table :article_no_types do |t| + t.string :title + t.string :body + t.integer :clicks, :default => 0 + t.datetime :created_at, :default => 'NOW()' + end end - end - Article.delete_all - Article.__elasticsearch__.create_index!(force: true) + ArticleNoType.delete_all + ArticleNoType.__elasticsearch__.create_index!(force: true) - Article.create!(title: 'Test', body: '', clicks: 1) - Article.create!(title: 'Testing Coding', body: '', clicks: 2) - Article.create!(title: 'Coding', body: '', clicks: 3) + ArticleNoType.create!(title: 'Test', body: '', clicks: 1) + ArticleNoType.create!(title: 'Testing Coding', body: '', clicks: 2) + ArticleNoType.create!(title: 'Coding', body: '', clicks: 3) - Article.__elasticsearch__.refresh_index! - end + ArticleNoType.__elasticsearch__.refresh_index! + end - describe 'indexing a document' do + describe 'indexing a document' do - let(:search_result) do - Article.search('title:test') - end + let(:search_result) do + ArticleNoType.search('title:test') + end - it 'allows searching for documents' do - expect(search_result.results.size).to be(2) - expect(search_result.records.size).to be(2) + it 'allows searching for documents' do + expect(search_result.results.size).to be(2) + expect(search_result.records.size).to be(2) + end end end - describe '#results' do + context 'when a document_type is defined for the Model' do - let(:search_result) do - Article.search('title:test') - end + before(:all) do + ActiveRecord::Schema.define(:version => 1) do + create_table :articles do |t| + t.string :title + t.string :body + t.integer :clicks, :default => 0 + t.datetime :created_at, :default => 'NOW()' + end + end - it 'returns an instance of Response::Result' do - expect(search_result.results.first).to be_a(Elasticsearch::Model::Response::Result) - end + Article.delete_all + Article.__elasticsearch__.create_index!(force: true, include_type_name: true) + + Article.create!(title: 'Test', body: '', clicks: 1) + Article.create!(title: 'Testing Coding', body: '', clicks: 2) + Article.create!(title: 'Coding', body: '', clicks: 3) - it 'prooperly loads the document' do - expect(search_result.results.first.title).to eq('Test') + Article.__elasticsearch__.refresh_index! end - context 'when the result contains other data' do + describe 'indexing a document' do let(:search_result) do - Article.search(query: { match: { title: 'test' } }, highlight: { fields: { title: {} } }) + Article.search('title:test') end - it 'allows access to the Elasticsearch result' do - expect(search_result.results.first.title).to eq('Test') - expect(search_result.results.first.title?).to be(true) - expect(search_result.results.first.boo?).to be(false) - expect(search_result.results.first.highlight?).to be(true) - expect(search_result.results.first.highlight.title?).to be(true) - expect(search_result.results.first.highlight.boo?).to be(false) + it 'allows searching for documents' do + expect(search_result.results.size).to be(2) + expect(search_result.records.size).to be(2) end end - end - describe '#records' do + describe '#results' do - let(:search_result) do - Article.search('title:test') - end - - it 'returns an instance of the model' do - expect(search_result.records.first).to be_a(Article) - end + let(:search_result) do + Article.search('title:test') + end - it 'prooperly loads the document' do - expect(search_result.records.first.title).to eq('Test') - end - end + it 'returns an instance of Response::Result' do + expect(search_result.results.first).to be_a(Elasticsearch::Model::Response::Result) + end - describe 'Enumerable' do + it 'prooperly loads the document' do + expect(search_result.results.first.title).to eq('Test') + end - let(:search_result) do - Article.search('title:test') - end + context 'when the result contains other data' do - it 'allows iteration over results' do - expect(search_result.results.map(&:_id)).to eq(['1', '2']) - end + let(:search_result) do + Article.search(query: { match: { title: 'test' } }, highlight: { fields: { title: {} } }) + end - it 'allows iteration over records' do - expect(search_result.records.map(&:id)).to eq([1, 2]) + it 'allows access to the Elasticsearch result' do + expect(search_result.results.first.title).to eq('Test') + expect(search_result.results.first.title?).to be(true) + expect(search_result.results.first.boo?).to be(false) + expect(search_result.results.first.highlight?).to be(true) + expect(search_result.results.first.highlight.title?).to be(true) + expect(search_result.results.first.highlight.boo?).to be(false) + end + end end - end - - describe '#id' do - let(:search_result) do - Article.search('title:test') - end + describe '#records' do - it 'returns the id' do - expect(search_result.results.first.id).to eq('1') - end - end + let(:search_result) do + Article.search('title:test') + end - describe '#id' do + it 'returns an instance of the model' do + expect(search_result.records.first).to be_a(Article) + end - let(:search_result) do - Article.search('title:test') + it 'prooperly loads the document' do + expect(search_result.records.first.title).to eq('Test') + end end - it 'returns the type' do - expect(search_result.results.first.type).to eq('article') - end - end + describe 'Enumerable' do - describe '#each_with_hit' do + let(:search_result) do + Article.search('title:test') + end - let(:search_result) do - Article.search('title:test') - end + it 'allows iteration over results' do + expect(search_result.results.map(&:_id)).to eq(['1', '2']) + end - it 'returns the record with the Elasticsearch hit' do - search_result.records.each_with_hit do |r, h| - expect(h._score).not_to be_nil - expect(h._source.title).not_to be_nil + it 'allows iteration over records' do + expect(search_result.records.map(&:id)).to eq([1, 2]) end end - end - describe 'search results order' do + describe '#id' do - let(:search_result) do - Article.search(query: { match: { title: 'code' }}, sort: { clicks: :desc }) - end + let(:search_result) do + Article.search('title:test') + end - it 'preserves the search results order when accessing a single record' do - expect(search_result.records[0].clicks).to be(3) - expect(search_result.records[1].clicks).to be(2) - expect(search_result.records.first).to eq(search_result.records[0]) + it 'returns the id' do + expect(search_result.results.first.id).to eq('1') + end end - it 'preserves the search results order for the list of records' do - search_result.records.each_with_hit do |r, h| - expect(r.id.to_s).to eq(h._id) + describe '#id' do + + let(:search_result) do + Article.search('title:test') end - search_result.records.map_with_hit do |r, h| - expect(r.id.to_s).to eq(h._id) + it 'returns the type' do + expect(search_result.results.first.type).to eq('article') end end - end - describe 'a paged collection' do + describe '#each_with_hit' do - let(:search_result) do - Article.search(query: { match: { title: { query: 'test' } } }, - size: 2, - from: 1) - end + let(:search_result) do + Article.search('title:test') + end - it 'applies the paged options to the search' do - expect(search_result.results.size).to eq(1) - expect(search_result.results.first.title).to eq('Testing Coding') - expect(search_result.records.size).to eq(1) - expect(search_result.records.first.title).to eq('Testing Coding') + it 'returns the record with the Elasticsearch hit' do + search_result.records.each_with_hit do |r, h| + expect(h._score).not_to be_nil + expect(h._source.title).not_to be_nil + end + end end - end - describe '#destroy' do + describe 'search results order' do - before do - Article.create!(title: 'destroy', body: '', clicks: 1) - Article.__elasticsearch__.refresh_index! - Article.where(title: 'destroy').first.destroy + let(:search_result) do + Article.search(query: { match: { title: 'code' }}, sort: { clicks: :desc }) + end - Article.__elasticsearch__.refresh_index! - end + it 'preserves the search results order when accessing a single record' do + expect(search_result.records[0].clicks).to be(3) + expect(search_result.records[1].clicks).to be(2) + expect(search_result.records.first).to eq(search_result.records[0]) + end - let(:search_result) do - Article.search('title:test') - end + it 'preserves the search results order for the list of records' do + search_result.records.each_with_hit do |r, h| + expect(r.id.to_s).to eq(h._id) + end - it 'removes the document from the index' do - expect(Article.count).to eq(3) - expect(search_result.results.size).to eq(2) - expect(search_result.records.size).to eq(2) + search_result.records.map_with_hit do |r, h| + expect(r.id.to_s).to eq(h._id) + end + end end - end - describe 'full document updates' do + describe 'a paged collection' do - before do - article = Article.create!(title: 'update', body: '', clicks: 1) - Article.__elasticsearch__.refresh_index! - article.title = 'Writing' - article.save + let(:search_result) do + Article.search(query: { match: { title: { query: 'test' } } }, + size: 2, + from: 1) + end - Article.__elasticsearch__.refresh_index! + it 'applies the paged options to the search' do + expect(search_result.results.size).to eq(1) + expect(search_result.results.first.title).to eq('Testing Coding') + expect(search_result.records.size).to eq(1) + expect(search_result.records.first.title).to eq('Testing Coding') + end end - let(:search_result) do - Article.search('title:write') - end + describe '#destroy' do - it 'applies the update' do - expect(search_result.results.size).to eq(1) - expect(search_result.records.size).to eq(1) - end - end + before do + Article.create!(title: 'destroy', body: '', clicks: 1) + Article.__elasticsearch__.refresh_index! + Article.where(title: 'destroy').first.destroy - describe 'attribute updates' do + Article.__elasticsearch__.refresh_index! + end - before do - article = Article.create!(title: 'update', body: '', clicks: 1) - Article.__elasticsearch__.refresh_index! - article.title = 'special' - article.save + let(:search_result) do + Article.search('title:test') + end - Article.__elasticsearch__.refresh_index! + it 'removes the document from the index' do + expect(Article.count).to eq(3) + expect(search_result.results.size).to eq(2) + expect(search_result.records.size).to eq(2) + end end - let(:search_result) do - Article.search('title:special') - end + describe 'full document updates' do - it 'applies the update' do - expect(search_result.results.size).to eq(1) - expect(search_result.records.size).to eq(1) - end - end + before do + article = Article.create!(title: 'update', body: '', clicks: 1) + Article.__elasticsearch__.refresh_index! + article.title = 'Writing' + article.save - describe '#save' do + Article.__elasticsearch__.refresh_index! + end - before do - article = Article.create!(title: 'save', body: '', clicks: 1) + let(:search_result) do + Article.search('title:write') + end - ActiveRecord::Base.transaction do - article.body = 'dummy' - article.save + it 'applies the update' do + expect(search_result.results.size).to eq(1) + expect(search_result.records.size).to eq(1) + end + end + + describe 'attribute updates' do + before do + article = Article.create!(title: 'update', body: '', clicks: 1) + Article.__elasticsearch__.refresh_index! article.title = 'special' article.save + + Article.__elasticsearch__.refresh_index! end - article.__elasticsearch__.update_document - Article.__elasticsearch__.refresh_index! - end + let(:search_result) do + Article.search('title:special') + end - let(:search_result) do - Article.search('body:dummy') + it 'applies the update' do + expect(search_result.results.size).to eq(1) + expect(search_result.records.size).to eq(1) + end end - it 'applies the save' do - expect(search_result.results.size).to eq(1) - expect(search_result.records.size).to eq(1) - end - end + describe '#save' do - describe 'a DSL search' do + before do + article = Article.create!(title: 'save', body: '', clicks: 1) - let(:search_result) do - Article.search(query: { match: { title: { query: 'test' } } }) - end + ActiveRecord::Base.transaction do + article.body = 'dummy' + article.save - it 'returns the results' do - expect(search_result.results.size).to eq(2) - expect(search_result.records.size).to eq(2) - end - end + article.title = 'special' + article.save + end - describe 'chaining SQL queries on response.records' do + article.__elasticsearch__.update_document + Article.__elasticsearch__.refresh_index! + end - let(:search_result) do - Article.search(query: { match: { title: { query: 'test' } } }) - end + let(:search_result) do + Article.search('body:dummy') + end - it 'executes the SQL request with the chained query criteria' do - expect(search_result.records.size).to eq(2) - expect(search_result.records.where(title: 'Test').size).to eq(1) - expect(search_result.records.where(title: 'Test').first.title).to eq('Test') + it 'applies the save' do + expect(search_result.results.size).to eq(1) + expect(search_result.records.size).to eq(1) + end end - end - - describe 'ordering of SQL queries' do - context 'when order is called on the ActiveRecord query' do + describe 'a DSL search' do let(:search_result) do - Article.search query: { match: { title: { query: 'test' } } } + Article.search(query: { match: { title: { query: 'test' } } }) end - it 'allows the SQL query to be ordered independent of the Elasticsearch results order', unless: active_record_at_least_4? do - expect(search_result.records.order('title DESC').first.title).to eq('Testing Coding') - expect(search_result.records.order('title DESC')[0].title).to eq('Testing Coding') - end - - it 'allows the SQL query to be ordered independent of the Elasticsearch results order', if: active_record_at_least_4? do - expect(search_result.records.order(title: :desc).first.title).to eq('Testing Coding') - expect(search_result.records.order(title: :desc)[0].title).to eq('Testing Coding') + it 'returns the results' do + expect(search_result.results.size).to eq(2) + expect(search_result.records.size).to eq(2) end end - context 'when more methods are chained on the ActiveRecord query' do + describe 'chaining SQL queries on response.records' do let(:search_result) do - Article.search query: {match: {title: {query: 'test'}}} + Article.search(query: { match: { title: { query: 'test' } } }) end - it 'allows the SQL query to be ordered independent of the Elasticsearch results order', if: active_record_at_least_4? do - expect(search_result.records.distinct.order(title: :desc).first.title).to eq('Testing Coding') - expect(search_result.records.distinct.order(title: :desc)[0].title).to eq('Testing Coding') + it 'executes the SQL request with the chained query criteria' do + expect(search_result.records.size).to eq(2) + expect(search_result.records.where(title: 'Test').size).to eq(1) + expect(search_result.records.where(title: 'Test').first.title).to eq('Test') end end - end - describe 'access to the response via methods' do + describe 'ordering of SQL queries' do + + context 'when order is called on the ActiveRecord query' do + + let(:search_result) do + Article.search query: { match: { title: { query: 'test' } } } + end + + it 'allows the SQL query to be ordered independent of the Elasticsearch results order', unless: active_record_at_least_4? do + expect(search_result.records.order('title DESC').first.title).to eq('Testing Coding') + expect(search_result.records.order('title DESC')[0].title).to eq('Testing Coding') + end + + it 'allows the SQL query to be ordered independent of the Elasticsearch results order', if: active_record_at_least_4? do + expect(search_result.records.order(title: :desc).first.title).to eq('Testing Coding') + expect(search_result.records.order(title: :desc)[0].title).to eq('Testing Coding') + end + end + + context 'when more methods are chained on the ActiveRecord query' do + + let(:search_result) do + Article.search query: {match: {title: {query: 'test'}}} + end - let(:search_result) do - Article.search(query: { match: { title: { query: 'test' } } }, - aggregations: { - dates: { date_histogram: { field: 'created_at', interval: 'hour' } }, - clicks: { global: {}, aggregations: { min: { min: { field: 'clicks' } } } } - }, - suggest: { text: 'tezt', title: { term: { field: 'title', suggest_mode: 'always' } } }) + it 'allows the SQL query to be ordered independent of the Elasticsearch results order', if: active_record_at_least_4? do + expect(search_result.records.distinct.order(title: :desc).first.title).to eq('Testing Coding') + expect(search_result.records.distinct.order(title: :desc)[0].title).to eq('Testing Coding') + end + end end - it 'allows document keys to be access via methods' do - expect(search_result.aggregations.dates.buckets.first.doc_count).to eq(2) - expect(search_result.aggregations.clicks.doc_count).to eq(6) - expect(search_result.aggregations.clicks.min.value).to eq(1.0) - expect(search_result.aggregations.clicks.max).to be_nil - expect(search_result.suggestions.title.first.options.size).to eq(1) - expect(search_result.suggestions.terms).to eq(['test']) + describe 'access to the response via methods' do + + let(:search_result) do + Article.search(query: { match: { title: { query: 'test' } } }, + aggregations: { + dates: { date_histogram: { field: 'created_at', interval: 'hour' } }, + clicks: { global: {}, aggregations: { min: { min: { field: 'clicks' } } } } + }, + suggest: { text: 'tezt', title: { term: { field: 'title', suggest_mode: 'always' } } }) + end + + it 'allows document keys to be access via methods' do + expect(search_result.aggregations.dates.buckets.first.doc_count).to eq(2) + expect(search_result.aggregations.clicks.doc_count).to eq(6) + expect(search_result.aggregations.clicks.min.value).to eq(1.0) + expect(search_result.aggregations.clicks.max).to be_nil + expect(search_result.suggestions.title.first.options.size).to eq(1) + expect(search_result.suggestions.terms).to eq(['test']) + end end end end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb index 9f4422050..3f93eca9d 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb @@ -27,7 +27,7 @@ end MyNamespace::Book.delete_all - MyNamespace::Book.__elasticsearch__.create_index!(force: true) + MyNamespace::Book.__elasticsearch__.create_index!(force: true, include_type_name: true) MyNamespace::Book.create!(title: 'Test') MyNamespace::Book.__elasticsearch__.refresh_index! end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb index 60d642103..675ab3b73 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb @@ -38,7 +38,7 @@ add_index(:answers, :question_id) unless index_exists?(:answers, :question_id) clear_tables(Question) - ParentChildSearchable.create_index!(force: true) + ParentChildSearchable.create_index!(force: true, include_type_name: true) q_1 = Question.create!(title: 'First Question', author: 'John') q_2 = Question.create!(title: 'Second Question', author: 'Jody') diff --git a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb index f625b538e..3ee081969 100644 --- a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb @@ -101,10 +101,8 @@ class NotFound < Exception; end expect(DummyIndexingModel.mappings).to be_a(Elasticsearch::Model::Indexing::Mappings) end - it 'raises an exception when there is no type passed to the #initialize method' do - expect { - Elasticsearch::Model::Indexing::Mappings.new - }.to raise_exception(ArgumentError) + it 'does not raise an exception when there is no type passed to the #initialize method' do + expect(Elasticsearch::Model::Indexing::Mappings.new) end it 'should be convertible to a hash' do @@ -115,7 +113,7 @@ class NotFound < Exception; end expect(Elasticsearch::Model::Indexing::Mappings.new(:mytype, { foo: 'bar' }).as_json).to eq(expected_mapping_hash) end - context 'when specific mappings are defined' do + context 'when a type is specified' do let(:mappings) do Elasticsearch::Model::Indexing::Mappings.new(:mytype) @@ -130,6 +128,65 @@ class NotFound < Exception; end expect(mappings.to_hash[:mytype][:properties][:foo][:type]).to eq('boolean') end + it 'uses text as the default field type' do + expect(mappings.to_hash[:mytype][:properties][:bar][:type]).to eq('text') + end + + context 'when the \'include_type_name\' option is specified' do + + let(:mappings) do + Elasticsearch::Model::Indexing::Mappings.new(:mytype, include_type_name: true) + end + + before do + mappings.indexes :foo, { type: 'boolean', include_in_all: false } + end + + it 'creates the correct mapping definition' do + expect(mappings.to_hash[:mytype][:properties][:foo][:type]).to eq('boolean') + end + + it 'sets the \'include_type_name\' option' do + expect(mappings.to_hash[:mytype][:include_type_name]).to eq(true) + end + end + end + + context 'when a type is not specified' do + + let(:mappings) do + Elasticsearch::Model::Indexing::Mappings.new + end + + before do + mappings.indexes :foo, { type: 'boolean', include_in_all: false } + mappings.indexes :bar + end + + it 'creates the correct mapping definition' do + expect(mappings.to_hash[:properties][:foo][:type]).to eq('boolean') + end + + it 'uses text as the default type' do + expect(mappings.to_hash[:properties][:bar][:type]).to eq('text') + end + end + + context 'when specific mappings are defined' do + + let(:mappings) do + Elasticsearch::Model::Indexing::Mappings.new(:mytype, include_type_name: true) + end + + before do + mappings.indexes :foo, { type: 'boolean', include_in_all: false } + mappings.indexes :bar + end + + it 'creates the correct mapping definition' do + expect(mappings.to_hash[:mytype][:properties][:foo][:type]).to eq('boolean') + end + it 'uses text as the default type' do expect(mappings.to_hash[:mytype][:properties][:bar][:type]).to eq('text') end @@ -186,6 +243,10 @@ class NotFound < Exception; end expect(mappings.to_hash[:mytype][:properties][:foo_nested_as_symbol][:properties]).not_to be_nil expect(mappings.to_hash[:mytype][:properties][:foo_nested_as_symbol][:fields]).to be_nil end + + it 'defines the settings' do + expect(mappings.to_hash[:mytype][:include_type_name]).to be(true) + end end end @@ -197,7 +258,7 @@ class NotFound < Exception; end end let(:expected_mappings_hash) do - { _doc: { foo: "boo", bar: "bam", properties: {} } } + { foo: "boo", bar: "bam", properties: {} } end it 'sets the mappings' do @@ -213,7 +274,25 @@ class NotFound < Exception; end end it 'sets the mappings' do - expect(DummyIndexingModel.mapping.to_hash[:_doc][:properties][:foo][:type]).to eq('boolean') + expect(DummyIndexingModel.mapping.to_hash[:properties][:foo][:type]).to eq('boolean') + end + end + + context 'when the class has a document_type' do + + before do + DummyIndexingModel.instance_variable_set(:@mapping, nil) + DummyIndexingModel.document_type(:mytype) + DummyIndexingModel.mappings(foo: 'boo') + DummyIndexingModel.mappings(bar: 'bam') + end + + let(:expected_mappings_hash) do + { mytype: { foo: "boo", bar: "bam", properties: {} } } + end + + it 'sets the mappings' do + expect(DummyIndexingModel.mappings.to_hash).to eq(expected_mappings_hash) end end end @@ -755,8 +834,8 @@ class ::DummyIndexingModelForCreate context 'when options are not provided' do let(:expected_body) do - { mappings: { _doc: { properties: { foo: { analyzer: 'keyword', - type: 'text' } } } }, + { mappings: { properties: { foo: { analyzer: 'keyword', + type: 'text' } } }, settings: { index: { number_of_shards: 1 } } } end @@ -806,8 +885,8 @@ class ::DummyIndexingModelForCreate before do expect(DummyIndexingModelForCreate).to receive(:client).and_return(client) - expect(DummyIndexingModelForCreate).to receive(:index_exists?).and_return(false) expect(DummyIndexingModelForCreate).to receive(:delete_index!).and_return(true) + expect(DummyIndexingModelForCreate).to receive(:index_exists?).and_return(false) expect(indices).to receive(:create).and_raise(Exception) end @@ -827,8 +906,8 @@ class ::DummyIndexingModelForCreate end let(:expected_body) do - { mappings: { _doc: { properties: { foo: { analyzer: 'keyword', - type: 'text' } } } }, + { mappings: { properties: { foo: { analyzer: 'keyword', + type: 'text' } } }, settings: { index: { number_of_shards: 1 } } } end diff --git a/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb b/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb index 93b7ed95e..0d8efb9a3 100644 --- a/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb @@ -97,9 +97,9 @@ class ::Cat < ::Animal describe '#document_type' do - it 'returns the default document type' do - expect(TestBase.document_type).to eq('_doc') - expect(TestBase.new.document_type).to eq('_doc') + it 'returns nil' do + expect(TestBase.document_type).to be_nil + expect(TestBase.new.document_type).to be_nil end it 'returns the explicit document type' do diff --git a/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb b/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb index 9e2365bcd..bdd5aafad 100644 --- a/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb @@ -51,9 +51,9 @@ class DummyNamingModelInNamespace expect(::MyNamespace::DummyNamingModelInNamespace.new.index_name).to eq('my_namespace-dummy_naming_model_in_namespaces') end - it 'returns the document type' do - expect(DummyNamingModel.document_type).to eq('_doc') - expect(DummyNamingModel.new.document_type).to eq('_doc') + it 'returns nil' do + expect(DummyNamingModel.document_type).to be_nil + expect(DummyNamingModel.new.document_type).to be_nil end describe '#index_name' do @@ -141,8 +141,8 @@ class DummyNamingModelInNamespace describe '#document_type' do - it 'returns the document type' do - expect(DummyNamingModel.document_type).to eq('_doc') + it 'returns nil' do + expect(DummyNamingModel.document_type).to be_nil end context 'when the method is called with an argument' do diff --git a/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb index 3824a0d44..4ad8ab5a1 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb @@ -31,11 +31,6 @@ def self.document_type; 'bar'; end remove_classes(ModelClass) end - let(:response_document) do - { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, - 'hits' => { 'total' => 100, 'hits' => (1..100).to_a.map { |i| { _id: i } } } } - end - let(:search) do Elasticsearch::Model::Searching::SearchRequest.new(model, '*') end @@ -391,37 +386,87 @@ def self.document_type; 'bar'; end end end - context 'when the model is a single one' do + context 'when Elasticsearch version is < 7.0' do - let(:model) do - ModelClass + let(:response_document) do + { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, + 'hits' => { 'total' => 100, 'hits' => (1..100).to_a.map { |i| { _id: i } } } } end - let(:type_field) do - 'bar' - end + context 'when the model is a single one' do + + let(:model) do + ModelClass + end + + let(:type_field) do + 'bar' + end + + let(:index_field) do + 'foo' + end - let(:index_field) do - 'foo' + it_behaves_like 'a search request that can be paginated' end - it_behaves_like 'a search request that can be paginated' - end + context 'when the model is a multimodel' do + + let(:model) do + Elasticsearch::Model::Multimodel.new(ModelClass) + end - context 'when the model is a multimodel' do + let(:type_field) do + ['bar'] + end + + let(:index_field) do + ['foo'] + end - let(:model) do - Elasticsearch::Model::Multimodel.new(ModelClass) + it_behaves_like 'a search request that can be paginated' end + end - let(:type_field) do - ['bar'] + context 'when Elasticsearch version is >= 7.0' do + + let(:response_document) do + { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, + 'hits' => { 'total' => { 'value' => 100, 'relation' => 'eq' }, 'hits' => (1..100).to_a.map { |i| { _id: i } } } } end - let(:index_field) do - ['foo'] + context 'when the model is a single one' do + + let(:model) do + ModelClass + end + + let(:type_field) do + 'bar' + end + + let(:index_field) do + 'foo' + end + + it_behaves_like 'a search request that can be paginated' end - it_behaves_like 'a search request that can be paginated' + context 'when the model is a multimodel' do + + let(:model) do + Elasticsearch::Model::Multimodel.new(ModelClass) + end + + let(:type_field) do + ['bar'] + end + + let(:index_field) do + ['foo'] + end + + it_behaves_like 'a search request that can be paginated' + end end end diff --git a/elasticsearch-model/spec/support/app.rb b/elasticsearch-model/spec/support/app.rb index d5c2c8ace..e009f9371 100644 --- a/elasticsearch-model/spec/support/app.rb +++ b/elasticsearch-model/spec/support/app.rb @@ -30,6 +30,7 @@ require 'support/app/series' require 'support/app/mongoid_article' require 'support/app/article' +require 'support/app/article_no_type' require 'support/app/searchable' require 'support/app/category' require 'support/app/author' diff --git a/elasticsearch-model/spec/support/app/article_no_type.rb b/elasticsearch-model/spec/support/app/article_no_type.rb new file mode 100644 index 000000000..5a08746ba --- /dev/null +++ b/elasticsearch-model/spec/support/app/article_no_type.rb @@ -0,0 +1,37 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +class ::ArticleNoType < ActiveRecord::Base + include Elasticsearch::Model + include Elasticsearch::Model::Callbacks + + settings index: {number_of_shards: 1, number_of_replicas: 0} do + mapping do + indexes :title, type: 'text', analyzer: 'snowball' + indexes :body, type: 'text' + indexes :clicks, type: 'integer' + indexes :created_at, type: 'date' + end + end + + def as_indexed_json(options = {}) + attributes + .symbolize_keys + .slice(:title, :body, :clicks, :created_at) + .merge(suggest_title: title) + end +end diff --git a/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb b/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb index e747bec2e..15b56186a 100644 --- a/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb +++ b/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb @@ -21,7 +21,7 @@ module ParentChildSearchable def create_index!(options={}) client = Question.__elasticsearch__.client - client.indices.delete index: INDEX_NAME rescue nil if options[:force] + client.indices.delete index: INDEX_NAME rescue nil if options.delete(:force) settings = Question.settings.to_hash.merge Answer.settings.to_hash mapping_properties = { join_field: { type: JOIN, @@ -31,10 +31,10 @@ def create_index!(options={}) Answer.mappings.to_hash[:doc][:properties]) mappings = { doc: { properties: merged_properties }} - client.indices.create index: INDEX_NAME, - body: { + client.indices.create({ index: INDEX_NAME, + body: { settings: settings.to_hash, - mappings: mappings } + mappings: mappings } }.merge(options)) end extend self diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index f20190052..3eb544f02 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -40,7 +40,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 1.9.3" - s.add_dependency "elasticsearch", '~> 6' + s.add_dependency "elasticsearch", '~> 7' s.add_dependency "elasticsearch-model", '~> 7' s.add_dependency "activesupport", '> 4' s.add_dependency "activemodel", '> 4' diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index e330ffb5c..c10b5c00b 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -80,16 +80,6 @@ def create(options = {}, &block) # @since 6.0.0 DEFAULT_INDEX_NAME = 'repository'.freeze - # The default document type. - # - # @return [ String ] The default document type. - # - # @note the document type will no longer be configurable in future versions - # of Elasticsearch. - # - # @since 6.0.0 - DEFAULT_DOC_TYPE = '_doc'.freeze - # The repository options. # # @return [ Hash ] @@ -141,8 +131,7 @@ def client # @since 6.0.0 def document_type @document_type ||= @options[:document_type] || - __get_class_value(:document_type) || - DEFAULT_DOC_TYPE + __get_class_value(:document_type) end # Get the index name used by the repository. diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb index 7373f9acd..81185cfd4 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb @@ -44,7 +44,7 @@ module ClassMethods # # @since 6.0.0 def document_type(_type = nil) - @document_type ||= (_type || DEFAULT_DOC_TYPE) + @document_type ||= _type end # Get or set the class-level index name setting. diff --git a/elasticsearch-persistence/spec/repository/store_spec.rb b/elasticsearch-persistence/spec/repository/store_spec.rb index bc9b7ba61..569aa410f 100644 --- a/elasticsearch-persistence/spec/repository/store_spec.rb +++ b/elasticsearch-persistence/spec/repository/store_spec.rb @@ -331,7 +331,7 @@ def to_hash context 'when the document does not exist' do before do - repository.create_index! + repository.create_index!(include_type_name: true) end it 'raises an exception' do diff --git a/elasticsearch-persistence/spec/repository_spec.rb b/elasticsearch-persistence/spec/repository_spec.rb index 06d32ef0a..472687fe5 100644 --- a/elasticsearch-persistence/spec/repository_spec.rb +++ b/elasticsearch-persistence/spec/repository_spec.rb @@ -107,9 +107,6 @@ class RepositoryWithoutDSL expect(repository.client).to be_a(Elasticsearch::Transport::Client) end - it 'sets a default document type' do - expect(repository.document_type).to eq('_doc') - end it 'sets a default index name' do expect(repository.index_name).to eq('repository') @@ -282,7 +279,7 @@ class RepositoryWithDSL before do begin; repository.delete_index!; rescue; end - repository.create_index! + repository.create_index!(include_type_name: true) end it 'creates the index' do @@ -337,7 +334,7 @@ class RepositoryWithDSL end before do - repository.create_index! + repository.create_index!(include_type_name: true) end it 'refreshes the index' do @@ -364,7 +361,7 @@ class RepositoryWithDSL end before do - repository.create_index! + repository.create_index!(include_type_name: true) end it 'determines if the index exists' do @@ -501,10 +498,6 @@ class RepositoryWithoutDSL }.to raise_exception(NoMethodError) end - it 'sets a default on the instance' do - expect(RepositoryWithoutDSL.new.document_type).to eq('_doc') - end - it 'allows the value to be overridden with options on the instance' do expect(RepositoryWithoutDSL.new(document_type: 'notes').document_type).to eq('notes') end @@ -547,6 +540,33 @@ class RepositoryWithoutDSL repository.create_index! expect(repository.index_exists?).to eq(true) end + + context 'when the repository has a document type defined' do + + let(:repository) do + RepositoryWithoutDSL.new(client: DEFAULT_CLIENT, document_type: 'mytype') + end + + context 'when the server is version >= 7.0', if: server_version > '7.0' do + + context 'when the include_type_name option is specified' do + + it 'creates an index' do + repository.create_index!(include_type_name: true) + expect(repository.index_exists?).to eq(true) + end + end + + context 'when the include_type_name option is not specified' do + + it 'raises an error' do + expect { + repository.create_index! + }.to raise_exception(Elasticsearch::Transport::Transport::Errors::BadRequest) + end + end + end + end end describe '#delete_index!' do @@ -562,7 +582,7 @@ class RepositoryWithoutDSL end it 'deletes an index' do - repository.create_index! + repository.create_index!(include_type_name: true) repository.delete_index! expect(repository.index_exists?).to eq(false) end @@ -585,7 +605,7 @@ class RepositoryWithoutDSL end it 'refreshes an index' do - repository.create_index! + repository.create_index!(include_type_name: true) expect(repository.refresh_index!['_shards']).to be_a(Hash) end end @@ -607,7 +627,7 @@ class RepositoryWithoutDSL end it 'returns whether the index exists' do - repository.create_index! + repository.create_index!(include_type_name: true) expect(repository.index_exists?).to be(true) end end @@ -621,7 +641,7 @@ class RepositoryWithoutDSL end it 'sets a default on an instance' do - expect(RepositoryWithoutDSL.new.mapping.to_hash).to eq(_doc: { properties: {} }) + expect(RepositoryWithoutDSL.new.mapping.to_hash).to eq(properties: {}) end it 'allows the mapping to be set as an option' do diff --git a/elasticsearch-persistence/spec/spec_helper.rb b/elasticsearch-persistence/spec/spec_helper.rb index 2caf7bcee..73243fe19 100644 --- a/elasticsearch-persistence/spec/spec_helper.rb +++ b/elasticsearch-persistence/spec/spec_helper.rb @@ -48,3 +48,12 @@ class MyTestRepository # # @since 6.0.0 DEFAULT_REPOSITORY = MyTestRepository.new(index_name: 'my_test_repository', document_type: 'test') + +# Get the Elasticsearch server version. +# +# @return [ String ] The version of Elasticsearch. +# +# @since 7.0.0 +def server_version(client = nil) + (client || DEFAULT_CLIENT).info['version']['number'] +end diff --git a/travis_before_script.sh b/travis_before_script.sh index 014864175..547a5a040 100644 --- a/travis_before_script.sh +++ b/travis_before_script.sh @@ -1,6 +1,15 @@ #!/bin/bash -curl https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ELASTICSEARCH_VERSION}-linux-x86_64.tar.gz | tar xz -C /tmp +if [ "$ELASTICSEARCH_VERSION" == "6.7.1" ] +then + url="https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ELASTICSEARCH_VERSION}.tar.gz" +else + url="https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ELASTICSEARCH_VERSION}-linux-x86_64.tar.gz" +fi + +echo "Downloading elasticsearch from $url" +curl $url | tar xz -C /tmp echo "Starting elasticsearch on port ${TEST_CLUSTER_PORT}" +/tmp/elasticsearch-${ELASTICSEARCH_VERSION}/bin/elasticsearch-keystore create /tmp/elasticsearch-${ELASTICSEARCH_VERSION}/bin/elasticsearch -E http.port=${TEST_CLUSTER_PORT} &> /dev/null & From 3a0c45d0d0a4d1a9b710bdafde88f63bb7b26e17 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Tue, 30 Apr 2019 15:31:51 +0900 Subject: [PATCH 442/582] Update version to 7.0.0.pre --- elasticsearch-model/lib/elasticsearch/model/version.rb | 2 +- elasticsearch-persistence/elasticsearch-persistence.gemspec | 2 +- .../lib/elasticsearch/persistence/version.rb | 2 +- elasticsearch-rails/lib/elasticsearch/rails/version.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/version.rb b/elasticsearch-model/lib/elasticsearch/model/version.rb index 4d6e36d2e..27561c41d 100644 --- a/elasticsearch-model/lib/elasticsearch/model/version.rb +++ b/elasticsearch-model/lib/elasticsearch/model/version.rb @@ -17,6 +17,6 @@ module Elasticsearch module Model - VERSION = "7.0.0" + VERSION = "7.0.0.pre" end end diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index 3eb544f02..de668fcc9 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -41,7 +41,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 1.9.3" s.add_dependency "elasticsearch", '~> 7' - s.add_dependency "elasticsearch-model", '~> 7' + s.add_dependency "elasticsearch-model", '7.0.0.pre' s.add_dependency "activesupport", '> 4' s.add_dependency "activemodel", '> 4' s.add_dependency "hashie" diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb index 2953d86a6..1293aa265 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb @@ -17,6 +17,6 @@ module Elasticsearch module Persistence - VERSION = '7.0.0' + VERSION = '7.0.0.pre' end end diff --git a/elasticsearch-rails/lib/elasticsearch/rails/version.rb b/elasticsearch-rails/lib/elasticsearch/rails/version.rb index 6d5c428e3..619d889a2 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/version.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/version.rb @@ -17,6 +17,6 @@ module Elasticsearch module Rails - VERSION = "7.0.0" + VERSION = "7.0.0.pre" end end From 16e03d06be0599af92eb6f868341dcd9ee0b3277 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Tue, 30 Apr 2019 15:55:23 +0900 Subject: [PATCH 443/582] Update Changelog for 7.0.0.pre release --- CHANGELOG.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bccab3d9f..a1ff80f10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,43 @@ +## 7.0.0.pre + +* Update test tasks and travis (#840) +* `respond_to_missing?` to silence Ruby 2.4 warnings (#838) +* Update README.md to link to migration blog post (#857) +* Add license headers, LICENSE and NOTICE files (#861) +* Only execute update if document attributes is not empty (#862) +* Remove bundler version requirement in gemspec files +* 7.0 support (#875) +* Update version to 7.0.0.pre + +### ActiveModel + +* Fix import when preprocess returns empty collection (#720) +* Add test for not importing when ActiveRecord query is empty +* with 0 +* Port basic response tests to rspec (#833) +* Add newlines at the end of files that are missing it +* Port adapter tests to rspec (#834) +* Ensure that specified ActiveRecord order is not overwritten by Elasticsearch search results order (#835) +* Port remainder of Elasticsearch::Model unit tests to rspec (#836) +* Port all integration tests to rspec (#837) +* Avoid executing search twice; Reuse response in Response#raw_response (#850) +* Update example to account for deprecation of _suggest endpoint in favor of _search +* Handle total hits as an object in search response +* Use logger to log index not found message (#868) +* Test against Rails 6.0.rc1 + +### Persistence + +* Ensure that arguments are passed to super (#853) +* Index name option is handled by super, no need to pass options expicitly +* Handle total hits as an object in search response + +### Ruby on Rails + +* Convert tests to rspec (#842) +* Fix seeds file to stop using outdated YAML method (#843) +* Fixed 03-expert.rb set tracer only in dev env (#621) + ## 6.0.0 * Update to test against Elasticsearch 6.4 From b3237ee04225c3c6f60f8767396364fd9f24240d Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Thu, 2 May 2019 12:25:50 +0900 Subject: [PATCH 444/582] [STORE] Fix minor documentation typo --- .../lib/elasticsearch/persistence/repository/search.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb index 4fad6a20a..dcc1b0d51 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb @@ -88,7 +88,7 @@ def search(query_or_definition, options={}) # # @example Return the count of domain object matching a query in the Elasticsearch DSL # - # repository.search(query: { match: { title: 'fox dog' } }) + # repository.count(query: { match: { title: 'fox dog' } }) # # => 1 # # @param [ Hash, String ] query_or_definition The query or search definition. From 26e4cf8af73794b48f6026a01faec13fd045b21a Mon Sep 17 00:00:00 2001 From: Katsuhisa Kitano <91katsuhisa@gmail.com> Date: Fri, 3 May 2019 17:52:58 +0900 Subject: [PATCH 445/582] fix README elasticsearch-model (#764) --- elasticsearch-model/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 6274463b1..b03ecf9a4 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -532,7 +532,7 @@ class Indexer Logger = Sidekiq.logger.level == Logger::DEBUG ? Sidekiq.logger : nil Client = Elasticsearch::Client.new host: 'localhost:9200', logger: Logger - def perform(operation, record_id) + def perform_async(operation, record_id) logger.debug [operation, "ID: #{record_id}"] case operation.to_s From a67ecc1afc3a0615efa7ca6daecbb9132f61c318 Mon Sep 17 00:00:00 2001 From: Jakub Godawa <jakub.godawa@gmail.com> Date: Fri, 3 May 2019 10:55:07 +0200 Subject: [PATCH 446/582] Install webpacker before running the server (#871) Fix the error: can't find config/webpack.yml --- elasticsearch-rails/lib/rails/templates/01-basic.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/elasticsearch-rails/lib/rails/templates/01-basic.rb b/elasticsearch-rails/lib/rails/templates/01-basic.rb index 2de5f1436..6ab4ffb34 100644 --- a/elasticsearch-rails/lib/rails/templates/01-basic.rb +++ b/elasticsearch-rails/lib/rails/templates/01-basic.rb @@ -338,6 +338,10 @@ def search git tag: "basic" git log: "--reverse --oneline" +# ----- Install Webpacker ------------------------------------------------------------------------- + +run 'rails webpacker:install' + # ----- Start the application --------------------------------------------------------------------- unless ENV['RAILS_NO_SERVER_START'] From f9e86dec7e90f5a334602e85dfffdcb69215c27c Mon Sep 17 00:00:00 2001 From: anandvc <anand.chhatpar@gmail.com> Date: Fri, 31 May 2019 02:40:01 -0700 Subject: [PATCH 447/582] Updated README.MD (#771) * Updated README.MD Include the step to create the index for the model that we'll import. * Commented to encourage asynchronous index creation I've added a comment so that users are encouraged to do `create_index!` asynchronously and not at the same time as `import`. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index ba6ff4814..d7394ded9 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,10 @@ class Article < ActiveRecord::Base include Elasticsearch::Model::Callbacks end +# Index creation right at import time is not encouraged. +# Typically, you would call create_index! asynchronously (e.g. in a cron job) +# However, we are adding it here so that this usage example can run correctly. +Article.__elasticsearch__.create_index! Article.import @articles = Article.search('foobar').records From 2adb8dd2e29c4815024ac441404b9df6e12ad6c7 Mon Sep 17 00:00:00 2001 From: "Tejas R. Mandke" <tejas.mandke@gmail.com> Date: Thu, 4 Jul 2019 07:26:30 -0700 Subject: [PATCH 448/582] [Model] Fix naming with inheritance when using Proxy (#887) * Add naming inheritance tests when using a proxy * Skip circular call to index_name/document_type when Proxy is used and inheritance is enabled --- .../lib/elasticsearch/model/naming.rb | 7 +- .../model/naming_inheritance_spec.rb | 210 ++++++++++++------ 2 files changed, 151 insertions(+), 66 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/naming.rb b/elasticsearch-model/lib/elasticsearch/model/naming.rb index 9db0a9b08..5a8e8f895 100644 --- a/elasticsearch-model/lib/elasticsearch/model/naming.rb +++ b/elasticsearch-model/lib/elasticsearch/model/naming.rb @@ -96,7 +96,12 @@ def implicit(prop) if Elasticsearch::Model.settings[:inheritance_enabled] self.ancestors.each do |klass| - next if klass == self + # When Naming is included in Proxy::ClassMethods the actual model + # is among its ancestors. We don't want to call the actual model + # since it will result in the same call to the same instance of + # Proxy::ClassMethods. To prevent this we also skip the ancestor + # that is the target. + next if klass == self || self.respond_to?(:target) && klass == self.target break if value = klass.respond_to?(prop) && klass.send(prop) end end diff --git a/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb b/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb index 0d8efb9a3..ef13d590d 100644 --- a/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb @@ -19,105 +19,185 @@ describe 'naming inheritance' do - before(:all) do - class ::TestBase - extend ActiveModel::Naming + context 'without using proxy' do + before(:all) do + TestBase = Class.new do + extend ActiveModel::Naming - extend Elasticsearch::Model::Naming::ClassMethods - include Elasticsearch::Model::Naming::InstanceMethods - end + extend Elasticsearch::Model::Naming::ClassMethods + include Elasticsearch::Model::Naming::InstanceMethods + end + + Animal = Class.new TestBase do + extend ActiveModel::Naming + + extend Elasticsearch::Model::Naming::ClassMethods + include Elasticsearch::Model::Naming::InstanceMethods + + index_name "mammals" + document_type "mammal" + end + + Dog = Class.new Animal - class ::Animal < ::TestBase - extend ActiveModel::Naming + module ::MyNamespace + Dog = Class.new Animal + end + + Cat = Class.new Animal do + extend ActiveModel::Naming - extend Elasticsearch::Model::Naming::ClassMethods - include Elasticsearch::Model::Naming::InstanceMethods + extend Elasticsearch::Model::Naming::ClassMethods + include Elasticsearch::Model::Naming::InstanceMethods + + index_name "cats" + document_type "cat" + end - index_name "mammals" - document_type "mammal" end - class ::Dog < ::Animal + after(:all) do + remove_classes(TestBase, Animal, MyNamespace, Cat) end - module ::MyNamespace - class Dog < ::Animal - end + around(:all) do |example| + original_value = Elasticsearch::Model.settings[:inheritance_enabled] + Elasticsearch::Model.settings[:inheritance_enabled] = true + example.run + Elasticsearch::Model.settings[:inheritance_enabled] = original_value end - class ::Cat < ::Animal - extend ActiveModel::Naming - extend Elasticsearch::Model::Naming::ClassMethods - include Elasticsearch::Model::Naming::InstanceMethods + describe '#index_name' do + + it 'returns the default index name' do + expect(TestBase.index_name).to eq('test_bases') + expect(TestBase.new.index_name).to eq('test_bases') + end - index_name "cats" - document_type "cat" + it 'returns the explicit index name' do + expect(Animal.index_name).to eq('mammals') + expect(Animal.new.index_name).to eq('mammals') + + expect(Cat.index_name).to eq('cats') + expect(Cat.new.index_name).to eq('cats') + end + + it 'returns the ancestor index name' do + expect(Dog.index_name).to eq('mammals') + expect(Dog.new.index_name).to eq('mammals') + end + + it 'returns the ancestor index name for namespaced models' do + expect(::MyNamespace::Dog.index_name).to eq('mammals') + expect(::MyNamespace::Dog.new.index_name).to eq('mammals') + end end - end + describe '#document_type' do - after(:all) do - remove_classes(TestBase, Animal, MyNamespace, Cat) - end + it 'returns nil' do + expect(TestBase.document_type).to be_nil + expect(TestBase.new.document_type).to be_nil + end - around(:all) do |example| - original_value = Elasticsearch::Model.settings[:inheritance_enabled] - Elasticsearch::Model.settings[:inheritance_enabled] = true - example.run - Elasticsearch::Model.settings[:inheritance_enabled] = original_value - end + it 'returns the explicit document type' do + expect(Animal.document_type).to eq('mammal') + expect(Animal.new.document_type).to eq('mammal') + expect(Cat.document_type).to eq('cat') + expect(Cat.new.document_type).to eq('cat') + end - describe '#index_name' do + it 'returns the ancestor document type' do + expect(Dog.document_type).to eq('mammal') + expect(Dog.new.document_type).to eq('mammal') + end - it 'returns the default index name' do - expect(TestBase.index_name).to eq('test_bases') - expect(TestBase.new.index_name).to eq('test_bases') + it 'returns the ancestor document type for namespaced models' do + expect(::MyNamespace::Dog.document_type).to eq('mammal') + expect(::MyNamespace::Dog.new.document_type).to eq('mammal') + end end + end + + context 'when using proxy' do + before(:all) do + TestBase = Class.new do + extend ActiveModel::Naming + + include Elasticsearch::Model + end + + Animal = Class.new TestBase do + index_name "mammals" + document_type "mammal" + end + + Dog = Class.new Animal - it 'returns the explicit index name' do - expect(Animal.index_name).to eq('mammals') - expect(Animal.new.index_name).to eq('mammals') + module MyNamespace + Dog = Class.new Animal + end - expect(Cat.index_name).to eq('cats') - expect(Cat.new.index_name).to eq('cats') + Cat = Class.new Animal do + index_name "cats" + document_type "cat" + end end - it 'returns the ancestor index name' do - expect(Dog.index_name).to eq('mammals') - expect(Dog.new.index_name).to eq('mammals') + after(:all) do + remove_classes(TestBase, Animal, MyNamespace, Cat) end - it 'returns the ancestor index name for namespaced models' do - expect(::MyNamespace::Dog.index_name).to eq('mammals') - expect(::MyNamespace::Dog.new.index_name).to eq('mammals') + around(:all) do |example| + original_value = Elasticsearch::Model.settings[:inheritance_enabled] + Elasticsearch::Model.settings[:inheritance_enabled] = true + example.run + Elasticsearch::Model.settings[:inheritance_enabled] = original_value end - end - describe '#document_type' do - it 'returns nil' do - expect(TestBase.document_type).to be_nil - expect(TestBase.new.document_type).to be_nil - end + describe '#index_name' do - it 'returns the explicit document type' do - expect(Animal.document_type).to eq('mammal') - expect(Animal.new.document_type).to eq('mammal') + it 'returns the default index name' do + expect(TestBase.index_name).to eq('test_bases') + end - expect(Cat.document_type).to eq('cat') - expect(Cat.new.document_type).to eq('cat') - end + it 'returns the explicit index name' do + expect(Animal.index_name).to eq('mammals') - it 'returns the ancestor document type' do - expect(Dog.document_type).to eq('mammal') - expect(Dog.new.document_type).to eq('mammal') + expect(Cat.index_name).to eq('cats') + end + + it 'returns the ancestor index name' do + expect(Dog.index_name).to eq('mammals') + end + + it 'returns the ancestor index name for namespaced models' do + expect(::MyNamespace::Dog.index_name).to eq('mammals') + end end - it 'returns the ancestor document type for namespaced models' do - expect(::MyNamespace::Dog.document_type).to eq('mammal') - expect(::MyNamespace::Dog.new.document_type).to eq('mammal') + describe '#document_type' do + + it 'returns nil' do + expect(TestBase.document_type).to be_nil + end + + it 'returns the explicit document type' do + expect(Animal.document_type).to eq('mammal') + + expect(Cat.document_type).to eq('cat') + end + + it 'returns the ancestor document type' do + expect(Dog.document_type).to eq('mammal') + end + + it 'returns the ancestor document type for namespaced models' do + expect(::MyNamespace::Dog.document_type).to eq('mammal') + end end end end From 592dfcf4eae22123943119059a5900545bf90342 Mon Sep 17 00:00:00 2001 From: Stephen Tiberius Schor <stephenschor@nypl.org> Date: Thu, 4 Jul 2019 10:27:06 -0400 Subject: [PATCH 449/582] Replace 'http://www.elasticsearch.org' with 'https://www.elastic.co' in documentation. (#881) When Elasticsearch changed their FQDN from/www.elasticsearch.org to www.elastic.co, they preserved most of their page's paths but didn't setup granular redirect rules. For example: Going to `http://www.elasticsearch.org/contributor-agreement/` will redirect browsers to https://www.elastic.co/, but drop the path `/contributor-agreement/`. --- CONTRIBUTING.md | 2 +- README.md | 2 +- elasticsearch-model/README.md | 6 +++--- elasticsearch-model/lib/elasticsearch/model/indexing.rb | 6 +++--- elasticsearch-persistence/README.md | 2 +- elasticsearch-persistence/examples/notes/README.markdown | 2 +- elasticsearch-rails/README.md | 2 +- elasticsearch-rails/lib/rails/templates/01-basic.rb | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1bf5d2a13..161dff67d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ The process for contributing to any of the [Elasticsearch](https://github.com/el 2. Make sure your changes don't break any existing tests, and that you add tests for both bugfixes and new functionality. 3. **Sign the contributor license agreement.** -Please make sure you have signed the [Contributor License Agreement](http://www.elasticsearch.org/contributor-agreement/). We are not asking you to assign copyright to us, but to give us the right to distribute your code without restriction. We ask this of all contributors in order to assure our users of the origin and continuing existence of the code. You only need to sign the CLA once. +Please make sure you have signed the [Contributor License Agreement](https://www.elastic.co/contributor-agreement/). We are not asking you to assign copyright to us, but to give us the right to distribute your code without restriction. We ask this of all contributors in order to assure our users of the origin and continuing existence of the code. You only need to sign the CLA once. 4. Submit a pull request. Push your local changes to your forked copy of the repository and submit a pull request. In the pull request, describe what your changes do and mention the number of the issue where discussion has taken place, eg “Closes #123″. diff --git a/README.md b/README.md index d7394ded9..752d3e141 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ See more information in the documentation for the [`elasticsearch-extensions`]( This software is licensed under the Apache 2 license, quoted below. - Copyright (c) 2014 Elasticsearch <http://www.elasticsearch.org> + Copyright (c) 2014 Elasticsearch <https://www.elastic.co> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index b03ecf9a4..5986d4527 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -5,7 +5,7 @@ the [`elasticsearch`](https://github.com/elastic/elasticsearch-ruby) library. It aims to simplify integration of Ruby classes ("models"), commonly found e.g. in [Ruby on Rails](http://rubyonrails.org) applications, with the -[Elasticsearch](http://www.elasticsearch.org) search and analytics engine. +[Elasticsearch](https://www.elastic.co) search and analytics engine. ## Compatibility @@ -322,7 +322,7 @@ Elasticsearch::Model::Response::Response.__send__ :include, Elasticsearch::Model #### The Elasticsearch DSL In most situations, you'll want to pass the search definition -in the Elasticsearch [domain-specific language](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html) to the client: +in the Elasticsearch [domain-specific language](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html) to the client: ```ruby response = Article.search query: { match: { title: "Fox Dogs" } }, @@ -760,7 +760,7 @@ SERVER=start TEST_CLUSTER_COMMAND=$PWD/tmp/elasticsearch-1.0.0.RC1/bin/elasticse This software is licensed under the Apache 2 license, quoted below. - Copyright (c) 2014 Elasticsearch <http://www.elasticsearch.org> + Copyright (c) 2014 Elasticsearch <https://www.elastic.co> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index 69baf597e..05635bca0 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -30,7 +30,7 @@ module Model # module Indexing - # Wraps the [index settings](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/setup-configuration.html#configuration-index-settings) + # Wraps the [index settings](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) # class Settings attr_accessor :settings @@ -48,7 +48,7 @@ def as_json(options={}) end end - # Wraps the [index mappings](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping.html) + # Wraps the [index mappings](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html) # class Mappings attr_accessor :options, :type @@ -308,7 +308,7 @@ def delete_index!(options={}) # # Article.__elasticsearch__.refresh_index! index: 'my-index' # - # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-refresh.html + # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-refresh.html # def refresh_index!(options={}) target_index = options.delete(:index) || self.index_name diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index b58e9f026..d5cfd8acf 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -574,7 +574,7 @@ The ActiveRecord pattern has been deprecated as of version 6.0.0 of this gem. Pl This software is licensed under the Apache 2 license, quoted below. - Copyright (c) 2014 Elasticsearch <http://www.elasticsearch.org> + Copyright (c) 2014 Elasticsearch <https://www.elastic.co> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/elasticsearch-persistence/examples/notes/README.markdown b/elasticsearch-persistence/examples/notes/README.markdown index 2aa15ec32..0d9313d72 100644 --- a/elasticsearch-persistence/examples/notes/README.markdown +++ b/elasticsearch-persistence/examples/notes/README.markdown @@ -21,7 +21,7 @@ The application demonstrates: This software is licensed under the Apache 2 license, quoted below. - Copyright (c) 2014 Elasticsearch <http://www.elasticsearch.org> + Copyright (c) 2014 Elasticsearch <https://www.elastic.co> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/elasticsearch-rails/README.md b/elasticsearch-rails/README.md index eb0cb2782..cf389b34b 100644 --- a/elasticsearch-rails/README.md +++ b/elasticsearch-rails/README.md @@ -129,7 +129,7 @@ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elast This software is licensed under the Apache 2 license, quoted below. - Copyright (c) 2014 Elasticsearch <http://www.elasticsearch.org> + Copyright (c) 2014 Elasticsearch <https://www.elastic.co> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/elasticsearch-rails/lib/rails/templates/01-basic.rb b/elasticsearch-rails/lib/rails/templates/01-basic.rb index 6ab4ffb34..154eb1f90 100644 --- a/elasticsearch-rails/lib/rails/templates/01-basic.rb +++ b/elasticsearch-rails/lib/rails/templates/01-basic.rb @@ -108,7 +108,7 @@ create_file 'README.md', <<-README # Ruby on Rails and Elasticsearch: Example application -This application is an example of integrating the {Elasticsearch}[http://www.elasticsearch.org] +This application is an example of integrating the {Elasticsearch}[https://www.elastic.co] search engine with the {Ruby On Rails}[http://rubyonrails.org] web framework. It has been generated by application templates available at From 74330c29f15f232426f5b95034b727783d229c0d Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Thu, 11 Jul 2019 15:47:28 +0200 Subject: [PATCH 450/582] Test against Elasticsearch version 7.2.0 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d164a0b0c..e293c85ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,7 @@ matrix: env: global: - - ELASTICSEARCH_VERSION=7.0.0 + - ELASTICSEARCH_VERSION=7.2.0 - TEST_ES_SERVER=http://localhost:9250 - TEST_CLUSTER_PORT=9250 - QUIET=true From 3c125b09bc29b33953ba4d9f91fd9d65f1fbc8bd Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Thu, 11 Jul 2019 16:44:09 +0200 Subject: [PATCH 451/582] Test against latest JRuby version on Travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e293c85ac..fd694e762 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ matrix: jdk: oraclejdk8 env: RAILS_VERSIONS=4.0,5.0,6.0 - - rvm: jruby-9.2.5.0 + - rvm: jruby-9.2.7.0 jdk: oraclejdk8 env: RAILS_VERSIONS=5.0 From ddebdef350e8954feb4caab4d222c7a2ff98eda5 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Mon, 15 Jul 2019 16:09:38 +0200 Subject: [PATCH 452/582] Update license text in READMEs --- README.md | 27 +++++++++++++++------------ elasticsearch-model/README.md | 27 +++++++++++++++------------ elasticsearch-persistence/README.md | 27 +++++++++++++++------------ elasticsearch-rails/README.md | 27 +++++++++++++++------------ 4 files changed, 60 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 752d3e141..c4c0b1453 100644 --- a/README.md +++ b/README.md @@ -153,16 +153,19 @@ See more information in the documentation for the [`elasticsearch-extensions`]( This software is licensed under the Apache 2 license, quoted below. - Copyright (c) 2014 Elasticsearch <https://www.elastic.co> - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. + Licensed to Elasticsearch B.V. under one or more contributor + license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright + ownership. Elasticsearch B.V. licenses this file to you under + the Apache License, Version 2.0 (the "License"); you may + not use this file except in compliance with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 5986d4527..698db95de 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -760,16 +760,19 @@ SERVER=start TEST_CLUSTER_COMMAND=$PWD/tmp/elasticsearch-1.0.0.RC1/bin/elasticse This software is licensed under the Apache 2 license, quoted below. - Copyright (c) 2014 Elasticsearch <https://www.elastic.co> - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. + Licensed to Elasticsearch B.V. under one or more contributor + license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright + ownership. Elasticsearch B.V. licenses this file to you under + the Apache License, Version 2.0 (the "License"); you may + not use this file except in compliance with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index d5cfd8acf..49807186c 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -574,16 +574,19 @@ The ActiveRecord pattern has been deprecated as of version 6.0.0 of this gem. Pl This software is licensed under the Apache 2 license, quoted below. - Copyright (c) 2014 Elasticsearch <https://www.elastic.co> - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. + Licensed to Elasticsearch B.V. under one or more contributor + license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright + ownership. Elasticsearch B.V. licenses this file to you under + the Apache License, Version 2.0 (the "License"); you may + not use this file except in compliance with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. diff --git a/elasticsearch-rails/README.md b/elasticsearch-rails/README.md index cf389b34b..0f6c69bfd 100644 --- a/elasticsearch-rails/README.md +++ b/elasticsearch-rails/README.md @@ -129,16 +129,19 @@ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elast This software is licensed under the Apache 2 license, quoted below. - Copyright (c) 2014 Elasticsearch <https://www.elastic.co> - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. + Licensed to Elasticsearch B.V. under one or more contributor + license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright + ownership. Elasticsearch B.V. licenses this file to you under + the Apache License, Version 2.0 (the "License"); you may + not use this file except in compliance with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. From 460475dd1e1ccaab4a4535dd8e7c346fdc58be12 Mon Sep 17 00:00:00 2001 From: Sung Kim <sunggyun187@gmail.com> Date: Mon, 15 Jul 2019 10:12:38 -0400 Subject: [PATCH 453/582] Update 03-expert.rb (#547) Added to Redis::CannnotConnectError To help debug for those who do not have Redis installed / the server currently running. --- elasticsearch-rails/lib/rails/templates/03-expert.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/elasticsearch-rails/lib/rails/templates/03-expert.rb b/elasticsearch-rails/lib/rails/templates/03-expert.rb index 176da41df..dbb4104cb 100644 --- a/elasticsearch-rails/lib/rails/templates/03-expert.rb +++ b/elasticsearch-rails/lib/rails/templates/03-expert.rb @@ -34,7 +34,8 @@ rescue Redis::CannotConnectError puts say_status "ERROR", "Redis not available", :red - say_status "", "This template uses an asynchronous indexer via Sidekiq, and requires a running Redis server." + say_status "", "This template uses an asynchronous indexer via Sidekiq, and requires a running Redis server. + Make sure you have installed Redis (brew install redis) and that you have launched the server" exit(1) end From 4b3046f2f11dea5a2ec039c90d648497260aef2a Mon Sep 17 00:00:00 2001 From: lulalala <mark@goodlife.tw> Date: Thu, 18 Jul 2019 17:17:15 +0800 Subject: [PATCH 454/582] Doc: fix `return` example as @example only allows one line (#889) Add return example --- .../lib/elasticsearch/model/importing.rb | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/importing.rb b/elasticsearch-model/lib/elasticsearch/model/importing.rb index ed89de433..0534c2519 100644 --- a/elasticsearch-model/lib/elasticsearch/model/importing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/importing.rb @@ -53,7 +53,29 @@ module ClassMethods # # @yield [Hash] Gives the Hash with the Elasticsearch response to the block # - # @return [Fixnum] Number of errors encountered during importing + # @return [Fixnum] default, number of errors encountered during importing + # @return [Array<Hash>] if +return+ option is specified to be +"errors"+, + # contains only those failed items in the response +items+ key, e.g.: + # + # [ + # { + # "index" => { + # "error" => 'FAILED', + # "_index" => "test", + # "_type" => "_doc", + # "_id" => '1', + # "_version" => 1, + # "result" => "foo", + # "_shards" => { + # "total" => 1, + # "successful" => 0, + # "failed" => 1 + # }, + # "status" => 400 + # } + # } + # ] + # # # @example Import all records into the index # @@ -99,20 +121,19 @@ module ClassMethods # # @example Update the batch before yielding it # - # class Article - # # ... - # def self.enrich(batch) - # batch.each do |item| - # item.metadata = MyAPI.get_metadata(item.id) - # end - # batch - # end - # end + # class Article + # # ... + # def self.enrich(batch) + # batch.each do |item| + # item.metadata = MyAPI.get_metadata(item.id) + # end + # batch + # end + # end # # Article.import preprocess: :enrich # - # @example Return an array of error elements instead of the number of errors, eg. - # to try importing these records again + # @example Return an array of error elements instead of the number of errors, e.g. to try importing these records again # # Article.import return: 'errors' # From 8c407be4f2a06668ea02db9600e772b776be3521 Mon Sep 17 00:00:00 2001 From: Sagar Patel <sagarpatel8384@users.noreply.github.com> Date: Thu, 18 Jul 2019 08:32:59 -0400 Subject: [PATCH 455/582] Fix Elasticsearch-model README typo under Importing the Data (#565) --- elasticsearch-model/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 698db95de..21aae0cf0 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -151,7 +151,7 @@ for information about the Ruby client API. ### Importing the data -The first thing you'll want to do is importing your data into the index: +The first thing you'll want to do is import your data into the index: ```ruby Article.import From 0f224491e942d96b8561409337aa7adfb2ca65eb Mon Sep 17 00:00:00 2001 From: mveer99 <mahaveer@outlook.in> Date: Mon, 22 Jul 2019 16:58:26 +0530 Subject: [PATCH 456/582] Add nested mapping for ActiveRecord associations (#890) --- .../examples/activerecord_associations.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/elasticsearch-model/examples/activerecord_associations.rb b/elasticsearch-model/examples/activerecord_associations.rb index 4d7125cbe..79a02aa65 100644 --- a/elasticsearch-model/examples/activerecord_associations.rb +++ b/elasticsearch-model/examples/activerecord_associations.rb @@ -98,6 +98,22 @@ module Searchable module Indexing + #Index only the specified fields + settings do + mappings dynamic: false do + indexes :categories, type: :object do + indexes :title + end + indexes :authors, type: :object do + indexes :full_name + indexes :department + end + indexes :comments, type: :object do + indexes :text + end + end + end + # Customize the JSON serialization for Elasticsearch def as_indexed_json(options={}) self.as_json( From f9a87ff3d07a6a95c771ed34f348f71e0f856221 Mon Sep 17 00:00:00 2001 From: Emily S <emily.stolfo@elastic.co> Date: Fri, 26 Jul 2019 13:15:54 +0200 Subject: [PATCH 457/582] [MODEL] Refactor module/mixin organization for clarity (#893) * [MODEL] Refactor module inclusion for clarity * [MODEL] Don't use Hash#transform_values as it's not available in ruby 2.2 * [MODEL] Make before_save callbacks consistent --- .../lib/elasticsearch/model.rb | 45 +++----------- .../lib/elasticsearch/model/indexing.rb | 18 +++--- .../lib/elasticsearch/model/proxy.rb | 62 ++++++++++++------- 3 files changed, 61 insertions(+), 64 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index 23e121896..492bd6ff1 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -89,9 +89,10 @@ module Model # Adds the `Elasticsearch::Model` functionality to the including class. # - # * Creates the `__elasticsearch__` class and instance methods, pointing to the proxy object - # * Includes the necessary modules in the proxy classes - # * Sets up delegation for crucial methods such as `search`, etc. + # * Creates the `__elasticsearch__` class and instance method. These methods return a proxy object with + # other common methods defined on them. + # * The module includes other modules with further functionality. + # * Sets up delegation for common methods such as `import` and `search`. # # @example Include the module in the `Article` model definition # @@ -108,44 +109,16 @@ def self.included(base) base.class_eval do include Elasticsearch::Model::Proxy - Elasticsearch::Model::Proxy::ClassMethodsProxy.class_eval do - include Elasticsearch::Model::Client::ClassMethods - include Elasticsearch::Model::Naming::ClassMethods - include Elasticsearch::Model::Indexing::ClassMethods - include Elasticsearch::Model::Searching::ClassMethods - end - - Elasticsearch::Model::Proxy::InstanceMethodsProxy.class_eval do - include Elasticsearch::Model::Client::InstanceMethods - include Elasticsearch::Model::Naming::InstanceMethods - include Elasticsearch::Model::Indexing::InstanceMethods - include Elasticsearch::Model::Serializing::InstanceMethods - end - - Elasticsearch::Model::Proxy::InstanceMethodsProxy.class_eval <<-CODE, __FILE__, __LINE__ + 1 - def as_indexed_json(options={}) - target.respond_to?(:as_indexed_json) ? target.__send__(:as_indexed_json, options) : super - end - CODE - - # Delegate important methods to the `__elasticsearch__` proxy, unless they are defined already - # + # Delegate common methods to the `__elasticsearch__` ClassMethodsProxy, unless they are defined already class << self METHODS.each do |method| - delegate method, to: :__elasticsearch__ unless self.public_instance_methods.include?(method) + delegate method, to: :__elasticsearch__ unless self.respond_to?(method) end end - - # Mix the importing module into the proxy - # - self.__elasticsearch__.class_eval do - include Elasticsearch::Model::Importing::ClassMethods - include Adapter.from_class(base).importing_mixin - end - - # Add to the registry if it's a class (and not in intermediate module) - Registry.add(base) if base.is_a?(Class) end + + # Add to the model to the registry if it's a class (and not in intermediate module) + Registry.add(base) if base.is_a?(Class) end # Access the module settings diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index 05635bca0..91fe5ce90 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -338,13 +338,17 @@ def self.included(base) # # @see #update_document # - base.before_save do |i| - if i.class.instance_methods.include?(:changes_to_save) # Rails 5.1 - i.instance_variable_set(:@__changed_model_attributes, - Hash[ i.changes_to_save.map { |key, value| [key, value.last] } ]) - elsif i.class.instance_methods.include?(:changes) - i.instance_variable_set(:@__changed_model_attributes, - Hash[ i.changes.map { |key, value| [key, value.last] } ]) + base.before_save do |obj| + if obj.respond_to?(:changes_to_save) # Rails 5.1 + changes_to_save = obj.changes_to_save + elsif obj.respond_to?(:changes) + changes_to_save = obj.changes + end + + if changes_to_save + attrs = obj.instance_variable_get(:@__changed_model_attributes) || {} + latest_changes = changes_to_save.inject({}) { |latest_changes, (k,v)| latest_changes.merge!(k => v.last) } + obj.instance_variable_set(:@__changed_model_attributes, attrs.merge(latest_changes)) end end if base.respond_to?(:before_save) end diff --git a/elasticsearch-model/lib/elasticsearch/model/proxy.rb b/elasticsearch-model/lib/elasticsearch/model/proxy.rb index 31ad5c3d3..013bdac12 100644 --- a/elasticsearch-model/lib/elasticsearch/model/proxy.rb +++ b/elasticsearch-model/lib/elasticsearch/model/proxy.rb @@ -19,15 +19,15 @@ module Elasticsearch module Model # This module provides a proxy interfacing between the including class and - # {Elasticsearch::Model}, preventing the pollution of the including class namespace. + # `Elasticsearch::Model`, preventing the pollution of the including class namespace. # # The only "gateway" between the model and Elasticsearch::Model is the - # `__elasticsearch__` class and instance method. + # `#__elasticsearch__` class and instance method. # # The including class must be compatible with # [ActiveModel](https://github.com/rails/rails/tree/master/activemodel). # - # @example Include the {Elasticsearch::Model} module into an `Article` model + # @example Include the `Elasticsearch::Model` module into an `Article` model # # class Article < ActiveRecord::Base # include Elasticsearch::Model @@ -53,21 +53,19 @@ module Proxy # module and the functionality is accessible via the proxy. # def self.included(base) + base.class_eval do - # {ClassMethodsProxy} instance, accessed as `MyModel.__elasticsearch__` - # + + # `ClassMethodsProxy` instance, accessed as `MyModel.__elasticsearch__` def self.__elasticsearch__ &block @__elasticsearch__ ||= ClassMethodsProxy.new(self) @__elasticsearch__.instance_eval(&block) if block_given? @__elasticsearch__ end - # {InstanceMethodsProxy}, accessed as `@mymodel.__elasticsearch__` - # - def __elasticsearch__ &block - @__elasticsearch__ ||= InstanceMethodsProxy.new(self) - @__elasticsearch__.instance_eval(&block) if block_given? - @__elasticsearch__ + # Mix the importing module into the `ClassMethodsProxy` + self.__elasticsearch__.class_eval do + include Adapter.from_class(base).importing_mixin end # Register a callback for storing changed attributes for models which implement @@ -75,18 +73,28 @@ def __elasticsearch__ &block # # @see http://api.rubyonrails.org/classes/ActiveModel/Dirty.html # - before_save do |i| - if i.class.instance_methods.include?(:changes_to_save) # Rails 5.1 - a = i.__elasticsearch__.instance_variable_get(:@__changed_model_attributes) || {} - i.__elasticsearch__.instance_variable_set(:@__changed_model_attributes, - a.merge(Hash[ i.changes_to_save.map { |key, value| [key, value.last] } ])) - elsif i.class.instance_methods.include?(:changes) - a = i.__elasticsearch__.instance_variable_get(:@__changed_model_attributes) || {} - i.__elasticsearch__.instance_variable_set(:@__changed_model_attributes, - a.merge(Hash[ i.changes.map { |key, value| [key, value.last] } ])) + before_save do |obj| + if obj.respond_to?(:changes_to_save) # Rails 5.1 + changes_to_save = obj.changes_to_save + elsif obj.respond_to?(:changes) + changes_to_save = obj.changes + end + + if changes_to_save + attrs = obj.__elasticsearch__.instance_variable_get(:@__changed_model_attributes) || {} + latest_changes = changes_to_save.inject({}) { |latest_changes, (k,v)| latest_changes.merge!(k => v.last) } + obj.__elasticsearch__.instance_variable_set(:@__changed_model_attributes, attrs.merge(latest_changes)) end end if respond_to?(:before_save) end + + # {InstanceMethodsProxy}, accessed as `@mymodel.__elasticsearch__` + # + def __elasticsearch__ &block + @__elasticsearch__ ||= InstanceMethodsProxy.new(self) + @__elasticsearch__.instance_eval(&block) if block_given? + @__elasticsearch__ + end end # @overload dup @@ -130,6 +138,11 @@ def inspect # class ClassMethodsProxy include Base + include Elasticsearch::Model::Client::ClassMethods + include Elasticsearch::Model::Naming::ClassMethods + include Elasticsearch::Model::Indexing::ClassMethods + include Elasticsearch::Model::Searching::ClassMethods + include Elasticsearch::Model::Importing::ClassMethods end # A proxy interfacing between Elasticsearch::Model instance methods and model instance methods @@ -138,6 +151,10 @@ class ClassMethodsProxy # class InstanceMethodsProxy include Base + include Elasticsearch::Model::Client::InstanceMethods + include Elasticsearch::Model::Naming::InstanceMethods + include Elasticsearch::Model::Indexing::InstanceMethods + include Elasticsearch::Model::Serializing::InstanceMethods def klass target.class @@ -153,8 +170,11 @@ def class def as_json(options={}) target.as_json(options) end - end + def as_indexed_json(options={}) + target.respond_to?(:as_indexed_json) ? target.__send__(:as_indexed_json, options) : super + end + end end end end From 62191f8cd494595b6ad4dee5e22a06c1091c1805 Mon Sep 17 00:00:00 2001 From: Emily S <emily.stolfo@elastic.co> Date: Fri, 26 Jul 2019 15:58:36 +0200 Subject: [PATCH 458/582] [MODEL] Only add the document_type to the request if it's defined (#894) --- .../lib/elasticsearch/model/indexing.rb | 51 ++++++++++--------- .../spec/elasticsearch/model/indexing_spec.rb | 6 +-- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index 91fe5ce90..16ad1b3e4 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -368,14 +368,13 @@ def self.included(base) # @see http://rubydoc.info/gems/elasticsearch-api/Elasticsearch/API/Actions:index # def index_document(options={}) - document = self.as_indexed_json - - client.index( - { index: index_name, - type: document_type, - id: self.id, - body: document }.merge(options) - ) + document = as_indexed_json + request = { index: index_name, + id: id, + body: document } + request.merge!(type: document_type) if document_type + + client.index(request.merge!(options)) end # Deletes the model instance from the index @@ -392,11 +391,11 @@ def index_document(options={}) # @see http://rubydoc.info/gems/elasticsearch-api/Elasticsearch/API/Actions:delete # def delete_document(options={}) - client.delete( - { index: index_name, - type: document_type, - id: self.id }.merge(options) - ) + request = { index: index_name, + id: self.id } + request.merge!(type: document_type) if document_type + + client.delete(request.merge!(options)) end # Tries to gather the changed attributes of a model instance @@ -431,12 +430,14 @@ def update_document(options={}) attributes_in_database end - client.update( - { index: index_name, - type: document_type, - id: self.id, - body: { doc: attributes } }.merge(options) - ) unless attributes.empty? + unless attributes.empty? + request = { index: index_name, + id: self.id, + body: { doc: attributes } } + request.merge!(type: document_type) if document_type + + client.update(request.merge!(options)) + end else index_document(options) end @@ -457,12 +458,12 @@ def update_document(options={}) # @return [Hash] The response from Elasticsearch # def update_document_attributes(attributes, options={}) - client.update( - { index: index_name, - type: document_type, - id: self.id, - body: { doc: attributes } }.merge(options) - ) + request = { index: index_name, + id: self.id, + body: { doc: attributes } } + request.merge!(type: document_type) if document_type + + client.update(request.merge!(options)) end end diff --git a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb index 3ee081969..09e7717d5 100644 --- a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb @@ -418,7 +418,7 @@ def changes expect(instance).to receive(:client).and_return(client) expect(instance).to receive(:as_indexed_json).and_return('JSON') expect(instance).to receive(:index_name).and_return('foo') - expect(instance).to receive(:document_type).and_return('bar') + expect(instance).to receive(:document_type).twice.and_return('bar') expect(instance).to receive(:id).and_return('1') end @@ -458,7 +458,7 @@ def changes before do expect(instance).to receive(:client).and_return(client) expect(instance).to receive(:index_name).and_return('foo') - expect(instance).to receive(:document_type).and_return('bar') + expect(instance).to receive(:document_type).twice.and_return('bar') expect(instance).to receive(:id).and_return('1') end @@ -602,7 +602,7 @@ def changes before do expect(instance).to receive(:client).and_return(client) expect(instance).to receive(:index_name).and_return('foo') - expect(instance).to receive(:document_type).and_return('bar') + expect(instance).to receive(:document_type).twice.and_return('bar') expect(instance).to receive(:id).and_return('1') instance.instance_variable_set(:@__changed_model_attributes, { author: 'john' }) end From 006108e87e24fa7d55e706776c015447b60d75c6 Mon Sep 17 00:00:00 2001 From: Emily S <emstolfo@gmail.com> Date: Mon, 5 Aug 2019 15:43:49 +0200 Subject: [PATCH 459/582] [MODEL] Add warning and documentation about STI support being deprecated (#895) * [MODEL] Add warning and documentation about STI support being deprecated * [MODEL] Minor change to STI deprecation warning * [MODEL] Freeze string constant depreaction warning --- elasticsearch-model/README.md | 18 +++++++++------ .../lib/elasticsearch/model.rb | 23 ++++++++++++------- .../model/naming_inheritance_spec.rb | 6 ++--- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 21aae0cf0..0a798d8b3 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -732,13 +732,8 @@ module and its submodules for technical information. The module provides a common `settings` method to customize various features. -At the moment, the only supported setting is `:inheritance_enabled`, which makes the class receiving the module -respect index names and document types of a super-class, eg. in case you're using "single table inheritance" (STI) -in Rails: - -```ruby -Elasticsearch::Model.settings[:inheritance_enabled] = true -``` +Before version 7.0.0 of the gem, the only supported setting was `:inheritance_enabled`. This setting has been deprecated +and removed. ## Development and Community @@ -756,6 +751,15 @@ curl -# https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticse SERVER=start TEST_CLUSTER_COMMAND=$PWD/tmp/elasticsearch-1.0.0.RC1/bin/elasticsearch bundle exec rake test:all ``` +### Single Table Inheritance support + +Versions < 7.0.0 of this gem supported inheritance-- more specifically, `Single Table Inheritance`. With this feature, +settings on a parent model could be inherited by a child model leading to different model documents being indexed +into the same Elasticsearch index. This feature depended on the ability to set a `type` for a document in Elasticsearch. +The Elasticsearch team has deprecated support for `types`, as is described [here](https://www.elastic.co/guide/en/elasticsearch/reference/current/removal-of-types.html) +so this gem has also removed support as it encourages an anti-pattern. Please save different model documents in +separate indices or implement an artificial `type` field manually in each document. + ## License This software is licensed under the Apache 2 license, quoted below. diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index 492bd6ff1..32a79f911 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -121,12 +121,6 @@ class << self Registry.add(base) if base.is_a?(Class) end - # Access the module settings - # - def self.settings - @settings ||= {} - end - module ClassMethods # Get the client common for all models # @@ -183,7 +177,7 @@ def search(query_or_payload, models=[], options={}) # @note Inheritance is disabled by default. # def inheritance_enabled - @inheritance_enabled ||= false + @settings[:inheritance_enabled] ||= false end # Enable inheritance of index_name and document_type @@ -193,8 +187,21 @@ def inheritance_enabled # Elasticsearch::Model.inheritance_enabled = true # def inheritance_enabled=(inheritance_enabled) - @inheritance_enabled = inheritance_enabled + warn STI_DEPRECATION_WARNING + @settings[:inheritance_enabled] = inheritance_enabled + end + + # Access the module settings + # + def settings + @settings ||= {} end + + private + + STI_DEPRECATION_WARNING = "DEPRECATION WARNING: Support for Single Table Inheritance (STI) is deprecated " + + "and will be removed in version 7.0.0.\nPlease save different model documents in separate indices and refer " + + "to the Elasticsearch documentation for more information.".freeze end extend ClassMethods diff --git a/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb b/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb index ef13d590d..826e44362 100644 --- a/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb @@ -61,10 +61,10 @@ module ::MyNamespace end around(:all) do |example| - original_value = Elasticsearch::Model.settings[:inheritance_enabled] - Elasticsearch::Model.settings[:inheritance_enabled] = true + original_value = Elasticsearch::Model.inheritance_enabled + Elasticsearch::Model.inheritance_enabled = true example.run - Elasticsearch::Model.settings[:inheritance_enabled] = original_value + Elasticsearch::Model.inheritance_enabled = original_value end From 82e1db0fb7b2ce0eaa1c51237bab360bf7562805 Mon Sep 17 00:00:00 2001 From: Masataka Pocke Kuwabara <p.ck.t22@gmail.com> Date: Tue, 6 Aug 2019 00:07:55 +0900 Subject: [PATCH 460/582] Remove unnecessary `self` as a return value of `initialize` method (#897) --- elasticsearch-model/lib/elasticsearch/model/response/records.rb | 1 - elasticsearch-persistence/examples/notes/application.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/records.rb b/elasticsearch-model/lib/elasticsearch/model/response/records.rb index 0cb832cb2..41aa6fd70 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/records.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/records.rb @@ -45,7 +45,6 @@ def initialize(klass, response, options={}) metaclass.__send__ :include, adapter.records_mixin self.options = options - self end # Returns the hit IDs diff --git a/elasticsearch-persistence/examples/notes/application.rb b/elasticsearch-persistence/examples/notes/application.rb index 27f796abb..6c122999d 100644 --- a/elasticsearch-persistence/examples/notes/application.rb +++ b/elasticsearch-persistence/examples/notes/application.rb @@ -35,7 +35,6 @@ def initialize(attributes={}) __add_date __extract_tags __truncate_text - self end def method_missing(method_name, *arguments, &block) From c1937d23fb0bea4bed8c3c6222c91fcb9a2ff254 Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Thu, 8 Aug 2019 17:18:17 +0200 Subject: [PATCH 461/582] [MODEL] Only warn if inheritance_enabled is set to true --- .../lib/elasticsearch/model.rb | 2 +- .../spec/elasticsearch/model/module_spec.rb | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index 32a79f911..d63c36015 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -187,7 +187,7 @@ def inheritance_enabled # Elasticsearch::Model.inheritance_enabled = true # def inheritance_enabled=(inheritance_enabled) - warn STI_DEPRECATION_WARNING + warn STI_DEPRECATION_WARNING if inheritance_enabled @settings[:inheritance_enabled] = inheritance_enabled end diff --git a/elasticsearch-model/spec/elasticsearch/model/module_spec.rb b/elasticsearch-model/spec/elasticsearch/model/module_spec.rb index 3f7d682d0..b731e183f 100644 --- a/elasticsearch-model/spec/elasticsearch/model/module_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/module_spec.rb @@ -89,5 +89,30 @@ def self.search(query, options={}) expect(Elasticsearch::Model.settings[:foo]).to eq('bar') end end + + context 'when \'inheritance_enabled\' is set' do + + around do |example| + original_value = Elasticsearch::Model.settings[:inheritance_enabled] + example.run + Elasticsearch::Model.settings[:inheritance_enabled] = original_value + end + + context 'when \'inheritance_enabled\' is true' do + + it 'warns with a deprecation message' do + expect(Elasticsearch::Model).to receive(:warn) + Elasticsearch::Model.inheritance_enabled = true + end + end + + context 'when \'inheritance_enabled\' is false' do + + it 'does not warn' do + expect(Elasticsearch::Model).not_to receive(:warn) + Elasticsearch::Model.inheritance_enabled = false + end + end + end end end From 284746f35a76e227d23c61f5af2f3f287eea6faa Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Mon, 12 Aug 2019 09:57:40 +0200 Subject: [PATCH 462/582] [MODEL] Tweak STI deprecation description --- elasticsearch-model/README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 0a798d8b3..95425bd96 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -754,11 +754,13 @@ SERVER=start TEST_CLUSTER_COMMAND=$PWD/tmp/elasticsearch-1.0.0.RC1/bin/elasticse ### Single Table Inheritance support Versions < 7.0.0 of this gem supported inheritance-- more specifically, `Single Table Inheritance`. With this feature, -settings on a parent model could be inherited by a child model leading to different model documents being indexed -into the same Elasticsearch index. This feature depended on the ability to set a `type` for a document in Elasticsearch. -The Elasticsearch team has deprecated support for `types`, as is described [here](https://www.elastic.co/guide/en/elasticsearch/reference/current/removal-of-types.html) -so this gem has also removed support as it encourages an anti-pattern. Please save different model documents in -separate indices or implement an artificial `type` field manually in each document. +elasticsearch settings (index mappings, etc) on a parent model could be inherited by a child model leading to different +model documents being indexed into the same Elasticsearch index. This feature depended on the ability to set a `type` +for a document in Elasticsearch. The Elasticsearch team has deprecated support for `types`, as is described +[here.](https://www.elastic.co/guide/en/elasticsearch/reference/current/removal-of-types.html) +This gem will also remove support for types and `Single Table Inheritance` in version 7.0 as it enables an anti-pattern. +Please save different model documents in separate indices. If you want to use STI, you can include an artificial +`type` field manually in each document and use it in other operations. ## License From 3d995093af90e16eebb45b9a2ec3ed6a88264e9e Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Mon, 12 Aug 2019 10:05:07 +0200 Subject: [PATCH 463/582] Ensure that 6.x is in the Compatibility table --- elasticsearch-model/README.md | 1 + elasticsearch-rails/README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 95425bd96..704f51e55 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -19,6 +19,7 @@ is compatible with the Elasticsearch `master` branch, therefore, with the next m | 0.1 | → | 1.x | | 2.x | → | 2.x | | 5.x | → | 5.x | +| 6.x | → | 6.x | | master | → | master | ## Installation diff --git a/elasticsearch-rails/README.md b/elasticsearch-rails/README.md index 0f6c69bfd..deb781f3c 100644 --- a/elasticsearch-rails/README.md +++ b/elasticsearch-rails/README.md @@ -16,6 +16,7 @@ is compatible with the Elasticsearch `master` branch, therefore, with the next m | 0.1 | → | 1.x | | 2.x | → | 2.x | | 5.x | → | 5.x | +| 6.x | → | 6.x | | master | → | master | ## Installation From da79b17b617bfaf4f544100b502990900c2b8faf Mon Sep 17 00:00:00 2001 From: Emily S <emily.stolfo@elastic.co> Date: Mon, 19 Aug 2019 14:29:34 +0200 Subject: [PATCH 464/582] Test Rails 6 (#902) * [MODEL] Test Rails 6 * [MODEL] Catch mongoid load errors due to gem version conflicts * Also remoe Gemfile.lock in top-level directory --- Rakefile | 1 + elasticsearch-model/gemfiles/6.0.gemfile | 6 +++--- elasticsearch-model/spec/spec_helper.rb | 12 +++++++++--- elasticsearch-model/spec/support/app.rb | 11 +++++++++-- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Rakefile b/Rakefile index 07dbe81ee..40f26188b 100644 --- a/Rakefile +++ b/Rakefile @@ -82,6 +82,7 @@ namespace :bundle do end sh "rm -f #{__current__.join('elasticsearch-model/gemfiles')}/*.lock" end + sh "rm -f Gemfile.lock" end namespace :test do diff --git a/elasticsearch-model/gemfiles/6.0.gemfile b/elasticsearch-model/gemfiles/6.0.gemfile index 484a9df3b..dd859f68b 100644 --- a/elasticsearch-model/gemfiles/6.0.gemfile +++ b/elasticsearch-model/gemfiles/6.0.gemfile @@ -25,10 +25,10 @@ source 'https://rubygems.org' gemspec path: '../' -gem 'activemodel', '6.0.0.rc1' -gem 'activerecord', '6.0.0.rc1' +gem 'activemodel', '6.0.0' +gem 'activerecord', '6.0.0' gem 'sqlite3' unless defined?(JRUBY_VERSION) -gem 'mongoid', '~> 6' +#gem 'mongoid', '~> 6' group :development, :testing do gem 'rspec' diff --git a/elasticsearch-model/spec/spec_helper.rb b/elasticsearch-model/spec/spec_helper.rb index 94e3e5dfd..eb4e5fbbc 100644 --- a/elasticsearch-model/spec/spec_helper.rb +++ b/elasticsearch-model/spec/spec_helper.rb @@ -23,7 +23,11 @@ require 'elasticsearch/model' require 'hashie/version' require 'active_model' -require 'mongoid' +begin + require 'mongoid' +rescue LoadError + $stderr.puts("'mongoid' gem could not be loaded") +end require 'yaml' require 'active_record' @@ -151,8 +155,10 @@ def test_mongoid? client.database.command(ping: 1) && true end end and true - rescue Timeout::Error, LoadError, Mongo::Error => e - client.close + rescue LoadError + $stderr.puts("'mongoid' gem could not be loaded") + rescue Timeout::Error, Mongo::Error => e + client.close if client $stderr.puts("MongoDB not installed or running: #{e}") end end diff --git a/elasticsearch-model/spec/support/app.rb b/elasticsearch-model/spec/support/app.rb index e009f9371..9169cb09f 100644 --- a/elasticsearch-model/spec/support/app.rb +++ b/elasticsearch-model/spec/support/app.rb @@ -26,9 +26,7 @@ require 'support/app/article_for_pagination' require 'support/app/article_with_dynamic_index_name' require 'support/app/episode' -require 'support/app/image' require 'support/app/series' -require 'support/app/mongoid_article' require 'support/app/article' require 'support/app/article_no_type' require 'support/app/searchable' @@ -37,3 +35,12 @@ require 'support/app/authorship' require 'support/app/comment' require 'support/app/post' + + +# Mongoid models +begin + require 'support/app/image' + require 'support/app/mongoid_article' +rescue + $stderr.puts("'mongoid' gem is not installed, could not load Mongoid models") +end \ No newline at end of file From b4286359198b66287daaa8cb1c02a83d4c365e11 Mon Sep 17 00:00:00 2001 From: Emily S <emily.stolfo@elastic.co> Date: Wed, 21 Aug 2019 11:31:10 +0200 Subject: [PATCH 465/582] [MODEL] Remove deprecation warning and functionality for Single Table Inheritance (#900) --- .../lib/elasticsearch/model.rb | 25 --- .../lib/elasticsearch/model/naming.rb | 16 +- .../spec/elasticsearch/model/module_spec.rb | 25 --- .../model/naming_inheritance_spec.rb | 203 ------------------ 4 files changed, 1 insertion(+), 268 deletions(-) delete mode 100644 elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index d63c36015..d50308d8d 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -172,36 +172,11 @@ def search(query_or_payload, models=[], options={}) Response::Response.new(models, request) end - # Check if inheritance is enabled - # - # @note Inheritance is disabled by default. - # - def inheritance_enabled - @settings[:inheritance_enabled] ||= false - end - - # Enable inheritance of index_name and document_type - # - # @example Enable inheritance - # - # Elasticsearch::Model.inheritance_enabled = true - # - def inheritance_enabled=(inheritance_enabled) - warn STI_DEPRECATION_WARNING if inheritance_enabled - @settings[:inheritance_enabled] = inheritance_enabled - end - # Access the module settings # def settings @settings ||= {} end - - private - - STI_DEPRECATION_WARNING = "DEPRECATION WARNING: Support for Single Table Inheritance (STI) is deprecated " + - "and will be removed in version 7.0.0.\nPlease save different model documents in separate indices and refer " + - "to the Elasticsearch documentation for more information.".freeze end extend ClassMethods diff --git a/elasticsearch-model/lib/elasticsearch/model/naming.rb b/elasticsearch-model/lib/elasticsearch/model/naming.rb index 5a8e8f895..9ab489db6 100644 --- a/elasticsearch-model/lib/elasticsearch/model/naming.rb +++ b/elasticsearch-model/lib/elasticsearch/model/naming.rb @@ -92,21 +92,7 @@ def document_type=(name) private def implicit(prop) - value = nil - - if Elasticsearch::Model.settings[:inheritance_enabled] - self.ancestors.each do |klass| - # When Naming is included in Proxy::ClassMethods the actual model - # is among its ancestors. We don't want to call the actual model - # since it will result in the same call to the same instance of - # Proxy::ClassMethods. To prevent this we also skip the ancestor - # that is the target. - next if klass == self || self.respond_to?(:target) && klass == self.target - break if value = klass.respond_to?(prop) && klass.send(prop) - end - end - - value || self.send("default_#{prop}") + self.send("default_#{prop}") end def default_index_name diff --git a/elasticsearch-model/spec/elasticsearch/model/module_spec.rb b/elasticsearch-model/spec/elasticsearch/model/module_spec.rb index b731e183f..3f7d682d0 100644 --- a/elasticsearch-model/spec/elasticsearch/model/module_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/module_spec.rb @@ -89,30 +89,5 @@ def self.search(query, options={}) expect(Elasticsearch::Model.settings[:foo]).to eq('bar') end end - - context 'when \'inheritance_enabled\' is set' do - - around do |example| - original_value = Elasticsearch::Model.settings[:inheritance_enabled] - example.run - Elasticsearch::Model.settings[:inheritance_enabled] = original_value - end - - context 'when \'inheritance_enabled\' is true' do - - it 'warns with a deprecation message' do - expect(Elasticsearch::Model).to receive(:warn) - Elasticsearch::Model.inheritance_enabled = true - end - end - - context 'when \'inheritance_enabled\' is false' do - - it 'does not warn' do - expect(Elasticsearch::Model).not_to receive(:warn) - Elasticsearch::Model.inheritance_enabled = false - end - end - end end end diff --git a/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb b/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb deleted file mode 100644 index 826e44362..000000000 --- a/elasticsearch-model/spec/elasticsearch/model/naming_inheritance_spec.rb +++ /dev/null @@ -1,203 +0,0 @@ -# Licensed to Elasticsearch B.V. under one or more contributor -# license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright -# ownership. Elasticsearch B.V. licenses this file to you under -# the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -require 'spec_helper' - -describe 'naming inheritance' do - - context 'without using proxy' do - before(:all) do - TestBase = Class.new do - extend ActiveModel::Naming - - extend Elasticsearch::Model::Naming::ClassMethods - include Elasticsearch::Model::Naming::InstanceMethods - end - - Animal = Class.new TestBase do - extend ActiveModel::Naming - - extend Elasticsearch::Model::Naming::ClassMethods - include Elasticsearch::Model::Naming::InstanceMethods - - index_name "mammals" - document_type "mammal" - end - - Dog = Class.new Animal - - module ::MyNamespace - Dog = Class.new Animal - end - - Cat = Class.new Animal do - extend ActiveModel::Naming - - extend Elasticsearch::Model::Naming::ClassMethods - include Elasticsearch::Model::Naming::InstanceMethods - - index_name "cats" - document_type "cat" - end - - end - - after(:all) do - remove_classes(TestBase, Animal, MyNamespace, Cat) - end - - around(:all) do |example| - original_value = Elasticsearch::Model.inheritance_enabled - Elasticsearch::Model.inheritance_enabled = true - example.run - Elasticsearch::Model.inheritance_enabled = original_value - end - - - describe '#index_name' do - - it 'returns the default index name' do - expect(TestBase.index_name).to eq('test_bases') - expect(TestBase.new.index_name).to eq('test_bases') - end - - it 'returns the explicit index name' do - expect(Animal.index_name).to eq('mammals') - expect(Animal.new.index_name).to eq('mammals') - - expect(Cat.index_name).to eq('cats') - expect(Cat.new.index_name).to eq('cats') - end - - it 'returns the ancestor index name' do - expect(Dog.index_name).to eq('mammals') - expect(Dog.new.index_name).to eq('mammals') - end - - it 'returns the ancestor index name for namespaced models' do - expect(::MyNamespace::Dog.index_name).to eq('mammals') - expect(::MyNamespace::Dog.new.index_name).to eq('mammals') - end - end - - describe '#document_type' do - - it 'returns nil' do - expect(TestBase.document_type).to be_nil - expect(TestBase.new.document_type).to be_nil - end - - it 'returns the explicit document type' do - expect(Animal.document_type).to eq('mammal') - expect(Animal.new.document_type).to eq('mammal') - - expect(Cat.document_type).to eq('cat') - expect(Cat.new.document_type).to eq('cat') - end - - it 'returns the ancestor document type' do - expect(Dog.document_type).to eq('mammal') - expect(Dog.new.document_type).to eq('mammal') - end - - it 'returns the ancestor document type for namespaced models' do - expect(::MyNamespace::Dog.document_type).to eq('mammal') - expect(::MyNamespace::Dog.new.document_type).to eq('mammal') - end - end - end - - context 'when using proxy' do - before(:all) do - TestBase = Class.new do - extend ActiveModel::Naming - - include Elasticsearch::Model - end - - Animal = Class.new TestBase do - index_name "mammals" - document_type "mammal" - end - - Dog = Class.new Animal - - module MyNamespace - Dog = Class.new Animal - end - - Cat = Class.new Animal do - index_name "cats" - document_type "cat" - end - end - - after(:all) do - remove_classes(TestBase, Animal, MyNamespace, Cat) - end - - around(:all) do |example| - original_value = Elasticsearch::Model.settings[:inheritance_enabled] - Elasticsearch::Model.settings[:inheritance_enabled] = true - example.run - Elasticsearch::Model.settings[:inheritance_enabled] = original_value - end - - - describe '#index_name' do - - it 'returns the default index name' do - expect(TestBase.index_name).to eq('test_bases') - end - - it 'returns the explicit index name' do - expect(Animal.index_name).to eq('mammals') - - expect(Cat.index_name).to eq('cats') - end - - it 'returns the ancestor index name' do - expect(Dog.index_name).to eq('mammals') - end - - it 'returns the ancestor index name for namespaced models' do - expect(::MyNamespace::Dog.index_name).to eq('mammals') - end - end - - describe '#document_type' do - - it 'returns nil' do - expect(TestBase.document_type).to be_nil - end - - it 'returns the explicit document type' do - expect(Animal.document_type).to eq('mammal') - - expect(Cat.document_type).to eq('cat') - end - - it 'returns the ancestor document type' do - expect(Dog.document_type).to eq('mammal') - end - - it 'returns the ancestor document type for namespaced models' do - expect(::MyNamespace::Dog.document_type).to eq('mammal') - end - end - end -end From 00e889d29cae28db88d333d68f458be2728e4e0b Mon Sep 17 00:00:00 2001 From: Emily Stolfo <emstolfo@gmail.com> Date: Wed, 21 Aug 2019 15:54:15 +0200 Subject: [PATCH 466/582] Release 7.0.0 --- elasticsearch-model/lib/elasticsearch/model/version.rb | 2 +- elasticsearch-persistence/elasticsearch-persistence.gemspec | 2 +- .../lib/elasticsearch/persistence/version.rb | 2 +- elasticsearch-rails/lib/elasticsearch/rails/version.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/version.rb b/elasticsearch-model/lib/elasticsearch/model/version.rb index 27561c41d..4d6e36d2e 100644 --- a/elasticsearch-model/lib/elasticsearch/model/version.rb +++ b/elasticsearch-model/lib/elasticsearch/model/version.rb @@ -17,6 +17,6 @@ module Elasticsearch module Model - VERSION = "7.0.0.pre" + VERSION = "7.0.0" end end diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index de668fcc9..069f5c0d3 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -41,7 +41,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 1.9.3" s.add_dependency "elasticsearch", '~> 7' - s.add_dependency "elasticsearch-model", '7.0.0.pre' + s.add_dependency "elasticsearch-model", '7.0.0' s.add_dependency "activesupport", '> 4' s.add_dependency "activemodel", '> 4' s.add_dependency "hashie" diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb index 1293aa265..2953d86a6 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb @@ -17,6 +17,6 @@ module Elasticsearch module Persistence - VERSION = '7.0.0.pre' + VERSION = '7.0.0' end end diff --git a/elasticsearch-rails/lib/elasticsearch/rails/version.rb b/elasticsearch-rails/lib/elasticsearch/rails/version.rb index 619d889a2..6d5c428e3 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/version.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/version.rb @@ -17,6 +17,6 @@ module Elasticsearch module Rails - VERSION = "7.0.0.pre" + VERSION = "7.0.0" end end From 19851a0273d74a2a80a99dd0309f0052046646b5 Mon Sep 17 00:00:00 2001 From: Pavel Shushpan <pavelshushpan@gmail.com> Date: Tue, 19 Nov 2019 20:51:16 +0300 Subject: [PATCH 467/582] Small update README for asynchronous Callbacks (#907) Sidekiq requires `perform` method for workers, with `perform_async` it doesn't work. --- elasticsearch-model/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 704f51e55..72662f4ee 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -533,7 +533,7 @@ class Indexer Logger = Sidekiq.logger.level == Logger::DEBUG ? Sidekiq.logger : nil Client = Elasticsearch::Client.new host: 'localhost:9200', logger: Logger - def perform_async(operation, record_id) + def perform(operation, record_id) logger.debug [operation, "ID: #{record_id}"] case operation.to_s From baea670d24b5c42ee994b880db9491b31da4598b Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Wed, 20 Nov 2019 10:02:33 +0000 Subject: [PATCH 468/582] Update README, adds backwards compatibility information. (#914) --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c4c0b1453..0c7565394 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ Elasticsearch client and Ruby API is provided by the The libraries are compatible with Ruby 1.9.3 and higher. -The version numbers follow the Elasticsearch major versions, and the `master` branch -is compatible with the Elasticsearch `master` branch, therefore, with the next major version. +The version numbers follow the Elasticsearch major versions. The `master` branch is compatible with +the Elasticsearch `master` branch, therefore, with the next major version. | Rubygem | | Elasticsearch | |:-------------:|:-:| :-----------: | @@ -30,8 +30,15 @@ is compatible with the Elasticsearch `master` branch, therefore, with the next m | 2.x | → | 2.x | | 5.x | → | 5.x | | 6.x | → | 6.x | +| 7.x | → | 7.x | | master | → | master | +Use a release that matches the major version of Elasticsearch in your stack. Each client version is +backwards compatible with all minor versions of the same major version. + +Check out [Elastic product end of life dates](https://www.elastic.co/support/eol) +to learn which releases are still actively supported and tested. + ## Installation Install each library from [Rubygems](https://rubygems.org/gems/elasticsearch): From f6d800d9020797b6520885d7bfd277d6bd0975c4 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Tue, 14 Jan 2020 12:47:55 +0000 Subject: [PATCH 469/582] Update Travis versions --- .../travis_before_script.sh | 0 .travis.yml | 36 +++++++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) rename travis_before_script.sh => .ci/travis_before_script.sh (100%) mode change 100644 => 100755 diff --git a/travis_before_script.sh b/.ci/travis_before_script.sh old mode 100644 new mode 100755 similarity index 100% rename from travis_before_script.sh rename to .ci/travis_before_script.sh diff --git a/.travis.yml b/.travis.yml index fd694e762..ddb68c295 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,25 +15,47 @@ branches: only: - master - 6.x + - 7.x matrix: include: - - rvm: 2.2 + - rvm: 2.2.10 jdk: oraclejdk8 - env: RAILS_VERSIONS=3.0 + env: RAILS_VERSIONS=5.0 - rvm: 2.3.8 jdk: oraclejdk8 env: RAILS_VERSIONS=5.0 - - rvm: 2.6.2 + - rvm: 2.4.9 + jdk: oraclejdk8 + env: RAILS_VERSIONS=5.0 + + - rvm: 2.5.7 + jdk: oraclejdk8 + env: RAILS_VERSIONS=5.0,6.0 + + - rvm: 2.6.5 jdk: oraclejdk8 env: RAILS_VERSIONS=4.0,5.0,6.0 - - rvm: jruby-9.2.7.0 + - rvm: 2.7.0 + jdk: oraclejdk8 + env: RAILS_VERSIONS=5.0,6.0 + + - rvm: ruby-head + jdk: oraclejdk8 + env: RAILS_VERSIONS=5.0,6.0 + + - rvm: jruby-9.2.9.0 jdk: oraclejdk8 env: RAILS_VERSIONS=5.0 + allow_failures: + - rvm: ruby-head + jdk: oraclejdk8 + env: RAILS_VERSIONS=5.0,6.0 + env: global: - ELASTICSEARCH_VERSION=7.2.0 @@ -42,8 +64,10 @@ env: - QUIET=true before_install: - - source ./travis_before_script.sh - - gem update --system + - source ./.ci/travis_before_script.sh + - if [ $TRAVIS_RUBY_VERSION != "2.2.10" ]; then + gem update --system; + fi - gem update bundler - gem --version - bundle version From 635536fe54be1c35e041391779763d87749a11ef Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Fri, 28 Feb 2020 19:58:52 +0000 Subject: [PATCH 470/582] Update rake version --- Gemfile | 2 +- elasticsearch-model/elasticsearch-model.gemspec | 2 +- elasticsearch-persistence/elasticsearch-persistence.gemspec | 2 +- elasticsearch-rails/elasticsearch-rails.gemspec | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 466fb946e..b3f93c953 100644 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,7 @@ source 'https://rubygems.org' -gem "rake", "~> 11.1" +gem "rake", "~> 12" gem 'elasticsearch-extensions' diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index fdfeac1fa..1fe334041 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -45,7 +45,7 @@ Gem::Specification.new do |s| s.add_dependency "hashie" s.add_development_dependency "bundler" - s.add_development_dependency "rake", "~> 11.1" + s.add_development_dependency "rake", "~> 12" s.add_development_dependency "elasticsearch-extensions" diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index 069f5c0d3..fd2c6fa95 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -47,7 +47,7 @@ Gem::Specification.new do |s| s.add_dependency "hashie" s.add_development_dependency "bundler" - s.add_development_dependency "rake", "~> 11.1" + s.add_development_dependency "rake", "~> 12" s.add_development_dependency "oj" unless defined?(JRUBY_VERSION) diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index 0911b111c..1af576d9f 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -41,7 +41,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 1.9.3" s.add_development_dependency "bundler" - s.add_development_dependency "rake", "~> 11.1" + s.add_development_dependency "rake", "~> 12" s.add_development_dependency "elasticsearch-extensions" From ccf604d5fba4e370707d33cb289650edc1259715 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 2 Mar 2020 16:52:14 +0000 Subject: [PATCH 471/582] Fix multi_model_spec --- .../adapters/mongoid/multi_model_spec.rb | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb index 952cda672..d702eecab 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb @@ -24,10 +24,10 @@ begin ActiveRecord::Schema.define(:version => 1) do - create_table Episode.table_name do |t| - t.string :name - t.datetime :created_at, :default => 'NOW()' - end + create_table Episode.table_name do |t| + t.string :name + t.datetime :created_at, :default => 'NOW()' + end end rescue end @@ -36,22 +36,22 @@ before do clear_tables(Episode, Image) Episode.__elasticsearch__.create_index! force: true - Episode.create name: "TheEpisode" - Episode.create name: "A great Episode" - Episode.create name: "The greatest Episode" + Episode.create name: 'TheEpisode' + Episode.create name: 'A great Episode' + Episode.create name: 'The greatest Episode' Episode.__elasticsearch__.refresh_index! Image.__elasticsearch__.create_index! force: true - Image.create! name: "The Image" - Image.create! name: "A great Image" - Image.create! name: "The greatest Image" + Image.create! name: 'The Image' + Image.create! name: 'A great Image' + Image.create! name: 'The greatest Image' Image.__elasticsearch__.refresh_index! Image.__elasticsearch__.client.cluster.health wait_for_status: 'yellow' end after do [Episode, Image].each do |model| - model.__elasticsearch__.client.delete_by_query(index: model.index_name, q: '*') + model.__elasticsearch__.client.delete_by_query(index: model.index_name, q: '*', body: {}) model.delete_all model.__elasticsearch__.refresh_index! end From 1ef2531be578d53db04e603a8ec42c76933895cd Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 2 Mar 2020 17:08:39 +0000 Subject: [PATCH 472/582] Update Ruby versions, drop 2.2 and 2.3 --- .travis.yml | 14 ++------------ README.md | 2 +- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index ddb68c295..69dfc5553 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,14 +19,6 @@ branches: matrix: include: - - rvm: 2.2.10 - jdk: oraclejdk8 - env: RAILS_VERSIONS=5.0 - - - rvm: 2.3.8 - jdk: oraclejdk8 - env: RAILS_VERSIONS=5.0 - - rvm: 2.4.9 jdk: oraclejdk8 env: RAILS_VERSIONS=5.0 @@ -37,7 +29,7 @@ matrix: - rvm: 2.6.5 jdk: oraclejdk8 - env: RAILS_VERSIONS=4.0,5.0,6.0 + env: RAILS_VERSIONS=5.0,6.0 - rvm: 2.7.0 jdk: oraclejdk8 @@ -65,9 +57,7 @@ env: before_install: - source ./.ci/travis_before_script.sh - - if [ $TRAVIS_RUBY_VERSION != "2.2.10" ]; then - gem update --system; - fi + - gem update --system; - gem update bundler - gem --version - bundle version diff --git a/README.md b/README.md index 0c7565394..65cd5d1c1 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Elasticsearch client and Ruby API is provided by the ## Compatibility -The libraries are compatible with Ruby 1.9.3 and higher. +The libraries are compatible with Ruby 2.4 and higher. The version numbers follow the Elasticsearch major versions. The `master` branch is compatible with the Elasticsearch `master` branch, therefore, with the next major version. From 16e53f557a8f152d86959a822c9b692135837bfa Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 2 Mar 2020 17:15:07 +0000 Subject: [PATCH 473/582] Fix/cleanup Rakefile --- elasticsearch-model/Rakefile | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index 76291b92a..b31a4b5dc 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -15,17 +15,13 @@ # specific language governing permissions and limitations # under the License. -require "bundler/gem_tasks" +require 'bundler/gem_tasks' -desc "Run unit tests" -task :default => 'test:unit' -task :test => 'test:unit' +desc 'Run unit tests' +task default: 'test:all' +task test: 'test:all' -if RUBY_VERSION < '2.3' - GEMFILES = ['3.0.gemfile', '4.0.gemfile', '5.0.gemfile'] -else - GEMFILES = ['4.0.gemfile', '5.0.gemfile', '6.0.gemfile'] -end +GEMFILES = ['4.0.gemfile', '5.0.gemfile', '6.0.gemfile'] namespace :bundle do desc 'Install dependencies for all the Gemfiles in /gemfiles. Optionally define env variable RAILS_VERSIONS. E.g. RAILS_VERSIONS=3.0,5.0' @@ -47,7 +43,6 @@ end require 'rake/testtask' namespace :test do - desc 'Run all tests. Optionally define env variable RAILS_VERSIONS. E.g. RAILS_VERSIONS=3.0,5.0' task :all, [:rails_versions] do |task, args| gemfiles = ENV['RAILS_VERSIONS'] ? ENV['RAILS_VERSIONS'].split(',').map {|v| "#{v}.gemfile"} : GEMFILES From 1c34f6c8f019de29adad6b9758443bd74f6ba4a4 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 2 Mar 2020 17:16:06 +0000 Subject: [PATCH 474/582] Fix specs to include body in spec_helper --- .../model/adapters/active_record/import_spec.rb | 13 +------------ elasticsearch-model/spec/spec_helper.rb | 12 ++++++++++-- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb index 39ff5095f..e4a6088c8 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb @@ -18,7 +18,6 @@ require 'spec_helper' describe 'Elasticsearch::Model::Adapter::ActiveRecord Importing' do - before(:all) do ActiveRecord::Schema.define(:version => 1) do create_table :import_articles do |t| @@ -43,11 +42,9 @@ end describe '#import' do - context 'when no search criteria is specified' do - before do - 10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" } + 10.times { |i| ImportArticle.create! title: 'Test', views: i.to_s } ImportArticle.import ImportArticle.__elasticsearch__.refresh_index! end @@ -58,7 +55,6 @@ end context 'when batch size is specified' do - before do 10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" } end @@ -82,7 +78,6 @@ end context 'when a scope is specified' do - before do 10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" } ImportArticle.import(scope: 'popular', force: true) @@ -95,7 +90,6 @@ end context 'when a query is specified' do - before do 10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" } ImportArticle.import(query: -> { where('views >= 3') }) @@ -108,7 +102,6 @@ end context 'when there are invalid documents' do - let!(:result) do 10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" } new_article @@ -132,7 +125,6 @@ end context 'when a transform proc is specified' do - before do 10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" } ImportArticle.import( transform: ->(a) {{ index: { data: { name: a.title, foo: 'BAR' } }}} ) @@ -151,7 +143,6 @@ end context 'when the model has a default scope' do - around(:all) do |example| 10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" } ImportArticle.instance_eval { default_scope { where('views > 3') } } @@ -170,7 +161,6 @@ end context 'when there is a default scope and a query specified' do - around(:all) do |example| 10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" } ImportArticle.instance_eval { default_scope { where('views > 3') } } @@ -189,7 +179,6 @@ end context 'when the batch is empty' do - before do ImportArticle.delete_all ImportArticle.import diff --git a/elasticsearch-model/spec/spec_helper.rb b/elasticsearch-model/spec/spec_helper.rb index eb4e5fbbc..7bf7f41dc 100644 --- a/elasticsearch-model/spec/spec_helper.rb +++ b/elasticsearch-model/spec/spec_helper.rb @@ -81,8 +81,16 @@ def active_record_at_least_4? # @since 6.0.1 def clear_indices(*models) models.each do |model| - begin; Elasticsearch::Model.client.delete_by_query(index: model.index_name, q: '*'); rescue; end - end and true + begin + Elasticsearch::Model.client.delete_by_query( + index: model.index_name, + q: '*', + body: {} + ) + rescue + end + end + true end # Delete all documents from the tables of the provided list of models. From 63c24c9fe48a74d00c65145cc55c32f4c6907448 Mon Sep 17 00:00:00 2001 From: Kelly Joseph Price <kelly@collaborativedrug.com> Date: Sat, 23 Sep 2017 12:21:15 -0700 Subject: [PATCH 475/582] Add pipeline to bulk params --- .../lib/elasticsearch/model/importing.rb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/importing.rb b/elasticsearch-model/lib/elasticsearch/model/importing.rb index 0534c2519..c0519ef9f 100644 --- a/elasticsearch-model/lib/elasticsearch/model/importing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/importing.rb @@ -143,6 +143,7 @@ def import(options={}, &block) target_index = options.delete(:index) || index_name target_type = options.delete(:type) || document_type transform = options.delete(:transform) || __transform + pipeline = options.delete(:pipeline) return_value = options.delete(:return) || 'count' unless transform.respond_to?(:call) @@ -158,10 +159,15 @@ def import(options={}, &block) end __find_in_batches(options) do |batch| - response = client.bulk \ - index: target_index, - type: target_type, - body: __batch_to_bulk(batch, transform) + params = { + index: target_index, + type: target_type, + body: __batch_to_bulk(batch, transform) + } + + params[:pipeline] = pipeline if pipeline + + response = client.bulk params yield response if block_given? From 589fd76f7eb388f5fcc1c4c4b854a47298e6dac6 Mon Sep 17 00:00:00 2001 From: Kelly Joseph Price <kelly@collaborativedrug.com> Date: Wed, 19 Sep 2018 12:26:01 -0700 Subject: [PATCH 476/582] Add spec --- .../spec/elasticsearch/model/importing_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb index f42166a25..19bbff715 100644 --- a/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb @@ -227,5 +227,17 @@ def importing_mixin end end end + + context 'when a pipeline is provided as an options' do + + before do + expect(DummyImportingModel).to receive(:client).and_return(client) + expect(client).to receive(:bulk).with(body: nil, index: 'foo', type: 'foo', pipeline: 'my-pipeline').and_return(response) + end + + it 'uses the pipeline option' do + expect(DummyImportingModel.import(pipeline: 'my-pipeline')).to eq(0) + end + end end end From a3ff5e3b595707e425c0aeb794b74f5e5074eac4 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 23 Mar 2020 12:47:39 +0000 Subject: [PATCH 477/582] [DOCS] Updates Readme Fixes #925 --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 65cd5d1c1..4a45c64ad 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ This repository contains various Ruby and Rails integrations for [Elasticsearch] * Templates for generating example Rails application Elasticsearch client and Ruby API is provided by the -**[elasticsearch-ruby](https://github.com/elasticsearch/elasticsearch-ruby)** project. +**[elasticsearch-ruby](https://github.com/elastic/elasticsearch-ruby)** project. ## Compatibility @@ -59,13 +59,13 @@ gem 'elasticsearch-rails', github: 'elastic/elasticsearch-rails', branch: '5.x' This project is split into three separate gems: -* [**`elasticsearch-model`**](https://github.com/elasticsearch/elasticsearch-rails/tree/master/elasticsearch-model), +* [**`elasticsearch-model`**](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-model), which contains search integration for Ruby/Rails models such as ActiveRecord::Base and Mongoid, -* [**`elasticsearch-persistence`**](https://github.com/elasticsearch/elasticsearch-rails/tree/master/elasticsearch-persistence), +* [**`elasticsearch-persistence`**](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-persistence), which provides a standalone persistence layer for Ruby/Rails objects and models -* [**`elasticsearch-rails`**](https://github.com/elasticsearch/elasticsearch-rails/tree/master/elasticsearch-rails), +* [**`elasticsearch-rails`**](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-rails), which contains various features for Ruby on Rails applications Example of a basic integration into an ActiveRecord-based model: @@ -88,7 +88,7 @@ Article.import ``` You can generate a simple Ruby on Rails application with a single command -(see the [other available templates](https://github.com/elasticsearch/elasticsearch-rails/tree/master/elasticsearch-rails#rails-application-templates)): +(see the [other available templates](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-rails#rails-application-templates)): ```bash rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/01-basic.rb @@ -115,21 +115,21 @@ repository.save Article.new(title: 'Test') ### Model -* [[README]](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-model/README.md) +* [[README]](https://github.com/elastic/elasticsearch-rails/blob/master/elasticsearch-model/README.md) * [[Documentation]](http://rubydoc.info/gems/elasticsearch-model/) -* [[Test Suite]](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-model/test) +* [[Test Suite]](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-model/spec/elasticsearch/model) ### Persistence -* [[README]](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-persistence/README.md) +* [[README]](https://github.com/elastic/elasticsearch-rails/blob/master/elasticsearch-persistence/README.md) * [[Documentation]](http://rubydoc.info/gems/elasticsearch-persistence/) -* [[Test Suite]](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-persistence/test) +* [[Test Suite]](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-persistence/spec) ### Rails -* [[README]](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-rails/README.md) +* [[README]](https://github.com/elastic/elasticsearch-rails/blob/master/elasticsearch-rails/README.md) * [[Documentation]](http://rubydoc.info/gems/elasticsearch-rails) -* [[Test Suite]](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-rails/test) +* [[Test Suite]](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-rails/spec) ## Development @@ -154,7 +154,7 @@ The test suite expects an Elasticsearch cluster running on port 9250, and **will TEST_CLUSTER_COMMAND=/tmp/builds/elasticsearch-2.0.0-SNAPSHOT/bin/elasticsearch TEST_CLUSTER_NODES=1 bundle exec rake test:cluster:start -See more information in the documentation for the [`elasticsearch-extensions`](https://github.com/elasticsearch/elasticsearch-ruby/tree/master/elasticsearch-extensions#testcluster) gem. +See more information in the documentation for the [`elasticsearch-extensions`](https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch-extensions#testcluster) gem. ## License From 82d799e910add123f2d95d8700ab607ba682a478 Mon Sep 17 00:00:00 2001 From: Orhan Toy <toyorhan@gmail.com> Date: Fri, 27 Mar 2020 16:33:11 +0100 Subject: [PATCH 478/582] [DOCS] Move `gem install` to a non-Ruby code block `gem install ...` is not Ruby, and the other places with `gem install` also use the indented block style. --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4a45c64ad..48f5e6f60 100644 --- a/README.md +++ b/README.md @@ -43,10 +43,8 @@ to learn which releases are still actively supported and tested. Install each library from [Rubygems](https://rubygems.org/gems/elasticsearch): -```ruby -gem install elasticsearch-model -gem install elasticsearch-rails -``` + gem install elasticsearch-model + gem install elasticsearch-rails To use an unreleased version, add it to your `Gemfile` for [Bundler](http://bundler.io): From 7dd63debddb8684e02b5e1855c944c238271e23f Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Wed, 8 Apr 2020 12:12:03 +0100 Subject: [PATCH 479/582] Test against Elasticsearch version 7.6.0 --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 69dfc5553..d829ee013 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,19 +19,19 @@ branches: matrix: include: - - rvm: 2.4.9 + - rvm: 2.4 jdk: oraclejdk8 env: RAILS_VERSIONS=5.0 - - rvm: 2.5.7 + - rvm: 2.5 jdk: oraclejdk8 env: RAILS_VERSIONS=5.0,6.0 - - rvm: 2.6.5 + - rvm: 2.6 jdk: oraclejdk8 env: RAILS_VERSIONS=5.0,6.0 - - rvm: 2.7.0 + - rvm: 2.7 jdk: oraclejdk8 env: RAILS_VERSIONS=5.0,6.0 @@ -50,7 +50,7 @@ matrix: env: global: - - ELASTICSEARCH_VERSION=7.2.0 + - ELASTICSEARCH_VERSION=7.6.2 - TEST_ES_SERVER=http://localhost:9250 - TEST_CLUSTER_PORT=9250 - QUIET=true From 87613bbd2fafcf77cabf5d7fced3377108010c6f Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Wed, 15 Apr 2020 11:09:32 +0100 Subject: [PATCH 480/582] Release 7.1.0 --- CHANGELOG.md | 9 +++++++-- elasticsearch-model/lib/elasticsearch/model/version.rb | 2 +- .../elasticsearch-persistence.gemspec | 2 +- .../lib/elasticsearch/persistence/version.rb | 2 +- elasticsearch-rails/lib/elasticsearch/rails/version.rb | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1ff80f10..b7af564d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ -## 7.0.0.pre +## 7.1.0 + +* Tested with elasticsearch Ruby client version 7.6.0 +* Updates rake version +* Adds pipeline to bulk params [commit](https://github.com/elastic/elasticsearch-rails/commit/63c24c9fe48a74d00c65145cc55c32f4c6907448) + +## 7.0.0 * Update test tasks and travis (#840) * `respond_to_missing?` to silence Ruby 2.4 warnings (#838) @@ -7,7 +13,6 @@ * Only execute update if document attributes is not empty (#862) * Remove bundler version requirement in gemspec files * 7.0 support (#875) -* Update version to 7.0.0.pre ### ActiveModel diff --git a/elasticsearch-model/lib/elasticsearch/model/version.rb b/elasticsearch-model/lib/elasticsearch/model/version.rb index 4d6e36d2e..cc92865fe 100644 --- a/elasticsearch-model/lib/elasticsearch/model/version.rb +++ b/elasticsearch-model/lib/elasticsearch/model/version.rb @@ -17,6 +17,6 @@ module Elasticsearch module Model - VERSION = "7.0.0" + VERSION = "7.1.0" end end diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index fd2c6fa95..ea21fadb4 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -41,7 +41,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 1.9.3" s.add_dependency "elasticsearch", '~> 7' - s.add_dependency "elasticsearch-model", '7.0.0' + s.add_dependency "elasticsearch-model", '7.1.0' s.add_dependency "activesupport", '> 4' s.add_dependency "activemodel", '> 4' s.add_dependency "hashie" diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb index 2953d86a6..e5ba66085 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb @@ -17,6 +17,6 @@ module Elasticsearch module Persistence - VERSION = '7.0.0' + VERSION = '7.1.0' end end diff --git a/elasticsearch-rails/lib/elasticsearch/rails/version.rb b/elasticsearch-rails/lib/elasticsearch/rails/version.rb index 6d5c428e3..23cddcf6f 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/version.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/version.rb @@ -17,6 +17,6 @@ module Elasticsearch module Rails - VERSION = "7.0.0" + VERSION = "7.1.0" end end From 80f85905c03e4b426794129463c8fa512e2757e1 Mon Sep 17 00:00:00 2001 From: Chris Yuska <chrisyuska@gmail.com> Date: Fri, 8 May 2020 17:12:33 -0400 Subject: [PATCH 481/582] fix: Ruby 2.7 deprecation warning on find_in_batches --- .../lib/elasticsearch/model/adapters/active_record.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb index 87a9e2ea7..dfa109161 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb @@ -102,7 +102,7 @@ def __find_in_batches(options={}, &block) scope = scope.__send__(named_scope) if named_scope scope = scope.instance_exec(&query) if query - scope.find_in_batches(options) do |batch| + scope.find_in_batches(**options) do |batch| batch = self.__send__(preprocess, batch) if preprocess yield(batch) if batch.present? end From 1c3226d8186af6e8446fa5091f5b27760c29f896 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Fri, 15 May 2020 16:47:50 +0100 Subject: [PATCH 482/582] Update README for generating app with template Related: #938 --- README.md | 15 ++++++++++++++- elasticsearch-rails/README.md | 5 +++-- .../lib/rails/templates/01-basic.rb | 4 ++-- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 48f5e6f60..4ef960d00 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,20 @@ Article.import ``` You can generate a simple Ruby on Rails application with a single command -(see the [other available templates](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-rails#rails-application-templates)): +(see the [other available templates](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-rails#rails-application-templates)). You'll need to have an Elasticsearch cluster running on your system before generating the app. The easiest way of getting this set up is by running it with Docker with this command: + +```bash + docker run \ + --name elasticsearch-rails-searchapp \ + --publish 9200:9200 \ + --env "discovery.type=single-node" \ + --env "cluster.name=elasticsearch-rails" \ + --env "cluster.routing.allocation.disk.threshold_enabled=false" \ + --rm \ + docker.elastic.co/elasticsearch/elasticsearch-oss:7.6.0 +``` + +Once Elasticsearch is running, you can generate the simple app with this command: ```bash rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/01-basic.rb diff --git a/elasticsearch-rails/README.md b/elasticsearch-rails/README.md index deb781f3c..687d016ae 100644 --- a/elasticsearch-rails/README.md +++ b/elasticsearch-rails/README.md @@ -17,6 +17,7 @@ is compatible with the Elasticsearch `master` branch, therefore, with the next m | 2.x | → | 2.x | | 5.x | → | 5.x | | 6.x | → | 6.x | +| 7.x | → | 7.x | | master | → | master | ## Installation @@ -98,8 +99,8 @@ You should see the duration of the request to Elasticsearch as part of each log ### Rails Application Templates You can generate a fully working example Ruby on Rails application, with an `Article` model and a search form, -to play with (it even downloads _Elasticsearch_ itself, generates the application skeleton and leaves you with -a _Git_ repository to explore the steps and the code) with the +to play with (it generates the application skeleton and leaves you with a _Git_ repository to explore the +steps and the code) with the [`01-basic.rb`](https://github.com/elastic/elasticsearch-rails/blob/master/elasticsearch-rails/lib/rails/templates/01-basic.rb) template: ```bash diff --git a/elasticsearch-rails/lib/rails/templates/01-basic.rb b/elasticsearch-rails/lib/rails/templates/01-basic.rb index 154eb1f90..e7305ecd5 100644 --- a/elasticsearch-rails/lib/rails/templates/01-basic.rb +++ b/elasticsearch-rails/lib/rails/templates/01-basic.rb @@ -44,7 +44,7 @@ # ----- Check for Elasticsearch ------------------------------------------------------------------- -required_elasticsearch_version = '6' +required_elasticsearch_version = '7' docker_command =<<-CMD.gsub(/\s{1,}/, ' ').strip docker run \ @@ -54,7 +54,7 @@ --env "cluster.name=elasticsearch-rails" \ --env "cluster.routing.allocation.disk.threshold_enabled=false" \ --rm \ - docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.0 + docker.elastic.co/elasticsearch/elasticsearch-oss:7.6.0 CMD begin From e0d46a864db2d1287afee1d88280a838df261bcb Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Tue, 26 May 2020 09:39:04 +0100 Subject: [PATCH 483/582] Add headers to missing file --- elasticsearch-rails/spec/lograge_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/elasticsearch-rails/spec/lograge_spec.rb b/elasticsearch-rails/spec/lograge_spec.rb index 4503b538e..6ab0763f9 100644 --- a/elasticsearch-rails/spec/lograge_spec.rb +++ b/elasticsearch-rails/spec/lograge_spec.rb @@ -1,3 +1,20 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Licensed to Elasticsearch B.V. under one or more contributor # license agreements. See the NOTICE file distributed with From 8628e73a15b12f84dbfba93c851d90fe2b08332f Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 29 Jun 2020 15:11:51 +0100 Subject: [PATCH 484/582] Linting --- Rakefile | 12 +-- elasticsearch-model/Gemfile | 2 +- .../elasticsearch-model.gemspec | 76 +++++++++---------- elasticsearch-persistence/Gemfile | 6 +- elasticsearch-rails/Gemfile | 10 ++- .../elasticsearch-rails.gemspec | 69 +++++++++-------- 6 files changed, 91 insertions(+), 84 deletions(-) diff --git a/Rakefile b/Rakefile index 40f26188b..9e3181a68 100644 --- a/Rakefile +++ b/Rakefile @@ -17,10 +17,10 @@ require 'pathname' -subprojects = [ 'elasticsearch-rails', 'elasticsearch-persistence' ] +subprojects = ['elasticsearch-rails', 'elasticsearch-persistence'] subprojects << 'elasticsearch-model' unless defined?(JRUBY_VERSION) -__current__ = Pathname( File.expand_path('..', __FILE__) ) +__current__ = Pathname(File.expand_path(__dir__)) def admin_client $admin_client ||= begin @@ -50,7 +50,7 @@ def admin_client end task :default do - system "rake --tasks" + system 'rake --tasks' end task :subprojects do @@ -62,11 +62,11 @@ task :subprojects do end end -desc "Alias for `bundle:install`" -task :bundle => 'bundle:install' +desc 'Alias for `bundle:install`' +task bundle: 'bundle:install' namespace :bundle do - desc "Run `bundle install` in all subprojects" + desc 'Run `bundle install` in all subprojects' task :install do subprojects.each do |project| puts '-'*80 diff --git a/elasticsearch-model/Gemfile b/elasticsearch-model/Gemfile index 6a70a3206..664ab4569 100644 --- a/elasticsearch-model/Gemfile +++ b/elasticsearch-model/Gemfile @@ -21,6 +21,6 @@ source 'https://rubygems.org' gemspec group :development, :testing do - gem 'rspec' gem 'pry-nav' + gem 'rspec' end diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index 1fe334041..a0cadd2b2 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -16,56 +16,54 @@ # under the License. # coding: utf-8 -lib = File.expand_path('../lib', __FILE__) + +lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'elasticsearch/model/version' Gem::Specification.new do |s| - s.name = "elasticsearch-model" + s.name = 'elasticsearch-model' s.version = Elasticsearch::Model::VERSION - s.authors = ["Karel Minarik"] - s.email = ["karel.minarik@elasticsearch.org"] - s.description = "ActiveModel/Record integrations for Elasticsearch." - s.summary = "ActiveModel/Record integrations for Elasticsearch." - s.homepage = "https://github.com/elasticsearch/elasticsearch-rails/" - s.license = "Apache 2" + s.authors = ['Karel Minarik'] + s.email = ['karel.minarik@elasticsearch.org'] + s.description = 'ActiveModel/Record integrations for Elasticsearch.' + s.summary = 'ActiveModel/Record integrations for Elasticsearch.' + s.homepage = 'https://github.com/elasticsearch/elasticsearch-rails/' + s.license = 'Apache 2' s.files = `git ls-files`.split($/) s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) } s.test_files = s.files.grep(%r{^(test|spec|features)/}) - s.require_paths = ["lib"] - - s.extra_rdoc_files = [ "README.md", "LICENSE.txt" ] - s.rdoc_options = [ "--charset=UTF-8" ] - - s.required_ruby_version = ">= 1.9.3" - - s.add_dependency "elasticsearch", '> 1' - s.add_dependency "activesupport", '> 3' - s.add_dependency "hashie" - - s.add_development_dependency "bundler" - s.add_development_dependency "rake", "~> 12" - - s.add_development_dependency "elasticsearch-extensions" + s.require_paths = ['lib'] - s.add_development_dependency "sqlite3" unless defined?(JRUBY_VERSION) - s.add_development_dependency "activemodel", "> 3" + s.extra_rdoc_files = ['README.md', 'LICENSE.txt'] + s.rdoc_options = ['--charset=UTF-8'] - s.add_development_dependency "oj" unless defined?(JRUBY_VERSION) - s.add_development_dependency "kaminari" - s.add_development_dependency "will_paginate" + s.required_ruby_version = '>= 2.4' - s.add_development_dependency "minitest" - s.add_development_dependency "test-unit" - s.add_development_dependency "shoulda-context" - s.add_development_dependency "mocha" - s.add_development_dependency "turn" - s.add_development_dependency "yard" - s.add_development_dependency "ruby-prof" unless defined?(JRUBY_VERSION) - s.add_development_dependency "pry" + s.add_dependency 'activesupport', '> 3' + s.add_dependency 'elasticsearch', '> 1' + s.add_dependency 'hashie' - s.add_development_dependency "simplecov" - s.add_development_dependency "cane" - s.add_development_dependency "require-prof" + s.add_development_dependency 'activemodel', '> 3' + s.add_development_dependency 'bundler' + s.add_development_dependency 'cane' + s.add_development_dependency 'elasticsearch-extensions' + s.add_development_dependency 'kaminari' + s.add_development_dependency 'minitest' + s.add_development_dependency 'mocha' + s.add_development_dependency 'pry' + s.add_development_dependency 'rake', '~> 12' + s.add_development_dependency 'require-prof' + s.add_development_dependency 'shoulda-context' + s.add_development_dependency 'simplecov' + s.add_development_dependency 'test-unit' + s.add_development_dependency 'turn' + s.add_development_dependency 'will_paginate' + s.add_development_dependency 'yard' + unless defined?(JRUBY_VERSION) + s.add_development_dependency 'oj' + s.add_development_dependency 'ruby-prof' + s.add_development_dependency 'sqlite3' + end end diff --git a/elasticsearch-persistence/Gemfile b/elasticsearch-persistence/Gemfile index 72fbae110..af517a160 100644 --- a/elasticsearch-persistence/Gemfile +++ b/elasticsearch-persistence/Gemfile @@ -20,11 +20,13 @@ source 'https://rubygems.org' # Specify your gem's dependencies in elasticsearch-persistence.gemspec gemspec -gem 'elasticsearch-model', :path => File.expand_path("../../elasticsearch-model", __FILE__), :require => false +gem 'elasticsearch-model', + path: File.expand_path('../elasticsearch-model', __dir__), + require: false gem 'virtus' group :development, :testing do - gem 'rspec' gem 'pry-nav' + gem 'rspec' end diff --git a/elasticsearch-rails/Gemfile b/elasticsearch-rails/Gemfile index ac2583628..6787846e4 100644 --- a/elasticsearch-rails/Gemfile +++ b/elasticsearch-rails/Gemfile @@ -20,12 +20,16 @@ source 'https://rubygems.org' # Specify your gem's dependencies in elasticsearch-rails.gemspec gemspec -gem 'elasticsearch-model', :path => File.expand_path("../../elasticsearch-model", __FILE__), :require => false -gem 'elasticsearch-persistence', :path => File.expand_path("../../elasticsearch-persistence", __FILE__), :require => false +gem 'elasticsearch-model', + path: File.expand_path('../elasticsearch-model', __dir__), + require: false +gem 'elasticsearch-persistence', + path: File.expand_path('../elasticsearch-persistence', __dir__), + require: false group :development, :testing do - gem 'rspec' gem 'pry-nav' + gem 'rspec' gem 'sqlite3' unless defined?(JRUBY_VERSION) end diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index 1af576d9f..400004a77 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -21,45 +21,48 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'elasticsearch/rails/version' Gem::Specification.new do |s| - s.name = "elasticsearch-rails" + s.name = 'elasticsearch-rails' s.version = Elasticsearch::Rails::VERSION - s.authors = ["Karel Minarik"] - s.email = ["karel.minarik@elasticsearch.org"] - s.description = "Ruby on Rails integrations for Elasticsearch." - s.summary = "Ruby on Rails integrations for Elasticsearch." - s.homepage = "https://github.com/elasticsearch/elasticsearch-rails/" - s.license = "Apache 2" + s.authors = ['Karel Minarik'] + s.email = ['karel.minarik@elasticsearch.org'] + s.description = 'Ruby on Rails integrations for Elasticsearch.' + s.summary = 'Ruby on Rails integrations for Elasticsearch.' + s.homepage = 'https://github.com/elasticsearch/elasticsearch-rails/' + s.license = 'Apache 2' + s.metadata = { + 'homepage_uri' => 'https://www.elastic.co/guide/en/elasticsearch/client/ruby-api/current/ruby_on_rails.html', + 'changelog_uri' => 'https://github.com/elastic/elasticsearch-rails/blob/master/CHANGELOG.md', + 'source_code_uri' => 'https://github.com/elastic/elasticsearch-rails/', + 'bug_tracker_uri' => 'https://github.com/elastic/elasticsearch-rails/issues' + } s.files = `git ls-files`.split($/) s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) } s.test_files = s.files.grep(%r{^(test|spec|features)/}) - s.require_paths = ["lib"] + s.require_paths = ['lib'] - s.extra_rdoc_files = [ "README.md", "LICENSE.txt" ] - s.rdoc_options = [ "--charset=UTF-8" ] + s.extra_rdoc_files = ['README.md', 'LICENSE.txt'] + s.rdoc_options = ['--charset=UTF-8'] - s.required_ruby_version = ">= 1.9.3" + s.required_ruby_version = '>= 2.4' - s.add_development_dependency "bundler" - s.add_development_dependency "rake", "~> 12" - - s.add_development_dependency "elasticsearch-extensions" - - s.add_development_dependency "rails", ">= 3.1" - - s.add_development_dependency "lograge" - - s.add_development_dependency "minitest" - s.add_development_dependency "test-unit" - s.add_development_dependency "shoulda-context" - s.add_development_dependency "mocha" - s.add_development_dependency "turn" - s.add_development_dependency "yard" - s.add_development_dependency "oj" unless defined?(JRUBY_VERSION) - s.add_development_dependency "ruby-prof" unless defined?(JRUBY_VERSION) - s.add_development_dependency "pry" - - s.add_development_dependency "simplecov" - s.add_development_dependency "cane" - s.add_development_dependency "require-prof" + s.add_development_dependency 'bundler' + s.add_development_dependency 'cane' + s.add_development_dependency 'elasticsearch-extensions' + s.add_development_dependency 'lograge' + s.add_development_dependency 'minitest' + s.add_development_dependency 'mocha' + s.add_development_dependency 'pry' + s.add_development_dependency 'rails', '> 3.1' + s.add_development_dependency 'rake', '~> 12' + s.add_development_dependency 'require-prof' + s.add_development_dependency 'shoulda-context' + s.add_development_dependency 'simplecov' + s.add_development_dependency 'test-unit' + s.add_development_dependency 'turn' + s.add_development_dependency 'yard' + unless defined?(JRUBY_VERSION) + s.add_development_dependency 'oj' + s.add_development_dependency 'ruby-prof' + end end From fdeac4bbde7bd3b841476c1b5a0a43586a065cec Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 29 Jun 2020 17:11:40 +0100 Subject: [PATCH 485/582] Update README, remove Virtus (unmaintained) --- README.md | 44 +++++++++++++---------------- elasticsearch-model/README.md | 34 ++++++++-------------- elasticsearch-persistence/Gemfile | 2 -- elasticsearch-persistence/README.md | 7 ++--- 4 files changed, 33 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 4ef960d00..b0d4da164 100644 --- a/README.md +++ b/README.md @@ -17,28 +17,6 @@ This repository contains various Ruby and Rails integrations for [Elasticsearch] Elasticsearch client and Ruby API is provided by the **[elasticsearch-ruby](https://github.com/elastic/elasticsearch-ruby)** project. -## Compatibility - -The libraries are compatible with Ruby 2.4 and higher. - -The version numbers follow the Elasticsearch major versions. The `master` branch is compatible with -the Elasticsearch `master` branch, therefore, with the next major version. - -| Rubygem | | Elasticsearch | -|:-------------:|:-:| :-----------: | -| 0.1 | → | 1.x | -| 2.x | → | 2.x | -| 5.x | → | 5.x | -| 6.x | → | 6.x | -| 7.x | → | 7.x | -| master | → | master | - -Use a release that matches the major version of Elasticsearch in your stack. Each client version is -backwards compatible with all minor versions of the same major version. - -Check out [Elastic product end of life dates](https://www.elastic.co/support/eol) -to learn which releases are still actively supported and tested. - ## Installation Install each library from [Rubygems](https://rubygems.org/gems/elasticsearch): @@ -53,6 +31,24 @@ gem 'elasticsearch-model', github: 'elastic/elasticsearch-rails', branch: '5.x' gem 'elasticsearch-rails', github: 'elastic/elasticsearch-rails', branch: '5.x' ``` +## Compatibility + +The libraries are compatible with Ruby 2.4 and higher. + +The version numbers follow the Elasticsearch major versions. The `master` branch is compatible with the latest Elasticsearch stack stable release. + +| Rubygem | | Elasticsearch | +|:-------------:|:-:| :-----------: | +| 0.1 | → | 1.x | +| 2.x | → | 2.x | +| 5.x | → | 5.x | +| 6.x | → | 6.x | +| master | → | 7.x | + +Use a release that matches the major version of Elasticsearch in your stack. Each client version is backwards compatible with all minor versions of the same major version. + +Check out [Elastic product end of life dates](https://www.elastic.co/support/eol) to learn which releases are still actively supported and tested. + ## Usage This project is split into three separate gems: @@ -108,10 +104,8 @@ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elast Example of using Elasticsearch as a repository for a Ruby domain object: ```ruby -require 'virtus' class Article - include Virtus.model - attribute :title, String + attr_accessor :title end require 'elasticsearch/persistence' diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 72662f4ee..bd1b3927b 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -1,18 +1,14 @@ # Elasticsearch::Model -The `elasticsearch-model` library builds on top of the -the [`elasticsearch`](https://github.com/elastic/elasticsearch-ruby) library. +The `elasticsearch-model` library builds on top of the the [`elasticsearch`](https://github.com/elastic/elasticsearch-ruby) library. -It aims to simplify integration of Ruby classes ("models"), commonly found -e.g. in [Ruby on Rails](http://rubyonrails.org) applications, with the -[Elasticsearch](https://www.elastic.co) search and analytics engine. +It aims to simplify integration of Ruby classes ("models"), commonly found e.g. in [Ruby on Rails](http://rubyonrails.org) applications, with the [Elasticsearch](https://www.elastic.co) search and analytics engine. ## Compatibility -This library is compatible with Ruby 1.9.3 and higher. +This library is compatible with Ruby 2.4 and higher. -The library version numbers follow the Elasticsearch major versions, and the `master` branch -is compatible with the Elasticsearch `master` branch, therefore, with the next major version. +The library version numbers follow the Elasticsearch major versions. The `master` branch is compatible with the latest Elasticsearch stack stable release. | Rubygem | | Elasticsearch | |:-------------:|:-:| :-----------: | @@ -20,7 +16,7 @@ is compatible with the Elasticsearch `master` branch, therefore, with the next m | 2.x | → | 2.x | | 5.x | → | 5.x | | 6.x | → | 6.x | -| master | → | master | +| master | → | 7.x | ## Installation @@ -73,9 +69,7 @@ This will extend the model with functionality related to Elasticsearch. #### Feature Extraction Pattern -Instead of including the `Elasticsearch::Model` module directly in your model, -you can include it in a "concern" or "trait" module, which is quite common pattern in Rails applications, -using e.g. `ActiveSupport::Concern` as the instrumentation: +Instead of including the `Elasticsearch::Model` module directly in your model, you can include it in a "concern" or "trait" module, which is quite common pattern in Rails applications, using e.g. `ActiveSupport::Concern` as the instrumentation: ```ruby # In: app/models/concerns/searchable.rb @@ -290,11 +284,8 @@ NOTE: It is _not_ possible to chain other methods on top of the `records` object #### Pagination -You can implement pagination with the `from` and `size` search parameters. However, search results -can be automatically paginated with the [`kaminari`](http://rubygems.org/gems/kaminari) or -[`will_paginate`](https://github.com/mislav/will_paginate) gems. -(The pagination gems must be added before the Elasticsearch gems in your Gemfile, -or loaded first in your application.) +You can implement pagination with the `from` and `size` search parameters. However, search results can be automatically paginated with the [`kaminari`](http://rubygems.org/gems/kaminari) or [`will_paginate`](https://github.com/mislav/will_paginate) gems. +(The pagination gems must be added before the Elasticsearch gems in your Gemfile, or loaded first in your application.) If Kaminari or WillPaginate is loaded, use the familiar paging methods: @@ -322,8 +313,7 @@ Elasticsearch::Model::Response::Response.__send__ :include, Elasticsearch::Model #### The Elasticsearch DSL -In most situations, you'll want to pass the search definition -in the Elasticsearch [domain-specific language](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html) to the client: +In most situations, you'll want to pass the search definition in the Elasticsearch [domain-specific language](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html) to the client: ```ruby response = Article.search query: { match: { title: "Fox Dogs" } }, @@ -333,8 +323,7 @@ response.results.first.highlight.title # ["Quick brown <em>fox</em>"] ``` -You can pass any object which implements a `to_hash` method, which is called automatically, -so you can use a custom class or your favourite JSON builder to build the search definition: +You can pass any object which implements a `to_hash` method, which is called automatically, so you can use a custom class or your favourite JSON builder to build the search definition: ```ruby require 'jbuilder' @@ -354,8 +343,7 @@ response.results.first.title # => "Quick brown fox" ``` -Also, you can use the [**`elasticsearch-dsl`**](https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch-dsl) library, which provides a specialized Ruby API for -the Elasticsearch Query DSL: +Also, you can use the [**`elasticsearch-dsl`**](https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch-dsl) library, which provides a specialized Ruby API for the Elasticsearch Query DSL: ```ruby require 'elasticsearch/dsl' diff --git a/elasticsearch-persistence/Gemfile b/elasticsearch-persistence/Gemfile index af517a160..439b88c36 100644 --- a/elasticsearch-persistence/Gemfile +++ b/elasticsearch-persistence/Gemfile @@ -24,8 +24,6 @@ gem 'elasticsearch-model', path: File.expand_path('../elasticsearch-model', __dir__), require: false -gem 'virtus' - group :development, :testing do gem 'pry-nav' gem 'rspec' diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index 49807186c..1d4fac551 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -4,10 +4,9 @@ Persistence layer for Ruby domain objects in Elasticsearch, using the Repository ## Compatibility -This library is compatible with Ruby 1.9.3 and higher. +This library is compatible with Ruby 2.4 and higher. -The library version numbers follow the Elasticsearch major versions, and the `master` branch -is compatible with the Elasticsearch `master` branch, therefore, with the next major version. +The library version numbers follow the Elasticsearch major versions. The `master` branch is compatible with the latest Elasticsearch stack stable release. | Rubygem | | Elasticsearch | |:-------------:|:-:| :-----------: | @@ -15,7 +14,7 @@ is compatible with the Elasticsearch `master` branch, therefore, with the next m | 2.x | → | 2.x | | 5.x | → | 5.x | | 6.x | → | 6.x | -| master | → | master | +| master | → | 7.x | ## Installation From a8311823a82b32a06da7f215b8c2ed4d1dd6c949 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 29 Jun 2020 14:37:16 +0100 Subject: [PATCH 486/582] [CI] Updates Elasticsearch version in Travis --- .travis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index d829ee013..7ae3e4370 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,11 +3,8 @@ # ----------------------------------------------------------------------------- dist: trusty - sudo: required - language: ruby - services: - mongodb @@ -50,7 +47,7 @@ matrix: env: global: - - ELASTICSEARCH_VERSION=7.6.2 + - ELASTICSEARCH_VERSION=7.8.0 - TEST_ES_SERVER=http://localhost:9250 - TEST_CLUSTER_PORT=9250 - QUIET=true From 239af1c0caa81b82f9b8805947aa94fa39be79c2 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 29 Jun 2020 14:47:23 +0100 Subject: [PATCH 487/582] [CI] Adds GitHub Actions --- .github/workflows/tests.yml | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..4bcdaf1af --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,41 @@ +name: master +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + test-master: + env: + TEST_ES_SERVER: http://localhost:9200 + strategy: + fail-fast: false + matrix: + ruby: [ 2.5, 2.6, 2.7, jruby ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Increase system limits + run: | + sudo swapoff -a + sudo sysctl -w vm.swappiness=1 + sudo sysctl -w fs.file-max=262144 + sudo sysctl -w vm.max_map_count=262144 + - uses: elastic/elastic-github-actions/elasticsearch@master + with: + stack-version: 7.8.0 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + - name: Bundle + run: | + sudo apt-get install libsqlite3-dev + gem install bundler + bundle install + bundle exec rake bundle:clean + bundle exec rake bundle:install + - name: Test + run: bundle exec rake test:all + From 4c44370c9f4f5f6d764b9dca662be8c0522d1f1a Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Tue, 30 Jun 2020 14:24:43 +0100 Subject: [PATCH 488/582] [CI] Don't test Rails 4.0 in model if Ruby > 2.7 --- elasticsearch-model/Rakefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index b31a4b5dc..02ab0beb7 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -21,7 +21,9 @@ desc 'Run unit tests' task default: 'test:all' task test: 'test:all' -GEMFILES = ['4.0.gemfile', '5.0.gemfile', '6.0.gemfile'] +gemfiles = ['5.0.gemfile', '6.0.gemfile'] +gemfiles << '4.0.gemfile' if RUBY_VERSION < '2.7' +GEMFILES = gemfiles.freeze namespace :bundle do desc 'Install dependencies for all the Gemfiles in /gemfiles. Optionally define env variable RAILS_VERSIONS. E.g. RAILS_VERSIONS=3.0,5.0' From 10ed5ca03cf297d6bb7b166d45160cc1dcc72aeb Mon Sep 17 00:00:00 2001 From: Michael Chen <michaelcmichaelchen@gmail.com> Date: Wed, 19 Aug 2020 06:01:12 -0700 Subject: [PATCH 489/582] [MODEL] Do not override existing methods (#936) * [MODEL] Do not override existing methods * fix spec to cover this case * Fixes #924 --- elasticsearch-model/lib/elasticsearch/model.rb | 2 +- elasticsearch-model/spec/elasticsearch/model/module_spec.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index d50308d8d..04adaffc1 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -112,7 +112,7 @@ def self.included(base) # Delegate common methods to the `__elasticsearch__` ClassMethodsProxy, unless they are defined already class << self METHODS.each do |method| - delegate method, to: :__elasticsearch__ unless self.respond_to?(method) + delegate method, to: :__elasticsearch__ unless self.public_instance_methods.include?(method) end end end diff --git a/elasticsearch-model/spec/elasticsearch/model/module_spec.rb b/elasticsearch-model/spec/elasticsearch/model/module_spec.rb index 3f7d682d0..72c90f4c5 100644 --- a/elasticsearch-model/spec/elasticsearch/model/module_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/module_spec.rb @@ -48,6 +48,7 @@ def self.search(query, options={}) end DummyIncludingModel.__send__ :include, Elasticsearch::Model + DummyIncludingModelWithSearchMethodDefined.__send__ :include, Elasticsearch::Model end after(:all) do From b3a34e0cd4e1fed7e6a3dc9cd60f64da353df26d Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Wed, 19 Aug 2020 14:12:40 +0100 Subject: [PATCH 490/582] [CI] Run GitHub Actions for 6.x --- .github/workflows/tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4bcdaf1af..a756f8025 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -3,9 +3,11 @@ on: push: branches: - master + - 6.x pull_request: branches: - master + - 6.x jobs: test-master: env: From eba44426e5a4024ad0d7df8bdc2a1e932cba82d4 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Wed, 19 Aug 2020 14:52:04 +0100 Subject: [PATCH 491/582] Selects bigdecimal gem version --- elasticsearch-model/gemfiles/4.0.gemfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/gemfiles/4.0.gemfile b/elasticsearch-model/gemfiles/4.0.gemfile index e7244994a..944568fce 100644 --- a/elasticsearch-model/gemfiles/4.0.gemfile +++ b/elasticsearch-model/gemfiles/4.0.gemfile @@ -26,10 +26,11 @@ gemspec path: '../' gem 'activemodel', '~> 4' gem 'activerecord', '~> 4' -gem 'sqlite3', '> 1.3', '< 1.4' unless defined?(JRUBY_VERSION) gem 'mongoid', '~> 5' +gem 'sqlite3', '> 1.3', '< 1.4' unless defined?(JRUBY_VERSION) group :development, :testing do - gem 'rspec' + gem 'bigdecimal', '~> 1' gem 'pry-nav' -end \ No newline at end of file + gem 'rspec' +end From df87fef38f6b6c837602371e0228e75a1e5928fa Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 31 Aug 2020 14:13:15 +0100 Subject: [PATCH 492/582] [CI] Adds stalebot configuration --- .github/stale.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 000000000..28276aad3 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,19 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security + - work-in-progress + - backport +# Label to use when marking an issue as stale +staleLabel: stale +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false From 2de67783d2b6927436c0072731fe337fc6677b2b Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Tue, 1 Sep 2020 09:04:44 +0100 Subject: [PATCH 493/582] [CI] Migrates tests to GitHub actions, removes Travis CI --- .ci/travis_before_script.sh | 15 ----- .github/workflows/{tests.yml => 2.4.yml} | 13 ++--- .github/workflows/2.5.yml | 40 +++++++++++++ .github/workflows/2.6.yml | 40 +++++++++++++ .github/workflows/2.7.yml | 40 +++++++++++++ .github/workflows/jruby.yml | 40 +++++++++++++ .travis.yml | 71 ------------------------ README.md | 7 ++- elasticsearch-model/Rakefile | 6 +- 9 files changed, 175 insertions(+), 97 deletions(-) delete mode 100755 .ci/travis_before_script.sh rename .github/workflows/{tests.yml => 2.4.yml} (82%) create mode 100644 .github/workflows/2.5.yml create mode 100644 .github/workflows/2.6.yml create mode 100644 .github/workflows/2.7.yml create mode 100644 .github/workflows/jruby.yml delete mode 100644 .travis.yml diff --git a/.ci/travis_before_script.sh b/.ci/travis_before_script.sh deleted file mode 100755 index 547a5a040..000000000 --- a/.ci/travis_before_script.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -if [ "$ELASTICSEARCH_VERSION" == "6.7.1" ] -then - url="https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ELASTICSEARCH_VERSION}.tar.gz" -else - url="https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ELASTICSEARCH_VERSION}-linux-x86_64.tar.gz" -fi - -echo "Downloading elasticsearch from $url" -curl $url | tar xz -C /tmp - -echo "Starting elasticsearch on port ${TEST_CLUSTER_PORT}" -/tmp/elasticsearch-${ELASTICSEARCH_VERSION}/bin/elasticsearch-keystore create -/tmp/elasticsearch-${ELASTICSEARCH_VERSION}/bin/elasticsearch -E http.port=${TEST_CLUSTER_PORT} &> /dev/null & diff --git a/.github/workflows/tests.yml b/.github/workflows/2.4.yml similarity index 82% rename from .github/workflows/tests.yml rename to .github/workflows/2.4.yml index a756f8025..cd2c4dae5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/2.4.yml @@ -1,21 +1,18 @@ -name: master +name: Ruby 2.4 on: push: branches: - master - - 6.x pull_request: branches: - master - - 6.x jobs: - test-master: + tests: env: TEST_ES_SERVER: http://localhost:9200 + RAILS_VERSIONS: '5.0' strategy: fail-fast: false - matrix: - ruby: [ 2.5, 2.6, 2.7, jruby ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -27,10 +24,10 @@ jobs: sudo sysctl -w vm.max_map_count=262144 - uses: elastic/elastic-github-actions/elasticsearch@master with: - stack-version: 7.8.0 + stack-version: 7.9.0 - uses: ruby/setup-ruby@v1 with: - ruby-version: ${{ matrix.ruby }} + ruby-version: 2.4 - name: Bundle run: | sudo apt-get install libsqlite3-dev diff --git a/.github/workflows/2.5.yml b/.github/workflows/2.5.yml new file mode 100644 index 000000000..19d432062 --- /dev/null +++ b/.github/workflows/2.5.yml @@ -0,0 +1,40 @@ +name: Ruby 2.5 +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + tests: + env: + TEST_ES_SERVER: http://localhost:9200 + RAILS_VERSIONS: '5.0,6.0' + strategy: + fail-fast: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Increase system limits + run: | + sudo swapoff -a + sudo sysctl -w vm.swappiness=1 + sudo sysctl -w fs.file-max=262144 + sudo sysctl -w vm.max_map_count=262144 + - uses: elastic/elastic-github-actions/elasticsearch@master + with: + stack-version: 7.9.0 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.5 + - name: Bundle + run: | + sudo apt-get install libsqlite3-dev + gem install bundler + bundle install + bundle exec rake bundle:clean + bundle exec rake bundle:install + - name: Test + run: bundle exec rake test:all + diff --git a/.github/workflows/2.6.yml b/.github/workflows/2.6.yml new file mode 100644 index 000000000..da076441e --- /dev/null +++ b/.github/workflows/2.6.yml @@ -0,0 +1,40 @@ +name: Ruby 2.6 +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + tests: + env: + TEST_ES_SERVER: http://localhost:9200 + RAILS_VERSIONS: '5.0,6.0' + strategy: + fail-fast: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Increase system limits + run: | + sudo swapoff -a + sudo sysctl -w vm.swappiness=1 + sudo sysctl -w fs.file-max=262144 + sudo sysctl -w vm.max_map_count=262144 + - uses: elastic/elastic-github-actions/elasticsearch@master + with: + stack-version: 7.9.0 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.6 + - name: Bundle + run: | + sudo apt-get install libsqlite3-dev + gem install bundler + bundle install + bundle exec rake bundle:clean + bundle exec rake bundle:install + - name: Test + run: bundle exec rake test:all + diff --git a/.github/workflows/2.7.yml b/.github/workflows/2.7.yml new file mode 100644 index 000000000..1823e8333 --- /dev/null +++ b/.github/workflows/2.7.yml @@ -0,0 +1,40 @@ +name: Ruby 2.7 +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + tests: + env: + TEST_ES_SERVER: http://localhost:9200 + RAILS_VERSIONS: '5.0,6.0' + strategy: + fail-fast: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Increase system limits + run: | + sudo swapoff -a + sudo sysctl -w vm.swappiness=1 + sudo sysctl -w fs.file-max=262144 + sudo sysctl -w vm.max_map_count=262144 + - uses: elastic/elastic-github-actions/elasticsearch@master + with: + stack-version: 7.9.0 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7 + - name: Bundle + run: | + sudo apt-get install libsqlite3-dev + gem install bundler + bundle install + bundle exec rake bundle:clean + bundle exec rake bundle:install + - name: Test + run: bundle exec rake test:all + diff --git a/.github/workflows/jruby.yml b/.github/workflows/jruby.yml new file mode 100644 index 000000000..e0dadec1c --- /dev/null +++ b/.github/workflows/jruby.yml @@ -0,0 +1,40 @@ +name: JRuby +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + tests: + env: + TEST_ES_SERVER: http://localhost:9200 + RAILS_VERSIONS: '5.0' + strategy: + fail-fast: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Increase system limits + run: | + sudo swapoff -a + sudo sysctl -w vm.swappiness=1 + sudo sysctl -w fs.file-max=262144 + sudo sysctl -w vm.max_map_count=262144 + - uses: elastic/elastic-github-actions/elasticsearch@master + with: + stack-version: 7.9.0 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: jruby-9.2 + - name: Bundle + run: | + sudo apt-get install libsqlite3-dev + gem install bundler + bundle install + bundle exec rake bundle:clean + bundle exec rake bundle:install + - name: Test + run: bundle exec rake test:all + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7ae3e4370..000000000 --- a/.travis.yml +++ /dev/null @@ -1,71 +0,0 @@ -# ----------------------------------------------------------------------------- -# Configuration file for http://travis-ci.org/elasticsearch/elasticsearch-rails -# ----------------------------------------------------------------------------- - -dist: trusty -sudo: required -language: ruby -services: - - mongodb - -branches: - only: - - master - - 6.x - - 7.x - -matrix: - include: - - rvm: 2.4 - jdk: oraclejdk8 - env: RAILS_VERSIONS=5.0 - - - rvm: 2.5 - jdk: oraclejdk8 - env: RAILS_VERSIONS=5.0,6.0 - - - rvm: 2.6 - jdk: oraclejdk8 - env: RAILS_VERSIONS=5.0,6.0 - - - rvm: 2.7 - jdk: oraclejdk8 - env: RAILS_VERSIONS=5.0,6.0 - - - rvm: ruby-head - jdk: oraclejdk8 - env: RAILS_VERSIONS=5.0,6.0 - - - rvm: jruby-9.2.9.0 - jdk: oraclejdk8 - env: RAILS_VERSIONS=5.0 - - allow_failures: - - rvm: ruby-head - jdk: oraclejdk8 - env: RAILS_VERSIONS=5.0,6.0 - -env: - global: - - ELASTICSEARCH_VERSION=7.8.0 - - TEST_ES_SERVER=http://localhost:9250 - - TEST_CLUSTER_PORT=9250 - - QUIET=true - -before_install: - - source ./.ci/travis_before_script.sh - - gem update --system; - - gem update bundler - - gem --version - - bundle version - -install: - - bundle install - - rake bundle:clean - - rake bundle:install - -script: - - rake test:all - -notifications: - disable: true diff --git a/README.md b/README.md index b0d4da164..220816e6e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # Elasticsearch -[![Build Status](https://travis-ci.org/elastic/elasticsearch-rails.svg?branch=master)](https://travis-ci.org/elastic/elasticsearch-rails) [![Code Climate](https://codeclimate.com/github/elastic/elasticsearch-rails/badges/gpa.svg)](https://codeclimate.com/github/elastic/elasticsearch-rails) +[![Ruby 2.7](https://github.com/elastic/elasticsearch-rails/workflows/Ruby%202.7/badge.svg)](https://github.com/elastic/elasticsearch-rails/actions) +[![Ruby 2.6](https://github.com/elastic/elasticsearch-rails/workflows/Ruby%202.6/badge.svg)](https://github.com/elastic/elasticsearch-rails/actions) +[![Ruby 2.5](https://github.com/elastic/elasticsearch-rails/workflows/Ruby%202.5/badge.svg)](https://github.com/elastic/elasticsearch-rails/actions) +[![Ruby 2.4](https://github.com/elastic/elasticsearch-rails/workflows/Ruby%202.4/badge.svg)](https://github.com/elastic/elasticsearch-rails/actions) +[![JRuby](https://github.com/elastic/elasticsearch-rails/workflows/JRuby/badge.svg)](https://github.com/elastic/elasticsearch-rails/actions) +[![Code Climate](https://codeclimate.com/github/elastic/elasticsearch-rails/badges/gpa.svg)](https://codeclimate.com/github/elastic/elasticsearch-rails) This repository contains various Ruby and Rails integrations for [Elasticsearch](http://elasticsearch.org): diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index 02ab0beb7..3d1ab402a 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -29,13 +29,14 @@ namespace :bundle do desc 'Install dependencies for all the Gemfiles in /gemfiles. Optionally define env variable RAILS_VERSIONS. E.g. RAILS_VERSIONS=3.0,5.0' task :install do unless defined?(JRUBY_VERSION) - puts '-'*80 + puts '-' * 80 gemfiles = ENV['RAILS_VERSIONS'] ? ENV['RAILS_VERSIONS'].split(',').map { |v| "#{v}.gemfile"} : GEMFILES gemfiles.each do |gemfile| + puts "GEMFILE: #{gemfile}" Bundler.with_clean_env do sh "bundle install --gemfile #{File.expand_path('../gemfiles/'+gemfile, __FILE__)}" end - puts '-'*80 + puts '-' * 80 end end end @@ -50,6 +51,7 @@ namespace :test do gemfiles = ENV['RAILS_VERSIONS'] ? ENV['RAILS_VERSIONS'].split(',').map {|v| "#{v}.gemfile"} : GEMFILES puts '-' * 80 gemfiles.each do |gemfile| + puts "GEMFILE: #{gemfile}" sh "BUNDLE_GEMFILE='#{File.expand_path("../gemfiles/#{gemfile}", __FILE__)}' " + " bundle exec rspec" puts '-' * 80 From e4545e4fe2a1ce80009206c831d5740360bad6c2 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Tue, 1 Sep 2020 09:28:56 +0100 Subject: [PATCH 494/582] Updates Bundler.with_clean_dev (deprecated) to with_unbundled_env --- elasticsearch-model/Rakefile | 2 +- elasticsearch-persistence/Rakefile | 6 +++--- elasticsearch-rails/Rakefile | 13 ++++++------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index 3d1ab402a..c2e5c9912 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -33,7 +33,7 @@ namespace :bundle do gemfiles = ENV['RAILS_VERSIONS'] ? ENV['RAILS_VERSIONS'].split(',').map { |v| "#{v}.gemfile"} : GEMFILES gemfiles.each do |gemfile| puts "GEMFILE: #{gemfile}" - Bundler.with_clean_env do + Bundler.with_unbundled_env do sh "bundle install --gemfile #{File.expand_path('../gemfiles/'+gemfile, __FILE__)}" end puts '-' * 80 diff --git a/elasticsearch-persistence/Rakefile b/elasticsearch-persistence/Rakefile index a49e40051..ec3daf3ca 100644 --- a/elasticsearch-persistence/Rakefile +++ b/elasticsearch-persistence/Rakefile @@ -40,11 +40,11 @@ end namespace :bundle do desc 'Install gem dependencies' task :install do - puts '-'*80 - Bundler.with_clean_env do + puts '-' * 80 + Bundler.with_unbundled_env do sh 'bundle install' end - puts '-'*80 + puts '-' * 80 end end diff --git a/elasticsearch-rails/Rakefile b/elasticsearch-rails/Rakefile index 36cd53074..1a03db185 100644 --- a/elasticsearch-rails/Rakefile +++ b/elasticsearch-rails/Rakefile @@ -18,8 +18,8 @@ require "bundler/gem_tasks" desc "Run unit tests" -task :default => 'test:unit' -task :test => 'test:unit' +task default: 'test:unit' +task test: 'test:unit' # ----- Test tasks ------------------------------------------------------------ @@ -27,24 +27,23 @@ require 'rake/testtask' require 'rspec/core/rake_task' namespace :test do - RSpec::Core::RakeTask.new(:spec) Rake::TestTask.new(:all) do |test| test.verbose = false test.warning = false - test.deps = [ :spec ] unless defined?(JRUBY_VERSION) + test.deps = [:spec] unless defined?(JRUBY_VERSION) end end namespace :bundle do desc 'Install gem dependencies' task :install do - puts '-'*80 - Bundler.with_clean_env do + puts '-' * 80 + Bundler.with_unbundled_env do sh 'bundle install' end - puts '-'*80 + puts '-' * 80 end end From 16a297a56e3d1845cc306c1541794280d4b2fdcc Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Tue, 1 Sep 2020 11:22:15 +0100 Subject: [PATCH 495/582] [CI] Test Rails 6 for JRuby --- .github/workflows/jruby.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/jruby.yml b/.github/workflows/jruby.yml index e0dadec1c..fdc9e951b 100644 --- a/.github/workflows/jruby.yml +++ b/.github/workflows/jruby.yml @@ -10,7 +10,7 @@ jobs: tests: env: TEST_ES_SERVER: http://localhost:9200 - RAILS_VERSIONS: '5.0' + RAILS_VERSIONS: '5.0,6.0' strategy: fail-fast: false runs-on: ubuntu-latest From 756a4dae1c51331a54bab8f00b0fcb88b4a480ad Mon Sep 17 00:00:00 2001 From: Fernando <fernando@picandocodigo.net> Date: Tue, 5 Jan 2021 17:49:50 +0000 Subject: [PATCH 496/582] [CI] Relax stalebot conditions --- .github/stale.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/stale.yml b/.github/stale.yml index 28276aad3..9b77b1806 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,13 +1,14 @@ # Number of days of inactivity before an issue becomes stale -daysUntilStale: 60 +daysUntilStale: 120 # Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 +daysUntilClose: 30 # Issues with these labels will never be considered stale exemptLabels: - pinned - security - work-in-progress - backport + - bug # Label to use when marking an issue as stale staleLabel: stale # Comment to post when marking an issue as stale. Set to `false` to disable From cd9c309b78de443d2e37760998418616ba34276d Mon Sep 17 00:00:00 2001 From: Pierre Chapuis <catwell@archlinux.us> Date: Sat, 6 Feb 2021 11:03:44 +0100 Subject: [PATCH 497/582] deal with `nil` document types in Multimodel --- elasticsearch-model/lib/elasticsearch/model/multimodel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/multimodel.rb b/elasticsearch-model/lib/elasticsearch/model/multimodel.rb index 2c2306274..f9ef2eaea 100644 --- a/elasticsearch-model/lib/elasticsearch/model/multimodel.rb +++ b/elasticsearch-model/lib/elasticsearch/model/multimodel.rb @@ -85,7 +85,7 @@ def index_name # @return [Array] the list of document types used for retrieving documents # def document_type - models.map { |m| m.document_type } + models.map { |m| m.document_type }.compact.presence end # Get the client common for all models From 65942e3da9cabad2f6965e69c8ef6a0994da9408 Mon Sep 17 00:00:00 2001 From: Takumasa Ochi <4468155+aeroastro@users.noreply.github.com> Date: Tue, 2 Mar 2021 02:08:22 +0900 Subject: [PATCH 498/582] Update dependency to explicitly support version 7 Dependency has been updated to follow Elasticsearch gems' version policy. This improves gem consistency and maintenance efficiency. --- elasticsearch-model/elasticsearch-model.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index a0cadd2b2..8f47becd0 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -42,7 +42,7 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.4' s.add_dependency 'activesupport', '> 3' - s.add_dependency 'elasticsearch', '> 1' + s.add_dependency 'elasticsearch', '~> 7' s.add_dependency 'hashie' s.add_development_dependency 'activemodel', '> 3' From 5db9207ca398c5d77f671109360ca7f63e3f2112 Mon Sep 17 00:00:00 2001 From: Takumasa Ochi <aeroastro007@gmail.com> Date: Tue, 2 Mar 2021 02:35:54 +0900 Subject: [PATCH 499/582] Stop emitting FATAL log when checking existence of indices Passing `ignore: 404` to Elasticsearch::Client can stop FATAL log in addition to stopping exception. This option is supported since the 5.0.4 and 6.0.0 of `elasticsearch-ruby`. https://github.com/elastic/elasticsearch-ruby/blob/36d45f0c12f4e9ef96646f269748ad33b43e974c/CHANGELOG.md#600 https://github.com/elastic/elasticsearch-ruby/blob/36d45f0c12f4e9ef96646f269748ad33b43e974c/CHANGELOG.md#504 This swallowing was implemented following exception catching as follows. https://github.com/elastic/elasticsearch-ruby/commit/9848c977a4cad6ff638b89828a3e6a6c599fe0d7 --- elasticsearch-model/lib/elasticsearch/model/indexing.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index 16ad1b3e4..9659e3408 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -270,7 +270,7 @@ def create_index!(options={}) def index_exists?(options={}) target_index = options[:index] || self.index_name - self.client.indices.exists(index: target_index) rescue false + self.client.indices.exists(index: target_index, ignore: 404) end # Deletes the index with corresponding name From ce57cc17e304b0a4af123c1599f37fb892a5d93a Mon Sep 17 00:00:00 2001 From: Takumasa Ochi <aeroastro007@gmail.com> Date: Tue, 2 Mar 2021 02:43:54 +0900 Subject: [PATCH 500/582] Remove unnecessary exception test on index checking Since we are using `ignore: 404` instead of manual `rescue` at `Elasticsearch::Model::Indexing`, there is no need to stub exceptions in the test. --- .../spec/elasticsearch/model/indexing_spec.rb | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb index 09e7717d5..281865a63 100644 --- a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb @@ -659,36 +659,6 @@ def changes expect(DummyIndexingModel.index_exists?).to be(false) end end - - context 'when the index API raises an error' do - - let(:client) do - double('client').tap do |cl| - expect(cl).to receive(:indices).and_raise(StandardError) - end - end - - it 'returns false' do - expect(DummyIndexingModel.index_exists?).to be(false) - end - end - - context 'when the indices.exists API raises an error' do - - let(:client) do - double('client', indices: indices) - end - - let(:indices) do - double('indices').tap do |ind| - expect(ind).to receive(:exists).and_raise(StandardError) - end - end - - it 'returns false' do - expect(DummyIndexingModel.index_exists?).to be(false) - end - end end describe '#delete_index!' do From ed070b8329ca48b4cb12b513ac81ed78c88acc61 Mon Sep 17 00:00:00 2001 From: Fernando <fernando@picandocodigo.net> Date: Mon, 1 Mar 2021 22:40:25 +0000 Subject: [PATCH 501/582] Removes dependency on extensions --- Gemfile | 4 +- README.md | 6 +-- Rakefile | 41 +------------------ .../elasticsearch-model.gemspec | 1 - .../elasticsearch-persistence.gemspec | 2 - .../elasticsearch-rails.gemspec | 1 - 6 files changed, 3 insertions(+), 52 deletions(-) diff --git a/Gemfile b/Gemfile index b3f93c953..804c5aae4 100644 --- a/Gemfile +++ b/Gemfile @@ -18,9 +18,7 @@ source 'https://rubygems.org' gem "rake", "~> 12" - -gem 'elasticsearch-extensions' - +gem "elasticsearch" gem "pry" gem "ansi" gem "cane" diff --git a/README.md b/README.md index 220816e6e..96c958674 100644 --- a/README.md +++ b/README.md @@ -160,11 +160,7 @@ You can also unit, integration, or both tests for all sub-projects from the top- rake test:all -The test suite expects an Elasticsearch cluster running on port 9250, and **will delete all the data**. You can launch an isolated, in-memory Elasticsearch cluster with the following Rake task: - - TEST_CLUSTER_COMMAND=/tmp/builds/elasticsearch-2.0.0-SNAPSHOT/bin/elasticsearch TEST_CLUSTER_NODES=1 bundle exec rake test:cluster:start - -See more information in the documentation for the [`elasticsearch-extensions`](https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch-extensions#testcluster) gem. +The test suite expects an Elasticsearch cluster running on port 9250, and **will delete all the data**. ## License diff --git a/Rakefile b/Rakefile index 9e3181a68..ee33d5724 100644 --- a/Rakefile +++ b/Rakefile @@ -16,6 +16,7 @@ # under the License. require 'pathname' +require 'elasticsearch' subprojects = ['elasticsearch-rails', 'elasticsearch-persistence'] subprojects << 'elasticsearch-model' unless defined?(JRUBY_VERSION) @@ -97,26 +98,6 @@ namespace :test do end end - desc "Run Elasticsearch (Docker)" - task :setup_elasticsearch_docker do - begin - sh <<-COMMAND.gsub(/^\s*/, '').gsub(/\s{1,}/, ' ') - docker run -d=true \ - --env "discovery.type=single-node" \ - --env "cluster.name=elasticsearch-rails" \ - --env "http.port=9200" \ - --env "cluster.routing.allocation.disk.threshold_enabled=false" \ - --publish 9250:9200 \ - --rm \ - docker.elastic.co/elasticsearch/elasticsearch:${ELASTICSEARCH_VERSION} - COMMAND - require 'elasticsearch/extensions/test/cluster' - Elasticsearch::Extensions::Test::Cluster::Cluster.new(version: ENV['ELASTICSEARCH_VERSION'], - number_of_nodes: 1).wait_for_green - rescue - end - end - desc "Setup MongoDB (Docker)" task :setup_mongodb_docker do begin @@ -167,26 +148,6 @@ namespace :test do puts "\n" end end - - namespace :cluster do - desc "Start Elasticsearch nodes for tests" - task :start do - require 'elasticsearch/extensions/test/cluster' - Elasticsearch::Extensions::Test::Cluster.start - end - - desc "Stop Elasticsearch nodes for tests" - task :stop do - require 'elasticsearch/extensions/test/cluster' - Elasticsearch::Extensions::Test::Cluster.stop - end - - task :status do - require 'elasticsearch/extensions/test/cluster' - (puts "\e[31m[!] Test cluster not running\e[0m"; exit(1)) unless Elasticsearch::Extensions::Test::Cluster.running? - Elasticsearch::Extensions::Test::Cluster.__print_cluster_info(ENV['TEST_CLUSTER_PORT'] || 9250) - end - end end diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index 8f47becd0..1bad4c5c8 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -48,7 +48,6 @@ Gem::Specification.new do |s| s.add_development_dependency 'activemodel', '> 3' s.add_development_dependency 'bundler' s.add_development_dependency 'cane' - s.add_development_dependency 'elasticsearch-extensions' s.add_development_dependency 'kaminari' s.add_development_dependency 'minitest' s.add_development_dependency 'mocha' diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index ea21fadb4..b09aaee03 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -53,8 +53,6 @@ Gem::Specification.new do |s| s.add_development_dependency "rails", '> 4' - s.add_development_dependency "elasticsearch-extensions" - s.add_development_dependency "minitest" s.add_development_dependency "test-unit" s.add_development_dependency "shoulda-context" diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index 400004a77..224e7fa36 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -48,7 +48,6 @@ Gem::Specification.new do |s| s.add_development_dependency 'bundler' s.add_development_dependency 'cane' - s.add_development_dependency 'elasticsearch-extensions' s.add_development_dependency 'lograge' s.add_development_dependency 'minitest' s.add_development_dependency 'mocha' From 9232d87a63484fa4b50cf7fa6c274318563eaa1d Mon Sep 17 00:00:00 2001 From: Fernando <fernando@picandocodigo.net> Date: Fri, 5 Mar 2021 11:00:23 +0000 Subject: [PATCH 502/582] [CI] Refactors GitHub actions --- .github/workflows/2.4.yml | 9 ++++++--- .github/workflows/2.5.yml | 8 ++++++-- .github/workflows/2.6.yml | 9 ++++++--- .github/workflows/2.7.yml | 9 ++++++--- .github/workflows/jruby.yml | 7 ++++--- 5 files changed, 28 insertions(+), 14 deletions(-) diff --git a/.github/workflows/2.4.yml b/.github/workflows/2.4.yml index cd2c4dae5..1702ef461 100644 --- a/.github/workflows/2.4.yml +++ b/.github/workflows/2.4.yml @@ -35,6 +35,9 @@ jobs: bundle install bundle exec rake bundle:clean bundle exec rake bundle:install - - name: Test - run: bundle exec rake test:all - + - name: Test elasticsearch-rails + run: cd elasticsearch-rails && bundle exec rake test:all + - name: Test elasticsearch-persistence + run: cd elasticsearch-persistence && bundle exec rake test:all + - name: Test elasticsearch-model + run: cd elasticsearch-model && bundle exec rake test:all diff --git a/.github/workflows/2.5.yml b/.github/workflows/2.5.yml index 19d432062..39ac86520 100644 --- a/.github/workflows/2.5.yml +++ b/.github/workflows/2.5.yml @@ -35,6 +35,10 @@ jobs: bundle install bundle exec rake bundle:clean bundle exec rake bundle:install - - name: Test - run: bundle exec rake test:all + - name: Test elasticsearch-rails + run: cd elasticsearch-rails && bundle exec rake test:all + - name: Test elasticsearch-persistence + run: cd elasticsearch-persistence && bundle exec rake test:all + - name: Test elasticsearch-model + run: cd elasticsearch-model && bundle exec rake test:all diff --git a/.github/workflows/2.6.yml b/.github/workflows/2.6.yml index da076441e..96fb50532 100644 --- a/.github/workflows/2.6.yml +++ b/.github/workflows/2.6.yml @@ -35,6 +35,9 @@ jobs: bundle install bundle exec rake bundle:clean bundle exec rake bundle:install - - name: Test - run: bundle exec rake test:all - + - name: Test elasticsearch-rails + run: cd elasticsearch-rails && bundle exec rake test:all + - name: Test elasticsearch-persistence + run: cd elasticsearch-persistence && bundle exec rake test:all + - name: Test elasticsearch-model + run: cd elasticsearch-model && bundle exec rake test:all diff --git a/.github/workflows/2.7.yml b/.github/workflows/2.7.yml index 1823e8333..cfd01a632 100644 --- a/.github/workflows/2.7.yml +++ b/.github/workflows/2.7.yml @@ -35,6 +35,9 @@ jobs: bundle install bundle exec rake bundle:clean bundle exec rake bundle:install - - name: Test - run: bundle exec rake test:all - + - name: Test elasticsearch-rails + run: cd elasticsearch-rails && bundle exec rake test:all + - name: Test elasticsearch-persistence + run: cd elasticsearch-persistence && bundle exec rake test:all + - name: Test elasticsearch-model + run: cd elasticsearch-model && bundle exec rake test:all diff --git a/.github/workflows/jruby.yml b/.github/workflows/jruby.yml index fdc9e951b..00a63fe08 100644 --- a/.github/workflows/jruby.yml +++ b/.github/workflows/jruby.yml @@ -35,6 +35,7 @@ jobs: bundle install bundle exec rake bundle:clean bundle exec rake bundle:install - - name: Test - run: bundle exec rake test:all - + - name: Test elasticsearch-rails + run: cd elasticsearch-rails && bundle exec rake test:all + - name: Test elasticsearch-persistence + run: cd elasticsearch-persistence && bundle exec rake test:all From 80822d69a7f33a13fdfc294035bf57fa9777ff17 Mon Sep 17 00:00:00 2001 From: Fernando <fernando@picandocodigo.net> Date: Fri, 5 Mar 2021 11:13:44 +0000 Subject: [PATCH 503/582] [CI] Update Stack version on GitHub Actions --- .github/workflows/2.4.yml | 2 +- .github/workflows/2.5.yml | 2 +- .github/workflows/2.6.yml | 2 +- .github/workflows/2.7.yml | 2 +- .github/workflows/jruby.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/2.4.yml b/.github/workflows/2.4.yml index 1702ef461..bac506823 100644 --- a/.github/workflows/2.4.yml +++ b/.github/workflows/2.4.yml @@ -24,7 +24,7 @@ jobs: sudo sysctl -w vm.max_map_count=262144 - uses: elastic/elastic-github-actions/elasticsearch@master with: - stack-version: 7.9.0 + stack-version: 7.x-SNAPSHOT - uses: ruby/setup-ruby@v1 with: ruby-version: 2.4 diff --git a/.github/workflows/2.5.yml b/.github/workflows/2.5.yml index 39ac86520..058182561 100644 --- a/.github/workflows/2.5.yml +++ b/.github/workflows/2.5.yml @@ -24,7 +24,7 @@ jobs: sudo sysctl -w vm.max_map_count=262144 - uses: elastic/elastic-github-actions/elasticsearch@master with: - stack-version: 7.9.0 + stack-version: 7.x-SNAPSHOT - uses: ruby/setup-ruby@v1 with: ruby-version: 2.5 diff --git a/.github/workflows/2.6.yml b/.github/workflows/2.6.yml index 96fb50532..d4e7f4345 100644 --- a/.github/workflows/2.6.yml +++ b/.github/workflows/2.6.yml @@ -24,7 +24,7 @@ jobs: sudo sysctl -w vm.max_map_count=262144 - uses: elastic/elastic-github-actions/elasticsearch@master with: - stack-version: 7.9.0 + stack-version: 7.x-SNAPSHOT - uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 diff --git a/.github/workflows/2.7.yml b/.github/workflows/2.7.yml index cfd01a632..514ee02eb 100644 --- a/.github/workflows/2.7.yml +++ b/.github/workflows/2.7.yml @@ -24,7 +24,7 @@ jobs: sudo sysctl -w vm.max_map_count=262144 - uses: elastic/elastic-github-actions/elasticsearch@master with: - stack-version: 7.9.0 + stack-version: 7.x-SNAPSHOT - uses: ruby/setup-ruby@v1 with: ruby-version: 2.7 diff --git a/.github/workflows/jruby.yml b/.github/workflows/jruby.yml index 00a63fe08..6a7569b95 100644 --- a/.github/workflows/jruby.yml +++ b/.github/workflows/jruby.yml @@ -24,7 +24,7 @@ jobs: sudo sysctl -w vm.max_map_count=262144 - uses: elastic/elastic-github-actions/elasticsearch@master with: - stack-version: 7.9.0 + stack-version: 7.x-SNAPSHOT - uses: ruby/setup-ruby@v1 with: ruby-version: jruby-9.2 From a4ec07b2d097545ca41c13686c9cbfc9eab9e639 Mon Sep 17 00:00:00 2001 From: Fernando <fernando@picandocodigo.net> Date: Fri, 2 Apr 2021 09:02:21 +0100 Subject: [PATCH 504/582] Fixes basic template elasticsearch dependency Fixes #984 --- elasticsearch-rails/lib/rails/templates/01-basic.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-rails/lib/rails/templates/01-basic.rb b/elasticsearch-rails/lib/rails/templates/01-basic.rb index e7305ecd5..516732893 100644 --- a/elasticsearch-rails/lib/rails/templates/01-basic.rb +++ b/elasticsearch-rails/lib/rails/templates/01-basic.rb @@ -156,7 +156,7 @@ say_status "Rubygems", "Adding Elasticsearch libraries into Gemfile...\n", :yellow puts '-'*80, ''; sleep 0.75 -gem 'elasticsearch', git: 'https://github.com/elasticsearch/elasticsearch-ruby.git' +gem 'elasticsearch' gem 'elasticsearch-model', git: 'https://github.com/elasticsearch/elasticsearch-rails.git' gem 'elasticsearch-rails', git: 'https://github.com/elasticsearch/elasticsearch-rails.git' From 62c5fef5c04a5ecdd4c5da65344dd9c90ac9c7e7 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 26 Apr 2021 16:30:59 +0100 Subject: [PATCH 505/582] [CI] Adds license headers check in GH Actions --- .github/check-license-headers.sh | 40 ++++++++++++++++++++++++++++++++ .github/license-header.txt | 16 +++++++++++++ .github/workflows/license.yml | 10 ++++++++ 3 files changed, 66 insertions(+) create mode 100755 .github/check-license-headers.sh create mode 100644 .github/license-header.txt create mode 100644 .github/workflows/license.yml diff --git a/.github/check-license-headers.sh b/.github/check-license-headers.sh new file mode 100755 index 000000000..e9b43d3d0 --- /dev/null +++ b/.github/check-license-headers.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +# Check that source code files in this repo have the appropriate license +# header. + +if [ "$TRACE" != "" ]; then + export PS4='${BASH_SOURCE}:${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' + set -o xtrace +fi +set -o errexit +set -o pipefail + +TOP=$(cd "$(dirname "$0")/.." >/dev/null && pwd) +LICENSE=$(cat .github/license-header.txt) + +function check_license_header { + local f + f=$1 + if ! grep -Fxq "$LICENSE" "$f"; then + echo "check-license-headers: error: '$f' does not have required license header" + return 1 + else + return 0 + fi +} + + +cd "$TOP" +nErrors=0 +for f in $(git ls-files | grep -E '\.rb|Rakefile|\.rake|\.erb|Gemfile'); do + if ! check_license_header $f; then + nErrors=$((nErrors+1)) + fi +done + +if [[ $nErrors -eq 0 ]]; then + exit 0 +else + exit 1 +fi diff --git a/.github/license-header.txt b/.github/license-header.txt new file mode 100644 index 000000000..5b0f712aa --- /dev/null +++ b/.github/license-header.txt @@ -0,0 +1,16 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. diff --git a/.github/workflows/license.yml b/.github/workflows/license.yml new file mode 100644 index 000000000..52dec1652 --- /dev/null +++ b/.github/workflows/license.yml @@ -0,0 +1,10 @@ +name: License headers +on: [pull_request] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Check license headers + run: | + ./.github/check-license-headers.sh From 40acb452837b19e539715e13516f22eb2263e0b3 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 26 Apr 2021 16:57:09 +0100 Subject: [PATCH 506/582] License: Updates license header in missing files --- .../lib/rails/templates/index.html.dsl.erb | 18 ++++++++++++++++++ .../lib/rails/templates/index.html.erb | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb b/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb index 97a9b8f81..5a55894b8 100644 --- a/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb +++ b/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb @@ -1,3 +1,21 @@ +<%# +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +%> <div class="col-md-12"> <h1 class="text-right"><%= link_to 'Search New York Times articles', root_path %></h1> diff --git a/elasticsearch-rails/lib/rails/templates/index.html.erb b/elasticsearch-rails/lib/rails/templates/index.html.erb index 656c1f891..b5bb19319 100644 --- a/elasticsearch-rails/lib/rails/templates/index.html.erb +++ b/elasticsearch-rails/lib/rails/templates/index.html.erb @@ -1,3 +1,21 @@ +<%# +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +%> <div class="col-md-12"> <h1 class="text-right"><%= link_to 'Search New York Times articles', root_path %></h1> From 473f8052c4047aa2aa2dfe599af2990056980afa Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Wed, 28 Apr 2021 09:05:34 +0100 Subject: [PATCH 507/582] [CI] Updates license headers check in GH Actions --- .github/check-license-headers.sh | 40 -------------------------------- .github/check_license_headers.rb | 33 ++++++++++++++++++++++++++ .github/workflows/license.yml | 5 +++- 3 files changed, 37 insertions(+), 41 deletions(-) delete mode 100755 .github/check-license-headers.sh create mode 100644 .github/check_license_headers.rb diff --git a/.github/check-license-headers.sh b/.github/check-license-headers.sh deleted file mode 100755 index e9b43d3d0..000000000 --- a/.github/check-license-headers.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash - -# Check that source code files in this repo have the appropriate license -# header. - -if [ "$TRACE" != "" ]; then - export PS4='${BASH_SOURCE}:${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' - set -o xtrace -fi -set -o errexit -set -o pipefail - -TOP=$(cd "$(dirname "$0")/.." >/dev/null && pwd) -LICENSE=$(cat .github/license-header.txt) - -function check_license_header { - local f - f=$1 - if ! grep -Fxq "$LICENSE" "$f"; then - echo "check-license-headers: error: '$f' does not have required license header" - return 1 - else - return 0 - fi -} - - -cd "$TOP" -nErrors=0 -for f in $(git ls-files | grep -E '\.rb|Rakefile|\.rake|\.erb|Gemfile'); do - if ! check_license_header $f; then - nErrors=$((nErrors+1)) - fi -done - -if [[ $nErrors -eq 0 ]]; then - exit 0 -else - exit 1 -fi diff --git a/.github/check_license_headers.rb b/.github/check_license_headers.rb new file mode 100644 index 000000000..4c3c21fea --- /dev/null +++ b/.github/check_license_headers.rb @@ -0,0 +1,33 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +LICENSE = File.read('./.github/license-header.txt') +files = `git ls-files | grep -E '\.rb|Rakefile|\.rake|\.erb|Gemfile|gemspec'`.split("\n") +errors = [] + +files.each do |file| + unless File.read(file).include?(LICENSE) + errors << file + puts "#{file} doesn't contain the correct license header" + end +end + +if errors.empty? + puts 'All checked files have the correct license header' +else + exit 1 +end diff --git a/.github/workflows/license.yml b/.github/workflows/license.yml index 52dec1652..d7357e56e 100644 --- a/.github/workflows/license.yml +++ b/.github/workflows/license.yml @@ -5,6 +5,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 3 - name: Check license headers run: | - ./.github/check-license-headers.sh + ruby ./.github/check_license_headers.rb From 939bf5d5e26acfdd3ab59d85e7fa5f3b661a7ac3 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Wed, 28 Apr 2021 09:46:20 +0100 Subject: [PATCH 508/582] License: Updates license headers --- Gemfile | 2 +- Rakefile | 2 +- elasticsearch-model/Gemfile | 2 +- elasticsearch-model/Rakefile | 2 +- elasticsearch-model/elasticsearch-model.gemspec | 2 +- elasticsearch-model/examples/activerecord_article.rb | 2 +- elasticsearch-model/examples/activerecord_associations.rb | 2 +- elasticsearch-model/examples/activerecord_custom_analyzer.rb | 2 +- .../examples/activerecord_mapping_completion.rb | 2 +- .../examples/activerecord_mapping_edge_ngram.rb | 2 +- elasticsearch-model/examples/couchbase_article.rb | 2 +- elasticsearch-model/examples/datamapper_article.rb | 2 +- elasticsearch-model/examples/mongoid_article.rb | 2 +- elasticsearch-model/examples/ohm_article.rb | 2 +- elasticsearch-model/examples/riak_article.rb | 2 +- elasticsearch-model/lib/elasticsearch/model.rb | 2 +- elasticsearch-model/lib/elasticsearch/model/adapter.rb | 2 +- .../lib/elasticsearch/model/adapters/active_record.rb | 2 +- .../lib/elasticsearch/model/adapters/default.rb | 2 +- .../lib/elasticsearch/model/adapters/mongoid.rb | 2 +- .../lib/elasticsearch/model/adapters/multiple.rb | 2 +- elasticsearch-model/lib/elasticsearch/model/callbacks.rb | 2 +- elasticsearch-model/lib/elasticsearch/model/client.rb | 2 +- .../lib/elasticsearch/model/ext/active_record.rb | 2 +- elasticsearch-model/lib/elasticsearch/model/hash_wrapper.rb | 2 +- elasticsearch-model/lib/elasticsearch/model/importing.rb | 2 +- elasticsearch-model/lib/elasticsearch/model/indexing.rb | 2 +- elasticsearch-model/lib/elasticsearch/model/multimodel.rb | 2 +- elasticsearch-model/lib/elasticsearch/model/naming.rb | 2 +- elasticsearch-model/lib/elasticsearch/model/proxy.rb | 2 +- elasticsearch-model/lib/elasticsearch/model/response.rb | 2 +- .../lib/elasticsearch/model/response/aggregations.rb | 2 +- elasticsearch-model/lib/elasticsearch/model/response/base.rb | 2 +- .../lib/elasticsearch/model/response/pagination.rb | 2 +- .../lib/elasticsearch/model/response/pagination/kaminari.rb | 2 +- .../elasticsearch/model/response/pagination/will_paginate.rb | 2 +- .../lib/elasticsearch/model/response/records.rb | 2 +- .../lib/elasticsearch/model/response/result.rb | 2 +- .../lib/elasticsearch/model/response/results.rb | 2 +- .../lib/elasticsearch/model/response/suggestions.rb | 2 +- elasticsearch-model/lib/elasticsearch/model/searching.rb | 2 +- elasticsearch-model/lib/elasticsearch/model/serializing.rb | 2 +- elasticsearch-model/lib/elasticsearch/model/version.rb | 2 +- elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb | 2 +- .../model/adapters/active_record/associations_spec.rb | 2 +- .../elasticsearch/model/adapters/active_record/basic_spec.rb | 2 +- .../model/adapters/active_record/dynamic_index_name_spec.rb | 2 +- .../elasticsearch/model/adapters/active_record/import_spec.rb | 2 +- .../model/adapters/active_record/multi_model_spec.rb | 2 +- .../model/adapters/active_record/namespaced_model_spec.rb | 2 +- .../model/adapters/active_record/pagination_spec.rb | 2 +- .../model/adapters/active_record/parent_child_spec.rb | 2 +- .../model/adapters/active_record/serialization_spec.rb | 2 +- .../spec/elasticsearch/model/adapters/active_record_spec.rb | 2 +- .../spec/elasticsearch/model/adapters/default_spec.rb | 2 +- .../spec/elasticsearch/model/adapters/mongoid/basic_spec.rb | 2 +- .../elasticsearch/model/adapters/mongoid/multi_model_spec.rb | 2 +- .../spec/elasticsearch/model/adapters/mongoid_spec.rb | 2 +- .../spec/elasticsearch/model/adapters/multiple_spec.rb | 2 +- .../spec/elasticsearch/model/callbacks_spec.rb | 2 +- elasticsearch-model/spec/elasticsearch/model/client_spec.rb | 2 +- .../spec/elasticsearch/model/hash_wrapper_spec.rb | 2 +- .../spec/elasticsearch/model/importing_spec.rb | 2 +- elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb | 2 +- elasticsearch-model/spec/elasticsearch/model/module_spec.rb | 2 +- .../spec/elasticsearch/model/multimodel_spec.rb | 2 +- elasticsearch-model/spec/elasticsearch/model/naming_spec.rb | 2 +- elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb | 2 +- .../spec/elasticsearch/model/response/aggregations_spec.rb | 2 +- .../spec/elasticsearch/model/response/base_spec.rb | 2 +- .../elasticsearch/model/response/pagination/kaminari_spec.rb | 2 +- .../model/response/pagination/will_paginate_spec.rb | 2 +- .../spec/elasticsearch/model/response/records_spec.rb | 2 +- .../spec/elasticsearch/model/response/response_spec.rb | 2 +- .../spec/elasticsearch/model/response/result_spec.rb | 2 +- .../spec/elasticsearch/model/response/results_spec.rb | 2 +- .../spec/elasticsearch/model/searching_search_request_spec.rb | 2 +- .../spec/elasticsearch/model/searching_spec.rb | 2 +- .../spec/elasticsearch/model/serializing_spec.rb | 2 +- elasticsearch-model/spec/spec_helper.rb | 2 +- elasticsearch-model/spec/support/app.rb | 4 ++-- elasticsearch-model/spec/support/app/answer.rb | 4 ++-- elasticsearch-model/spec/support/app/article.rb | 2 +- .../spec/support/app/article_for_pagination.rb | 2 +- elasticsearch-model/spec/support/app/article_no_type.rb | 2 +- .../spec/support/app/article_with_custom_serialization.rb | 2 +- .../spec/support/app/article_with_dynamic_index_name.rb | 2 +- elasticsearch-model/spec/support/app/author.rb | 2 +- elasticsearch-model/spec/support/app/authorship.rb | 2 +- elasticsearch-model/spec/support/app/category.rb | 2 +- elasticsearch-model/spec/support/app/comment.rb | 2 +- elasticsearch-model/spec/support/app/episode.rb | 2 +- elasticsearch-model/spec/support/app/image.rb | 2 +- elasticsearch-model/spec/support/app/import_article.rb | 2 +- elasticsearch-model/spec/support/app/mongoid_article.rb | 2 +- elasticsearch-model/spec/support/app/namespaced_book.rb | 2 +- .../spec/support/app/parent_and_child_searchable.rb | 2 +- elasticsearch-model/spec/support/app/post.rb | 2 +- elasticsearch-model/spec/support/app/question.rb | 2 +- elasticsearch-model/spec/support/app/searchable.rb | 2 +- elasticsearch-model/spec/support/app/series.rb | 2 +- elasticsearch-persistence/Gemfile | 2 +- elasticsearch-persistence/Rakefile | 2 +- elasticsearch-persistence/elasticsearch-persistence.gemspec | 2 +- elasticsearch-persistence/examples/notes/Gemfile | 2 +- elasticsearch-persistence/examples/notes/application.rb | 2 +- elasticsearch-persistence/examples/notes/test.rb | 2 +- elasticsearch-persistence/lib/elasticsearch/persistence.rb | 2 +- .../lib/elasticsearch/persistence/repository.rb | 2 +- .../lib/elasticsearch/persistence/repository/dsl.rb | 2 +- .../lib/elasticsearch/persistence/repository/find.rb | 2 +- .../elasticsearch/persistence/repository/response/results.rb | 2 +- .../lib/elasticsearch/persistence/repository/search.rb | 2 +- .../lib/elasticsearch/persistence/repository/serialize.rb | 2 +- .../lib/elasticsearch/persistence/repository/store.rb | 2 +- .../lib/elasticsearch/persistence/version.rb | 2 +- elasticsearch-persistence/spec/repository/find_spec.rb | 2 +- .../spec/repository/response/results_spec.rb | 2 +- elasticsearch-persistence/spec/repository/search_spec.rb | 2 +- elasticsearch-persistence/spec/repository/serialize_spec.rb | 2 +- elasticsearch-persistence/spec/repository/store_spec.rb | 2 +- elasticsearch-persistence/spec/repository_spec.rb | 2 +- elasticsearch-persistence/spec/spec_helper.rb | 2 +- elasticsearch-rails/Gemfile | 2 +- elasticsearch-rails/Rakefile | 2 +- elasticsearch-rails/elasticsearch-rails.gemspec | 2 +- elasticsearch-rails/lib/elasticsearch/rails.rb | 2 +- .../lib/elasticsearch/rails/instrumentation.rb | 2 +- .../elasticsearch/rails/instrumentation/controller_runtime.rb | 2 +- .../lib/elasticsearch/rails/instrumentation/log_subscriber.rb | 2 +- .../lib/elasticsearch/rails/instrumentation/publishers.rb | 2 +- .../lib/elasticsearch/rails/instrumentation/railtie.rb | 2 +- elasticsearch-rails/lib/elasticsearch/rails/lograge.rb | 2 +- elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb | 2 +- elasticsearch-rails/lib/elasticsearch/rails/version.rb | 2 +- elasticsearch-rails/lib/rails/templates/01-basic.rb | 2 +- elasticsearch-rails/lib/rails/templates/02-pretty.rb | 2 +- elasticsearch-rails/lib/rails/templates/03-expert.rb | 2 +- elasticsearch-rails/lib/rails/templates/04-dsl.rb | 2 +- elasticsearch-rails/lib/rails/templates/05-settings-files.rb | 2 +- elasticsearch-rails/lib/rails/templates/indexer.rb | 2 +- .../lib/rails/templates/search_controller_test.dsl.rb | 2 +- .../lib/rails/templates/search_controller_test.rb | 2 +- elasticsearch-rails/lib/rails/templates/searchable.dsl.rb | 2 +- elasticsearch-rails/lib/rails/templates/searchable.rb | 2 +- elasticsearch-rails/lib/rails/templates/seeds.rb | 2 +- elasticsearch-rails/spec/instrumentation_spec.rb | 2 +- elasticsearch-rails/spec/lograge_spec.rb | 2 +- elasticsearch-rails/spec/spec_helper.rb | 2 +- 149 files changed, 151 insertions(+), 151 deletions(-) diff --git a/Gemfile b/Gemfile index 804c5aae4..255f70850 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/Rakefile b/Rakefile index ee33d5724..a2dd17e69 100644 --- a/Rakefile +++ b/Rakefile @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/Gemfile b/elasticsearch-model/Gemfile index 664ab4569..5a31c3095 100644 --- a/elasticsearch-model/Gemfile +++ b/elasticsearch-model/Gemfile @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index c2e5c9912..ab1d6cab7 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index 1bad4c5c8..083938d51 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/examples/activerecord_article.rb b/elasticsearch-model/examples/activerecord_article.rb index 2bd2e1e27..1e584fd2c 100644 --- a/elasticsearch-model/examples/activerecord_article.rb +++ b/elasticsearch-model/examples/activerecord_article.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/examples/activerecord_associations.rb b/elasticsearch-model/examples/activerecord_associations.rb index 79a02aa65..b8100b33b 100644 --- a/elasticsearch-model/examples/activerecord_associations.rb +++ b/elasticsearch-model/examples/activerecord_associations.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/examples/activerecord_custom_analyzer.rb b/elasticsearch-model/examples/activerecord_custom_analyzer.rb index e2a79ef23..886eaab91 100644 --- a/elasticsearch-model/examples/activerecord_custom_analyzer.rb +++ b/elasticsearch-model/examples/activerecord_custom_analyzer.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/examples/activerecord_mapping_completion.rb b/elasticsearch-model/examples/activerecord_mapping_completion.rb index 901d93232..264746180 100644 --- a/elasticsearch-model/examples/activerecord_mapping_completion.rb +++ b/elasticsearch-model/examples/activerecord_mapping_completion.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/examples/activerecord_mapping_edge_ngram.rb b/elasticsearch-model/examples/activerecord_mapping_edge_ngram.rb index b3af42959..c1a714f80 100644 --- a/elasticsearch-model/examples/activerecord_mapping_edge_ngram.rb +++ b/elasticsearch-model/examples/activerecord_mapping_edge_ngram.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/examples/couchbase_article.rb b/elasticsearch-model/examples/couchbase_article.rb index b7372a2d0..cfbab22d2 100644 --- a/elasticsearch-model/examples/couchbase_article.rb +++ b/elasticsearch-model/examples/couchbase_article.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/examples/datamapper_article.rb b/elasticsearch-model/examples/datamapper_article.rb index 0c483a005..d29460966 100644 --- a/elasticsearch-model/examples/datamapper_article.rb +++ b/elasticsearch-model/examples/datamapper_article.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/examples/mongoid_article.rb b/elasticsearch-model/examples/mongoid_article.rb index 01d6e5295..e2f3ae464 100644 --- a/elasticsearch-model/examples/mongoid_article.rb +++ b/elasticsearch-model/examples/mongoid_article.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/examples/ohm_article.rb b/elasticsearch-model/examples/ohm_article.rb index 52091083c..1c50877f8 100644 --- a/elasticsearch-model/examples/ohm_article.rb +++ b/elasticsearch-model/examples/ohm_article.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/examples/riak_article.rb b/elasticsearch-model/examples/riak_article.rb index 2ff12d9bd..8fd024e41 100644 --- a/elasticsearch-model/examples/riak_article.rb +++ b/elasticsearch-model/examples/riak_article.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index 04adaffc1..fe9c33047 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/adapter.rb b/elasticsearch-model/lib/elasticsearch/model/adapter.rb index 5fa2cd974..cdb0c7d0e 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapter.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapter.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb index dfa109161..64e0277d8 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/default.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/default.rb index 62cec409c..9891b5d64 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/default.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/default.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb index fb671158b..8626d743c 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb index b4df631f4..30d851043 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/callbacks.rb b/elasticsearch-model/lib/elasticsearch/model/callbacks.rb index 4f0397e29..cff3d1c9d 100644 --- a/elasticsearch-model/lib/elasticsearch/model/callbacks.rb +++ b/elasticsearch-model/lib/elasticsearch/model/callbacks.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/client.rb b/elasticsearch-model/lib/elasticsearch/model/client.rb index 8a49d98f4..01a122440 100644 --- a/elasticsearch-model/lib/elasticsearch/model/client.rb +++ b/elasticsearch-model/lib/elasticsearch/model/client.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/ext/active_record.rb b/elasticsearch-model/lib/elasticsearch/model/ext/active_record.rb index 543329191..2cdbe7f35 100644 --- a/elasticsearch-model/lib/elasticsearch/model/ext/active_record.rb +++ b/elasticsearch-model/lib/elasticsearch/model/ext/active_record.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/hash_wrapper.rb b/elasticsearch-model/lib/elasticsearch/model/hash_wrapper.rb index 50a5727f4..18f4dad23 100644 --- a/elasticsearch-model/lib/elasticsearch/model/hash_wrapper.rb +++ b/elasticsearch-model/lib/elasticsearch/model/hash_wrapper.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/importing.rb b/elasticsearch-model/lib/elasticsearch/model/importing.rb index c0519ef9f..927dd16ee 100644 --- a/elasticsearch-model/lib/elasticsearch/model/importing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/importing.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index 9659e3408..f35ef7342 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/multimodel.rb b/elasticsearch-model/lib/elasticsearch/model/multimodel.rb index f9ef2eaea..6b5fc2a81 100644 --- a/elasticsearch-model/lib/elasticsearch/model/multimodel.rb +++ b/elasticsearch-model/lib/elasticsearch/model/multimodel.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/naming.rb b/elasticsearch-model/lib/elasticsearch/model/naming.rb index 9ab489db6..c4c796ab2 100644 --- a/elasticsearch-model/lib/elasticsearch/model/naming.rb +++ b/elasticsearch-model/lib/elasticsearch/model/naming.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/proxy.rb b/elasticsearch-model/lib/elasticsearch/model/proxy.rb index 013bdac12..955280fba 100644 --- a/elasticsearch-model/lib/elasticsearch/model/proxy.rb +++ b/elasticsearch-model/lib/elasticsearch/model/proxy.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/response.rb b/elasticsearch-model/lib/elasticsearch/model/response.rb index 718c06987..5d79a1827 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb b/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb index c5f42ff54..6fc4a8e89 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/aggregations.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/response/base.rb b/elasticsearch-model/lib/elasticsearch/model/response/base.rb index 2afa1193e..21853d5bb 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/base.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/base.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb index 60830c75b..f12604179 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/pagination.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/response/pagination/kaminari.rb b/elasticsearch-model/lib/elasticsearch/model/response/pagination/kaminari.rb index 755ed567a..66de07ccb 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/pagination/kaminari.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/pagination/kaminari.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/response/pagination/will_paginate.rb b/elasticsearch-model/lib/elasticsearch/model/response/pagination/will_paginate.rb index 88023f12b..2d4658544 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/pagination/will_paginate.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/pagination/will_paginate.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/response/records.rb b/elasticsearch-model/lib/elasticsearch/model/response/records.rb index 41aa6fd70..e18ecc83d 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/records.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/records.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/response/result.rb b/elasticsearch-model/lib/elasticsearch/model/response/result.rb index 83e07e917..5e102c2b3 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/result.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/result.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/response/results.rb b/elasticsearch-model/lib/elasticsearch/model/response/results.rb index b0060f3fe..cf7e5aaf5 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/results.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/results.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb b/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb index d02b1dcf5..72261a930 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/suggestions.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/searching.rb b/elasticsearch-model/lib/elasticsearch/model/searching.rb index 1ee103344..714f98a22 100644 --- a/elasticsearch-model/lib/elasticsearch/model/searching.rb +++ b/elasticsearch-model/lib/elasticsearch/model/searching.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/serializing.rb b/elasticsearch-model/lib/elasticsearch/model/serializing.rb index 43c9afbbf..9433dfa12 100644 --- a/elasticsearch-model/lib/elasticsearch/model/serializing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/serializing.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/lib/elasticsearch/model/version.rb b/elasticsearch-model/lib/elasticsearch/model/version.rb index cc92865fe..ce56f7798 100644 --- a/elasticsearch-model/lib/elasticsearch/model/version.rb +++ b/elasticsearch-model/lib/elasticsearch/model/version.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb index a02a633d6..33dd62590 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/associations_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/associations_spec.rb index f451f5d49..c3e460ffb 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/associations_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/associations_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb index eef1bd42c..a769b38f6 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb index 02c2bb59d..abb4a0bdc 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb index e4a6088c8..1f5fe0847 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb index 08dc869d0..c8196242a 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb index 3f93eca9d..730dad0e0 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb index fdf296261..c4a503ed7 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb index 675ab3b73..5147943ee 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb index 6e30c64b7..3f01c348b 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb index 02068bc54..8a6fe0171 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/default_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/default_spec.rb index 612e8a784..cbc59fa70 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/default_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/default_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb index 1f12c0afd..0a0e71b58 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb index d702eecab..5614522c9 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb index ac8e73551..81a286c0d 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb index c0bf116e6..eb5a983b0 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/callbacks_spec.rb b/elasticsearch-model/spec/elasticsearch/model/callbacks_spec.rb index 325467bbe..f128d8732 100644 --- a/elasticsearch-model/spec/elasticsearch/model/callbacks_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/callbacks_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/client_spec.rb b/elasticsearch-model/spec/elasticsearch/model/client_spec.rb index 9fa9a5943..421bd3c50 100644 --- a/elasticsearch-model/spec/elasticsearch/model/client_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/client_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/hash_wrapper_spec.rb b/elasticsearch-model/spec/elasticsearch/model/hash_wrapper_spec.rb index d300e7bff..ae4c857ac 100644 --- a/elasticsearch-model/spec/elasticsearch/model/hash_wrapper_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/hash_wrapper_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb index 19bbff715..02cde6d6c 100644 --- a/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb index 281865a63..257294fa6 100644 --- a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/module_spec.rb b/elasticsearch-model/spec/elasticsearch/model/module_spec.rb index 72c90f4c5..2712318c5 100644 --- a/elasticsearch-model/spec/elasticsearch/model/module_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/module_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb b/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb index b0cfcb1c2..3652dac1e 100644 --- a/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb b/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb index bdd5aafad..57a53b8d7 100644 --- a/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb b/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb index f8bed3fbc..e9d9712c8 100644 --- a/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb index 2d3339991..a455140ea 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb index c4d4499c9..d3459b93b 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb index 4ad8ab5a1..15ee6113c 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb index fa78c0784..25f1f0d1e 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb index 72b46182b..2a80dcfc6 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb index 9406b773a..e9a423812 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb index 7cf701660..5aa7c8f4f 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb index dca4c658c..946043a9f 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb b/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb index a49fa8e28..1f5e72bf2 100644 --- a/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/searching_spec.rb b/elasticsearch-model/spec/elasticsearch/model/searching_spec.rb index ced7257d9..ccede7435 100644 --- a/elasticsearch-model/spec/elasticsearch/model/searching_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/searching_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/elasticsearch/model/serializing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/serializing_spec.rb index f18079dcc..c5a26a4e4 100644 --- a/elasticsearch-model/spec/elasticsearch/model/serializing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/serializing_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/spec_helper.rb b/elasticsearch-model/spec/spec_helper.rb index 7bf7f41dc..3bb7f8742 100644 --- a/elasticsearch-model/spec/spec_helper.rb +++ b/elasticsearch-model/spec/spec_helper.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app.rb b/elasticsearch-model/spec/support/app.rb index 9169cb09f..b5a26ebff 100644 --- a/elasticsearch-model/spec/support/app.rb +++ b/elasticsearch-model/spec/support/app.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an @@ -43,4 +43,4 @@ require 'support/app/mongoid_article' rescue $stderr.puts("'mongoid' gem is not installed, could not load Mongoid models") -end \ No newline at end of file +end diff --git a/elasticsearch-model/spec/support/app/answer.rb b/elasticsearch-model/spec/support/app/answer.rb index ec94c4b60..9c58cc991 100644 --- a/elasticsearch-model/spec/support/app/answer.rb +++ b/elasticsearch-model/spec/support/app/answer.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an @@ -47,4 +47,4 @@ def as_indexed_json(options={}) after_commit lambda { __elasticsearch__.index_document(routing: (question_id || 1)) }, on: :create after_commit lambda { __elasticsearch__.update_document(routing: (question_id || 1)) }, on: :update after_commit lambda {__elasticsearch__.delete_document(routing: (question_id || 1)) }, on: :destroy -end \ No newline at end of file +end diff --git a/elasticsearch-model/spec/support/app/article.rb b/elasticsearch-model/spec/support/app/article.rb index 82e863b1c..2c122dd77 100644 --- a/elasticsearch-model/spec/support/app/article.rb +++ b/elasticsearch-model/spec/support/app/article.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app/article_for_pagination.rb b/elasticsearch-model/spec/support/app/article_for_pagination.rb index b785b40eb..7916e56b9 100644 --- a/elasticsearch-model/spec/support/app/article_for_pagination.rb +++ b/elasticsearch-model/spec/support/app/article_for_pagination.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app/article_no_type.rb b/elasticsearch-model/spec/support/app/article_no_type.rb index 5a08746ba..d3cb6b4e3 100644 --- a/elasticsearch-model/spec/support/app/article_no_type.rb +++ b/elasticsearch-model/spec/support/app/article_no_type.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app/article_with_custom_serialization.rb b/elasticsearch-model/spec/support/app/article_with_custom_serialization.rb index 784f0338e..527143694 100644 --- a/elasticsearch-model/spec/support/app/article_with_custom_serialization.rb +++ b/elasticsearch-model/spec/support/app/article_with_custom_serialization.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app/article_with_dynamic_index_name.rb b/elasticsearch-model/spec/support/app/article_with_dynamic_index_name.rb index d72341d96..6fa1f1298 100644 --- a/elasticsearch-model/spec/support/app/article_with_dynamic_index_name.rb +++ b/elasticsearch-model/spec/support/app/article_with_dynamic_index_name.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app/author.rb b/elasticsearch-model/spec/support/app/author.rb index da8de2070..47abe43a3 100644 --- a/elasticsearch-model/spec/support/app/author.rb +++ b/elasticsearch-model/spec/support/app/author.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app/authorship.rb b/elasticsearch-model/spec/support/app/authorship.rb index 0ef2c1786..55258f9f7 100644 --- a/elasticsearch-model/spec/support/app/authorship.rb +++ b/elasticsearch-model/spec/support/app/authorship.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app/category.rb b/elasticsearch-model/spec/support/app/category.rb index 66518361b..6585ad806 100644 --- a/elasticsearch-model/spec/support/app/category.rb +++ b/elasticsearch-model/spec/support/app/category.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app/comment.rb b/elasticsearch-model/spec/support/app/comment.rb index c2fcd16eb..f4ae205d7 100644 --- a/elasticsearch-model/spec/support/app/comment.rb +++ b/elasticsearch-model/spec/support/app/comment.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app/episode.rb b/elasticsearch-model/spec/support/app/episode.rb index 66f37a989..34ebfc3eb 100644 --- a/elasticsearch-model/spec/support/app/episode.rb +++ b/elasticsearch-model/spec/support/app/episode.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app/image.rb b/elasticsearch-model/spec/support/app/image.rb index e605ae942..155e974cd 100644 --- a/elasticsearch-model/spec/support/app/image.rb +++ b/elasticsearch-model/spec/support/app/image.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app/import_article.rb b/elasticsearch-model/spec/support/app/import_article.rb index 4bb228e16..eb0a4962c 100644 --- a/elasticsearch-model/spec/support/app/import_article.rb +++ b/elasticsearch-model/spec/support/app/import_article.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app/mongoid_article.rb b/elasticsearch-model/spec/support/app/mongoid_article.rb index 2dacd62b6..5e7c991ca 100644 --- a/elasticsearch-model/spec/support/app/mongoid_article.rb +++ b/elasticsearch-model/spec/support/app/mongoid_article.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app/namespaced_book.rb b/elasticsearch-model/spec/support/app/namespaced_book.rb index 32a486ee2..f6d9c838c 100644 --- a/elasticsearch-model/spec/support/app/namespaced_book.rb +++ b/elasticsearch-model/spec/support/app/namespaced_book.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb b/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb index 15b56186a..91693479f 100644 --- a/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb +++ b/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app/post.rb b/elasticsearch-model/spec/support/app/post.rb index 15aa0e041..9805d9cc3 100644 --- a/elasticsearch-model/spec/support/app/post.rb +++ b/elasticsearch-model/spec/support/app/post.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app/question.rb b/elasticsearch-model/spec/support/app/question.rb index eec7d3b04..62a7d4550 100644 --- a/elasticsearch-model/spec/support/app/question.rb +++ b/elasticsearch-model/spec/support/app/question.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app/searchable.rb b/elasticsearch-model/spec/support/app/searchable.rb index bfa7416a6..81ddf78b3 100644 --- a/elasticsearch-model/spec/support/app/searchable.rb +++ b/elasticsearch-model/spec/support/app/searchable.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-model/spec/support/app/series.rb b/elasticsearch-model/spec/support/app/series.rb index 6028650b4..3b4d17989 100644 --- a/elasticsearch-model/spec/support/app/series.rb +++ b/elasticsearch-model/spec/support/app/series.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/Gemfile b/elasticsearch-persistence/Gemfile index 439b88c36..17e92d051 100644 --- a/elasticsearch-persistence/Gemfile +++ b/elasticsearch-persistence/Gemfile @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/Rakefile b/elasticsearch-persistence/Rakefile index ec3daf3ca..6d53d4c22 100644 --- a/elasticsearch-persistence/Rakefile +++ b/elasticsearch-persistence/Rakefile @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index b09aaee03..4d01500db 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/examples/notes/Gemfile b/elasticsearch-persistence/examples/notes/Gemfile index 1f98fc7f2..ef2c098c4 100644 --- a/elasticsearch-persistence/examples/notes/Gemfile +++ b/elasticsearch-persistence/examples/notes/Gemfile @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/examples/notes/application.rb b/elasticsearch-persistence/examples/notes/application.rb index 6c122999d..0f1724476 100644 --- a/elasticsearch-persistence/examples/notes/application.rb +++ b/elasticsearch-persistence/examples/notes/application.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/examples/notes/test.rb b/elasticsearch-persistence/examples/notes/test.rb index c99fff3f4..b57105341 100644 --- a/elasticsearch-persistence/examples/notes/test.rb +++ b/elasticsearch-persistence/examples/notes/test.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence.rb b/elasticsearch-persistence/lib/elasticsearch/persistence.rb index cf4337a25..ac845ce77 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index c10b5c00b..607de1769 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb index 81185cfd4..de2848071 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb index e2714ce4d..cdd50c042 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb index 4eae5b913..1852f192a 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/response/results.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb index dcc1b0d51..2aa7db725 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb index 04ec367aa..fe68a7fbb 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/serialize.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb index 96f68637f..a8eeb1866 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb index e5ba66085..b66194776 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/spec/repository/find_spec.rb b/elasticsearch-persistence/spec/repository/find_spec.rb index ab00e8d8b..083ee4c25 100644 --- a/elasticsearch-persistence/spec/repository/find_spec.rb +++ b/elasticsearch-persistence/spec/repository/find_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/spec/repository/response/results_spec.rb b/elasticsearch-persistence/spec/repository/response/results_spec.rb index 981773515..9d98cfc79 100644 --- a/elasticsearch-persistence/spec/repository/response/results_spec.rb +++ b/elasticsearch-persistence/spec/repository/response/results_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/spec/repository/search_spec.rb b/elasticsearch-persistence/spec/repository/search_spec.rb index 6986830c2..8e94ecc1d 100644 --- a/elasticsearch-persistence/spec/repository/search_spec.rb +++ b/elasticsearch-persistence/spec/repository/search_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/spec/repository/serialize_spec.rb b/elasticsearch-persistence/spec/repository/serialize_spec.rb index 7d6dfdfdc..17e358686 100644 --- a/elasticsearch-persistence/spec/repository/serialize_spec.rb +++ b/elasticsearch-persistence/spec/repository/serialize_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/spec/repository/store_spec.rb b/elasticsearch-persistence/spec/repository/store_spec.rb index 569aa410f..bebf810aa 100644 --- a/elasticsearch-persistence/spec/repository/store_spec.rb +++ b/elasticsearch-persistence/spec/repository/store_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/spec/repository_spec.rb b/elasticsearch-persistence/spec/repository_spec.rb index 472687fe5..eb957910f 100644 --- a/elasticsearch-persistence/spec/repository_spec.rb +++ b/elasticsearch-persistence/spec/repository_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-persistence/spec/spec_helper.rb b/elasticsearch-persistence/spec/spec_helper.rb index 73243fe19..6e5f58402 100644 --- a/elasticsearch-persistence/spec/spec_helper.rb +++ b/elasticsearch-persistence/spec/spec_helper.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/Gemfile b/elasticsearch-rails/Gemfile index 6787846e4..90572cba1 100644 --- a/elasticsearch-rails/Gemfile +++ b/elasticsearch-rails/Gemfile @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/Rakefile b/elasticsearch-rails/Rakefile index 1a03db185..20c7d1616 100644 --- a/elasticsearch-rails/Rakefile +++ b/elasticsearch-rails/Rakefile @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index 224e7fa36..55951bbaf 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/elasticsearch/rails.rb b/elasticsearch-rails/lib/elasticsearch/rails.rb index a2f85b1e0..1f6bac36f 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation.rb b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation.rb index 61a23093d..ca210066a 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/controller_runtime.rb b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/controller_runtime.rb index 09ca20ca7..a26765c54 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/controller_runtime.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/controller_runtime.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/log_subscriber.rb b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/log_subscriber.rb index cf313a8c2..c388961c0 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/log_subscriber.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/log_subscriber.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/publishers.rb b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/publishers.rb index ec4e4e743..2c36d91bd 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/publishers.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/publishers.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/railtie.rb b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/railtie.rb index adb1e494a..38fdbc6f1 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/railtie.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/railtie.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/elasticsearch/rails/lograge.rb b/elasticsearch-rails/lib/elasticsearch/rails/lograge.rb index 562756e81..d2bd06467 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/lograge.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/lograge.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb b/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb index 87fef3daa..91f4441ea 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/elasticsearch/rails/version.rb b/elasticsearch-rails/lib/elasticsearch/rails/version.rb index 23cddcf6f..ab125b547 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/version.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/version.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/rails/templates/01-basic.rb b/elasticsearch-rails/lib/rails/templates/01-basic.rb index 516732893..c517a0f3f 100644 --- a/elasticsearch-rails/lib/rails/templates/01-basic.rb +++ b/elasticsearch-rails/lib/rails/templates/01-basic.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/rails/templates/02-pretty.rb b/elasticsearch-rails/lib/rails/templates/02-pretty.rb index 4a70e5f39..3be42b481 100644 --- a/elasticsearch-rails/lib/rails/templates/02-pretty.rb +++ b/elasticsearch-rails/lib/rails/templates/02-pretty.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/rails/templates/03-expert.rb b/elasticsearch-rails/lib/rails/templates/03-expert.rb index dbb4104cb..aa7635531 100644 --- a/elasticsearch-rails/lib/rails/templates/03-expert.rb +++ b/elasticsearch-rails/lib/rails/templates/03-expert.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/rails/templates/04-dsl.rb b/elasticsearch-rails/lib/rails/templates/04-dsl.rb index 1325d57c1..b6eb6f893 100644 --- a/elasticsearch-rails/lib/rails/templates/04-dsl.rb +++ b/elasticsearch-rails/lib/rails/templates/04-dsl.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/rails/templates/05-settings-files.rb b/elasticsearch-rails/lib/rails/templates/05-settings-files.rb index 5c5e4bbeb..cf84213ce 100644 --- a/elasticsearch-rails/lib/rails/templates/05-settings-files.rb +++ b/elasticsearch-rails/lib/rails/templates/05-settings-files.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/rails/templates/indexer.rb b/elasticsearch-rails/lib/rails/templates/indexer.rb index 912b960ca..1ea1d9b6c 100644 --- a/elasticsearch-rails/lib/rails/templates/indexer.rb +++ b/elasticsearch-rails/lib/rails/templates/indexer.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb b/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb index a73119e66..fd10ad9e1 100644 --- a/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb +++ b/elasticsearch-rails/lib/rails/templates/search_controller_test.dsl.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/rails/templates/search_controller_test.rb b/elasticsearch-rails/lib/rails/templates/search_controller_test.rb index d195772c6..d627ae528 100644 --- a/elasticsearch-rails/lib/rails/templates/search_controller_test.rb +++ b/elasticsearch-rails/lib/rails/templates/search_controller_test.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb b/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb index fb3ff03d2..a0efeb997 100644 --- a/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb +++ b/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/rails/templates/searchable.rb b/elasticsearch-rails/lib/rails/templates/searchable.rb index d101826ef..cf7e4e800 100644 --- a/elasticsearch-rails/lib/rails/templates/searchable.rb +++ b/elasticsearch-rails/lib/rails/templates/searchable.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/lib/rails/templates/seeds.rb b/elasticsearch-rails/lib/rails/templates/seeds.rb index 74bf4a379..7b7294d2d 100644 --- a/elasticsearch-rails/lib/rails/templates/seeds.rb +++ b/elasticsearch-rails/lib/rails/templates/seeds.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/spec/instrumentation_spec.rb b/elasticsearch-rails/spec/instrumentation_spec.rb index ff70d4266..3fcbc5916 100644 --- a/elasticsearch-rails/spec/instrumentation_spec.rb +++ b/elasticsearch-rails/spec/instrumentation_spec.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/spec/lograge_spec.rb b/elasticsearch-rails/spec/lograge_spec.rb index 6ab0763f9..980aa9f1b 100644 --- a/elasticsearch-rails/spec/lograge_spec.rb +++ b/elasticsearch-rails/spec/lograge_spec.rb @@ -24,7 +24,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/elasticsearch-rails/spec/spec_helper.rb b/elasticsearch-rails/spec/spec_helper.rb index 9c2522eb9..e0a299964 100644 --- a/elasticsearch-rails/spec/spec_helper.rb +++ b/elasticsearch-rails/spec/spec_helper.rb @@ -6,7 +6,7 @@ # not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an From fadded5eb3242afc93c4217612ec2a65fffdd649 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 5 Aug 2021 21:48:07 +0100 Subject: [PATCH 509/582] [CI] Adds manual trigger for GH Actions --- .github/workflows/2.4.yml | 3 +++ .github/workflows/2.5.yml | 3 +++ .github/workflows/2.6.yml | 3 +++ .github/workflows/2.7.yml | 3 +++ .github/workflows/jruby.yml | 3 +++ .github/workflows/license.yml | 2 +- 6 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/2.4.yml b/.github/workflows/2.4.yml index bac506823..b16804be3 100644 --- a/.github/workflows/2.4.yml +++ b/.github/workflows/2.4.yml @@ -6,6 +6,9 @@ on: pull_request: branches: - master + workflow_dispatch: + branches: + - '*' jobs: tests: env: diff --git a/.github/workflows/2.5.yml b/.github/workflows/2.5.yml index 058182561..8d6352206 100644 --- a/.github/workflows/2.5.yml +++ b/.github/workflows/2.5.yml @@ -6,6 +6,9 @@ on: pull_request: branches: - master + workflow_dispatch: + branches: + - '*' jobs: tests: env: diff --git a/.github/workflows/2.6.yml b/.github/workflows/2.6.yml index d4e7f4345..a5c709e2d 100644 --- a/.github/workflows/2.6.yml +++ b/.github/workflows/2.6.yml @@ -6,6 +6,9 @@ on: pull_request: branches: - master + workflow_dispatch: + branches: + - '*' jobs: tests: env: diff --git a/.github/workflows/2.7.yml b/.github/workflows/2.7.yml index 514ee02eb..7a03199bb 100644 --- a/.github/workflows/2.7.yml +++ b/.github/workflows/2.7.yml @@ -6,6 +6,9 @@ on: pull_request: branches: - master + workflow_dispatch: + branches: + - '*' jobs: tests: env: diff --git a/.github/workflows/jruby.yml b/.github/workflows/jruby.yml index 6a7569b95..7175cbfb2 100644 --- a/.github/workflows/jruby.yml +++ b/.github/workflows/jruby.yml @@ -6,6 +6,9 @@ on: pull_request: branches: - master + workflow_dispatch: + branches: + - '*' jobs: tests: env: diff --git a/.github/workflows/license.yml b/.github/workflows/license.yml index d7357e56e..27201b4b7 100644 --- a/.github/workflows/license.yml +++ b/.github/workflows/license.yml @@ -1,5 +1,5 @@ name: License headers -on: [pull_request] +on: [pull_request, workflow_dispatch] jobs: build: runs-on: ubuntu-latest From 2a01d9fdc7686d7a9b5830c36ecdb521af8a1610 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 5 Aug 2021 21:56:07 +0100 Subject: [PATCH 510/582] [STORE] Update specs --- elasticsearch-persistence/spec/repository_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-persistence/spec/repository_spec.rb b/elasticsearch-persistence/spec/repository_spec.rb index eb957910f..05aeb1f53 100644 --- a/elasticsearch-persistence/spec/repository_spec.rb +++ b/elasticsearch-persistence/spec/repository_spec.rb @@ -104,7 +104,7 @@ class RepositoryWithoutDSL end it 'sets a default client' do - expect(repository.client).to be_a(Elasticsearch::Transport::Client) + expect(repository.client).to be_a(Elasticsearch::Client) end @@ -465,7 +465,7 @@ class RepositoryWithoutDSL end it 'sets a default on the instance' do - expect(RepositoryWithoutDSL.new.client).to be_a(Elasticsearch::Transport::Client) + expect(RepositoryWithoutDSL.new.client).to be_a(Elasticsearch::Client) end it 'allows the value to be overridden with options on the instance' do From f0500422a8f15584a731e52d907f15b8f37c7714 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 5 Aug 2021 22:02:00 +0100 Subject: [PATCH 511/582] [MODEL] Fixes indexing to use right logger in client --- elasticsearch-model/lib/elasticsearch/model.rb | 4 ++-- elasticsearch-model/lib/elasticsearch/model/client.rb | 5 ----- .../lib/elasticsearch/model/indexing.rb | 4 ++-- .../spec/elasticsearch/model/indexing_spec.rb | 10 ++++++++-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index fe9c33047..af2f34cb6 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -127,7 +127,7 @@ module ClassMethods # @example Get the client # # Elasticsearch::Model.client - # => #<Elasticsearch::Transport::Client:0x007f96a7d0d000 @transport=... > + # => #<Elasticsearch::Client:0x007f96a7d0d000... > # def client @client ||= Elasticsearch::Client.new @@ -138,7 +138,7 @@ def client # @example Configure (set) the client for all models # # Elasticsearch::Model.client = Elasticsearch::Client.new host: 'http://localhost:9200', tracer: true - # => #<Elasticsearch::Transport::Client:0x007f96a6dd0d80 @transport=... > + # => #<Elasticsearch::Client:0x007f96a6dd0d80... > # # @note You have to set the client before you call Elasticsearch methods on the model, # or set it directly on the model; see {Elasticsearch::Model::Client::ClassMethods#client} diff --git a/elasticsearch-model/lib/elasticsearch/model/client.rb b/elasticsearch-model/lib/elasticsearch/model/client.rb index 01a122440..b47a0925f 100644 --- a/elasticsearch-model/lib/elasticsearch/model/client.rb +++ b/elasticsearch-model/lib/elasticsearch/model/client.rb @@ -17,13 +17,10 @@ module Elasticsearch module Model - # Contains an `Elasticsearch::Client` instance # module Client - module ClassMethods - # Get the client for a specific model class # # @example Get the client for `Article` and perform API request @@ -48,7 +45,6 @@ def client=(client) end module InstanceMethods - # Get or set the client for a specific model instance # # @example Get the client for a specific record and perform API request @@ -72,7 +68,6 @@ def client=(client) @client = client end end - end end end diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index f35ef7342..3ebd8cfb4 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -290,7 +290,7 @@ def delete_index!(options={}) self.client.indices.delete index: target_index rescue Exception => e if e.class.to_s =~ /NotFound/ && options[:force] - client.transport.logger.debug("[!!!] Index does not exist (#{e.class})") if client.transport.logger + client.transport.transport.logger.debug("[!!!] Index does not exist (#{e.class})") if client.transport.transport.logger nil else raise e @@ -317,7 +317,7 @@ def refresh_index!(options={}) self.client.indices.refresh index: target_index rescue Exception => e if e.class.to_s =~ /NotFound/ && options[:force] - client.transport.logger.debug("[!!!] Index does not exist (#{e.class})") if client.transport.logger + client.transport.transport.logger.debug("[!!!] Index does not exist (#{e.class})") if client.transport.transport.logger nil else raise e diff --git a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb index 257294fa6..efda997c8 100644 --- a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb @@ -676,9 +676,15 @@ class ::DummyIndexingModelForRecreate end context 'when the index is not found' do - let(:client) do - double('client', indices: indices, transport: double('transport', { logger: nil })) + double( + 'client', + indices: indices, + transport: double( + 'transport', + double('transport', { logger: nil }) + ) + ) end let(:indices) do From 5d5df6332474351f8c5a6ec82db9e4369cbb8970 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 5 Aug 2021 22:20:27 +0100 Subject: [PATCH 512/582] [MODEL] Updates ES client spec for client 7.14.0 --- .../spec/elasticsearch/model/client_spec.rb | 6 ++-- .../spec/elasticsearch/model/indexing_spec.rb | 35 +++++++++---------- .../spec/elasticsearch/model/module_spec.rb | 2 +- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/elasticsearch-model/spec/elasticsearch/model/client_spec.rb b/elasticsearch-model/spec/elasticsearch/model/client_spec.rb index 421bd3c50..12814d701 100644 --- a/elasticsearch-model/spec/elasticsearch/model/client_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/client_spec.rb @@ -33,14 +33,14 @@ class ::DummyClientModel context 'when a class includes the client module class methods' do it 'defines the client module class methods on the model' do - expect(DummyClientModel.client).to be_a(Elasticsearch::Transport::Client) + expect(DummyClientModel.client).to be_a(Elasticsearch::Client) end end context 'when a class includes the client module instance methods' do it 'defines the client module class methods on the model' do - expect(DummyClientModel.new.client).to be_a(Elasticsearch::Transport::Client) + expect(DummyClientModel.new.client).to be_a(Elasticsearch::Client) end end @@ -77,7 +77,7 @@ class ::DummyClientModel end it 'does not set the client on the class' do - expect(DummyClientModel.client).to be_a(Elasticsearch::Transport::Client) + expect(DummyClientModel.client).to be_a(Elasticsearch::Client) end end end diff --git a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb index efda997c8..eb9651a73 100644 --- a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb @@ -676,15 +676,13 @@ class ::DummyIndexingModelForRecreate end context 'when the index is not found' do + let(:logger) { nil } + let(:transport) do + Elasticsearch::Transport::Client.new(logger: logger) + end + let(:client) do - double( - 'client', - indices: indices, - transport: double( - 'transport', - double('transport', { logger: nil }) - ) - ) + double('client', indices: indices, transport: transport) end let(:indices) do @@ -698,19 +696,17 @@ class ::DummyIndexingModelForRecreate end context 'when the force option is true' do - it 'deletes the index without raising an exception' do expect(DummyIndexingModelForRecreate.delete_index!(force: true)).to be_nil end context 'when the client has a logger' do - let(:logger) do Logger.new(STDOUT).tap { |l| l.level = Logger::DEBUG } end let(:client) do - double('client', indices: indices, transport: double('transport', { logger: logger })) + double('client', indices: indices, transport: transport) end it 'deletes the index without raising an exception' do @@ -918,7 +914,11 @@ class ::DummyIndexingModelForRefresh end let(:client) do - double('client', indices: indices, transport: double('transport', { logger: nil })) + double('client', indices: indices, transport: transport) + end + + let(:transport) do + Elasticsearch::Transport::Client.new(logger: nil) end let(:indices) do @@ -930,9 +930,7 @@ class ::DummyIndexingModelForRefresh end context 'when the force option is true' do - context 'when the operation raises a NotFound exception' do - before do expect(indices).to receive(:refresh).and_raise(NotFound) end @@ -942,13 +940,16 @@ class ::DummyIndexingModelForRefresh end context 'when the client has a logger' do - let(:logger) do Logger.new(STDOUT).tap { |l| l.level = Logger::DEBUG } end let(:client) do - double('client', indices: indices, transport: double('transport', { logger: logger })) + double('client', indices: indices, transport: transport) + end + + let(:transport) do + Elasticsearch::Transport::Client.new(logger: logger) end it 'does not raise an exception' do @@ -963,7 +964,6 @@ class ::DummyIndexingModelForRefresh end context 'when the operation raises another type of exception' do - before do expect(indices).to receive(:refresh).and_raise(Exception) end @@ -977,7 +977,6 @@ class ::DummyIndexingModelForRefresh end context 'when an index name is provided in the options' do - before do expect(indices).to receive(:refresh).with(index: 'custom-foo') end diff --git a/elasticsearch-model/spec/elasticsearch/model/module_spec.rb b/elasticsearch-model/spec/elasticsearch/model/module_spec.rb index 2712318c5..ed7a58297 100644 --- a/elasticsearch-model/spec/elasticsearch/model/module_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/module_spec.rb @@ -22,7 +22,7 @@ describe '#client' do it 'should have a default' do - expect(Elasticsearch::Model.client).to be_a(Elasticsearch::Transport::Client) + expect(Elasticsearch::Model.client).to be_a(Elasticsearch::Client) end end From 6a0b594cbfe71368fcd8452b1025f618c17a3431 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 9 Aug 2021 10:02:08 +0100 Subject: [PATCH 513/582] [MODEL][STORE] Updates transport references --- elasticsearch-model/examples/activerecord_associations.rb | 2 +- elasticsearch-model/examples/activerecord_custom_analyzer.rb | 4 ++-- elasticsearch-persistence/README.md | 4 ++-- elasticsearch-persistence/examples/notes/test.rb | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/elasticsearch-model/examples/activerecord_associations.rb b/elasticsearch-model/examples/activerecord_associations.rb index b8100b33b..17d521cbf 100644 --- a/elasticsearch-model/examples/activerecord_associations.rb +++ b/elasticsearch-model/examples/activerecord_associations.rb @@ -81,7 +81,7 @@ # ----- Elasticsearch client setup ---------------------------------------------------------------- Elasticsearch::Model.client = Elasticsearch::Client.new log: true -Elasticsearch::Model.client.transport.logger.formatter = proc { |s, d, p, m| "\e[2m#{m}\n\e[0m" } +Elasticsearch::Model.client.transport.transport.logger.formatter = proc { |s, d, p, m| "\e[2m#{m}\n\e[0m" } # ----- Search integration ------------------------------------------------------------------------ diff --git a/elasticsearch-model/examples/activerecord_custom_analyzer.rb b/elasticsearch-model/examples/activerecord_custom_analyzer.rb index 886eaab91..d16ddbd11 100644 --- a/elasticsearch-model/examples/activerecord_custom_analyzer.rb +++ b/elasticsearch-model/examples/activerecord_custom_analyzer.rb @@ -37,8 +37,8 @@ end end -Elasticsearch::Model.client.transport.logger = ActiveSupport::Logger.new(STDOUT) -Elasticsearch::Model.client.transport.logger.formatter = lambda { |s, d, p, m| "#{m.ansi(:faint)}\n" } +Elasticsearch::Model.client.transport.transport.logger = ActiveSupport::Logger.new(STDOUT) +Elasticsearch::Model.client.transport.transport.logger.formatter = lambda { |s, d, p, m| "#{m.ansi(:faint)}\n" } class Article < ActiveRecord::Base include Elasticsearch::Model diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index 1d4fac551..c196c2f95 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -236,7 +236,7 @@ You can also override the default configuration with options passed to the initi ```ruby client = Elasticsearch::Client.new(url: 'http://localhost:9250', log: true) -client.transport.logger.formatter = proc { |s, d, p, m| "\e[2m# #{m}\n\e[0m" } +client.transport.transport.logger.formatter = proc { |s, d, p, m| "\e[2m# #{m}\n\e[0m" } repository = NoteRepository.new(client: client, index_name: 'notes_development') repository.create_index!(force: true) @@ -267,7 +267,7 @@ The repository uses the standard Elasticsearch [client](https://github.com/elast ```ruby client = Elasticsearch::Client.new(url: 'http://search.server.org') repository = NoteRepository.new(client: client) -repository.client.transport.logger = Logger.new(STDERR) +repository.client.transport.transport.logger = Logger.new(STDERR) repository.client # => Elasticsearch::Client diff --git a/elasticsearch-persistence/examples/notes/test.rb b/elasticsearch-persistence/examples/notes/test.rb index b57105341..27922933b 100644 --- a/elasticsearch-persistence/examples/notes/test.rb +++ b/elasticsearch-persistence/examples/notes/test.rb @@ -90,7 +90,7 @@ def app app.settings.repository.client = Elasticsearch::Client.new \ hosts: [{ host: 'localhost', port: ENV.fetch('TEST_CLUSTER_PORT', 9250)}], log: true - app.settings.repository.client.transport.logger.formatter = proc { |s, d, p, m| "\e[2m#{m}\n\e[0m" } + app.settings.repository.client.transport.transport.logger.formatter = proc { |s, d, p, m| "\e[2m#{m}\n\e[0m" } app.settings.repository.create_index! force: true app.settings.repository.client.cluster.health wait_for_status: 'yellow' end From f965acc84ff11fcf480340e7f4182d697f652416 Mon Sep 17 00:00:00 2001 From: NoharaMasato <masato338914914@gmail.com> Date: Thu, 4 Feb 2021 12:42:04 +0900 Subject: [PATCH 514/582] [#966] Adds alias for Elasticsearch::Model::Response::Results#records By @NoharaMasato in #976 --- .../lib/elasticsearch/model/response/results.rb | 1 + .../elasticsearch/model/response/results_spec.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/elasticsearch-model/lib/elasticsearch/model/response/results.rb b/elasticsearch-model/lib/elasticsearch/model/response/results.rb index cf7e5aaf5..564a684a3 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/results.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/results.rb @@ -42,6 +42,7 @@ def results response.response['hits']['hits'].map { |hit| Result.new(hit) } end + alias records results end end end diff --git a/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb index 946043a9f..f69db2c01 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb @@ -48,6 +48,10 @@ def self.document_type; 'bar'; end response.results end + let(:records) do + response.records + end + describe '#results' do it 'provides access to the results' do @@ -70,4 +74,12 @@ def self.document_type; 'bar'; end expect(response.raw_response).to eq(response_document) end end + + describe '#records' do + + it 'provides access to the records' do + expect(results.records.size).to be(results.results.size) + expect(results.records.first.foo).to eq(results.results.first.foo) + end + end end From bd626568eafeed4ee0242e605e70b313332a6b81 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Wed, 11 Aug 2021 10:21:03 +0100 Subject: [PATCH 515/582] Fixes some styling in Rakefile --- Rakefile | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Rakefile b/Rakefile index a2dd17e69..9b3347f86 100644 --- a/Rakefile +++ b/Rakefile @@ -54,12 +54,13 @@ task :default do system 'rake --tasks' end +desc 'Show subprojects information' task :subprojects do - puts '-'*80 + puts '-' * 80 subprojects.each do |project| commit = `git log --pretty=format:'%h %ar: %s' -1 #{project}` - version = Gem::Specification::load(__current__.join(project, "#{project}.gemspec").to_s).version.to_s - puts "[#{version}] \e[1m#{project.ljust(subprojects.map {|s| s.length}.max)}\e[0m | #{commit[ 0..80]}..." + version = Gem::Specification.load(__current__.join(project, "#{project}.gemspec").to_s).version.to_s + puts "[#{version}] \e[1m#{project.ljust(subprojects.map(&:length).max)}\e[0m | #{commit[0..80]}..." end end @@ -87,7 +88,7 @@ namespace :bundle do end namespace :test do - task :bundle => 'bundle:install' + task bundle: 'bundle:install' desc "Run unit tests in all subprojects" task :unit do @@ -114,7 +115,7 @@ namespace :test do end desc "Run integration tests in all subprojects" - task :integration => :setup_elasticsearch do + task integration: :setup_elasticsearch do # 1/ elasticsearch-model # puts '-'*80 @@ -139,7 +140,7 @@ namespace :test do end desc "Run all tests in all subprojects" - task :all => :wait_for_green do + task all: :wait_for_green do subprojects.each do |project| puts '-'*80 sh "cd #{project} && " + @@ -150,7 +151,6 @@ namespace :test do end end - desc "Wait for elasticsearch cluster to be in green state" task :wait_for_green do require 'elasticsearch' @@ -176,7 +176,7 @@ task :wait_for_green do end end -desc "Generate documentation for all subprojects" +desc 'Generate documentation for all subprojects' task :doc do subprojects.each do |project| sh "cd #{__current__.join(project)} && rake doc" @@ -184,11 +184,11 @@ task :doc do end end -desc "Release all subprojects to Rubygems" +desc 'Release all subprojects to Rubygems' task :release do subprojects.each do |project| sh "cd #{__current__.join(project)} && rake release" - puts '-'*80 + puts '-' * 80 end end @@ -199,17 +199,17 @@ desc <<-DESC $ rake update_version[5.0.0,5.0.1] DESC -task :update_version, :old, :new do |task, args| +task :update_version, :old, :new do |_, args| require 'ansi' - puts "[!!!] Required argument [old] missing".ansi(:red) unless args[:old] - puts "[!!!] Required argument [new] missing".ansi(:red) unless args[:new] + puts '[!!!] Required argument [old] missing'.ansi(:red) unless args[:old] + puts '[!!!] Required argument [new] missing'.ansi(:red) unless args[:new] - files = Dir['**/**/version.rb','**/**/*.gemspec'] + files = Dir['**/**/version.rb', '**/**/*.gemspec'] - longest_line = files.map { |f| f.size }.max + longest_line = files.map(&:size).max - puts "\n", "= FILES ".ansi(:faint) + ('='*92).ansi(:faint), "\n" + puts "\n", '= FILES '.ansi(:faint) + ('=' * 92).ansi(:faint), "\n" files.each do |file| begin @@ -217,7 +217,7 @@ task :update_version, :old, :new do |task, args| content = f.read if content.match Regexp.new(args[:old]) content.gsub! Regexp.new(args[:old]), args[:new] - puts "+ [#{file}]".ansi(:green).ljust(longest_line+20) + " [#{args[:old]}] -> [#{args[:new]}]".ansi(:green,:bold) + puts "+ [#{file}]".ansi(:green).ljust(longest_line + 20) + " [#{args[:old]}] -> [#{args[:new]}]".ansi(:green, :bold) f.rewind f.write content else @@ -300,8 +300,8 @@ task :update_version, :old, :new do |task, args| puts "\n\n", "= COMMIT ".ansi(:faint) + ('='*91).ansi(:faint), "\n" - puts "git add CHANGELOG.md elasticsearch-*", + puts 'git add CHANGELOG.md elasticsearch-*', "git commit --verbose --message='Release #{args[:new]}' --edit", - "rake release" + 'rake release' "\n" end From 78ee91f30f761fbebf8cda7f08099aae649ce510 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Wed, 11 Aug 2021 10:45:01 +0100 Subject: [PATCH 516/582] Bumps version to 7.2.0 --- elasticsearch-model/lib/elasticsearch/model/version.rb | 2 +- elasticsearch-persistence/elasticsearch-persistence.gemspec | 2 +- .../lib/elasticsearch/persistence/version.rb | 2 +- elasticsearch-rails/lib/elasticsearch/rails/version.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/version.rb b/elasticsearch-model/lib/elasticsearch/model/version.rb index ce56f7798..fe79283ab 100644 --- a/elasticsearch-model/lib/elasticsearch/model/version.rb +++ b/elasticsearch-model/lib/elasticsearch/model/version.rb @@ -17,6 +17,6 @@ module Elasticsearch module Model - VERSION = "7.1.0" + VERSION = "7.2.0" end end diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index 4d01500db..6e7970e8e 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -41,7 +41,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 1.9.3" s.add_dependency "elasticsearch", '~> 7' - s.add_dependency "elasticsearch-model", '7.1.0' + s.add_dependency "elasticsearch-model", '7.2.0' s.add_dependency "activesupport", '> 4' s.add_dependency "activemodel", '> 4' s.add_dependency "hashie" diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb index b66194776..4c6ad62d9 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb @@ -17,6 +17,6 @@ module Elasticsearch module Persistence - VERSION = '7.1.0' + VERSION = '7.2.0' end end diff --git a/elasticsearch-rails/lib/elasticsearch/rails/version.rb b/elasticsearch-rails/lib/elasticsearch/rails/version.rb index ab125b547..9be1f1045 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/version.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/version.rb @@ -17,6 +17,6 @@ module Elasticsearch module Rails - VERSION = "7.1.0" + VERSION = "7.2.0" end end From 2e33f226789c612599850dc5728f6bddcfa9fee7 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Wed, 11 Aug 2021 10:45:12 +0100 Subject: [PATCH 517/582] [DOCS] Updates Changelog to 7.2.0 --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7af564d7..02e595151 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +## 7.2.0 + +* Updates specs and dependency to use with `elasticsearch` v7.14.0. +* Update README, remove Virtus (unmaintained) +* Updates `Bundler.with_clean_dev` (deprecated) to `with_unbundled_env` [commit](https://github.com/elastic/elasticsearch-rails/commit/e4545e4fe2a1ce80009206c831d5740360bad6c2) +* Deal with `nil` document types in Multimodel [commit](https://github.com/elastic/elasticsearch-rails/commit/cd9c309b78de443d2e37760998418616ba34276d) +* Update dependency to explicitly support version 7 [commit](https://github.com/elastic/elasticsearch-rails/commit/65942e3da9cabad2f6965e69c8ef6a0994da9408) +* Stop emitting FATAL log when checking existence of indices [commit](https://github.com/elastic/elasticsearch-rails/commit/5db9207ca398c5d77f671109360ca7f63e3f2112) +* Remove unnecessary exception test on index checking [commit](https://github.com/elastic/elasticsearch-rails/commit/ce57cc17e304b0a4af123c1599f37fb892a5d93a) +* Removes dependency on extensions [commit](https://github.com/elastic/elasticsearch-rails/commit/ed070b8329ca48b4cb12b513ac81ed78c88acc61) +* Fixes basic template elasticsearch dependency [commit](https://github.com/elastic/elasticsearch-rails/commit/a4ec07b2d097545ca41c13686c9cbfc9eab9e639) + +### ActiveModel + +* Fixes indexing to use right logger in client +* Updates ES client spec for client 7.14.0 +* Updates transport references + +## 7.1.1 + +* Fix: Ruby 2.7 deprecation warning on `find_in_batches` +* Updates README for generating app with template. Related: #938 + +### ActiveModel + +* Do not override existing methods (#936) + ## 7.1.0 * Tested with elasticsearch Ruby client version 7.6.0 From 8404aaacad7d6826f5daed4f006837c6f65b725e Mon Sep 17 00:00:00 2001 From: Andre Arko <andre@arko.net> Date: Sun, 25 Apr 2021 22:21:39 -0700 Subject: [PATCH 518/582] create failing spec for keyword args proxy when the proxy fails to forward keywoard arguments, the error for methods that only accept keyword arguments looks like this: ArgumentError: wrong number of arguments (given 1, expected 0) --- .../spec/elasticsearch/model/proxy_spec.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb b/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb index e9d9712c8..f21baad3d 100644 --- a/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb @@ -31,6 +31,10 @@ def bar 'insta barr' end + def keyword_method(foo: 'default value') + foo + end + def as_json(options) {foo: 'bar'} end @@ -98,7 +102,6 @@ def changes_to_save end context 'when instances are cloned' do - let!(:model) do DummyProxyModel.new end @@ -121,4 +124,9 @@ def changes_to_save expect(duplicate).to eq(duplicate_target) end end + + it 'forwards keyword arguments to target methods' do + expect(DummyProxyModel.new.__elasticsearch__.keyword_method(foo: 'bar')).to eq('bar') + end + end From 224c0b660b23ccfb393189df94f9a32592e5ae88 Mon Sep 17 00:00:00 2001 From: Andre Arko <andre@arko.net> Date: Sun, 25 Apr 2021 22:26:44 -0700 Subject: [PATCH 519/582] fix proxying keyword arguments this resolves a bug with Rails 6.1 on Ruby 3, where keyword arguments are not forwarded and this causes an ArgumentError --- elasticsearch-model/lib/elasticsearch/model/proxy.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/proxy.rb b/elasticsearch-model/lib/elasticsearch/model/proxy.rb index 955280fba..969593e46 100644 --- a/elasticsearch-model/lib/elasticsearch/model/proxy.rb +++ b/elasticsearch-model/lib/elasticsearch/model/proxy.rb @@ -117,8 +117,8 @@ def initialize(target) # Delegate methods to `@target` # - def method_missing(method_name, *arguments, &block) - target.respond_to?(method_name) ? target.__send__(method_name, *arguments, &block) : super + def method_missing(method_name, *args, **kwargs, &block) + target.respond_to?(method_name) ? target.__send__(method_name, *args, **kwargs, &block) : super end # Respond to methods from `@target` From 4030e6d563ab95b5a54a314bbeebac5688fa0ea8 Mon Sep 17 00:00:00 2001 From: Andre Arko <andre@arko.net> Date: Sun, 25 Apr 2021 22:28:06 -0700 Subject: [PATCH 520/582] use respond_to_missing? not respond_to? since Ruby 1.9.2, about 12 years ago, you're supposed to define respond_to_missing? if you also define method_missing. there's more explanation in this blog post by a ruby committer in 2010: http://blog.marc-andre.ca/2010/11/15/methodmissing-politely/ --- elasticsearch-model/lib/elasticsearch/model/proxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/proxy.rb b/elasticsearch-model/lib/elasticsearch/model/proxy.rb index 969593e46..f26cda899 100644 --- a/elasticsearch-model/lib/elasticsearch/model/proxy.rb +++ b/elasticsearch-model/lib/elasticsearch/model/proxy.rb @@ -123,7 +123,7 @@ def method_missing(method_name, *args, **kwargs, &block) # Respond to methods from `@target` # - def respond_to?(method_name, include_private = false) + def respond_to_missing?(method_name, include_private = false) target.respond_to?(method_name) || super end From d12d812c3f52ac484cf73805ef41986dd95ba5a0 Mon Sep 17 00:00:00 2001 From: Andre Arko <andre@arko.net> Date: Mon, 26 Apr 2021 15:33:14 -0700 Subject: [PATCH 521/582] update method missing for Ruby 2.x-3.0 all at once --- elasticsearch-model/lib/elasticsearch/model/proxy.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/proxy.rb b/elasticsearch-model/lib/elasticsearch/model/proxy.rb index f26cda899..6fdf66322 100644 --- a/elasticsearch-model/lib/elasticsearch/model/proxy.rb +++ b/elasticsearch-model/lib/elasticsearch/model/proxy.rb @@ -115,10 +115,13 @@ def initialize(target) @target = target end - # Delegate methods to `@target` + def ruby2_keywords(*) # :nodoc: + end if RUBY_VERSION < "2.7" + + # Delegate methods to `@target`. As per [the Ruby 3.0 explanation for keyword arguments](https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/), the only way to work on Ruby <2.7, and 2.7, and 3.0+ is to use `ruby2_keywords`. # - def method_missing(method_name, *args, **kwargs, &block) - target.respond_to?(method_name) ? target.__send__(method_name, *args, **kwargs, &block) : super + ruby2_keywords def method_missing(method_name, *arguments, &block) + target.respond_to?(method_name) ? target.__send__(method_name, *arguments, &block) : super end # Respond to methods from `@target` From 835ea07643011544f9a6f0866557cc5f27ad1e51 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 9 Sep 2021 11:02:41 +0100 Subject: [PATCH 522/582] Renames master to main --- .github/workflows/2.4.yml | 4 +-- .github/workflows/2.5.yml | 4 +-- .github/workflows/2.6.yml | 4 +-- .github/workflows/2.7.yml | 4 +-- .github/workflows/jruby.yml | 4 +-- README.md | 26 +++++++++---------- elasticsearch-model/README.md | 12 ++++----- elasticsearch-persistence/README.md | 4 +-- elasticsearch-rails/README.md | 18 ++++++------- .../elasticsearch-rails.gemspec | 2 +- .../lib/rails/templates/01-basic.rb | 4 +-- .../lib/rails/templates/02-pretty.rb | 2 +- .../lib/rails/templates/03-expert.rb | 16 ++++++------ .../lib/rails/templates/04-dsl.rb | 6 ++--- .../lib/rails/templates/05-settings-files.rb | 4 +-- 15 files changed, 57 insertions(+), 57 deletions(-) diff --git a/.github/workflows/2.4.yml b/.github/workflows/2.4.yml index b16804be3..890c78613 100644 --- a/.github/workflows/2.4.yml +++ b/.github/workflows/2.4.yml @@ -2,10 +2,10 @@ name: Ruby 2.4 on: push: branches: - - master + - main pull_request: branches: - - master + - main workflow_dispatch: branches: - '*' diff --git a/.github/workflows/2.5.yml b/.github/workflows/2.5.yml index 8d6352206..68f8294fe 100644 --- a/.github/workflows/2.5.yml +++ b/.github/workflows/2.5.yml @@ -2,10 +2,10 @@ name: Ruby 2.5 on: push: branches: - - master + - main pull_request: branches: - - master + - main workflow_dispatch: branches: - '*' diff --git a/.github/workflows/2.6.yml b/.github/workflows/2.6.yml index a5c709e2d..c003a58e5 100644 --- a/.github/workflows/2.6.yml +++ b/.github/workflows/2.6.yml @@ -2,10 +2,10 @@ name: Ruby 2.6 on: push: branches: - - master + - main pull_request: branches: - - master + - main workflow_dispatch: branches: - '*' diff --git a/.github/workflows/2.7.yml b/.github/workflows/2.7.yml index 7a03199bb..8bfd9b66f 100644 --- a/.github/workflows/2.7.yml +++ b/.github/workflows/2.7.yml @@ -2,10 +2,10 @@ name: Ruby 2.7 on: push: branches: - - master + - main pull_request: branches: - - master + - main workflow_dispatch: branches: - '*' diff --git a/.github/workflows/jruby.yml b/.github/workflows/jruby.yml index 7175cbfb2..3a397ebc0 100644 --- a/.github/workflows/jruby.yml +++ b/.github/workflows/jruby.yml @@ -2,10 +2,10 @@ name: JRuby on: push: branches: - - master + - main pull_request: branches: - - master + - main workflow_dispatch: branches: - '*' diff --git a/README.md b/README.md index 96c958674..8a6a745fe 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ gem 'elasticsearch-rails', github: 'elastic/elasticsearch-rails', branch: '5.x' The libraries are compatible with Ruby 2.4 and higher. -The version numbers follow the Elasticsearch major versions. The `master` branch is compatible with the latest Elasticsearch stack stable release. +The version numbers follow the Elasticsearch major versions. The `main` branch is compatible with the latest Elasticsearch stack stable release. | Rubygem | | Elasticsearch | |:-------------:|:-:| :-----------: | @@ -48,7 +48,7 @@ The version numbers follow the Elasticsearch major versions. The `master` branch | 2.x | → | 2.x | | 5.x | → | 5.x | | 6.x | → | 6.x | -| master | → | 7.x | +| main | → | 7.x | Use a release that matches the major version of Elasticsearch in your stack. Each client version is backwards compatible with all minor versions of the same major version. @@ -58,13 +58,13 @@ Check out [Elastic product end of life dates](https://www.elastic.co/support/eol This project is split into three separate gems: -* [**`elasticsearch-model`**](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-model), +* [**`elasticsearch-model`**](https://github.com/elastic/elasticsearch-rails/tree/main/elasticsearch-model), which contains search integration for Ruby/Rails models such as ActiveRecord::Base and Mongoid, -* [**`elasticsearch-persistence`**](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-persistence), +* [**`elasticsearch-persistence`**](https://github.com/elastic/elasticsearch-rails/tree/main/elasticsearch-persistence), which provides a standalone persistence layer for Ruby/Rails objects and models -* [**`elasticsearch-rails`**](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-rails), +* [**`elasticsearch-rails`**](https://github.com/elastic/elasticsearch-rails/tree/main/elasticsearch-rails), which contains various features for Ruby on Rails applications Example of a basic integration into an ActiveRecord-based model: @@ -87,7 +87,7 @@ Article.import ``` You can generate a simple Ruby on Rails application with a single command -(see the [other available templates](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-rails#rails-application-templates)). You'll need to have an Elasticsearch cluster running on your system before generating the app. The easiest way of getting this set up is by running it with Docker with this command: +(see the [other available templates](https://github.com/elastic/elasticsearch-rails/tree/main/elasticsearch-rails#rails-application-templates)). You'll need to have an Elasticsearch cluster running on your system before generating the app. The easiest way of getting this set up is by running it with Docker with this command: ```bash docker run \ @@ -103,7 +103,7 @@ You can generate a simple Ruby on Rails application with a single command Once Elasticsearch is running, you can generate the simple app with this command: ```bash -rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/01-basic.rb +rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/01-basic.rb ``` Example of using Elasticsearch as a repository for a Ruby domain object: @@ -125,21 +125,21 @@ repository.save Article.new(title: 'Test') ### Model -* [[README]](https://github.com/elastic/elasticsearch-rails/blob/master/elasticsearch-model/README.md) +* [[README]](https://github.com/elastic/elasticsearch-rails/blob/main/elasticsearch-model/README.md) * [[Documentation]](http://rubydoc.info/gems/elasticsearch-model/) -* [[Test Suite]](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-model/spec/elasticsearch/model) +* [[Test Suite]](https://github.com/elastic/elasticsearch-rails/tree/main/elasticsearch-model/spec/elasticsearch/model) ### Persistence -* [[README]](https://github.com/elastic/elasticsearch-rails/blob/master/elasticsearch-persistence/README.md) +* [[README]](https://github.com/elastic/elasticsearch-rails/blob/main/elasticsearch-persistence/README.md) * [[Documentation]](http://rubydoc.info/gems/elasticsearch-persistence/) -* [[Test Suite]](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-persistence/spec) +* [[Test Suite]](https://github.com/elastic/elasticsearch-rails/tree/main/elasticsearch-persistence/spec) ### Rails -* [[README]](https://github.com/elastic/elasticsearch-rails/blob/master/elasticsearch-rails/README.md) +* [[README]](https://github.com/elastic/elasticsearch-rails/blob/main/elasticsearch-rails/README.md) * [[Documentation]](http://rubydoc.info/gems/elasticsearch-rails) -* [[Test Suite]](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-rails/spec) +* [[Test Suite]](https://github.com/elastic/elasticsearch-rails/tree/main/elasticsearch-rails/spec) ## Development diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index bd1b3927b..a5345b4b9 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -8,7 +8,7 @@ It aims to simplify integration of Ruby classes ("models"), commonly found e.g. This library is compatible with Ruby 2.4 and higher. -The library version numbers follow the Elasticsearch major versions. The `master` branch is compatible with the latest Elasticsearch stack stable release. +The library version numbers follow the Elasticsearch major versions. The `main` branch is compatible with the latest Elasticsearch stack stable release. | Rubygem | | Elasticsearch | |:-------------:|:-:| :-----------: | @@ -16,7 +16,7 @@ The library version numbers follow the Elasticsearch major versions. The `master | 2.x | → | 2.x | | 5.x | → | 5.x | | 6.x | → | 6.x | -| master | → | 7.x | +| main | → | 7.x | ## Installation @@ -116,7 +116,7 @@ See the `Elasticsearch::Model` module documentation for technical information. ### The Elasticsearch client -The module will set up a [client](https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch), +The module will set up a [client](https://github.com/elastic/elasticsearch-ruby/tree/main/elasticsearch), connected to `localhost:9200`, by default. You can access and use it as any other `Elasticsearch::Client`: ```ruby @@ -139,7 +139,7 @@ Elasticsearch::Model.client = Elasticsearch::Client.new log: true You might want to do this during your application bootstrap process, e.g. in a Rails initializer. Please refer to the -[`elasticsearch-transport`](https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch-transport) +[`elasticsearch-transport`](https://github.com/elastic/elasticsearch-ruby/tree/main/elasticsearch-transport) library documentation for all the configuration options, and to the [`elasticsearch-api`](http://rubydoc.info/gems/elasticsearch-api) library documentation for information about the Ruby client API. @@ -248,7 +248,7 @@ response.records.order(:title).to_a The `records` method returns the real instances of your model, which is useful when you want to access your model methods -- at the expense of slowing down your application, of course. In most cases, working with `results` coming from Elasticsearch is sufficient, and much faster. See the -[`elasticsearch-rails`](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-rails) +[`elasticsearch-rails`](https://github.com/elastic/elasticsearch-rails/tree/main/elasticsearch-rails) library for more information about compatibility with the Ruby on Rails framework. When you want to access both the database `records` and search `results`, use the `each_with_hit` @@ -343,7 +343,7 @@ response.results.first.title # => "Quick brown fox" ``` -Also, you can use the [**`elasticsearch-dsl`**](https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch-dsl) library, which provides a specialized Ruby API for the Elasticsearch Query DSL: +Also, you can use the [**`elasticsearch-dsl`**](https://github.com/elastic/elasticsearch-ruby/tree/main/elasticsearch-dsl) library, which provides a specialized Ruby API for the Elasticsearch Query DSL: ```ruby require 'elasticsearch/dsl' diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index c196c2f95..6b905f593 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -6,7 +6,7 @@ Persistence layer for Ruby domain objects in Elasticsearch, using the Repository This library is compatible with Ruby 2.4 and higher. -The library version numbers follow the Elasticsearch major versions. The `master` branch is compatible with the latest Elasticsearch stack stable release. +The library version numbers follow the Elasticsearch major versions. The `main` branch is compatible with the latest Elasticsearch stack stable release. | Rubygem | | Elasticsearch | |:-------------:|:-:| :-----------: | @@ -14,7 +14,7 @@ The library version numbers follow the Elasticsearch major versions. The `master | 2.x | → | 2.x | | 5.x | → | 5.x | | 6.x | → | 6.x | -| master | → | 7.x | +| main | → | 7.x | ## Installation diff --git a/elasticsearch-rails/README.md b/elasticsearch-rails/README.md index 687d016ae..375f379f5 100644 --- a/elasticsearch-rails/README.md +++ b/elasticsearch-rails/README.md @@ -1,14 +1,14 @@ # Elasticsearch::Rails The `elasticsearch-rails` library is a companion for the -the [`elasticsearch-model`](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-model) +the [`elasticsearch-model`](https://github.com/elastic/elasticsearch-rails/tree/main/elasticsearch-model) library, providing features suitable for Ruby on Rails applications. ## Compatibility This library is compatible with Ruby 1.9.3 and higher. -The library version numbers follow the Elasticsearch major versions, and the `master` branch +The library version numbers follow the Elasticsearch major versions, and the `main` branch is compatible with the Elasticsearch `master` branch, therefore, with the next major version. | Rubygem | | Elasticsearch | @@ -18,7 +18,7 @@ is compatible with the Elasticsearch `master` branch, therefore, with the next m | 5.x | → | 5.x | | 6.x | → | 6.x | | 7.x | → | 7.x | -| master | → | master | +| main | → | master | ## Installation @@ -101,22 +101,22 @@ You should see the duration of the request to Elasticsearch as part of each log You can generate a fully working example Ruby on Rails application, with an `Article` model and a search form, to play with (it generates the application skeleton and leaves you with a _Git_ repository to explore the steps and the code) with the -[`01-basic.rb`](https://github.com/elastic/elasticsearch-rails/blob/master/elasticsearch-rails/lib/rails/templates/01-basic.rb) template: +[`01-basic.rb`](https://github.com/elastic/elasticsearch-rails/blob/main/elasticsearch-rails/lib/rails/templates/01-basic.rb) template: ```bash -rails new searchapp --skip --skip-bundle --template https://raw.github.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/01-basic.rb +rails new searchapp --skip --skip-bundle --template https://raw.github.com/elastic/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/01-basic.rb ``` Run the same command again, in the same folder, with the -[`02-pretty`](https://github.com/elastic/elasticsearch-rails/blob/master/elasticsearch-rails/lib/rails/templates/02-pretty.rb) +[`02-pretty`](https://github.com/elastic/elasticsearch-rails/blob/main/elasticsearch-rails/lib/rails/templates/02-pretty.rb) template to add features such as a custom `Article.search` method, result highlighting and [_Bootstrap_](http://getbootstrap.com) integration: ```bash -rails new searchapp --skip --skip-bundle --template https://raw.github.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/02-pretty.rb +rails new searchapp --skip --skip-bundle --template https://raw.github.com/elastic/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/02-pretty.rb ``` -Run the same command with the [`03-expert.rb`](https://github.com/elastic/elasticsearch-rails/blob/master/elasticsearch-rails/lib/rails/templates/03-expert.rb) +Run the same command with the [`03-expert.rb`](https://github.com/elastic/elasticsearch-rails/blob/main/elasticsearch-rails/lib/rails/templates/03-expert.rb) template to refactor the application into a more complex use case, with couple of hundreds of The New York Times articles as the example content. The template will extract the Elasticsearch integration into a `Searchable` "concern" module, @@ -124,7 +124,7 @@ define complex mapping, custom serialization, implement faceted navigation and s a complex query, and add a _Sidekiq_-based worker for updating the index in the background. ```bash -rails new searchapp --skip --skip-bundle --template https://raw.github.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/03-expert.rb +rails new searchapp --skip --skip-bundle --template https://raw.github.com/elastic/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/03-expert.rb ``` ## License diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index 55951bbaf..9b2b34781 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -31,7 +31,7 @@ Gem::Specification.new do |s| s.license = 'Apache 2' s.metadata = { 'homepage_uri' => 'https://www.elastic.co/guide/en/elasticsearch/client/ruby-api/current/ruby_on_rails.html', - 'changelog_uri' => 'https://github.com/elastic/elasticsearch-rails/blob/master/CHANGELOG.md', + 'changelog_uri' => 'https://github.com/elastic/elasticsearch-rails/blob/main/CHANGELOG.md', 'source_code_uri' => 'https://github.com/elastic/elasticsearch-rails/', 'bug_tracker_uri' => 'https://github.com/elastic/elasticsearch-rails/issues' } diff --git a/elasticsearch-rails/lib/rails/templates/01-basic.rb b/elasticsearch-rails/lib/rails/templates/01-basic.rb index c517a0f3f..5e07e14a4 100644 --- a/elasticsearch-rails/lib/rails/templates/01-basic.rb +++ b/elasticsearch-rails/lib/rails/templates/01-basic.rb @@ -32,7 +32,7 @@ # Usage: # ------ # -# $ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/01-basic.rb +# $ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/01-basic.rb # # ===================================================================================================== @@ -112,7 +112,7 @@ search engine with the {Ruby On Rails}[http://rubyonrails.org] web framework. It has been generated by application templates available at -https://github.com/elasticsearch/elasticsearch-rails/tree/master/elasticsearch-rails/lib/rails/templates. +https://github.com/elasticsearch/elasticsearch-rails/tree/main/elasticsearch-rails/lib/rails/templates. ## [1] Basic diff --git a/elasticsearch-rails/lib/rails/templates/02-pretty.rb b/elasticsearch-rails/lib/rails/templates/02-pretty.rb index 3be42b481..fb88e0c47 100644 --- a/elasticsearch-rails/lib/rails/templates/02-pretty.rb +++ b/elasticsearch-rails/lib/rails/templates/02-pretty.rb @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. -# $ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/02-pretty.rb +# $ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/02-pretty.rb unless File.read('README.md').include? '## [1] Basic' say_status "ERROR", "You have to run the 01-basic.rb template first.", :red diff --git a/elasticsearch-rails/lib/rails/templates/03-expert.rb b/elasticsearch-rails/lib/rails/templates/03-expert.rb index aa7635531..5715eb0d6 100644 --- a/elasticsearch-rails/lib/rails/templates/03-expert.rb +++ b/elasticsearch-rails/lib/rails/templates/03-expert.rb @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. -# $ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/03-expert.rb +# $ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/03-expert.rb unless File.read('README.md').include? '## [2] Pretty' say_status "ERROR", "You have to run the 01-basic.rb and 02-pretty.rb templates first.", :red @@ -181,7 +181,7 @@ class Article < ActiveRecord::Base gsub_file "test/models/article_test.rb", %r{assert_equal 'foo', definition\[:query\]\[:multi_match\]\[:query\]}, "assert_equal 'foo', definition.to_hash[:query][:bool][:should][0][:multi_match][:query]" # copy_file File.expand_path('../searchable.rb', __FILE__), 'app/models/concerns/searchable.rb' -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/searchable.rb', 'app/models/concerns/searchable.rb' +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/searchable.rb', 'app/models/concerns/searchable.rb' insert_into_file "app/models/article.rb", after: "ActiveRecord::Base" do <<-CODE @@ -209,7 +209,7 @@ class Article < ActiveRecord::Base run "bundle install" # copy_file File.expand_path('../indexer.rb', __FILE__), 'app/workers/indexer.rb' -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/indexer.rb', 'app/workers/indexer.rb' +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/indexer.rb', 'app/workers/indexer.rb' insert_into_file "test/test_helper.rb", "require 'sidekiq/testing'\n\n", @@ -244,16 +244,16 @@ def index end # copy_file File.expand_path('../search_controller_test.rb', __FILE__), 'test/controllers/search_controller_test.rb' -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/search_controller_test.rb', 'test/controllers/search_controller_test.rb' +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/search_controller_test.rb', 'test/controllers/search_controller_test.rb' route "get '/search', to: 'search#index', as: 'search'" gsub_file 'config/routes.rb', %r{root to: 'articles#index'$}, "root to: 'search#index'" # copy_file File.expand_path('../index.html.erb', __FILE__), 'app/views/search/index.html.erb' -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/index.html.erb', 'app/views/search/index.html.erb' +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/index.html.erb', 'app/views/search/index.html.erb' # copy_file File.expand_path('../search.css', __FILE__), 'app/assets/stylesheets/search.css' -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/search.css', 'app/assets/stylesheets/search.css' +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/search.css', 'app/assets/stylesheets/search.css' git add: "app/controllers/ test/controllers/ config/routes.rb" git add: "app/views/search/ app/assets/stylesheets/search.css" @@ -315,11 +315,11 @@ def index puts '-'*80, ''; sleep 0.25 # copy_file File.expand_path('../articles.yml.gz', __FILE__), 'db/articles.yml.gz' -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/articles.yml.gz', 'db/articles.yml.gz' +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/articles.yml.gz', 'db/articles.yml.gz' remove_file 'db/seeds.rb' # copy_file File.expand_path('../seeds.rb', __FILE__), 'db/seeds.rb' -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/seeds.rb', 'db/seeds.rb' +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/seeds.rb', 'db/seeds.rb' rake "db:reset" rake "environment elasticsearch:import:model CLASS='Article' BATCH=100 FORCE=y" diff --git a/elasticsearch-rails/lib/rails/templates/04-dsl.rb b/elasticsearch-rails/lib/rails/templates/04-dsl.rb index b6eb6f893..d4bd598a0 100644 --- a/elasticsearch-rails/lib/rails/templates/04-dsl.rb +++ b/elasticsearch-rails/lib/rails/templates/04-dsl.rb @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. -# $ rails new searchapp --skip --skip-bundle --template https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/04-dsl.rb +# $ rails new searchapp --skip --skip-bundle --template https://raw.githubusercontent.com/elastic/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/04-dsl.rb unless File.read('README.md').include? '## [3] Expert' say_status "ERROR", "You have to run the 01-basic.rb, 02-pretty.rb and 03-expert.rb templates first.", :red @@ -56,10 +56,10 @@ # ----- Change the search definition implementation and associated views and tests ---------------- # copy_file File.expand_path('../searchable.dsl.rb', __FILE__), 'app/models/concerns/searchable.rb', force: true -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb', 'app/models/concerns/searchable.rb', force: true +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/searchable.dsl.rb', 'app/models/concerns/searchable.rb', force: true # copy_file File.expand_path('../index.html.dsl.erb', __FILE__), 'app/views/search/index.html.erb', force: true -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb', 'app/views/search/index.html.erb', force: true +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/index.html.dsl.erb', 'app/views/search/index.html.erb', force: true gsub_file "test/controllers/search_controller_test.rb", %r{test "should return facets" do.*?end}m, <<-CODE test "should return aggregations" do diff --git a/elasticsearch-rails/lib/rails/templates/05-settings-files.rb b/elasticsearch-rails/lib/rails/templates/05-settings-files.rb index cf84213ce..989895445 100644 --- a/elasticsearch-rails/lib/rails/templates/05-settings-files.rb +++ b/elasticsearch-rails/lib/rails/templates/05-settings-files.rb @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. -# $ rails new searchapp --skip --skip-bundle --template https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/05-settings-files.rb +# $ rails new searchapp --skip --skip-bundle --template https://raw.githubusercontent.com/elastic/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/05-settings-files.rb # (See: 01-basic.rb, 02-pretty.rb, 03-expert.rb, 04-dsl.rb) @@ -43,7 +43,7 @@ # ----- Copy the articles_settings.json file ------------------------------------------------------- # copy_file File.expand_path('../articles_settings.json', __FILE__), 'config/elasticsearch/articles_settings.json' -get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/articles_settings.json', +get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/main/elasticsearch-rails/lib/rails/templates/articles_settings.json', 'config/elasticsearch/articles_settings.json', force: true git add: "config/elasticsearch/articles_settings.json" From b2f95ba961b6dc4a3399b6b996a7ba0ca39bf29a Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Wed, 23 Feb 2022 08:36:48 +0000 Subject: [PATCH 523/582] [CI] Updates JRuby version in GitHub Actions --- .github/workflows/jruby.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/jruby.yml b/.github/workflows/jruby.yml index 3a397ebc0..7a3ec1be7 100644 --- a/.github/workflows/jruby.yml +++ b/.github/workflows/jruby.yml @@ -30,7 +30,7 @@ jobs: stack-version: 7.x-SNAPSHOT - uses: ruby/setup-ruby@v1 with: - ruby-version: jruby-9.2 + ruby-version: jruby-9.3 - name: Bundle run: | sudo apt-get install libsqlite3-dev From a0cef1b3e90d22b4648e6869836cc3536dad8ff1 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Wed, 23 Feb 2022 08:20:45 +0000 Subject: [PATCH 524/582] [DOCS] Updates CHANGELOG for 7.2.1 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02e595151..c3405dea7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 7.2.1 + +* The default git branch `master` has been renamed to `main` +* Adds compatibility with Ruby 3 [Pull Request](https://github.com/elastic/elasticsearch-rails/pull/992) + ## 7.2.0 * Updates specs and dependency to use with `elasticsearch` v7.14.0. From 97ac1ff417d13a240552fc202a53c263019d478a Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Wed, 23 Feb 2022 08:34:45 +0000 Subject: [PATCH 525/582] [DOCS] Updates README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8a6a745fe..615c7a01e 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,9 @@ gem 'elasticsearch-rails', github: 'elastic/elasticsearch-rails', branch: '5.x' The libraries are compatible with Ruby 2.4 and higher. -The version numbers follow the Elasticsearch major versions. The `main` branch is compatible with the latest Elasticsearch stack stable release. +We follow Ruby’s own maintenance policy and officially support all currently maintained versions per [Ruby Maintenance Branches](https://www.ruby-lang.org/en/downloads/branches/). + +The version numbers follow the Elasticsearch major versions. Currently the `main` branch is compatible with version `7.x` of the Elasticsearch stack. **We haven't tested and updated the code for Elasticsearch `8.0` yet**. | Rubygem | | Elasticsearch | |:-------------:|:-:| :-----------: | @@ -50,8 +52,6 @@ The version numbers follow the Elasticsearch major versions. The `main` branch i | 6.x | → | 6.x | | main | → | 7.x | -Use a release that matches the major version of Elasticsearch in your stack. Each client version is backwards compatible with all minor versions of the same major version. - Check out [Elastic product end of life dates](https://www.elastic.co/support/eol) to learn which releases are still actively supported and tested. ## Usage From 03fa16b2774988c59978abb13aae962c9580ec57 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Wed, 23 Feb 2022 08:35:39 +0000 Subject: [PATCH 526/582] Bumps version to 7.2.1 --- elasticsearch-model/lib/elasticsearch/model/version.rb | 2 +- elasticsearch-persistence/elasticsearch-persistence.gemspec | 2 +- .../lib/elasticsearch/persistence/version.rb | 2 +- elasticsearch-rails/lib/elasticsearch/rails/version.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/version.rb b/elasticsearch-model/lib/elasticsearch/model/version.rb index fe79283ab..cdd631896 100644 --- a/elasticsearch-model/lib/elasticsearch/model/version.rb +++ b/elasticsearch-model/lib/elasticsearch/model/version.rb @@ -17,6 +17,6 @@ module Elasticsearch module Model - VERSION = "7.2.0" + VERSION = "7.2.1" end end diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index 6e7970e8e..f1a736127 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -41,7 +41,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 1.9.3" s.add_dependency "elasticsearch", '~> 7' - s.add_dependency "elasticsearch-model", '7.2.0' + s.add_dependency "elasticsearch-model", '7.2.1' s.add_dependency "activesupport", '> 4' s.add_dependency "activemodel", '> 4' s.add_dependency "hashie" diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb index 4c6ad62d9..18fe2466d 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb @@ -17,6 +17,6 @@ module Elasticsearch module Persistence - VERSION = '7.2.0' + VERSION = '7.2.1' end end diff --git a/elasticsearch-rails/lib/elasticsearch/rails/version.rb b/elasticsearch-rails/lib/elasticsearch/rails/version.rb index 9be1f1045..07d084dae 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/version.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/version.rb @@ -17,6 +17,6 @@ module Elasticsearch module Rails - VERSION = "7.2.0" + VERSION = "7.2.1" end end From b66fe71cc4b9b10069bf6d39dbbfcc0b117a08a5 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Tue, 19 Apr 2022 08:38:48 +0100 Subject: [PATCH 527/582] [DOCS] Updates README and links to elasticsearch-dsl Closes #1026 --- README.md | 2 +- elasticsearch-model/README.md | 2 +- elasticsearch-rails/lib/rails/templates/04-dsl.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 615c7a01e..df4d71db8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Elasticsearch +# Elasticsearch Rails [![Ruby 2.7](https://github.com/elastic/elasticsearch-rails/workflows/Ruby%202.7/badge.svg)](https://github.com/elastic/elasticsearch-rails/actions) [![Ruby 2.6](https://github.com/elastic/elasticsearch-rails/workflows/Ruby%202.6/badge.svg)](https://github.com/elastic/elasticsearch-rails/actions) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index a5345b4b9..2a0135960 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -343,7 +343,7 @@ response.results.first.title # => "Quick brown fox" ``` -Also, you can use the [**`elasticsearch-dsl`**](https://github.com/elastic/elasticsearch-ruby/tree/main/elasticsearch-dsl) library, which provides a specialized Ruby API for the Elasticsearch Query DSL: +Also, you can use the [**`elasticsearch-dsl`**](https://github.com/elastic/elasticsearch-dsl-ruby) library, which provides a specialized Ruby API for the Elasticsearch Query DSL: ```ruby require 'elasticsearch/dsl' diff --git a/elasticsearch-rails/lib/rails/templates/04-dsl.rb b/elasticsearch-rails/lib/rails/templates/04-dsl.rb index d4bd598a0..6e0d265fd 100644 --- a/elasticsearch-rails/lib/rails/templates/04-dsl.rb +++ b/elasticsearch-rails/lib/rails/templates/04-dsl.rb @@ -27,7 +27,7 @@ ## [4] DSL The `dsl` template refactors the search definition in SearchController#index -to use the [`elasticsearch-dsl`](https://github.com/elastic/elasticsearch-ruby/tree/dsl/elasticsearch-dsl) +to use the [`elasticsearch-dsl`](https://github.com/elastic/elasticsearch-dsl-ruby) Rubygem for better expresivity and readability of the code. README @@ -44,7 +44,7 @@ say_status "Rubygems", "Adding Rubygems into Gemfile...\n", :yellow puts '-'*80, ''; sleep 0.25 -gem "elasticsearch-dsl", git: "git://github.com/elastic/elasticsearch-ruby.git" +gem "elasticsearch-dsl", git: "git://github.com/elastic/elasticsearch-dsl-ruby.git" git add: "Gemfile*" git commit: "-m 'Added the `elasticsearch-dsl` gem'" From 2b30cbade674ea89ed08d2a1124bd82336f27fd0 Mon Sep 17 00:00:00 2001 From: Doug Tabuchi <Doug@DougTabuchi.com> Date: Tue, 17 May 2022 12:43:48 -0400 Subject: [PATCH 528/582] Template installs deps using main branch --- elasticsearch-rails/lib/rails/templates/01-basic.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-rails/lib/rails/templates/01-basic.rb b/elasticsearch-rails/lib/rails/templates/01-basic.rb index 5e07e14a4..faf67489a 100644 --- a/elasticsearch-rails/lib/rails/templates/01-basic.rb +++ b/elasticsearch-rails/lib/rails/templates/01-basic.rb @@ -157,8 +157,8 @@ puts '-'*80, ''; sleep 0.75 gem 'elasticsearch' -gem 'elasticsearch-model', git: 'https://github.com/elasticsearch/elasticsearch-rails.git' -gem 'elasticsearch-rails', git: 'https://github.com/elasticsearch/elasticsearch-rails.git' +gem 'elasticsearch-model', git: 'https://github.com/elasticsearch/elasticsearch-rails.git', branch: 'main' +gem 'elasticsearch-rails', git: 'https://github.com/elasticsearch/elasticsearch-rails.git', branch: 'main' git add: "Gemfile*" From dc0efc4d020f41712c190b94b755e600319b0450 Mon Sep 17 00:00:00 2001 From: Hannes Moser <box@hannesmoser.at> Date: Sun, 16 Oct 2022 16:06:55 +0200 Subject: [PATCH 529/582] Fix missing environment dependency for import tasks --- elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb b/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb index 91f4441ea..b43d67163 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb @@ -57,7 +57,7 @@ $ rake environment elasticsearch:import:model CLASS='Article' SCOPE='published' DESC desc import_model_desc - task :model do + task model: :environment do if ENV['CLASS'].to_s == '' puts '='*90, 'USAGE', '='*90, import_model_desc, "" exit(1) @@ -97,7 +97,7 @@ $ rake environment elasticsearch:import:all DIR=app/models DESC - task :all do + task all: :environment do dir = ENV['DIR'].to_s != '' ? ENV['DIR'] : Rails.root.join("app/models") puts "[IMPORT] Loading models from: #{dir}" From 37b7897e5090db18977ccfeac4db20ccce579b35 Mon Sep 17 00:00:00 2001 From: Yoshinori Tokuno <yoshi.jpn.10.11@gmail.com> Date: Sat, 29 Oct 2022 22:34:35 +0900 Subject: [PATCH 530/582] renamed docker image name in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index df4d71db8..26ee3860e 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ You can generate a simple Ruby on Rails application with a single command --env "cluster.name=elasticsearch-rails" \ --env "cluster.routing.allocation.disk.threshold_enabled=false" \ --rm \ - docker.elastic.co/elasticsearch/elasticsearch-oss:7.6.0 + docker.elastic.co/elasticsearch/elasticsearch:7.6.0 ``` Once Elasticsearch is running, you can generate the simple app with this command: From 8e0526cc9bf9c3c41a4e989222a19a1f5318d3b6 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Fri, 4 Nov 2022 10:37:01 +0000 Subject: [PATCH 531/582] Updates README files --- elasticsearch-model/README.md | 2 +- elasticsearch-persistence/README.md | 3 ++- elasticsearch-rails/README.md | 5 ++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 2a0135960..c7d90150a 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -8,7 +8,7 @@ It aims to simplify integration of Ruby classes ("models"), commonly found e.g. This library is compatible with Ruby 2.4 and higher. -The library version numbers follow the Elasticsearch major versions. The `main` branch is compatible with the latest Elasticsearch stack stable release. +The version numbers follow the Elasticsearch major versions. Currently the `main` branch is compatible with version `7.x` of the Elasticsearch stack. **We haven't tested and updated the code for Elasticsearch `8.0` yet**. | Rubygem | | Elasticsearch | |:-------------:|:-:| :-----------: | diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index 6b905f593..e8cb0aa8a 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -6,7 +6,7 @@ Persistence layer for Ruby domain objects in Elasticsearch, using the Repository This library is compatible with Ruby 2.4 and higher. -The library version numbers follow the Elasticsearch major versions. The `main` branch is compatible with the latest Elasticsearch stack stable release. +The version numbers follow the Elasticsearch major versions. Currently the `main` branch is compatible with version `7.x` of the Elasticsearch stack. **We haven't tested and updated the code for Elasticsearch `8.0` yet**. | Rubygem | | Elasticsearch | |:-------------:|:-:| :-----------: | @@ -14,6 +14,7 @@ The library version numbers follow the Elasticsearch major versions. The `main` | 2.x | → | 2.x | | 5.x | → | 5.x | | 6.x | → | 6.x | +| 7.x | → | 7.x | | main | → | 7.x | ## Installation diff --git a/elasticsearch-rails/README.md b/elasticsearch-rails/README.md index 375f379f5..12e7d7d59 100644 --- a/elasticsearch-rails/README.md +++ b/elasticsearch-rails/README.md @@ -8,8 +8,7 @@ library, providing features suitable for Ruby on Rails applications. This library is compatible with Ruby 1.9.3 and higher. -The library version numbers follow the Elasticsearch major versions, and the `main` branch -is compatible with the Elasticsearch `master` branch, therefore, with the next major version. +The version numbers follow the Elasticsearch major versions. Currently the `main` branch is compatible with version `7.x` of the Elasticsearch stack. **We haven't tested and updated the code for Elasticsearch `8.0` yet**. | Rubygem | | Elasticsearch | |:-------------:|:-:| :-----------: | @@ -18,7 +17,7 @@ is compatible with the Elasticsearch `master` branch, therefore, with the next m | 5.x | → | 5.x | | 6.x | → | 6.x | | 7.x | → | 7.x | -| main | → | master | +| main | → | 7.x | ## Installation From b887b63ad9117bbb84ca9bce4929b37a8b57b4e8 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 16 Jan 2023 09:19:02 +0000 Subject: [PATCH 532/582] Minor updates --- Gemfile | 12 ++-- elasticsearch-model/Rakefile | 6 +- .../elasticsearch-model.gemspec | 2 +- elasticsearch-model/gemfiles/6.0.gemfile | 5 +- .../elasticsearch/model/adapters/mongoid.rb | 20 +++--- elasticsearch-model/spec/spec_helper.rb | 6 +- elasticsearch-persistence/Rakefile | 15 +++-- .../elasticsearch-persistence.gemspec | 67 +++++++++---------- elasticsearch-rails/Rakefile | 2 + .../elasticsearch-rails.gemspec | 2 +- 10 files changed, 68 insertions(+), 69 deletions(-) diff --git a/Gemfile b/Gemfile index 255f70850..0edd2dd95 100644 --- a/Gemfile +++ b/Gemfile @@ -17,13 +17,13 @@ source 'https://rubygems.org' -gem "rake", "~> 12" -gem "elasticsearch" -gem "pry" -gem "ansi" -gem "cane" +gem 'ansi' +gem 'cane' +gem 'elasticsearch', '~> 7' +gem 'pry' +gem 'rake', '~> 12' group :development do - gem 'yard' gem 'rspec' + gem 'yard' end diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index ab1d6cab7..6e03532b0 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -48,12 +48,12 @@ require 'rake/testtask' namespace :test do desc 'Run all tests. Optionally define env variable RAILS_VERSIONS. E.g. RAILS_VERSIONS=3.0,5.0' task :all, [:rails_versions] do |task, args| - gemfiles = ENV['RAILS_VERSIONS'] ? ENV['RAILS_VERSIONS'].split(',').map {|v| "#{v}.gemfile"} : GEMFILES + gemfiles = ENV['RAILS_VERSIONS'] ? ENV['RAILS_VERSIONS'].split(',').map { |v| "#{v}.gemfile" } : GEMFILES puts '-' * 80 gemfiles.each do |gemfile| puts "GEMFILE: #{gemfile}" - sh "BUNDLE_GEMFILE='#{File.expand_path("../gemfiles/#{gemfile}", __FILE__)}' " + - " bundle exec rspec" + sh "BUNDLE_GEMFILE='#{File.expand_path("../gemfiles/#{gemfile}", __FILE__)}' " \ + ' bundle exec rspec' puts '-' * 80 end end diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index 083938d51..b8f16c902 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |s| s.name = 'elasticsearch-model' s.version = Elasticsearch::Model::VERSION s.authors = ['Karel Minarik'] - s.email = ['karel.minarik@elasticsearch.org'] + s.email = ['support@elastic.co'] s.description = 'ActiveModel/Record integrations for Elasticsearch.' s.summary = 'ActiveModel/Record integrations for Elasticsearch.' s.homepage = 'https://github.com/elasticsearch/elasticsearch-rails/' diff --git a/elasticsearch-model/gemfiles/6.0.gemfile b/elasticsearch-model/gemfiles/6.0.gemfile index dd859f68b..2461d66f0 100644 --- a/elasticsearch-model/gemfiles/6.0.gemfile +++ b/elasticsearch-model/gemfiles/6.0.gemfile @@ -20,7 +20,6 @@ # $ BUNDLE_GEMFILE=./gemfiles/6.0.gemfile bundle install # $ BUNDLE_GEMFILE=./gemfiles/6.0.gemfile bundle exec rake test:integration - source 'https://rubygems.org' gemspec path: '../' @@ -28,9 +27,9 @@ gemspec path: '../' gem 'activemodel', '6.0.0' gem 'activerecord', '6.0.0' gem 'sqlite3' unless defined?(JRUBY_VERSION) -#gem 'mongoid', '~> 6' +# gem 'mongoid', '~> 6' group :development, :testing do - gem 'rspec' gem 'pry-nav' + gem 'rspec' end diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb index 8626d743c..850ea50ea 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/mongoid.rb @@ -18,18 +18,21 @@ module Elasticsearch module Model module Adapter - # An adapter for Mongoid-based models # # @see http://mongoid.org # module Mongoid - - Adapter.register self, - lambda { |klass| !!defined?(::Mongoid::Document) && klass.respond_to?(:ancestors) && klass.ancestors.include?(::Mongoid::Document) } + Adapter.register( + self, + lambda do |klass| + !!defined?(::Mongoid::Document) && + klass.respond_to?(:ancestors) && + klass.ancestors.include?(::Mongoid::Document) + end + ) module Records - # Return a `Mongoid::Criteria` instance # def records @@ -59,7 +62,6 @@ def records end module Callbacks - # Handle index updates (creating, updating or deleting documents) # when the model changes, by hooking into the lifecycle # @@ -73,7 +75,6 @@ def self.included(base) end module Importing - # Fetch batches of records from the database # # @see https://github.com/mongoid/mongoid/issues/1334 @@ -88,19 +89,16 @@ def __find_in_batches(options={}, &block) scope = all scope = scope.send(named_scope) if named_scope scope = query.is_a?(Proc) ? scope.class_exec(&query) : scope.where(query) if query - scope.no_timeout.each_slice(batch_size) do |items| yield (preprocess ? self.__send__(preprocess, items) : items) end end def __transform - lambda {|a| { index: { _id: a.id.to_s, data: a.as_indexed_json } }} + lambda { |a| { index: { _id: a.id.to_s, data: a.as_indexed_json } } } end end - end - end end end diff --git a/elasticsearch-model/spec/spec_helper.rb b/elasticsearch-model/spec/spec_helper.rb index 3bb7f8742..413b5f03e 100644 --- a/elasticsearch-model/spec/spec_helper.rb +++ b/elasticsearch-model/spec/spec_helper.rb @@ -43,8 +43,10 @@ require 'ansi' tracer = ::Logger.new(STDERR) tracer.formatter = lambda { |s, d, p, m| "#{m.gsub(/^.*$/) { |n| ' ' + n }.ansi(:faint)}\n" } - Elasticsearch::Model.client = Elasticsearch::Client.new host: ELASTICSEARCH_URL, - tracer: (ENV['QUIET'] ? nil : tracer) + Elasticsearch::Model.client = Elasticsearch::Client.new( + host: ELASTICSEARCH_URL, + tracer: (ENV['QUIET'] ? nil : tracer) + ) puts "Elasticsearch Version: #{Elasticsearch::Model.client.info['version']}" unless ActiveRecord::Base.connected? diff --git a/elasticsearch-persistence/Rakefile b/elasticsearch-persistence/Rakefile index 6d53d4c22..07ba8c2a2 100644 --- a/elasticsearch-persistence/Rakefile +++ b/elasticsearch-persistence/Rakefile @@ -15,11 +15,11 @@ # specific language governing permissions and limitations # under the License. -require "bundler/gem_tasks" +require 'bundler/gem_tasks' -desc "Run unit tests" -task :default => 'test:unit' -task :test => 'test:unit' +desc 'Run unit tests' +task default: 'test:unit' +task test: 'test:unit' # ----- Test tasks ------------------------------------------------------------ @@ -27,14 +27,15 @@ require 'rake/testtask' require 'rspec/core/rake_task' namespace :test do - RSpec::Core::RakeTask.new(:spec) Rake::TestTask.new(:all) do |test| test.verbose = false test.warning = false - test.deps = [ :spec ] + test.deps = [:spec] end + + task unit: :spec end namespace :bundle do @@ -65,6 +66,6 @@ if defined?(RUBY_VERSION) && RUBY_VERSION > '1.9' cane.style_measure = 120 end rescue LoadError - warn "cane not available, quality task not provided." + warn 'cane not available, quality task not provided.' end end diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index f1a736127..a37232c93 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -16,52 +16,49 @@ # under the License. # coding: utf-8 -lib = File.expand_path('../lib', __FILE__) + +lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'elasticsearch/persistence/version' Gem::Specification.new do |s| - s.name = "elasticsearch-persistence" + s.name = 'elasticsearch-persistence' s.version = Elasticsearch::Persistence::VERSION - s.authors = ["Karel Minarik"] - s.email = ["karel.minarik@elasticsearch.org"] - s.description = "Persistence layer for Ruby models and Elasticsearch." - s.summary = "Persistence layer for Ruby models and Elasticsearch." - s.homepage = "https://github.com/elasticsearch/elasticsearch-rails/" - s.license = "Apache 2" + s.authors = ['Karel Minarik'] + s.email = ['support@elastic.co'] + s.description = 'Persistence layer for Ruby models and Elasticsearch.' + s.summary = 'Persistence layer for Ruby models and Elasticsearch.' + s.homepage = 'https://github.com/elasticsearch/elasticsearch-rails/' + s.license = 'Apache 2' s.files = `git ls-files -z`.split("\x0") s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) } s.test_files = s.files.grep(%r{^(test|spec|features)/}) - s.require_paths = ["lib"] - - s.extra_rdoc_files = [ "README.md", "LICENSE.txt" ] - s.rdoc_options = [ "--charset=UTF-8" ] - - s.required_ruby_version = ">= 1.9.3" - - s.add_dependency "elasticsearch", '~> 7' - s.add_dependency "elasticsearch-model", '7.2.1' - s.add_dependency "activesupport", '> 4' - s.add_dependency "activemodel", '> 4' - s.add_dependency "hashie" - - s.add_development_dependency "bundler" - s.add_development_dependency "rake", "~> 12" + s.require_paths = ['lib'] - s.add_development_dependency "oj" unless defined?(JRUBY_VERSION) + s.extra_rdoc_files = ['README.md', 'LICENSE.txt'] + s.rdoc_options = ['--charset=UTF-8'] - s.add_development_dependency "rails", '> 4' + s.required_ruby_version = '>= 1.9.3' - s.add_development_dependency "minitest" - s.add_development_dependency "test-unit" - s.add_development_dependency "shoulda-context" - s.add_development_dependency "mocha" - s.add_development_dependency "turn" - s.add_development_dependency "yard" - s.add_development_dependency "ruby-prof" unless defined?(JRUBY_VERSION) - s.add_development_dependency "pry" + s.add_dependency 'activemodel', '> 4' + s.add_dependency 'activesupport', '> 4' + s.add_dependency 'elasticsearch', '~> 7' + s.add_dependency 'elasticsearch-model', '7.2.1' + s.add_dependency 'hashie' - s.add_development_dependency "simplecov" - s.add_development_dependency "cane" + s.add_development_dependency 'bundler' + s.add_development_dependency 'cane' + s.add_development_dependency 'minitest' + s.add_development_dependency 'mocha' + s.add_development_dependency 'oj' unless defined?(JRUBY_VERSION) + s.add_development_dependency 'pry' + s.add_development_dependency 'rails', '> 4' + s.add_development_dependency 'rake', '~> 12' + s.add_development_dependency 'ruby-prof' unless defined?(JRUBY_VERSION) + s.add_development_dependency 'shoulda-context' + s.add_development_dependency 'simplecov' + s.add_development_dependency 'test-unit' + s.add_development_dependency 'turn' + s.add_development_dependency 'yard' end diff --git a/elasticsearch-rails/Rakefile b/elasticsearch-rails/Rakefile index 20c7d1616..bbd245469 100644 --- a/elasticsearch-rails/Rakefile +++ b/elasticsearch-rails/Rakefile @@ -34,6 +34,8 @@ namespace :test do test.warning = false test.deps = [:spec] unless defined?(JRUBY_VERSION) end + + task unit: :spec end namespace :bundle do diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index 9b2b34781..0ba3f2b22 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -24,7 +24,7 @@ Gem::Specification.new do |s| s.name = 'elasticsearch-rails' s.version = Elasticsearch::Rails::VERSION s.authors = ['Karel Minarik'] - s.email = ['karel.minarik@elasticsearch.org'] + s.email = ['support@elastic.co'] s.description = 'Ruby on Rails integrations for Elasticsearch.' s.summary = 'Ruby on Rails integrations for Elasticsearch.' s.homepage = 'https://github.com/elasticsearch/elasticsearch-rails/' From 5605d1fe69d3727bf1d0c703d5510583e8acc96b Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 16 Jan 2023 10:22:52 +0000 Subject: [PATCH 533/582] [CI] Removing 2.4 and 2.5 tests in CI --- .github/workflows/2.4.yml | 46 -------------------------------------- .github/workflows/2.5.yml | 47 --------------------------------------- 2 files changed, 93 deletions(-) delete mode 100644 .github/workflows/2.4.yml delete mode 100644 .github/workflows/2.5.yml diff --git a/.github/workflows/2.4.yml b/.github/workflows/2.4.yml deleted file mode 100644 index 890c78613..000000000 --- a/.github/workflows/2.4.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Ruby 2.4 -on: - push: - branches: - - main - pull_request: - branches: - - main - workflow_dispatch: - branches: - - '*' -jobs: - tests: - env: - TEST_ES_SERVER: http://localhost:9200 - RAILS_VERSIONS: '5.0' - strategy: - fail-fast: false - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Increase system limits - run: | - sudo swapoff -a - sudo sysctl -w vm.swappiness=1 - sudo sysctl -w fs.file-max=262144 - sudo sysctl -w vm.max_map_count=262144 - - uses: elastic/elastic-github-actions/elasticsearch@master - with: - stack-version: 7.x-SNAPSHOT - - uses: ruby/setup-ruby@v1 - with: - ruby-version: 2.4 - - name: Bundle - run: | - sudo apt-get install libsqlite3-dev - gem install bundler - bundle install - bundle exec rake bundle:clean - bundle exec rake bundle:install - - name: Test elasticsearch-rails - run: cd elasticsearch-rails && bundle exec rake test:all - - name: Test elasticsearch-persistence - run: cd elasticsearch-persistence && bundle exec rake test:all - - name: Test elasticsearch-model - run: cd elasticsearch-model && bundle exec rake test:all diff --git a/.github/workflows/2.5.yml b/.github/workflows/2.5.yml deleted file mode 100644 index 68f8294fe..000000000 --- a/.github/workflows/2.5.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Ruby 2.5 -on: - push: - branches: - - main - pull_request: - branches: - - main - workflow_dispatch: - branches: - - '*' -jobs: - tests: - env: - TEST_ES_SERVER: http://localhost:9200 - RAILS_VERSIONS: '5.0,6.0' - strategy: - fail-fast: false - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Increase system limits - run: | - sudo swapoff -a - sudo sysctl -w vm.swappiness=1 - sudo sysctl -w fs.file-max=262144 - sudo sysctl -w vm.max_map_count=262144 - - uses: elastic/elastic-github-actions/elasticsearch@master - with: - stack-version: 7.x-SNAPSHOT - - uses: ruby/setup-ruby@v1 - with: - ruby-version: 2.5 - - name: Bundle - run: | - sudo apt-get install libsqlite3-dev - gem install bundler - bundle install - bundle exec rake bundle:clean - bundle exec rake bundle:install - - name: Test elasticsearch-rails - run: cd elasticsearch-rails && bundle exec rake test:all - - name: Test elasticsearch-persistence - run: cd elasticsearch-persistence && bundle exec rake test:all - - name: Test elasticsearch-model - run: cd elasticsearch-model && bundle exec rake test:all - From f0ca114689a73522d004805da21da1a31fdf72f0 Mon Sep 17 00:00:00 2001 From: done <23427957+sensuikan1973@users.noreply.github.com> Date: Fri, 2 Jun 2023 14:48:20 +0900 Subject: [PATCH 534/582] Update README.md --- elasticsearch-model/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index c7d90150a..0e120c349 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -16,6 +16,7 @@ The version numbers follow the Elasticsearch major versions. Currently the `main | 2.x | → | 2.x | | 5.x | → | 5.x | | 6.x | → | 6.x | +| 7.x | → | 7.x | | main | → | 7.x | ## Installation From 55096833b7771a3ce4443931c8da0afcc6bbade2 Mon Sep 17 00:00:00 2001 From: Nigel Small <nigel.small@elastic.co> Date: Mon, 21 Aug 2023 14:16:56 +0100 Subject: [PATCH 535/582] Updated project metadata to use group email address --- elasticsearch-persistence/elasticsearch-persistence.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index a37232c93..893103d4b 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -24,8 +24,8 @@ require 'elasticsearch/persistence/version' Gem::Specification.new do |s| s.name = 'elasticsearch-persistence' s.version = Elasticsearch::Persistence::VERSION - s.authors = ['Karel Minarik'] - s.email = ['support@elastic.co'] + s.authors = ['Elastic Client Library Maintainers'] + s.email = ['client-libs@elastic.co'] s.description = 'Persistence layer for Ruby models and Elasticsearch.' s.summary = 'Persistence layer for Ruby models and Elasticsearch.' s.homepage = 'https://github.com/elasticsearch/elasticsearch-rails/' From 8fe0a555083d16d9d64dea7b0ae8a8695c1d4211 Mon Sep 17 00:00:00 2001 From: Nigel Small <nigel.small@elastic.co> Date: Mon, 21 Aug 2023 14:19:10 +0100 Subject: [PATCH 536/582] Added other files --- elasticsearch-model/elasticsearch-model.gemspec | 4 ++-- elasticsearch-rails/elasticsearch-rails.gemspec | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index b8f16c902..0dc71775c 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -24,8 +24,8 @@ require 'elasticsearch/model/version' Gem::Specification.new do |s| s.name = 'elasticsearch-model' s.version = Elasticsearch::Model::VERSION - s.authors = ['Karel Minarik'] - s.email = ['support@elastic.co'] + s.authors = ['Elastic Client Library Maintainers'] + s.email = ['client-libs@elastic.co'] s.description = 'ActiveModel/Record integrations for Elasticsearch.' s.summary = 'ActiveModel/Record integrations for Elasticsearch.' s.homepage = 'https://github.com/elasticsearch/elasticsearch-rails/' diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index 0ba3f2b22..ac9375233 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -23,8 +23,8 @@ require 'elasticsearch/rails/version' Gem::Specification.new do |s| s.name = 'elasticsearch-rails' s.version = Elasticsearch::Rails::VERSION - s.authors = ['Karel Minarik'] - s.email = ['support@elastic.co'] + s.authors = ['Elastic Client Library Maintainers'] + s.email = ['client-libs@elastic.co'] s.description = 'Ruby on Rails integrations for Elasticsearch.' s.summary = 'Ruby on Rails integrations for Elasticsearch.' s.homepage = 'https://github.com/elasticsearch/elasticsearch-rails/' From d9c494e8a930639918135688ad98ead769acc188 Mon Sep 17 00:00:00 2001 From: Kazuhiro NISHIYAMA <znz@users.noreply.github.com> Date: Tue, 28 Nov 2023 15:59:16 +0900 Subject: [PATCH 537/582] `ruby2_keywords` is not an instance method --- elasticsearch-model/lib/elasticsearch/model/proxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/proxy.rb b/elasticsearch-model/lib/elasticsearch/model/proxy.rb index 6fdf66322..69a6059d2 100644 --- a/elasticsearch-model/lib/elasticsearch/model/proxy.rb +++ b/elasticsearch-model/lib/elasticsearch/model/proxy.rb @@ -115,7 +115,7 @@ def initialize(target) @target = target end - def ruby2_keywords(*) # :nodoc: + def self.ruby2_keywords(*) # :nodoc: end if RUBY_VERSION < "2.7" # Delegate methods to `@target`. As per [the Ruby 3.0 explanation for keyword arguments](https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/), the only way to work on Ruby <2.7, and 2.7, and 3.0+ is to use `ruby2_keywords`. From 110b07bfea9fd504cbac08ce6c2e1a9da1ef094c Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 19 Jan 2023 11:24:41 +0000 Subject: [PATCH 538/582] Initial work on gemfiles --- elasticsearch-model/Rakefile | 3 +- .../elasticsearch-model.gemspec | 2 +- elasticsearch-model/gemfiles/3.0.gemfile | 35 ------------------ elasticsearch-model/gemfiles/4.0.gemfile | 36 ------------------- .../gemfiles/{6.0.gemfile => 6.1.gemfile} | 5 +-- .../gemfiles/{5.0.gemfile => 7.0.gemfile} | 13 +++---- .../elasticsearch-persistence.gemspec | 2 +- .../elasticsearch-rails.gemspec | 2 +- 8 files changed, 14 insertions(+), 84 deletions(-) delete mode 100644 elasticsearch-model/gemfiles/3.0.gemfile delete mode 100644 elasticsearch-model/gemfiles/4.0.gemfile rename elasticsearch-model/gemfiles/{6.0.gemfile => 6.1.gemfile} (94%) rename elasticsearch-model/gemfiles/{5.0.gemfile => 7.0.gemfile} (82%) diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index 6e03532b0..a57680a9a 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -21,8 +21,7 @@ desc 'Run unit tests' task default: 'test:all' task test: 'test:all' -gemfiles = ['5.0.gemfile', '6.0.gemfile'] -gemfiles << '4.0.gemfile' if RUBY_VERSION < '2.7' +gemfiles = ['6.1.gemfile', '7.0.gemfile'] GEMFILES = gemfiles.freeze namespace :bundle do diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index 0dc71775c..66712701f 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -39,7 +39,7 @@ Gem::Specification.new do |s| s.extra_rdoc_files = ['README.md', 'LICENSE.txt'] s.rdoc_options = ['--charset=UTF-8'] - s.required_ruby_version = '>= 2.4' + s.required_ruby_version = '>= 3' s.add_dependency 'activesupport', '> 3' s.add_dependency 'elasticsearch', '~> 7' diff --git a/elasticsearch-model/gemfiles/3.0.gemfile b/elasticsearch-model/gemfiles/3.0.gemfile deleted file mode 100644 index 1641023d7..000000000 --- a/elasticsearch-model/gemfiles/3.0.gemfile +++ /dev/null @@ -1,35 +0,0 @@ -# Licensed to Elasticsearch B.V. under one or more contributor -# license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright -# ownership. Elasticsearch B.V. licenses this file to you under -# the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -# Usage: -# -# $ BUNDLE_GEMFILE=./gemfiles/3.0.gemfile bundle install -# $ BUNDLE_GEMFILE=./gemfiles/3.0.gemfile bundle exec rake test:integration - -source 'https://rubygems.org' - -gemspec path: '../' - -gem 'activemodel', '>= 3.0' -gem 'activerecord', '~> 3.2' -gem 'mongoid', '>= 3.0' -gem 'sqlite3', '> 1.3', '< 1.4' unless defined?(JRUBY_VERSION) - -group :development, :testing do - gem 'rspec' - gem 'pry-nav' -end \ No newline at end of file diff --git a/elasticsearch-model/gemfiles/4.0.gemfile b/elasticsearch-model/gemfiles/4.0.gemfile deleted file mode 100644 index 944568fce..000000000 --- a/elasticsearch-model/gemfiles/4.0.gemfile +++ /dev/null @@ -1,36 +0,0 @@ -# Licensed to Elasticsearch B.V. under one or more contributor -# license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright -# ownership. Elasticsearch B.V. licenses this file to you under -# the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -# Usage: -# -# $ BUNDLE_GEMFILE=./gemfiles/4.0.gemfile bundle install -# $ BUNDLE_GEMFILE=./gemfiles/4.0.gemfile bundle exec rake test:integration - -source 'https://rubygems.org' - -gemspec path: '../' - -gem 'activemodel', '~> 4' -gem 'activerecord', '~> 4' -gem 'mongoid', '~> 5' -gem 'sqlite3', '> 1.3', '< 1.4' unless defined?(JRUBY_VERSION) - -group :development, :testing do - gem 'bigdecimal', '~> 1' - gem 'pry-nav' - gem 'rspec' -end diff --git a/elasticsearch-model/gemfiles/6.0.gemfile b/elasticsearch-model/gemfiles/6.1.gemfile similarity index 94% rename from elasticsearch-model/gemfiles/6.0.gemfile rename to elasticsearch-model/gemfiles/6.1.gemfile index 2461d66f0..5e5be171f 100644 --- a/elasticsearch-model/gemfiles/6.0.gemfile +++ b/elasticsearch-model/gemfiles/6.1.gemfile @@ -24,12 +24,13 @@ source 'https://rubygems.org' gemspec path: '../' -gem 'activemodel', '6.0.0' -gem 'activerecord', '6.0.0' +gem 'activemodel', '6.1' +gem 'activerecord', '6.1' gem 'sqlite3' unless defined?(JRUBY_VERSION) # gem 'mongoid', '~> 6' group :development, :testing do + gem 'byebug' gem 'pry-nav' gem 'rspec' end diff --git a/elasticsearch-model/gemfiles/5.0.gemfile b/elasticsearch-model/gemfiles/7.0.gemfile similarity index 82% rename from elasticsearch-model/gemfiles/5.0.gemfile rename to elasticsearch-model/gemfiles/7.0.gemfile index 8b1930961..4479207e1 100644 --- a/elasticsearch-model/gemfiles/5.0.gemfile +++ b/elasticsearch-model/gemfiles/7.0.gemfile @@ -17,19 +17,20 @@ # Usage: # -# $ BUNDLE_GEMFILE=./gemfiles/5.0.gemfile bundle install -# $ BUNDLE_GEMFILE=./gemfiles/5.0.gemfile bundle exec rake test:integration +# $ BUNDLE_GEMFILE=./gemfiles/7.0.gemfile bundle install +# $ BUNDLE_GEMFILE=./gemfiles/7.0.gemfile bundle exec rake test:integration source 'https://rubygems.org' gemspec path: '../' -gem 'activemodel', '~> 5' -gem 'activerecord', '~> 5' +gem 'activemodel', '7' +gem 'activerecord', '7' gem 'sqlite3' unless defined?(JRUBY_VERSION) -gem 'mongoid', '~> 6' +# gem 'mongoid', '~> 6' group :development, :testing do - gem 'rspec' + gem 'byebug' gem 'pry-nav' + gem 'rspec' end diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index 893103d4b..c8f5d0fa2 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -39,7 +39,7 @@ Gem::Specification.new do |s| s.extra_rdoc_files = ['README.md', 'LICENSE.txt'] s.rdoc_options = ['--charset=UTF-8'] - s.required_ruby_version = '>= 1.9.3' + s.required_ruby_version = '>= 3' s.add_dependency 'activemodel', '> 4' s.add_dependency 'activesupport', '> 4' diff --git a/elasticsearch-rails/elasticsearch-rails.gemspec b/elasticsearch-rails/elasticsearch-rails.gemspec index ac9375233..f7f2f5077 100644 --- a/elasticsearch-rails/elasticsearch-rails.gemspec +++ b/elasticsearch-rails/elasticsearch-rails.gemspec @@ -44,7 +44,7 @@ Gem::Specification.new do |s| s.extra_rdoc_files = ['README.md', 'LICENSE.txt'] s.rdoc_options = ['--charset=UTF-8'] - s.required_ruby_version = '>= 2.4' + s.required_ruby_version = '>= 3' s.add_development_dependency 'bundler' s.add_development_dependency 'cane' From 99d906f8e7b6fb90090c74ac51d7df589a780087 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 19 Jan 2023 11:25:22 +0000 Subject: [PATCH 539/582] Rubocoping: whitespace and styling --- .../lib/elasticsearch/model/adapter.rb | 2 -- .../lib/elasticsearch/model/importing.rb | 5 +---- .../lib/elasticsearch/model/proxy.rb | 4 ---- .../spec/elasticsearch/model/adapter_spec.rb | 11 ---------- .../adapters/active_record/import_spec.rb | 4 ++-- .../elasticsearch/model/importing_spec.rb | 21 ++----------------- 6 files changed, 5 insertions(+), 42 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapter.rb b/elasticsearch-model/lib/elasticsearch/model/adapter.rb index cdb0c7d0e..3283112e9 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapter.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapter.rb @@ -17,7 +17,6 @@ module Elasticsearch module Model - # Contains an adapter which provides OxM-specific implementations for common behaviour: # # * {Adapter::Adapter#records_mixin Fetching records from the database} @@ -29,7 +28,6 @@ module Model # @see Elasticsearch::Model::Adapter::Mongoid # module Adapter - # Returns an adapter based on the Ruby class passed # # @example Create an adapter for an ActiveRecord-based model diff --git a/elasticsearch-model/lib/elasticsearch/model/importing.rb b/elasticsearch-model/lib/elasticsearch/model/importing.rb index 927dd16ee..8655f9ac4 100644 --- a/elasticsearch-model/lib/elasticsearch/model/importing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/importing.rb @@ -17,14 +17,12 @@ module Elasticsearch module Model - # Provides support for easily and efficiently importing large amounts of # records from the including class into the index. # # @see ClassMethods#import # module Importing - # When included in a model, adds the importing methods. # # @example Import all records from the `Article` model @@ -42,13 +40,12 @@ def self.included(base) end module ClassMethods - # Import all model records into the index # # The method will pick up correct strategy based on the `Importing` module # defined in the corresponding adapter. # - # @param options [Hash] Options passed to the underlying `__find_in_batches`method + # @param options [Hash] Options passed to the underlying `__find_in_batches` method # @param block [Proc] Optional block to evaluate for each batch # # @yield [Hash] Gives the Hash with the Elasticsearch response to the block diff --git a/elasticsearch-model/lib/elasticsearch/model/proxy.rb b/elasticsearch-model/lib/elasticsearch/model/proxy.rb index 69a6059d2..8a93bc359 100644 --- a/elasticsearch-model/lib/elasticsearch/model/proxy.rb +++ b/elasticsearch-model/lib/elasticsearch/model/proxy.rb @@ -17,7 +17,6 @@ module Elasticsearch module Model - # This module provides a proxy interfacing between the including class and # `Elasticsearch::Model`, preventing the pollution of the including class namespace. # @@ -45,7 +44,6 @@ module Model # # => true # module Proxy - # Define the `__elasticsearch__` class and instance methods in the including class # and register a callback for intercepting changes in the model. # @@ -53,9 +51,7 @@ module Proxy # module and the functionality is accessible via the proxy. # def self.included(base) - base.class_eval do - # `ClassMethodsProxy` instance, accessed as `MyModel.__elasticsearch__` def self.__elasticsearch__ &block @__elasticsearch__ ||= ClassMethodsProxy.new(self) diff --git a/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb index 33dd62590..a2f2f850a 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapter_spec.rb @@ -18,7 +18,6 @@ require 'spec_helper' describe Elasticsearch::Model::Adapter do - before(:all) do class ::DummyAdapterClass; end class ::DummyAdapterClassWithAdapter; end @@ -37,14 +36,12 @@ class ::DummyAdapter end describe '#from_class' do - it 'should return an Adapter instance' do expect(Elasticsearch::Model::Adapter.from_class(DummyAdapterClass)).to be_a(Elasticsearch::Model::Adapter::Adapter) end end describe 'register' do - before do expect(Elasticsearch::Model::Adapter::Adapter).to receive(:register).and_call_original Elasticsearch::Model::Adapter.register(:foo, lambda { |c| false }) @@ -55,7 +52,6 @@ class ::DummyAdapter end context 'when a specific adapter class is set' do - before do expect(Elasticsearch::Model::Adapter::Adapter).to receive(:register).and_call_original Elasticsearch::Model::Adapter::Adapter.register(DummyAdapter, @@ -73,7 +69,6 @@ class ::DummyAdapter end describe 'default adapter' do - let(:adapter) do Elasticsearch::Model::Adapter::Adapter.new(DummyAdapterClass) end @@ -84,11 +79,9 @@ class ::DummyAdapter end describe '#records_mixin' do - before do Elasticsearch::Model::Adapter::Adapter.register(DummyAdapter, lambda { |c| c == DummyAdapterClassWithAdapter }) - end let(:adapter) do @@ -101,11 +94,9 @@ class ::DummyAdapter end describe '#callbacks_mixin' do - before do Elasticsearch::Model::Adapter::Adapter.register(DummyAdapter, lambda { |c| c == DummyAdapterClassWithAdapter }) - end let(:adapter) do @@ -118,11 +109,9 @@ class ::DummyAdapter end describe '#importing_mixin' do - before do Elasticsearch::Model::Adapter::Adapter.register(DummyAdapter, lambda { |c| c == DummyAdapterClassWithAdapter }) - end let(:adapter) do diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb index 1f5fe0847..a23e58c37 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb @@ -19,12 +19,12 @@ describe 'Elasticsearch::Model::Adapter::ActiveRecord Importing' do before(:all) do - ActiveRecord::Schema.define(:version => 1) do + ActiveRecord::Schema.define(version: 1) do create_table :import_articles do |t| t.string :title t.integer :views t.string :numeric # For the sake of invalid data sent to Elasticsearch - t.datetime :created_at, :default => 'NOW()' + t.datetime :created_at, default: 'NOW()' end end diff --git a/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb index 02cde6d6c..343e04d00 100644 --- a/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb @@ -18,18 +18,17 @@ require 'spec_helper' describe Elasticsearch::Model::Importing do - before(:all) do class DummyImportingModel end module DummyImportingAdapter module ImportingMixin - def __find_in_batches(options={}, &block) + def __find_in_batches( options = {}, &block) yield if block_given? end def __transform - lambda {|a|} + lambda { |a| } end end @@ -49,7 +48,6 @@ def importing_mixin end context 'when a model includes the Importing module' do - it 'provides importing methods' do expect(DummyImportingModel.respond_to?(:import)).to be(true) expect(DummyImportingModel.respond_to?(:__find_in_batches)).to be(true) @@ -57,7 +55,6 @@ def importing_mixin end describe '#import' do - before do allow(DummyImportingModel).to receive(:index_name).and_return('foo') allow(DummyImportingModel).to receive(:document_type).and_return('foo') @@ -75,7 +72,6 @@ def importing_mixin end context 'when no options are provided' do - before do expect(DummyImportingModel).to receive(:client).and_return(client) allow(DummyImportingModel).to receive(:index_exists?).and_return(true) @@ -87,7 +83,6 @@ def importing_mixin end context 'when there is an error' do - before do expect(DummyImportingModel).to receive(:client).and_return(client) allow(DummyImportingModel).to receive(:index_exists?).and_return(true) @@ -102,14 +97,12 @@ def importing_mixin end context 'when the method is called with the option to return the errors' do - it 'returns the errors' do expect(DummyImportingModel.import(return: 'errors')).to eq([{ 'index' => { 'error' => 'FAILED' } }]) end end context 'when the method is called with a block' do - it 'yields the response to the block' do DummyImportingModel.import do |response| expect(response['items'].size).to eq(2) @@ -119,7 +112,6 @@ def importing_mixin end context 'when the index does not exist' do - before do allow(DummyImportingModel).to receive(:index_exists?).and_return(false) end @@ -132,7 +124,6 @@ def importing_mixin end context 'when the method is called with the force option' do - before do expect(DummyImportingModel).to receive(:create_index!).with(force: true, index: 'foo').and_return(true) expect(DummyImportingModel).to receive(:__find_in_batches).with(foo: 'bar').and_return(true) @@ -144,7 +135,6 @@ def importing_mixin end context 'when the method is called with the refresh option' do - before do expect(DummyImportingModel).to receive(:refresh_index!).with(index: 'foo').and_return(true) expect(DummyImportingModel).to receive(:__find_in_batches).with(foo: 'bar').and_return(true) @@ -156,7 +146,6 @@ def importing_mixin end context 'when a different index name is provided' do - before do expect(DummyImportingModel).to receive(:client).and_return(client) expect(client).to receive(:bulk).with(body: nil, index: 'my-new-index', type: 'foo').and_return(response) @@ -168,7 +157,6 @@ def importing_mixin end context 'when a different document type is provided' do - before do expect(DummyImportingModel).to receive(:client).and_return(client) expect(client).to receive(:bulk).with(body: nil, index: 'foo', type: 'my-new-type').and_return(response) @@ -180,7 +168,6 @@ def importing_mixin end context 'the transform method' do - before do expect(DummyImportingModel).to receive(:client).and_return(client) expect(DummyImportingModel).to receive(:__transform).and_return(transform) @@ -197,9 +184,7 @@ def importing_mixin end context 'when a transform is provided as an option' do - context 'when the transform option is not a lambda' do - let(:transform) do 'not_callable' end @@ -212,7 +197,6 @@ def importing_mixin end context 'when the transform option is a lambda' do - before do expect(DummyImportingModel).to receive(:client).and_return(client) expect(DummyImportingModel).to receive(:__batch_to_bulk).with(anything, transform) @@ -229,7 +213,6 @@ def importing_mixin end context 'when a pipeline is provided as an options' do - before do expect(DummyImportingModel).to receive(:client).and_return(client) expect(client).to receive(:bulk).with(body: nil, index: 'foo', type: 'foo', pipeline: 'my-pipeline').and_return(response) From bec2e7776e1247433947191905f86885c0f2fc7a Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 19 Jan 2023 11:25:58 +0000 Subject: [PATCH 540/582] Updates gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 43ddc2e30..e3359ace9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ coverage/ rdoc/ doc/ Gemfile.lock +.byebug_history \ No newline at end of file From a2132ac79c6fa7f470edceced6c75802a56f553e Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Tue, 20 Jun 2023 16:04:41 +0100 Subject: [PATCH 541/582] Updates dependency on elasticsearch gem to 8 Replaces Elasticsearch::Transport with Elastic::Transport --- Gemfile | 2 +- Rakefile | 2 +- elasticsearch-model/README.md | 21 ++++++++++--------- .../elasticsearch-model.gemspec | 2 +- .../lib/elasticsearch/model/importing.rb | 2 -- .../lib/elasticsearch/model/multimodel.rb | 2 +- .../adapters/active_record/basic_spec.rb | 1 - .../spec/elasticsearch/model/indexing_spec.rb | 6 +++--- .../model/response/result_spec.rb | 1 - .../persistence/repository/find.rb | 2 +- .../spec/repository/store_spec.rb | 6 +++--- .../spec/repository_spec.rb | 2 +- 12 files changed, 23 insertions(+), 26 deletions(-) diff --git a/Gemfile b/Gemfile index 0edd2dd95..b6cb7cca7 100644 --- a/Gemfile +++ b/Gemfile @@ -19,7 +19,7 @@ source 'https://rubygems.org' gem 'ansi' gem 'cane' -gem 'elasticsearch', '~> 7' +gem 'elasticsearch', '~> 8' gem 'pry' gem 'rake', '~> 12' diff --git a/Rakefile b/Rakefile index 9b3347f86..4d49a8f34 100644 --- a/Rakefile +++ b/Rakefile @@ -163,7 +163,7 @@ task :wait_for_green do ready = true break end - rescue Elasticsearch::Transport::Transport::Errors::RequestTimeout => ex + rescue Elastic::Transport::Transport::Errors::RequestTimeout => ex puts "Couldn't confirm green status.\n#{ex.inspect}." rescue Faraday::ConnectionFailed => ex puts "Couldn't connect to Elasticsearch.\n#{ex.inspect}." diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 0e120c349..655485495 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -6,18 +6,19 @@ It aims to simplify integration of Ruby classes ("models"), commonly found e.g. ## Compatibility -This library is compatible with Ruby 2.4 and higher. +This library is compatible with Ruby 3 and higher. The version numbers follow the Elasticsearch major versions. Currently the `main` branch is compatible with version `7.x` of the Elasticsearch stack. **We haven't tested and updated the code for Elasticsearch `8.0` yet**. -| Rubygem | | Elasticsearch | -|:-------------:|:-:| :-----------: | -| 0.1 | → | 1.x | -| 2.x | → | 2.x | -| 5.x | → | 5.x | -| 6.x | → | 6.x | -| 7.x | → | 7.x | -| main | → | 7.x | +| Rubygem | | Elasticsearch | +|:-------:|:-:|:-------------:| +| 0.1 | → | 1.x | +| 2.x | → | 2.x | +| 5.x | → | 5.x | +| 6.x | → | 6.x | +| 7.x | → | 7.x | +| 8.x | → | 8.x | +| main | → | 8.x | ## Installation @@ -532,7 +533,7 @@ class Indexer when /delete/ begin Client.delete index: 'articles', type: 'article', id: record_id - rescue Elasticsearch::Transport::Transport::Errors::NotFound + rescue Elastic::Transport::Transport::Errors::NotFound logger.debug "Article not found, ID: #{record_id}" end else raise ArgumentError, "Unknown operation '#{operation}'" diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index 66712701f..c00b5c87a 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -42,7 +42,7 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 3' s.add_dependency 'activesupport', '> 3' - s.add_dependency 'elasticsearch', '~> 7' + s.add_dependency 'elasticsearch', '~> 8' s.add_dependency 'hashie' s.add_development_dependency 'activemodel', '> 3' diff --git a/elasticsearch-model/lib/elasticsearch/model/importing.rb b/elasticsearch-model/lib/elasticsearch/model/importing.rb index 8655f9ac4..b9f9ea392 100644 --- a/elasticsearch-model/lib/elasticsearch/model/importing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/importing.rb @@ -185,8 +185,6 @@ def __batch_to_bulk(batch, transform) batch.map { |model| transform.call(model) } end end - end - end end diff --git a/elasticsearch-model/lib/elasticsearch/model/multimodel.rb b/elasticsearch-model/lib/elasticsearch/model/multimodel.rb index 6b5fc2a81..b8c415cad 100644 --- a/elasticsearch-model/lib/elasticsearch/model/multimodel.rb +++ b/elasticsearch-model/lib/elasticsearch/model/multimodel.rb @@ -90,7 +90,7 @@ def document_type # Get the client common for all models # - # @return Elasticsearch::Transport::Client + # @return Elastic::Transport::Client # def client Elasticsearch::Model.client diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb index a769b38f6..9c62a03a6 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb @@ -20,7 +20,6 @@ describe Elasticsearch::Model::Adapter::ActiveRecord do context 'when a document_type is not defined for the Model' do - before do ActiveRecord::Schema.define(:version => 1) do create_table :article_no_types do |t| diff --git a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb index eb9651a73..b4d5777da 100644 --- a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb @@ -678,7 +678,7 @@ class ::DummyIndexingModelForRecreate context 'when the index is not found' do let(:logger) { nil } let(:transport) do - Elasticsearch::Transport::Client.new(logger: logger) + Elastic::Transport::Client.new(logger: logger) end let(:client) do @@ -918,7 +918,7 @@ class ::DummyIndexingModelForRefresh end let(:transport) do - Elasticsearch::Transport::Client.new(logger: nil) + Elastic::Transport::Client.new(logger: nil) end let(:indices) do @@ -949,7 +949,7 @@ class ::DummyIndexingModelForRefresh end let(:transport) do - Elasticsearch::Transport::Client.new(logger: logger) + Elastic::Transport::Client.new(logger: logger) end it 'does not raise an exception' do diff --git a/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb index 5aa7c8f4f..c3c73fec6 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb @@ -19,7 +19,6 @@ require 'active_support/json/encoding' describe Elasticsearch::Model::Response::Result do - let(:result) do described_class.new(foo: 'bar', bar: { bam: 'baz' }) end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb index cdd50c042..fad2593e8 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb @@ -87,7 +87,7 @@ def __find_one(id, options={}) request[:type] = document_type if document_type document = client.get(request.merge(options)) deserialize(document) - rescue Elasticsearch::Transport::Transport::Errors::NotFound => e + rescue Elastic::Transport::Transport::Errors::NotFound => e raise DocumentNotFound, e.message, caller end diff --git a/elasticsearch-persistence/spec/repository/store_spec.rb b/elasticsearch-persistence/spec/repository/store_spec.rb index bebf810aa..92b7035e7 100644 --- a/elasticsearch-persistence/spec/repository/store_spec.rb +++ b/elasticsearch-persistence/spec/repository/store_spec.rb @@ -242,7 +242,7 @@ def to_hash it 'raises an exception' do expect { repository.update(1, doc: { text: 'testing_2' }) - }.to raise_exception(Elasticsearch::Transport::Transport::Errors::NotFound) + }.to raise_exception(Elastic::Transport::Transport::Errors::NotFound) end context 'when upsert is provided' do @@ -262,7 +262,7 @@ def to_hash it 'raises an exception' do expect { repository.update(id: 1, text: 'testing_2') - }.to raise_exception(Elasticsearch::Transport::Transport::Errors::NotFound) + }.to raise_exception(Elastic::Transport::Transport::Errors::NotFound) end context 'when upsert is provided' do @@ -337,7 +337,7 @@ def to_hash it 'raises an exception' do expect { repository.delete(1) - }.to raise_exception(Elasticsearch::Transport::Transport::Errors::NotFound) + }.to raise_exception(Elastic::Transport::Transport::Errors::NotFound) end end end diff --git a/elasticsearch-persistence/spec/repository_spec.rb b/elasticsearch-persistence/spec/repository_spec.rb index 05aeb1f53..ef9f0575d 100644 --- a/elasticsearch-persistence/spec/repository_spec.rb +++ b/elasticsearch-persistence/spec/repository_spec.rb @@ -562,7 +562,7 @@ class RepositoryWithoutDSL it 'raises an error' do expect { repository.create_index! - }.to raise_exception(Elasticsearch::Transport::Transport::Errors::BadRequest) + }.to raise_exception(Elastic::Transport::Transport::Errors::BadRequest) end end end From 99fcb362ef7b3e9a7cf924ba76c8689863b120b7 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Tue, 20 Jun 2023 16:05:43 +0100 Subject: [PATCH 542/582] Adds debug gem to development/testing --- elasticsearch-model/Gemfile | 1 + elasticsearch-persistence/Gemfile | 1 + elasticsearch-rails/Gemfile | 1 + 3 files changed, 3 insertions(+) diff --git a/elasticsearch-model/Gemfile b/elasticsearch-model/Gemfile index 5a31c3095..72fae31be 100644 --- a/elasticsearch-model/Gemfile +++ b/elasticsearch-model/Gemfile @@ -21,6 +21,7 @@ source 'https://rubygems.org' gemspec group :development, :testing do + gem 'debug' gem 'pry-nav' gem 'rspec' end diff --git a/elasticsearch-persistence/Gemfile b/elasticsearch-persistence/Gemfile index 17e92d051..2afccf280 100644 --- a/elasticsearch-persistence/Gemfile +++ b/elasticsearch-persistence/Gemfile @@ -25,6 +25,7 @@ gem 'elasticsearch-model', require: false group :development, :testing do + gem 'debug' gem 'pry-nav' gem 'rspec' end diff --git a/elasticsearch-rails/Gemfile b/elasticsearch-rails/Gemfile index 90572cba1..2f333c4b5 100644 --- a/elasticsearch-rails/Gemfile +++ b/elasticsearch-rails/Gemfile @@ -29,6 +29,7 @@ gem 'elasticsearch-persistence', require: false group :development, :testing do + gem 'debug' gem 'pry-nav' gem 'rspec' gem 'sqlite3' unless defined?(JRUBY_VERSION) From b71d5a9dfb1a5212cbad8b6f9321a1e0a6726579 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Tue, 20 Jun 2023 16:29:00 +0100 Subject: [PATCH 543/582] Removing type related code --- .../lib/elasticsearch/model.rb | 2 +- .../lib/elasticsearch/model/importing.rb | 3 - .../lib/elasticsearch/model/indexing.rb | 11 +-- .../elasticsearch/model/response/result.rb | 6 -- .../lib/elasticsearch/model/searching.rb | 5 +- .../adapters/active_record/basic_spec.rb | 14 +--- .../active_record/namespaced_model_spec.rb | 6 +- .../elasticsearch/model/importing_spec.rb | 16 +---- .../spec/elasticsearch/model/indexing_spec.rb | 1 - .../spec/elasticsearch/model/naming_spec.rb | 68 ------------------- .../elasticsearch/model/response/base_spec.rb | 1 - .../response/pagination/kaminari_spec.rb | 7 +- .../model/response/response_spec.rb | 1 - .../model/response/result_spec.rb | 16 ----- .../model/searching_search_request_spec.rb | 11 ++- .../spec/support/app/answer.rb | 1 - .../spec/support/app/article.rb | 2 - .../spec/support/app/article_no_type.rb | 2 +- .../spec/support/app/namespaced_book.rb | 2 - .../spec/support/app/question.rb | 1 - .../spec/repository/store_spec.rb | 6 +- 21 files changed, 22 insertions(+), 160 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index af2f34cb6..7efc5caa9 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -85,7 +85,7 @@ module Elasticsearch # # ... # module Model - METHODS = [:search, :mapping, :mappings, :settings, :index_name, :document_type, :import] + METHODS = [:search, :mapping, :mappings, :settings, :index_name, :import] # Adds the `Elasticsearch::Model` functionality to the including class. # diff --git a/elasticsearch-model/lib/elasticsearch/model/importing.rb b/elasticsearch-model/lib/elasticsearch/model/importing.rb index b9f9ea392..3060e178a 100644 --- a/elasticsearch-model/lib/elasticsearch/model/importing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/importing.rb @@ -59,7 +59,6 @@ module ClassMethods # "index" => { # "error" => 'FAILED', # "_index" => "test", - # "_type" => "_doc", # "_id" => '1', # "_version" => 1, # "result" => "foo", @@ -138,7 +137,6 @@ def import(options={}, &block) errors = [] refresh = options.delete(:refresh) || false target_index = options.delete(:index) || index_name - target_type = options.delete(:type) || document_type transform = options.delete(:transform) || __transform pipeline = options.delete(:pipeline) return_value = options.delete(:return) || 'count' @@ -158,7 +156,6 @@ def import(options={}, &block) __find_in_batches(options) do |batch| params = { index: target_index, - type: target_type, body: __batch_to_bulk(batch, transform) } diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index 3ebd8cfb4..f270a26b4 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -51,13 +51,12 @@ def as_json(options={}) # Wraps the [index mappings](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html) # class Mappings - attr_accessor :options, :type + attr_accessor :options # @private TYPES_WITH_EMBEDDED_PROPERTIES = %w(object nested) - def initialize(type = nil, options={}) - @type = type + def initialize(options={}) @options = options @mapping = {} end @@ -152,7 +151,7 @@ module ClassMethods # when it doesn't already define them. Use the `__elasticsearch__` proxy otherwise. # def mapping(options={}, &block) - @mapping ||= Mappings.new(document_type, options) + @mapping ||= Mappings.new(options) @mapping.options.update(options) unless options.empty? @@ -372,7 +371,6 @@ def index_document(options={}) request = { index: index_name, id: id, body: document } - request.merge!(type: document_type) if document_type client.index(request.merge!(options)) end @@ -393,7 +391,6 @@ def index_document(options={}) def delete_document(options={}) request = { index: index_name, id: self.id } - request.merge!(type: document_type) if document_type client.delete(request.merge!(options)) end @@ -434,7 +431,6 @@ def update_document(options={}) request = { index: index_name, id: self.id, body: { doc: attributes } } - request.merge!(type: document_type) if document_type client.update(request.merge!(options)) end @@ -461,7 +457,6 @@ def update_document_attributes(attributes, options={}) request = { index: index_name, id: self.id, body: { doc: attributes } } - request.merge!(type: document_type) if document_type client.update(request.merge!(options)) end diff --git a/elasticsearch-model/lib/elasticsearch/model/response/result.rb b/elasticsearch-model/lib/elasticsearch/model/response/result.rb index 5e102c2b3..d293e6efa 100644 --- a/elasticsearch-model/lib/elasticsearch/model/response/result.rb +++ b/elasticsearch-model/lib/elasticsearch/model/response/result.rb @@ -40,12 +40,6 @@ def id @result['_id'] end - # Return document `_type` as `_type` - # - def type - @result['_type'] - end - # Delegate methods to `@result` or `@result._source` # def method_missing(name, *arguments) diff --git a/elasticsearch-model/lib/elasticsearch/model/searching.rb b/elasticsearch-model/lib/elasticsearch/model/searching.rb index 714f98a22..e913444dd 100644 --- a/elasticsearch-model/lib/elasticsearch/model/searching.rb +++ b/elasticsearch-model/lib/elasticsearch/model/searching.rb @@ -37,7 +37,6 @@ def initialize(klass, query_or_payload, options={}) @options = options __index_name = options[:index] || klass.index_name - __document_type = options[:type] || klass.document_type case # search query: ... @@ -54,9 +53,9 @@ def initialize(klass, query_or_payload, options={}) end if body - @definition = { index: __index_name, type: __document_type, body: body }.update options + @definition = { index: __index_name, body: body }.update options else - @definition = { index: __index_name, type: __document_type, q: q }.update options + @definition = { index: __index_name, q: q }.update options end end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb index 9c62a03a6..5dc91da9e 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb @@ -66,7 +66,7 @@ end Article.delete_all - Article.__elasticsearch__.create_index!(force: true, include_type_name: true) + Article.__elasticsearch__.create_index!(force: true) Article.create!(title: 'Test', body: '', clicks: 1) Article.create!(title: 'Testing Coding', body: '', clicks: 2) @@ -159,19 +159,7 @@ end end - describe '#id' do - - let(:search_result) do - Article.search('title:test') - end - - it 'returns the type' do - expect(search_result.results.first.type).to eq('article') - end - end - describe '#each_with_hit' do - let(:search_result) do Article.search('title:test') end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb index 730dad0e0..09f4670a6 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb @@ -27,7 +27,7 @@ end MyNamespace::Book.delete_all - MyNamespace::Book.__elasticsearch__.create_index!(force: true, include_type_name: true) + MyNamespace::Book.__elasticsearch__.create_index!(force: true) MyNamespace::Book.create!(title: 'Test') MyNamespace::Book.__elasticsearch__.refresh_index! end @@ -43,10 +43,6 @@ expect(MyNamespace::Book.index_name).to eq('my_namespace-books') end - it 'has the proper document type' do - expect(MyNamespace::Book.document_type).to eq('book') - end - it 'saves the document into the index' do expect(MyNamespace::Book.search('title:test').results.size).to eq(1) expect(MyNamespace::Book.search('title:test').results.first.title).to eq('Test') diff --git a/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb index 343e04d00..438d0c9e9 100644 --- a/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb @@ -57,7 +57,6 @@ def importing_mixin describe '#import' do before do allow(DummyImportingModel).to receive(:index_name).and_return('foo') - allow(DummyImportingModel).to receive(:document_type).and_return('foo') allow(DummyImportingModel).to receive(:index_exists?).and_return(true) allow(DummyImportingModel).to receive(:__batch_to_bulk) allow(client).to receive(:bulk).and_return(response) @@ -148,7 +147,7 @@ def importing_mixin context 'when a different index name is provided' do before do expect(DummyImportingModel).to receive(:client).and_return(client) - expect(client).to receive(:bulk).with(body: nil, index: 'my-new-index', type: 'foo').and_return(response) + expect(client).to receive(:bulk).with(body: nil, index: 'my-new-index').and_return(response) end it 'uses the alternate index name' do @@ -156,17 +155,6 @@ def importing_mixin end end - context 'when a different document type is provided' do - before do - expect(DummyImportingModel).to receive(:client).and_return(client) - expect(client).to receive(:bulk).with(body: nil, index: 'foo', type: 'my-new-type').and_return(response) - end - - it 'uses the alternate index name' do - expect(DummyImportingModel.import(type: 'my-new-type')).to eq(0) - end - end - context 'the transform method' do before do expect(DummyImportingModel).to receive(:client).and_return(client) @@ -215,7 +203,7 @@ def importing_mixin context 'when a pipeline is provided as an options' do before do expect(DummyImportingModel).to receive(:client).and_return(client) - expect(client).to receive(:bulk).with(body: nil, index: 'foo', type: 'foo', pipeline: 'my-pipeline').and_return(response) + expect(client).to receive(:bulk).with(body: nil, index: 'foo', pipeline: 'my-pipeline').and_return(response) end it 'uses the pipeline option' do diff --git a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb index b4d5777da..5836bd2a6 100644 --- a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb @@ -38,7 +38,6 @@ class NotFound < Exception; end end describe 'the Settings class' do - it 'should be convertible to a hash' do expect(Elasticsearch::Model::Indexing::Settings.new(foo: 'bar').to_hash).to eq(foo: 'bar') end diff --git a/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb b/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb index 57a53b8d7..917bc099c 100644 --- a/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/naming_spec.rb @@ -51,13 +51,7 @@ class DummyNamingModelInNamespace expect(::MyNamespace::DummyNamingModelInNamespace.new.index_name).to eq('my_namespace-dummy_naming_model_in_namespaces') end - it 'returns nil' do - expect(DummyNamingModel.document_type).to be_nil - expect(DummyNamingModel.new.document_type).to be_nil - end - describe '#index_name' do - context 'when the index name is set on the class' do before do @@ -138,66 +132,4 @@ class DummyNamingModelInNamespace end end end - - describe '#document_type' do - - it 'returns nil' do - expect(DummyNamingModel.document_type).to be_nil - end - - context 'when the method is called with an argument' do - - before do - DummyNamingModel.document_type 'foo' - end - - it 'changes the document type' do - expect(DummyNamingModel.document_type).to eq('foo') - end - end - - context 'when the method is called on an instance' do - - let(:instance) do - DummyNamingModel.new - end - - before do - instance.document_type 'foobar_d' - end - - it 'changes the document type' do - expect(instance.document_type).to eq('foobar_d') - end - end - end - - describe '#document_type=' do - - context 'when the method is called on the class' do - - before do - DummyNamingModel.document_type = 'foo_z' - end - - it 'changes the document type' do - expect(DummyNamingModel.document_type).to eq('foo_z') - end - end - - context 'when the method is called on an instance' do - - let(:instance) do - DummyNamingModel.new - end - - before do - instance.document_type = 'foobar_b' - end - - it 'changes the document type' do - expect(instance.document_type).to eq('foobar_b') - end - end - end end diff --git a/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb index d3459b93b..dd5ff2d26 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/base_spec.rb @@ -26,7 +26,6 @@ class DummyBaseClass class OriginClass def self.index_name; 'foo'; end - def self.document_type; 'bar'; end end end diff --git a/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb index 15ee6113c..cb01c86cb 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/pagination/kaminari_spec.rb @@ -23,7 +23,6 @@ class ModelClass include ::Kaminari::ConfigurationMethods def self.index_name; 'foo'; end - def self.document_type; 'bar'; end end end @@ -58,7 +57,7 @@ def self.document_type; 'bar'; end context 'when page is called once' do let(:search_request) do - { index: index_field, from: 25, size: 25, q: '*', type: type_field} + { index: index_field, from: 25, size: 25, q: '*' } end before do @@ -75,11 +74,11 @@ def self.document_type; 'bar'; end context 'when page is called more than once' do let(:search_request_one) do - { index: index_field, from: 25, size: 25, q: '*', type: type_field} + { index: index_field, from: 25, size: 25, q: '*' } end let(:search_request_two) do - { index: index_field, from: 75, size: 25, q: '*', type: type_field} + { index: index_field, from: 75, size: 25, q: '*' } end before do diff --git a/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb index e9a423812..7186b4710 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/response_spec.rb @@ -22,7 +22,6 @@ before(:all) do class OriginClass def self.index_name; 'foo'; end - def self.document_type; 'bar'; end end end diff --git a/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb index c3c73fec6..cf5abd38f 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/result_spec.rb @@ -44,23 +44,7 @@ end end - describe '#type' do - - let(:result) do - described_class.new(foo: 'bar', _type: 'baz', _source: { type: 'BAM' }) - end - - it 'returns the _type field' do - expect(result.type).to eq('baz') - end - - it 'provides access to the source type field' do - expect(result._source.type).to eq('BAM') - end - end - describe 'method delegation' do - let(:result) do described_class.new(foo: 'bar', _source: { bar: { bam: 'baz' } }) end diff --git a/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb b/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb index 1f5e72bf2..1a7b4e413 100644 --- a/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb @@ -23,7 +23,6 @@ class ::DummySearchingModel extend Elasticsearch::Model::Searching::ClassMethods def self.index_name; 'foo'; end - def self.document_type; 'bar'; end end end @@ -44,7 +43,7 @@ def self.document_type; 'bar'; end context 'when the search definition is a simple query' do before do - expect(client).to receive(:search).with(index: 'foo', type: 'bar', q: 'foo').and_return({}) + expect(client).to receive(:search).with(index: 'foo', q: 'foo').and_return({}) end let(:search) do @@ -59,7 +58,7 @@ def self.document_type; 'bar'; end context 'when the search definition is a hash' do before do - expect(client).to receive(:search).with(index: 'foo', type: 'bar', body: { foo: 'bar' }).and_return({}) + expect(client).to receive(:search).with(index: 'foo', body: { foo: 'bar' }).and_return({}) end let(:search) do @@ -74,7 +73,7 @@ def self.document_type; 'bar'; end context 'when the search definition is a json string' do before do - expect(client).to receive(:search).with(index: 'foo', type: 'bar', body: '{"foo":"bar"}').and_return({}) + expect(client).to receive(:search).with(index: 'foo', body: '{"foo":"bar"}').and_return({}) end let(:search) do @@ -99,7 +98,7 @@ def to_hash; {foo: 'bar'}; end end before do - expect(client).to receive(:search).with(index: 'foo', type: 'bar', body: {foo: 'bar'}).and_return({}) + expect(client).to receive(:search).with(index: 'foo', body: {foo: 'bar'}).and_return({}) end let(:search) do @@ -114,7 +113,7 @@ def to_hash; {foo: 'bar'}; end context 'when extra options are specified' do before do - expect(client).to receive(:search).with(index: 'foo', type: 'bar', q: 'foo', size: 15).and_return({}) + expect(client).to receive(:search).with(index: 'foo', q: 'foo', size: 15).and_return({}) end let(:search) do diff --git a/elasticsearch-model/spec/support/app/answer.rb b/elasticsearch-model/spec/support/app/answer.rb index 9c58cc991..ec010e3d2 100644 --- a/elasticsearch-model/spec/support/app/answer.rb +++ b/elasticsearch-model/spec/support/app/answer.rb @@ -23,7 +23,6 @@ class Answer < ActiveRecord::Base JOIN_TYPE = 'answer'.freeze index_name 'questions_and_answers'.freeze - document_type 'doc'.freeze before_create :randomize_id diff --git a/elasticsearch-model/spec/support/app/article.rb b/elasticsearch-model/spec/support/app/article.rb index 2c122dd77..659d16cec 100644 --- a/elasticsearch-model/spec/support/app/article.rb +++ b/elasticsearch-model/spec/support/app/article.rb @@ -19,8 +19,6 @@ class ::Article < ActiveRecord::Base include Elasticsearch::Model include Elasticsearch::Model::Callbacks - document_type 'article' - settings index: {number_of_shards: 1, number_of_replicas: 0} do mapping do indexes :title, type: 'text', analyzer: 'snowball' diff --git a/elasticsearch-model/spec/support/app/article_no_type.rb b/elasticsearch-model/spec/support/app/article_no_type.rb index d3cb6b4e3..9e6b8443f 100644 --- a/elasticsearch-model/spec/support/app/article_no_type.rb +++ b/elasticsearch-model/spec/support/app/article_no_type.rb @@ -21,7 +21,7 @@ class ::ArticleNoType < ActiveRecord::Base settings index: {number_of_shards: 1, number_of_replicas: 0} do mapping do - indexes :title, type: 'text', analyzer: 'snowball' + indexes :title, analyzer: 'snowball' indexes :body, type: 'text' indexes :clicks, type: 'integer' indexes :created_at, type: 'date' diff --git a/elasticsearch-model/spec/support/app/namespaced_book.rb b/elasticsearch-model/spec/support/app/namespaced_book.rb index f6d9c838c..29949772a 100644 --- a/elasticsearch-model/spec/support/app/namespaced_book.rb +++ b/elasticsearch-model/spec/support/app/namespaced_book.rb @@ -20,8 +20,6 @@ class Book < ActiveRecord::Base include Elasticsearch::Model include Elasticsearch::Model::Callbacks - document_type 'book' - mapping { indexes :title } end end diff --git a/elasticsearch-model/spec/support/app/question.rb b/elasticsearch-model/spec/support/app/question.rb index 62a7d4550..fce543139 100644 --- a/elasticsearch-model/spec/support/app/question.rb +++ b/elasticsearch-model/spec/support/app/question.rb @@ -24,7 +24,6 @@ class Question < ActiveRecord::Base JOIN_METADATA = { join_field: JOIN_TYPE}.freeze index_name 'questions_and_answers'.freeze - document_type 'doc'.freeze mapping do indexes :title diff --git a/elasticsearch-persistence/spec/repository/store_spec.rb b/elasticsearch-persistence/spec/repository/store_spec.rb index 92b7035e7..82e1d3b01 100644 --- a/elasticsearch-persistence/spec/repository/store_spec.rb +++ b/elasticsearch-persistence/spec/repository/store_spec.rb @@ -74,14 +74,14 @@ def serialize(document) context 'when options are provided' do let!(:response) do - repository.save(document, type: 'other_note') + repository.save(document) end it 'saves the document using the options' do expect { repository.find(response['_id']) }.to raise_exception(Elasticsearch::Persistence::Repository::DocumentNotFound) - expect(repository.find(response['_id'], type: 'other_note')).to eq('a' => 1) + expect(repository.find(response['_id'])).to eq('a' => 1) end end end @@ -331,7 +331,7 @@ def to_hash context 'when the document does not exist' do before do - repository.create_index!(include_type_name: true) + repository.create_index! end it 'raises an exception' do From 272789878118b3170c92f34999b03cf0d4d124af Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Wed, 21 Jun 2023 16:41:20 +0100 Subject: [PATCH 544/582] Updates elasticsearch-model rake task, fixes dependencies --- elasticsearch-model/Rakefile | 2 +- elasticsearch-model/gemfiles/7.0.gemfile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index a57680a9a..bbcf9ad41 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -46,7 +46,7 @@ end require 'rake/testtask' namespace :test do desc 'Run all tests. Optionally define env variable RAILS_VERSIONS. E.g. RAILS_VERSIONS=3.0,5.0' - task :all, [:rails_versions] do |task, args| + task :all do |task, args| gemfiles = ENV['RAILS_VERSIONS'] ? ENV['RAILS_VERSIONS'].split(',').map { |v| "#{v}.gemfile" } : GEMFILES puts '-' * 80 gemfiles.each do |gemfile| diff --git a/elasticsearch-model/gemfiles/7.0.gemfile b/elasticsearch-model/gemfiles/7.0.gemfile index 4479207e1..bc3bd63a5 100644 --- a/elasticsearch-model/gemfiles/7.0.gemfile +++ b/elasticsearch-model/gemfiles/7.0.gemfile @@ -24,8 +24,8 @@ source 'https://rubygems.org' gemspec path: '../' -gem 'activemodel', '7' -gem 'activerecord', '7' +gem 'activemodel', '~> 7' +gem 'activerecord', '~> 7' gem 'sqlite3' unless defined?(JRUBY_VERSION) # gem 'mongoid', '~> 6' From 157a7b38a861311b5903e9319788c415de3c5a3b Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 22 Jun 2023 08:32:48 +0100 Subject: [PATCH 545/582] Updates indexing spec Removes type Updates option hash/keyword argument expectations --- .../spec/elasticsearch/model/indexing_spec.rb | 131 ++++++------------ 1 file changed, 41 insertions(+), 90 deletions(-) diff --git a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb index 5836bd2a6..d82570910 100644 --- a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb @@ -18,7 +18,6 @@ require 'spec_helper' describe Elasticsearch::Model::Indexing do - before(:all) do class ::DummyIndexingModel extend ActiveModel::Naming @@ -48,13 +47,11 @@ class NotFound < Exception; end end describe '#settings' do - it 'returns an instance of the Settings class' do expect(DummyIndexingModel.settings).to be_a(Elasticsearch::Model::Indexing::Settings) end context 'when the settings are updated' do - before do DummyIndexingModel.settings(foo: 'boo') DummyIndexingModel.settings(bar: 'bam') @@ -66,7 +63,6 @@ class NotFound < Exception; end end context 'when the settings are updated with a yml file' do - before do DummyIndexingModel.settings File.open('spec/support/model.yml') DummyIndexingModel.settings bar: 'bam' @@ -78,7 +74,6 @@ class NotFound < Exception; end end context 'when the settings are updated with a json file' do - before do DummyIndexingModel.settings File.open('spec/support/model.json') DummyIndexingModel.settings bar: 'bam' @@ -91,9 +86,8 @@ class NotFound < Exception; end end describe '#mappings' do - let(:expected_mapping_hash) do - { :mytype => { foo: 'bar', :properties => {} } } + { foo: 'bar', :properties => {} } end it 'returns an instance of the Mappings class' do @@ -105,17 +99,16 @@ class NotFound < Exception; end end it 'should be convertible to a hash' do - expect(Elasticsearch::Model::Indexing::Mappings.new(:mytype, { foo: 'bar' }).to_hash).to eq(expected_mapping_hash) + expect(Elasticsearch::Model::Indexing::Mappings.new({ foo: 'bar' }).to_hash).to eq(expected_mapping_hash) end it 'should be convertible to json' do - expect(Elasticsearch::Model::Indexing::Mappings.new(:mytype, { foo: 'bar' }).as_json).to eq(expected_mapping_hash) + expect(Elasticsearch::Model::Indexing::Mappings.new({ foo: 'bar' }).as_json).to eq(expected_mapping_hash) end context 'when a type is specified' do - let(:mappings) do - Elasticsearch::Model::Indexing::Mappings.new(:mytype) + Elasticsearch::Model::Indexing::Mappings.new end before do @@ -124,17 +117,17 @@ class NotFound < Exception; end end it 'creates the correct mapping definition' do - expect(mappings.to_hash[:mytype][:properties][:foo][:type]).to eq('boolean') + expect(mappings.to_hash[:properties][:foo][:type]).to eq('boolean') end it 'uses text as the default field type' do - expect(mappings.to_hash[:mytype][:properties][:bar][:type]).to eq('text') + expect(mappings.to_hash[:properties][:bar][:type]).to eq('text') end context 'when the \'include_type_name\' option is specified' do let(:mappings) do - Elasticsearch::Model::Indexing::Mappings.new(:mytype, include_type_name: true) + Elasticsearch::Model::Indexing::Mappings.new(include_type_name: true) end before do @@ -142,17 +135,16 @@ class NotFound < Exception; end end it 'creates the correct mapping definition' do - expect(mappings.to_hash[:mytype][:properties][:foo][:type]).to eq('boolean') + expect(mappings.to_hash[:properties][:foo][:type]).to eq('boolean') end it 'sets the \'include_type_name\' option' do - expect(mappings.to_hash[:mytype][:include_type_name]).to eq(true) + expect(mappings.to_hash[:include_type_name]).to eq(true) end end end context 'when a type is not specified' do - let(:mappings) do Elasticsearch::Model::Indexing::Mappings.new end @@ -174,7 +166,7 @@ class NotFound < Exception; end context 'when specific mappings are defined' do let(:mappings) do - Elasticsearch::Model::Indexing::Mappings.new(:mytype, include_type_name: true) + Elasticsearch::Model::Indexing::Mappings.new(include_type_name: true) end before do @@ -183,15 +175,14 @@ class NotFound < Exception; end end it 'creates the correct mapping definition' do - expect(mappings.to_hash[:mytype][:properties][:foo][:type]).to eq('boolean') + expect(mappings.to_hash[:properties][:foo][:type]).to eq('boolean') end it 'uses text as the default type' do - expect(mappings.to_hash[:mytype][:properties][:bar][:type]).to eq('text') + expect(mappings.to_hash[:properties][:bar][:type]).to eq('text') end context 'when mappings are defined for multiple fields' do - before do mappings.indexes :my_field, type: 'text' do indexes :raw, type: 'keyword' @@ -199,14 +190,13 @@ class NotFound < Exception; end end it 'defines the mapping for all the fields' do - expect(mappings.to_hash[:mytype][:properties][:my_field][:type]).to eq('text') - expect(mappings.to_hash[:mytype][:properties][:my_field][:fields][:raw][:type]).to eq('keyword') - expect(mappings.to_hash[:mytype][:properties][:my_field][:fields][:raw][:properties]).to be_nil + expect(mappings.to_hash[:properties][:my_field][:type]).to eq('text') + expect(mappings.to_hash[:properties][:my_field][:fields][:raw][:type]).to eq('keyword') + expect(mappings.to_hash[:properties][:my_field][:fields][:raw][:properties]).to be_nil end end context 'when embedded properties are defined' do - before do mappings.indexes :foo do indexes :bar @@ -226,31 +216,30 @@ class NotFound < Exception; end end it 'defines mappings for the embedded properties' do - expect(mappings.to_hash[:mytype][:properties][:foo][:type]).to eq('object') - expect(mappings.to_hash[:mytype][:properties][:foo][:properties][:bar][:type]).to eq('text') - expect(mappings.to_hash[:mytype][:properties][:foo][:fields]).to be_nil + expect(mappings.to_hash[:properties][:foo][:type]).to eq('object') + expect(mappings.to_hash[:properties][:foo][:properties][:bar][:type]).to eq('text') + expect(mappings.to_hash[:properties][:foo][:fields]).to be_nil - expect(mappings.to_hash[:mytype][:properties][:foo_object][:type]).to eq('object') - expect(mappings.to_hash[:mytype][:properties][:foo_object][:properties][:bar][:type]).to eq('text') - expect(mappings.to_hash[:mytype][:properties][:foo_object][:fields]).to be_nil + expect(mappings.to_hash[:properties][:foo_object][:type]).to eq('object') + expect(mappings.to_hash[:properties][:foo_object][:properties][:bar][:type]).to eq('text') + expect(mappings.to_hash[:properties][:foo_object][:fields]).to be_nil - expect(mappings.to_hash[:mytype][:properties][:foo_nested][:type]).to eq('nested') - expect(mappings.to_hash[:mytype][:properties][:foo_nested][:properties][:bar][:type]).to eq('text') - expect(mappings.to_hash[:mytype][:properties][:foo_nested][:fields]).to be_nil + expect(mappings.to_hash[:properties][:foo_nested][:type]).to eq('nested') + expect(mappings.to_hash[:properties][:foo_nested][:properties][:bar][:type]).to eq('text') + expect(mappings.to_hash[:properties][:foo_nested][:fields]).to be_nil - expect(mappings.to_hash[:mytype][:properties][:foo_nested_as_symbol][:type]).to eq(:nested) - expect(mappings.to_hash[:mytype][:properties][:foo_nested_as_symbol][:properties]).not_to be_nil - expect(mappings.to_hash[:mytype][:properties][:foo_nested_as_symbol][:fields]).to be_nil + expect(mappings.to_hash[:properties][:foo_nested_as_symbol][:type]).to eq(:nested) + expect(mappings.to_hash[:properties][:foo_nested_as_symbol][:properties]).not_to be_nil + expect(mappings.to_hash[:properties][:foo_nested_as_symbol][:fields]).to be_nil end it 'defines the settings' do - expect(mappings.to_hash[:mytype][:include_type_name]).to be(true) + expect(mappings.to_hash[:include_type_name]).to be(true) end end end context 'when the method is called on a class' do - before do DummyIndexingModel.mappings(foo: 'boo') DummyIndexingModel.mappings(bar: 'bam') @@ -265,7 +254,6 @@ class NotFound < Exception; end end context 'when the method is called with a block' do - before do DummyIndexingModel.mapping do indexes :foo, type: 'boolean' @@ -278,16 +266,14 @@ class NotFound < Exception; end end context 'when the class has a document_type' do - before do DummyIndexingModel.instance_variable_set(:@mapping, nil) - DummyIndexingModel.document_type(:mytype) DummyIndexingModel.mappings(foo: 'boo') DummyIndexingModel.mappings(bar: 'bam') end let(:expected_mappings_hash) do - { mytype: { foo: "boo", bar: "bam", properties: {} } } + { foo: "boo", bar: "bam", properties: {} } end it 'sets the mappings' do @@ -298,7 +284,6 @@ class NotFound < Exception; end end describe 'instance methods' do - before(:all) do class ::DummyIndexingModelWithCallbacks extend Elasticsearch::Model::Indexing::ClassMethods @@ -365,9 +350,7 @@ def changes end context 'when the module is included' do - context 'when the model uses the old ActiveModel::Dirty' do - before do DummyIndexingModelWithOldDirty.__send__ :include, Elasticsearch::Model::Indexing::InstanceMethods end @@ -389,7 +372,6 @@ def changes end context 'when the model users the current ActiveModel::Dirty' do - before do DummyIndexingModelWithCallbacks.__send__ :include, Elasticsearch::Model::Indexing::InstanceMethods end @@ -412,12 +394,10 @@ def changes end describe '#index_document' do - before do expect(instance).to receive(:client).and_return(client) expect(instance).to receive(:as_indexed_json).and_return('JSON') expect(instance).to receive(:index_name).and_return('foo') - expect(instance).to receive(:document_type).twice.and_return('bar') expect(instance).to receive(:id).and_return('1') end @@ -430,9 +410,8 @@ def changes end context 'when no options are passed to the method' do - before do - expect(client).to receive(:index).with(index: 'foo', type: 'bar', id: '1', body: 'JSON').and_return(true) + expect(client).to receive(:index).with({ index: 'foo', id: '1', body: 'JSON' }).and_return(true) end it 'provides the method on an instance' do @@ -441,9 +420,8 @@ def changes end context 'when extra options are passed to the method' do - before do - expect(client).to receive(:index).with(index: 'foo', type: 'bar', id: '1', body: 'JSON', parent: 'A').and_return(true) + expect(client).to receive(:index).with({ index: 'foo', id: '1', body: 'JSON', parent: 'A' }).and_return(true) end it 'passes the extra options to the method call on the client' do @@ -453,11 +431,9 @@ def changes end describe '#delete_document' do - before do expect(instance).to receive(:client).and_return(client) expect(instance).to receive(:index_name).and_return('foo') - expect(instance).to receive(:document_type).twice.and_return('bar') expect(instance).to receive(:id).and_return('1') end @@ -470,9 +446,8 @@ def changes end context 'when no options are passed to the method' do - before do - expect(client).to receive(:delete).with(index: 'foo', type: 'bar', id: '1').and_return(true) + expect(client).to receive(:delete).with({ index: 'foo', id: '1' }).and_return(true) end it 'provides the method on an instance' do @@ -481,9 +456,8 @@ def changes end context 'when extra options are passed to the method' do - before do - expect(client).to receive(:delete).with(index: 'foo', type: 'bar', id: '1', parent: 'A').and_return(true) + expect(client).to receive(:delete).with({ index: 'foo', id: '1', parent: 'A' }).and_return(true) end it 'passes the extra options to the method call on the client' do @@ -493,7 +467,6 @@ def changes end describe '#update_document' do - let(:client) do double('client') end @@ -503,7 +476,6 @@ def changes end context 'when no changes are present' do - before do expect(instance).to receive(:index_document).and_return(true) expect(client).to receive(:update).never @@ -516,19 +488,16 @@ def changes end context 'when changes are present' do - before do allow(instance).to receive(:client).and_return(client) allow(instance).to receive(:index_name).and_return('foo') - allow(instance).to receive(:document_type).and_return('bar') allow(instance).to receive(:id).and_return('1') end context 'when the changes are included in the as_indexed_json representation' do - before do instance.instance_variable_set(:@__changed_model_attributes, { foo: 'bar' }) - expect(client).to receive(:update).with(index: 'foo', type: 'bar', id: '1', body: { doc: { foo: 'bar' } }).and_return(true) + expect(client).to receive(:update).with({ index: 'foo', id: '1', body: { doc: { foo: 'bar' } } }).and_return(true) end it 'updates the document' do @@ -537,14 +506,13 @@ def changes end context 'when the changes are not all included in the as_indexed_json representation' do - let(:instance) do DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new end before do instance.instance_variable_set(:@__changed_model_attributes, {'foo' => 'B', 'bar' => 'D' }) - expect(client).to receive(:update).with(index: 'foo', type: 'bar', id: '1', body: { doc: { foo: 'B' } }).and_return(true) + expect(client).to receive(:update).with({ index: 'foo', id: '1', body: { doc: { foo: 'B' } } }).and_return(true) end it 'updates the document' do @@ -553,7 +521,6 @@ def changes end context 'when none of the changes are included in the as_indexed_json representation' do - let(:instance) do DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new end @@ -568,7 +535,6 @@ def changes end context 'when there are partial updates' do - let(:instance) do DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new end @@ -576,7 +542,7 @@ def changes before do instance.instance_variable_set(:@__changed_model_attributes, { 'foo' => { 'bar' => 'BAR'} }) expect(instance).to receive(:as_indexed_json).and_return('foo' => 'BAR') - expect(client).to receive(:update).with(index: 'foo', type: 'bar', id: '1', body: { doc: { 'foo' => 'BAR' } }).and_return(true) + expect(client).to receive(:update).with({ index: 'foo', id: '1', body: { doc: { 'foo' => 'BAR' } } }).and_return(true) end it 'updates the document' do @@ -587,7 +553,6 @@ def changes end describe '#update_document_attributes' do - let(:client) do double('client') end @@ -597,19 +562,16 @@ def changes end context 'when changes are present' do - before do expect(instance).to receive(:client).and_return(client) expect(instance).to receive(:index_name).and_return('foo') - expect(instance).to receive(:document_type).twice.and_return('bar') expect(instance).to receive(:id).and_return('1') instance.instance_variable_set(:@__changed_model_attributes, { author: 'john' }) end context 'when no options are specified' do - before do - expect(client).to receive(:update).with(index: 'foo', type: 'bar', id: '1', body: { doc: { title: 'green' } }).and_return(true) + expect(client).to receive(:update).with({ index: 'foo', id: '1', body: { doc: { title: 'green' } } }).and_return(true) end it 'updates the document' do @@ -618,9 +580,8 @@ def changes end context 'when extra options are provided' do - before do - expect(client).to receive(:update).with(index: 'foo', type: 'bar', id: '1', body: { doc: { title: 'green' } }, refresh: true).and_return(true) + expect(client).to receive(:update).with({ index: 'foo', id: '1', body: { doc: { title: 'green' } }, refresh: true }).and_return(true) end it 'updates the document' do @@ -632,7 +593,6 @@ def changes end describe '#index_exists?' do - before do expect(DummyIndexingModel).to receive(:client).and_return(client) end @@ -649,7 +609,6 @@ def changes end context 'when the index does not exists' do - let(:client) do double('client', indices: double('indices', exists: false)) end @@ -661,7 +620,6 @@ def changes end describe '#delete_index!' do - before(:all) do class ::DummyIndexingModelForRecreate extend ActiveModel::Naming @@ -720,7 +678,6 @@ class ::DummyIndexingModelForRecreate end context 'when the force option is not provided' do - it 'raises an exception' do expect { DummyIndexingModelForRecreate.delete_index! @@ -729,7 +686,6 @@ class ::DummyIndexingModelForRecreate end context 'when the exception is not NotFound' do - let(:indices) do double('indices').tap do |ind| expect(ind).to receive(:delete).and_raise(Exception) @@ -745,7 +701,6 @@ class ::DummyIndexingModelForRecreate end context 'when an index name is provided in the options' do - before do expect(DummyIndexingModelForRecreate).to receive(:client).and_return(client) expect(indices).to receive(:delete).with(index: 'custom-foo') @@ -766,7 +721,6 @@ class ::DummyIndexingModelForRecreate end describe '#create_index' do - before(:all) do class ::DummyIndexingModelForCreate extend ActiveModel::Naming @@ -811,7 +765,7 @@ class ::DummyIndexingModelForCreate end before do - expect(indices).to receive(:create).with(index: 'foo', body: expected_body).and_return(true) + expect(indices).to receive(:create).with({ index: 'foo', body: expected_body }).and_return(true) end it 'creates the index' do @@ -827,7 +781,7 @@ class ::DummyIndexingModelForCreate end before do - expect(indices).to receive(:create).with(index: 'foobar', body: expected_body).and_return(true) + expect(indices).to receive(:create).with({ index: 'foobar', body: expected_body }).and_return(true) end it 'creates the index' do @@ -853,7 +807,6 @@ class ::DummyIndexingModelForCreate end context 'when creating the index raises an exception' do - before do expect(DummyIndexingModelForCreate).to receive(:client).and_return(client) expect(DummyIndexingModelForCreate).to receive(:delete_index!).and_return(true) @@ -869,11 +822,10 @@ class ::DummyIndexingModelForCreate end context 'when an index name is provided in the options' do - before do expect(DummyIndexingModelForCreate).to receive(:client).and_return(client).twice expect(indices).to receive(:exists).and_return(false) - expect(indices).to receive(:create).with(index: 'custom-foo', body: expected_body) + expect(indices).to receive(:create).with({ index: 'custom-foo', body: expected_body }) end let(:expected_body) do @@ -891,7 +843,6 @@ class ::DummyIndexingModelForCreate end describe '#refresh_index!' do - before(:all) do class ::DummyIndexingModelForRefresh extend ActiveModel::Naming From 4ac12367f4398695172fae036316ce8e9e118fa1 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 22 Jun 2023 09:24:26 +0100 Subject: [PATCH 546/582] Updates indexing spec Updates usage of client --- .../spec/elasticsearch/model/indexing_spec.rb | 52 ++++--------------- 1 file changed, 9 insertions(+), 43 deletions(-) diff --git a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb index d82570910..c8df409e5 100644 --- a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb @@ -125,7 +125,6 @@ class NotFound < Exception; end end context 'when the \'include_type_name\' option is specified' do - let(:mappings) do Elasticsearch::Model::Indexing::Mappings.new(include_type_name: true) end @@ -164,7 +163,6 @@ class NotFound < Exception; end end context 'when specific mappings are defined' do - let(:mappings) do Elasticsearch::Model::Indexing::Mappings.new(include_type_name: true) end @@ -608,7 +606,7 @@ def changes end end - context 'when the index does not exists' do + context 'when the index does not exist' do let(:client) do double('client', indices: double('indices', exists: false)) end @@ -634,19 +632,7 @@ class ::DummyIndexingModelForRecreate context 'when the index is not found' do let(:logger) { nil } - let(:transport) do - Elastic::Transport::Client.new(logger: logger) - end - - let(:client) do - double('client', indices: indices, transport: transport) - end - - let(:indices) do - double('indices').tap do |ind| - expect(ind).to receive(:delete).and_raise(NotFound) - end - end + let(:client) { Elasticsearch::Client.new(logger: logger) } before do expect(DummyIndexingModelForRecreate).to receive(:client).at_most(3).times.and_return(client) @@ -662,16 +648,12 @@ class ::DummyIndexingModelForRecreate Logger.new(STDOUT).tap { |l| l.level = Logger::DEBUG } end - let(:client) do - double('client', indices: indices, transport: transport) - end - it 'deletes the index without raising an exception' do expect(DummyIndexingModelForRecreate.delete_index!(force: true)).to be_nil end it 'logs the message that the index is not found' do - expect(logger).to receive(:debug) + expect(logger).to receive(:debug).at_least(:once) expect(DummyIndexingModelForRecreate.delete_index!(force: true)).to be_nil end end @@ -681,7 +663,7 @@ class ::DummyIndexingModelForRecreate it 'raises an exception' do expect { DummyIndexingModelForRecreate.delete_index! - }.to raise_exception(NotFound) + }.to raise_exception(Elastic::Transport::Transport::Errors::NotFound) end end @@ -750,14 +732,12 @@ class ::DummyIndexingModelForCreate end context 'when the index does not exist' do - before do expect(DummyIndexingModelForCreate).to receive(:client).and_return(client) expect(DummyIndexingModelForCreate).to receive(:index_exists?).and_return(false) end context 'when options are not provided' do - let(:expected_body) do { mappings: { properties: { foo: { analyzer: 'keyword', type: 'text' } } }, @@ -774,7 +754,6 @@ class ::DummyIndexingModelForCreate end context 'when options are provided' do - let(:expected_body) do { mappings: { foobar: { properties: { foo: { analyzer: 'bar' } } } }, settings: { index: { number_of_shards: 3 } } } @@ -795,7 +774,6 @@ class ::DummyIndexingModelForCreate end context 'when the index exists' do - before do expect(DummyIndexingModelForCreate).to receive(:index_exists?).and_return(true) expect(indices).to receive(:create).never @@ -864,15 +842,7 @@ class ::DummyIndexingModelForRefresh end let(:client) do - double('client', indices: indices, transport: transport) - end - - let(:transport) do - Elastic::Transport::Client.new(logger: nil) - end - - let(:indices) do - double('indices') + Elasticsearch::Client.new(logger: nil) end before do @@ -882,7 +852,7 @@ class ::DummyIndexingModelForRefresh context 'when the force option is true' do context 'when the operation raises a NotFound exception' do before do - expect(indices).to receive(:refresh).and_raise(NotFound) + expect(client).to receive_message_chain(:indices, :refresh).and_raise(NotFound) end it 'does not raise an exception' do @@ -895,11 +865,7 @@ class ::DummyIndexingModelForRefresh end let(:client) do - double('client', indices: indices, transport: transport) - end - - let(:transport) do - Elastic::Transport::Client.new(logger: logger) + Elasticsearch::Client.new(logger: logger) end it 'does not raise an exception' do @@ -915,7 +881,7 @@ class ::DummyIndexingModelForRefresh context 'when the operation raises another type of exception' do before do - expect(indices).to receive(:refresh).and_raise(Exception) + expect(client).to receive_message_chain(:indices, :refresh).and_raise(Exception) end it 'does not raise an exception' do @@ -928,7 +894,7 @@ class ::DummyIndexingModelForRefresh context 'when an index name is provided in the options' do before do - expect(indices).to receive(:refresh).with(index: 'custom-foo') + expect(client).to receive_message_chain(:indices, :refresh).with(index: 'custom-foo') end it 'uses the index name' do From aa0dfaae355d5fdacbd2db72a707a5e5aaaf4f97 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 22 Jun 2023 10:31:54 +0100 Subject: [PATCH 547/582] Updates module_spec --- elasticsearch-model/spec/elasticsearch/model/module_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/elasticsearch-model/spec/elasticsearch/model/module_spec.rb b/elasticsearch-model/spec/elasticsearch/model/module_spec.rb index ed7a58297..8808475ee 100644 --- a/elasticsearch-model/spec/elasticsearch/model/module_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/module_spec.rb @@ -65,7 +65,6 @@ def self.search(query, options={}) expect(DummyIncludingModel).to respond_to(:mapping) expect(DummyIncludingModel).to respond_to(:settings) expect(DummyIncludingModel).to respond_to(:index_name) - expect(DummyIncludingModel).to respond_to(:document_type) expect(DummyIncludingModel).to respond_to(:import) end From f9bcc23b8236bdc670332c5f9e68ce21fe31576a Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 22 Jun 2023 10:35:59 +0100 Subject: [PATCH 548/582] Updates mongoid spec --- .../spec/elasticsearch/model/adapters/mongoid_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb index 81a286c0d..88ffbd2a6 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/mongoid_spec.rb @@ -174,7 +174,7 @@ class DummyClassForMongoid; end context 'query criteria specified as a hash' do before do - expect(relation).to receive(:where).with(color: 'red').and_return(relation) + expect(relation).to receive(:where).with({ color: 'red' }).and_return(relation) end let(:query) do From 3ebd8da90b6052aa32dfe079a1e5a6dcb16464d4 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 22 Jun 2023 10:38:42 +0100 Subject: [PATCH 549/582] Updates adapters/active_record/serialization spec --- .../adapters/active_record/serialization_spec.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb index 3f01c348b..da9d3306a 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb @@ -41,9 +41,10 @@ context 'when a document is indexed' do let(:search_result) do - ArticleWithCustomSerialization.__elasticsearch__.client.get(index: 'article_with_custom_serializations', - type: '_doc', - id: '1') + ArticleWithCustomSerialization.__elasticsearch__.client.get( + index: 'article_with_custom_serializations', + id: '1' + ) end it 'applies the serialization when indexing' do @@ -54,7 +55,7 @@ context 'when a document is updated' do before do - article.update_attributes(title: 'UPDATED', status: 'yellow') + article.update(title: 'UPDATED', status: 'yellow') ArticleWithCustomSerialization.__elasticsearch__.refresh_index! end @@ -65,9 +66,10 @@ end let(:search_result) do - ArticleWithCustomSerialization.__elasticsearch__.client.get(index: 'article_with_custom_serializations', - type: '_doc', - id: article.id) + ArticleWithCustomSerialization.__elasticsearch__.client.get( + index: 'article_with_custom_serializations', + id: article.id + ) end it 'applies the serialization when updating' do From 5f88f9406d35ed07aaae5cf304c5a42b21bc014e Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 22 Jun 2023 10:46:04 +0100 Subject: [PATCH 550/582] Updates parent child specs --- .../model/adapters/active_record/parent_child_spec.rb | 2 +- .../spec/support/app/parent_and_child_searchable.rb | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb index 5147943ee..06790f613 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb @@ -38,7 +38,7 @@ add_index(:answers, :question_id) unless index_exists?(:answers, :question_id) clear_tables(Question) - ParentChildSearchable.create_index!(force: true, include_type_name: true) + ParentChildSearchable.create_index!(force: true) q_1 = Question.create!(title: 'First Question', author: 'John') q_2 = Question.create!(title: 'Second Question', author: 'Jody') diff --git a/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb b/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb index 91693479f..210e44076 100644 --- a/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb +++ b/elasticsearch-model/spec/support/app/parent_and_child_searchable.rb @@ -26,10 +26,12 @@ def create_index!(options={}) settings = Question.settings.to_hash.merge Answer.settings.to_hash mapping_properties = { join_field: { type: JOIN, relations: { Question::JOIN_TYPE => Answer::JOIN_TYPE } } } - - merged_properties = mapping_properties.merge(Question.mappings.to_hash[:doc][:properties]).merge( - Answer.mappings.to_hash[:doc][:properties]) - mappings = { doc: { properties: merged_properties }} + merged_properties = mapping_properties.merge( + Question.mappings.to_hash[:properties] + ).merge( + Answer.mappings.to_hash[:properties] + ) + mappings = { properties: merged_properties } client.indices.create({ index: INDEX_NAME, body: { From 2ef285abc06d3d04e60858fbf68dcae0386ba18f Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 22 Jun 2023 18:03:41 +0100 Subject: [PATCH 551/582] Updates elasticsearch-persistence repository spec --- .../spec/repository_spec.rb | 125 +++++------------- 1 file changed, 32 insertions(+), 93 deletions(-) diff --git a/elasticsearch-persistence/spec/repository_spec.rb b/elasticsearch-persistence/spec/repository_spec.rb index ef9f0575d..93fd19c16 100644 --- a/elasticsearch-persistence/spec/repository_spec.rb +++ b/elasticsearch-persistence/spec/repository_spec.rb @@ -18,9 +18,7 @@ require 'spec_helper' describe Elasticsearch::Persistence::Repository do - describe '#create' do - before(:all) do class RepositoryWithoutDSL include Elasticsearch::Persistence::Repository @@ -38,7 +36,6 @@ class RepositoryWithoutDSL end context 'when options are provided' do - let(:repository) do RepositoryWithoutDSL.create(document_type: 'note') end @@ -49,7 +46,6 @@ class RepositoryWithoutDSL end context 'when a block is passed' do - let(:repository) do RepositoryWithoutDSL.create(document_type: 'note') do mapping dynamic: 'strict' do @@ -59,11 +55,10 @@ class RepositoryWithoutDSL end it 'executes the block on the instance' do - expect(repository.mapping.to_hash).to eq(note: { dynamic: 'strict', properties: { foo: { type: 'text' } } }) + expect(repository.mapping.to_hash).to eq({ dynamic: 'strict', properties: { foo: { type: 'text' } } }) end context 'when options are provided in the args and set in the block' do - let(:repository) do RepositoryWithoutDSL.create(mapping: double('mapping', to_hash: {}), document_type: 'note') do mapping dynamic: 'strict' do @@ -80,7 +75,6 @@ class RepositoryWithoutDSL end describe '#initialize' do - before(:all) do class RepositoryWithoutDSL include Elasticsearch::Persistence::Repository @@ -98,7 +92,6 @@ class RepositoryWithoutDSL end context 'when options are not provided' do - let(:repository) do RepositoryWithoutDSL.new end @@ -118,7 +111,6 @@ class RepositoryWithoutDSL end context 'when options are provided' do - let(:client) do Elasticsearch::Client.new end @@ -146,13 +138,11 @@ class RepositoryWithoutDSL end context 'when the DSL module is included' do - before(:all) do class RepositoryWithDSL include Elasticsearch::Persistence::Repository include Elasticsearch::Persistence::Repository::DSL - document_type 'note' index_name 'notes_repo' klass Hash client DEFAULT_CLIENT @@ -179,7 +169,6 @@ class RepositoryWithDSL end context '#client' do - it 'allows the value to be set only once on the class' do RepositoryWithDSL.client(double('client', class: 'other_client')) expect(RepositoryWithDSL.client).to be(DEFAULT_CLIENT) @@ -199,7 +188,6 @@ class RepositoryWithDSL end context '#klass' do - it 'allows the value to be set only once on the class' do RepositoryWithDSL.klass(Array) expect(RepositoryWithDSL.klass).to eq(Hash) @@ -229,28 +217,7 @@ class RepositoryWithDSL end end - context '#document_type' do - - it 'allows the value to be set only once on the class' do - RepositoryWithDSL.document_type('other_note') - expect(RepositoryWithDSL.document_type).to eq('note') - end - - it 'sets the value at the class level' do - expect(RepositoryWithDSL.document_type).to eq('note') - end - - it 'sets the value as the default at the instance level' do - expect(RepositoryWithDSL.new.document_type).to eq('note') - end - - it 'allows the value to be overridden with options on the instance' do - expect(RepositoryWithDSL.new(document_type: 'other_note').document_type).to eq('other_note') - end - end - context '#index_name' do - it 'allows the value to be set only once on the class' do RepositoryWithDSL.index_name('other_name') expect(RepositoryWithDSL.index_name).to eq('notes_repo') @@ -270,16 +237,14 @@ class RepositoryWithDSL end describe '#create_index!' do - context 'when the method is called on an instance' do - let(:repository) do RepositoryWithDSL.new end before do begin; repository.delete_index!; rescue; end - repository.create_index!(include_type_name: true) + repository.create_index! end it 'creates the index' do @@ -288,7 +253,6 @@ class RepositoryWithDSL end context 'when the method is called on the class' do - it 'raises a NotImplementedError' do expect { RepositoryWithDSL.create_index! @@ -298,9 +262,7 @@ class RepositoryWithDSL end describe '#delete_index!' do - context 'when the method is called on an instance' do - let(:repository) do RepositoryWithDSL.new end @@ -316,7 +278,6 @@ class RepositoryWithDSL end context 'when the method is called on the class' do - it 'raises a NotImplementedError' do expect { RepositoryWithDSL.delete_index! @@ -326,15 +287,13 @@ class RepositoryWithDSL end describe '#refresh_index!' do - context 'when the method is called on an instance' do - let(:repository) do RepositoryWithDSL.new end before do - repository.create_index!(include_type_name: true) + repository.create_index! end it 'refreshes the index' do @@ -343,7 +302,6 @@ class RepositoryWithDSL end context 'when the method is called on the class' do - it 'raises a NotImplementedError' do expect { RepositoryWithDSL.refresh_index! @@ -353,15 +311,13 @@ class RepositoryWithDSL end describe '#index_exists?' do - context 'when the method is called on an instance' do - let(:repository) do RepositoryWithDSL.new end before do - repository.create_index!(include_type_name: true) + repository.create_index! end it 'determines if the index exists' do @@ -377,7 +333,6 @@ class RepositoryWithDSL end context 'when the method is called on the class' do - it 'raises a NotImplementedError' do expect { RepositoryWithDSL.index_exists? @@ -387,13 +342,15 @@ class RepositoryWithDSL end describe '#mapping' do - let(:expected_mapping) do - { note: { dynamic: 'strict', - properties: { foo: { type: 'object', - properties: { bar: { type: 'text' } } }, - baz: { type: 'text' } } - } + { + dynamic: 'strict', + properties: { + foo: { + type: 'object', + properties: { bar: { type: 'text' } } }, + baz: { type: 'text' } + } } end @@ -410,7 +367,6 @@ class RepositoryWithDSL end context 'when the instance has a different document type' do - let(:expected_mapping) do { other_note: { dynamic: 'strict', properties: { foo: { type: 'object', @@ -427,7 +383,6 @@ class RepositoryWithDSL end describe '#settings' do - it 'sets the value at the class level' do expect(RepositoryWithDSL.settings.to_hash).to eq(number_of_shards: 1, number_of_replicas: 0) end @@ -443,7 +398,6 @@ class RepositoryWithDSL end context 'when the DSL module is not included' do - before(:all) do class RepositoryWithoutDSL include Elasticsearch::Persistence::Repository @@ -546,26 +500,6 @@ class RepositoryWithoutDSL let(:repository) do RepositoryWithoutDSL.new(client: DEFAULT_CLIENT, document_type: 'mytype') end - - context 'when the server is version >= 7.0', if: server_version > '7.0' do - - context 'when the include_type_name option is specified' do - - it 'creates an index' do - repository.create_index!(include_type_name: true) - expect(repository.index_exists?).to eq(true) - end - end - - context 'when the include_type_name option is not specified' do - - it 'raises an error' do - expect { - repository.create_index! - }.to raise_exception(Elastic::Transport::Transport::Errors::BadRequest) - end - end - end end end @@ -582,7 +516,7 @@ class RepositoryWithoutDSL end it 'deletes an index' do - repository.create_index!(include_type_name: true) + repository.create_index! repository.delete_index! expect(repository.index_exists?).to eq(false) end @@ -605,7 +539,7 @@ class RepositoryWithoutDSL end it 'refreshes an index' do - repository.create_index!(include_type_name: true) + repository.create_index! expect(repository.refresh_index!['_shards']).to be_a(Hash) end end @@ -627,7 +561,7 @@ class RepositoryWithoutDSL end it 'returns whether the index exists' do - repository.create_index!(include_type_name: true) + repository.create_index! expect(repository.index_exists?).to be(true) end end @@ -651,10 +585,14 @@ class RepositoryWithoutDSL context 'when a block is passed to the create method' do let(:expected_mapping) do - { note: { dynamic: 'strict', - properties: { foo: { type: 'object', - properties: { bar: { type: 'text' } } }, - baz: { type: 'text' } } + { + dynamic: 'strict', + properties: { + foo: { + type: 'object', + properties: { bar: { type: 'text' } } + }, + baz: { type: 'text' } } } end @@ -695,7 +633,6 @@ class RepositoryWithoutDSL end describe '#settings' do - it 'does not define the method at the class level' do expect { RepositoryWithoutDSL.settings @@ -711,7 +648,6 @@ class RepositoryWithoutDSL end context 'when a block is passed to the #create method' do - let(:repository) do RepositoryWithoutDSL.create(document_type: 'note') do settings number_of_shards: 1, number_of_replicas: 0 @@ -723,13 +659,16 @@ class RepositoryWithoutDSL end context 'when a mapping is set in the block as well' do - let(:expected_mapping) do - { note: { dynamic: 'strict', - properties: { foo: { type: 'object', - properties: { bar: { type: 'text' } } }, - baz: { type: 'text' } } - } + { + dynamic: 'strict', + properties: { + foo: { + type: 'object', + properties: { bar: { type: 'text' } } + }, + baz: { type: 'text' } + } } end From 34a3aa843cdee9a9fae7512522a54a7ce3d1436d Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Fri, 23 Jun 2023 17:36:12 +0100 Subject: [PATCH 552/582] Removes _type, cleans whitespace in elasticsearch-model --- elasticsearch-model/README.md | 14 ++++++-------- elasticsearch-model/lib/elasticsearch/model.rb | 2 -- .../elasticsearch/model/adapters/active_record.rb | 4 ---- .../lib/elasticsearch/model/indexing.rb | 2 -- 4 files changed, 6 insertions(+), 16 deletions(-) diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 655485495..12add7e01 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -269,8 +269,8 @@ It is possible to search across multiple models with the module method: ```ruby Elasticsearch::Model.search('fox', [Article, Comment]).results.to_a.map(&:to_hash) # => [ -# {"_index"=>"articles", "_type"=>"article", "_id"=>"1", "_score"=>0.35136628, "_source"=>...}, -# {"_index"=>"comments", "_type"=>"comment", "_id"=>"1", "_score"=>0.35136628, "_source"=>...} +# {"_index"=>"articles", "_id"=>"1", "_score"=>0.35136628, "_source"=>...}, +# {"_index"=>"comments", "_id"=>"1", "_score"=>0.35136628, "_source"=>...} # ] Elasticsearch::Model.search('fox', [Article, Comment]).records.to_a @@ -415,13 +415,11 @@ Article.__elasticsearch__.create_index! force: true Article.__elasticsearch__.refresh_index! ``` -By default, index name and document type will be inferred from your class name, -you can set it explicitly, however: +By default, index name will be inferred from your class name, you can set it explicitly, however: ```ruby class Article index_name "articles-#{Rails.env}" - document_type "post" end ``` @@ -529,10 +527,10 @@ class Indexer case operation.to_s when /index/ record = Article.find(record_id) - Client.index index: 'articles', type: 'article', id: record.id, body: record.__elasticsearch__.as_indexed_json + Client.index index: 'articles', id: record.id, body: record.__elasticsearch__.as_indexed_json when /delete/ begin - Client.delete index: 'articles', type: 'article', id: record_id + Client.delete index: 'articles', id: record_id rescue Elastic::Transport::Transport::Errors::NotFound logger.debug "Article not found, ID: #{record_id}" end @@ -555,7 +553,7 @@ You'll see the job being processed in the console where you started the _Sidekiq Indexer JID-eb7e2daf389a1e5e83697128 DEBUG: ["index", "ID: 7"] Indexer JID-eb7e2daf389a1e5e83697128 INFO: PUT http://localhost:9200/articles/article/1 [status:200, request:0.004s, query:n/a] Indexer JID-eb7e2daf389a1e5e83697128 DEBUG: > {"id":1,"title":"Updated", ...} -Indexer JID-eb7e2daf389a1e5e83697128 DEBUG: < {"ok":true,"_index":"articles","_type":"article","_id":"1","_version":6} +Indexer JID-eb7e2daf389a1e5e83697128 DEBUG: < {"ok":true,"_index":"articles","_id":"1","_version":6} Indexer JID-eb7e2daf389a1e5e83697128 INFO: done: 0.006 sec ``` diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index 7efc5caa9..5b28726cb 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -62,7 +62,6 @@ end module Elasticsearch - # Elasticsearch integration for Ruby models # ========================================= # @@ -108,7 +107,6 @@ module Model def self.included(base) base.class_eval do include Elasticsearch::Model::Proxy - # Delegate common methods to the `__elasticsearch__` ClassMethodsProxy, unless they are defined already class << self METHODS.each do |method| diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb index 64e0277d8..20dee0505 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb @@ -18,11 +18,9 @@ module Elasticsearch module Model module Adapter - # An adapter for ActiveRecord-based models # module ActiveRecord - Adapter.register self, lambda { |klass| !!defined?(::ActiveRecord::Base) && klass.respond_to?(:ancestors) && klass.ancestors.include?(::ActiveRecord::Base) } @@ -71,7 +69,6 @@ def load end module Callbacks - # Handle index updates (creating, updating or deleting documents) # when the model changes, by hooking into the lifecycle # @@ -87,7 +84,6 @@ def self.included(base) end module Importing - # Fetch batches of records from the database (used by the import method) # # diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index f270a26b4..30fe0b8ff 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -17,7 +17,6 @@ module Elasticsearch module Model - # Provides the necessary support to set up index options (mappings, settings) # as well as instance methods to create, update or delete documents in the index. # @@ -29,7 +28,6 @@ module Model # @see InstanceMethods#delete_document # module Indexing - # Wraps the [index settings](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) # class Settings From e800f0f0fd70679dfdd0eaca750f5616f43a9ce8 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Fri, 23 Jun 2023 17:38:37 +0100 Subject: [PATCH 553/582] elasticsearch-persistence: Updates to elasticsearch 8, removes type, whitespace cleanup --- .../elasticsearch-persistence.gemspec | 2 +- .../examples/notes/application.rb | 1 - .../elasticsearch/persistence/repository.rb | 18 +- .../persistence/repository/dsl.rb | 14 -- .../persistence/repository/find.rb | 3 - .../persistence/repository/search.rb | 6 +- .../persistence/repository/store.rb | 15 +- .../spec/repository/find_spec.rb | 105 ++-------- .../spec/repository/response/results_spec.rb | 35 ++-- .../spec/repository/search_spec.rb | 180 +++--------------- .../spec/repository/store_spec.rb | 48 +---- .../spec/repository_spec.rb | 70 ++----- elasticsearch-persistence/spec/spec_helper.rb | 2 +- 13 files changed, 92 insertions(+), 407 deletions(-) diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index c8f5d0fa2..b88c0a317 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -43,7 +43,7 @@ Gem::Specification.new do |s| s.add_dependency 'activemodel', '> 4' s.add_dependency 'activesupport', '> 4' - s.add_dependency 'elasticsearch', '~> 7' + s.add_dependency 'elasticsearch', '~> 8' s.add_dependency 'elasticsearch-model', '7.2.1' s.add_dependency 'hashie' diff --git a/elasticsearch-persistence/examples/notes/application.rb b/elasticsearch-persistence/examples/notes/application.rb index 0f1724476..9b7bc88ae 100644 --- a/elasticsearch-persistence/examples/notes/application.rb +++ b/elasticsearch-persistence/examples/notes/application.rb @@ -75,7 +75,6 @@ class NoteRepository client Elasticsearch::Client.new url: ENV['ELASTICSEARCH_URL'], log: true index_name :notes - document_type :note mapping do indexes :text, analyzer: 'snowball' diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb index 607de1769..e5fb23c13 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb @@ -58,7 +58,6 @@ module ClassMethods # @param [ Proc ] block A block to evaluate on the new repository instance. # # @option options [ Symbol, String ] :index_name The name of the index. - # @option options [ Symbol, String ] :document_type The type of documents persisted in this repository. # @option options [ Symbol, String ] :client The client used to handle requests to and from Elasticsearch. # @option options [ Symbol, String ] :klass The class used to instantiate an object when documents are # deserialized. The default is nil, in which case the raw document will be returned as a Hash. @@ -95,7 +94,6 @@ def create(options = {}, &block) # @param [ Hash ] options The options to use. # # @option options [ Symbol, String ] :index_name The name of the index. - # @option options [ Symbol, String ] :document_type The type of documents persisted in this repository. # @option options [ Symbol, String ] :client The client used to handle requests to and from Elasticsearch. # @option options [ Symbol, String ] :klass The class used to instantiate an object when documents are # deserialized. The default is nil, in which case the raw document will be returned as a Hash. @@ -121,19 +119,6 @@ def client Elasticsearch::Client.new end - # Get the document type used by the repository object. - # - # @example - # repository.document_type - # - # @return [ String, Symbol ] The repository's document type. - # - # @since 6.0.0 - def document_type - @document_type ||= @options[:document_type] || - __get_class_value(:document_type) - end - # Get the index name used by the repository. # # @example @@ -180,7 +165,6 @@ def klass def mapping(*args) @memoized_mapping ||= @options[:mapping] || (begin if _mapping = __get_class_value(:mapping) - _mapping.instance_variable_set(:@type, document_type) _mapping end end) || (super && @mapping) @@ -229,7 +213,7 @@ def index_exists?(*args) # # @since 6.0.0 def inspect - "#<#{self.class}:0x#{object_id} index_name=#{index_name} document_type=#{document_type} klass=#{klass}>" + "#<#{self.class}:0x#{object_id} index_name=#{index_name} klass=#{klass}>" end private diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb index de2848071..cab567d1d 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/dsl.rb @@ -23,7 +23,6 @@ module Repository # # @since 6.0.0 module DSL - def self.included(base) base.send(:extend, Elasticsearch::Model::Indexing::ClassMethods) base.send(:extend, ClassMethods) @@ -34,19 +33,6 @@ def self.included(base) # # @since 6.0.0 module ClassMethods - - # Get or set the class-level document type setting. - # - # @example - # MyRepository.document_type - # - # @return [ String, Symbol ] _type The repository's document type. - # - # @since 6.0.0 - def document_type(_type = nil) - @document_type ||= _type - end - # Get or set the class-level index name setting. # # @example diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb index fad2593e8..281486a4a 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/find.rb @@ -64,7 +64,6 @@ def find(*args) # def exists?(id, options={}) request = { index: index_name, id: id } - request[:type] = document_type if document_type client.exists(request.merge(options)) end @@ -84,7 +83,6 @@ def exists?(id, options={}) # def __find_one(id, options={}) request = { index: index_name, id: id } - request[:type] = document_type if document_type document = client.get(request.merge(options)) deserialize(document) rescue Elastic::Transport::Transport::Errors::NotFound => e @@ -95,7 +93,6 @@ def __find_one(id, options={}) # def __find_many(ids, options={}) request = { index: index_name, body: { ids: ids } } - request[:type] = document_type if document_type documents = client.mget(request.merge(options)) documents[DOCS].map do |document| deserialize(document) if document[FOUND] diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb index 2aa7db725..db7788682 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/search.rb @@ -60,8 +60,7 @@ module Search # @return [Elasticsearch::Persistence::Repository::Response::Results] # def search(query_or_definition, options={}) - request = { index: index_name, - type: document_type } + request = { index: index_name } if query_or_definition.respond_to?(:to_hash) request[:body] = query_or_definition.to_hash elsif query_or_definition.is_a?(String) @@ -98,8 +97,7 @@ def search(query_or_definition, options={}) # def count(query_or_definition=nil, options={}) query_or_definition ||= { query: { match_all: {} } } - request = { index: index_name, - type: document_type } + request = { index: index_name } if query_or_definition.respond_to?(:to_hash) request[:body] = query_or_definition.to_hash diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb index a8eeb1866..0986d5540 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/repository/store.rb @@ -27,7 +27,7 @@ module Store # # @example # repository.save(myobject) - # => {"_index"=>"...", "_type"=>"...", "_id"=>"...", "_version"=>1, "created"=>true} + # => {"_index"=>"...", "_id"=>"...", "_version"=>1, "created"=>true} # # @param [ Object ] document The document to save into Elasticsearch. # @param [ Hash ] options The save request options. @@ -40,7 +40,6 @@ def save(document, options={}) request = { index: index_name, id: id, body: serialized } - request[:type] = document_type if document_type client.index(request.merge(options)) end @@ -49,12 +48,12 @@ def save(document, options={}) # @example Update the document with partial data # # repository.update id: 1, title: 'UPDATED', tags: [] - # # => {"_index"=>"...", "_type"=>"...", "_id"=>"1", "_version"=>2} + # # => {"_index"=>"...", "_id"=>"1", "_version"=>2} # # @example Update the document with a script # # repository.update 1, script: 'ctx._source.views += 1' - # # => {"_index"=>"...", "_type"=>"...", "_id"=>"1", "_version"=>3} + # # => {"_index"=>"...", "_id"=>"1", "_version"=>3} # # @param [ Object ] document_or_id The document to update or the id of the document to update. # @param [ Hash ] options The update request options. @@ -65,7 +64,6 @@ def update(document_or_id, options = {}) if document_or_id.is_a?(String) || document_or_id.is_a?(Integer) id = document_or_id body = options - type = document_type else document = serialize(document_or_id) id = __extract_id_from_document(document) @@ -74,9 +72,8 @@ def update(document_or_id, options = {}) else body = { doc: document }.merge(options) end - type = document.delete(:type) || document_type end - client.update(index: index_name, id: id, type: type, body: body) + client.update(index: index_name, id: id, body: body) end # Remove the serialized object or document with specified ID from Elasticsearch @@ -84,7 +81,7 @@ def update(document_or_id, options = {}) # @example Remove the document with ID 1 # # repository.delete(1) - # # => {"_index"=>"...", "_type"=>"...", "_id"=>"1", "_version"=>4} + # # => {"_index"=>"...", "_id"=>"1", "_version"=>4} # # @param [ Object ] document_or_id The document to delete or the id of the document to delete. # @param [ Hash ] options The delete request options. @@ -98,7 +95,7 @@ def delete(document_or_id, options = {}) serialized = serialize(document_or_id) id = __get_id_from_document(serialized) end - client.delete({ index: index_name, type: document_type, id: id }.merge(options)) + client.delete({ index: index_name, id: id }.merge(options)) end end end diff --git a/elasticsearch-persistence/spec/repository/find_spec.rb b/elasticsearch-persistence/spec/repository/find_spec.rb index 083ee4c25..22cc5af5e 100644 --- a/elasticsearch-persistence/spec/repository/find_spec.rb +++ b/elasticsearch-persistence/spec/repository/find_spec.rb @@ -18,7 +18,6 @@ require 'spec_helper' describe Elasticsearch::Persistence::Repository::Find do - after do begin; repository.delete_index!; rescue; end end @@ -28,9 +27,7 @@ end describe '#exists?' do - context 'when the document exists' do - let(:id) do repository.save(a: 1)['_id'] end @@ -41,30 +38,15 @@ end context 'when the document does not exist' do - it 'returns false' do expect(repository.exists?('1')).to be(false) end end - - context 'when options are provided' do - - let(:id) do - repository.save(a: 1)['_id'] - end - - it 'applies the options' do - expect(repository.exists?(id, type: 'other_type')).to be(false) - end - end end describe '#find' do - context 'when options are not provided' do - context 'when a single id is provided' do - let!(:id) do repository.save(a: 1)['_id'] end @@ -75,7 +57,6 @@ end context 'when an array of ids is provided' do - let!(:ids) do 3.times.collect do |i| repository.save(a: i)['_id'] @@ -83,28 +64,30 @@ end it 'retrieves the documents' do - expect(repository.find(ids)).to eq([{ 'a' =>0 }, - { 'a' => 1 }, - { 'a' => 2 }]) + expect(repository.find(ids)).to eq([ + { 'a' =>0 }, + { 'a' => 1 }, + { 'a' => 2 } + ]) end context 'when some documents are found and some are not' do - before do ids[1] = 22 ids end it 'returns nil in the result list for the documents not found' do - expect(repository.find(ids)).to eq([{ 'a' =>0 }, + expect(repository.find(ids)).to eq([ + { 'a' =>0 }, nil, - { 'a' => 2 }]) + { 'a' => 2 } + ]) end end end context 'when multiple ids are provided' do - let!(:ids) do 3.times.collect do |i| repository.save(a: i)['_id'] @@ -112,14 +95,15 @@ end it 'retrieves the documents' do - expect(repository.find(*ids)).to eq([{ 'a' =>0 }, - { 'a' => 1 }, - { 'a' => 2 }]) + expect(repository.find(*ids)).to eq([ + { 'a' =>0 }, + { 'a' => 1 }, + { 'a' => 2 } + ]) end end context 'when the document cannot be found' do - before do begin; repository.create_index!; rescue; end end @@ -131,66 +115,5 @@ end end end - - context 'when options are provided' do - - context 'when a single id is passed' do - - let!(:id) do - repository.save(a: 1)['_id'] - end - - it 'applies the options' do - expect { - repository.find(id, type: 'none') - }.to raise_exception(Elasticsearch::Persistence::Repository::DocumentNotFound) - end - end - - context 'when an array of ids is passed' do - - let!(:ids) do - 3.times.collect do |i| - repository.save(a: i)['_id'] - end - end - - it 'applies the options' do - expect(repository.find(ids, type: 'none')).to eq([nil, nil, nil]) - end - end - - context 'when multiple ids are passed' do - - let!(:ids) do - 3.times.collect do |i| - repository.save(a: i)['_id'] - end - end - - it 'applies the options' do - expect(repository.find(*ids, type: 'none')).to eq([nil, nil, nil]) - end - end - end - - context 'when a document_type is defined on the class' do - - let(:repository) do - MyTestRepository.new(document_type:'other_type', client: DEFAULT_CLIENT, index_name: 'test') - end - - let!(:ids) do - 3.times.collect do |i| - repository.save(a: i)['_id'] - end - end - - it 'uses the document type in the query' do - expect(repository.find(ids)).to eq([{ 'a' =>0 }, - { 'a' => 1 }, - { 'a' => 2 }]) - end - end end end diff --git a/elasticsearch-persistence/spec/repository/response/results_spec.rb b/elasticsearch-persistence/spec/repository/response/results_spec.rb index 9d98cfc79..c4ca44683 100644 --- a/elasticsearch-persistence/spec/repository/response/results_spec.rb +++ b/elasticsearch-persistence/spec/repository/response/results_spec.rb @@ -43,23 +43,24 @@ def deserialize(document) { "took" => 2, "timed_out" => false, "_shards" => {"total" => 5, "successful" => 5, "failed" => 0}, - "hits" => - { "total" => 2, - "max_score" => 0.19, - "hits" => - [{"_index" => "my_index", - "_type" => "note", - "_id" => "1", - "_score" => 0.19, - "_source" => {"id" => 1, "title" => "Test 1"}}, - - {"_index" => "my_index", - "_type" => "note", - "_id" => "2", - "_score" => 0.19, - "_source" => {"id" => 2, "title" => "Test 2"}} - ] - } + "hits" => { + "total" => 2, + "max_score" => 0.19, + "hits" => [ + { + "_index" => "my_index", + "_id" => "1", + "_score" => 0.19, + "_source" => {"id" => 1, "title" => "Test 1"} + }, + { + "_index" => "my_index", + "_id" => "2", + "_score" => 0.19, + "_source" => {"id" => 2, "title" => "Test 2"} + } + ] + } } end diff --git a/elasticsearch-persistence/spec/repository/search_spec.rb b/elasticsearch-persistence/spec/repository/search_spec.rb index 8e94ecc1d..d2e323791 100644 --- a/elasticsearch-persistence/spec/repository/search_spec.rb +++ b/elasticsearch-persistence/spec/repository/search_spec.rb @@ -18,180 +18,54 @@ require 'spec_helper' describe Elasticsearch::Persistence::Repository::Search do - after do begin; repository.delete_index!; rescue; end end - describe '#search' do + let(:repository) do + DEFAULT_REPOSITORY + end - let(:repository) do - DEFAULT_REPOSITORY + describe '#search' do + before do + repository.save({ name: 'user' }, refresh: true) end - context 'when the repository does not have a type set' do - - before do - repository.save({ name: 'user' }, refresh: true) - end - - context 'when a query definition is provided as a hash' do - - it 'uses the default document type' do - expect(repository.search({ query: { match: { name: 'user' } } }).first).to eq('name' => 'user') - end - end - - context 'when a query definition is provided as a string' do - - it 'uses the default document type' do - expect(repository.search('user').first).to eq('name' => 'user') - end - end - - context 'when the query definition is neither a String nor a Hash' do - - it 'raises an ArgumentError' do - expect { - repository.search(1) - }.to raise_exception(ArgumentError) - end - end - - context 'when options are provided' do - - context 'when a query definition is provided as a hash' do - - it 'uses the default document type' do - expect(repository.search({ query: { match: { name: 'user' } } }, type: 'other').first).to be_nil - end - end - - context 'when a query definition is provided as a string' do - - it 'uses the default document type' do - expect(repository.search('user', type: 'other').first).to be_nil - end - end - - context 'when the query definition is neither a String nor a Hash' do - - it 'raises an ArgumentError' do - expect { - repository.search(1) - }.to raise_exception(ArgumentError) - end - end + context 'when a query definition is provided as a hash' do + it 'searches' do + expect(repository.search({ query: { match: { name: 'user' } } }).first).to eq('name' => 'user') end end - context 'when the repository does have a type set' do - - let(:repository) do - MyTestRepository.new(document_type: 'other_note') - end - - before do - repository.save({ name: 'user' }, refresh: true) + context 'when a query definition is provided as a string' do + it 'searches' do + expect(repository.search('user').first).to eq('name' => 'user') end + end - context 'when options are provided' do - - context 'when a query definition is provided as a hash' do - - it 'uses the options' do - expect(repository.search({ query: { match: { name: 'user' } } }, type: 'other').first).to be_nil - end - end - - context 'when a query definition is provided as a string' do - - it 'uses the options' do - expect(repository.search('user', type: 'other').first).to be_nil - end - end - - context 'when the query definition is neither a String nor a Hash' do - - it 'raises an ArgumentError' do - expect { - repository.search(1) - }.to raise_exception(ArgumentError) - end - end + context 'when the query definition is neither a String nor a Hash' do + it 'raises an ArgumentError' do + expect { + repository.search(1) + }.to raise_exception(ArgumentError) end end end describe '#count' do - - context 'when the repository does not have a type set' do - - let(:repository) do - DEFAULT_REPOSITORY - end - - before do - repository.save({ name: 'user' }, refresh: true) - end - - context 'when a query definition is provided as a hash' do - - it 'uses the default document type' do - expect(repository.count({ query: { match: { name: 'user' } } })).to eq(1) - end - end - - context 'when a query definition is provided as a string' do - - it 'uses the default document type' do - expect(repository.count('user')).to eq(1) - end - end - - context 'when options are provided' do - - context 'when a query definition is provided as a hash' do - - it 'uses the options' do - expect(repository.count({ query: { match: { name: 'user' } } }, type: 'other')).to eq(0) - end - end - - context 'when a query definition is provided as a string' do - - it 'uses the options' do - expect(repository.count('user', type: 'other')).to eq(0) - end - end - end + before do + repository.save({ name: 'usuario' }, refresh: true) end - context 'when the repository does have a type set' do - - let(:repository) do - MyTestRepository.new(document_type: 'other_note') + context 'when a query definition is provided as a hash' do + it 'uses the default document type' do + expect(repository.count({ query: { match: { name: 'usuario' } } })).to eq(1) end + end - before do - repository.save({ name: 'user' }, refresh: true) - end - - context 'when options are provided' do - - context 'when a query definition is provided as a hash' do - - it 'uses the options' do - expect(repository.count({ query: { match: { name: 'user' } } }, type: 'other')).to eq(0) - end - end - - context 'when a query definition is provided as a string' do - - it 'uses the options' do - expect(repository.count('user', type: 'other')).to eq(0) - end - end + context 'when a query definition is provided as a string' do + it 'uses the default document type' do + expect(repository.count('usuario')).to eq(1) end end end diff --git a/elasticsearch-persistence/spec/repository/store_spec.rb b/elasticsearch-persistence/spec/repository/store_spec.rb index 82e1d3b01..5be0b5df8 100644 --- a/elasticsearch-persistence/spec/repository/store_spec.rb +++ b/elasticsearch-persistence/spec/repository/store_spec.rb @@ -18,7 +18,6 @@ require 'spec_helper' describe Elasticsearch::Persistence::Repository::Store do - let(:repository) do DEFAULT_REPOSITORY end @@ -28,7 +27,6 @@ end describe '#save' do - let(:document) do { a: 1 } end @@ -42,7 +40,6 @@ end context 'when the repository defines a custom serialize method' do - before do class OtherNoteRepository include Elasticsearch::Persistence::Repository @@ -70,24 +67,9 @@ def serialize(document) expect(repository.find(response['_id'])).to eq('b' => 1) end end - - context 'when options are provided' do - - let!(:response) do - repository.save(document) - end - - it 'saves the document using the options' do - expect { - repository.find(response['_id']) - }.to raise_exception(Elasticsearch::Persistence::Repository::DocumentNotFound) - expect(repository.find(response['_id'])).to eq('a' => 1) - end - end end describe '#update' do - before(:all) do class Note def to_hash @@ -103,15 +85,12 @@ def to_hash end context 'when the document exists' do - let!(:id) do repository.save(Note.new)['_id'] end context 'when an id is provided' do - context 'when a doc is specified in the options' do - before do repository.update(id, doc: { text: 'testing_2' }) end @@ -122,7 +101,6 @@ def to_hash end context 'when a script is specified in the options' do - before do repository.update(id, script: { inline: 'ctx._source.views += 1' }) end @@ -133,7 +111,6 @@ def to_hash end context 'when params are specified in the options' do - before do repository.update(id, script: { inline: 'ctx._source.views += params.count', params: { count: 2 } }) @@ -145,7 +122,6 @@ def to_hash end context 'when upsert is specified in the options' do - before do repository.update(id, script: { inline: 'ctx._source.views += 1' }, upsert: { text: 'testing_2' }) @@ -157,7 +133,6 @@ def to_hash end context 'when doc_as_upsert is specified in the options' do - before do repository.update(id, doc: { text: 'testing_2' }, doc_as_upsert: true) @@ -170,9 +145,7 @@ def to_hash end context 'when a document is provided as the query criteria' do - context 'when no options are provided' do - before do repository.update(id: id, text: 'testing_2') end @@ -183,9 +156,7 @@ def to_hash end context 'when options are provided' do - context 'when a doc is specified in the options' do - before do repository.update({ id: id, text: 'testing' }, doc: { text: 'testing_2' }) end @@ -196,10 +167,11 @@ def to_hash end context 'when a script is specified in the options' do - before do - repository.update({ id: id, text: 'testing' }, - script: { inline: 'ctx._source.views += 1' }) + repository.update( + { id: id, text: 'testing' }, + script: { inline: 'ctx._source.views += 1' } + ) end it 'updates using the id and script from the options' do @@ -208,7 +180,6 @@ def to_hash end context 'when params are specified in the options' do - before do repository.update({ id: id, text: 'testing' }, script: { inline: 'ctx._source.views += params.count', @@ -221,7 +192,6 @@ def to_hash end context 'when upsert is specified in the options' do - before do repository.update({ id: id, text: 'testing_2' }, doc_as_upsert: true) @@ -236,9 +206,7 @@ def to_hash end context 'when the document does not exist' do - context 'when an id is provided 'do - it 'raises an exception' do expect { repository.update(1, doc: { text: 'testing_2' }) @@ -246,7 +214,6 @@ def to_hash end context 'when upsert is provided' do - before do repository.update(1, doc: { text: 'testing' }, doc_as_upsert: true) end @@ -258,7 +225,6 @@ def to_hash end context 'when a document is provided' do - it 'raises an exception' do expect { repository.update(id: 1, text: 'testing_2') @@ -266,7 +232,6 @@ def to_hash end context 'when upsert is provided' do - before do repository.update({ id: 1, text: 'testing' }, doc_as_upsert: true) end @@ -280,7 +245,6 @@ def to_hash end describe '#delete' do - before(:all) do class Note def to_hash @@ -296,13 +260,11 @@ def to_hash end context 'when the document exists' do - let!(:id) do repository.save(Note.new)['_id'] end context 'an id is provided' do - before do repository.delete(id) end @@ -315,7 +277,6 @@ def to_hash end context 'when a document is provided' do - before do repository.delete(id: id, text: 'testing') end @@ -329,7 +290,6 @@ def to_hash end context 'when the document does not exist' do - before do repository.create_index! end diff --git a/elasticsearch-persistence/spec/repository_spec.rb b/elasticsearch-persistence/spec/repository_spec.rb index 93fd19c16..a605a5250 100644 --- a/elasticsearch-persistence/spec/repository_spec.rb +++ b/elasticsearch-persistence/spec/repository_spec.rb @@ -35,19 +35,9 @@ class RepositoryWithoutDSL expect(RepositoryWithoutDSL.create).to be_a(RepositoryWithoutDSL) end - context 'when options are provided' do - let(:repository) do - RepositoryWithoutDSL.create(document_type: 'note') - end - - it 'sets the options on the instance' do - expect(repository.document_type).to eq('note') - end - end - context 'when a block is passed' do let(:repository) do - RepositoryWithoutDSL.create(document_type: 'note') do + RepositoryWithoutDSL.create do mapping dynamic: 'strict' do indexes :foo end @@ -60,7 +50,7 @@ class RepositoryWithoutDSL context 'when options are provided in the args and set in the block' do let(:repository) do - RepositoryWithoutDSL.create(mapping: double('mapping', to_hash: {}), document_type: 'note') do + RepositoryWithoutDSL.create(mapping: double('mapping', to_hash: {})) do mapping dynamic: 'strict' do indexes :foo end @@ -116,17 +106,13 @@ class RepositoryWithoutDSL end let(:repository) do - RepositoryWithoutDSL.new(client: client, document_type: 'user', index_name: 'users', klass: Array) + RepositoryWithoutDSL.new(client: client, index_name: 'users', klass: Array) end it 'sets the client' do expect(repository.client).to be(client) end - it 'sets document type' do - expect(repository.document_type).to eq('user') - end - it 'sets index name' do expect(repository.index_name).to eq('users') end @@ -368,17 +354,20 @@ class RepositoryWithDSL context 'when the instance has a different document type' do let(:expected_mapping) do - { other_note: { dynamic: 'strict', - properties: { foo: { type: 'object', - properties: { bar: { type: 'text' } } }, - baz: { type: 'text' } } - } + { + other_note: + { + dynamic: 'strict', + properties: { + foo: { + type: 'object', + properties: { bar: { type: 'text' } } + }, + baz: { type: 'text' } + } + } } end - - it 'updates the mapping to use the document type' do - expect(RepositoryWithDSL.new(document_type: 'other_note').mapping.to_hash).to eq(expected_mapping) - end end end @@ -444,21 +433,8 @@ class RepositoryWithoutDSL end end - context '#document_type' do - - it 'does not define the method at the class level' do - expect { - RepositoryWithoutDSL.document_type - }.to raise_exception(NoMethodError) - end - - it 'allows the value to be overridden with options on the instance' do - expect(RepositoryWithoutDSL.new(document_type: 'notes').document_type).to eq('notes') - end - end context '#index_name' do - it 'does not define the method at the class level' do expect { RepositoryWithoutDSL.index_name @@ -475,7 +451,6 @@ class RepositoryWithoutDSL end describe '#create_index!' do - let(:repository) do RepositoryWithoutDSL.new(client: DEFAULT_CLIENT) end @@ -494,17 +469,9 @@ class RepositoryWithoutDSL repository.create_index! expect(repository.index_exists?).to eq(true) end - - context 'when the repository has a document type defined' do - - let(:repository) do - RepositoryWithoutDSL.new(client: DEFAULT_CLIENT, document_type: 'mytype') - end - end end describe '#delete_index!' do - let(:repository) do RepositoryWithoutDSL.new(client: DEFAULT_CLIENT) end @@ -523,7 +490,6 @@ class RepositoryWithoutDSL end describe '#refresh_index!' do - let(:repository) do RepositoryWithoutDSL.new(client: DEFAULT_CLIENT) end @@ -598,7 +564,7 @@ class RepositoryWithoutDSL end let(:repository) do - RepositoryWithoutDSL.create(document_type: 'note') do + RepositoryWithoutDSL.create do mapping dynamic: 'strict' do indexes :foo do indexes :bar @@ -649,7 +615,7 @@ class RepositoryWithoutDSL context 'when a block is passed to the #create method' do let(:repository) do - RepositoryWithoutDSL.create(document_type: 'note') do + RepositoryWithoutDSL.create do settings number_of_shards: 1, number_of_replicas: 0 end end @@ -673,7 +639,7 @@ class RepositoryWithoutDSL end let(:repository) do - RepositoryWithoutDSL.create(document_type: 'note') do + RepositoryWithoutDSL.create do settings number_of_shards: 1, number_of_replicas: 0 do mapping dynamic: 'strict' do indexes :foo do diff --git a/elasticsearch-persistence/spec/spec_helper.rb b/elasticsearch-persistence/spec/spec_helper.rb index 6e5f58402..e577d3808 100644 --- a/elasticsearch-persistence/spec/spec_helper.rb +++ b/elasticsearch-persistence/spec/spec_helper.rb @@ -47,7 +47,7 @@ class MyTestRepository # The default repository to be used by tests. # # @since 6.0.0 -DEFAULT_REPOSITORY = MyTestRepository.new(index_name: 'my_test_repository', document_type: 'test') +DEFAULT_REPOSITORY = MyTestRepository.new(index_name: 'my_test_repository') # Get the Elasticsearch server version. # From e45678941c334553ab4ad0c212aa846fa332fc4d Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Fri, 23 Jun 2023 17:39:36 +0100 Subject: [PATCH 554/582] elasticsearch-persistence: Update calling client transport --- elasticsearch-persistence/README.md | 4 ++-- elasticsearch-persistence/examples/notes/test.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index e8cb0aa8a..a9b1b39ec 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -237,7 +237,7 @@ You can also override the default configuration with options passed to the initi ```ruby client = Elasticsearch::Client.new(url: 'http://localhost:9250', log: true) -client.transport.transport.logger.formatter = proc { |s, d, p, m| "\e[2m# #{m}\n\e[0m" } +client.transport.logger.formatter = proc { |s, d, p, m| "\e[2m# #{m}\n\e[0m" } repository = NoteRepository.new(client: client, index_name: 'notes_development') repository.create_index!(force: true) @@ -268,7 +268,7 @@ The repository uses the standard Elasticsearch [client](https://github.com/elast ```ruby client = Elasticsearch::Client.new(url: 'http://search.server.org') repository = NoteRepository.new(client: client) -repository.client.transport.transport.logger = Logger.new(STDERR) +repository.client.transport.logger = Logger.new(STDERR) repository.client # => Elasticsearch::Client diff --git a/elasticsearch-persistence/examples/notes/test.rb b/elasticsearch-persistence/examples/notes/test.rb index 27922933b..b57105341 100644 --- a/elasticsearch-persistence/examples/notes/test.rb +++ b/elasticsearch-persistence/examples/notes/test.rb @@ -90,7 +90,7 @@ def app app.settings.repository.client = Elasticsearch::Client.new \ hosts: [{ host: 'localhost', port: ENV.fetch('TEST_CLUSTER_PORT', 9250)}], log: true - app.settings.repository.client.transport.transport.logger.formatter = proc { |s, d, p, m| "\e[2m#{m}\n\e[0m" } + app.settings.repository.client.transport.logger.formatter = proc { |s, d, p, m| "\e[2m#{m}\n\e[0m" } app.settings.repository.create_index! force: true app.settings.repository.client.cluster.health wait_for_status: 'yellow' end From 3fc610187fa731ba17b243eee0c571e8fa2c7dcc Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Fri, 23 Jun 2023 17:40:32 +0100 Subject: [PATCH 555/582] elasticsearch-rails: remove type, whitespace cleanup --- .../lib/rails/templates/indexer.rb | 2 +- .../spec/instrumentation_spec.rb | 37 +++++++++++-------- elasticsearch-rails/spec/lograge_spec.rb | 9 +++-- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/elasticsearch-rails/lib/rails/templates/indexer.rb b/elasticsearch-rails/lib/rails/templates/indexer.rb index 1ea1d9b6c..4ba92a45e 100644 --- a/elasticsearch-rails/lib/rails/templates/indexer.rb +++ b/elasticsearch-rails/lib/rails/templates/indexer.rb @@ -37,7 +37,7 @@ def perform(operation, klass, record_id, options={}) record.__elasticsearch__.client = Client record.__elasticsearch__.__send__ "#{operation}_document" when /delete/ - Client.delete index: klass.constantize.index_name, type: klass.constantize.document_type, id: record_id + Client.delete index: klass.constantize.index_name, id: record_id else raise ArgumentError, "Unknown operation '#{operation}'" end end diff --git a/elasticsearch-rails/spec/instrumentation_spec.rb b/elasticsearch-rails/spec/instrumentation_spec.rb index 3fcbc5916..9245549af 100644 --- a/elasticsearch-rails/spec/instrumentation_spec.rb +++ b/elasticsearch-rails/spec/instrumentation_spec.rb @@ -18,13 +18,10 @@ require 'spec_helper' describe 'ActiveSupport::Instrumentation integration' do - before(:all) do class DummyInstrumentationModel extend Elasticsearch::Model::Searching::ClassMethods - - def self.index_name; 'foo'; end - def self.document_type; 'bar'; end + def self.index_name; 'foo'; end end end @@ -33,10 +30,14 @@ def self.document_type; 'bar'; end end let(:response_document) do - { 'took' => '5ms', - 'hits' => { 'total' => 123, - 'max_score' => 456, - 'hits' => [] } } + { + 'took' => '5ms', + 'hits' => { + 'total' => 123, + 'max_score' => 456, + 'hits' => [] + } + } end let(:search) do @@ -53,7 +54,6 @@ def self.document_type; 'bar'; end end context 'SearchRequest#execute!' do - it 'wraps the method with instrumentation' do expect(search).to respond_to(:execute_without_instrumentation!) expect(search).to respond_to(:execute_with_instrumentation!) @@ -61,14 +61,19 @@ def self.document_type; 'bar'; end end context 'Model#search' do - before do - expect(ActiveSupport::Notifications).to receive(:instrument).with('search.elasticsearch', - { klass: 'DummyInstrumentationModel', - name: 'Search', - search: { body: query, - index: 'foo', - type: 'bar' } }).and_return({}) + expect(ActiveSupport::Notifications). + to receive(:instrument). + with('search.elasticsearch', + { + klass: 'DummyInstrumentationModel', + name: 'Search', + search: { + body: query, + index: 'foo', + } + } + ).and_return({}) end let(:query) do diff --git a/elasticsearch-rails/spec/lograge_spec.rb b/elasticsearch-rails/spec/lograge_spec.rb index 980aa9f1b..3b54f1839 100644 --- a/elasticsearch-rails/spec/lograge_spec.rb +++ b/elasticsearch-rails/spec/lograge_spec.rb @@ -38,14 +38,15 @@ require 'elasticsearch/rails/lograge' describe 'ActiveSupport::Instrumentation integration' do - before do Elasticsearch::Rails::Lograge::Railtie.run_initializers end it 'customizes the Lograge configuration' do - expect(Elasticsearch::Rails::Lograge::Railtie.initializers - .select { |i| i.name == 'elasticsearch.lograge' } - .first).not_to be_nil + expect( + Elasticsearch::Rails::Lograge::Railtie.initializers + .select { |i| i.name == 'elasticsearch.lograge' } + .first + ).not_to be_nil end end From c18676b925df060d96daec5b886a47643f75bb09 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Fri, 23 Jun 2023 17:46:04 +0100 Subject: [PATCH 556/582] Bump version to 8.0.0 --- elasticsearch-model/lib/elasticsearch/model/version.rb | 2 +- .../lib/elasticsearch/persistence/version.rb | 2 +- elasticsearch-rails/lib/elasticsearch/rails/version.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/version.rb b/elasticsearch-model/lib/elasticsearch/model/version.rb index cdd631896..eb3aebc12 100644 --- a/elasticsearch-model/lib/elasticsearch/model/version.rb +++ b/elasticsearch-model/lib/elasticsearch/model/version.rb @@ -17,6 +17,6 @@ module Elasticsearch module Model - VERSION = "7.2.1" + VERSION = '8.0.0'.freeze end end diff --git a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb index 18fe2466d..7fa2ce112 100644 --- a/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb +++ b/elasticsearch-persistence/lib/elasticsearch/persistence/version.rb @@ -17,6 +17,6 @@ module Elasticsearch module Persistence - VERSION = '7.2.1' + VERSION = '8.0.0'.freeze end end diff --git a/elasticsearch-rails/lib/elasticsearch/rails/version.rb b/elasticsearch-rails/lib/elasticsearch/rails/version.rb index 07d084dae..6506ad906 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/version.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/version.rb @@ -17,6 +17,6 @@ module Elasticsearch module Rails - VERSION = "7.2.1" + VERSION = '8.0.0'.freeze end end From 6d9cb023c579158cec34bf6745d4b1f08342f2be Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Fri, 23 Jun 2023 17:54:29 +0100 Subject: [PATCH 557/582] elasticsearch-model: update calling transport in examples --- elasticsearch-model/examples/activerecord_associations.rb | 2 +- elasticsearch-model/examples/activerecord_custom_analyzer.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/elasticsearch-model/examples/activerecord_associations.rb b/elasticsearch-model/examples/activerecord_associations.rb index 17d521cbf..b8100b33b 100644 --- a/elasticsearch-model/examples/activerecord_associations.rb +++ b/elasticsearch-model/examples/activerecord_associations.rb @@ -81,7 +81,7 @@ # ----- Elasticsearch client setup ---------------------------------------------------------------- Elasticsearch::Model.client = Elasticsearch::Client.new log: true -Elasticsearch::Model.client.transport.transport.logger.formatter = proc { |s, d, p, m| "\e[2m#{m}\n\e[0m" } +Elasticsearch::Model.client.transport.logger.formatter = proc { |s, d, p, m| "\e[2m#{m}\n\e[0m" } # ----- Search integration ------------------------------------------------------------------------ diff --git a/elasticsearch-model/examples/activerecord_custom_analyzer.rb b/elasticsearch-model/examples/activerecord_custom_analyzer.rb index d16ddbd11..886eaab91 100644 --- a/elasticsearch-model/examples/activerecord_custom_analyzer.rb +++ b/elasticsearch-model/examples/activerecord_custom_analyzer.rb @@ -37,8 +37,8 @@ end end -Elasticsearch::Model.client.transport.transport.logger = ActiveSupport::Logger.new(STDOUT) -Elasticsearch::Model.client.transport.transport.logger.formatter = lambda { |s, d, p, m| "#{m.ansi(:faint)}\n" } +Elasticsearch::Model.client.transport.logger = ActiveSupport::Logger.new(STDOUT) +Elasticsearch::Model.client.transport.logger.formatter = lambda { |s, d, p, m| "#{m.ansi(:faint)}\n" } class Article < ActiveRecord::Base include Elasticsearch::Model From 8f645f54d4a27a9d2e3112dc5f77901a236af623 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Tue, 27 Jun 2023 10:27:41 +0100 Subject: [PATCH 558/582] persistence: Updates dependency on model --- elasticsearch-persistence/elasticsearch-persistence.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch-persistence/elasticsearch-persistence.gemspec b/elasticsearch-persistence/elasticsearch-persistence.gemspec index b88c0a317..6dbb7386f 100644 --- a/elasticsearch-persistence/elasticsearch-persistence.gemspec +++ b/elasticsearch-persistence/elasticsearch-persistence.gemspec @@ -44,7 +44,7 @@ Gem::Specification.new do |s| s.add_dependency 'activemodel', '> 4' s.add_dependency 'activesupport', '> 4' s.add_dependency 'elasticsearch', '~> 8' - s.add_dependency 'elasticsearch-model', '7.2.1' + s.add_dependency 'elasticsearch-model', '8' s.add_dependency 'hashie' s.add_development_dependency 'bundler' From 1422382dcfa4bfae1fb44d6c711e8ed1d7092287 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Tue, 27 Jun 2023 10:54:00 +0100 Subject: [PATCH 559/582] specs: removes active_record_at_least_4? function and related code --- .../active_record/associations_spec.rb | 124 +++++++----------- .../adapters/active_record/basic_spec.rb | 12 +- elasticsearch-model/spec/spec_helper.rb | 9 -- 3 files changed, 50 insertions(+), 95 deletions(-) diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/associations_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/associations_spec.rb index c3e460ffb..14000b693 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/associations_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/associations_spec.rb @@ -18,7 +18,6 @@ require 'spec_helper' describe 'Elasticsearch::Model::Adapter::ActiveRecord Associations' do - before(:all) do ActiveRecord::Schema.define(version: 1) do create_table :categories do |t| @@ -76,7 +75,6 @@ end context 'when a document is created' do - before do Post.create!(title: 'Test') Post.create!(title: 'Testing Coding') @@ -97,9 +95,7 @@ end describe 'has_many_and_belongs_to association' do - - context 'when an association is updated' do - + context 'when an association is updated' do before do post.categories = [category_a, category_b] Post.__elasticsearch__.refresh_index! @@ -119,20 +115,20 @@ let(:search_result) do Post.search(query: { - bool: { - must: { - multi_match: { - fields: ['title'], - query: 'first' - } - }, - filter: { - terms: { - categories: ['One'] - } - } - } - } ) + bool: { + must: { + multi_match: { + fields: ['title'], + query: 'first' + } + }, + filter: { + terms: { + categories: ['One'] + } + } + } + } ) end it 'applies the update with' do @@ -144,7 +140,6 @@ end context 'when an association is deleted' do - before do post.categories = [category_a, category_b] post.categories = [category_b] @@ -165,20 +160,20 @@ let(:search_result) do Post.search(query: { - bool: { - must: { - multi_match: { - fields: ['title'], - query: 'first' - } - }, - filter: { - terms: { - categories: ['One'] - } - } - } - } ) + bool: { + must: { + multi_match: { + fields: ['title'], + query: 'first' + } + }, + filter: { + terms: { + categories: ['One'] + } + } + } + } ) end it 'applies the update with a reindex' do @@ -189,9 +184,7 @@ end describe 'has_many through association' do - context 'when the association is updated' do - before do author_a = Author.where(first_name: "John", last_name: "Smith").first_or_create! author_b = Author.where(first_name: "Mary", last_name: "Smith").first_or_create! @@ -210,33 +203,17 @@ Post.__elasticsearch__.refresh_index! end - context 'if active record is at least 4' do - - let(:search_result) do - Post.search('authors.full_name:john') - end - - it 'applies the update', if: active_record_at_least_4? do - expect(search_result.results.size).to eq(2) - expect(search_result.records.size).to eq(2) - end + let(:search_result) do + Post.search('authors.full_name:john') end - context 'if active record is less than 4' do - - let(:search_result) do - Post.search('authors.author.full_name:john') - end - - it 'applies the update', if: !active_record_at_least_4? do - expect(search_result.results.size).to eq(2) - expect(search_result.records.size).to eq(2) - end + it 'applies the update' do + expect(search_result.results.size).to eq(2) + expect(search_result.records.size).to eq(2) end end - context 'when an association is added', if: active_record_at_least_4? do - + context 'when an association is added' do before do author_a = Author.where(first_name: "John", last_name: "Smith").first_or_create! author_b = Author.where(first_name: "Mary", last_name: "Smith").first_or_create! @@ -262,9 +239,7 @@ end describe 'has_many association' do - - context 'when an association is added', if: active_record_at_least_4? do - + context 'when an association is added' do before do # Create posts post_1 = Post.create!(title: "First Post", text: "This is the first post...") @@ -282,18 +257,18 @@ let(:search_result) do Post.search(query: { - nested: { - path: 'comments', - query: { - bool: { - must: [ - { match: { 'comments.author' => 'john' } }, - { match: { 'comments.text' => 'good' } } - ] - } - } - } - }) + nested: { + path: 'comments', + query: { + bool: { + must: [ + { match: { 'comments.author' => 'john' } }, + { match: { 'comments.text' => 'good' } } + ] + } + } + } + }) end it 'adds the association' do @@ -303,9 +278,7 @@ end describe '#touch' do - context 'when a touch callback is defined on the model' do - before do # Create categories category_a = Category.where(title: "One").first_or_create! @@ -329,7 +302,6 @@ end describe '#includes' do - before do post_1 = Post.create(title: 'One') post_2 = Post.create(title: 'Two') diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb index 5dc91da9e..20392b42b 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb @@ -327,31 +327,23 @@ end describe 'ordering of SQL queries' do - context 'when order is called on the ActiveRecord query' do - let(:search_result) do Article.search query: { match: { title: { query: 'test' } } } end - it 'allows the SQL query to be ordered independent of the Elasticsearch results order', unless: active_record_at_least_4? do - expect(search_result.records.order('title DESC').first.title).to eq('Testing Coding') - expect(search_result.records.order('title DESC')[0].title).to eq('Testing Coding') - end - - it 'allows the SQL query to be ordered independent of the Elasticsearch results order', if: active_record_at_least_4? do + it 'allows the SQL query to be ordered independent of the Elasticsearch results order' do expect(search_result.records.order(title: :desc).first.title).to eq('Testing Coding') expect(search_result.records.order(title: :desc)[0].title).to eq('Testing Coding') end end context 'when more methods are chained on the ActiveRecord query' do - let(:search_result) do Article.search query: {match: {title: {query: 'test'}}} end - it 'allows the SQL query to be ordered independent of the Elasticsearch results order', if: active_record_at_least_4? do + it 'allows the SQL query to be ordered independent of the Elasticsearch results order' do expect(search_result.records.distinct.order(title: :desc).first.title).to eq('Testing Coding') expect(search_result.records.distinct.order(title: :desc)[0].title).to eq('Testing Coding') end diff --git a/elasticsearch-model/spec/spec_helper.rb b/elasticsearch-model/spec/spec_helper.rb index 413b5f03e..d3a1e303f 100644 --- a/elasticsearch-model/spec/spec_helper.rb +++ b/elasticsearch-model/spec/spec_helper.rb @@ -65,15 +65,6 @@ end end -# Is the ActiveRecord version at least 4.0? -# -# @return [ true, false ] Whether the ActiveRecord version is at least 4.0. -# -# @since 6.0.1 -def active_record_at_least_4? - defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4 -end - # Delete all documents from the indices of the provided list of models. # # @param [ Array<ActiveRecord::Base> ] models The list of models. From e5281378a388b0edacdd9587d66d86bcf947d7ab Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 14 Sep 2023 09:21:19 +0100 Subject: [PATCH 560/582] Adds debug gem --- Gemfile | 1 + elasticsearch-model/gemfiles/6.1.gemfile | 2 +- elasticsearch-model/gemfiles/7.0.gemfile | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index b6cb7cca7..b04ce3e9e 100644 --- a/Gemfile +++ b/Gemfile @@ -24,6 +24,7 @@ gem 'pry' gem 'rake', '~> 12' group :development do + gem 'debug' gem 'rspec' gem 'yard' end diff --git a/elasticsearch-model/gemfiles/6.1.gemfile b/elasticsearch-model/gemfiles/6.1.gemfile index 5e5be171f..2260df5cd 100644 --- a/elasticsearch-model/gemfiles/6.1.gemfile +++ b/elasticsearch-model/gemfiles/6.1.gemfile @@ -30,7 +30,7 @@ gem 'sqlite3' unless defined?(JRUBY_VERSION) # gem 'mongoid', '~> 6' group :development, :testing do - gem 'byebug' + gem 'debug' gem 'pry-nav' gem 'rspec' end diff --git a/elasticsearch-model/gemfiles/7.0.gemfile b/elasticsearch-model/gemfiles/7.0.gemfile index bc3bd63a5..97f1366c1 100644 --- a/elasticsearch-model/gemfiles/7.0.gemfile +++ b/elasticsearch-model/gemfiles/7.0.gemfile @@ -30,7 +30,7 @@ gem 'sqlite3' unless defined?(JRUBY_VERSION) # gem 'mongoid', '~> 6' group :development, :testing do - gem 'byebug' + gem 'debug' gem 'pry-nav' gem 'rspec' end From 283ce0a59edbc4c580ede043aaced221b63ab8b6 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 14 Sep 2023 09:22:35 +0100 Subject: [PATCH 561/582] Whitespace fixes --- .../lib/elasticsearch/model/adapters/default.rb | 4 ---- .../lib/elasticsearch/model/adapters/multiple.rb | 1 - .../lib/elasticsearch/model/importing.rb | 3 --- .../lib/elasticsearch/model/indexing.rb | 1 - .../model/adapters/active_record/basic_spec.rb | 16 +--------------- .../adapters/active_record/pagination_spec.rb | 8 -------- .../adapters/active_record/parent_child_spec.rb | 3 --- .../adapters/active_record/serialization_spec.rb | 4 ---- .../spec/elasticsearch/model/multimodel_spec.rb | 2 -- .../spec/elasticsearch/model/proxy_spec.rb | 1 - 10 files changed, 1 insertion(+), 42 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/default.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/default.rb index 9891b5d64..914514136 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/default.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/default.rb @@ -18,15 +18,12 @@ module Elasticsearch module Model module Adapter - # The default adapter for models which haven't one registered # module Default - # Module for implementing methods and logic related to fetching records from the database # module Records - # Return the collection of records fetched from the database # # By default uses `MyModel#find[1, 2, 3]` @@ -60,7 +57,6 @@ def __transform raise NotImplemented, "Method not implemented for default adapter" end end - end end end diff --git a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb index 30d851043..9a8657f1d 100644 --- a/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb +++ b/elasticsearch-model/lib/elasticsearch/model/adapters/multiple.rb @@ -18,7 +18,6 @@ module Elasticsearch module Model module Adapter - # An adapter to be used for deserializing results from multiple models, # retrieved through `Elasticsearch::Model.search` # diff --git a/elasticsearch-model/lib/elasticsearch/model/importing.rb b/elasticsearch-model/lib/elasticsearch/model/importing.rb index 3060e178a..26e05458c 100644 --- a/elasticsearch-model/lib/elasticsearch/model/importing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/importing.rb @@ -158,11 +158,8 @@ def import(options={}, &block) index: target_index, body: __batch_to_bulk(batch, transform) } - params[:pipeline] = pipeline if pipeline - response = client.bulk params - yield response if block_given? errors += response['items'].select { |k, v| k.values.first['error'] } diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index 30fe0b8ff..08763a8f5 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -97,7 +97,6 @@ def as_json(options={}) end module ClassMethods - # Defines mappings for the index # # @example Define mapping for model diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb index 20392b42b..83754cd0b 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb @@ -76,7 +76,6 @@ end describe 'indexing a document' do - let(:search_result) do Article.search('title:test') end @@ -88,7 +87,6 @@ end describe '#results' do - let(:search_result) do Article.search('title:test') end @@ -97,12 +95,11 @@ expect(search_result.results.first).to be_a(Elasticsearch::Model::Response::Result) end - it 'prooperly loads the document' do + it 'properly loads the document' do expect(search_result.results.first.title).to eq('Test') end context 'when the result contains other data' do - let(:search_result) do Article.search(query: { match: { title: 'test' } }, highlight: { fields: { title: {} } }) end @@ -119,7 +116,6 @@ end describe '#records' do - let(:search_result) do Article.search('title:test') end @@ -134,7 +130,6 @@ end describe 'Enumerable' do - let(:search_result) do Article.search('title:test') end @@ -149,7 +144,6 @@ end describe '#id' do - let(:search_result) do Article.search('title:test') end @@ -173,7 +167,6 @@ end describe 'search results order' do - let(:search_result) do Article.search(query: { match: { title: 'code' }}, sort: { clicks: :desc }) end @@ -196,7 +189,6 @@ end describe 'a paged collection' do - let(:search_result) do Article.search(query: { match: { title: { query: 'test' } } }, size: 2, @@ -212,7 +204,6 @@ end describe '#destroy' do - before do Article.create!(title: 'destroy', body: '', clicks: 1) Article.__elasticsearch__.refresh_index! @@ -233,7 +224,6 @@ end describe 'full document updates' do - before do article = Article.create!(title: 'update', body: '', clicks: 1) Article.__elasticsearch__.refresh_index! @@ -254,7 +244,6 @@ end describe 'attribute updates' do - before do article = Article.create!(title: 'update', body: '', clicks: 1) Article.__elasticsearch__.refresh_index! @@ -275,7 +264,6 @@ end describe '#save' do - before do article = Article.create!(title: 'save', body: '', clicks: 1) @@ -302,7 +290,6 @@ end describe 'a DSL search' do - let(:search_result) do Article.search(query: { match: { title: { query: 'test' } } }) end @@ -314,7 +301,6 @@ end describe 'chaining SQL queries on response.records' do - let(:search_result) do Article.search(query: { match: { title: { query: 'test' } } }) end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb index c4a503ed7..3cbd30b7f 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb @@ -18,7 +18,6 @@ require 'spec_helper' describe 'Elasticsearch::Model::Adapter::ActiveRecord Pagination' do - before(:all) do ActiveRecord::Schema.define(:version => 1) do create_table ArticleForPagination.table_name do |t| @@ -41,48 +40,41 @@ end context 'when no other page is specified' do - let(:records) do ArticleForPagination.search('title:test').page(1).records end describe '#size' do - it 'returns the correct size' do expect(records.size).to eq(25) end end describe '#current_page' do - it 'returns the correct current page' do expect(records.current_page).to eq(1) end end describe '#prev_page' do - it 'returns the correct previous page' do expect(records.prev_page).to be_nil end end describe '#next_page' do - it 'returns the correct next page' do expect(records.next_page).to eq(2) end end describe '#total_pages' do - it 'returns the correct total pages' do expect(records.total_pages).to eq(3) end end describe '#first_page?' do - it 'returns the correct first page' do expect(records.first_page?).to be(true) end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb index 06790f613..a9feb9b58 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb @@ -53,7 +53,6 @@ end describe 'has_child search' do - let(:search_result) do Question.search(query: { has_child: { type: 'answer', query: { match: { author: 'john' } } } }) end @@ -64,7 +63,6 @@ end describe 'hash_parent search' do - let(:search_result) do Answer.search(query: { has_parent: { parent_type: 'question', query: { match: { author: 'john' } } } }) end @@ -75,7 +73,6 @@ end context 'when a parent is deleted' do - before do Question.where(title: 'First Question').each(&:destroy) Question.__elasticsearch__.refresh_index! diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb index da9d3306a..3837169a9 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb @@ -18,7 +18,6 @@ require 'spec_helper' describe 'Elasticsearch::Model::Adapter::ActiveRecord Serialization' do - before(:all) do ActiveRecord::Schema.define(:version => 1) do create_table ArticleWithCustomSerialization.table_name do |t| @@ -32,14 +31,12 @@ end context 'when the model has a custom serialization defined' do - before do ArticleWithCustomSerialization.create!(title: 'Test', status: 'green') ArticleWithCustomSerialization.__elasticsearch__.refresh_index! end context 'when a document is indexed' do - let(:search_result) do ArticleWithCustomSerialization.__elasticsearch__.client.get( index: 'article_with_custom_serializations', @@ -53,7 +50,6 @@ end context 'when a document is updated' do - before do article.update(title: 'UPDATED', status: 'yellow') ArticleWithCustomSerialization.__elasticsearch__.refresh_index! diff --git a/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb b/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb index 3652dac1e..484b8c248 100644 --- a/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb @@ -44,9 +44,7 @@ end describe 'the model registry' do - before(:all) do - class JustAModel include Elasticsearch::Model end diff --git a/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb b/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb index f21baad3d..9e229b0ac 100644 --- a/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb @@ -18,7 +18,6 @@ require 'spec_helper' describe Elasticsearch::Model::Proxy do - before(:all) do class ::DummyProxyModel include Elasticsearch::Model::Proxy From 36a002766424722fc26ec40baba9ee2af41c1771 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 14 Sep 2023 09:25:33 +0100 Subject: [PATCH 562/582] Removes more type ocurrances --- .../lib/elasticsearch/model/indexing.rb | 7 +- .../lib/elasticsearch/model/multimodel.rb | 8 --- .../lib/elasticsearch/model/naming.rb | 65 ++----------------- .../adapters/active_record/basic_spec.rb | 38 +---------- .../model/adapters/multiple_spec.rb | 2 - .../spec/elasticsearch/model/indexing_spec.rb | 43 +----------- .../elasticsearch/model/multimodel_spec.rb | 8 +-- .../model/response/aggregations_spec.rb | 1 - .../response/pagination/will_paginate_spec.rb | 1 - .../model/response/records_spec.rb | 1 - .../model/response/results_spec.rb | 1 - 11 files changed, 12 insertions(+), 163 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index 08763a8f5..b1e07e61a 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -51,7 +51,6 @@ def as_json(options={}) class Mappings attr_accessor :options - # @private TYPES_WITH_EMBEDDED_PROPERTIES = %w(object nested) def initialize(options={}) @@ -84,11 +83,7 @@ def indexes(name, options={}, &block) end def to_hash - if @type - { @type.to_sym => @options.merge( properties: @mapping ) } - else - @options.merge( properties: @mapping ) - end + @options.merge( properties: @mapping ) end def as_json(options={}) diff --git a/elasticsearch-model/lib/elasticsearch/model/multimodel.rb b/elasticsearch-model/lib/elasticsearch/model/multimodel.rb index b8c415cad..c7dfaedb1 100644 --- a/elasticsearch-model/lib/elasticsearch/model/multimodel.rb +++ b/elasticsearch-model/lib/elasticsearch/model/multimodel.rb @@ -80,14 +80,6 @@ def index_name models.map { |m| m.index_name } end - # Get an Array of document types used for retrieving documents when doing a search across multiple models - # - # @return [Array] the list of document types used for retrieving documents - # - def document_type - models.map { |m| m.document_type }.compact.presence - end - # Get the client common for all models # # @return Elastic::Transport::Client diff --git a/elasticsearch-model/lib/elasticsearch/model/naming.rb b/elasticsearch-model/lib/elasticsearch/model/naming.rb index c4c796ab2..36f9288b0 100644 --- a/elasticsearch-model/lib/elasticsearch/model/naming.rb +++ b/elasticsearch-model/lib/elasticsearch/model/naming.rb @@ -17,15 +17,10 @@ module Elasticsearch module Model - - # Provides methods for getting and setting index name and document type for the model + # Provides methods for getting and setting index name the model # module Naming - - DEFAULT_DOC_TYPE = '_doc'.freeze - module ClassMethods - # Get or set the name of the index # # @example Set the index name for the `Article` model @@ -64,46 +59,18 @@ def index_name=(name) @index_name = name end - # Get or set the document type - # - # @example Set the document type for the `Article` model - # - # class Article - # document_type "my-article" - # end - # - # @example Directly set the document type for the `Article` model - # - # Article.document_type "my-article" - # - def document_type name=nil - @document_type = name || @document_type || implicit(:document_type) - end - - - # Set the document type - # - # @see document_type - # - def document_type=(name) - @document_type = name - end - private - def implicit(prop) - self.send("default_#{prop}") - end - - def default_index_name - self.model_name.collection.gsub(/\//, '-') - end + def implicit(prop) + self.send("default_#{prop}") + end - def default_document_type; end + def default_index_name + self.model_name.collection.gsub(/\//, '-') + end end module InstanceMethods - # Get or set the index name for the model instance # # @example Set the index name for an instance of the `Article` model @@ -129,25 +96,7 @@ def index_name name=nil, &block def index_name=(name) @index_name = name end - - # @example Set the document type for an instance of the `Article` model - # - # @article.document_type "my-article" - # @article.__elasticsearch__.update_document - # - def document_type name=nil - @document_type = name || @document_type || self.class.document_type - end - - # Set the document type - # - # @see document_type - # - def document_type=(name) - @document_type = name - end end - end end end diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb index 83754cd0b..5cdd13ef1 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb @@ -18,43 +18,7 @@ require 'spec_helper' describe Elasticsearch::Model::Adapter::ActiveRecord do - - context 'when a document_type is not defined for the Model' do - before do - ActiveRecord::Schema.define(:version => 1) do - create_table :article_no_types do |t| - t.string :title - t.string :body - t.integer :clicks, :default => 0 - t.datetime :created_at, :default => 'NOW()' - end - end - - ArticleNoType.delete_all - ArticleNoType.__elasticsearch__.create_index!(force: true) - - ArticleNoType.create!(title: 'Test', body: '', clicks: 1) - ArticleNoType.create!(title: 'Testing Coding', body: '', clicks: 2) - ArticleNoType.create!(title: 'Coding', body: '', clicks: 3) - - ArticleNoType.__elasticsearch__.refresh_index! - end - - describe 'indexing a document' do - - let(:search_result) do - ArticleNoType.search('title:test') - end - - it 'allows searching for documents' do - expect(search_result.results.size).to be(2) - expect(search_result.records.size).to be(2) - end - end - end - - context 'when a document_type is defined for the Model' do - + context 'for the Model' do before(:all) do ActiveRecord::Schema.define(:version => 1) do create_table :articles do |t| diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb index eb5a983b0..f52368b55 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb @@ -18,13 +18,11 @@ require 'spec_helper' describe Elasticsearch::Model::Adapter::Multiple do - before(:all) do class DummyOne include Elasticsearch::Model index_name 'dummy' - document_type 'dummy_one' def self.find(ids) ids.map { |id| new(id) } diff --git a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb index c8df409e5..68fc2c242 100644 --- a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb @@ -94,10 +94,6 @@ class NotFound < Exception; end expect(DummyIndexingModel.mappings).to be_a(Elasticsearch::Model::Indexing::Mappings) end - it 'does not raise an exception when there is no type passed to the #initialize method' do - expect(Elasticsearch::Model::Indexing::Mappings.new) - end - it 'should be convertible to a hash' do expect(Elasticsearch::Model::Indexing::Mappings.new({ foo: 'bar' }).to_hash).to eq(expected_mapping_hash) end @@ -106,44 +102,7 @@ class NotFound < Exception; end expect(Elasticsearch::Model::Indexing::Mappings.new({ foo: 'bar' }).as_json).to eq(expected_mapping_hash) end - context 'when a type is specified' do - let(:mappings) do - Elasticsearch::Model::Indexing::Mappings.new - end - - before do - mappings.indexes :foo, { type: 'boolean', include_in_all: false } - mappings.indexes :bar - end - - it 'creates the correct mapping definition' do - expect(mappings.to_hash[:properties][:foo][:type]).to eq('boolean') - end - - it 'uses text as the default field type' do - expect(mappings.to_hash[:properties][:bar][:type]).to eq('text') - end - - context 'when the \'include_type_name\' option is specified' do - let(:mappings) do - Elasticsearch::Model::Indexing::Mappings.new(include_type_name: true) - end - - before do - mappings.indexes :foo, { type: 'boolean', include_in_all: false } - end - - it 'creates the correct mapping definition' do - expect(mappings.to_hash[:properties][:foo][:type]).to eq('boolean') - end - - it 'sets the \'include_type_name\' option' do - expect(mappings.to_hash[:include_type_name]).to eq(true) - end - end - end - - context 'when a type is not specified' do + context 'basic mappings' do let(:mappings) do Elasticsearch::Model::Indexing::Mappings.new end diff --git a/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb b/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb index 484b8c248..31d429074 100644 --- a/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/multimodel_spec.rb @@ -24,21 +24,17 @@ end let(:model_1) do - double('Foo', index_name: 'foo_index', document_type: 'foo', to_ary: nil) + double('Foo', index_name: 'foo_index', to_ary: nil) end let(:model_2) do - double('Bar', index_name: 'bar_index', document_type: 'bar', to_ary: nil) + double('Bar', index_name: 'bar_index', to_ary: nil) end it 'has an index name' do expect(multimodel.index_name).to eq(['foo_index', 'bar_index']) end - it 'has an document type' do - expect(multimodel.document_type).to eq(['foo', 'bar']) - end - it 'has a client' do expect(multimodel.client).to eq(Elasticsearch::Model.client) end diff --git a/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb index a455140ea..e840d949d 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb @@ -22,7 +22,6 @@ before(:all) do class OriginClass def self.index_name; 'foo'; end - def self.document_type; 'bar'; end end end diff --git a/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb index 25f1f0d1e..0f7303adf 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb @@ -22,7 +22,6 @@ before(:all) do class ModelClass def self.index_name; 'foo'; end - def self.document_type; 'bar'; end def self.per_page 33 diff --git a/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb index 2a80dcfc6..a4e5e7072 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/records_spec.rb @@ -31,7 +31,6 @@ def foo; 'BAR'; end class DummyModel def self.index_name; 'foo'; end - def self.document_type; 'bar'; end def self.find(*args) DummyCollection.new diff --git a/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb index f69db2c01..f944e1877 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/results_spec.rb @@ -22,7 +22,6 @@ before(:all) do class OriginClass def self.index_name; 'foo'; end - def self.document_type; 'bar'; end end end From 15911543900fff42cc16e1c017df9926452f7bd6 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 14 Sep 2023 09:26:06 +0100 Subject: [PATCH 563/582] Code Style changes --- elasticsearch-model/Rakefile | 2 ++ .../spec/elasticsearch/model/importing_spec.rb | 10 +++++----- .../spec/elasticsearch/model/proxy_spec.rb | 7 +++---- .../elasticsearch/model/response/aggregations_spec.rb | 7 ++++--- .../model/searching_search_request_spec.rb | 10 +++++----- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/elasticsearch-model/Rakefile b/elasticsearch-model/Rakefile index bbcf9ad41..9efdda0af 100644 --- a/elasticsearch-model/Rakefile +++ b/elasticsearch-model/Rakefile @@ -56,6 +56,8 @@ namespace :test do puts '-' * 80 end end + + task unit: :all end # ----- Documentation tasks --------------------------------------------------- diff --git a/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb index 438d0c9e9..b905fa4eb 100644 --- a/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/importing_spec.rb @@ -24,7 +24,7 @@ class DummyImportingModel module DummyImportingAdapter module ImportingMixin - def __find_in_batches( options = {}, &block) + def __find_in_batches(options = {}, &block) yield if block_given? end def __transform @@ -125,7 +125,7 @@ def importing_mixin context 'when the method is called with the force option' do before do expect(DummyImportingModel).to receive(:create_index!).with(force: true, index: 'foo').and_return(true) - expect(DummyImportingModel).to receive(:__find_in_batches).with(foo: 'bar').and_return(true) + expect(DummyImportingModel).to receive(:__find_in_batches).with({ foo: 'bar' }).and_return(true) end it 'deletes and creates the index' do @@ -136,7 +136,7 @@ def importing_mixin context 'when the method is called with the refresh option' do before do expect(DummyImportingModel).to receive(:refresh_index!).with(index: 'foo').and_return(true) - expect(DummyImportingModel).to receive(:__find_in_batches).with(foo: 'bar').and_return(true) + expect(DummyImportingModel).to receive(:__find_in_batches).with({ foo: 'bar' }).and_return(true) end it 'refreshes the index' do @@ -147,7 +147,7 @@ def importing_mixin context 'when a different index name is provided' do before do expect(DummyImportingModel).to receive(:client).and_return(client) - expect(client).to receive(:bulk).with(body: nil, index: 'my-new-index').and_return(response) + expect(client).to receive(:bulk).with({ body: nil, index: 'my-new-index' }).and_return(response) end it 'uses the alternate index name' do @@ -203,7 +203,7 @@ def importing_mixin context 'when a pipeline is provided as an options' do before do expect(DummyImportingModel).to receive(:client).and_return(client) - expect(client).to receive(:bulk).with(body: nil, index: 'foo', pipeline: 'my-pipeline').and_return(response) + expect(client).to receive(:bulk).with({ body: nil, index: 'foo', pipeline: 'my-pipeline' }).and_return(response) end it 'uses the pipeline option' do diff --git a/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb b/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb index 9e229b0ac..5465c22a9 100644 --- a/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/proxy_spec.rb @@ -122,10 +122,9 @@ def changes_to_save expect(model).to eq(model_target) expect(duplicate).to eq(duplicate_target) end - end - it 'forwards keyword arguments to target methods' do - expect(DummyProxyModel.new.__elasticsearch__.keyword_method(foo: 'bar')).to eq('bar') + it 'forwards keyword arguments to target methods' do + expect(DummyProxyModel.new.__elasticsearch__.keyword_method(foo: 'bar')).to eq('bar') + end end - end diff --git a/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb b/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb index e840d949d..4bfd89efe 100644 --- a/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/response/aggregations_spec.rb @@ -33,9 +33,10 @@ def self.index_name; 'foo'; end { 'aggregations' => { 'foo' => {'bar' => 10 }, - 'price' => { 'doc_count' => 123, - 'min' => { 'value' => 1.0}, - 'max' => { 'value' => 99 } + 'price' => { + 'doc_count' => 123, + 'min' => { 'value' => 1.0}, + 'max' => { 'value' => 99 } } } } diff --git a/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb b/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb index 1a7b4e413..1ab23365a 100644 --- a/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/searching_search_request_spec.rb @@ -43,7 +43,7 @@ def self.index_name; 'foo'; end context 'when the search definition is a simple query' do before do - expect(client).to receive(:search).with(index: 'foo', q: 'foo').and_return({}) + expect(client).to receive(:search).with({ index: 'foo', q: 'foo' }).and_return({}) end let(:search) do @@ -58,7 +58,7 @@ def self.index_name; 'foo'; end context 'when the search definition is a hash' do before do - expect(client).to receive(:search).with(index: 'foo', body: { foo: 'bar' }).and_return({}) + expect(client).to receive(:search).with({ index: 'foo', body: { foo: 'bar' } }).and_return({}) end let(:search) do @@ -73,7 +73,7 @@ def self.index_name; 'foo'; end context 'when the search definition is a json string' do before do - expect(client).to receive(:search).with(index: 'foo', body: '{"foo":"bar"}').and_return({}) + expect(client).to receive(:search).with({ index: 'foo', body: '{"foo":"bar"}' }).and_return({}) end let(:search) do @@ -98,7 +98,7 @@ def to_hash; {foo: 'bar'}; end end before do - expect(client).to receive(:search).with(index: 'foo', body: {foo: 'bar'}).and_return({}) + expect(client).to receive(:search).with({ index: 'foo', body: {foo: 'bar'} }).and_return({}) end let(:search) do @@ -113,7 +113,7 @@ def to_hash; {foo: 'bar'}; end context 'when extra options are specified' do before do - expect(client).to receive(:search).with(index: 'foo', q: 'foo', size: 15).and_return({}) + expect(client).to receive(:search).with({ index: 'foo', q: 'foo', size: 15 }).and_return({}) end let(:search) do From 136d4f3e44633b0b9eb679b6d2347f5e8fdda2db Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 14 Sep 2023 09:26:55 +0100 Subject: [PATCH 564/582] Addresses client.transport access --- elasticsearch-model/lib/elasticsearch/model/indexing.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticsearch-model/lib/elasticsearch/model/indexing.rb b/elasticsearch-model/lib/elasticsearch/model/indexing.rb index b1e07e61a..0e9c68b30 100644 --- a/elasticsearch-model/lib/elasticsearch/model/indexing.rb +++ b/elasticsearch-model/lib/elasticsearch/model/indexing.rb @@ -281,7 +281,7 @@ def delete_index!(options={}) self.client.indices.delete index: target_index rescue Exception => e if e.class.to_s =~ /NotFound/ && options[:force] - client.transport.transport.logger.debug("[!!!] Index does not exist (#{e.class})") if client.transport.transport.logger + client.transport.logger.debug("[!!!] Index does not exist (#{e.class})") if client.transport.logger nil else raise e @@ -308,7 +308,7 @@ def refresh_index!(options={}) self.client.indices.refresh index: target_index rescue Exception => e if e.class.to_s =~ /NotFound/ && options[:force] - client.transport.transport.logger.debug("[!!!] Index does not exist (#{e.class})") if client.transport.transport.logger + client.transport.logger.debug("[!!!] Index does not exist (#{e.class})") if client.transport.logger nil else raise e From 4e85fa38eb1ee02af733b109737955f08e2d08ae Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 14 Sep 2023 09:27:07 +0100 Subject: [PATCH 565/582] Updates aggregation interval to calendar_interval --- .../elasticsearch/model/adapters/active_record/basic_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb index 5cdd13ef1..d334685b2 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/basic_spec.rb @@ -301,11 +301,10 @@ end describe 'access to the response via methods' do - let(:search_result) do Article.search(query: { match: { title: { query: 'test' } } }, aggregations: { - dates: { date_histogram: { field: 'created_at', interval: 'hour' } }, + dates: { date_histogram: { field: 'created_at', calendar_interval: 'hour' } }, clicks: { global: {}, aggregations: { min: { min: { field: 'clicks' } } } } }, suggest: { text: 'tezt', title: { term: { field: 'title', suggest_mode: 'always' } } }) From 2468640a64293dea90512713e90a27315240c682 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 14 Sep 2023 09:37:32 +0100 Subject: [PATCH 566/582] Updates MultiModel tests I need to understand if we still need MultiModel or if it was based on the document_type which is no longer a thing in Elasticsearch. --- .../spec/elasticsearch/model/adapters/multiple_spec.rb | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb index f52368b55..86c001db8 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/multiple_spec.rb @@ -40,7 +40,6 @@ class DummyTwo include Elasticsearch::Model index_name 'dummy' - document_type 'dummy_two' def self.find(ids) ids.map { |id| new(id) } @@ -58,7 +57,6 @@ class DummyTwo include Elasticsearch::Model index_name 'other_index' - document_type 'dummy_two' def self.find(ids) ids.map { |id| new(id) } @@ -84,27 +82,22 @@ def initialize(id) [ { _index: 'dummy', - _type: 'dummy_two', _id: '2' }, { _index: 'dummy', - _type: 'dummy_one', _id: '2' }, { _index: 'other_index', - _type: 'dummy_two', _id: '1' }, { _index: 'dummy', - _type: 'dummy_two', _id: '1' }, { _index: 'dummy', - _type: 'dummy_one', _id: '3' } ] @@ -119,13 +112,12 @@ def initialize(id) end describe '#records' do - before do multimodel.class.send :include, Elasticsearch::Model::Adapter::Multiple::Records expect(multimodel).to receive(:response).at_least(:once).and_return(response) end - it 'instantiates the correct types of instances' do + xit 'instantiates the correct types of instances' do expect(multimodel.records[0]).to be_a(Namespace::DummyTwo) expect(multimodel.records[1]).to be_a(DummyOne) expect(multimodel.records[2]).to be_a(DummyTwo) From 3cf23800f24159a0d9416ab5755827b236f40706 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 14 Sep 2023 10:24:51 +0100 Subject: [PATCH 567/582] [CI] Update GitHub Actions: - Adds Ruby 3.0 tests - Removes Ruby 2.6 and 2.7 tests --- .github/workflows/2.7.yml | 46 -------------------------- .github/workflows/{2.6.yml => 3.0.yml} | 20 +++++------ 2 files changed, 10 insertions(+), 56 deletions(-) delete mode 100644 .github/workflows/2.7.yml rename .github/workflows/{2.6.yml => 3.0.yml} (79%) diff --git a/.github/workflows/2.7.yml b/.github/workflows/2.7.yml deleted file mode 100644 index 8bfd9b66f..000000000 --- a/.github/workflows/2.7.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Ruby 2.7 -on: - push: - branches: - - main - pull_request: - branches: - - main - workflow_dispatch: - branches: - - '*' -jobs: - tests: - env: - TEST_ES_SERVER: http://localhost:9200 - RAILS_VERSIONS: '5.0,6.0' - strategy: - fail-fast: false - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Increase system limits - run: | - sudo swapoff -a - sudo sysctl -w vm.swappiness=1 - sudo sysctl -w fs.file-max=262144 - sudo sysctl -w vm.max_map_count=262144 - - uses: elastic/elastic-github-actions/elasticsearch@master - with: - stack-version: 7.x-SNAPSHOT - - uses: ruby/setup-ruby@v1 - with: - ruby-version: 2.7 - - name: Bundle - run: | - sudo apt-get install libsqlite3-dev - gem install bundler - bundle install - bundle exec rake bundle:clean - bundle exec rake bundle:install - - name: Test elasticsearch-rails - run: cd elasticsearch-rails && bundle exec rake test:all - - name: Test elasticsearch-persistence - run: cd elasticsearch-persistence && bundle exec rake test:all - - name: Test elasticsearch-model - run: cd elasticsearch-model && bundle exec rake test:all diff --git a/.github/workflows/2.6.yml b/.github/workflows/3.0.yml similarity index 79% rename from .github/workflows/2.6.yml rename to .github/workflows/3.0.yml index c003a58e5..fb65ce452 100644 --- a/.github/workflows/2.6.yml +++ b/.github/workflows/3.0.yml @@ -1,21 +1,20 @@ -name: Ruby 2.6 +name: Ruby 3.0 on: push: branches: - - main + - 8.x pull_request: branches: - - main - workflow_dispatch: - branches: - - '*' + - 8.x jobs: tests: env: - TEST_ES_SERVER: http://localhost:9200 - RAILS_VERSIONS: '5.0,6.0' + ELASTICSEARCH_URL: http://localhost:9200 + RAILS_VERSIONS: ${{ matrix.rails }} strategy: fail-fast: false + matrix: + rails: [ '6.1', '7.0' ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -27,10 +26,11 @@ jobs: sudo sysctl -w vm.max_map_count=262144 - uses: elastic/elastic-github-actions/elasticsearch@master with: - stack-version: 7.x-SNAPSHOT + stack-version: 8.11.0-SNAPSHOT + security-enabled: false - uses: ruby/setup-ruby@v1 with: - ruby-version: 2.6 + ruby-version: 3.0 - name: Bundle run: | sudo apt-get install libsqlite3-dev From 33aa4bbc2621b5e6e0d34011fa820c73ce893f5c Mon Sep 17 00:00:00 2001 From: Colin MacKenzie IV <sinisterchipmunk@gmail.com> Date: Fri, 12 Apr 2024 15:47:21 -0400 Subject: [PATCH 568/582] Handle security config for ES8 --- Rakefile | 18 ++++++++++-------- .../spec/elasticsearch/model/indexing_spec.rb | 2 +- elasticsearch-model/spec/spec_helper.rb | 3 ++- elasticsearch-persistence/spec/spec_helper.rb | 3 ++- elasticsearch-rails/spec/spec_helper.rb | 3 ++- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Rakefile b/Rakefile index 4d49a8f34..55c294486 100644 --- a/Rakefile +++ b/Rakefile @@ -38,14 +38,16 @@ def admin_client if test_suite == 'security' transport_options.merge!(:ssl => { verify: false, - ca_path: CERT_DIR }) + ca_path: defined?(CERT_DIR) ? CERT_DIR : nil + }.compact) password = ENV['ELASTIC_PASSWORD'] user = ENV['ELASTIC_USER'] || 'elastic' - url = "https://#{user}:#{password}@#{host}:#{port}" + url = "https://#{user}:#{password}@#{host || 'localhost'}:#{port || 9200}" else url = "http://#{host || 'localhost'}:#{port || 9200}" end + ENV['ELASTICSEARCH_URL'] ||= url Elasticsearch::Client.new(host: url, transport_options: transport_options) end end @@ -140,7 +142,7 @@ namespace :test do end desc "Run all tests in all subprojects" - task all: :wait_for_green do + task all: :wait_for_green_or_yellow do subprojects.each do |project| puts '-'*80 sh "cd #{project} && " + @@ -151,20 +153,20 @@ namespace :test do end end -desc "Wait for elasticsearch cluster to be in green state" -task :wait_for_green do +desc "Wait for elasticsearch cluster to be in green or yellow state" +task :wait_for_green_or_yellow do require 'elasticsearch' ready = nil 5.times do |i| begin - puts "Attempting to wait for green status: #{i + 1}" - if admin_client.cluster.health(wait_for_status: 'green', timeout: '50s') + puts "Attempting to wait for green or yellow status: #{i + 1}" + if admin_client.cluster.health(wait_for_status: 'yellow', timeout: '50s') ready = true break end rescue Elastic::Transport::Transport::Errors::RequestTimeout => ex - puts "Couldn't confirm green status.\n#{ex.inspect}." + puts "Couldn't confirm green or yellow status.\n#{ex.inspect}." rescue Faraday::ConnectionFailed => ex puts "Couldn't connect to Elasticsearch.\n#{ex.inspect}." sleep(30) diff --git a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb index 68fc2c242..b4c0d299d 100644 --- a/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/indexing_spec.rb @@ -591,7 +591,7 @@ class ::DummyIndexingModelForRecreate context 'when the index is not found' do let(:logger) { nil } - let(:client) { Elasticsearch::Client.new(logger: logger) } + let(:client) { Elasticsearch::Client.new(logger: logger, transport_options: { ssl: { verify: false } }) } before do expect(DummyIndexingModelForRecreate).to receive(:client).at_most(3).times.and_return(client) diff --git a/elasticsearch-model/spec/spec_helper.rb b/elasticsearch-model/spec/spec_helper.rb index d3a1e303f..921c1cfa2 100644 --- a/elasticsearch-model/spec/spec_helper.rb +++ b/elasticsearch-model/spec/spec_helper.rb @@ -45,7 +45,8 @@ tracer.formatter = lambda { |s, d, p, m| "#{m.gsub(/^.*$/) { |n| ' ' + n }.ansi(:faint)}\n" } Elasticsearch::Model.client = Elasticsearch::Client.new( host: ELASTICSEARCH_URL, - tracer: (ENV['QUIET'] ? nil : tracer) + tracer: (ENV['QUIET'] ? nil : tracer), + transport_options: { :ssl => { verify: false } } ) puts "Elasticsearch Version: #{Elasticsearch::Model.client.info['version']}" diff --git a/elasticsearch-persistence/spec/spec_helper.rb b/elasticsearch-persistence/spec/spec_helper.rb index e577d3808..bf9ac3e07 100644 --- a/elasticsearch-persistence/spec/spec_helper.rb +++ b/elasticsearch-persistence/spec/spec_helper.rb @@ -36,7 +36,8 @@ # # @since 6.0.0 DEFAULT_CLIENT = Elasticsearch::Client.new(host: ELASTICSEARCH_URL, - tracer: (ENV['QUIET'] ? nil : ::Logger.new(STDERR))) + tracer: (ENV['QUIET'] ? nil : ::Logger.new(STDERR)), + transport_options: { :ssl => { verify: false } }) class MyTestRepository include Elasticsearch::Persistence::Repository diff --git a/elasticsearch-rails/spec/spec_helper.rb b/elasticsearch-rails/spec/spec_helper.rb index e0a299964..ccc28f345 100644 --- a/elasticsearch-rails/spec/spec_helper.rb +++ b/elasticsearch-rails/spec/spec_helper.rb @@ -36,7 +36,8 @@ tracer = ::Logger.new(STDERR) tracer.formatter = lambda { |s, d, p, m| "#{m.gsub(/^.*$/) { |n| ' ' + n }.ansi(:faint)}\n" } Elasticsearch::Model.client = Elasticsearch::Client.new host: ELASTICSEARCH_URL, - tracer: (ENV['QUIET'] ? nil : tracer) + tracer: (ENV['QUIET'] ? nil : tracer), + transport_options: { :ssl => { verify: false } } puts "Elasticsearch Version: #{Elasticsearch::Model.client.info['version']}" unless ActiveRecord::Base.connected? From 4ee1708c906084cc357f9ae906512cd654a3d6dc Mon Sep 17 00:00:00 2001 From: Colin MacKenzie IV <sinisterchipmunk@gmail.com> Date: Fri, 12 Apr 2024 19:11:44 -0400 Subject: [PATCH 569/582] Test that the model namespace isn't polluted. The goal of ClassMethodsProxy is to avoid polluting the target's namespace, but it was possible to do this by accident when calling `class_eval` before ActiveSupport was completely loaded. This test ensures the namespace isn't polluted, regardless of the load state of ActiveSupport. --- .../elasticsearch/model/adapters/active_record/import_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb index a23e58c37..73be18c59 100644 --- a/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb +++ b/elasticsearch-model/spec/elasticsearch/model/adapters/active_record/import_spec.rb @@ -52,6 +52,10 @@ it 'imports all documents' do expect(ImportArticle.search('*').results.total).to eq(10) end + + it "does not pollute the model's namespace" do + expect(ImportArticle.methods).not_to include(:__transform) + end end context 'when batch size is specified' do From 98c5fa177f6f1913b8ee0cf145784df312469891 Mon Sep 17 00:00:00 2001 From: Colin MacKenzie IV <sinisterchipmunk@gmail.com> Date: Fri, 12 Apr 2024 19:12:33 -0400 Subject: [PATCH 570/582] Require activesupport/all to more closely mirror a production Rails app. ActiveSupport patches Kernel to add `class_eval` but this behavior wasn't loaded in the test environment. This created a discrepancy between test and prod, causing tests to fail that should have passed and vice versa. Fully loading ActiveSupport makes the test environment more accurate. --- elasticsearch-model/spec/spec_helper.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/elasticsearch-model/spec/spec_helper.rb b/elasticsearch-model/spec/spec_helper.rb index 921c1cfa2..7c4f21a4a 100644 --- a/elasticsearch-model/spec/spec_helper.rb +++ b/elasticsearch-model/spec/spec_helper.rb @@ -31,6 +31,10 @@ require 'yaml' require 'active_record' +# Load all of ActiveSupport to be sure of complete compatibility - +# see https://github.com/elastic/elasticsearch-rails/pull/1075 for details +require 'active_support/all' + unless defined?(ELASTICSEARCH_URL) ELASTICSEARCH_URL = ENV['ELASTICSEARCH_URL'] || "localhost:#{(ENV['TEST_CLUSTER_PORT'] || 9200)}" end From e40cb7b2af89d47d0464045bb9af579155863bcb Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 15 Apr 2024 15:25:46 +0100 Subject: [PATCH 571/582] Updates Ruby, Rails, Stack versions for tests --- .github/workflows/3.0.yml | 6 ++-- elasticsearch-model/gemfiles/7.1.gemfile | 36 +++++++++++++++++++ .../lib/elasticsearch/model/multimodel.rb | 1 - .../lib/elasticsearch/model/naming.rb | 2 +- 4 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 elasticsearch-model/gemfiles/7.1.gemfile diff --git a/.github/workflows/3.0.yml b/.github/workflows/3.0.yml index fb65ce452..556caeaef 100644 --- a/.github/workflows/3.0.yml +++ b/.github/workflows/3.0.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - rails: [ '6.1', '7.0' ] + rails: [ '6.1', '7.0', '7.1' ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -26,11 +26,11 @@ jobs: sudo sysctl -w vm.max_map_count=262144 - uses: elastic/elastic-github-actions/elasticsearch@master with: - stack-version: 8.11.0-SNAPSHOT + stack-version: 8.13-SNAPSHOT security-enabled: false - uses: ruby/setup-ruby@v1 with: - ruby-version: 3.0 + ruby-version: 3.3 - name: Bundle run: | sudo apt-get install libsqlite3-dev diff --git a/elasticsearch-model/gemfiles/7.1.gemfile b/elasticsearch-model/gemfiles/7.1.gemfile new file mode 100644 index 000000000..bd1d30838 --- /dev/null +++ b/elasticsearch-model/gemfiles/7.1.gemfile @@ -0,0 +1,36 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Usage: +# +# $ BUNDLE_GEMFILE=./gemfiles/7.0.gemfile bundle install +# $ BUNDLE_GEMFILE=./gemfiles/7.0.gemfile bundle exec rake test:integration + +source 'https://rubygems.org' + +gemspec path: '../' + +gem 'activemodel', '~> 7.1' +gem 'activerecord', '~> 7.1' +gem 'sqlite3' unless defined?(JRUBY_VERSION) +# gem 'mongoid', '~> 6' + +group :development, :testing do + gem 'debug' + gem 'pry-nav' + gem 'rspec' +end diff --git a/elasticsearch-model/lib/elasticsearch/model/multimodel.rb b/elasticsearch-model/lib/elasticsearch/model/multimodel.rb index c7dfaedb1..c519ea968 100644 --- a/elasticsearch-model/lib/elasticsearch/model/multimodel.rb +++ b/elasticsearch-model/lib/elasticsearch/model/multimodel.rb @@ -17,7 +17,6 @@ module Elasticsearch module Model - # Keeps a global registry of classes that include `Elasticsearch::Model` # class Registry diff --git a/elasticsearch-model/lib/elasticsearch/model/naming.rb b/elasticsearch-model/lib/elasticsearch/model/naming.rb index 36f9288b0..643f3559b 100644 --- a/elasticsearch-model/lib/elasticsearch/model/naming.rb +++ b/elasticsearch-model/lib/elasticsearch/model/naming.rb @@ -17,7 +17,7 @@ module Elasticsearch module Model - # Provides methods for getting and setting index name the model + # Provides methods for getting and setting index and name for the model # module Naming module ClassMethods From 77ebcef17159b5efb230e7d8a1f145bfc8546562 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 15 Apr 2024 15:38:32 +0100 Subject: [PATCH 572/582] [CI] Updates GitHub Actions --- .github/workflows/{3.0.yml => tests.yml} | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) rename .github/workflows/{3.0.yml => tests.yml} (89%) diff --git a/.github/workflows/3.0.yml b/.github/workflows/tests.yml similarity index 89% rename from .github/workflows/3.0.yml rename to .github/workflows/tests.yml index 556caeaef..2cc6457ad 100644 --- a/.github/workflows/3.0.yml +++ b/.github/workflows/tests.yml @@ -15,9 +15,10 @@ jobs: fail-fast: false matrix: rails: [ '6.1', '7.0', '7.1' ] + ruby: ['3.1', '3.2', '3.3'] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Increase system limits run: | sudo swapoff -a @@ -26,11 +27,11 @@ jobs: sudo sysctl -w vm.max_map_count=262144 - uses: elastic/elastic-github-actions/elasticsearch@master with: - stack-version: 8.13-SNAPSHOT + stack-version: 8.14.0-SNAPSHOT security-enabled: false - uses: ruby/setup-ruby@v1 with: - ruby-version: 3.3 + ruby-version: ${{ matrix.ruby }} - name: Bundle run: | sudo apt-get install libsqlite3-dev From f9631b8856aa67db8e52009098840c5c50dc3b85 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 15 Apr 2024 15:57:54 +0100 Subject: [PATCH 573/582] [DOCS] Updates README --- README.md | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 26ee3860e..fd639b862 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,7 @@ # Elasticsearch Rails -[![Ruby 2.7](https://github.com/elastic/elasticsearch-rails/workflows/Ruby%202.7/badge.svg)](https://github.com/elastic/elasticsearch-rails/actions) -[![Ruby 2.6](https://github.com/elastic/elasticsearch-rails/workflows/Ruby%202.6/badge.svg)](https://github.com/elastic/elasticsearch-rails/actions) -[![Ruby 2.5](https://github.com/elastic/elasticsearch-rails/workflows/Ruby%202.5/badge.svg)](https://github.com/elastic/elasticsearch-rails/actions) -[![Ruby 2.4](https://github.com/elastic/elasticsearch-rails/workflows/Ruby%202.4/badge.svg)](https://github.com/elastic/elasticsearch-rails/actions) -[![JRuby](https://github.com/elastic/elasticsearch-rails/workflows/JRuby/badge.svg)](https://github.com/elastic/elasticsearch-rails/actions) -[![Code Climate](https://codeclimate.com/github/elastic/elasticsearch-rails/badges/gpa.svg)](https://codeclimate.com/github/elastic/elasticsearch-rails) +[![Ruby tests](https://github.com/elastic/elasticsearch-rails/actions/workflows/tests.yml/badge.svg)](https://github.com/elastic/elasticsearch-rails/actions/workflows/tests.yml) +[![JRuby tests](https://github.com/elastic/elasticsearch-rails/actions/workflows/jruby.yml/badge.svg)](https://github.com/elastic/elasticsearch-rails/actions/workflows/jruby.yml) This repository contains various Ruby and Rails integrations for [Elasticsearch](http://elasticsearch.org): @@ -29,20 +25,13 @@ Install each library from [Rubygems](https://rubygems.org/gems/elasticsearch): gem install elasticsearch-model gem install elasticsearch-rails -To use an unreleased version, add it to your `Gemfile` for [Bundler](http://bundler.io): - -```ruby -gem 'elasticsearch-model', github: 'elastic/elasticsearch-rails', branch: '5.x' -gem 'elasticsearch-rails', github: 'elastic/elasticsearch-rails', branch: '5.x' -``` - ## Compatibility -The libraries are compatible with Ruby 2.4 and higher. +The libraries are compatible with Ruby 3.0 and higher. We follow Ruby’s own maintenance policy and officially support all currently maintained versions per [Ruby Maintenance Branches](https://www.ruby-lang.org/en/downloads/branches/). -The version numbers follow the Elasticsearch major versions. Currently the `main` branch is compatible with version `7.x` of the Elasticsearch stack. **We haven't tested and updated the code for Elasticsearch `8.0` yet**. +The version numbers follow the Elasticsearch major versions. Currently the `main` branch is compatible with version `8.x` of the Elasticsearch stack. | Rubygem | | Elasticsearch | |:-------------:|:-:| :-----------: | @@ -50,7 +39,9 @@ The version numbers follow the Elasticsearch major versions. Currently the `main | 2.x | → | 2.x | | 5.x | → | 5.x | | 6.x | → | 6.x | -| main | → | 7.x | +| 7.x | → | 7.x | +| 8.x | → | 8.x | +| main | → | 8.x | Check out [Elastic product end of life dates](https://www.elastic.co/support/eol) to learn which releases are still actively supported and tested. From 0aedb0da202aaf4619f8e3aa3d9c3fbb91290f8e Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Mon, 15 Apr 2024 15:55:25 +0100 Subject: [PATCH 574/582] [CI] Updates GitHub Actions for JRuby, names --- .github/workflows/jruby.yml | 20 ++++++++++++-------- .github/workflows/tests.yml | 2 +- Gemfile | 2 +- elasticsearch-model/Gemfile | 2 +- elasticsearch-persistence/Gemfile | 2 +- elasticsearch-rails/Gemfile | 6 ++++-- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/.github/workflows/jruby.yml b/.github/workflows/jruby.yml index 7a3ec1be7..5ac8c162d 100644 --- a/.github/workflows/jruby.yml +++ b/.github/workflows/jruby.yml @@ -1,11 +1,11 @@ -name: JRuby +name: JRuby tests on: push: branches: - - main + - 8.x pull_request: branches: - - main + - 8.x workflow_dispatch: branches: - '*' @@ -13,12 +13,15 @@ jobs: tests: env: TEST_ES_SERVER: http://localhost:9200 - RAILS_VERSIONS: '5.0,6.0' + RAILS_VERSIONS: ${{ matrix.rails }} strategy: fail-fast: false + matrix: + rails: [ '6.1', '7.0', '7.1' ] + ruby: ['jruby-9.4'] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Increase system limits run: | sudo swapoff -a @@ -27,13 +30,14 @@ jobs: sudo sysctl -w vm.max_map_count=262144 - uses: elastic/elastic-github-actions/elasticsearch@master with: - stack-version: 7.x-SNAPSHOT + stack-version: 8.14.0-SNAPSHOT + security-enabled: false - uses: ruby/setup-ruby@v1 with: - ruby-version: jruby-9.3 + ruby-version: ${{ matrix.ruby }} - name: Bundle run: | - sudo apt-get install libsqlite3-dev + sudo apt-get install libsqlite3-dev libcurl4-openssl-dev gem install bundler bundle install bundle exec rake bundle:clean diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2cc6457ad..ff76e3925 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,4 +1,4 @@ -name: Ruby 3.0 +name: Ruby tests on: push: branches: diff --git a/Gemfile b/Gemfile index b04ce3e9e..ddb9b7bbd 100644 --- a/Gemfile +++ b/Gemfile @@ -24,7 +24,7 @@ gem 'pry' gem 'rake', '~> 12' group :development do - gem 'debug' + gem 'debug' unless defined?(JRUBY_VERSION) gem 'rspec' gem 'yard' end diff --git a/elasticsearch-model/Gemfile b/elasticsearch-model/Gemfile index 72fae31be..58f5b6b92 100644 --- a/elasticsearch-model/Gemfile +++ b/elasticsearch-model/Gemfile @@ -21,7 +21,7 @@ source 'https://rubygems.org' gemspec group :development, :testing do - gem 'debug' + gem 'debug' unless defined?(JRUBY_VERSION) gem 'pry-nav' gem 'rspec' end diff --git a/elasticsearch-persistence/Gemfile b/elasticsearch-persistence/Gemfile index 2afccf280..4e228558e 100644 --- a/elasticsearch-persistence/Gemfile +++ b/elasticsearch-persistence/Gemfile @@ -25,7 +25,7 @@ gem 'elasticsearch-model', require: false group :development, :testing do - gem 'debug' + gem 'debug' unless defined?(JRUBY_VERSION) gem 'pry-nav' gem 'rspec' end diff --git a/elasticsearch-rails/Gemfile b/elasticsearch-rails/Gemfile index 2f333c4b5..f51541c74 100644 --- a/elasticsearch-rails/Gemfile +++ b/elasticsearch-rails/Gemfile @@ -29,8 +29,10 @@ gem 'elasticsearch-persistence', require: false group :development, :testing do - gem 'debug' gem 'pry-nav' gem 'rspec' - gem 'sqlite3' unless defined?(JRUBY_VERSION) + unless defined?(JRUBY_VERSION) + gem 'sqlite3' + gem 'debug' + end end From 18fa01d1f2110c00278ac3dd4bf52f34fe4c59ea Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Tue, 16 Apr 2024 12:46:41 +0100 Subject: [PATCH 575/582] [CI] Updates GitHub Actions --- .github/workflows/jruby.yml | 4 ++-- .github/workflows/tests.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/jruby.yml b/.github/workflows/jruby.yml index 5ac8c162d..f61c93932 100644 --- a/.github/workflows/jruby.yml +++ b/.github/workflows/jruby.yml @@ -2,10 +2,10 @@ name: JRuby tests on: push: branches: - - 8.x + - main pull_request: branches: - - 8.x + - main workflow_dispatch: branches: - '*' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ff76e3925..30474143c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,10 +2,10 @@ name: Ruby tests on: push: branches: - - 8.x + - main pull_request: branches: - - 8.x + - main jobs: tests: env: From 54117829a0887cbe7da35dcbf664fc1c0bf824b0 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Wed, 17 Apr 2024 09:47:17 +0100 Subject: [PATCH 576/582] [DOCS] Updates READMEs --- README.md | 6 +-- elasticsearch-model/README.md | 4 +- elasticsearch-persistence/README.md | 75 +++++++++-------------------- elasticsearch-rails/README.md | 25 +++++----- 4 files changed, 41 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index fd639b862..0e27afb21 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ repository = Elasticsearch::Persistence::Repository.new repository.save Article.new(title: 'Test') # POST http://localhost:9200/repository/article -# => {"_index"=>"repository", "_type"=>"article", "_id"=>"Ak75E0U9Q96T5Y999_39NA", ...} +# => {"_index"=>"repository", "_id"=>"Ak75E0U9Q96T5Y999_39NA", ...} ``` **Please refer to each library documentation for detailed information and examples.** @@ -164,9 +164,9 @@ This software is licensed under the Apache 2 license, quoted below. the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY diff --git a/elasticsearch-model/README.md b/elasticsearch-model/README.md index 12add7e01..4103ffa8b 100644 --- a/elasticsearch-model/README.md +++ b/elasticsearch-model/README.md @@ -8,7 +8,7 @@ It aims to simplify integration of Ruby classes ("models"), commonly found e.g. This library is compatible with Ruby 3 and higher. -The version numbers follow the Elasticsearch major versions. Currently the `main` branch is compatible with version `7.x` of the Elasticsearch stack. **We haven't tested and updated the code for Elasticsearch `8.0` yet**. +The version numbers follow the Elasticsearch major versions. Currently the `main` branch is compatible with version `8.x` of the Elasticsearch stack. | Rubygem | | Elasticsearch | |:-------:|:-:|:-------------:| @@ -141,7 +141,7 @@ Elasticsearch::Model.client = Elasticsearch::Client.new log: true You might want to do this during your application bootstrap process, e.g. in a Rails initializer. Please refer to the -[`elasticsearch-transport`](https://github.com/elastic/elasticsearch-ruby/tree/main/elasticsearch-transport) +[`elastic-transport`](https://github.com/elastic/elastic-transport-ruby/) library documentation for all the configuration options, and to the [`elasticsearch-api`](http://rubydoc.info/gems/elasticsearch-api) library documentation for information about the Ruby client API. diff --git a/elasticsearch-persistence/README.md b/elasticsearch-persistence/README.md index a9b1b39ec..47747b6ff 100644 --- a/elasticsearch-persistence/README.md +++ b/elasticsearch-persistence/README.md @@ -4,18 +4,19 @@ Persistence layer for Ruby domain objects in Elasticsearch, using the Repository ## Compatibility -This library is compatible with Ruby 2.4 and higher. +This library is compatible with Ruby 3.1 and higher. -The version numbers follow the Elasticsearch major versions. Currently the `main` branch is compatible with version `7.x` of the Elasticsearch stack. **We haven't tested and updated the code for Elasticsearch `8.0` yet**. +The version numbers follow the Elasticsearch major versions. Currently the `main` branch is compatible with version `8.x` of the Elasticsearch stack. -| Rubygem | | Elasticsearch | -|:-------------:|:-:| :-----------: | -| 0.1 | → | 1.x | -| 2.x | → | 2.x | -| 5.x | → | 5.x | -| 6.x | → | 6.x | -| 7.x | → | 7.x | -| main | → | 7.x | +| Rubygem | | Elasticsearch | +|:-------:|:-:|:-------------:| +| 0.1 | → | 1.x | +| 2.x | → | 2.x | +| 5.x | → | 5.x | +| 6.x | → | 6.x | +| 7.x | → | 7.x | +| 8.x | → | 8.x | +| main | → | 8.x | ## Installation @@ -78,7 +79,7 @@ note = Note.new id: 1, text: 'Test' repository.save(note) # PUT http://localhost:9200/repository/_doc/1 [status:201, request:0.210s, query:n/a] # > {"id":1,"text":"Test"} -# < {"_index":"repository","_type":"note","_id":"1","_version":1,"created":true} +# < {"_index":"repository","_id":"1","_version":1,"created":true} ``` ...find it... @@ -86,7 +87,7 @@ repository.save(note) ```ruby n = repository.find(1) # GET http://localhost:9200/repository/_doc/1 [status:200, request:0.003s, query:n/a] -# < {"_index":"repository","_type":"note","_id":"1","_version":2,"found":true, "_source" : {"id":1,"text":"Test"}} +# < {"_index":"repository","_id":"1","_version":2,"found":true, "_source" : {"id":1,"text":"Test"}} => <Note:0x007fcbfc0c4980 @attributes={"id"=>1, "text"=>"Test"}> ``` @@ -105,14 +106,14 @@ repository.search(query: { match: { text: 'test' } }).first ```ruby repository.delete(note) # DELETE http://localhost:9200/repository/_doc/1 [status:200, request:0.014s, query:n/a] -# < {"found":true,"_index":"repository","_type":"note","_id":"1","_version":3} -=> {"found"=>true, "_index"=>"repository", "_type"=>"note", "_id"=>"1", "_version"=>2} +# < {"found":true,"_index":"repository","_id":"1","_version":3} +=> {"found"=>true, "_index"=>"repository", "_id"=>"1", "_version"=>2} ``` The repository module provides a number of features and facilities to configure and customize the behavior: * Configuring the Elasticsearch [client](https://github.com/elastic/elasticsearch-ruby#usage) being used -* Setting the index name, document type, and object class for deserialization +* Setting the index name, and object class for deserialization * Composing mappings and settings for the index * Creating, deleting or refreshing the index * Finding or searching for documents @@ -145,7 +146,7 @@ class MyRepository end client = Elasticsearch::Client.new(url: ENV['ELASTICSEARCH_URL'], log: true) -repository = MyRepository.new(client: client, index_name: :my_notes, type: :note, klass: Note) +repository = MyRepository.new(client: client, index_name: :my_notes, klass: Note) repository.settings number_of_shards: 1 do mapping do indexes :text, analyzer: 'snowball' @@ -153,8 +154,7 @@ repository.settings number_of_shards: 1 do end ``` -The custom Elasticsearch client will be used now, with a custom index and type names, -as well as the custom serialization and de-serialization logic. +The custom Elasticsearch client will be used now, with a custom index, as well as the custom serialization and de-serialization logic. We can create the index with the desired settings and mappings: @@ -170,7 +170,7 @@ Save the document with extra properties added by the `serialize` method: repository.save(note) # PUT http://localhost:9200/my_notes/note/1 # > {"id":1,"text":"Test","my_special_key":"my_special_stuff"} -{"_index"=>"my_notes", "_type"=>"my_note", "_id"=>"1", "_version"=>4, ... } +{"_index"=>"my_notes", "_id"=>"1", "_version"=>4, ... } ``` And `deserialize` it: @@ -194,7 +194,6 @@ class NoteRepository include Elasticsearch::Persistence::Repository::DSL index_name 'notes' - document_type 'note' klass Note settings number_of_shards: 1 do @@ -318,36 +317,8 @@ repository.index_name ``` -The `document_type` method specifies the Elasticsearch document type to use for storage, lookup and search. The default value is -'_doc'. Keep in mind that future versions of Elasticsearch will not allow you to set this yourself and will use the type, -'_doc'. - -```ruby -repository = NoteRepository.new(document_type: 'note') -repository.document_type -# => 'note' - -``` - -or with the DSL mixin: - -```ruby -class NoteRepository - include Elasticsearch::Persistence::Repository - include Elasticsearch::Persistence::Repository::DSL - - document_type 'note' -end - -repository = NoteRepository.new -repository.document_type -# => 'note' - -``` - The `klass` method specifies the Ruby class name to use when initializing objects from -documents retrieved from the repository. If this value is not set, a Hash representation of the document will be -returned instead. +documents retrieved from the repository. If this value is not set, a Hash representation of the document will be returned instead. ```ruby repository = NoteRepository.new(klass: Note) @@ -452,7 +423,7 @@ The `save` method allows you to store a domain object in the repository: ```ruby note = Note.new id: 1, title: 'Quick Brown Fox' repository.save(note) -# => {"_index"=>"notes_development", "_type"=>"_doc", "_id"=>"1", "_version"=>1, "created"=>true} +# => {"_index"=>"notes_development", "_id"=>"1", "_version"=>1, "created"=>true} ``` The `update` method allows you to perform a partial update of a document in the repository. @@ -460,14 +431,14 @@ Use either a partial document: ```ruby repository.update id: 1, title: 'UPDATED', tags: [] -# => {"_index"=>"notes_development", "_type"=>"_doc", "_id"=>"1", "_version"=>2} +# => {"_index"=>"notes_development", "_id"=>"1", "_version"=>2} ``` Or a script (optionally with parameters): ```ruby repository.update 1, script: 'if (!ctx._source.tags.contains(t)) { ctx._source.tags += t }', params: { t: 'foo' } -# => {"_index"=>"notes_development", "_type"=>"_doc", "_id"=>"1", "_version"=>3} +# => {"_index"=>"notes_development", "_id"=>"1", "_version"=>3} ``` diff --git a/elasticsearch-rails/README.md b/elasticsearch-rails/README.md index 12e7d7d59..4ec8ac2f8 100644 --- a/elasticsearch-rails/README.md +++ b/elasticsearch-rails/README.md @@ -6,18 +6,19 @@ library, providing features suitable for Ruby on Rails applications. ## Compatibility -This library is compatible with Ruby 1.9.3 and higher. +This library is compatible with Ruby 3.1 and higher. -The version numbers follow the Elasticsearch major versions. Currently the `main` branch is compatible with version `7.x` of the Elasticsearch stack. **We haven't tested and updated the code for Elasticsearch `8.0` yet**. +The version numbers follow the Elasticsearch major versions. Currently the `main` branch is compatible with version `8.x` of the Elasticsearch stack. -| Rubygem | | Elasticsearch | -|:-------------:|:-:| :-----------: | -| 0.1 | → | 1.x | -| 2.x | → | 2.x | -| 5.x | → | 5.x | -| 6.x | → | 6.x | -| 7.x | → | 7.x | -| main | → | 7.x | +| Rubygem | | Elasticsearch | +|:-------:|:-:|:-------------:| +| 0.1 | → | 1.x | +| 2.x | → | 2.x | +| 5.x | → | 5.x | +| 6.x | → | 6.x | +| 7.x | → | 7.x | +| 8.x | → | 8.x | +| main | → | 8.x | ## Installation @@ -137,9 +138,9 @@ This software is licensed under the Apache 2 license, quoted below. the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY From 98b6ce022f03e3fe5f899e5dda6a378c897759ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciek=20Dubi=C5=84ski?= <maciek@dubinski.net> Date: Wed, 27 Dec 2023 16:53:40 +0100 Subject: [PATCH 577/582] Address Rails 7.1 deprecation warning in elasticsearch-rails This PR switches the elasticsearch-rails gem to use a new signature of the ActiveSupport::LogSubscriber#color method when called in an app using Rails 7.1+. With older Rails, the gem uses the working, old signature. --- .../rails/instrumentation/log_subscriber.rb | 13 ++++- .../instrumentation/log_subscriber_spec.rb | 57 +++++++++++++++++++ elasticsearch-rails/spec/spec_helper.rb | 1 + 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 elasticsearch-rails/spec/instrumentation/log_subscriber_spec.rb diff --git a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/log_subscriber.rb b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/log_subscriber.rb index c388961c0..a9f112f5d 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/log_subscriber.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/instrumentation/log_subscriber.rb @@ -46,8 +46,19 @@ def search(event) payload = event.payload name = "#{payload[:klass]} #{payload[:name]} (#{event.duration.round(1)}ms)" search = payload[:search].inspect.gsub(/:(\w+)=>/, '\1: ') + debug %Q| #{color(name, GREEN, color_option(true))} #{colorize_logging ? "\e[2m#{search}\e[0m" : search}| + end + + private + + def color_option(bold_value) + new_color_syntax? ? { bold: bold_value } : bold_value + end + + def new_color_syntax? + return @new_color_syntax if defined?(@new_color_syntax) - debug %Q| #{color(name, GREEN, true)} #{colorize_logging ? "\e[2m#{search}\e[0m" : search}| + @new_color_syntax = ::Rails.respond_to?(:gem_version) && ::Rails.gem_version >= '7.1' end end diff --git a/elasticsearch-rails/spec/instrumentation/log_subscriber_spec.rb b/elasticsearch-rails/spec/instrumentation/log_subscriber_spec.rb new file mode 100644 index 000000000..7512f006a --- /dev/null +++ b/elasticsearch-rails/spec/instrumentation/log_subscriber_spec.rb @@ -0,0 +1,57 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +require 'spec_helper' +require 'elasticsearch/rails/instrumentation/log_subscriber' + +describe Elasticsearch::Rails::Instrumentation::LogSubscriber do + subject(:instance) { described_class.new } + + let(:logger) { instance_double(Logger) } + + before do + allow(instance).to receive(:logger) { logger } + end + + describe "#search" do + subject { instance.search(event) } + + let(:event) { double("search.elasticsearch", duration: 1.2345, payload: { name: "execute", search: { query: { match_all: {}}}}) } + + it "logs the event" do + expect(instance).to receive(:color).with(" execute (1.2ms)", described_class::GREEN, { bold: true }).and_call_original + expect(logger).to receive(:debug?) { true } + expect(logger).to receive(:debug).with(" \e[1m\e[32m execute (1.2ms)\e[0m \e[2m{query: {match_all: {}}}\e[0m") + subject + end + + context "when Rails version is older" do + let(:rails_version) { "7.0.0" } + + before do + allow(::Rails).to receive(:gem_version) { Gem::Version.new(rails_version) } + end + + it "logs the event" do + expect(instance).to receive(:color).with(" execute (1.2ms)", described_class::GREEN, true).and_call_original + expect(logger).to receive(:debug?) { true } + expect(logger).to receive(:debug).with(" \e[1m\e[32m execute (1.2ms)\e[0m \e[2m{query: {match_all: {}}}\e[0m") + subject + end + end + end +end diff --git a/elasticsearch-rails/spec/spec_helper.rb b/elasticsearch-rails/spec/spec_helper.rb index ccc28f345..3a9eb8e6c 100644 --- a/elasticsearch-rails/spec/spec_helper.rb +++ b/elasticsearch-rails/spec/spec_helper.rb @@ -20,6 +20,7 @@ require 'elasticsearch/model' require 'elasticsearch/rails' require 'rails/railtie' +require 'rails/version' require 'elasticsearch/rails/instrumentation' From bd546fa8d93a6b2adc6f9a5dbc568eedba2c9ccb Mon Sep 17 00:00:00 2001 From: Martin Streicher <martin.streicher@gadget.consulting> Date: Mon, 4 Mar 2024 11:06:38 -0500 Subject: [PATCH 578/582] Ensure subclasses are added to the registry --- elasticsearch-model/lib/elasticsearch/model.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/elasticsearch-model/lib/elasticsearch/model.rb b/elasticsearch-model/lib/elasticsearch/model.rb index 5b28726cb..6cc595050 100644 --- a/elasticsearch-model/lib/elasticsearch/model.rb +++ b/elasticsearch-model/lib/elasticsearch/model.rb @@ -112,6 +112,11 @@ class << self METHODS.each do |method| delegate method, to: :__elasticsearch__ unless self.public_instance_methods.include?(method) end + + def inherited(subclass) + super + Registry.add(subclass) if subclass.is_a?(Class) + end end end From 9a49625382bce0ac792c2c783099156446bd5ff6 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 30 May 2024 15:36:33 +0100 Subject: [PATCH 579/582] Dependency management --- elasticsearch-model/elasticsearch-model.gemspec | 2 +- elasticsearch-model/gemfiles/6.1.gemfile | 2 +- elasticsearch-model/gemfiles/7.0.gemfile | 2 +- elasticsearch-model/gemfiles/7.1.gemfile | 2 +- elasticsearch-rails/Gemfile | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/elasticsearch-model/elasticsearch-model.gemspec b/elasticsearch-model/elasticsearch-model.gemspec index c00b5c87a..57f95b6ae 100644 --- a/elasticsearch-model/elasticsearch-model.gemspec +++ b/elasticsearch-model/elasticsearch-model.gemspec @@ -63,6 +63,6 @@ Gem::Specification.new do |s| unless defined?(JRUBY_VERSION) s.add_development_dependency 'oj' s.add_development_dependency 'ruby-prof' - s.add_development_dependency 'sqlite3' + s.add_development_dependency 'sqlite3', '~> 1.4' end end diff --git a/elasticsearch-model/gemfiles/6.1.gemfile b/elasticsearch-model/gemfiles/6.1.gemfile index 2260df5cd..79a13d5c4 100644 --- a/elasticsearch-model/gemfiles/6.1.gemfile +++ b/elasticsearch-model/gemfiles/6.1.gemfile @@ -26,7 +26,7 @@ gemspec path: '../' gem 'activemodel', '6.1' gem 'activerecord', '6.1' -gem 'sqlite3' unless defined?(JRUBY_VERSION) +gem 'sqlite3', '~> 1.4' unless defined?(JRUBY_VERSION) # gem 'mongoid', '~> 6' group :development, :testing do diff --git a/elasticsearch-model/gemfiles/7.0.gemfile b/elasticsearch-model/gemfiles/7.0.gemfile index 97f1366c1..f1b5e3ba0 100644 --- a/elasticsearch-model/gemfiles/7.0.gemfile +++ b/elasticsearch-model/gemfiles/7.0.gemfile @@ -26,7 +26,7 @@ gemspec path: '../' gem 'activemodel', '~> 7' gem 'activerecord', '~> 7' -gem 'sqlite3' unless defined?(JRUBY_VERSION) +gem 'sqlite3', '~> 1.4' unless defined?(JRUBY_VERSION) # gem 'mongoid', '~> 6' group :development, :testing do diff --git a/elasticsearch-model/gemfiles/7.1.gemfile b/elasticsearch-model/gemfiles/7.1.gemfile index bd1d30838..6a8e59da9 100644 --- a/elasticsearch-model/gemfiles/7.1.gemfile +++ b/elasticsearch-model/gemfiles/7.1.gemfile @@ -26,7 +26,7 @@ gemspec path: '../' gem 'activemodel', '~> 7.1' gem 'activerecord', '~> 7.1' -gem 'sqlite3' unless defined?(JRUBY_VERSION) +gem 'sqlite3', '~> 1.4' unless defined?(JRUBY_VERSION) # gem 'mongoid', '~> 6' group :development, :testing do diff --git a/elasticsearch-rails/Gemfile b/elasticsearch-rails/Gemfile index f51541c74..4be2dd014 100644 --- a/elasticsearch-rails/Gemfile +++ b/elasticsearch-rails/Gemfile @@ -32,7 +32,7 @@ group :development, :testing do gem 'pry-nav' gem 'rspec' unless defined?(JRUBY_VERSION) - gem 'sqlite3' + gem 'sqlite3', '~> 1.4' gem 'debug' end end From 6d1eeeb0232f41372286ff99579755314b3ee8e0 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 30 May 2024 15:36:50 +0100 Subject: [PATCH 580/582] [CI] Tests with latest snapshot stack version --- .github/workflows/jruby.yml | 2 +- .github/workflows/tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/jruby.yml b/.github/workflows/jruby.yml index f61c93932..91dc5ff48 100644 --- a/.github/workflows/jruby.yml +++ b/.github/workflows/jruby.yml @@ -30,7 +30,7 @@ jobs: sudo sysctl -w vm.max_map_count=262144 - uses: elastic/elastic-github-actions/elasticsearch@master with: - stack-version: 8.14.0-SNAPSHOT + stack-version: 8.15.0-SNAPSHOT security-enabled: false - uses: ruby/setup-ruby@v1 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 30474143c..e2ae0ca8b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,7 +27,7 @@ jobs: sudo sysctl -w vm.max_map_count=262144 - uses: elastic/elastic-github-actions/elasticsearch@master with: - stack-version: 8.14.0-SNAPSHOT + stack-version: 8.15.0-SNAPSHOT security-enabled: false - uses: ruby/setup-ruby@v1 with: From 92c8d16f14f2a931c0daa7339aabc23762dffd44 Mon Sep 17 00:00:00 2001 From: Fernando Briano <fernando@picandocodigo.net> Date: Thu, 30 May 2024 15:04:35 +0100 Subject: [PATCH 581/582] [DOCS] Updates CHANGELOG for 8.0.0 --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3405dea7..ce1eed141 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +## 8.0.0 + +First general availability release for `8.0.0`. Major versions of `8.x` will support Elasticsearch version `8.x` changing the `elasticsearch` dependency's major version: `gem 'elasticsearch', '~> 8'`. +All references to `type` should have been removed. Document types were deprecated and do not exist in `8.x`. + +The dependency from `elasticsearch` on `elasticsearch-transport` was updated to `elastic-transport`. All `8.x` Elasticsearch APIs supported by `elasticsearch` should now be supported on the Rails library. See [Release notes for the Elasticsearch client 8.0](https://www.elastic.co/guide/en/elasticsearch/client/ruby-api/current/release_notes_80.html) and the [8.x release notes](https://www.elastic.co/guide/en/elasticsearch/client/ruby-api/current/release_notes.html#_8_x) for more information. + +### Compatibility + +The gem is currently testing with Ruby 3.1, 3.2 and 3.3 and JRuby 9.4. Testing for Ruby `2.x` versions has been dropped as they're no longer updated or supported. Currently testing with Rails 6.1, 7.0 and 7.1. + +### Changes since 8.0.0.pre + +- Address Rails 7.1 deprecation warning in elasticsearch-rails [PR](https://github.com/elastic/elasticsearch-rails/pull/1067) +- Ensure subclasses are added to the registry [PR](https://github.com/elastic/elasticsearch-rails/pull/1073) + +### Development changes + +- Using `debug` for debugging in `development` and `testing` Gemfile groups. +- Minor general code cleanups and styling changes. +- Updated code for `elasticsearch` 8.x. + ## 7.2.1 * The default git branch `master` has been renamed to `main` From 0b92c86ce24222d7422aeb2a67a8f13dfd358681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beat=20J=C3=B6rg?= <beat.joerg@gmail.com> Date: Tue, 18 Jun 2024 22:37:05 +0200 Subject: [PATCH 582/582] Update import.rb Fixes issue #1082 --- elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb b/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb index b43d67163..dcdc82d89 100644 --- a/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb +++ b/elasticsearch-rails/lib/elasticsearch/rails/tasks/import.rb @@ -80,7 +80,6 @@ total_errors = klass.__elasticsearch__.import force: ENV.fetch('FORCE', false), batch_size: ENV.fetch('BATCH', 1000).to_i, index: ENV.fetch('INDEX', nil), - type: ENV.fetch('TYPE', nil), scope: ENV.fetch('SCOPE', nil) do |response| pbar.inc response['items'].size if pbar STDERR.flush