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/

On 16 Sep 2008, at 19:34, Ad Richards wrote:


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

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

<% @students.each do |student| %>

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

Fred

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

<% @students.each do |student| %>

<%= h student.name %> <% @assignments.each do |assignment| %> <% if g = student.gradation_hash[assignment.id] %> <%= h g.grade %> <% end %> <% end %> <% 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?

On Sep 24, 11:38 pm, Ad Richards [email protected]
wrote:

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

On 25 Sep 2008, at 11:17, Ad Richards wrote:

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.

field
is created for each and every existing student…

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

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 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…

On 25 Sep 2008, at 13:53, Ad Richards wrote:

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

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).??

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!