simplec 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/models/simplec/document.rb +1 -0
- data/app/models/simplec/page.rb +162 -2
- data/db/migrate/20170917144923_add_search_to_simplec_pages.rb +58 -0
- data/lib/simplec/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96f4e9c1f6b7ebca90065eaafc12867bd2402760
|
4
|
+
data.tar.gz: bdbd5d66111b3afa442c3cb9a1d84cd25bc7387a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e60a95279a8c1aebbb8cdbfd2a1c17f8f66f8d6598a6d6fd307342a17597f95431a60bfccadf100f0c5df526bb0bc9d8b930741e92218791bcf0617f816ba31
|
7
|
+
data.tar.gz: 3a267018544e4d5c9c5990beee28b1d694ce24055d70f7156481830a2c0de3a825d58ccebfbef14f787e3dc4a33b429b94703b8c83bb640393b05e067dbc9ca2
|
data/app/models/simplec/page.rb
CHANGED
@@ -55,6 +55,7 @@ module Simplec
|
|
55
55
|
before_validation :match_parent_subdomain
|
56
56
|
before_validation :build_path
|
57
57
|
after_save :link_embedded_images!
|
58
|
+
after_save :index!
|
58
59
|
|
59
60
|
# @!attribute slug
|
60
61
|
# The value is normalized to a string starting without a leading slash
|
@@ -86,6 +87,37 @@ module Simplec
|
|
86
87
|
# know what you are doing.
|
87
88
|
# @return [JSON]
|
88
89
|
|
90
|
+
# @!method search(term, options={})
|
91
|
+
#
|
92
|
+
# If the term is nil or blank, all results will be returned.
|
93
|
+
#
|
94
|
+
# @param term [String]
|
95
|
+
# @param options [Hash]
|
96
|
+
# @option options [Class, Array, String] :types
|
97
|
+
# A Class or Array of Classes (of page types, typically `Page::Home`) to
|
98
|
+
# limit results to.
|
99
|
+
# @option options [Symbol, String] all other options
|
100
|
+
# All other options are matched to the `query` JSONB field. These are
|
101
|
+
# all direct matches on the indexed `query` field. If you want to do
|
102
|
+
# anything more complicated, append it to the scope returned.
|
103
|
+
#
|
104
|
+
# @return [ActiveRecord::Relation] a relation for the query
|
105
|
+
# @!scope class
|
106
|
+
scope :search, ->(term, options={}) {
|
107
|
+
_types = Array(options.delete(:types))
|
108
|
+
|
109
|
+
query = all
|
110
|
+
query = query.where(type: _types) if _types.any?
|
111
|
+
options.each { |k,v| query = query.where("query->>:k = :v", k: k, v: v) }
|
112
|
+
|
113
|
+
if term.blank?
|
114
|
+
query
|
115
|
+
else
|
116
|
+
tsq = tsquery term
|
117
|
+
query.where("tsv @@ #{tsq}").order("ts_rank_cd(tsv, #{tsq}) DESC")
|
118
|
+
end
|
119
|
+
}
|
120
|
+
|
89
121
|
# Define a field on the page
|
90
122
|
#
|
91
123
|
# There is as template for each type for customization located in:
|
@@ -140,12 +172,86 @@ module Simplec
|
|
140
172
|
_fields = case type
|
141
173
|
when :file
|
142
174
|
fields.select {|k, v| FILE_FIELDS.member?(v[:type])}
|
175
|
+
when :textual
|
176
|
+
fields.select {|k, v| !FILE_FIELDS.member?(v[:type])}
|
143
177
|
else
|
144
178
|
fields
|
145
179
|
end
|
146
180
|
_fields.keys
|
147
181
|
end
|
148
182
|
|
183
|
+
# Set extra attributes on the record for querying.
|
184
|
+
#
|
185
|
+
# @example set attributes
|
186
|
+
# class Page::Home < Page
|
187
|
+
# has_many :tags
|
188
|
+
#
|
189
|
+
# field :category
|
190
|
+
#
|
191
|
+
# # Where category is a Simplec::Page::field and tags is a defined
|
192
|
+
# # method.
|
193
|
+
# search_query_attributes! :category, :tags
|
194
|
+
#
|
195
|
+
# def tags
|
196
|
+
# self.tags.pluck(:name)
|
197
|
+
# end
|
198
|
+
# end
|
199
|
+
#
|
200
|
+
# # Built-in matching
|
201
|
+
# Page.search('foo', category: 'how-to')
|
202
|
+
#
|
203
|
+
# # Manual matching
|
204
|
+
# Page.search('bar').where("query->>'tags' IN ('home', 'garden')")
|
205
|
+
#
|
206
|
+
def self.search_query_attributes!(*args)
|
207
|
+
@_search_query_attrs = args.map(&:to_sym)
|
208
|
+
end
|
209
|
+
|
210
|
+
# Get extra attributes on the record for querying.
|
211
|
+
#
|
212
|
+
# See #search_query_attributes! for more information.
|
213
|
+
#
|
214
|
+
# @return [Array] of attributes
|
215
|
+
def self.search_query_attributes
|
216
|
+
@_search_query_attrs = Set.new(@_search_query_attrs).add(:id).to_a
|
217
|
+
end
|
218
|
+
|
219
|
+
# Index every record.
|
220
|
+
#
|
221
|
+
# Internally this method iterates over all pages in batches of 3.
|
222
|
+
#
|
223
|
+
# @return [NilClass]
|
224
|
+
def self.index!
|
225
|
+
find_each(batch_size: 3) { |page| page.index! }
|
226
|
+
end
|
227
|
+
|
228
|
+
# Create a to_tsquery statement.
|
229
|
+
#
|
230
|
+
# Mainly used internally, but could be used in custom queries.
|
231
|
+
#
|
232
|
+
# @param input [String] string to be queried
|
233
|
+
# @param options [Hash] optional
|
234
|
+
# @option options [String] :language defaults to 'english'
|
235
|
+
# This is really a future addition, all of the tsvector fields are set to
|
236
|
+
# 'english'.
|
237
|
+
#
|
238
|
+
# @return [String] a to_tsquery statement
|
239
|
+
def self.tsquery(input, options={})
|
240
|
+
options[:language] ||= 'english'
|
241
|
+
value = input.to_s.strip
|
242
|
+
value = value.
|
243
|
+
gsub('(', '').
|
244
|
+
gsub(')', '').
|
245
|
+
gsub(%q('), '').
|
246
|
+
gsub(' ', '\\ ').
|
247
|
+
gsub(':', '').
|
248
|
+
gsub("\t", '').
|
249
|
+
gsub("!", '')
|
250
|
+
value << ':*'
|
251
|
+
query = "to_tsquery(?, ?)"
|
252
|
+
sanitize_sql_array([query, options[:language], value])
|
253
|
+
end
|
254
|
+
|
149
255
|
# Return field options for building forms.
|
150
256
|
#
|
151
257
|
def field_options
|
@@ -156,7 +262,6 @@ module Simplec
|
|
156
262
|
#
|
157
263
|
# This is a recursive, expensive call.
|
158
264
|
#
|
159
|
-
# @return [Array] of parent Pages
|
160
265
|
def parents
|
161
266
|
page, parents = self, Array.new
|
162
267
|
while page.parent
|
@@ -225,9 +330,64 @@ module Simplec
|
|
225
330
|
@layouts ||= Subdomain.new.layouts
|
226
331
|
end
|
227
332
|
|
333
|
+
# Index this record for search.
|
334
|
+
#
|
335
|
+
# Internally, this method uses update_columns so it can be used in
|
336
|
+
# `after_save` callbacks, etc.
|
337
|
+
#
|
338
|
+
# @return [Boolean] success
|
339
|
+
def index!
|
340
|
+
set_search_text!
|
341
|
+
set_query_attributes!
|
342
|
+
update_columns text: self.text, query: self.query
|
343
|
+
end
|
344
|
+
|
345
|
+
# Extract text out of HTML or plain strings. Basically removes html
|
346
|
+
# formatting.
|
347
|
+
#
|
348
|
+
# @param attributes [Symbol, String]
|
349
|
+
# variable list of attributes or methods to be extracted for search
|
350
|
+
#
|
351
|
+
# @return [String] content of each attribute separated by new lines
|
352
|
+
def extract_search_text(*attributes)
|
353
|
+
Array(attributes).map { |meth|
|
354
|
+
Nokogiri::HTML(self.send(meth)).xpath("//text()").
|
355
|
+
map {|node| text = node.text; text.try(:strip!); text}.join(" ")
|
356
|
+
}.reject(&:blank?).join("\n")
|
357
|
+
end
|
358
|
+
|
359
|
+
# Set the text which will be index.
|
360
|
+
#
|
361
|
+
# a title, meta_description
|
362
|
+
# b slug, path (non-printable, add tags, added terms)
|
363
|
+
# c textual fields
|
364
|
+
# d (reserved for sub-records, etc)
|
365
|
+
#
|
366
|
+
# 'a' correlates to 'A' priority in Postgresql. For more information:
|
367
|
+
# https://www.postgresql.org/docs/9.6/static/functions-textsearch.html
|
368
|
+
#
|
369
|
+
def set_search_text!
|
370
|
+
self.text['a'] = extract_search_text :title, :meta_description
|
371
|
+
self.text['b'] = extract_search_text :slug, :path
|
372
|
+
self.text['c'] = extract_search_text *self.class.field_names(:textual)
|
373
|
+
self.text['d'] = nil
|
374
|
+
end
|
375
|
+
|
376
|
+
# Build query attribute hash.
|
377
|
+
#
|
378
|
+
# Internally stored as JSONB.
|
379
|
+
#
|
380
|
+
# @return [Hash] to be set for query attribute
|
381
|
+
def set_query_attributes!
|
382
|
+
attr_names = self.class.search_query_attributes.map(&:to_s)
|
383
|
+
self.query = attr_names.inject({}) { |memo, attr|
|
384
|
+
memo[attr] = self.send(attr)
|
385
|
+
memo
|
386
|
+
}
|
387
|
+
end
|
388
|
+
|
228
389
|
# @!visibility private
|
229
390
|
module Normalizers
|
230
|
-
|
231
391
|
def slug=(val)
|
232
392
|
val = val ? val.to_s.split('/').reject(&:blank?).join('/') : val
|
233
393
|
super val
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class AddSearchToSimplecPages < ActiveRecord::Migration[5.0]
|
2
|
+
def change
|
3
|
+
add_column :simplec_pages, :tsv, :tsvector
|
4
|
+
add_column :simplec_pages, :query, :jsonb
|
5
|
+
add_column :simplec_pages, :text, :jsonb
|
6
|
+
|
7
|
+
add_index :simplec_pages, :tsv, using: :gin
|
8
|
+
|
9
|
+
reversible do |dir|
|
10
|
+
dir.up {
|
11
|
+
execute <<-SQL.gsub(/\s+/, " ").strip
|
12
|
+
ALTER TABLE simplec_pages ALTER COLUMN query
|
13
|
+
SET DEFAULT '{}'::JSONB;
|
14
|
+
ALTER TABLE simplec_pages ALTER COLUMN text
|
15
|
+
SET DEFAULT '{"a": null, "b": null, "c": null, "d": null}'::JSONB;
|
16
|
+
|
17
|
+
UPDATE simplec_pages
|
18
|
+
SET
|
19
|
+
query = '{}'::JSONB,
|
20
|
+
text = '{"a": null, "b": null, "c": null, "d": null}'::JSONB;
|
21
|
+
|
22
|
+
CREATE INDEX simplec_pages_query ON simplec_pages
|
23
|
+
USING GIN (query jsonb_path_ops);
|
24
|
+
|
25
|
+
CREATE FUNCTION simplec_pages_search_tsvector_update_trigger() RETURNS trigger AS $$
|
26
|
+
begin
|
27
|
+
new.tsv :=
|
28
|
+
setweight(
|
29
|
+
to_tsvector('pg_catalog.english', coalesce(new.text ->> 'a','')), 'A'
|
30
|
+
) ||
|
31
|
+
setweight(
|
32
|
+
to_tsvector('pg_catalog.english', coalesce(new.text ->> 'b','')), 'B'
|
33
|
+
) ||
|
34
|
+
setweight(
|
35
|
+
to_tsvector('pg_catalog.english', coalesce(new.text ->> 'c','')), 'C'
|
36
|
+
) ||
|
37
|
+
setweight(
|
38
|
+
to_tsvector('pg_catalog.english', coalesce(new.text ->> 'd','')), 'D'
|
39
|
+
);
|
40
|
+
return new;
|
41
|
+
end
|
42
|
+
|
43
|
+
$$ LANGUAGE plpgsql;
|
44
|
+
CREATE TRIGGER simplec_pages_search_tsvector_update BEFORE INSERT OR UPDATE
|
45
|
+
ON simplec_pages FOR EACH ROW
|
46
|
+
EXECUTE PROCEDURE simplec_pages_search_tsvector_update_trigger();
|
47
|
+
SQL
|
48
|
+
}
|
49
|
+
dir.down {
|
50
|
+
execute <<-SQL.gsub(/\s+/, " ").strip
|
51
|
+
DROP TRIGGER IF EXISTS simplec_pages_search_tsvector_update ON simplec_pages;
|
52
|
+
DROP FUNCTION IF EXISTS simplec_pages_search_tsvector_update_trigger();
|
53
|
+
DROP INDEX simplec_pages_query;
|
54
|
+
SQL
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/simplec/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simplec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Smith
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-09-
|
11
|
+
date: 2017-09-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -153,6 +153,7 @@ files:
|
|
153
153
|
- db/migrate/20170809210304_create_simplec_embedded_images.rb
|
154
154
|
- db/migrate/20170814211816_create_simplec_documents.rb
|
155
155
|
- db/migrate/20170814211929_create_simplec_document_sets.rb
|
156
|
+
- db/migrate/20170917144923_add_search_to_simplec_pages.rb
|
156
157
|
- lib/simplec.rb
|
157
158
|
- lib/simplec/action_controller/extensions.rb
|
158
159
|
- lib/simplec/action_view/helper.rb
|