simplec 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3e3189399a08bf6dc14a452311b79f7f48eb6a16
4
- data.tar.gz: 179e5ac787ed6456ac46282808591748ba10e560
3
+ metadata.gz: 96f4e9c1f6b7ebca90065eaafc12867bd2402760
4
+ data.tar.gz: bdbd5d66111b3afa442c3cb9a1d84cd25bc7387a
5
5
  SHA512:
6
- metadata.gz: 61dcc253ee7661aaea00164e559700fedd731d5932049fb9f21e285a65fd4840e35a3c1dbc84f433cd79bf61611cf75b0bffe7abd6876a4db7ce63774dba7f72
7
- data.tar.gz: 8d0115591036074ab888821f17b04fdfdd2692bb941273a8329fa61d25104763ea1630215a06b5fac7ed714bd1ea9bfff5b1a44f93f05a1d1589f825cb51fa1e
6
+ metadata.gz: 2e60a95279a8c1aebbb8cdbfd2a1c17f8f66f8d6598a6d6fd307342a17597f95431a60bfccadf100f0c5df526bb0bc9d8b930741e92218791bcf0617f816ba31
7
+ data.tar.gz: 3a267018544e4d5c9c5990beee28b1d694ce24055d70f7156481830a2c0de3a825d58ccebfbef14f787e3dc4a33b429b94703b8c83bb640393b05e067dbc9ca2
@@ -25,6 +25,7 @@ module Simplec
25
25
  end
26
26
 
27
27
  def validate_slug!
28
+ return if self.slug.nil?
28
29
  similar = self.class.where(slug: self.slug).where.not(id: self.id)
29
30
  return if similar.size == 0
30
31
  return if (
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Simplec
2
- VERSION = '0.6.0'
2
+ VERSION = '0.7.0'
3
3
  end
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.6.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-16 00:00:00.000000000 Z
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