Rails 5 Activerecord: как отобразить тип записи на пользователя на панели инструментов - PullRequest
0 голосов
/ 22 февраля 2019

В настоящее время я пытаюсь реорганизовать какой-то старый код, который показывает панель управления для менеджера.

Пользователь отправляет множество электронных писем, связанных с бронированием еды, которые в основном хранятся в целях аудита.Мы отслеживаем количество электронных писем, отправленных каждому пользователю, и значение этого бронирования, сгруппированные по email_type

. В настоящее время ActiveRecord получает каждое электронное письмо с предложением where для фильтрации по create_at, а затем rails добавляет в массивкоторый затем выводится в таблицу.Это кажется действительно неэффективным, и Nginx истекает, поэтому мы не можем видеть результаты

Мне кажется, что использование в основном ActiveRecord с некоторыми группами значительно упростит все это.

Я только что добавил ассоциацию к user.rb следующим образом - потому что ее не былона месте (!) и в настоящее время он не используется:

  has_many :emails, :foreign_key => "triggered_by_id"

В настоящее время MVC выглядит так:

Модель - email.rb:

  scope :sent_between, -> ( start_date, end_date ) { where("emails.created_at >= ? AND emails.created_at <= ?", start_date, end_date) }

  def self.sent_today
    sent_between(DateTime.now.beginning_of_day, DateTime.now.end_of_day)
  end

  def self.metric_hash
    {
      "venue_confirmation"                    => [0, 0],
      "enquiry_confirmation"                  => [0, 0],
      "menu_verification"                     => [0, 0],
      "amendment_information"                 => [0, 0],
      "amendment_confirmation"                => [0, 0],
      "booking_confirmation_to_venue"         => [0, 0],
      "released_confirmation_to_venue"        => [0, 0],
      "cancellation_confirmation_to_venue"    => [0, 0],
      "transfer"                              => [0, 0],
      "total"                                 => [0, 0]
    }
  end

  def self.type_for_metrics(email)
    if %w(released_confirmation_to_venue cancellation_confirmation_to_venue).include?(email.email_type)
      return "transfer" if email.booking.transferred_at
  end

    email.email_type
  end

  def self.metrics(start_date, end_date)
    metrics = {}
    totals = metric_hash
    observed_bookings = Set.new

     Email.includes(:booking).select(:id, :email_type, :triggered_by_id, :booking_id).sent_between(start_date, end_date).references(:booking).select(:booking_total).find_each do |email|

      email_type = type_for_metrics(email)

      if totals.has_key?(email_type)
        metrics[email.triggered_by_id] ||= metric_hash
        metrics[email.triggered_by_id][email_type][0] += 1
        metrics[email.triggered_by_id][email_type][1] += email.booking.booking_total
        metrics[email.triggered_by_id]["total"][0] += 1
        metrics[email.triggered_by_id]["total"][1] += email.booking.booking_total
        totals[email_type][0] += 1
        totals[email_type][1] += email.booking.booking_total
        totals["total"][0] += 1

        # Only count the each booking once for the total value
        unless observed_bookings.include?(email.booking_id)
          totals["total"][1] += email.booking.booking_total
          observed_bookings << email.booking_id
        end
      end
    end

    results = metrics.inject({}) do |memo, row|
      if row[1]["total"][0] > 0
        if row[0]
          user = User.find(row[0])
          memo[user.name] = row[1]
        else
          memo["Sent Before Tracking"] = row[1]
        end
        memo
      end
    end
    results["Total"] = totals

    results
  end

index_controller.rb:

  def metrics
      @start = ( params[:start] && Time.zone.parse(params[:start]) ) || DateTime.now.start_of_period
      @end   = ( params[:end] && Time.zone.parse(params[:end]) ) || DateTime.now.end_of_period
      @email_metrics = Email.metrics(@start, @end)
  end

_metrics.html.erb

<h2>Emails Sent</h2>

  <table>
    <thead>
      <tr>
        <th></th>
        <th title="New Enquiry">New</th>
        <th title="Menu Confirmation">Menu</th>
        <th title="Operator Confirmation">Confirm</th>
        <th title="Released Enquiry">Released</th>
        <th title="Cancelled Booking">Cancelled</th>
        <th title="Amendment Information">Amend Info</th>
        <th title="Amendment Confirmation">Amend Confirm</th>
        <th title="Transferred">Transfer</th>
        <th>Total</th>
      </tr>
    </thead>
    <tbody>
      <% @email_metrics.each do |key, metrics| %>
        <tr>
          <th rowspan="2"><%= key %></th>
          <td><%= metrics["venue_confirmation"][0] %></td>
          <td><%= metrics["menu_verification"][0] %></td>
          <td><%= metrics["booking_confirmation_to_venue"][0] %></td>
          <td><%= metrics["released_confirmation_to_venue"][0] %></td>
          <td><%= metrics["cancellation_confirmation_to_venue"][0] %></td>
          <td><%= metrics["amendment_information"][0] %></td>
          <td><%= metrics["amendment_confirmation"][0] %></td>
          <td><%= metrics["transfer"][0] %></td>
          <td><%= metrics["total"][0] %></td>
        </tr>
        <tr>
          <td><%= number_to_currency metrics["venue_confirmation"][1] %></td>
          <td><%= number_to_currency metrics["menu_verification"][1] %></td>
          <td><%= number_to_currency metrics["booking_confirmation_to_venue"][1] %></td>
          <td><%= number_to_currency metrics["released_confirmation_to_venue"][1] %></td>
          <td><%= number_to_currency metrics["cancellation_confirmation_to_venue"][1] %></td>
          <td><%= number_to_currency metrics["amendment_information"][1] %></td>
          <td><%= number_to_currency metrics["amendment_confirmation"][1] %></td>
          <td><%= number_to_currency metrics["transfer"][1] %></td>
          <td>N/A</td>
        </tr>
      <% end %>
    </tbody>
  </table>
