Finishing a Basic Student Interface and Building a Basic Output Interface

Getting up at 2:30 AM (completely accidentally) has its advantages. I listened to an episode of mysterious universe, got the below programming work done, read the paper, drank a pot of coffee, chatted with friends, ate two meals, and lumbered again… all before noon.


A laptop at work

Notes-on-Rails-wise, two main accomplishments today: finished up the student interface and also created a basic output interface. Neither of these are pretty, and there’s no CSS to be seen yet. However, this is solid progress, not only in the project, but as importantly in my understanding of the framework.


Behold!

The day’s work was built around a simple motto: “A good programmer is lazy, and rubyonrails makes it easy to be a good programmer.”


Instead of figuring out by reading documentation how to update students and notes into database, instead create static scaffolds for the students and notes model. Then we will integrate them into our student interface

First, edit the condition model with the line

has_many :students

Then generate two more static scaffolds, as we did on the second day

ruby script/generate scaffold student manage_students
ruby script/generate scaffold note manage_notes

That done, we will finish up the static scaffolds before we use code from here

Set the _form.rhtml partial in manage_student’s form partial to

<%= error_messages_for ‘student’ %>
<!–[form:notes_field]–>
<p><label for="student_name">Name</label><br/>
<%= text_field ‘student’, ‘name’ %></p>
<p><label for="student_condition_id">Condition ID</label><br/>
<%=
@conditions = Condition.find(:all, :order => "name" ).map {|u| [u.name, u.id] }
select :student, :condition_id, @conditions
%></p>
<!–[eoform:notes_field]–>

This works, but the controller doesn’t show anymore… let’s fix that

Change show in manage_student’s controller to

def show
@student = Student.find(params[:id])

@student_name = @student[‘name’]
condition = Condition.find(@student[‘condition_id’])
@student_condition = condition.name
end

and the _show.rhtml partial to

<p>
<br /><b>Name</b>: <%= @student_name %>
<br /><b>Condition</b>: <%= @student_condition %>
</p>
<%= link_to ‘Edit’, :action => ‘edit’, :id => @notes_record %> |
<%= link_to ‘Back’, :action => ‘list’ %>

For a reason I’m not clear about (how’s that for confidence!) the show function does not work, as it onyl shows the id but not other fields. But I have an idea.

We generated the scaffold before we generated the controller. Does this matter?

Who knows. But let’s go through the steps properly. First, let’s destroy our static scaffolds

Warning: This is a mistake, but unlike most mistakes, will wrongly destroy the student.rb and note.rb. I use jEdit to recover lost files!

ruby script/generate scaffold student manage_students
ruby script/generate scaffold note manage_notes

Now generate the controllers

ruby script/generate controller manage_students
ruby script/generate controller manage_notes

Now put in the line scaffold :student or scaffold :note, respectively, in these files.

Eep! The destroy accidentally destroyed student.rb and note.rb. No problem. I’ll open jEdit which I use for text editing and which autosaves everything, and recover it. Now it works fine, and we can even see the data we inputed.

That done, regenerate the stattic scaffold the same way we did before

ruby script/generate scaffold student manage_students
ruby script/generate scaffold note manage_notes

Woot! That works fine. So let’s go back now and rejigger manage_student’s _form like it was before.

And it works!

Now for the experiment… trying to put this code in the students controller (the one being worked on for the past few days).

First take that form we created and move it over to the _select_condition.rhtml partial in app/views/students

<%= form_remote_tag(:update => "notetaking_display",
:url => { :action => :select_notes_from_condition },
:position => "top" ) %>
Your Condition:
<%= select :student, :condition_id, @conditions %>
<p><label for="student_name">Your Name</label><br/>
<%= text_field ‘student’, ‘name’ %></p>
<%= submit_tag "Select Condition" %>
<%= end_form_tag %>
<div id="notetaking_display"></div>

