0

I'm modelling a decision matrix, so for each Decision (n of them), there are x Alternatives to choose from and y Goals to meet. Each of x*y pairings of Alternative and Goal has a Score associated.

Other documentation (listed below) has explained simpler modelling challenges, so I'm still lost. How do I model the decision matrix and use Score attributes.

Below are code snippets of each model and a test I tried.

Decisions

class Decision < ActiveRecord::Base
  has_many :alternatives, dependent: :destroy
  has_many :goals, dependent: :destroy
  has_many :scores, dependent: :destroy
  validates :name, presence: true, length: { maximum: 50 }
end

Alternatives

class Alternative < ActiveRecord::Base
  belongs_to :decision
  has_many :scores, dependent: :destroy
  validates :decision_id, presence: true
  validates :name, presence: true, length: { maximum: 50 }
end

Goals

class Goal < ActiveRecord::Base

  belongs_to :decision
  has_many :scores, dependent: :destroy
  validates :decision_id, presence: true
  validates :name, presence: true, length: { maximum: 50 }
  validates :constraint, inclusion: [true, false]
  validates :rank, numericality: {only_integer: true,
                                    greater_than_or_equal_to: 1},
                                    allow_blank: true
  validates :weight, numericality: {greater_than_or_equal_to: 0,
                                    less_than_or_equal_to: 1},
                                    allow_blank: true
end

Score

class Score < ActiveRecord::Base
  belongs_to :decision
  belongs_to :goal
  belongs_to :alternative
  validates :decision_id, presence: true
  validates :goal_id, presence: true
  validates :alternative_id, presence: true
  validates :rating, numericality: {only_integer: true,
                                    greater_than_or_equal_to: -2,
                                    less_than_or_equal_to: 2},
                                    allow_blank: true
end

I tried the following test in decision_test.rb that doesn't work, before realizing how difficult using Score attributes would be.

test "associated decision data should be destroyed" do
    @decision.save
    @alternative_1 = @decision.alternatives.create!(name: "toaster")
    @goal_1 = @decision.goals.create!(name: "fast")
    @score_1 = @decision.scores.build(
                    params[:score].merge(:alternative_id => @alternative_1.id,
                                         :goal_id => @goal_1.id)) ## doesn't work
    assert_difference ['Alternative.count','Goal.count'], -1 do
        @decision.destroy
    end
  end

Schema.rb

ActiveRecord::Schema.define(version: 20150816211809) do

  create_table "alternatives", force: :cascade do |t|
    t.string   "name"
    t.integer  "decision_id"
    t.datetime "created_at",  null: false
    t.datetime "updated_at",  null: false
    t.decimal  "score"
  end

  add_index "alternatives", ["decision_id"], name: "index_alternatives_on_decision_id"

  create_table "decisions", force: :cascade do |t|
    t.string   "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "goals", force: :cascade do |t|
    t.string   "name"
    t.boolean  "constraint",  default: false
    t.integer  "rank"
    t.decimal  "weight"
    t.integer  "decision_id"
    t.datetime "created_at",                  null: false
    t.datetime "updated_at",                  null: false
  end

  add_index "goals", ["decision_id"], name: "index_goals_on_decision_id"

  create_table "scores", force: :cascade do |t|
    t.integer  "rating"
    t.decimal  "value"
    t.integer  "decision_id"
    t.integer  "goal_id"
    t.integer  "alternative_id"
    t.datetime "created_at",     null: false
    t.datetime "updated_at",     null: false
  end

  add_index "scores", ["alternative_id"], name: "index_scores_on_alternative_id"
  add_index "scores", ["decision_id"], name: "index_scores_on_decision_id"
  add_index "scores", ["goal_id"], name: "index_scores_on_goal_id"

end

Resources (the most relevant ones):

Community
  • 1
  • 1
  • do you mind adding to your question the "schema.rb" creation script for the table "scores"? – The Fabio Aug 17 '15 at 04:17
  • It seems like you may be wanting to make use of a polymorphic relationship here, an `accepts_nested_attributes` form of updating, or something involving a `has_many ... :through` type of syntax here. We really need to see your schema to understand what you're getting at as I suspect you're not architecting your data structure in a good way for this. – Kelsey Hannan Aug 17 '15 at 06:42
  • Thanks @TheFabio. I've added it in above. – purplengineer Aug 17 '15 at 17:25
  • @Kelseydh - would a polymorphic relationship maintain the association of a score to both an alternative and goal? – purplengineer Aug 17 '15 at 17:27
  • @purplengineer Where is your schema for Goal, Score, Decision and Alternative? I see none of these tables in your schema. – Kelsey Hannan Aug 18 '15 at 02:58
  • I don't think this is the `schema.rb` file from your question... as the models described on the top of the question are not corresponding to tables in this file. do you mind attaching the correct version of the file? – The Fabio Aug 18 '15 at 11:47
  • @Kelseydh, sorry about that... I've also included the full model file for each of Goal, Score, Decision and Alternative – purplengineer Aug 18 '15 at 15:03
  • @TheFabio, thanks for pointing that out. I replaced the file with the correct one. – purplengineer Aug 18 '15 at 15:04
  • Thank you for the update! (this now shows your model tables are consistent with the relationships of your model classes). The way this is modeled the same `score` row can belong to one or multiple other models (`decisions`,`alternative`,`goals`). is this the intended behavior? – The Fabio Aug 18 '15 at 15:17
  • Is your objective to assign separated scores for each `decisions`,`alternative` and `goals`? – The Fabio Aug 18 '15 at 15:28
  • @TheFabio A score row should always belong to 1 decision, 1 alternative, and 1 goal concurrently. No score should exist without a foreign key to all 3 of these models. – purplengineer Aug 18 '15 at 21:25
  • I forgot to ask... could you please describe the error a little more? What "doesn't work" means? is there an error message? – The Fabio Aug 19 '15 at 00:06
  • @TheFabio It's more than an error as I'm certain there's a better way of modelling it all. Anyhow, the error I got for the test was as follows: NameError: undefined local variable or method `params' – purplengineer Aug 19 '15 at 04:04
  • I updated my answer with a recommendation for your model. The `params` error is happening because your test function is not defining the `params` hash. – The Fabio Aug 19 '15 at 13:42
  • @TheFabio - If it's possible, how can I define the params hash in a test? – purplengineer Aug 22 '15 at 01:07
  • Just add something like `params[:score]={:rank => 2, :weight => 3}` before the line with `@score_1 = ..` – The Fabio Aug 22 '15 at 01:32

2 Answers2

0

This might be a way of how to go about tackling this.

Since each Decision has many Alternatives(x) and has many Goals (y), and these are merely X,Y pairings, those pairings should stored as a join table. Score is this join table, as it essentially represents an AlternativeGoal type join table, only it also stores the score value for the X,Y join pair.

To get Decision to link to score you just manually create the relationship when setting the IDs. My hunch is rails will see the relationship once its been created. Not ideal (syntax might be off) but I think this might work:

Decision:

has_many :alternatives, dependent: :destroy
has_many :goals, dependent: :destroy
has_many :scores, dependent: :destroy, class: Score

Alternative:

has_many :goals, through: :scores

Goal:

has_many :alternatives, through: :scores

Score:

belongs_to :alternative
belongs_to :goal

Then your execution would be something along the lines of:

@decision.save
@alternative_1 = @decision.alternatives.create!(name: "toaster")
@goal_1 = @decision.goals.create!(name: "fast")
@score_1 = Score.new(alternative_id: @alternative_1.id, goal_id: @goal_1.id, score: params[:score], decision_id: @decision.id)
@score_1.save

Then @decision.scores should work.

Kelsey Hannan
  • 2,857
  • 2
  • 30
  • 46
  • 1
    Thanks! I replaced my Decision, Goal, Alternative, and Score models with the schema you suggested, but it failed on all 27 tests I had. Maybe I missed something, but decided to undo the changes. With my original situation, I tried the line with " @score_1 = Score.new(alternative_id: ......., rating: 0 " with my parameter ("rating" attribute) entered directly, and my test passed! – purplengineer Aug 22 '15 at 04:13
0

I am inclined to agree that the scores model is not totally functional the way it is at the moment. It is hard to create its instances through calls to the other related models. I will suggest an improvement for you.

I believe the relationships between decision, alternative and goal are modeled in an appropriate way.

I would suggest you to model score in separated from the rest. The score class should not belong_to the other models. and the other models should not be configured with has_many :scores

The table scores from your schemar.rb could be used in the state you have it.

You could then create a scores function on the other three models which would retrieve the score model for you, something like:

in Decisions model

def scores
  Score.where(decision_id:self.id)
end 

in Alternatives model

def scores
  Score.where(decision_id:self.decision.id,alternative_id:self.id)
end 

in Goals model

def scores
  Score.where(decision_id:self.decision.id,goal_id:self.id)
end 

This way the matrix that holds the scoring system (scores) can be configured in separated.

The Fabio
  • 5,369
  • 1
  • 25
  • 55