plok 1.0.0 → 1.0.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8824645af9b5b16c2e3540eb298a43be8df6b0e1539d2274bf833d62a4ee2c9b
4
- data.tar.gz: 53298ba35f0393728429121aeb7ace1ebafca3b744bb3f47e584478eea6ae6e3
3
+ metadata.gz: caeb408fa4077fd24c19a153f273eb83f21d1da02a601035838e054080b80f85
4
+ data.tar.gz: e9ebfd53e89fdb4adddfb9498e2c7411ce994caf53039e0136459e90dd4a838e
5
5
  SHA512:
6
- metadata.gz: 54f0aec7e6af22ec5f1e9d988880ebf03e110a5c8f619169c1ad1a5a82188c11f9da0b28a71189cd9aac21824921223e035cc718d0c2de7e661812cc715d7576
7
- data.tar.gz: 4a274b94c88692dadd40061cd90778a7c0c2d7c5d931ef0c123e2b1a6e13ead58331f60e26d3c8c14fdeb709a414b7d93d44be072e3b4b85b74710c8a2f959cd
6
+ metadata.gz: 728e58a7153d8bb7bc4e19fde5b676ebec49d671b93d724b0c895d261e07e77bd7c6c347bf97c0dbf6dbc80e9c510e0c3f0bdf161713cf38b407a758f3da6ad0
7
+ data.tar.gz: d14e3d051253c34406c8736d4785b2ea7a1fba11f9e1dc46302b4ace6965d56f599b847ea604838aef90822595b7a526bcf18958f1e86a395e6a39a0c186cb18
@@ -0,0 +1,40 @@
1
+ /*
2
+ * jQuery UI Autocomplete HTML Extension
3
+ *
4
+ * Copyright 2010, Scott González (http://scottgonzalez.com)
5
+ * Dual licensed under the MIT or GPL Version 2 licenses.
6
+ *
7
+ * http://github.com/scottgonzalez/jquery-ui-extensions
8
+ */
9
+ (function( $ ) {
10
+
11
+ var proto = $.ui.autocomplete.prototype,
12
+ initSource = proto._initSource;
13
+
14
+ function filter( array, term ) {
15
+ var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
16
+ return $.grep( array, function(value) {
17
+ return matcher.test( $( "<div>" ).html( value.label || value.value || value ).text() );
18
+ });
19
+ }
20
+
21
+ $.extend( proto, {
22
+ _initSource: function() {
23
+ if ( this.options.html && $.isArray(this.options.source) ) {
24
+ this.source = function( request, response ) {
25
+ response( filter( this.options.source, request.term ) );
26
+ };
27
+ } else {
28
+ initSource.call( this );
29
+ }
30
+ },
31
+
32
+ _renderItem: function( ul, item) {
33
+ return $( "<li></li>" )
34
+ .data( "item.autocomplete", item )
35
+ .append( $( "<a></a>" )[ this.options.html ? "html" : "text" ]( item.label ) )
36
+ .appendTo( ul );
37
+ }
38
+ });
39
+
40
+ })( jQuery );
@@ -0,0 +1,26 @@
1
+ var search = search || {
2
+ init: function() {
3
+ this.target().autocomplete({
4
+ minLength: 2,
5
+ source: search.target().parents('form').attr('action'),
6
+ select: search.select,
7
+ html: true
8
+ }).on('keypress', this.keypress_listener);
9
+ },
10
+
11
+ keypress_listener: function(e) {
12
+ var code = (e.keyCode ? e.keyCode : e.which);
13
+ if(code == 13) return false;
14
+ },
15
+
16
+ select: function(event, ui) {
17
+ window.location = ui.item.value;
18
+ return false;
19
+ },
20
+
21
+ target: function() {
22
+ return $('#search input');
23
+ }
24
+ };
25
+
26
+ $(function(){ search.init(); });
@@ -0,0 +1,43 @@
1
+ .ui-autocomplete {
2
+ width: 24rem !important;
3
+ padding: 0;
4
+ z-index: 9999;
5
+ filter: drop-shadow(5px 5px 5px #666);
6
+ }
7
+
8
+ // Shows up during autocompletion sometimes.
9
+ .ui-helper-hidden-accessible {
10
+ display: none;
11
+ }
12
+
13
+ li.ui-menu-item {
14
+ list-style: none;
15
+ cursor: pointer;
16
+ border-bottom: 1px solid #ccc;
17
+ padding: 5px;
18
+
19
+ &:last-child {
20
+ border: none;
21
+ margin-bottom: 0;
22
+ padding-bottom: 0;
23
+ }
24
+
25
+ &:hover {
26
+ background-color: $primary;
27
+ color: white !important;
28
+ }
29
+
30
+ a.ui-menu-item-wrapper {
31
+ border: none;
32
+
33
+ &.ui-state-active {
34
+ background: none;
35
+ color: inherit;
36
+ text-decoration: none;
37
+
38
+ .text-muted {
39
+ color: white !important;
40
+ }
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,96 @@
1
+ module Plok::Searchable
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ has_many :search_indices, as: :searchable, dependent: :destroy
6
+
7
+ # The after_save block creates or saves indices for every indicated
8
+ # searchable field. Takes both translations and flexible content into
9
+ # account.
10
+ #
11
+ # Translation support was relatively painless, but FlexibleContent
12
+ # required more thought. See #save_flexible_content_indices!
13
+ after_save do
14
+ trigger_indices_save!
15
+ end
16
+
17
+ def trigger_indices_save!
18
+ self.class.searchable_fields_list.each do |key|
19
+ if key == :flexible_content
20
+ save_flexible_content_search_indices! && next
21
+ end
22
+
23
+ if respond_to?(:translatable?) && self.class.translatable_fields_list.include?(key)
24
+ save_translatable_search_index!(key) && next
25
+ end
26
+
27
+ save_search_index!(key)
28
+ end
29
+ end
30
+
31
+ # I save all ContentText#content values as indices linked to the parent
32
+ # object. The Plok::Search::Base#indices method already groups by
33
+ # searchable resource, so there are never any duplicates.
34
+ # Only additional matches for the searchable because it takes
35
+ # ContentText#content sources into account.
36
+ #
37
+ # ContentColumn and ContentText have after_{save,destroy} callbacks
38
+ # to help facilitate searchable management. Note that said code was
39
+ # initially present in this class, and it was such a mess that it became
40
+ # unpractical to maintain.
41
+ def save_flexible_content_search_indices!
42
+ content_rows.each do |row|
43
+ row.columns.each do |column|
44
+ next unless column.content.is_a?(ContentText)
45
+ key = "flexible_content:#{column.content_id}"
46
+ save_index!(key, value: column.content.content, locale: row.locale)
47
+ end
48
+ end
49
+ end
50
+
51
+ def save_index!(key, value: nil, locale: nil)
52
+ value = read_attribute(key) if value.blank? && respond_to?(key)
53
+ return if value.blank?
54
+
55
+ search_indices
56
+ .find_or_create_by!(name: key, locale: locale)
57
+ .update_column(value: value)
58
+ end
59
+
60
+ # This exists so we can use #save_search_index! as the main method
61
+ # to override, and then be able to call #save_index! in the overridden
62
+ # method to accommodate further defaults.
63
+ def save_search_index!(key)
64
+ value = read_attribute(key)
65
+ save_index!(key, value: value)
66
+ end
67
+
68
+ def save_translatable_search_index!(key)
69
+ # TODO: locales can't be hardcoded
70
+ %w(nl fr).each do |locale|
71
+ value = translation(locale.to_sym).send(key)
72
+ save_index!(key, value: value, locale: locale)
73
+ end
74
+ end
75
+
76
+ def searchable?
77
+ true
78
+ end
79
+ end
80
+
81
+ module ClassMethods
82
+ def searchable_field(key)
83
+ unless searchable_fields_list.include?(key.to_sym)
84
+ searchable_fields_list << key.to_sym
85
+ end
86
+ end
87
+
88
+ def searchable_fields(*args)
89
+ args.each { |key| searchable_field(key) }
90
+ end
91
+
92
+ def searchable_fields_list
93
+ @searchable_fields_list ||= []
94
+ end
95
+ end
96
+ end
@@ -55,6 +55,13 @@ class QueuedTask < ActiveRecord::Base
55
55
  destroy
56
56
  end
57
57
 
58
+ # You want this separate from #unlock so the ensure block in #process! does
59
+ # not keep resetting attempts when it shouldn't. This should be called
60
+ # from the controller where you manually unlock tasks in your backend.
61
+ def reset_attempts!
62
+ update_column(:attempts, 0)
63
+ end
64
+
58
65
  def increase_attempts!
59
66
  update_column(:attempts, attempts + 1)
60
67
  end
@@ -0,0 +1,5 @@
1
+ class SearchIndex < ActiveRecord::Base
2
+ belongs_to :searchable, polymorphic: true
3
+
4
+ validates :name, presence: true
5
+ end
@@ -0,0 +1,11 @@
1
+ class SearchModule < ActiveRecord::Base
2
+ scope :weighted, -> { order('weight DESC') }
3
+
4
+ validates :name, presence: true
5
+
6
+ def indices
7
+ SearchIndex
8
+ .joins('INNER JOIN search_modules ON search_indices.searchable_type = search_modules.name')
9
+ .where('search_modules.name = ?', name)
10
+ end
11
+ end
@@ -1,10 +1,3 @@
1
- def class_exists?(class_name)
2
- klass = Module.const_get(class_name)
3
- return klass.is_a?(Class)
4
- rescue NameError
5
- return false
6
- end
7
-
8
1
  class Module
9
2
  def takes(*arg_names)
10
3
  define_method(:initialize) do |*arg_values|
@@ -15,6 +8,7 @@ class Module
15
8
  end
16
9
 
17
10
  instance_variable_set(:"@#{name}", value)
11
+ singleton_class.instance_eval { attr_reader name.to_sym }
18
12
  end
19
13
  end
20
14
  end
@@ -0,0 +1,19 @@
1
+ # This migration originally comes from udongo_engine
2
+ class CreateSearchIndices < ActiveRecord::Migration[6.1]
3
+ def change
4
+ return if table_exists?(:search_indices)
5
+
6
+ create_table :search_indices do |t|
7
+ t.string :searchable_type
8
+ t.integer :searchable_id
9
+ t.string :locale, index: true
10
+ t.string :name
11
+ t.text :value
12
+
13
+ t.timestamps
14
+ end
15
+
16
+ add_index :search_indices, [:searchable_type, :searchable_id]
17
+ add_index :search_indices, [:locale, :name]
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ # This migration originally comes from udongo_engine
2
+ class CreateSearchModules < ActiveRecord::Migration[6.1]
3
+ def change
4
+ return if table_exists?(:search_modules)
5
+
6
+ create_table :search_modules do |t|
7
+ t.string :name
8
+ t.boolean :searchable
9
+ t.integer :weight
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :search_modules, [:name, :searchable]
15
+ end
16
+ end
@@ -2,13 +2,13 @@
2
2
  <% href ||= defined?(path) ? path : "#nav-#{name}" %>
3
3
 
4
4
  <%= content_tag :li, class: 'top-level' do %>
5
- <%= link_to href, class: 'top-level-anchor link-light', data: { bs_toggle: ('collapse' unless defined?(path)) } do %>
5
+ <%= link_to href, class: 'top-level-anchor link-light', data: { bs_toggle: (defined?(href) ? 'tooltip' : 'collapse') }, title: lbl do %>
6
6
  <%= fa_icon icon, class: 'fa-fw' %>
7
7
  <%= content_tag :span, lbl, class: 'text' %>
8
8
  <% end %>
9
9
 
10
- <% unless defined?(path) %>
11
- <%= content_tag :div, id: "nav-#{name}", class: 'collapse' do %>
10
+ <% if defined?(path) %>
11
+ <%= content_tag :div, id: path, class: 'collapse' do %>
12
12
  <h4><%= lbl %></h4>
13
13
  <%= yield %>
14
14
  <% end %>
data/lib/plok/engine.rb CHANGED
@@ -22,14 +22,14 @@ module Plok
22
22
  # Autoload classes from the lib dir
23
23
  config.autoload_paths << File.expand_path('../..', __FILE__)
24
24
 
25
- def class_exists?(class_name)
25
+ def self.class_exists?(class_name)
26
26
  klass = Module.const_get(class_name.to_s)
27
27
  klass.is_a?(Class)
28
28
  rescue NameError
29
29
  return false
30
30
  end
31
31
 
32
- def module_exists?(module_name)
32
+ def self.module_exists?(module_name)
33
33
  # By casting to a string and making a constant, we can assume module_name
34
34
  # can be either one without it being a problem.
35
35
  module_name.to_s.constantize.is_a?(Module)
@@ -0,0 +1,22 @@
1
+ # Due to the load order of classes, Backend precedes the required Base class.
2
+ require_relative 'base'
3
+
4
+ module Plok::Search
5
+ # The goal of this class is to provide a manipulated version of the filtered
6
+ # index data that we can use in the result set of an autocomplete-triggered
7
+ # search query. See Plok::Search::Base for more information on how this
8
+ # search functionality is designed.
9
+ class Backend < Plok::Search::Base
10
+ # This translates the filtered indices into meaningful result objects.
11
+ # These require a { label: ... value: ... } to accommodate jquery-ui.
12
+ #
13
+ # Note that the result_object#url method is defined in
14
+ # Plok::Search::ResultObjects::Backend::Page.
15
+ def search
16
+ indices.map do |index|
17
+ result = result_object(index)
18
+ { label: result.build_html, value: result.url }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,71 @@
1
+ module Plok::Search
2
+ # The goal of the Base class is to filter our indices on the given search
3
+ # term. Further manipulation of individual index data into a meaningful
4
+ # result set (think autocomplete results) is done by extending this class.
5
+ #
6
+ # Examples of class extensions could be:
7
+ # Plok::Search::Backend - included in Plok
8
+ # Plok::Search::Frontend
9
+ # Plok::Search::Api
10
+ #
11
+ # The primary benefit in having these namespaced search interfaces is to
12
+ # provide a way for the developer to have different result objects for
13
+ # each resource.
14
+ #
15
+ # Example #1: A search request for a specific Page instance in the frontend
16
+ # will typically return a link to said page. However, in search requests made
17
+ # in the backend for the same Page instance, you'd expect a link to a form in
18
+ # the backend Page module where you can edit the page's contents.
19
+ #
20
+ # Example #2: Some autocompletes in a frontend namespace might require an
21
+ # image or a price to be included in its body.
22
+ #
23
+ # However these result objects are structured are also up to the developer.
24
+ class Base
25
+ attr_reader :term, :controller
26
+
27
+ def initialize(term, controller: nil, namespace: nil)
28
+ @term = Plok::Search::Term.new(term, controller: controller)
29
+ @controller = controller
30
+ @namespace = namespace
31
+ end
32
+
33
+ def indices
34
+ # Having the searchmodules sorted by weight returns indices in the
35
+ # correct order.
36
+ @indices ||= SearchModule.weighted.inject([]) do |stack, m|
37
+ # The group happens to make sure we end up with just 1 copy of
38
+ # a searchable result. Otherwise matches from both an indexed
39
+ # Page#title and Page#description would be in the result set.
40
+ stack << m.indices
41
+ .where(
42
+ '(searchable_id = ? OR search_indices.value LIKE ?)',
43
+ term.value,
44
+ "%#{term.value}%"
45
+ )
46
+ .group([:searchable_type, :searchable_id])
47
+ end.flatten
48
+ end
49
+
50
+ def namespace
51
+ # This looks daft, but it gives us a foot in the door for when a frontend
52
+ # search is triggered in the backend.
53
+ return @namespace unless @namespace.nil?
54
+ return 'Frontend' if controller.nil?
55
+ controller.class.module_parent.to_s
56
+ end
57
+
58
+ # In order to provide a good result set in a search autocomplete, we have
59
+ # to translate the raw index to a class that makes an index adhere
60
+ # to a certain interface (that can include links).
61
+ def result_object(index)
62
+ klass = "Plok::Search::ResultObjects::#{namespace}::#{index.searchable_type}"
63
+ klass = 'Plok::Search::ResultObjects::Base' unless result_object_exists?(klass)
64
+ klass.constantize.new(index, search_context: self)
65
+ end
66
+
67
+ def result_object_exists?(name)
68
+ Plok::Engine.class_exists?(name) && name.constantize.method_defined?(:build_html)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,74 @@
1
+ module Plok
2
+ class InterfaceNotImplementedError < NoMethodError
3
+ end
4
+
5
+ class Search::ResultObjects::Base
6
+ attr_reader :index, :search_context
7
+
8
+ delegate :searchable, to: :index
9
+
10
+ def initialize(index, search_context: nil)
11
+ @index = index
12
+ @search_context = search_context
13
+ end
14
+
15
+ # Typically, an autocomplete requires 3 things:
16
+ #
17
+ # * A title indicating a resource name.
18
+ # Examples: Page#title, Product#name,...
19
+ # * A truncated summary providing a glimpse of the resource's contents.
20
+ # Examples: Page#subtitle, Product#description,...
21
+ # * A link to the resource.
22
+ # Examples: edit_backend_page_path(37), product_path(37),...
23
+ #
24
+ # However, this seems very restrictive to me. If I narrow down the data
25
+ # a dev can use in an autocomplete, it severely reduces options he/she has
26
+ # in how the autocomplete results look like. Think of autocompletes in a
27
+ # shop that require images or prices to be included in their result bodies.
28
+ #
29
+ # This is why I chose to let ApplicationController.render work around the
30
+ # problem by letting the dev decide how the row should look.
31
+ #
32
+ def build_html
33
+ ApplicationController.render(partial: partial, locals: locals)
34
+ end
35
+
36
+ def hidden?
37
+ searchable.respond_to?(:visible?) && searchable.hidden?
38
+ end
39
+
40
+ def label
41
+ # We want to grab the name of the index from ContentText whenever
42
+ # we're dealing with FlexibleContent stuff.
43
+ if index.name.include?('flexible_content')
44
+ id = index.name.split(':')[1].to_i
45
+ return ContentText.find(id).content
46
+ end
47
+
48
+ searchable.send(index.name)
49
+ end
50
+
51
+ def locals
52
+ { "#{partial_target}": index.searchable, index: index }
53
+ end
54
+
55
+ def partial
56
+ "#{partial_path}/#{partial_target}"
57
+ end
58
+
59
+ def partial_path
60
+ "#{search_context.namespace.to_s.underscore}/search"
61
+ end
62
+
63
+ def partial_target
64
+ index.searchable_type.underscore
65
+ end
66
+
67
+ def unpublished?
68
+ searchable.respond_to?(:published?) && searchable.unpublished?
69
+ end
70
+
71
+ def url
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,23 @@
1
+ module Plok::Search
2
+ class Term
3
+ attr_reader :controller, :string
4
+
5
+ def initialize(string, controller: nil)
6
+ @string = string
7
+ @controller = controller
8
+ end
9
+
10
+ def locale
11
+ return controller.locale if controller.present?
12
+ :nl
13
+ end
14
+
15
+ def valid?
16
+ @string.present?
17
+ end
18
+
19
+ def value
20
+ string
21
+ end
22
+ end
23
+ end
data/lib/plok/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Plok
2
- VERSION = '1.0.0'
2
+ VERSION = '1.0.1'
3
3
  end
@@ -1,4 +1,11 @@
1
- # desc "Explaining what the task does"
2
- # task :plok do
3
- # # Task goes here
4
- # end
1
+ namespace 'plok:search' do
2
+ desc 'Rebuild all search indices'
3
+ task rebuild_indices: :environment do
4
+ SearchIndex.destroy_all
5
+ SearchModule.where(searchable: true).each do |m|
6
+ puts "Rebuilding #{m.name} indices..."
7
+ m.name.constantize.all.each(&:trigger_indices_save!)
8
+ end
9
+ puts "Done."
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :search_index do
3
+ locale { 'nl' }
4
+ name { 'foo' }
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ FactoryBot.define do
2
+ factory :search_module do
3
+ name { 'Page' }
4
+ end
5
+ end
@@ -0,0 +1,95 @@
1
+ # This exists so one is able to mock actual ActiveRecord model classes that are
2
+ # not tied to a database. This is useful if you want to write tests for concerns
3
+ # without tying them to project-specific models.
4
+ #
5
+ # * It allows you to not mock simple ActiveRecord DSL like find/find_by/where
6
+ # calls.
7
+ # * Projects using Plok can also use this, provided they call
8
+ # Plok::Engine.load_spec_supports in their spec/rails_helper.rb file.
9
+ # * Even if you're required to mock additional business logic onto your classes,
10
+ # you can just extend from Plok::FakeArModel to fill in gaps.
11
+ #
12
+ # Example:
13
+ #
14
+ # class Product < Plok::FakeArModel
15
+ # def visible?
16
+ # true
17
+ # end
18
+ #
19
+ # def hidden?
20
+ # !visible?
21
+ # end
22
+ # end
23
+ #
24
+ class Plok::FakeArModel
25
+ extend ActiveModel::Naming
26
+ extend ActiveModel::Translation
27
+ include ActiveModel::Validations
28
+ include ActiveModel::Conversion
29
+ include ActiveModel::Dirty
30
+
31
+ def initialize(attributes = {})
32
+ attributes.each do |key, value|
33
+ instance_variable_set(:"@#{key}", value)
34
+ self.class.send(:attr_accessor, key)
35
+ end
36
+ end
37
+
38
+ def self.collection(list)
39
+ @collection = list.to_a.map { |values| new(values) }
40
+ end
41
+
42
+ def self.primary_key
43
+ :id
44
+ end
45
+
46
+ def self.polymorphic_name
47
+ :fake_ar_model
48
+ end
49
+
50
+ def self.current_scope
51
+ end
52
+
53
+ def self.find(id)
54
+ @collection.to_a.detect { |o| o.id == id }
55
+ end
56
+
57
+ def self.find_by(arguments)
58
+ where(arguments)&.first
59
+ end
60
+
61
+ def self.where(arguments = nil)
62
+ @collection.select do |o|
63
+ if arguments.is_a?(Hash)
64
+ !!arguments.detect { |k, v| o.send(k) == v }
65
+ elsif(arguments.is_a?(String))
66
+ # NOTE: This will require intensive tinkering if we ever need it,
67
+ # so lets leave it for now.
68
+ []
69
+ end
70
+ end
71
+ end
72
+
73
+ def destroyed?
74
+ false
75
+ end
76
+
77
+ def new_record?
78
+ true
79
+ end
80
+
81
+ def save(options = {})
82
+ true
83
+ end
84
+
85
+ def _read_attribute(a)
86
+ a
87
+ end
88
+
89
+ private
90
+
91
+ def collection
92
+ self.class.instance_variable_get(:@collection)&.to_a || []
93
+ end
94
+
95
+ end
@@ -0,0 +1,100 @@
1
+ require 'rails_helper'
2
+
3
+ shared_examples_for :searchable do
4
+ let(:klass) { described_class.to_s.underscore.to_sym }
5
+
6
+ describe 'after_save' do
7
+ context 'non-translatable model' do
8
+ before do
9
+ # Because this is a spec/support included through it_behaves_like,
10
+ # we have to mock the entry points of the data used in
11
+ # Plok::Searchable.
12
+ allow(described_class).to receive(:searchable_fields_list) { [:foo] }
13
+ if described_class.respond_to?(:translatable_fields_list)
14
+ allow(described_class).to receive(:translatable_fields_list) { [] }
15
+ end
16
+ described_class.define_method(:foo) { 'bar' }
17
+ end
18
+
19
+ it 'default' do
20
+ subject = build(klass)
21
+ allow(subject).to receive(:foo) { 'bar' }
22
+ expect(subject.search_indices).to eq []
23
+ end
24
+
25
+ # NOTE: See if you can dynamically test the creation of indices on save.
26
+ end
27
+
28
+ if described_class.respond_to?(:translatable_fields)
29
+ before do
30
+ allow(described_class).to receive(:searchable_fields_list) { [:foo] }
31
+ end
32
+
33
+ context 'translatable model' do
34
+ before do
35
+ described_class.translatable_fields :foo, :bar
36
+ end
37
+
38
+ let(:create_subject!) do
39
+ subject = build(klass)
40
+ %w(nl fr).each do |l|
41
+ t = subject.translation(l.to_sym)
42
+ t.foo = "baz #{l}"
43
+ t.bar = 'bak'
44
+ end
45
+ subject.save
46
+ subject
47
+ end
48
+
49
+ it 'does not copy translatable fields that are not in searchable fields' do
50
+ subject = create_subject!
51
+ expect(subject.search_indices.find_by(locale: :nl, name: 'bar')).to be nil
52
+ end
53
+ end
54
+ end
55
+
56
+ if described_class.respond_to?(:content_rows)
57
+ context 'model with flexible_content' do
58
+ let(:subject) { create(klass) }
59
+ let(:content) { create(:content_text, content: 'Lorem ipsum') }
60
+
61
+ before do
62
+ # Because this is a spec/support included through it_behaves_like,
63
+ # we have to mock the entry points of the data used in
64
+ # Plok::Searchable.
65
+ allow(described_class).to receive(:searchable_fields_list) { [:foo] }
66
+ allow_any_subject_of(described_class).to receive(:foo) { 'bar' }
67
+ allow_any_subject_of(Concerns::Storable::Collection).to receive(:foo) { 'bar' }
68
+
69
+ row = create(:content_row, locale: 'nl', rowable: subject)
70
+ row.columns << create(:content_column, content: content)
71
+ subject.content_rows << row
72
+ end
73
+
74
+ it 'saves index when updating searchable subject' do
75
+ subject.save!
76
+ key = "flexible_content:#{content.id}"
77
+ expect(subject.search_indices.find_by(locale: :nl, name: key).value).to eq 'Lorem ipsum'
78
+ end
79
+
80
+ it 'saves index when updating flexible content' do
81
+ subject.save!
82
+ content.content = 'Dolor sit amet'
83
+ content.save!
84
+ key = "flexible_content:#{content.id}"
85
+ expect(subject.search_indices.find_by(locale: :nl, name: key).value).to eq 'Dolor sit amet'
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ it '.respond_to?' do
92
+ expect(described_class).to respond_to(
93
+ :searchable_field, :searchable_fields, :searchable_fields_list
94
+ )
95
+ end
96
+
97
+ it '#respond_to?' do
98
+ expect(subject).to respond_to(:search_indices)
99
+ end
100
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plok
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Davy Hellemans
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-06-17 00:00:00.000000000 Z
12
+ date: 2022-10-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -78,14 +78,20 @@ files:
78
78
  - MIT-LICENSE
79
79
  - Rakefile
80
80
  - app/assets/config/plok_manifest.js
81
+ - app/assets/javascripts/jquery-ui.autocomplete.html.js
82
+ - app/assets/javascripts/plok/searchable.js
81
83
  - app/assets/javascripts/plok/sidebar.js
84
+ - app/assets/stylesheets/plok/_autocomplete.scss
82
85
  - app/assets/stylesheets/plok/_sidebar.scss
83
86
  - app/assets/stylesheets/plok/_sidebar_compact.scss
84
87
  - app/controllers/catch_all_controller.rb
85
88
  - app/controllers/plok/version_controller.rb
86
89
  - app/models/concerns/plok/loggable.rb
90
+ - app/models/concerns/plok/searchable.rb
87
91
  - app/models/log.rb
88
92
  - app/models/queued_task.rb
93
+ - app/models/search_index.rb
94
+ - app/models/search_module.rb
89
95
  - config/initializers/module.rb
90
96
  - config/routes.rb
91
97
  - db/migrate/20211008130809_create_plok_logs.rb
@@ -96,6 +102,8 @@ files:
96
102
  - db/migrate/20211203103118_add_file_to_logs.rb
97
103
  - db/migrate/20220512141814_add_log_indexes_to_category_type_and_id.rb
98
104
  - db/migrate/20220512142356_add_index_to_queued_task_weight.rb
105
+ - db/migrate/20220923162300_create_search_indices.rb
106
+ - db/migrate/20220923164100_create_search_modules.rb
99
107
  - lib/generators/plok/sidebar/USAGE
100
108
  - lib/generators/plok/sidebar/sidebar_generator.rb
101
109
  - lib/generators/plok/sidebar/templates/_menu_item.html.erb
@@ -104,11 +112,19 @@ files:
104
112
  - lib/generators/plok/sidebar/templates/_wrapper.html.erb
105
113
  - lib/plok.rb
106
114
  - lib/plok/engine.rb
115
+ - lib/plok/search/backend.rb
116
+ - lib/plok/search/base.rb
117
+ - lib/plok/search/result_objects/base.rb
118
+ - lib/plok/search/term.rb
107
119
  - lib/plok/version.rb
108
120
  - lib/tasks/plok_tasks.rake
109
121
  - spec/factories/logs.rb
110
122
  - spec/factories/queued_tasks.rb
123
+ - spec/factories/search_indices.rb
124
+ - spec/factories/search_modules.rb
125
+ - spec/support/fake_ar_model.rb
111
126
  - spec/support/plok/loggable.rb
127
+ - spec/support/plok/searchable.rb
112
128
  homepage: https://plok.blimp.be
113
129
  licenses:
114
130
  - MIT