Forum: Ruby on Rails Newbie Join-Model Misery

Db9cd94b67668e3d0cc745ef293759ea?d=identicon&s=25 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/
81b61875e41eaa58887543635d556fca?d=identicon&s=25 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
Db9cd94b67668e3d0cc745ef293759ea?d=identicon&s=25 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.
Db9cd94b67668e3d0cc745ef293759ea?d=identicon&s=25 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?
D188e591eac11021329b8821a5f954c7?d=identicon&s=25 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.
Db9cd94b67668e3d0cc745ef293759ea?d=identicon&s=25 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?
81b61875e41eaa58887543635d556fca?d=identicon&s=25 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
Db9cd94b67668e3d0cc745ef293759ea?d=identicon&s=25 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...
81b61875e41eaa58887543635d556fca?d=identicon&s=25 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
Db9cd94b67668e3d0cc745ef293759ea?d=identicon&s=25 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).??
81b61875e41eaa58887543635d556fca?d=identicon&s=25 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
D188e591eac11021329b8821a5f954c7?d=identicon&s=25 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.
Db9cd94b67668e3d0cc745ef293759ea?d=identicon&s=25 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

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.