iron-cms 0.13.1 → 0.14.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/controllers/iron/field_definitions_controller.rb +1 -1
- data/app/jobs/iron/reindex_search_job.rb +7 -0
- data/app/models/iron/entry/searchable.rb +85 -0
- data/app/models/iron/entry/titlable.rb +7 -4
- data/app/models/iron/entry.rb +1 -1
- data/app/models/iron/field.rb +4 -0
- data/app/models/iron/field_definition/ranked.rb +10 -0
- data/app/models/iron/field_definition/searchable.rb +54 -0
- data/app/models/iron/field_definition.rb +1 -4
- data/app/models/iron/fields/block.rb +1 -13
- data/app/models/iron/fields/rich_text_area.rb +4 -0
- data/app/models/iron/fields/text_area.rb +4 -0
- data/app/models/iron/fields/text_field.rb +4 -0
- data/app/models/iron/search/query.rb +33 -0
- data/app/models/iron/search_record/fts.rb +20 -0
- data/app/models/iron/search_record.rb +31 -0
- data/app/views/iron/field_definitions/rich_text_area/_form.html.erb +5 -1
- data/app/views/iron/field_definitions/text_area/_form.html.erb +5 -1
- data/app/views/iron/field_definitions/text_field/_form.html.erb +5 -0
- data/config/locales/en.yml +5 -0
- data/db/migrate/20260210220330_create_iron_search_records.rb +31 -0
- data/lib/iron/engine.rb +7 -0
- data/lib/iron/fts5.rb +23 -0
- data/lib/iron/version.rb +1 -1
- data/lib/tasks/iron_tasks.rake +14 -0
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 579a71fed1f35c24d51560f6055a00ab5d8072b6f2ff934e64a80363f86a6690
|
|
4
|
+
data.tar.gz: 3d512392984fa15623c10c13dc06500a4f839966404f12849f55ce3cf894031b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3d39a0aeeaaa0a9b5e07f6fa60b4e18b078d4cdae252565a984ad54980ffc1e08ec8d2abcf9dcf7f168999fa9f68f3c7cb0b272b2dcc96f2439f90e61429724e
|
|
7
|
+
data.tar.gz: fc378916749f9802f668e61b303d86d563b2e910269cddc3b053770dca41ec1d2d5b71dbbd551756f76c90817a801bddf809e3c768ea62d60d9b9bbb716a0111
|
|
@@ -42,7 +42,7 @@ module Iron
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def field_definition_params
|
|
45
|
-
params.expect(field_definition: [ :type, :name, :handle, :rank, :allowed_values_text, :required, :file_scope, selected_presets: [], supported_block_definition_ids: [], supported_content_type_ids: [] ])
|
|
45
|
+
params.expect(field_definition: [ :type, :name, :handle, :rank, :allowed_values_text, :required, :searchable, :file_scope, selected_presets: [], supported_block_definition_ids: [], supported_content_type_ids: [] ])
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def selected_field_definition_type
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
module Entry::Searchable
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
SEARCH_CONTENT_LIMIT = 32.kilobytes
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
has_many :search_records, dependent: :destroy
|
|
9
|
+
|
|
10
|
+
after_create_commit :reindex
|
|
11
|
+
after_update_commit :reindex
|
|
12
|
+
|
|
13
|
+
scope :search, ->(query, locale: nil) {
|
|
14
|
+
parsed = Search::Query.new(query)
|
|
15
|
+
return none if parsed.blank?
|
|
16
|
+
|
|
17
|
+
joins(:search_records)
|
|
18
|
+
.merge(SearchRecord.matching(parsed.to_s))
|
|
19
|
+
.merge(SearchRecord.for_locale(locale))
|
|
20
|
+
.distinct
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def reindex
|
|
25
|
+
searchable_definitions = collect_searchable_definitions
|
|
26
|
+
|
|
27
|
+
if searchable_definitions.empty?
|
|
28
|
+
search_records.destroy_all
|
|
29
|
+
return
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
active_locale_ids = fields.map(&:locale_id).uniq
|
|
33
|
+
Locale.where(id: active_locale_ids).find_each do |locale|
|
|
34
|
+
title_text = title_field_for(locale)&.value
|
|
35
|
+
content_text = assemble_search_content(searchable_definitions, locale)
|
|
36
|
+
|
|
37
|
+
if title_text.present? || content_text.present?
|
|
38
|
+
SearchRecord.upsert!(
|
|
39
|
+
entry_id: id,
|
|
40
|
+
locale_id: locale.id,
|
|
41
|
+
title: title_text,
|
|
42
|
+
content: content_text,
|
|
43
|
+
created_at: created_at || Time.current
|
|
44
|
+
)
|
|
45
|
+
else
|
|
46
|
+
search_records.where(locale:).destroy_all
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
search_records.where.not(locale_id: active_locale_ids).destroy_all
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
def collect_searchable_definitions(field_definitions = content_type.field_definitions.reload, visited = Set.new)
|
|
55
|
+
field_definitions.flat_map { |fd|
|
|
56
|
+
nested = case fd
|
|
57
|
+
when FieldDefinitions::Block
|
|
58
|
+
block_def = fd.supported_block_definition
|
|
59
|
+
block_def && visited.add?(block_def.id) ? collect_searchable_definitions(block_def.field_definitions, visited) : []
|
|
60
|
+
when FieldDefinitions::BlockList
|
|
61
|
+
fd.supported_block_definitions.flat_map { |bd|
|
|
62
|
+
visited.add?(bd.id) ? collect_searchable_definitions(bd.field_definitions, visited) : []
|
|
63
|
+
}
|
|
64
|
+
else
|
|
65
|
+
[]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
fd.searchable? ? [ fd, *nested ] : nested
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def assemble_search_content(definitions, locale)
|
|
73
|
+
definition_ids = definitions.map(&:id).to_set
|
|
74
|
+
|
|
75
|
+
matching_fields = fields.select { |f|
|
|
76
|
+
f.locale_id == locale.id && definition_ids.include?(f.definition_id)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
matching_fields
|
|
80
|
+
.filter_map(&:searchable_text)
|
|
81
|
+
.join(" ")
|
|
82
|
+
.truncate_bytes(SEARCH_CONTENT_LIMIT, omission: "")
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -15,17 +15,20 @@ module Iron
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def title
|
|
18
|
-
|
|
18
|
+
field = title_field_for(Current.locale || Locale.default)
|
|
19
|
+
return default_title if field.blank?
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
field.value.presence || default_title
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
private
|
|
24
25
|
|
|
25
|
-
def
|
|
26
|
+
def title_field_for(locale)
|
|
26
27
|
return nil unless content_type.title_field_definition.present?
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
fields.find { |f|
|
|
30
|
+
f.definition_id == content_type.title_field_definition_id && f.locale_id == locale.id
|
|
31
|
+
}
|
|
29
32
|
end
|
|
30
33
|
|
|
31
34
|
def default_title
|
data/app/models/iron/entry.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Iron
|
|
2
2
|
class Entry < ApplicationRecord
|
|
3
|
-
include Titlable, Schemable, WebPublishable, Presentable, DeepValidation, InstanceScoped, Exportable, Importable
|
|
3
|
+
include Titlable, Schemable, Searchable, WebPublishable, Presentable, DeepValidation, InstanceScoped, Exportable, Importable
|
|
4
4
|
|
|
5
5
|
belongs_to :creator, class_name: "Iron::User", default: -> { Current.user }
|
|
6
6
|
has_many :fields, inverse_of: :entry, dependent: :destroy
|
data/app/models/iron/field.rb
CHANGED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
module FieldDefinition::Searchable
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
SEARCHABLE_TYPES = %w[Iron::FieldDefinitions::TextField Iron::FieldDefinitions::TextArea Iron::FieldDefinitions::RichTextArea].freeze
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
scope :searchable, -> { where(type: SEARCHABLE_TYPES).where("json_extract(metadata, '$.searchable') = 1") }
|
|
9
|
+
|
|
10
|
+
after_update_commit :reindex_entries_later, if: :searchable_previously_changed?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def searchable
|
|
14
|
+
ActiveModel::Type::Boolean.new.cast(metadata&.dig("searchable"))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def searchable?
|
|
18
|
+
!!searchable
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def searchable=(value)
|
|
22
|
+
self.metadata = (metadata || {}).merge("searchable" => ActiveModel::Type::Boolean.new.cast(value))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def reindex_entries
|
|
26
|
+
entries_for_reindex.find_each(&:reindex)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
def reindex_entries_later
|
|
31
|
+
ReindexSearchJob.perform_later(self)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def searchable_previously_changed?
|
|
35
|
+
return false unless saved_change_to_metadata?
|
|
36
|
+
|
|
37
|
+
old_metadata = saved_changes["metadata"]&.first || {}
|
|
38
|
+
new_metadata = saved_changes["metadata"]&.last || {}
|
|
39
|
+
|
|
40
|
+
old_metadata["searchable"] != new_metadata["searchable"]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def entries_for_reindex
|
|
44
|
+
case schemable
|
|
45
|
+
when ContentType
|
|
46
|
+
schemable.entries
|
|
47
|
+
when BlockDefinition
|
|
48
|
+
Entry.joins(:fields).where(iron_fields: { definition_id: schemable.referencing_field_definition_ids }).distinct
|
|
49
|
+
else
|
|
50
|
+
Entry.none
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
module Iron
|
|
2
2
|
class FieldDefinition < ApplicationRecord
|
|
3
|
-
include Exportable, Importable
|
|
3
|
+
include Ranked, Searchable, Exportable, Importable
|
|
4
4
|
|
|
5
5
|
TYPES = %w[text_field text_area rich_text_area number file boolean date block block_list reference_list reference].freeze
|
|
6
6
|
|
|
7
7
|
belongs_to :schemable, polymorphic: true
|
|
8
8
|
|
|
9
|
-
has_rank scoped_by: :schemable
|
|
10
|
-
before_create :move_to_bottom
|
|
11
|
-
|
|
12
9
|
has_many :fields, as: :definition, inverse_of: :definition, dependent: :destroy
|
|
13
10
|
|
|
14
11
|
has_one :titlable_content_type, class_name: "Iron::ContentType", inverse_of: :title_field_definition, dependent: :nullify
|
|
@@ -70,19 +70,7 @@ module Iron
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def title
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
text_field = fields.find { |f| text_types.include?(f.type) }
|
|
76
|
-
return nil unless text_field
|
|
77
|
-
|
|
78
|
-
content = case text_field
|
|
79
|
-
when Fields::RichTextArea
|
|
80
|
-
text_field.rich_text&.to_plain_text
|
|
81
|
-
else
|
|
82
|
-
text_field.value
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
content&.truncate(300)
|
|
73
|
+
fields.filter_map(&:searchable_text).first&.truncate(300)
|
|
86
74
|
end
|
|
87
75
|
|
|
88
76
|
def thumbnail
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
class Search::Query
|
|
3
|
+
attr_reader :terms
|
|
4
|
+
|
|
5
|
+
def initialize(raw)
|
|
6
|
+
@terms = sanitize(raw)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def blank? = terms.blank?
|
|
10
|
+
def to_s = terms.to_s
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
def sanitize(raw)
|
|
14
|
+
return nil if raw.blank?
|
|
15
|
+
result = strip_special_characters(raw)
|
|
16
|
+
return nil if result.blank?
|
|
17
|
+
result = remove_unbalanced_quotes(result)
|
|
18
|
+
neutralize_operators(result)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def strip_special_characters(text)
|
|
22
|
+
text.gsub(/[^\w\s"]/, "").squish
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def remove_unbalanced_quotes(text)
|
|
26
|
+
text.count('"').even? ? text : text.delete('"')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def neutralize_operators(text)
|
|
30
|
+
text.downcase
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
class SearchRecord::Fts < ApplicationRecord
|
|
3
|
+
self.table_name = "iron_search_records_fts"
|
|
4
|
+
self.primary_key = "rowid"
|
|
5
|
+
|
|
6
|
+
attribute :rowid, :integer
|
|
7
|
+
attribute :title, :string
|
|
8
|
+
attribute :content, :string
|
|
9
|
+
|
|
10
|
+
scope :with_rowid, -> { select(:rowid, :title, :content) }
|
|
11
|
+
|
|
12
|
+
def self.upsert(rowid, title, content)
|
|
13
|
+
connection.exec_query(
|
|
14
|
+
"INSERT OR REPLACE INTO iron_search_records_fts(rowid, title, content) VALUES (?, ?, ?)",
|
|
15
|
+
"Iron::SearchRecord::Fts Upsert",
|
|
16
|
+
[ rowid, title, content ]
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
class SearchRecord < ApplicationRecord
|
|
3
|
+
belongs_to :entry
|
|
4
|
+
belongs_to :locale
|
|
5
|
+
|
|
6
|
+
has_one :search_records_fts, -> { with_rowid },
|
|
7
|
+
class_name: "Iron::SearchRecord::Fts", foreign_key: :rowid, primary_key: :id, dependent: :destroy
|
|
8
|
+
|
|
9
|
+
after_save :upsert_to_fts5_table
|
|
10
|
+
|
|
11
|
+
scope :matching, ->(query) { joins(:search_records_fts).where("iron_search_records_fts MATCH ?", query) }
|
|
12
|
+
scope :for_locale, ->(locale) { locale ? where(locale:) : all }
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def upsert!(attributes)
|
|
16
|
+
record = find_by(entry_id: attributes[:entry_id], locale_id: attributes[:locale_id])
|
|
17
|
+
if record
|
|
18
|
+
record.update!(attributes)
|
|
19
|
+
record
|
|
20
|
+
else
|
|
21
|
+
create!(attributes)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
def upsert_to_fts5_table
|
|
28
|
+
Fts.upsert(id, title, content)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
<%= render layout: "iron/field_definitions/layouts/form", locals: { field_definition: field_definition } do %>
|
|
1
|
+
<%= render layout: "iron/field_definitions/layouts/form", locals: { field_definition: field_definition } do |form| %>
|
|
2
|
+
<div class="field flex items-center justify-between">
|
|
3
|
+
<%= form.label :searchable, t("iron.field_definitions.rich_text_area.searchable") %>
|
|
4
|
+
<%= form.toggle :searchable %>
|
|
5
|
+
</div>
|
|
2
6
|
<% end %>
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
<%= render layout: "iron/field_definitions/layouts/form", locals: { field_definition: field_definition } do %>
|
|
1
|
+
<%= render layout: "iron/field_definitions/layouts/form", locals: { field_definition: field_definition } do |form| %>
|
|
2
|
+
<div class="field flex items-center justify-between">
|
|
3
|
+
<%= form.label :searchable, t("iron.field_definitions.text_area.searchable") %>
|
|
4
|
+
<%= form.toggle :searchable %>
|
|
5
|
+
</div>
|
|
2
6
|
<% end %>
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
<%= form.toggle :required %>
|
|
5
5
|
</div>
|
|
6
6
|
|
|
7
|
+
<div class="field flex items-center justify-between">
|
|
8
|
+
<%= form.label :searchable, t("iron.field_definitions.text_field.searchable") %>
|
|
9
|
+
<%= form.toggle :searchable %>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
7
12
|
<div class="field">
|
|
8
13
|
<%= form.label :allowed_values_text, t("iron.field_definitions.text_field.allowed_values") %>
|
|
9
14
|
<div>
|
data/config/locales/en.yml
CHANGED
|
@@ -207,10 +207,15 @@ en:
|
|
|
207
207
|
title: "New Field"
|
|
208
208
|
reference:
|
|
209
209
|
supported_content_types: "Supported content types"
|
|
210
|
+
rich_text_area:
|
|
211
|
+
searchable: "Searchable"
|
|
212
|
+
text_area:
|
|
213
|
+
searchable: "Searchable"
|
|
210
214
|
text_field:
|
|
211
215
|
allowed_values: "Allowed values"
|
|
212
216
|
allowed_values_help: "One value per line, leave blank to allow any value"
|
|
213
217
|
required: "Required"
|
|
218
|
+
searchable: "Searchable"
|
|
214
219
|
first_runs:
|
|
215
220
|
show:
|
|
216
221
|
description: "Start managing your content with Iron CMS"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
class CreateIronSearchRecords < ActiveRecord::Migration[8.1]
|
|
2
|
+
def up
|
|
3
|
+
create_table :iron_search_records do |t|
|
|
4
|
+
t.references :entry, null: false, foreign_key: { to_table: :iron_entries }
|
|
5
|
+
t.references :locale, null: false, foreign_key: { to_table: :iron_locales }
|
|
6
|
+
t.string :title
|
|
7
|
+
t.text :content
|
|
8
|
+
t.datetime :created_at, null: false
|
|
9
|
+
|
|
10
|
+
t.index [ :entry_id, :locale_id ], unique: true
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
return unless connection.adapter_name == "SQLite"
|
|
14
|
+
|
|
15
|
+
execute <<-SQL
|
|
16
|
+
CREATE VIRTUAL TABLE iron_search_records_fts USING fts5(
|
|
17
|
+
title,
|
|
18
|
+
content,
|
|
19
|
+
tokenize='porter'
|
|
20
|
+
)
|
|
21
|
+
SQL
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def down
|
|
25
|
+
if connection.adapter_name == "SQLite"
|
|
26
|
+
execute "DROP TABLE IF EXISTS iron_search_records_fts"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
drop_table :iron_search_records, if_exists: true
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/iron/engine.rb
CHANGED
|
@@ -12,6 +12,7 @@ require "iron/sdk"
|
|
|
12
12
|
require "pagy"
|
|
13
13
|
require "iron/routing"
|
|
14
14
|
require "iron/image_analyzer"
|
|
15
|
+
require "iron/fts5"
|
|
15
16
|
|
|
16
17
|
module Iron
|
|
17
18
|
class Engine < ::Rails::Engine
|
|
@@ -58,6 +59,12 @@ module Iron
|
|
|
58
59
|
end
|
|
59
60
|
end
|
|
60
61
|
|
|
62
|
+
initializer "iron.fts5" do |app|
|
|
63
|
+
app.config.after_initialize do
|
|
64
|
+
ActiveSupport.on_load(:active_record) { Fts5.ensure_virtual_table }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
61
68
|
config.to_prepare do
|
|
62
69
|
ActionView::Base.include(Module.new do
|
|
63
70
|
def attachment_url(attachment, **options)
|
data/lib/iron/fts5.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# SQLite FTS5 virtual tables are not captured by Rails' schema.rb dumper.
|
|
2
|
+
# When a database is created via db:schema:load (instead of running migrations),
|
|
3
|
+
# the FTS5 table will be missing. This ensures it exists on boot.
|
|
4
|
+
|
|
5
|
+
module Iron
|
|
6
|
+
module Fts5
|
|
7
|
+
def self.ensure_virtual_table
|
|
8
|
+
ActiveRecord::Base.connection_pool.with_connection do |conn|
|
|
9
|
+
return unless conn.adapter_name == "SQLite"
|
|
10
|
+
return unless conn.table_exists?(:iron_search_records)
|
|
11
|
+
return if conn.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='iron_search_records_fts'").any?
|
|
12
|
+
|
|
13
|
+
conn.execute <<~SQL
|
|
14
|
+
CREATE VIRTUAL TABLE iron_search_records_fts USING fts5(
|
|
15
|
+
title,
|
|
16
|
+
content,
|
|
17
|
+
tokenize='porter'
|
|
18
|
+
)
|
|
19
|
+
SQL
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/iron/version.rb
CHANGED
data/lib/tasks/iron_tasks.rake
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
namespace :iron do
|
|
2
|
+
namespace :search do
|
|
3
|
+
desc "Rebuild the full-text search index for all entries"
|
|
4
|
+
task reindex: :environment do
|
|
5
|
+
puts "Clearing search records..."
|
|
6
|
+
ActiveRecord::Base.connection.execute("DELETE FROM iron_search_records")
|
|
7
|
+
ActiveRecord::Base.connection.execute("DELETE FROM iron_search_records_fts")
|
|
8
|
+
|
|
9
|
+
puts "Reindexing entries..."
|
|
10
|
+
Iron::Entry.find_each(&:reindex)
|
|
11
|
+
|
|
12
|
+
puts "Done! Processed #{Iron::Entry.count} entries."
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
2
16
|
namespace :seed do
|
|
3
17
|
desc "Dump CMS schema and content to db/seeds/iron.zip"
|
|
4
18
|
task dump: :environment do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: iron-cms
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.14.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Massimo De Marchi
|
|
@@ -321,6 +321,7 @@ files:
|
|
|
321
321
|
- app/jobs/iron/export_job.rb
|
|
322
322
|
- app/jobs/iron/generate_entry_routes_job.rb
|
|
323
323
|
- app/jobs/iron/import_job.rb
|
|
324
|
+
- app/jobs/iron/reindex_search_job.rb
|
|
324
325
|
- app/mailers/iron/application_mailer.rb
|
|
325
326
|
- app/mailers/iron/passwords_mailer.rb
|
|
326
327
|
- app/models/concerns/iron/broadcastable.rb
|
|
@@ -350,6 +351,7 @@ files:
|
|
|
350
351
|
- app/models/iron/entry/importable.rb
|
|
351
352
|
- app/models/iron/entry/presentable.rb
|
|
352
353
|
- app/models/iron/entry/schemable.rb
|
|
354
|
+
- app/models/iron/entry/searchable.rb
|
|
353
355
|
- app/models/iron/entry/titlable.rb
|
|
354
356
|
- app/models/iron/entry/web_publishable.rb
|
|
355
357
|
- app/models/iron/exporter.rb
|
|
@@ -358,6 +360,8 @@ files:
|
|
|
358
360
|
- app/models/iron/field_definition.rb
|
|
359
361
|
- app/models/iron/field_definition/exportable.rb
|
|
360
362
|
- app/models/iron/field_definition/importable.rb
|
|
363
|
+
- app/models/iron/field_definition/ranked.rb
|
|
364
|
+
- app/models/iron/field_definition/searchable.rb
|
|
361
365
|
- app/models/iron/field_definitions/block.rb
|
|
362
366
|
- app/models/iron/field_definitions/block_list.rb
|
|
363
367
|
- app/models/iron/field_definitions/boolean.rb
|
|
@@ -386,6 +390,9 @@ files:
|
|
|
386
390
|
- app/models/iron/locale.rb
|
|
387
391
|
- app/models/iron/qr_code_link.rb
|
|
388
392
|
- app/models/iron/reference.rb
|
|
393
|
+
- app/models/iron/search/query.rb
|
|
394
|
+
- app/models/iron/search_record.rb
|
|
395
|
+
- app/models/iron/search_record/fts.rb
|
|
389
396
|
- app/models/iron/seed.rb
|
|
390
397
|
- app/models/iron/session.rb
|
|
391
398
|
- app/models/iron/user.rb
|
|
@@ -523,6 +530,7 @@ files:
|
|
|
523
530
|
- db/migrate/20251209103110_create_iron_account_imports.rb
|
|
524
531
|
- db/migrate/20260207103057_add_language_to_iron_users.rb
|
|
525
532
|
- db/migrate/20260209215027_add_active_to_iron_users.rb
|
|
533
|
+
- db/migrate/20260210220330_create_iron_search_records.rb
|
|
526
534
|
- lib/generators/iron/pages/pages_generator.rb
|
|
527
535
|
- lib/generators/iron/pages/templates/pages_controller.rb
|
|
528
536
|
- lib/generators/iron/pages/templates/show.html.erb
|
|
@@ -531,6 +539,7 @@ files:
|
|
|
531
539
|
- lib/iron-cms.rb
|
|
532
540
|
- lib/iron.rb
|
|
533
541
|
- lib/iron/engine.rb
|
|
542
|
+
- lib/iron/fts5.rb
|
|
534
543
|
- lib/iron/global_id/instance_scoped_locator.rb
|
|
535
544
|
- lib/iron/image_analyzer.rb
|
|
536
545
|
- lib/iron/lexorank.rb
|