带有聚合数据的Rails模型(不支持表)

时间:2009-11-27 17:39:01

标签: ruby-on-rails activerecord model

我想在rails中创建一个与数据库中的表无关的模型。相反,模型应该动态地提取有关其他模型的聚合数据。

示例:

我有一个餐厅模型存储在DB的餐厅表中。我希望有一个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

问题是如何以rails y方式定义find_total_visitors,find_time_spent函数,以便填充restaurant_id,stat_value字段?

3 个答案:

答案 0 :(得分:2)

你确定你不只是希望这些是餐馆的方法吗?

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 :(得分:1)

使用self。(fieldname)设置值,然后保存这些值(在运行查找或构建之后)。

答案 2 :(得分:1)

鉴于您希望对统计信息进行排序,向餐厅模型添加计数器缓存似乎是您的最佳选择。

在总访问者的情况下,这很简单。在总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 

N.B。代码尚未经过测试,因此可能包含轻微错误。

现在,您可以按这些列进行排序,创建包含它们的命名范围或在方法中使用它们。