I have the following code in a view partial to show a students average score for each evaluation period:
<tr class="<%= cycle("odd", "even", name: "students")%>">
<td>
<%= link_to "#{student.name}",
student_path({student_group_id: student.student_group_id, id: student.id})%>
</td>
<% student.eval_count.times do |i| %>
<td class="center"><%= student.avg_for_eval(i) %></td>
<% end %>
<td class="center"><%= student.avg unless student.avg.nan? %></td>
</tr>
I'm using this method in student.rb to generate the average score, and at first I couldn't figure out why I couldn't get it to generate empty rows when there was no data.
def evals
evals = self.evaluations.order("eval_number").group_by(&:eval_number)
end
def eval_number_set(index)
numbers = Evaluation.where('student_id = ?', self.id).uniq.pluck(:eval_number)
numbers[index]
end
def avg_for_eval(i)
scores = []
evals = self.evals.select { |k, v| k == self.eval_number_set(i) }.values.first
for eval in evals
scores << eval.score
end
evals.empty? #(scores.sum.to_f / scores.size).round(2)
end
I changed the last line of the method to evals.empty? as shown above and got this in the browser:

Then I realized that because of select any evaluations the student was not present for (in the case of a new student) was not part of the dataset I was using to generate the code.
The problem, as you can see in the picture, is that the two students who only have data for the last evaluation don't have their data in the correct columns - everything has shifted to the left because no <td> is being produced by the view code.
So the question, then, is how can I rewrite the method code so that I get the same output, but if scores.empty? the method inserts "" or "no data" or some other placeholder so that a <td> is printed in the view?
I understand what's going on a bit better now. This method:
def eval_number_set(index)
numbers = Evaluation.where('student_id = ?', self.id).uniq.pluck(:eval_number)
numbers[index]
end
is returning the evaluation numbers for each student - so when those numbers are used in the next method
def avg_for_eval(i)
scores = []
evals = self.evals.select { |k, v| k == self.eval_number_set(i) }.values.first
for eval in evals
scores << eval.score
end
evals.empty? #(scores.sum.to_f / scores.size).round(2)
end
it can only pull the evaluations that a student has been present for. back to the drawing board for now...
I've changed the class methods as follows:
#returns all 'eval_number's for a given group of students
#as the first student will have been present for all evaluations
def eval_number_set(index)
numbers = self.student_group.students.first.evals.keys
# numbers = Evaluation.where('student_id = ?', self.id).uniq.pluck(:eval_number)
numbers[index]
end
#attempts to match the first present 'eval_number' for a given student against
#the first number in the set of all 'eval_number's and react accordingly
def avg_for_eval(i)
scores = []
if self.evals.keys[i] == self.eval_number_set(i)
"match"
else
"no_match"
end
end
this matched all the students who were present for every evaluation, but did not match any evaluations for the students who missed some. I changed the code to the following
def avg_for_eval(i)
scores = []
if self.evals.keys[i] == self.eval_number_set(i)
"#{self.evals.keys[i]} vs #{self.eval_number_set(i)}"
else
"#{self.evals.keys[i]} vs #{self.eval_number_set(i)}"
end
end
and it returned the following in the browser:

so I tried to add a counter in the if statement such that if the statement matched, it would increment the key being tried, otherwise it would stay on the same key:
def avg_for_eval(i)
scores = []
key_match = 0
if self.evals.keys[key_match] == self.eval_number_set(i)
"#{self.evals.keys[i]} vs #{self.eval_number_set(i)}"
key_match += 1
else
"#{self.evals.keys[i]} vs #{self.eval_number_set(i)}"
end
end
which produces this:

and makes sense - the count incrementing doesn't take any real effect because of the way it's being called. but while i think that I'm now closer to the effect I want, i'm not sure how to make it happen!
closer still...I've changed things so that the keys are read in reverse, so all of the data is being read - but I'd still like the data to be printed with the most recent on the right (see picture below). the model method code now looks like this:
def eval_number_set(index)
numbers = self.student_group.students.first.evals.keys.reverse
# numbers = Evaluation.where('student_id = ?', self.id).uniq.pluck(:eval_number)
numbers[index]
end
def avg_for_eval(i)
scores = []
eval_number = self.eval_number_set(i)
if self.evals.keys.reverse[i] == eval_number
for eval in self.evals.values[i]
scores << eval.score if self.evals.values[i]
end
scores
else
"no data"
end
end
which returns the following, which I've annotated so you can understand what it is that I'm looking for a bit more clearly:

Calling student.evals returns the following (for student with id 32):
{29=>[
#<Evaluation id: 1949, score: 3, created_at: "2013-08-28 09:44:32", updated_at: "2013-08-28 09:44:32", student_id: 32, goal_id: 63, eval_number: 29>,
#<Evaluation id: 1950, score: 4, created_at: "2013-08-28 09:44:32", updated_at: "2013-08-28 09:44:32", student_id: 32, goal_id: 64, eval_number: 29>,
#<Evaluation id: 1951, score: 5, created_at: "2013-08-28 09:44:32", updated_at: "2013-08-28 09:44:32", student_id: 32, goal_id: 65, eval_number: 29>],
30=>[
#<Evaluation id: 1957, score: 3, created_at: "2013-08-28 09:44:43", updated_at: "2013-08-28 09:44:43", student_id: 32, goal_id: 65, eval_number: 30>,
#<Evaluation id: 1956, score: 2, created_at: "2013-08-28 09:44:43", updated_at: "2013-08-28 09:44:43", student_id: 32, goal_id: 64, eval_number: 30>,
#<Evaluation id: 1955, score: 1, created_at: "2013-08-28 09:44:43", updated_at: "2013-08-28 09:44:43", student_id: 32, goal_id: 63, eval_number: 30>],
31=>[
#<Evaluation id: 1968, score: 2, created_at: "2013-08-28 11:26:56", updated_at: "2013-08-28 11:26:56", student_id: 32, goal_id: 70, eval_number: 31>,
#<Evaluation id: 1967, score: 2, created_at: "2013-08-28 11:26:56", updated_at: "2013-08-28 11:26:56", student_id: 32, goal_id: 69, eval_number: 31>,
#<Evaluation id: 1966, score: 1, created_at: "2013-08-28 11:26:56", updated_at: "2013-08-28 11:26:56", student_id: 32, goal_id: 68, eval_number: 31>,
#<Evaluation id: 1965, score: 1, created_at: "2013-08-28 11:26:56", updated_at: "2013-08-28 11:26:56", student_id: 32, goal_id: 67, eval_number: 31>,
#<Evaluation id: 1964, score: 1, created_at: "2013-08-28 11:26:56", updated_at: "2013-08-28 11:26:56", student_id: 32, goal_id: 66, eval_number: 31>,
#<Evaluation id: 1963, score: 3, created_at: "2013-08-28 11:26:56", updated_at: "2013-08-28 11:26:56", student_id: 32, goal_id: 65, eval_number: 31>,
#<Evaluation id: 1962, score: 3, created_at: "2013-08-28 11:26:56", updated_at: "2013-08-28 11:26:56", student_id: 32, goal_id: 64, eval_number: 31>,
#<Evaluation id: 1961, score: 3, created_at: "2013-08-28 11:26:56", updated_at: "2013-08-28 11:26:56", student_id: 32, goal_id: 63, eval_number: 31>],
32=>[
#<Evaluation id: 1983, score: 3, created_at: "2013-08-29 19:31:48", updated_at: "2013-08-29 19:31:48", student_id: 32, goal_id: 69, eval_number: 32>,
#<Evaluation id: 1982, score: 2, created_at: "2013-08-29 19:31:48", updated_at: "2013-08-29 19:31:48", student_id: 32, goal_id: 68, eval_number: 32>,
#<Evaluation id: 1981, score: 3, created_at: "2013-08-29 19:31:48", updated_at: "2013-08-29 19:31:48", student_id: 32, goal_id: 67, eval_number: 32>,
#<Evaluation id: 1980, score: 4, created_at: "2013-08-29 19:31:48", updated_at: "2013-08-29 19:31:48", student_id: 32, goal_id: 66, eval_number: 32>,
#<Evaluation id: 1979, score: 4, created_at: "2013-08-29 19:31:48", updated_at: "2013-08-29 19:31:48", student_id: 32, goal_id: 65, eval_number: 32>,
#<Evaluation id: 1978, score: 3, created_at: "2013-08-29 19:31:48", updated_at: "2013-08-29 19:31:48", student_id: 32, goal_id: 64, eval_number: 32>,
#<Evaluation id: 1977, score: 3, created_at: "2013-08-29 19:31:48", updated_at: "2013-08-29 19:31:48", student_id: 32, goal_id: 63, eval_number: 32>,
#<Evaluation id: 1984, score: 3, created_at: "2013-08-29 19:31:48", updated_at: "2013-08-29 19:31:48", student_id: 32, goal_id: 70, eval_number: 32>]
}
UPDATE
You should change eval_count method or use constant value to make 4 cells. For example,
4.times do |i|
<td class="center"><%= student.avg_for_eval(i) %></td>
end
END OF UPDATE
You can try this approach:
def avg_for_eval(i)
scores = Array.new(4)
evals = self.evals.select { |k, v| k == self.eval_number_set(i) }.values.first
evals.each_with_index do |eval, i|
scores[i] = eval.score
end
(scores.sum.to_f / scores.size).round(2)
end
Pay attention to scores array initialization. In this method [nil,nil,nil,nil] is created initially. Scores would be equal to [first_score, second_score, nil, nil] for students with only two marks.
It leads to possible mistake: result of method would be
(first_score + second_score) / 4
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With