Remember recent searches
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
class SearchesController < ApplicationController
|
||||
MAX_RESULTS = 50
|
||||
def create
|
||||
Current.user.remember_search(query_param)
|
||||
head :ok
|
||||
end
|
||||
|
||||
def show
|
||||
@search = Search.new(Current.user, query_param)
|
||||
@search_results = Current.user.search(query_param, max_results: 50)
|
||||
@recent_search_queries = Current.user.search_queries.order(created_at: :desc).limit(10)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -7,7 +7,7 @@ class Search
|
||||
|
||||
def initialize(user, query, max_results: 50)
|
||||
@user = user
|
||||
@query = Query.new(query)
|
||||
@query = Query.wrap(query)
|
||||
@max_results = max_results
|
||||
end
|
||||
|
||||
|
||||
+12
-18
@@ -1,32 +1,26 @@
|
||||
class Search::Query
|
||||
attr_reader :terms
|
||||
class Search::Query < ApplicationRecord
|
||||
validates :terms, presence: true
|
||||
before_validation :sanitize_query_syntax
|
||||
|
||||
class << self
|
||||
def wrap(query)
|
||||
if query.is_a?(self)
|
||||
self.new(query)
|
||||
else
|
||||
query
|
||||
else
|
||||
self.new(terms: query)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(terms)
|
||||
@terms = sanitize_query_syntax(terms)
|
||||
end
|
||||
|
||||
def valid?
|
||||
@terms.present?
|
||||
end
|
||||
|
||||
alias to_s terms
|
||||
alias_attribute :to_s, :terms
|
||||
|
||||
private
|
||||
def sanitize_query_syntax(terms)
|
||||
terms = terms.to_s
|
||||
terms = remove_invalid_search_characters(terms)
|
||||
terms = remove_unbalanced_quotes(terms)
|
||||
terms.presence
|
||||
def sanitize_query_syntax
|
||||
self.terms = begin
|
||||
terms = remove_invalid_search_characters(self.terms)
|
||||
terms = remove_unbalanced_quotes(terms)
|
||||
terms.presence
|
||||
end
|
||||
end
|
||||
|
||||
def remove_invalid_search_characters(terms)
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
class User < ApplicationRecord
|
||||
include Accessor, ActionText::Attachable, Assignee, Mentionable, Named, Role, SignalUser, Transferable
|
||||
include Accessor, ActionText::Attachable, Assignee, Mentionable, Named, Role, Searcher,
|
||||
SignalUser, Transferable
|
||||
include Timelined # Depends on Accessor
|
||||
|
||||
has_one_attached :avatar
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
module User::Searcher
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :search_queries, class_name: "Search::Query", dependent: :destroy
|
||||
end
|
||||
|
||||
def search(terms, max_results: 50)
|
||||
Search.new(self, terms, max_results: max_results).results
|
||||
end
|
||||
|
||||
def remember_search(terms)
|
||||
search_queries.create(terms: terms) if search_queries.last&.terms != terms
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
<%= form_with url: search_path, method: :get, class: "search__form" do |form| %>
|
||||
<div class="search__field">
|
||||
<%= text_field_tag :q, params[:q], class: "input", type: "search", placeholder: "What are you looking for?" %>
|
||||
<span class="search__field-icon btn btn--circle borderless">
|
||||
<%= icon_tag "search" %>
|
||||
</span>
|
||||
<%= link_to search_path, class: "search__field-clear btn btn--circle borderless" do %>
|
||||
<%= icon_tag "close" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,3 @@
|
||||
<li>
|
||||
<%= link_to search_query.terms, search_path(q: search_query.terms) %>
|
||||
</li>
|
||||
@@ -13,27 +13,15 @@
|
||||
<% end %>
|
||||
|
||||
<section class="search">
|
||||
<%= form_with url: search_path, method: :get, class: "search__form" do |form| %>
|
||||
<div class="search__field">
|
||||
<%= text_field_tag :q, params[:q], class: "input", type: "search", placeholder: "What are you looking for?" %>
|
||||
<span class="search__field-icon btn btn--circle borderless">
|
||||
<%= icon_tag "search" %>
|
||||
</span>
|
||||
<%= link_to search_path, class: "search__field-clear btn btn--circle borderless" do %>
|
||||
<%= icon_tag "close" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render "searches/form" %>
|
||||
|
||||
<%= auto_submit_form_with url: search_path(q: params[:q]), method: :post %>
|
||||
|
||||
<ul class="search__list">
|
||||
<%= render partial: "searches/result", collection: @search.results %>
|
||||
<%= render partial: "searches/result", collection: @search_results %>
|
||||
</ul>
|
||||
|
||||
<ul class="search__history">
|
||||
<% 7.times do %>
|
||||
<li>
|
||||
<%= link_to "historic search term", "#" %>
|
||||
</li>
|
||||
<% end %>
|
||||
<%= render partial: "searches/search_query", collection: @recent_search_queries %>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
class CreateSearchQueries < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
create_table :search_queries do |t|
|
||||
t.references :user, null: false, foreign_key: true
|
||||
t.string :terms, limit: 2000, null: false
|
||||
|
||||
t.timestamps
|
||||
|
||||
t.index %i[ user_id terms ]
|
||||
end
|
||||
end
|
||||
end
|
||||
Generated
+12
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.1].define(version: 2025_06_24_063244) do
|
||||
ActiveRecord::Schema[8.1].define(version: 2025_06_24_080408) do
|
||||
create_table "accesses", force: :cascade do |t|
|
||||
t.integer "collection_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
@@ -304,6 +304,15 @@ ActiveRecord::Schema[8.1].define(version: 2025_06_24_063244) do
|
||||
# Unknown type '' for column 'rowid'
|
||||
|
||||
|
||||
create_table "search_queries", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.string "terms", limit: 2000, null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.index ["user_id", "terms"], name: "index_search_queries_on_user_id_and_terms"
|
||||
t.index ["user_id"], name: "index_search_queries_on_user_id"
|
||||
end
|
||||
|
||||
create_table "search_results", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
@@ -331,6 +340,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_06_24_063244) do
|
||||
t.datetime "created_at", null: false
|
||||
t.string "title"
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["title"], name: "index_tags_on_account_id_and_title", unique: true
|
||||
end
|
||||
|
||||
create_table "users", force: :cascade do |t|
|
||||
@@ -391,6 +401,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_06_24_063244) do
|
||||
add_foreign_key "notifications", "users", column: "creator_id"
|
||||
add_foreign_key "pins", "cards"
|
||||
add_foreign_key "pins", "users"
|
||||
add_foreign_key "search_queries", "users"
|
||||
add_foreign_key "sessions", "users"
|
||||
add_foreign_key "taggings", "cards"
|
||||
add_foreign_key "taggings", "tags"
|
||||
|
||||
+79
-2
@@ -913,6 +913,31 @@ columns:
|
||||
default_function:
|
||||
collation:
|
||||
comment:
|
||||
search_queries:
|
||||
- *5
|
||||
- *6
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
|
||||
auto_increment:
|
||||
name: terms
|
||||
cast_type: !ruby/object:ActiveModel::Type::String
|
||||
true: t
|
||||
false: f
|
||||
precision:
|
||||
scale:
|
||||
limit: 2000
|
||||
sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
|
||||
sql_type: varchar(2000)
|
||||
type: :string
|
||||
limit: 2000
|
||||
precision:
|
||||
scale:
|
||||
'null': false
|
||||
default:
|
||||
default_function:
|
||||
collation:
|
||||
comment:
|
||||
- *9
|
||||
- *25
|
||||
search_results:
|
||||
- *5
|
||||
- *6
|
||||
@@ -1088,6 +1113,7 @@ primary_keys:
|
||||
reactions: id
|
||||
schema_migrations: version
|
||||
search_embeddings_vector_chunks00: rowid
|
||||
search_queries: id
|
||||
search_results: id
|
||||
sessions: id
|
||||
taggings: id
|
||||
@@ -1130,6 +1156,7 @@ data_sources:
|
||||
reactions: true
|
||||
schema_migrations: true
|
||||
search_embeddings_vector_chunks00: true
|
||||
search_queries: true
|
||||
search_results: true
|
||||
sessions: true
|
||||
taggings: true
|
||||
@@ -2191,6 +2218,40 @@ indexes:
|
||||
valid: true
|
||||
schema_migrations: []
|
||||
search_embeddings_vector_chunks00: []
|
||||
search_queries:
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
table: search_queries
|
||||
name: index_search_queries_on_user_id
|
||||
unique: false
|
||||
columns:
|
||||
- user_id
|
||||
lengths: {}
|
||||
orders: {}
|
||||
opclasses: {}
|
||||
where:
|
||||
type:
|
||||
using:
|
||||
include:
|
||||
nulls_not_distinct:
|
||||
comment:
|
||||
valid: true
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
table: search_queries
|
||||
name: index_search_queries_on_user_id_and_terms
|
||||
unique: false
|
||||
columns:
|
||||
- user_id
|
||||
- terms
|
||||
lengths: {}
|
||||
orders: {}
|
||||
opclasses: {}
|
||||
where:
|
||||
type:
|
||||
using:
|
||||
include:
|
||||
nulls_not_distinct:
|
||||
comment:
|
||||
valid: true
|
||||
search_results: []
|
||||
sessions:
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
@@ -2243,7 +2304,23 @@ indexes:
|
||||
nulls_not_distinct:
|
||||
comment:
|
||||
valid: true
|
||||
tags: []
|
||||
tags:
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
table: tags
|
||||
name: index_tags_on_account_id_and_title
|
||||
unique: true
|
||||
columns:
|
||||
- title
|
||||
lengths: {}
|
||||
orders: {}
|
||||
opclasses: {}
|
||||
where:
|
||||
type:
|
||||
using:
|
||||
include:
|
||||
nulls_not_distinct:
|
||||
comment:
|
||||
valid: true
|
||||
users:
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
table: users
|
||||
@@ -2344,4 +2421,4 @@ indexes:
|
||||
comment:
|
||||
valid: true
|
||||
workflows: []
|
||||
version: 20250624063244
|
||||
version: 20250624080408
|
||||
|
||||
Reference in New Issue
Block a user