plok 1.0.3 → 1.1.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: 0d547235d31f52b56077f2329d5e654fb84bab1bcde461161d922f89b745ebd9
4
- data.tar.gz: 25e975e1bbe1e10d5c574e0ca0a86f8ccf22b7f1085be5bc42ad35b80b55a120
3
+ metadata.gz: 7801b68f7ae1a30624a95fcf9713bb1251f2e7831960ddd173e64b152ba24f94
4
+ data.tar.gz: 057c7d13a8922bdeba6150e6d2df6ecec65d0f8af169a50b879ccadbee3688b3
5
5
  SHA512:
6
- metadata.gz: '045079c93e3c9f510fc29831a908c3de4c29a2a803e62fa3fd6288f3ed030efc6f91de118c36bc9b7be29504a8edbb3c0f67d4cd95666c3c76d7848b720815cc'
7
- data.tar.gz: 1c1ff0f3a2fa04de54c55c6a409bfb1da55cebea2ce56dd3ad9b1206ac01cef359da4220d71f897e749904353cebf62836a65da6ea20747ce2ed7cef8f30bcea
6
+ metadata.gz: '087677f2caccca0fc4d2e342790860f8514aa002447f2555e018aa4e60e5bb817111209c7cea3c84452ec24b717291fe1e05dc061623cc7b5dd2cd5791271f4b'
7
+ data.tar.gz: 394542cf38ab37c5740622fee0fc6de8c0b9bd9697e91b94323a3b0edd84ce60aa750af2daf14ccdaaa5782282d548c5ac7a9a77fd56567e936f9164c9a631c2
@@ -19,7 +19,7 @@ var search = search || {
19
19
  },
20
20
 
21
21
  target: function() {
22
- return $('#plok-search input');
22
+ return $('[data-plok-searchable] input');
23
23
  }
24
24
  };
25
25
 
@@ -1,8 +1,9 @@
1
+ # TODO: Find a way to spec this properly.
1
2
  module Plok::Searchable
2
3
  extend ActiveSupport::Concern
3
4
 
4
5
  included do
5
- has_many :search_indices, as: :searchable, dependent: :destroy
6
+ has_many :search_indices, as: :searchable
6
7
 
7
8
  # The after_save block creates or saves indices for every indicated
8
9
  # searchable field. Takes both translations and flexible content into
@@ -15,16 +16,18 @@ module Plok::Searchable
15
16
  end
16
17
 
17
18
  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
19
+ self.class.searchable_fields_list.each do |namespace, keys|
20
+ keys.each do |key|
21
+ if key == :flexible_content
22
+ save_flexible_content_search_indices!(namespace) && next
23
+ end
22
24
 
23
- if respond_to?(:translatable?) && self.class.translatable_fields_list.include?(key)
24
- save_translatable_search_index!(key) && next
25
- end
25
+ if respond_to?(:translatable?) && self.class.translatable_fields_list.include?(key)
26
+ save_translatable_search_index!(namespace, key) && next
27
+ end
26
28
 
27
- save_search_index!(key)
29
+ save_search_index!(namespace, key)
30
+ end
28
31
  end
29
32
  end
30
33
 
@@ -38,38 +41,37 @@ module Plok::Searchable
38
41
  # to help facilitate searchable management. Note that said code was
39
42
  # initially present in this class, and it was such a mess that it became
40
43
  # unpractical to maintain.
41
- def save_flexible_content_search_indices!
44
+ def save_flexible_content_search_indices!(namespace)
42
45
  content_rows.each do |row|
43
46
  row.columns.each do |column|
44
47
  next unless column.content.is_a?(ContentText)
45
48
  key = "flexible_content:#{column.content_id}"
46
- save_index!(key, value: column.content.content, locale: row.locale)
49
+ save_search_index!(namespace, key, value: column.content.content, locale: row.locale)
47
50
  end
48
51
  end
49
52
  end
50
53
 
51
- def save_index!(key, value: nil, locale: nil)
52
- value = read_attribute(key) if value.blank? && respond_to?(key)
54
+ def save_search_index!(namespace, key, value: nil, locale: nil)
55
+ value = if value.present?
56
+ value
57
+ elsif ActiveRecord::Base.connection.column_exists?(self.class.table_name, key)
58
+ read_attribute(key)
59
+ elsif respond_to?(key)
60
+ send(key)
61
+ end
62
+
53
63
  return if value.blank?
54
64
 
55
65
  search_indices
56
- .find_or_create_by!(name: key, locale: locale)
66
+ .find_or_create_by!(namespace: namespace, name: key, locale: locale)
57
67
  .update_column(:value, value)
58
68
  end
59
69
 
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)
70
+ def save_translatable_search_index!(namespace, key)
69
71
  # TODO: locales can't be hardcoded
70
72
  %w(nl fr).each do |locale|
71
73
  value = translation(locale.to_sym).send(key)
72
- save_index!(key, value: value, locale: locale)
74
+ save_search_index!(namespace, key, value: value, locale: locale)
73
75
  end
74
76
  end
75
77
 
@@ -79,18 +81,22 @@ module Plok::Searchable
79
81
  end
80
82
 
81
83
  module ClassMethods
82
- def searchable_field(key)
83
- unless searchable_fields_list.include?(key.to_sym)
84
- searchable_fields_list << key.to_sym
84
+ def searchable_field(namespace, key)
85
+ return if searchable_fields_list.dig(namespace.to_sym)&.include?(key.to_sym)
86
+
87
+ if searchable_fields_list[namespace.to_sym].blank?
88
+ searchable_fields_list[namespace.to_sym] = []
85
89
  end
90
+
91
+ searchable_fields_list[namespace.to_sym] << key.to_sym
86
92
  end
87
93
 
88
- def searchable_fields(*args)
89
- args.each { |key| searchable_field(key) }
94
+ def plok_searchable(namespace: nil, fields: [], conditions: [])
95
+ fields.each { |key| searchable_field(namespace, key) }
90
96
  end
91
97
 
92
98
  def searchable_fields_list
93
- @searchable_fields_list ||= []
99
+ @searchable_fields_list ||= {}
94
100
  end
95
101
  end
96
102
  end
@@ -1,11 +1,12 @@
1
1
  class SearchModule < ActiveRecord::Base
2
- scope :weighted, -> { order('weight DESC') }
3
2
 
4
- validates :name, presence: true
3
+ scope :searchable, -> { where('search_modules.searchable': true) }
4
+ scope :weighted, -> { order('search_modules.weight DESC') }
5
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)
6
+ validates :klass, presence: true
7
+
8
+ def search_indices
9
+ SearchIndex.where(searchable_type: klass)
10
10
  end
11
+
11
12
  end
