Newbie Join-Model Misery

Being a rails newbie, I appear to have hit a large wall.

I am trying to create an online gradebook using rails, where I and other teachers at my school can record students' grades for all their homework assignments.

students assessment1 assessment2 assessment3 -------- ----------- ----------- ----------- Bart Simpson 12 15 9 Lisa Simpson 7 6 14 Charlie Brown 12 14 17

I would like teachers to be able to dynamically add columns for new assessments as they occur, and understand that this is not such a simple task.

It is obvious that I need a join model in which to store the grades as there will be many students and many assignments, and each grade will be linked to both a specific assignment and specific student.

So I have written my models as follows:

   1. class Student    2. has_many :gradations    3. has_many :assignments, :through => :gradations    4. end    5. class Gradation    6. belongs_to :student    7. belongs_to :assignment    8. end    9. class Assignment   10. has_many :gradations   11. has_many :students, :through => :gradations   12. end

So far so good right? Well I have since spent an infuriating amount of time fiddling about querying the models, and I can't for the life of me figure out how to pull out the data as in the table above. i.e. listing all the students' grades for each assignment. I wonder if there is somebody out there who can put me out of my misery. Or maybe I am barking up the completely wrong tree? Is there an easier way of doing this that I have totally overlooked?.. Please, somebody help me. I don't want to lose any more hair over this.

I have posted a more detailed picture of my dilemma at http://railspool.blogspot.com/

Being a rails newbie, I appear to have hit a large wall.

I am trying to create an online gradebook using rails, where I and
other teachers at my school can record students' grades for all their
homework assignments.

students assessment1 assessment2 assessment3 -------- ----------- ----------- ----------- Bart Simpson 12 15 9 Lisa Simpson 7 6 14 Charlie Brown 12 14 17

I would like teachers to be able to dynamically add columns for new assessments as they occur, and understand that this is not such a
simple task.

This doesn't have to be very complicated. If we forget for a second
about the layout of the table, this would output students and their
grades

<% @students.each do |student|%>    <%= h student.name %>:    <% student.gradations.each do |gradation| %>       <%= h gradation.assignment.name %>: <%= gradation.grade %>    <% end %> <% end %>

Your layout is a bit more complicated because you need to iterate over
the gradations in the same assignment order for everyone, cope with
people not having done an assignment yet etc.

I might do something like this:

in your model:

def gradation_hash    @gradation_hash ||= gradations.index_by {|g| g.assignment_id } end

in your controller

@assignments = Assignment.find :all @students = Student.find :all

in your view

<tr><td></td>    <% @assignments.each do |assignment| %>      <td>        <%= h assignment.name %>      </td>    <% end %> </tr>

<% @students.each do |student| %> <tr>    <td> <%= h student.name %> </td>    <% @assignments.each do |assignment| %>      <td>        <% if g = student.gradation_hash[assignment.id] %>          <%= h g.grade %>        <% end %>      </td>    <% end %> </tr> <% end >

Fred

<tr><td></td>    <% @assignments.each do |assignment| %>      <td>        <%= h assignment.name %>      </td>    <% end %> </tr>

<% @students.each do |student| %> <tr>    <td> <%= h student.name %> </td>    <% @assignments.each do |assignment| %>      <td>        <% if g = student.gradation_hash[assignment.id] %>          <%= h g.grade %>        <% end %>      </td>    <% end %> </tr> <% end %>

Fred, your code has done the trick. It works perfect! Thanks a bundle!

All I need to do now is figure out how to get edit_in_place working with the grade fields in the view and I am sorted.

Everything is now working and I am able to edit the grades inline using super inplace controls from http://os.flvorful.com/super_in_place_controls.

However, I have realised that when I create a new assignment a corresponding field for the grade is not automatically created in the join model.

Is something wrong with my model associations?

Ad Richards wrote:

Everything is now working and I am able to edit the grades inline using super inplace controls from http://os.flvorful.com/super_in_place_controls.

However, I have realised that when I create a new assignment a corresponding field for the grade is not automatically created in the join model.

Is something wrong with my model associations?

How does the new assignment know which students it applies to? I assume that there is something else (like a Course) that the assignment belongs to, to which the students are related as well...

When you create a new assignment for a course, that action should be the one to walk the list of students in the course and create the gradations to link its students to the new assignment.

