Forum: Ruby on Rails Newbie Join-Model Misery

Posted by Ad Richards (arichards)
on 2008-09-16 20:34
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/
Posted by Frederick Cheung (Guest)
on 2008-09-16 21:46
(Received via mailing list)
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

<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
Posted by Ad Richards (arichards)
on 2008-09-17 23:14
> <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.
Posted by Ad Richards (arichards)
on 2008-09-24 22:05
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?
Posted by Ar Chron (railsdog)
on 2008-09-24 22:33
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.
Posted by Ad Richards (arichards)
on 2008-09-25 00:38
> 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?

Posted by Frederick Cheung (Guest)
on 2008-09-25 10:38
(Received via mailing list)
On Sep 24, 11:38 pm, Ad Richards <rails-mailing-l...@andreas-s.net>
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
Posted by Ad Richards (arichards)
on 2008-09-25 12:17
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...



Posted by Frederick Cheung (Guest)
on 2008-09-25 13:08
(Received via mailing list)
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
Posted by Ad Richards (arichards)
on 2008-09-25 14:53
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).??
Posted by Frederick Cheung (Guest)
on 2008-09-25 15:07
(Received via mailing list)
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
Posted by Ar Chron (railsdog)
on 2008-09-25 19:58
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.
Posted by Ad Richards (arichards)
on 2008-12-29 11:41
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!
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.