@@ -0,0 +1,9 @@
1
+ class RenameNameToKlassForSearchModules < ActiveRecord::Migration[6.1]
2
+ def up
3
+ rename_column :search_modules, :name, :klass
4
+ end
5
+
6
+ def down
7
+ rename_column :search_modules, :klass, :name
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ class AddNamespaceToSearchIndices < ActiveRecord::Migration[6.1]
2
+ def change
3
+ add_column :search_indices, :namespace, :string, after: :searchable_id, index: true
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ Description:
2
+ This generates a Plok::Search class for a given namespace, which you can
3
+ use to search for search_indices tagged with the same namespace.
4
+
5
+ Example:
6
+ bin/rails g plok/search/class --namespace backend
7
+
8
+ This will create:
9
+ lib/plok/search/backend.rb
10
+ spec/lib/plok/search/backend_spec.rb
@@ -0,0 +1,36 @@
1
+ require 'rails/generators/base'
2
+
3
+ class Plok::Search::ClassGenerator < Rails::Generators::Base
4
+ source_root File.expand_path('templates', __dir__)
5
+ class_option :namespace, type: :string
6
+
7
+ def create
8
+ copy_file "namespace_class.rb", namespace_class_path
9
+ copy_file "namespace_class_spec.rb", namespace_class_spec_path
10
+ gsub_file(namespace_class_path, '[namespace]', namespace_class)
11
+ gsub_file(namespace_class_spec_path, '[namespace]', namespace_class)
12
+ gsub_file(namespace_class_spec_path, '[snakecased_namespace]', options.namespace)
13
+ end
14
+
15
+ private
16
+
17
+ def app_name
18
+ Rails.application.class.name.split('::').first
19
+ end
20
+
21
+ def namespace_class_path
22
+ "lib/plok/search/#{options.namespace}.rb"
23
+ end
24
+
25
+ def namespace_class_spec_path
26
+ "spec/lib/plok/search/#{options.namespace}_spec.rb"
27
+ end
28
+
29
+ def file_contains?(file, content)
30
+ File.readlines(file).grep(content).any?
31
+ end
32
+
33
+ def namespace_class
34
+ options.namespace.to_s.camelcase
35
+ end
36
+ end
@@ -0,0 +1,41 @@
1
+ require 'rails/generators/base'
2
+
3
+ class Plok::Search::ResultObjectGenerator < Rails::Generators::Base
4
+ source_root File.expand_path('templates', __dir__)
5
+ class_option :name, type: :string
6
+ class_option :namespace, type: :string
7
+
8
+ def create
9
+ # TODO: Check for missing params
10
+ copy_file 'result_object.rb', class_path
11
+ gsub_file(class_path, '[name]', options.name)
12
+ gsub_file(class_path, '[namespace]', options.namespace)
13
+ gsub_file(class_path, '[class_name]', to_snakecase(options.name))
14
+ gsub_file(class_path, '[namespace_module_name]', to_snakecase(options.namespace))
15
+
16
+ copy_file 'result_object.html.erb', markup_path
17
+ gsub_file(markup_path, '[name]', options.name)
18
+ end
19
+
20
+ private
21
+
22
+ def app_name
23
+ Rails.application.class.name.split('::').first
24
+ end
25
+
26
+ def class_path
27
+ "lib/plok/search/result_objects/#{options.namespace}/#{options.name}.rb"
28
+ end
29
+
30
+ def markup_path
31
+ "app/views/plok/search/result_objects/#{options.namespace}/_#{options.name}.html.erb"
32
+ end
33
+
34
+ def namespace_class_spec_path
35
+ "spec/lib/plok/search/#{options.namespace}_spec.rb"
36
+ end
37
+
38
+ def to_snakecase(name)
39
+ name.to_s.camelcase
40
+ end
41
+ end
@@ -0,0 +1,25 @@
1
+ module Plok::Search
2
+ # The reason this class exists is mainly to provide a context layer to
3
+ # override Plok::Search::Base. It provides a manipulated version of the
4
+ # filtered index data that we can use in the result set of an
5
+ # autocomplete-triggered search query.
6
+ class [namespace] < Plok::Search::Base
7
+ # This translates the filtered indices into meaningful result objects.
8
+ #
9
+ # #search_indices will return an ActiveRecord::Relation of SearchIndex
10
+ # records. Because #lazy is chained after it, we still have a way to fiddle
11
+ # with the AR query when necessary.
12
+ #
13
+ # #format_search_results will return a list of jquery-ui friendly hashes:
14
+ # [
15
+ # { label: ... value: ... },
16
+ # { label: ... value: ... }
17
+ # ]
18
+ #
19
+ def search(modules: [])
20
+ format_search_results(
21
+ search_indices(modules: modules).lazy
22
+ )
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,20 @@
1
+ require 'rails_helper'
2
+
3
+ describe Plok::Search::[namespace] do
4
+ subject { described_class.new('John', namespace: '[snakecased_namespace]') }
5
+
6
+ describe '#search' do
7
+ it 'defaults to an empty list' do
8
+ # Have to cast to an array because search lazy loads results.
9
+ expect(subject.search.to_a).to eq []
10
+ end
11
+
12
+ it 'returns relevant indices' do
13
+ # TODO:
14
+ end
15
+ end
16
+
17
+ it '#respond_to?' do
18
+ expect(subject).to respond_to(:format_search_results, :search_indices)
19
+ end
20
+ end
@@ -0,0 +1,4 @@
1
+ <% [name] = [name].decorate if [name].respond_to?(:decorate) %>
2
+
3
+ <strong><%= t 'b.[name]' %></strong><br>
4
+ <small class="text-muted"><%= [name].id %></small>
@@ -0,0 +1,19 @@
1
+ module Plok::Search::ResultObjects
2
+ # This class should be used to further manipulate any of the data provided
3
+ # through #search_context or Plok::Search::ResultObjects::Base.
4
+ #
5
+ # A search context class is accessible through #search_context. This
6
+ # gives you access to #search_context.controller, which can be used to
7
+ # call routes upon.
8
+ #
9
+ # Example of: If an autocomplete requires additional data to be rendered in
10
+ # the partial (think another model, or an API call), one could override
11
+ # the #locals method to include more variables. You could do this directly
12
+ # in the partial as well, but this way we have separation of concerns.
13
+ class [namespace_module_name]::[class_name] < Plok::Search::ResultObjects::Base
14
+ def url
15
+ # TODO: Add a path to where people should end up by clicking the result.
16
+ #search_context.controller.[namespace]_[name]_path(searchable)
17
+ end
18
+ end
19
+ end
@@ -4,7 +4,7 @@ module Plok::Search
4
4
  # result set (think autocomplete results) is done by extending this class.
