debian-mirror-gitlab/elasticsearch-model/examples/activerecord_associations.rb
2020-03-09 13:42:32 +05:30

213 lines
5.9 KiB
Ruby

# ActiveRecord associations and Elasticsearch
# ===========================================
#
# https://github.com/rails/rails/tree/master/activerecord
# http://guides.rubyonrails.org/association_basics.html
#
# Run me with:
#
# ruby -I lib examples/activerecord_associations.rb
#
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
require 'pry'
require 'logger'
require 'ansi/core'
require 'active_record'
require 'json'
require 'elasticsearch/model'
ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
ActiveRecord::Base.establish_connection( adapter: 'sqlite3', database: ":memory:" )
# ----- Schema definition -------------------------------------------------------------------------
ActiveRecord::Schema.define(version: 1) do
create_table :categories do |t|
t.string :title
t.timestamps null: false
end
create_table :authors do |t|
t.string :first_name, :last_name
t.string :department
t.timestamps null: false
end
create_table :authorships do |t|
t.references :article
t.references :author
t.timestamps null: false
end
create_table :articles do |t|
t.string :title
t.timestamps null: false
end
create_table :articles_categories, id: false do |t|
t.references :article, :category
end
create_table :comments do |t|
t.string :text
t.references :article
t.timestamps null: false
end
add_index(:comments, :article_id) unless index_exists?(: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[2m#{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, :department], only: [:full_name, :department] },
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
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 :article, touch: true
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
has_many :authors, through: :authorships
has_many :comments
end
class Comment < ActiveRecord::Base
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
belongs_to :article, touch: true
end
# ----- Insert data -------------------------------------------------------------------------------
# Create category
#
category = Category.create title: 'One'
# Create author
#
author = Author.create first_name: 'John', last_name: 'Smith', department: 'Business'
# Create article
article = Article.create title: 'First Article'
# Assign category
#
article.categories << category
# Assign author
#
article.authors << author
# Add comment
#
article.comments.create text: 'First comment for article One'
article.comments.create text: 'Second comment for article One'
Elasticsearch::Model.client.indices.refresh index: Elasticsearch::Model::Registry.all.map(&:index_name)
# Search for a term and return records
#
puts "",
"Articles containing 'one':".ansi(:bold),
Article.search('one').records.to_a.map(&:inspect),
""
puts "",
"All Models containing 'one':".ansi(:bold),
Elasticsearch::Model.search('one').records.to_a.map(&:inspect),
""
# Difference between `records` and `results`
#
response = Article.search query: { match: { title: 'first' } }
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 "",
"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 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),
""
# ----- Pry ---------------------------------------------------------------------------------------
Pry.start(binding, prompt: lambda { |obj, nest_level, _| '> ' },
input: StringIO.new('response.records.first'),
quiet: true)