ruby-on-rails - Ruby Rails 在 ERB 搜索最佳实践中创建变量
问题描述
我有一个带有搜索功能的项目。我有三个类,产品、评论和用户。我在产品模型中有一个 self.search 方法对所有三个类表进行查询。
当我将搜索视图放在一起时,它列出了产品名称、评论作者以及评论中与搜索字符串匹配的任何文本。
然后我想,让漂亮一点。
所以我交叉引用 id 来列出用户头像、时间戳和星级。但要做到这一点,我要在所有类中搜索局部变量。
所以我认为现在这里有一股代码味道。没有控制器可以使用@users = User.all 加载。我坚持使用 _user = User.find_by 类型的局部变量赋值。搜索手指现在在所有类别中。
所以我的问题是,我的臃肿搜索现在是否应该被正确地搭建到一个实际的班级中?或者有没有像助手这样的另一种方法?这有可能不是那么糟糕的方法吗?
红宝石 2.6.5 轨道 5.2.4
这是我的看法:
<div class="home-container">
<div class="welcome-sub-header">
<h1>Search Results</h1>
<div class='search-flex-container'>
<h2>Products that Match</h2>
<% if @results_products.empty?%>
<div class='search-noresult-card'>
<p>No products matched the search.</p>
</div>
<% end %>
<% @results_products.each do |results_product| %>
<div class='search-product-card'>
<div class='search-product-flex'>
<%= render partial: "shared/product_image", locals: { product: results_product } %>
<%= link_to results_product.name, product_path(results_product.id) %>
<p>Imported From <%= results_product.country %></p>
<div class="search-adjust-stars">
<%= render partial: "shared/review_stars", locals: { review: results_product.average_review } %>
</div>
</div>
</div>
<% end %>
<%= will_paginate @results_products %>
<h2>Product Reviews that Match</h2>
<% if @results_reviewers.empty?%>
<div class='search-noresult-card'>
<p>No reviewers matched the search.</p>
</div>
<% end %>
<% @results_reviewers.each do |results_reviewer| %>
<div class='search-review-card'>
<div class='search-review-flex'>
<% _reviewed_product = Product.find_by(id: results_reviewer.product_id) %>
<% _user = User.find_by(id: results_reviewer.user_id) %>
<div class="search-review-box1">
<%= render partial: "shared/avatar", locals: { user: _user } %>
<%= link_to (results_reviewer.author + " review of " + _reviewed_product.name), review_path(id: results_reviewer, product_id: results_reviewer.product_id) %><br>
</div>
<div class="search-review-box2">
<%= render partial: "shared/review_stars", locals: { review: results_reviewer.rating } %>
</div>
<div class="search-review-box3">
<p>On <%= results_reviewer.created_at.strftime('%m-%d-%Y') %></p>
</div>
<div class="search-review-box5">
<%= render partial: "shared/product_image", locals: { product: _reviewed_product } %>
</div>
</div>
</div>
<% end %>
<%= will_paginate @results_reviewers %>
<h2>Review Text that Matches</h2>
<% if @results_reviews.empty?%>
<div class='search-noresult-card'>
<p>No review text matched the search.</p>
</div>
<% end %>
<% @results_reviews.each do |results_review| %>
<% _user = User.find_by(id: results_review.user_id) %>
<div class='search-review-card'>
<%= render partial: "shared/avatar", locals: { user: _user } %>
<% _reviewed_product = Product.find_by(id: results_review.product_id) %>
<%= link_to (results_review.author + " review of " + _reviewed_product.name), review_path(id: results_review, product_id: results_review.product_id) %>
<p>Reviewed on <%= results_review.created_at.strftime('%m-%d-%Y') %></p>
<div class="search-review-text">
<%= results_review.content_body %>
</div>
</div>
<% end %>
<%= will_paginate @results_reviews %>
</div>
</div>
<div>Font made from <a href="http://www.onlinewebfonts.com">oNline Web Fonts</a>is licensed by CC BY 3.0</div>
</div>
这是具有 self.search 方法的 Product 模型。
class Product < ApplicationRecord
has_many :reviews, dependent: :destroy
validates :name, presence: true
validates_length_of :name, maximum: 30
validates :price, presence: true
validates_length_of :price, maximum: 8
validates :country, presence: true
validates_length_of :country, maximum: 50
has_one_attached :product_photo
before_save(:titleize_product)
scope :most_reviewed, -> {(
select("products.id, products.name, products.average_review,products.price, products.country, count(reviews.id) as reviews_count")
.joins(:reviews)
.group("products.id")
.order("reviews_count DESC")
.limit(6)
)}
scope :newest_product, -> { order(created_at: :desc).limit(6) }
scope :highest_reviewed, -> {(
select("products.id, products.name, products.price, products.country, products.average_review as average_review")
.joins(:reviews)
.group("products.id")
.order("average_review DESC")
.limit(6)
)}
def self.search(search)
where("lower(reviews.author) LIKE :search OR lower(products.name) LIKE :search OR lower(reviews.content_body) LIKE :search", search: "%#{search.downcase}%").uniq
end
def next
Product.where("id > ?", id).order("id ASC").first || Product.first
end
def previous
Product.where("id < ?", id).order("id DESC").first || Product.last
end
private
def titleize_product
self.name = self.name.titleize
self.country = self.country.titleize
end
end
解决方案
我认为您应该将搜索方法分离到可搜索模块中,并允许可搜索模型声明搜索查询中涉及的属性。
module Searchable
extend ActiveSupport::Concern
included do
@search_clauses ||= Hash.new
end
module ClassMethods
def search(key_search, scope: 'default')
search_clause ||= search_string_for(scope)
relation = joins(*search_clause[:joins].map(&:to_sym)) unless search_clause[:joins].blank?
(relation || self).where(search_clause[:where], key: "%#{key_search.downcase}%")
end
def search_clause(clause_str, scope: 'default')
@search_clauses[scope] = parse(clause_str)
# we can add dynamic function like seach_`scope_name`
end
def search_attributes(attributes, scope: 'default')
search_clause(attributes.map { |attribute| "%#{attribute}%"}.join(" OR "), scope: scope)
end
private
def search_string_for(scope)
@search_clauses&.dig(scope)
end
def parse(clause)
join_tables = Set.new
where_clause = clause.dup
parse_tables(clause) do |table|
join_tables << table unless table == self.class_name.tableize || table.blank?
where_clause.sub!("#{table}.", "#{table.tableize}.")
end
parse_logics!(where_clause)
parse_matches!(where_clause)
# other cases ...
{
joins: join_tables,
where: where_clause
}
end
def parse_logics!(clause)
clause.gsub!(/\|/, "OR")
clause.gsub!(/\&/, "AND")
# other ...
end
def parse_matches!(clause)
clause.scan(/(?<=%)[^\s\|\&\z\Z]*(?=%)/).each do |attribute|
clause.sub!(/%[^\s\|\&\z\Z]*%/, "lower(#{attribute}) LIKE :key")
end
end
def parse_tables(clause, &block)
clause.scan(/(?<=[%\s])[^%\s\|\&\z]*(?=\.)/).each(&block)
end
end
end
我的实践项目中的示例:
class Task < ApplicationRecord
include Searchable
belongs_to :requirement
has_many :skills
# declare search clause, we can set the whole where-clause here
# those syntax %,|,& ... just for fun (simple way)
search_clause "%content% | %requirement.description%"
# with scope
search_clause "(%content% | %requirement.description%) & status = 0", scope: :open
# declare attributes
search_attributes %w[content skills.name], scope: :skill
end
# Demo
Task.search("setup") # default scope
Task.search("setup", scope: :open)
Task.search("ruby", scope: :skill)
注意:我建议您使用 search-gems,例如 ransack、pg_search(在全文搜索的情况下)。
推荐阅读
- r - 如何根据 R 中的第一原理绘制双变量密度?
- python - 调用函数直到输入为“否”
- node.js - 无法安装包,因为它没有 package.json
- javascript - Javascript element.click() 不适用于 div 标签
- sharepoint - Power BI - 使用 SharePoint 的术语库/托管元数据层次结构
- react-hooks - 在反应中显示确认消息 2 次
- php - 使用 php 中的 API 访问 Google My Business Statistics
- reactjs - 将状态共享给其他组件 | 将数据传递给其他组件
- gitlab-ci-runner - Gitlab Runner 在启动下一个管道之前等待
- ansible - 使用带有 replace/lineinfile 模块的 ansible 删除 conf 文件中的重复条目