Add table to store stats versions
We need a way to manually expire the cache for a budget or poll without expiring the cache of every budget or poll. Using the `updated_at` column would be dangerous because most of the times we update a budget or a poll, we don't need to regenerate their stats. We've considered adding a `stats_updated_at` column to each of these tables. However, in that case we would also need to add a similar column in the future to every process type whose stats we want to generate.
This commit is contained in:
@@ -2,6 +2,7 @@ class Budget < ApplicationRecord
|
|||||||
|
|
||||||
include Measurable
|
include Measurable
|
||||||
include Sluggable
|
include Sluggable
|
||||||
|
include StatsVersionable
|
||||||
|
|
||||||
translates :name, touch: true
|
translates :name, touch: true
|
||||||
include Globalizable
|
include Globalizable
|
||||||
|
|||||||
@@ -178,6 +178,6 @@ class Budget::Stats
|
|||||||
stats_cache :voters, :participants, :authors, :balloters, :poll_ballot_voters
|
stats_cache :voters, :participants, :authors, :balloters, :poll_ballot_voters
|
||||||
|
|
||||||
def stats_cache(key, &block)
|
def stats_cache(key, &block)
|
||||||
Rails.cache.fetch("budgets_stats/#{budget.id}/#{phases.join}/#{key}/v10", &block)
|
Rails.cache.fetch("budgets_stats/#{budget.id}/#{phases.join}/#{key}/#{version}", &block)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -88,6 +88,10 @@ module Statisticable
|
|||||||
PercentageCalculator.calculate(fraction, total)
|
PercentageCalculator.calculate(fraction, total)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def version
|
||||||
|
"v#{resource.find_or_create_stats_version.updated_at.to_i}"
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def base_stats_methods
|
def base_stats_methods
|
||||||
|
|||||||
11
app/models/concerns/stats_versionable.rb
Normal file
11
app/models/concerns/stats_versionable.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
module StatsVersionable
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
has_one :stats_version, as: :process
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_or_create_stats_version
|
||||||
|
stats_version || create_stats_version
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -6,6 +6,7 @@ class Poll < ApplicationRecord
|
|||||||
include ActsAsParanoidAliases
|
include ActsAsParanoidAliases
|
||||||
include Notifiable
|
include Notifiable
|
||||||
include Sluggable
|
include Sluggable
|
||||||
|
include StatsVersionable
|
||||||
|
|
||||||
translates :name, touch: true
|
translates :name, touch: true
|
||||||
translates :summary, touch: true
|
translates :summary, touch: true
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class Poll::Stats
|
|||||||
stats_cache :participants, :voters, :recounts
|
stats_cache :participants, :voters, :recounts
|
||||||
|
|
||||||
def stats_cache(key, &block)
|
def stats_cache(key, &block)
|
||||||
Rails.cache.fetch("polls_stats/#{poll.id}/#{key}/v12", &block)
|
Rails.cache.fetch("polls_stats/#{poll.id}/#{key}/#{version}", &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
5
app/models/stats_version.rb
Normal file
5
app/models/stats_version.rb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
class StatsVersion < ApplicationRecord
|
||||||
|
validates :process, presence: true
|
||||||
|
|
||||||
|
belongs_to :process, polymorphic: true
|
||||||
|
end
|
||||||
8
db/migrate/20190408133956_create_stats_versions.rb
Normal file
8
db/migrate/20190408133956_create_stats_versions.rb
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
class CreateStatsVersions < ActiveRecord::Migration[4.2]
|
||||||
|
def change
|
||||||
|
create_table :stats_versions do |t|
|
||||||
|
t.references :process, polymorphic: true, index: true
|
||||||
|
t.timestamps null: false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1339,6 +1339,14 @@ ActiveRecord::Schema.define(version: 20190411090023) do
|
|||||||
t.index ["tsv"], name: "index_spending_proposals_on_tsv", using: :gin
|
t.index ["tsv"], name: "index_spending_proposals_on_tsv", using: :gin
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "stats_versions", force: :cascade do |t|
|
||||||
|
t.string "process_type"
|
||||||
|
t.integer "process_id"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["process_type", "process_id"], name: "index_stats_versions_on_process_type_and_process_id", using: :btree
|
||||||
|
end
|
||||||
|
|
||||||
create_table "taggings", force: :cascade do |t|
|
create_table "taggings", force: :cascade do |t|
|
||||||
t.integer "tag_id"
|
t.integer "tag_id"
|
||||||
t.integer "taggable_id"
|
t.integer "taggable_id"
|
||||||
|
|||||||
@@ -231,4 +231,30 @@ describe Poll::Stats do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#version", :with_frozen_time do
|
||||||
|
context "record with no stats" do
|
||||||
|
it "returns a string based on the current time" do
|
||||||
|
expect(stats.version).to eq "v#{Time.current.to_i}"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't overwrite the timestamp when called multiple times" do
|
||||||
|
time = Time.current
|
||||||
|
|
||||||
|
expect(stats.version).to eq "v#{time.to_i}"
|
||||||
|
|
||||||
|
travel_to 2.seconds.from_now do
|
||||||
|
expect(stats.version).to eq "v#{time.to_i}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "record with stats" do
|
||||||
|
before { poll.create_stats_version(updated_at: 1.day.ago) }
|
||||||
|
|
||||||
|
it "returns the version of the existing stats" do
|
||||||
|
expect(stats.version).to eq "v#{1.day.ago.to_i}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
13
spec/models/stats_version_spec.rb
Normal file
13
spec/models/stats_version_spec.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
describe StatsVersion do
|
||||||
|
describe "validations" do
|
||||||
|
it "is valid with a process" do
|
||||||
|
expect(StatsVersion.new(process: Budget.new)).to be_valid
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is not valid without a process" do
|
||||||
|
expect(StatsVersion.new(process: nil)).not_to be_valid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user