[英]Is there a better way to implement my business logic in Rails?
我有一个有很多评论的 WorkSpace model 。 评论 model 有一个属性列表,每个属性都有自己计算的平均评分。 我想让用户找到他们选择的具有最高评价属性的 WorkSpaces。
我已经能够使用范围来实现这一点,并将逻辑保留在 WorkSpace model 中。
这是我第一次尝试在 Rails 中执行任何类型的逻辑,我想知道这种逻辑在 controller 中是否会更好。 它运行良好,但它生成的信息附加到每个 WorkSpace,我认为这有点多余,因为用户需要访问这些数据的唯一时间是当他们使用过滤系统时,而不是每次点击工作区。
WorkSpace Model (我正在讨论的逻辑的一半)
class WorkSpace < ApplicationRecord
belongs_to :user
has_many :reviews, dependent: :delete_all
scope :max_rating, ->(rating) { joins(:reviews)
.group('work_spaces.id')
.order('AVG(reviews.rating) desc')
.having('AVG(reviews.rating) > ?', rating) if rating }
scope :max_bathroom, ->(bathroom) { joins(:reviews)
.group('work_spaces.id')
.order('AVG(reviews.bathroom) desc')
.having('AVG(reviews.bathroom) > ?', bathroom) if bathroom }
scope :max_noise, ->(noise) { joins(:reviews)
.group('work_spaces.id')
.order('AVG(reviews.noise) desc')
.having('AVG(reviews.noise) > ?', noise) if noise }
scope :max_wifi, ->(wifi) { joins(:reviews)
.group('work_spaces.id')
.order('AVG(reviews.wifi) desc')
.having('AVG(reviews.wifi) > ?', wifi) if wifi }
scope :max_seating, ->(seating) { joins(:reviews)
.group('work_spaces.id')
.order('AVG(reviews.seating) desc')
.having('AVG(reviews.seating) > ?', seating) if seating }
def top_avg_rating
WorkSpace.max_rating(2).limit(5)
end
def top_avg_bathroom
WorkSpace.max_bathroom(2).limit(5)
end
def top_avg_noise
WorkSpace.max_noise(2).limit(5)
end
def top_avg_wifi
WorkSpace.max_wifi(2).limit(5)
end
def top_avg_seating
WorkSpace.max_seating(2).limit(5)
end
审查 Model
# frozen_string_literal: true
class Review < ApplicationRecord
belongs_to :work_space
belongs_to :user
end
WorkSpace 序列化程序
class WorkSpaceSerializer < ActiveModel::Serializer
attributes :id,
:place_id,
:lat,
:lng,
:name,
:address,
:photo,
:reviews,
:user,
:count_reviews,
:avg_rating,
:avg_noise,
:avg_wifi,
:avg_bathroom,
:avg_food,
:avg_coffee,
:avg_seating,
:avg_outlet,
:bool_outlet,
:bool_seating,
:bool_coffee,
:bool_food,
:bool_bathroom,
:bool_wifi,
:top_avg_rating,
:top_avg_bathroom,
:top_avg_noise,
:top_avg_wifi,
:top_avg_seating
has_one :user
has_many :reviews
end
这种类型的逻辑是否可以在 WorkSpace controller 中完成? 并且仅在发出 Axios GET 请求时访问? 或者……我是不是太离谱了,我应该现在就放弃吗?
更新
到目前为止,我能够用这段代码来干掉范围。
scope :by_average_for, ->(column) {
joins(:reviews)
.group('work_spaces.id')
.order("AVG(reviews.#{column}) desc")
.having("AVG(reviews.#{column}) > 4", column) if column
}
谢谢你, https://stackoverflow.com/users/14660/schwern 。
接下来我正在实施 class 方法。 似乎无法正常工作...
通常,控制器是“瘦的”。 它们应该只包含将模型连接到视图的逻辑,仅此而已。 数据库逻辑进入模型。 显示逻辑进入装饰器。 与 API 和服务对话进入服务对象。
你的逻辑可以被干掉。 您的示波器可以变成一个带参数的 scope。
scope :by_average_for, ->(column) {
joins(:reviews)
.group('work_spaces.id')
.order("AVG(reviews.#{column})", :desc)
}
同样,单个 class 方法可以替换所有top_foo
方法。 通过采用默认值 arguments 可以使这些变得更加灵活。
class << self
def top_averages_for(column, greater_than: 2, limit: 5)
by_average_for(column)
.having("AVG(reviews.#{column}) > ?", greater_than)
.limit(5)
end
end
如果您需要单独的实例方法进行序列化,也可以通过使用define_method动态定义方法来干掉它们。
TOP_AVG_COLUMNS = [
:rating,
:seating,
...
].freeze
TOP_AVG_COLUMNS.each do |column|
define_method(:"top_avg_#{column}") do
top_averages_for(column)
end
end
如果这些仅用于序列化,则它们可能更适合装饰器。
您可以使用TOP_AVG_COLUMNS
和类似的常量来干燥要序列化的属性列表。
# In WorkSpace
TOP_AVG_ATTRIBUTES = TOP_AVG_COLUMNS.map { |col|
:"top_avg_#{col}"
}.freeze
# In WorkSpaceSerializer
ATTRIBUTE_COLUMNS = [
:id,
:place_id,
:lat,
:lng,
:name,
:address,
:photo,
:reviews,
:user
].freeze
attributes(ATTRIBUTE_COLUMNS + WorkSpace::TOP_AVG_ATTRIBUTES)
如果这个业务逻辑变得更复杂,你的 model 可能会变胖。 然后将其移至 WorkSpaceManager,即ActiveModel::Model ,其目的是在 WorkSpaces 上执行业务逻辑。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.