Say I have a big composite formula to compute the quality of a widget
quality = 0.4(factory_quality) + 0.3(1/days_since_manufacture) + 0.3(materials_quality)
Each of these three factors are functions themselves, which require joins to the factories table, and maybe to a bill of materials join table with materials, where the associated records are averaged or something or other.
Architecturally, how would you manage this in a Rails project? What's the best practice to a) produce the correct query and b) manage the code in Rails?
Currently for the sql, I'm using a subquery in the FROM statement:
SELECT *,
(0.4 * factory_quality + 0.3 * (1/days_since_manufacture) + 0.3 * materials_quality) AS quality
FROM (
SELECT *,
((factories.last_inspection_score + factories.variance_score)/2) AS factory_quality,
(now() - widgets.created_at) AS days_since_manufacture,
SUM(materials.quality_score) AS materials_quality
FROM widgets,
JOIN factories ON widget.factory_id = factories.id
JOIN bills_of_materials ON widget.id = bills_of_materials.widget_id
JOIN materials ON bills_of_materials.material_id = materials.id
GROUP BY widgets.id
) AS widgets;
In rails, I have this implemented mostly using ActiveRecord:
class Widget < ActiveRecord::Base
belongs_to :factory
has_many :bills_of_material
has_many :materials, through :bills_of_material
class << self
def with_quality
select([
"widgets.*",
"(0.4 * factory_quality + 0.3 * (1/days_since_manufacture) + 0.3 * materials_quality) AS quality"
].join(",")
.from("(#{subquery}) AS widgets")
end
private
def subquery
select([
"widgets.*",
"((factories.last_inspection_score + factories.variance_score)/2) AS factory_quality",
"(now() - widgets.created_at) AS days_since_manufacture",
"SUM(materials.quality_score) AS materials_quality"
].join(","))
.joins(:factory,:materials)
.group("widgets.id")
.to_sql
end
end
end
That said, I feel like I could make this a custom function in Postgres, move all this sql in to that function, migrate it, and clean up the rails to look like
def with_scores
select("*,quality_score_func(id) AS quality")
end
or something to that effect, but I feel like it will be a pain in the ass to manage what will be an evolving formula through database migrations, not to mention somewhat of a task to find out what the current form of the formula is (and also difficult to test).
How have other people solved this problem? Any tips or suggestions?