plok 1.0.0 → 1.0.1

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
  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