</div>

Мне кажется, что я бы начал с User.emails.etc ... но я получаюзастрял с несколькими group_by и массивно сложным массивом в настоящее время на месте.

Вот скриншот того, как это должно выглядеть.В среде разработки только один пользователь.

Снимок экрана: как должны выглядеть данные

Ответы [ 2 ]

0 голосов
/ 23 февраля 2019

Я согласен с вами, что получение всех электронных писем, их перебор и создание результирующего набора вручную кажется абсолютно контрпродуктивным.

Вы не сможете исправить это с помощью одного запроса, но вместо выборки 10000 записей вы сможете решить ее с помощью нескольких (более быстрых запросов) и позволить базе данных выполнять свою работу.

Я приведу несколько примеров, которые, надеюсь, помогут вам начать.

Извлечение всех типов за указанный период:

Email.sent_between(start_date, end_date).group(:email_type).count 

Извлечение количества писем на пользователя за данный период

Email.sent_between(start_date, end_date).group(:triggered_by_id).count 

Это возвращает две вещи:пользователи и их количество.Используйте этих пользователей, чтобы получить результат для каждого пользователя:

Email.where(trigger_by_id: user_id).sent_between(start_date, end_date).group(:email_type).count 

Общая сумма заказа кажется немного сложнее, но я думаю, что-то вроде этого должно работать:

Email.includes(:booking).sent_between(start_date, end_date).references(:booking).group(:email_type).sum(:booking_total) 

Я понимаю, что этоне полное решение, вы все равно должны составить свой «хеш-результат», но это должно привести к получению ваших необработанных данных намного быстрее.

0 голосов
/ 22 февраля 2019

Я нашел решение.

  scope :sent_between, -> ( start_date, end_date ) { where("emails.created_at >= ? AND emails.created_at <= ?", start_date, end_date) }

  def self.sent_today
    sent_between(DateTime.now.beginning_of_day, DateTime.now.end_of_day)
  end

  def self.metric_hash
    {
        "venue_confirmation"                    => [0, 0],
        "enquiry_confirmation"                  => [0, 0],
        "menu_verification"                     => [0, 0],
        "amendment_information"                 => [0, 0],
        "amendment_confirmation"                => [0, 0],
        "booking_confirmation_to_venue"         => [0, 0],
        "released_confirmation_to_venue"        => [0, 0],
        "cancellation_confirmation_to_venue"    => [0, 0],
        "transfer"                              => [0, 0],
        "total"                                 => [0, 0]
    }
  end

  def self.type_for_metrics(email)
    if %w(released_confirmation_to_venue cancellation_confirmation_to_venue).include?(email.email_type)
      return "transfer" if email.booking.transferred_at
    end

    email.email_type
  end

  def self.metrics(start_date, end_date)
    metrics = {}
    totals = metric_hash
    observed_bookings = {}

    Email.includes(:booking).select(:id, :email_type, :triggered_by_id, :booking_id).sent_between(start_date, end_date).
        references(:booking).select(:booking_total).find_each do |email|

      email_type = type_for_metrics(email)

      if totals[email_type]
        metrics[email.triggered_by_id] ||= metric_hash
        metrics[email.triggered_by_id][email_type][0] += 1
        metrics[email.triggered_by_id][email_type][1] += email.booking.booking_total
        metrics[email.triggered_by_id]["total"][0] += 1
        metrics[email.triggered_by_id]["total"][1] += email.booking.booking_total
        totals[email_type][0] += 1
        totals[email_type][1] += email.booking.booking_total
        totals["total"][0] += 1

        # Only count the each booking once for the total value
        unless observed_bookings[email.booking_id]
          totals["total"][1] += email.booking.booking_total
          observed_bookings[email.booking_id] = true
        end
      end
    end

    users = users = User.select(:id,:name).where(id: metrics.keys).group_by(&:id).transform_values{ |value| value.first }
    results = metrics.inject({}) do |memo, row|
      if row[1]["total"][0] > 0
        if row[0]
          user = users[row[0]]
          memo[user.name] = row[1]
        else
          memo["Sent Before Tracking"] = row[1]
        end
        memo
      end
    end
    results["Total"] = totals

    results
  end

То, что я сделал здесь, я изменил тип observed_bookings на хеш.Поскольку хэши имеют быстрый быстрый поиск и вставка нового элемента в хэш - это O (1) в вашем сценарии.В вашем сценарии вы делаете поиск и хотите, чтобы структура данных была уникальной.Так что хэш твой друг.

Я изменил totals.has_key?(email_type) на totals[email_type].Снова здесь у нас есть сверкающий взгляд, который O (1).

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

И да, пользователи являются хэшем, а идентификатор пользователя является ключом хэша.Итак, снова у нас есть сверкающий быстрый поиск, который является O (1).

Я думаю, что этот запрос должен дать то, что вы ожидаете с точки зрения времени ответа.

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