Модель Rails с агрегированными данными (без поддержки таблицы) - PullRequest
3 голосов
/ 27 ноября 2009

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

Пример:

У меня есть модель ресторана, хранящаяся в таблице ресторанов в БД. Я хотел бы иметь модель RestaurantStats, где я могу запустить RestaurantStats.find_total_visitors или RestaurantStats.find_time_spent и т. Д. ... и он возвращает набор моделей RestaurantStats, каждая из которых имеет:

[: restaurant_id,: stat_value]

Очевидно, что в каждом методе find ... stat_value будет означать что-то свое (для find_time_spent это будет потрачено секунд, для find_total_visitors это будет число посетителей). Идея состоит в том, чтобы вернуть 100 лучших ресторанов по затраченному времени или общему количеству посетителей.

Пока что я создаю модель (не унаследованную от ActiveRecord)

class RestaurantStats 
   attr_reader :restaurant_id
   attr_reader :stat_value

   def self.find_total_visitors ... 
   def self.find_time_spent ...
end

Вопрос в том, как определить функции find_total_visitors, find_time_spent рельсовым способом, чтобы он заполнил поля restaurant_id, stat_value?

Ответы [ 3 ]

2 голосов
/ 27 ноября 2009

Вы уверены, что не хотите, чтобы это были методы в ресторане?

class Restaurant < ActiveRecord::Base
  has_many :visitors

  def total_visitors
    visitors.count # Or whatever
  end

  def time_spent
    visitors.average(:visit_time) # Or whatever      
  end
end
1 голос
/ 28 ноября 2009

Учитывая, что вы хотите отсортировать результаты поиска по статистике, добавление счетчиков кешей в модель Restaurant выглядит как ваш лучший вариант.

В случае общего количества посетителей это просто. В случае общего time_spent, это будет немного сложнее, но все же не является неуправляемым. Если вы ищете среднего, то все становится немного сложнее.

Вот код, необходимый для добавления счетчиков в модель вашего ресторана. Обратите внимание, что большая часть нового кода модели находится в модели посетителя.

Добавление новых столбцов в Restaurant с помощью миграции:

class AddCounterCaches < ActiveRecord::Migration
  def self.up
    add_column :restaurants, :visitors_count, :integer, :default => 0
    add_column :restaurants, :total_time_spent, :integer, :default => 0
    Restaurant.reset_column_information
    Restaurant.find(:all).each do |r|
      count = r.visitors.length
      total = r.visitors.inject(0) {|sum, v| sum + v.time_spent}
      average = count == 0 ? 0 : total/count
      r.update_counters r.id, :visitors_count => count
       :total_time_spent => total, :average_time_spent => average
    end
  end

  def self.down
    remove_column :restaurants, :visitors_count        
    remove_column :restaurants, :total_time_spent
  end
end

Обновление модели посетителя для обновления кэшей счетчика

class Vistor < ActiveRecord::Base
  belongs_to :restaurant, :counter_cache => true 

  after_save :update_restaurant_time_spent, 
    :if => Proc.new {|v| v.changed.include?("time_spent")}

  def :update_restaurant_time_spent
    difference = changes["time_spent"].last - changes["time_spent"].first
    Restaurant.update_counters(restaurant_id, :total_time_spent => difference)       
    restaurant.reload   
    avg = restaurant.visitors_count == 0 ? 
      0 : restaurant.total_time_spent / restaurant.visitors_count
    restaurant.update_attribute(:average_time_spent, avg)
  end     
end 

Примечание. Код не был проверен, поэтому он может содержать незначительные ошибки.

Теперь вы можете сортировать по этим столбцам, создавать именованные области, которые включают их, или использовать их в ваших методах.

1 голос
/ 27 ноября 2009

Установите значения, используя self. (Fieldname), а затем сохраните их (после выполнения поиска или сборки).

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