5
5
  #
6
6
  # Examples of class extensions could be:
7
- # Plok::Search::Backend - included in Plok
7
+ # Plok::Search::Backend
8
8
  # Plok::Search::Frontend
9
9
  # Plok::Search::Api
10
10
  #
@@ -22,44 +22,36 @@ module Plok::Search
22
22
  #
23
23
  # However these result objects are structured are also up to the developer.
24
24
  class Base
25
- attr_reader :term, :controller
26
25
 
27
- def initialize(term, controller: nil, namespace: nil)
26
+ attr_reader :term, :namespace, :controller
27
+
28
+ def initialize(term, namespace: nil, controller: nil)
28
29
  @term = Plok::Search::Term.new(term, controller: controller)
29
- @controller = controller
30
30
  @namespace = namespace
31
+ @controller = controller
31
32
  end
32
33
 
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
34
+ def format_search_results(indices, options = {})
35
+ options.reverse_merge!(
36
+ label_method: :build_html,
37
+ value_method: :url
38
+ )
39
+
40
+ indices.map do |index|
41
+ result = result_object(index)
49
42
 
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
43
+ {
44
+ label: result.send(options[:label_method]),
45
+ value: result.send(options[:value_method])
46
+ }
47
+ end
56
48
  end
57
49
 
58
50
  # In order to provide a good result set in a search autocomplete, we have
59
51
  # to translate the raw index to a class that makes an index adhere
60
52
  # to a certain interface (that can include links).
61
53
  def result_object(index)
62
- klass = "Plok::Search::ResultObjects::#{namespace}::#{index.searchable_type}"
54
+ klass = "Plok::Search::ResultObjects::#{@namespace.camelcase}::#{index.searchable_type}"
63
55
  klass = 'Plok::Search::ResultObjects::Base' unless result_object_exists?(klass)
64
56
  klass.constantize.new(index, search_context: self)
65
57
  end
@@ -67,5 +59,28 @@ module Plok::Search
67
59
  def result_object_exists?(name)
68
60
  Plok::Engine.class_exists?(name) && name.constantize.method_defined?(:build_html)
69
61
  end
62
+
63
+ # TODO: SearchIndexCollection
64
+ # TODO: What if records are hidden? Make this smart and have SearchIndex#visible?
65
+ # TODO: See if there's a way to pass weight through individual records.
66
+ def search_indices(modules: [])
67
+ modules = SearchModule.searchable.pluck(:klass) if modules.blank?
68
+
69
+ # Having the searchmodules sorted by weight returns indices in the
70
+ # correct order.
71
+ #
72
+ # The group happens to make sure we end up with just 1 copy of
73
+ # a searchable result. Otherwise matches from both an indexed
74
+ # Page#title and Page#description would be in the result set.
75
+ @search_indices ||= SearchIndex
76
+ .joins('INNER JOIN search_modules ON search_indices.searchable_type = search_modules.klass')
77
+ .where('search_modules.searchable': true)
78
+ .where('search_modules.klass in (?)', modules)
79
+ .where('search_indices.namespace = ?', @namespace)
80
+ .where('search_indices.value LIKE ?', "%#{term.value}%")
81
+ .group([:searchable_type, :searchable_id])
82
+ .preload(:searchable) # ".includes" for polymorphic relations
83
+ end
84
+
70
85
  end
71
86
  end
@@ -52,12 +52,23 @@ module Plok
52
52
  { "#{partial_target}": index.searchable, index: index }
53
53
  end
54
54
 
55
+ def namespace
56
+ search_context.namespace.to_s.underscore
57
+ end
58
+
55
59
  def partial
56
60
  "#{partial_path}/#{partial_target}"
57
61
  end
58
62
 
59
63
  def partial_path
60
- "#{search_context.namespace.to_s.underscore}/search"
64
+ # TODO: This is a fallback for older Plok versions whose result object
65
+ # partials still reside in app/views/backens/search/*. Best to remove
66
+ # this at a later stage, but for now they can be backwards compatible.
67
+ if File.exists?("app/views/#{namespace}/search/_#{partial_target}.html.erb")
68
+ return "#{namespace}/search/"
69
+ end
70
+
71
+ "plok/search/result_objects/#{namespace}"
61
72
  end
