繁体   English   中英

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

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM