有没有更好的方法在Rails中实现我的业务逻辑



我有一个WorkSpace模型,该模型具有has_many评论。Reviews模型有一个属性列表,每个属性都有自己的平均评分。我希望允许用户找到他们选择的具有最高评级属性的工作空间。

我已经能够使用作用域并将逻辑保留在WorkSpace模型中来实现这一点。

这是我第一次尝试在Rails中执行任何类型的逻辑,我想知道这种逻辑在控制器中是否会更好。它运行良好,但它生成的信息附加到每个工作空间,我认为这有点多余,因为用户唯一需要访问这些数据的时间是在使用过滤系统时,而不是每次点击工作空间时。

工作空间模型(我正在讨论的逻辑的一半(

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

审核模型

# frozen_string_literal: true
class Review < ApplicationRecord
belongs_to :work_space
belongs_to :user
end

工作空间序列化程序

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控制器中执行这种类型的逻辑?并且仅在发出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.

接下来我将致力于实现类方法。似乎无法实现。。。

通常,控制器是"瘦"的。它们应该只包含将模型连接到视图的逻辑,仅此而已。数据库逻辑进入模型。显示逻辑进入装饰器。与API和服务的对话进入服务对象。

你的逻辑可能是枯燥的。您的作用域可以转换为一个接受参数的作用域。

scope :by_average_for, ->(column) {
joins(:reviews)
.group('work_spaces.id')
.order("AVG(reviews.#{column})", :desc)
}

类似地,一个类方法可以替换所有的top_foo方法。通过采用默认参数,可以使这些方法更加灵活。

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动态定义方法来对它们进行DRY。

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)

如果这种业务逻辑变得更加复杂,那么您的模型可能会变得更胖。然后将其移动到WorkSpaceManager中,即ActiveModel::Model,其目的是在WorkSpaces上执行业务逻辑。

最新更新