debian-mirror-gitlab/elasticsearch-model/test/integration/active_record_associations_test.rb
2019-12-22 22:52:31 +05:30

326 lines
11 KiB
Ruby

require 'test_helper'
require 'active_record'
module Elasticsearch
module Model
class ActiveRecordAssociationsIntegrationTest < Elasticsearch::Test::IntegrationTestCase
context "ActiveRecord associations" do
setup do
# ----- Schema definition ---------------------------------------------------------------
ActiveRecord::Schema.define(version: 1) do
create_table :categories do |t|
t.string :title
t.timestamps
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
end
create_table :authorships do |t|
t.string :first_name, :last_name
t.references :post
t.references :author
t.timestamps
end
create_table :comments do |t|
t.string :text
t.string :author
t.references :post
t.timestamps
end and add_index(:comments, :post_id)
create_table :posts do |t|
t.string :title
t.text :text
t.boolean :published
t.timestamps
end
end
# ----- Models definition -------------------------------------------------------------------------
class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
end
class Author < ActiveRecord::Base
has_many :authorships
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
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
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: {
filtered: {
query: {
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