Harry
Harry

Reputation: 4835

Rails - Scalable calculation model

I currently have a calculation structure in my rails app that has models metric, operand and operation_type.

Presently, the metric model has many operands, and can perform calculations based on the operation_type (e.g. sum, multiply, etc.), and each operand is defined as being right or left (i.e. so that if the operation is division, the numerator and denominator can be identified).

Presently, an operand is always an attribute of some model, e.g. @customer.sales.selling_price.sum.

In order to make this scalable, in need to allow an operand to be either an attribute of some kind, or the results of a previous operation, i.e. an operand can be a metric.

I have included a diagram of how my models currently look:

enter image description here

Can anyone assist me with the most elegant way of allowing an operand to be an actual operand, or another metric?

Thanks!

EDIT: It seems based on the only answer so far that perhaps polymorphic associations are the way to go on this, but the answer is so brief I have no idea how they could be used in this way - can anyone elaborate?

EDIT 2: OK, I think I'm getting somewhere - essentially i presently have a metric, which has_many operands, and an operand has_many metrics. I need a polymorphic self join, where a metric can also have many metrics - do I need to call this something else, perhaps calculated_metrics, so that the metric model can use itself? That would leave me with a situation where a metric has_many operands, and a metric has many calculated_metrics.

EDIT 3: I have updated my models as below, and would appreciate any critiques regarding whether this is a good way to approach the problem. You will note that I have added a model called calculated_metric that is essentially a holder for other metrics - i.e. a metric can be calculated using two operands, or a combination of an operand and a calculated_metric.

Revised Model

EDIT 4: Bounty added for anyone who can show me a detailed rails-y way of doing this.

EDIT 5: Bounty is still up for grabs; while the answer provided below is detailed, I am looking for the best way to do this, rather than alternative ways to approach the issue (i.e. please let me know the best way to tackle the issue, rather than looking for ways to sidestep it, as I need this functionality). Thanks!

Edit 6: Not getting much attention on this - would an increased bounty help, or is this question just not a runner?

Upvotes: 2

Views: 598

Answers (2)

Alex Blakemore
Alex Blakemore

Reputation: 11921

Consider stepping back and asking yourself if you really need to tear apart your expressions, and store them piece by piece in a relational database.

  • Does that really make sense for your purposes?
  • Are other applications going to query the underlying tables holding expression fragments?
  • Are you going to generate reports that show how often each operator is used?
  • Or are you using a relational database to store expressions just because that's most familiar?

A RDBMS really doesn't seem to be the right tool for this job.

You could store your expressions as strings and then parse them into expression trees as you load the strings from a file or a database. There are plenty of examples showing how to write an expression parser.

So to put it in Ruby and Rails terms. I'd do the following:

  1. Make most of the classes that comprise expression trees into plain old Ruby classes that do not extend ActiveRecord.
  2. Implement to_s in each of those classes, so that if you send to_s to the root of an expression tree, you'll get a well formed string representation of the expression in return.
  3. Implement an ExpressionParser or ExpressionBuilder or ExpressionFactory (use whatever term you like) that can create expression trees from a string.
  4. Then if some ActiveRecord model used to point to a metric that it "owned" exclusively, you can have it point to an expression tree in memory, but have it store the string representation (via to_s) when saving itself.
  5. If several ActiveRecord models pointed to the same (shared) expression in the old design, then you'll have to create a class for SharedExpressions that uses a string for it's persistent representation, and then change The AciveRecords to point to the new SharedExpression.

If you go this route, the ActiveRecord callbacks like before_save might be useful. Good luck.

Upvotes: 1

ghoppe
ghoppe

Reputation: 21794

If I understand your question correctly, I believe you are looking for Polymorphic Associations.

Example:

class Metric < ActiveRecord::Base
   belongs_to :calculable, :polymorphic => true
   has_many :operations, :as => calculable
end

class Operand < ActiveRecord::Base
   belongs_to :calculable, :polymorphic => true
end

Upvotes: 0

Related Questions