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.
精彩评论