开发者

Rails Model With Aggregrate Data (not backed by a table)

开发者 https://www.devze.com 2022-12-12 21:58 出处:网络
Id like to create a model in rails that does not correlate to a table in the database. Instead the model should dynamically pull aggregrate data about other models.

Id like to create a model in rails that does not correlate to a table in the database. Instead the model should dynamically pull aggregrate data about other models.

Example:

I have a Restaurant model stored in the restaurants table in the DB. Id like to have a RestaurantStats model where i can run a RestaurantStats.find_total_visitors, or RestaurantStats.find_time_spent etc... on it and it returns a set of RestaurantStats models each with:

[:restaurant_id, :stat_value]

Obviously in each find... method that stat_value will mean something different (for find_time_spent it will be s开发者_Go百科econds spent, for find_total_visitors it will be number of visitors). The idea will be to return the top 100 restaurants by time spent, or total visitors.

So far im creating a model (not inherited from ActiveRecord)

class RestaurantStats 
   attr_reader :restaurant_id
   attr_reader :stat_value

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

The question is how do define the find_total_visitors, find_time_spent functions in a rails y way so that it will populate the restaurant_id, stat_value fields?


Are you sure you don't just want these to be methods on Restaurant?

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


Set the values using self.(fieldname) and then save those value (after running a find or build).


Given that you want to sort your searches on statistics, adding counter caches to the Restaurant model looks like your best option.

In the case of total visitors this is simple. In the case of total time_spent, it will be a little more complex, but still not unmanageable. If you're looking for an average then things get a little more complicated.

Here's the code required to add counter caches to your restaurant model. Notice that most of new model code is in the visitor's model.

Add new columns to Restaurant via migration:

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

Update visitor model to update counter caches

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. Code hasn't been tested, so it may contain minor errors.

Now you can sort by these columns, create named scopes that include them or use them in your methods.

0

精彩评论

暂无评论...
验证码 换一张
取 消