Есть ли лучший способ реализовать мою бизнес логи c в Rails? - PullRequest
2 голосов
/ 09 апреля 2020

У меня есть модель WorkSpace, которую has_many Отзывы. Модель «Обзоры» имеет список атрибутов, каждый из которых рассчитывает свой средний рейтинг. Я бы хотел, чтобы пользователи могли находить WorkSpaces с атрибутом с наивысшим рейтингом по своему выбору.

Я смог добиться этого, используя области и сохраняя логику c внутри модели WorkSpace.

Это моя первая попытка сделать любой вид логики c в Rails, и мне интересно, будет ли эта логика c лучше в контроллере. Это работает хорошо, но информация, которую он генерирует, прикрепляется к каждому WorkSpace, и я думаю, что это немного избыточно, поскольку единственный раз, когда пользователю потребуется доступ к этим данным, это когда они используют систему фильтрации, а не каждый раз, когда нажимают на WorkSpace.

Модель WorkSpace (половина с логикой c Я обсуждаю)

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

Сериализатор 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

Может ли это или должен быть выполнен этот тип логики c в контроллере WorkSpace? И доступен только тогда, когда сделан запрос Ax ios GET? Или ... я далеко от базы, и я должен сейчас просто сдаться?

Обновление

До сих пор я смог Dry увеличить границы с этим бит кода.

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

Спасибо, { ссылка }.

Далее я работаю над реализацией метода класса. Не могу заставить это работать ...

1 Ответ

1 голос
/ 09 апреля 2020

Как правило, контроллеры "тощие". Они должны содержать только logi c, который соединяет модели с представлениями и все. База данных logi c переходит в модели. Дисплей логи c переходит в декораторов . Общение с API и сервисами происходит с объектами обслуживания .

Ваш логин c может быть DRY до . Ваши области видимости могут быть превращены в одну область видимости, которая принимает аргумент.

  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

Если вам нужны методы отдельных экземпляров для сериализации, их также можно увеличить на DRY, определив методы динамически с помощью 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 и аналогичные константы для DRY до вашего список атрибутов для сериализации.

# 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)

Если эти бизнес-логи c усложняются, ваша модель может стать толстой. Затем вы переместили бы его в WorkSpaceManager, ActiveModel :: Model , целью которого является выполнение бизнес-логики c на рабочих пространствах.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...