How does the new assignment know which students it applies to? I assume that there is something else (like a Course) that the assignment belongs to, to which the students are related as well...

When you create a new assignment for a course, that action should be the one to walk the list of students in the course and create the gradations to link its students to the new assignment.

Okay thanks,

I have tried the following in my gradations controller;

def create        @assignment = Assignment.new(params[:assignment])        @gradation = Gradation.new(params[:gradation])

But it doesn't seem to work. I know I need to link the two together but am not sure how. Any ideas?

Okay thanks,

I have tried the following in my gradations controller;

def create @assignment = Assignment.new(params[:assignment]) @gradation = Gradation.new(params[:gradation])

But it doesn't seem to work. I know I need to link the two together but am not sure how.

How on would you expect that to work ? Nothing's going to guess that two things are related because they are performed in quick succession.

Either you explicitly create the relationship (ie @gradation.assignment = ...) or you do it implicitly (@gradation.build_assignment(...)) It might make more sense to do things the other way round in your case, but that's for you to determine (although from your description previously I would have expected you to be creating assignments over in one corner of the app, and the creating gradations in a separate part, as and when students complete assignments - not create both at the same time)

Fred

Thanks fred,

The problem with creating gradations separately when students complete assignments, is that I am then left with no fields in my view in which to enter grades. My plan was to have it so that when an assignment was created, a corresponding grade field would be created for each student containing some kind of default value that could then be edited using the inplace edit control.

I think you're solving this the wrong way (for example you're still
have this problem if you add a new student). I'd worry about working
around the editing stuff rather than doing this sort of mucking around.

I have got as far as making it so that a grade field is automatically created for each new assignment:

def create    @assignment = Assignment.new(params[:assignment])    @gradation = @assignment.gradations.build(params[:gradation])

But I don't know how to pass the student id in so that a new grade
field is created for each and every existing student...

You'd have to do a Student.find :all and loop over all those things

Oh that's a good point, I didn't consider that.

What do you mean by working around the editing stuff? Do you mean like including an "if, else" somewhere in the view. i.e: if a grade already exists for a student and assignment then show it, otherwise create a new one (with a default value).??

Oh that's a good point, I didn't consider that.

What do you mean by working around the editing stuff? Do you mean like including an "if, else" somewhere in the view. i.e: if a grade already exists for a student and assignment then show it, otherwise create a new one (with a default value).??

Yeah something like that at (or rather show the appropriate ui for
creating a new one)

Fred

Ad Richards wrote:

I have got as far as making it so that a grade field is automatically created for each new assignment:

def create     @assignment = Assignment.new(params[:assignment])     @gradation = @assignment.gradations.build(params[:gradation])

But I don't know how to pass the student id in so that a new grade field is created for each and every existing student...

I would think that you could do something like (pardon the sparsity, but I cobbled this together over lunch):

class Course < ActiveRecord::Base   has_many :enrollments   has_many :students, :through => :enrollments   has_many :assignments   has_many :gradations, :through => :assignments   validates_presence_of :name end

class Student < ActiveRecord::Base   has_many :enrollments   has_many :courses, :through => :enrollments   # not positive this works through this many levels, but conceptually   has_many :assignments, :through => :courses   has_many :gradations, :through => :assignments   validates_presence_of :first_name, :list_name end

class Enrollment < ActiveRecord::Base   belongs_to :student   belongs_to :course   validates_presence_of :student_id, :course_id end

class Gradation < ActiveRecord::Base   belongs_to :assignment   belongs_to :student   validates_presence_of :assignment_id, :student_id end

class Assignment   belongs_to :course   has_many :gradations   validates_presence_of :course_id

  after_create :build_gradations

  def build_gradations     self.course.students.each do |student|       Gradation.create(:assignment_id => self.id, :student_id => student.id)     end   end end

When I create an assignment for a course, the gradations are built for each student currently associated with the course.

A grading process could start from choose a course, choose the assignment, then present the list of students and their grades (I'd allow for grade edits because, well, it happens).

Or something like that... late for a meeting at the moment, so gotta run.

Perfect! Works like a charm!

A big thanks to all!

If anyone is interested the finished app can be downloaded from:

http://github.com/aglasspool/rubygrade/tree/master

or there is an online demo at http://rubygrade.heroku.com

Thanks again!