And wahoo! While looking at the previously generated code (specifically app/controllers/manage_students_controller.rb#create) I see the .save() function that saves an object to the database. So change student_controller.rb#select_notes_from_condition to

def select_notes_from_condition
@student = Student.new(params[:student])
@student.save

@notes_fields = NotesField.find_by_condition(@student.condition_id)
@notes_records = NotesRecord.find_by_condition(@student.condition_id)

render :partial => “notes_view”
end

and the student is added to the database just fine.

Note to self: the next goal is to make sure that student is in the session and also save the notes. Using the universal .save method, we’ll do essentially the same thing here, too:

In app/controllers/students_controller.rb:

def select_notes_from_condition
@student = Student.new(params[:student])
@student.save
session[:student] = @student;

@notes_fields = NotesField.find_by_condition(@student.condition_id)
@notes_records = NotesRecord.find_by_condition(@student.condition_id)

render :partial => “notes_view”
end

def save_student_notes
@student = session[:student]

@notes_fields = NotesField.find_by_condition(@student.condition_id)
@notes_records = NotesRecord.find_by_condition(@student.condition_id)

for note_field in @notes_fields
for note_record in @notes_records
@note = Note.new(
:notetext => params[note_field.name][note_record.name],
:student_id => @student.id
)
@note.save
end
end
render_text “Notes Updated”
end

And let’s clean up the _notes_view partial a little too:

<h1>Note-Taking Matrix</h1>
<%= form_remote_tag(:update => "notetaking_display",
:url => { :action => :save_student_notes },
:position => "top" ) %>
<table border="1">
<tr>
<td>  </td>
<% for notes_record in @notes_records %>
<td><%= h(notes_record.name) %></td>
<% end %>
</tr>
<% for notes_field in @notes_fields %>
<tr>
<td><%= notes_field.name %></td>
<% for notes_record in @notes_records %>
<td><%= text_area h(notes_field.name), h(notes_record.name), :cols => 20, :rows => 8 %></td>
<% end %>
</tr>
<% end %>
</table>
<%= submit_tag "Save Notes" %>
<%= end_form_tag %>
<div id="notetaking_display" name="notetaking_display"></div>

The last big thing to do is to create the code that will display the student records we already have. However, I accidentally deleted student.rb and note.rb. Thus I need to re-create them as they should be. student.rb should now be:

class Student < ActiveRecord::Base
belongs_to :condition

has_many :notes

def self.find_students(selected_condition_id)
@students = Student.find(:all, :conditions => [‘condition_id = ?’ , selected_condition_id ] )
end
end

and note.rb is:

class Note < ActiveRecord::Base
belongs_to :student

def self.find_notes(selected_student_id)
@students = Note.find(:all, :conditions => [‘student_id = ?’ , selected_student_id ] )
end
end

To do this, create a new controller called output

ruby script/generate controller output

First, some prepwork

in condition.rb, add the function

def self.find_conditions_array(selected_experiment_id)
@conditions = Condition.find(:all, :conditions => [‘experiment_id = ?’ , selected_experiment_id ] )
end

In the new app/controllers/output_controller.rb file, create a new function called index

def index
@experiments = Experiment.find(:all)
for experiment in @experiments
@conditions = Condition.find_conditions(experiment.id)
for condition in @conditions
@students = Student.find_students(condition)
for student in @students
@notes = Note.find_notes(condition)
end
end
end
end

Now for the app/view/index.rhtml

<html><head><title>Output for Notes on Rails</title></head>
<body>
<h1>Output Listing</h2>
<% for experiment in @experiments %>
<h2>Experiment <i><%= experiment.name %></i></h2>
<% for condition in Condition.find_conditions_array(experiment.id) %>
<h3>Condition <i><%= condition.name %></i></h3>
<% for student in Student.find_students(condition.id) %>
<h4>Student <i><%= student.name %></i></h3>
<% for note in Note.find_notes(student.id) %>
<p><%= note.notetext %></p>
<% end %>
<% end %>
<% end %>
<% end%>
</body>
</html>

Roh roh! A quick test reveals that the notes table does not save record or field information, making it impossible to reconstruct what went where. This can be solved by retrofitting some code and doing a new migration, but it’s still frustrating.

Go back in the notes to when the basic administration interface was finished to alter the notes table

ruby script/generate migration alter_notes

This generates 014_alter_notes.rb, which should read (after editing)

class AlterNotes < ActiveRecord::Migration
def self.up
add_column :notes, :notes_field_id, :integer
add_column :notes, :notes_record_id, :integer
end

def self.down
remove_column :notes, :notes_field_id
remove_column :notes, notes_record_id
end
end

Then rake db:migrate

In notes_field.rb and notes_record.rb, add the function (from Agile Web Development with Rails, 28)

def self.name_from_id(selected_id)
@entries = find(selected_id);
@entries.name
end

Then, index.rhtml in output’s view:

<html><head><title>Output for Notes on Rails</title></head>
<body>
<h1>Output Listing</h2>
<% for experiment in @experiments %>
<h2>Experiment <i><%= experiment.name %></i></h2>
<% for condition in Condition.find_conditions_array(experiment.id) %>
<h3>Condition <i><%= condition.name %></i></h3>
<% for student in Student.find_students(condition.id) %>
<h4>Student <i><%= student.name %></i></h3>
<% for note in Note.find_notes(student.id) %>
<p><i>On the <%= NotesField.name_from_id(note.notes_field_id) %> of <%= NotesRecord.name_from_id(note.notes_record_id) %></i>: <%= note.notetext %></p>
<% end %>
<% end %>
<% end %>
<% end%>
</body>
</html>

And your done!

Tomorrow: making it pretty.