62
73
 
63
74
  def partial_target
data/lib/plok/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Plok
2
- VERSION = '1.0.3'
2
+ VERSION = '1.1.1'
3
3
  end
@@ -1,11 +1,46 @@
1
1
  namespace 'plok:search' do
2
- desc 'Rebuild all search indices'
2
+ # The official Rails way to pass arguments to Rake tasks is this:
3
+ #
4
+ # task :rebuild_indices, [:modules] => :environment do |_t, args|
5
+ # args.with_defaults(modules: SearchModule.searchable.pluck(:klass))
6
+ # end
7
+ #
8
+ # The problem with that is that you can't comma-separate multiple modules
9
+ # unless you escape them. This is because commas denote multiple arguments.
10
+ #
11
+ # In short, this wouldn't work:
12
+ #
13
+ # bin/rails plok:search:rebuild_indices[EmailTemplate,Article]
14
+ #
15
+ # And you'd need to do this:
16
+ #
17
+ # bin/rails plok:search:rebuild_indices["EmailTemplate\,Article"]
18
+ #
19
+ # I don't agree with that notation, so instead I opted for ENV vars instead:
20
+ #
21
+ # bin/rails plok:search:rebuild_indices modules=EmailTemplate,Article
22
+ #
23
+ desc 'Rebuild select search indices (or all of them without additional arguments).'
3
24
  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!)
25
+ # Default to all searchable modules
26
+ modules = ENV['modules']&.split(',') || SearchModule.searchable.pluck(:klass)
27
+
28
+ # A safeguard to prevent mishaps
29
+ modules.select! do |m|
30
+ SearchModule.exists?(klass: m) && # Only known modules
31
+ Plok::Engine.class_exists?(m.constantize) # Only existing classes
8
32
  end
33
+
34
+ # Faster than SearchIndex.where(searchable_type: modules).destroy_all
35
+ ActiveRecord::Base
36
+ .connection
37
+ .execute("DELETE FROM search_indices WHERE searchable_type in ('#{modules.join("','")}')")
38
+
39
+ SearchModule.where(klass: modules).each do |m|
40
+ puts "Rebuilding #{m.klass} indices..."
41
+ m.klass.constantize.all.each(&:trigger_indices_save!)
42
+ end
43
+
9
44
  puts "Done."
10
45
  end
11
46
  end
@@ -1,5 +1,5 @@
1
1
  FactoryBot.define do
2
2
  factory :search_module do
3
- name { 'Page' }
3
+ klass { 'Page' }
4
4
  end
5
5
  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.3
4
+ version: 1.1.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-10-28 00:00:00.000000000 Z
12
+ date: 2022-11-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -104,6 +104,15 @@ files:
104
104
  - db/migrate/20220512142356_add_index_to_queued_task_weight.rb
105
105
  - db/migrate/20220923162300_create_search_indices.rb
106
106
  - db/migrate/20220923164100_create_search_modules.rb
107
+ - db/migrate/20221021133242_rename_name_to_klass_for_search_modules.rb
108
+ - db/migrate/20221031143932_add_namespace_to_search_indices.rb
109
+ - lib/generators/plok/search/USAGE
110
+ - lib/generators/plok/search/class_generator.rb
111
+ - lib/generators/plok/search/result_object_generator.rb
112
+ - lib/generators/plok/search/templates/namespace_class.rb
113
+ - lib/generators/plok/search/templates/namespace_class_spec.rb
114
+ - lib/generators/plok/search/templates/result_object.html.erb
115
+ - lib/generators/plok/search/templates/result_object.rb
107
116
  - lib/generators/plok/sidebar/USAGE
108
117
  - lib/generators/plok/sidebar/sidebar_generator.rb
109
118
  - lib/generators/plok/sidebar/templates/_menu_item.html.erb
@@ -112,7 +121,6 @@ files:
112
121
  - lib/generators/plok/sidebar/templates/_wrapper.html.erb
113
122
  - lib/plok.rb
114
123
  - lib/plok/engine.rb
115
- - lib/plok/search/backend.rb
116
124
  - lib/plok/search/base.rb
117
125
  - lib/plok/search/result_objects/base.rb
118
126
  - lib/plok/search/term.rb
@@ -1,22 +0,0 @@
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