Skip to content

Instantly share code, notes, and snippets.

@vierarb
Last active August 29, 2015 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vierarb/8a69f04b2fe009a78a0d to your computer and use it in GitHub Desktop.
Save vierarb/8a69f04b2fe009a78a0d to your computer and use it in GitHub Desktop.
Metaprogramming the metrics
# Usage:
# coach = Coach.first
# coach.sessions_count(:started_at)
# coach.coachees_count(:created_at)
# coach.sessions_sum(:price, :started_at)
class Coach < ActiveRecord::Base
include Metric
metrics_for :sessions, :coachees
end
module Metric
extend ActiveSupport::Concern
module ClassMethods
def metrics_for(*tables)
tables.each do |table|
define_method("#{table}_count") do |column, options = {}|
send(:count, table, column, options)
end
define_method("#{table}_sum") do |field, column, options = {}|
send(:sum, table, column, field, options)
end
end
end
end
extend ClassMethods
private
def count(table, column, options)
aggregate = 'count(id)'
find(table, column, aggregate, options)
end
def sum(table, field, column, options)
aggregate = "sum(#{field})"
find(table, column, aggregate, options)
end
def find(table, column, aggregate, options = {})
period, quantity = format_period(options[:period])
options = options.merge(id: id,
period: period,
quantity: quantity,
where: "#{self.class.name.downcase}_id")
query = sql(table, column, aggregate, options)
ActiveRecord::Base.connection.execute(query)
end
def format_period(period)
if period =~ /\A(\d+\s(week|month|year))\z/
[$2, $1]
else
['month', '1 month']
end
end
def sql(table, column, aggregate, options)
%Q{
WITH results AS
(
SELECT
#{aggregate} AS amount,
date_trunc('#{options[:period]}', #{column}) AS date
FROM #{table}
WHERE #{options[:where]} = #{options[:id]}
GROUP BY date
)
SELECT date, COALESCE(sum(amount), 0) AS amount
FROM
(
SELECT
generate_series(
min(date), max(date), '#{options[:quantity]}'
)
AS date
FROM results
) x
LEFT JOIN results
USING (date)
GROUP BY date
ORDER BY date DESC
}
end
end
@oinak
Copy link

oinak commented May 12, 2014

# maybe I can suggest:
def find(table, column, aggregate, options)
  fail ArgumentError, 'First argument (column) must be present' if column.nil?
  where = "#{self.class.name.downcase}_id"
  query = sql(table, column, aggregate, where)
  model(table).find_by_sql([query, {id: id, period: 'month', quantity: 1}.merge(options))])
end

@Serabe
Copy link

Serabe commented May 12, 2014

There are two lines that contradict each other. Line 8 states that |column = nil| while line 47 states that 'First argument (column) must be present'. Get the conditional lines out and just remove the = nil part from line 8.

@vierarb
Copy link
Author

vierarb commented May 14, 2014

Pues lo voy a sacar a una gema, no?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment