forked from elastic/elasticsearch-rails
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathactiverecord_associations.rb
213 lines (161 loc) · 5.86 KB
/
activerecord_associations.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# 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)