Category Archives: UNL / Notes on Rails

Adding support for likert scales

I’m working on another draft of my dissertation proposal presentation (I haven’t given it yet, but I keep finding ways to make it better), and I found some scales I would like to use.

However, I realized that as the questions are pretty similar, I don’t want to have to manually put it nearly identical material each time. I also want to format the displayed questions prettily, to present a proper likert scale…


So I want to create a new feature on manage_questions, called “clone,” which will create a nearly identical copy of an question that varies only in its id. This should be pretty easy, as ruby already has an ActiveRecord::clone function that does this.

First, create a new option entry after the destroy line in app/views/manage_questions/list.rhtml:

<td><%= link_to ‘Clone’, { :action => ‘clone_question’, :id => question }, :method => :post %></td>

(clone_question is used instead of clone because clone is a reserved word)

And then in manage_questions_controller.rb:

def clone_question
time = Time.new
old_question = Question.find(params[:id])
new_question = old_question.clone
new_question["name"] = old_question["name"] + " (cloned copy at " + time.to_s + ")"
result_of_save = new_question.save
if result_of_save
QuestionOption.clone_question_options(old_question.id,new_question.id)
flash[:notice] = ‘Question was successfully cloned with ‘ + old_question.id.to_s + ‘ to ‘ + new_question.id.to_s
else
flash[:notice] = ‘Question not cloned’
end
redirect_to :action => ‘list’
end

In question_option.rb:

def self.clone_question_options(old_question_id,new_question_id)
question_options = self.find(:all,:conditions => [‘question_id = ?’ , old_question_id ] )
if question_options
for old_question_option in question_options
new_question_option = old_question_option.clone
new_question_option.question_id = new_question_id
new_question_option.save
end
end
end

One final thing: when the question options are displayed, they should be presented in the order of their option id. That’s really easy in Question.rb, edit such that:

def self.find_question_options_array(question_id)
@options = QuestionOption.find(:all,:conditions=>[‘question_id = ?’, question_id], :order => ‘option_id ASC’)
end

Finally, after some housekeeping (we don’t need the

Question list <%= @student.current_question_list %> and ordering is <%= @student.current_ordering %> line anymore), we’re done!

Finishing the rough draft

Today (and last night, actually) the rough draft, I guess you could call it the first beta or so, of the system works. From beginning to end, from survey items, notes, and output, it’s all there. A lot of things were changed in the final stretch. Below the fold are some of them.

The notes today are scattered, but so was the accomplishment: a functional system.


app/views/output/index.rhtml

<html><head><title>Output for Notes on Rails</title></head>
<body>
<table border=1>
<tr><td colspan="6"><h1>Output Listing</h2></td></tr>
<% for experiment in @experiments %>
<tr><td colspan="6"> <h2>Experiment <i><%= experiment.name %></i></h2></td></tr>
<% for condition in Condition.find_conditions_array(experiment.id) %>
<tr><td colspan="6"> <h3>Condition <i><%= condition.name %></i></h3></td></tr>
<tr>
<td><b>Survey Response ID</b></td>
<td><i>QuestionList</i></td>
<td>Question</td>
<td><b>Field (i/a)</b></td>
<td><i>Record (i/a)</i></td>
<td>Survey Text</td>
</tr>
<% for student in Student.find_students(condition.id) %>
<tr><td colspan="6"> <h4>Student <i><%= student.name %></i> (<%= student.id %>)</h3></td></tr>
<% for survey_response in SurveyResponse.find(:all, :conditions => [‘student_id = ?’,student.id]) %>
<tr>
<td><%= survey_response.id %></td>
<td><%= QuestionList.find_name_by_id(survey_response.question_list_id) %>
<% question = Question.find(survey_response.question_id) %>
</td>
<td><%= question.name %></td>
<td>
<% if survey_response.field_identifier %>
<br /><%= NotesField.find_name_by_id(survey_response.field_identifier) %>
<% end %>
</td>
<td><%
if question.type_id == QuestionType::NOTES_MATRIX || question.type_id == QuestionType::NOTES_LINEAR
record_name = NotesRecord.find_name_by_id(survey_response.record_identifier)
elsif question.type_id == QuestionType::CHECKBOX || question.type_id = QuestionType::SELECTION || question.type_id = QuestionType::RADIO
#record_name = "Find display text with QuID = " + question.id.to_s + " and OptID = " + survey_response.record_identifier.to_s
record_name = QuestionOption.find_display_text_by_question_id_option_id(question.id,survey_response.record_identifier)
else
record_name = ""
end
%>
<%= record_name %>
</td>
<td><%= survey_response.survey_text %></td>
</tr>
<% end %>
<% end %>

<% end %>
<% end%>
</table>
</body>
</html>

app/views/students/_notes_view.rhtml

<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 :survey_text, notes_field.id.to_s + "_" + notes_record.id.to_s, :cols => 20, :rows => 8 %></td>
<% end %>
</tr>
<% end %>
</table>

the run_experiment function of students_controller.rb:

def run_experiment
@student = Student.find(session[:student])

unless @student.condition_id
@student.condition_id = params[:student][:condition_id]
@student.save
end

## Saving data goes here
@questions = Question.find_by_question_list_id(@student.current_question_list)

if (@questions)
for question in @questions

question = Question.find(question.id)

if (question.type_id == QuestionType::NOTES_MATRIX || question.type_id == QuestionType::NOTES_LINEAR)
@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

@survey_response = SurveyResponse.new(
:student_id => @student.id,
:question_list_id => @student.current_question_list,
:question_id => question.id,
:field_identifier => note_field.id,
:record_identifier => note_record.id,
:survey_text => params[:survey_text][note_field.id.to_s + "_" + note_record.id.to_s]
)
@survey_response.save()
end
end
elsif question.type_id == QuestionType::CHECKBOX
checkboxes = params[‘question_’ + question.id.to_s]
for checkbox in checkboxes
checkbox_option = checkbox[0]
checkbox_value = checkbox[1]

if checkbox_value == "1"

@survey_response = SurveyResponse.new(
:student_id => @student.id,
:question_list_id => @student.current_question_list,
:question_id => question.id,
:record_identifier => checkbox_option,
:survey_text => 1
)
@survey_response.save
end
end

else
if question_text = params[:question]
question_text = params[:question][question.id.to_s]
@survey_response = SurveyResponse.new(
:student_id => @student.id,
:question_list_id => @student.current_question_list,
:question_id => question.id,
:survey_text => question_text
)
@survey_response.save()
flash[:notice] = ‘0D. for ‘ + question_text
end
end

end
end
## stop saving data here

@next_question_list = Student.find_next_question_list(@student.condition_id,@student.current_ordering)
if @next_question_list

@student.current_question_list = @next_question_list.question_list_id
@student.current_ordering = @next_question_list.ordering
@student.save
@questions = Question.find_by_question_list_id(@student.current_question_list)
else
flash[:notice] = ‘Finished experiment.’
redirect_to :action => ‘index’
end"
end

First, the little hidden field in _question_types.rhtml should now read:

<%= hidden_field_tag "question[" + question.id.to_s + "]" %></i>

in _question_types.rhtml

<i> <% if question.type_id == QuestionType::CHECKBOX %>
<% @options = Question.find_question_options_array(question.id) %>
<% for option in @options %>
<%= check_box(question.id,option.option_id) %><%= option.display_text %>

<% end %>
<% end %>

Hmmm — we also need to update manage_question_list/create. The solution here is trivially easy. In manage_question_lists/_form.rhtml, change the foreach experiment line to read

<% for experiment in Experiment.find(:all) %>

Also, rename NotesField.name_from_id and NotesRecord.name_from id to find_name_by_id. Consistency, consistency, consistency!

Saving Simple Survey Data

Saving survey data from the notesonrails application is not just a matter of adding something new — it also involves destroying something old. Today’s work doesn’t get us a completely functional system, but it migrates the system from a “notes” to a “survey response” method of saving data, and lays the groundwork for more tomorrow…


The old notes model is built on the idea that all instrument data involves the following structure

ID
StudentId
NoteText
NotesFieldId
NotesRecordID

However, now that notes are just detailed questions, a better format would be

Id
StudentId
QuestionListID
QuestionId
Field_Identifier
Record_Identifier
SurveyText

On one level this replaces record/field with questionlist/question as the primary identifier of where data comes from. Field and record can now be used by other purposes as well.

First, generate a model for the new survey_responses

ruby script/generate model survey_response

This creates 027_create_survey_responses.db, which should be altered to read (recall the ID is created manually):

class CreateSurveyResponses < ActiveRecord::Migration
def self.up
create_table :survey_responses do |t|
t.column :student_id, :integer
t.column :question_list_id, :integer
t.column :question_id, :integer
t.column :field_identifier, :integer
t.column :record_identifier, :integer
t.column :survey_text, :string
end
end

def self.down
drop_table :survey_responses
end
end

And next, delete the old notes table

ruby script/generate migration dump_notes

The new file that creates, 028_dump_notes.rb, should read (now that we entered the current description of the notes table in case we want to undo this)

class DumpNotes < ActiveRecord::Migration
def self.up
drop_table :notes
end

def self.down
create_table :notes do |t|
t.column :student_id, :integer
t.column :notetext, :string
t.column :notes_field_id, :integer
t.column :notes_record_id, :integer
end
end
end

And then, rake db:migrate

Good. The next part is just relatively straightforward: update the students controller to actually save information.

And in the _notes_view.rhtml partial, you’ll need to add lines to identify the detailed data of the notes survey responses, as well as getting rid of the old and redundant ajax code from prevoius days:

<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>
<br /><%= hidden_field_tag ‘field_’ + question.id.to_s, notes_field.id %>
<br /><%= hidden_field_tag ‘record_’ + question.id.to_s, notes_record.id %>
<% end %>
</tr>
<% end %>
</table>

For its part, the relevent section of _question_types.rhtml changes hardly at all:

<% if question.type_id == QuestionType::NOTES_MATRIX or question.type_id == QuestionType::NOTES_LINEAR %>
<br />Before, we go father, question is <%= question.id %>
<% @notes_fields = NotesField.find_by_condition(@student.condition_id) %>
<% @notes_records = NotesRecord.find_by_condition(@student.condition_id) %>
<%= hidden_field_tag "question_" + question.id.to_s %>
<%= render :partial => "notes_view", :locals => {:question => question } %>
<% end %>

Likewise, modify students_controller.rb so it starts like this:

def run_experiment
@student = Student.find(session[:student])

unless @student.condition_id
@student.condition_id = params[:student][:condition_id]
@student.save
end

## Saving data goes here
@questions = params[:question]
if (@questions)
for question in @questions
question_number = question[0]
question_data = question[1]

question_field = params[‘field_’ + question_number]
question_record = params[‘record_’ + question_number]

@survey_response = SurveyResponse.new(
:student_id => @student.id,
:question_list_id => @student.current_ordering,
:question_id => question_number,
:field_identifier => question_field,
:record_identifier => question_record,
:survey_text => question_data
)
@survey_response.save()
end
end
## stop saving data here

There’s still stuff to do. Complicated form data, such as checkboxes, are not saved correctly, and the notes are still not saved at all. But it’s a start

Error Catching, and Implementing Notes

Today we repair what we broke last time.

This entry is the most boring of all, as all it does is fix errors and “features” that were introduced (intentionally or no) in the prior work. At the end, the student experiment integrates quesiton lists and notes, but doesn’t actually save the results.


Try to go to the manage_question_lists controller and, at least with how I’ve entered data, you get this unfriendly message

ActiveRecord::RecordNotFound in Manage_question_lists#index

Showing app/views/manage_question_lists/list.rhtml where line #32 raised:

Couldn’t find QuestionList without an ID

Extracted source (around line #32)

..

32: <% question_list = QuestionList.find(question_list_condition.question_list_id) %>

First, it’s easy to see that this is a bad design. It makes catching errors hard, as we see right now. So in question_list.rb, add a new function

def self.find_by_id(id)
begin
found_object = find(id)
@to_return = found_object
rescue
logger.error("Error: No record found for id " + id.to_s + " generated by question_list.rb:find_by_id")
end
@to_return
end

The error reveals that there is a QuestionListID (no. 4, in my case) being called, in spite of its presense in QuestionListConditions. Now we’ll do some begin/rescue/end code in _list.rhtml to make it error out prettier (wouldn’t want this error while showing it off someday!) while next finding out what causes this problem in the first place.

The code to do the first part is pretty simple. Also We’ll check to make sure that the question_list exists — so now we have a double-layer of code!

<% if question_list %>
<tr>
<% begin %>
<td><%= h question_list.id %></td>
<td><%= h question_list.name %></td>
<td><%= text_field :question_ordering, question_list_condition.id, :value => question_list_condition.ordering, :size=>3 %></td>
<td><%= h question_list.description %></td>
<td><%= link_to ‘Show’, :action => ‘show’, :id => question_list %></td>
<td><%= link_to ‘Edit’, :action => ‘edit’, :id => question_list %></td>
<td><%= link_to ‘Destroy’, { :action => ‘destroy’, :id => question_list }, :confirm => ‘Are you sure?’, :method => :post %></td>
<% rescue %>
<br />Error encountered</td>
<% end %>
</tr>
<% else %>
<tr /><td>QL <%= condition.id %> does not exist </td></tr>
<% end %>

(That extra td is there after Error encountered because the problem presumable happened while in a table cell)

Presumably this problem is caused by a question_list that existed but was deleted. And indeed, the question_list id tag appears in two places: in question_lists and question_list_conditions. Therefore, whenever a question_list is destroyed, the question_list_conditions with that question_list_id should be destroyed as well.

in models,question_list_condition.rb, add the following function

def self.destroy_by_question_list_id(question_list_id)
question_list_conditions = find(:all,$conditions=>{:question_list_id => question_list_id})
for question_list_condition in question_list_conditions
QuestionListCondition(question_list_condition.id).destroy
end
end

And update controllers/manage_question_lists_controller.rb to have

def destroy
begin
question_list_id = params[:id]
QuestionList.find(question_list_id).destroy
QuestionListConditions.find(:all, :conditions=>{:question_list_id =>question_list_id}).destroy
QuestionListCondition.destroy_by_question_list_id(question_list_id)
flash[:notice] = ‘QuestionList was successfully deleted.’
rescue
flash[:notice] = ‘QuestionList was not successfully deleted.’
end
redirect_to :action => ‘list’
end

Next to fix: manage_question_options for a similar problem. Update views/manage_question_options/list.rhtml to read as follows:

<h1>Listing question_options</h1>

<table>
<tr>
<th>ID</th>
<th>Question ID</th>
<th>Question Text</th>
<th>Option</th>
<th>Display Text</th>
</tr>
<% for question_option in @question_options %>
<% begin %>
<tr>
<td><%= question_option.id %></td>
<td><%= question_option.question_id %></td>
<td><%= Question.find_name_by_id(question_option.question_id) %></td>
<td><%= question_option.option_id %></td>
<td><%= question_option.display_text %></td>
<% rescue %>
<br />QuestionOption no. <%= question_option.id %> generated an error</td>
<% end %>
<td><%= link_to ‘Show’, :action => ‘show’, :id => question_option %></td>
<td><%= link_to ‘Edit’, :action => ‘edit’, :id => question_option %></td>
<td><%= link_to ‘Destroy’, { :action => ‘destroy’, :id => question_option }, :confirm => ‘Are you sure?’, :method => :post %></td>
</tr>
<% end %>
</table>

<%= link_to ‘Previous page’, { :page => @question_option_pages.current.previous } if @question_option_pages.current.previous %>
<%= link_to ‘Next page’, { :page => @question_option_pages.current.next } if @question_option_pages.current.next %>

<br />

<%= link_to ‘New question_option’, :action => ‘new’ %>

Now that this works, I went through and deleted all the questions and question_lists stuff. Now to re-create. First, after a question_list is created, send the user to the create new question screen with this line:

redirect_to :controller => ‘manage_questions’, :action => ‘new’

in manage_question_lists_controll.rb:create()

Now that’s done, I went through and created some appropiate question_lists and questions. Finally, we need to finish up the student controller to allow experiments to be taken.

Add the following code to the _question_types.rhtml partial under the students view

<% if question.type_id == QuestionType::NOTES_MATRIX or question.type_id == QuestionType::NOTES_LINEAR %>

<% @notes_fields = NotesField.find_by_condition(@student.condition_id) %>
<% @notes_records = NotesRecord.find_by_condition(@student.condition_id) %>
<%= render :partial => "notes_view" %>
<% end %>

The concluding portion of the students_controller.rb:run_experiment function should read

else
flash[:notice] = ‘Finished experiment.’
redirect_to :action => ‘index’

end
end

And now we can talk through the notes experiment!

Of course, it doesn’t save, but that’s a project for another time…

Multi-Page Experiments

Today experiments (or, rather, surveys) that incorporate multiple question sheets were implemented, and the groundwork was laid to making complex experiments that have both survey and matrix items. Work stopped once an unknown error was encountered — trapping that will be the top priority for the next note on rails.


First, we’ll change the code so the question_sheets are no longer just dynamically added to the old page, but come up as something new. This will make the page both easier to navigate and easier to program for. To do this, change the app/views/students/_selection_condition.rhtml partial to

<% form_tag :action => ‘run_experiment’ do %>

Your Condition: <%= select :student, :condition_id, @conditions %>
<%= submit_tag "Select Condition" %>

<% end %>

Now go tinto the students_controller.rb, and rename select_notes_from_condition to run_experiment(). This will pull up our old _question_list_view.rhtml partial. However, partials means that the standard header doesn’t get added. So rename _question_list_view.rhtml to run_experiment.rhtml

Open the run_experiment.rhtml file, and update the form tag so that it’s a traditional form and not one of these hip AJAX forms. Leaving out the body of the file, run_experiment.rhtml should now look like:

<h1>Question List Page</h1>
<% form_tag :action => ‘run_experiment’ do %>

<u>…</u>

<%= submit_tag "Continue" %>
<% end %>

Now we get into the problem, however, that run_experiment.rhtml contains both the outer form that does the work of handling form processing and the inner control logic of what to do for different types of questions. Furhter, a check of our code as it now runs reveals that the controller is going to try to determine the condition of the student each time So break out the inner part of the page to a partial called _question_types.rhtml

<% for question in @questions %>
<p><h3><%= question.display_text %></h3>

<% if question.type_id == QuestionType::STRING %>
<%= text_field("question",question.id) %>
<% end %>

<% if question.type_id == QuestionType::TEXT %>
<%= text_area("question",question.id) %>
<% end %>

<% if question.type_id == QuestionType::SELECTION %>
<% @options = Question.find_question_options_map(question.id) %>
<%= select :question, question.id, @options %>
<% end %>

<% if question.type_id == QuestionType::CHECKBOX %>
<% @options = Question.find_question_options_array(question.id) %>
<% for option in @options %>
<%= check_box("question",question.id) %><%= option.display_text %>

<% end %>
<% end %>

<% if question.type_id == QuestionType::RADIO %>
<% @options = Question.find_question_options_array(question.id) %>
<% for option in @options %>
<%= radio_button("question",question.id,option.id) %><%= option.display_text %>

<% end %>
<% end %>

<% if question.type_id == QuestionType::INSTRUCTION %>

<% end %>

</p>
<% end %>

And now run_experiment.rhtml should read:

<h1>Question List Page</h1>
<% form_tag :action => ‘running_experiment’ do %>
<%= render :partial => ‘question_types’ %>
<%= submit_tag "Continue" %>
<% end %>

For now create a skeleten running_experiment() in the students controller

def running_experiment
@student = Student.find(session[:student])
@next_question_list = Student.find_next_question_list(@student.condition_id,@student.current_question_list)
@student.current_question_list = @next_question_list
@student.save
@questions = Question.find_by_question_list_id(@student.current_question_list)
end

Hmmm.. Some quick testing reveals that Question List never goes above “1,” even after @student.save is added to the code. Some investigation reveals an error in how the find_next_question_list method and related code was programmed. We need both to know the question_list_id (so that the current question list can be pulled out) and the ordering (so that we know how to search for the next one). So change find_next_question_list in student.rb to:

def self.find_next_question_list(condition_id,ordering)
to_find = QuestionListCondition.find(
:first,
:conditions=> [ ‘condition_id = ? AND ordering > ?’, condition_id, ordering ],
:order => ‘ordering ASC’
)

@to_return = to_find
end

We’ll also update the database again to store the ordering information

ruby script/generate migration alter_student_add_current_ordering

The new file, 026_alter_student_add_current_ordering.rb

class AlterStudentAddCurrentOrdering < ActiveRecord::Migration
def self.up
add_column :students, :current_ordering, :integer, :default => 0
end

def self.down
remove_column :students, :current_ordering
end
end

Then rake db:migrate

Updating the controller as appropriate makes it work, but now a new question — what happens when the system comes to the end of questions? The solution will be to collapse back into one run_experiment() procedure that tests for both the beginninga and the end. Change the “running_experiment” reference to “run_experiment” in run_experiment.rhtml and there you go!

Next step is to integrate the matrix back into the experiment. It won’t eb fully functioning now, but at least it will allow the matrix to appear in the regular rotation

add these lines: at the very bottom of question_type.rb:

QuestionType.add_item :NOTES_LINEAR, 7
QuestionType.add_item :NOTES_MATRIX, 8

The full run_experiment function will look like this:

def run_experiment
@student = Student.find(session[:student])

unless @student.condition_id
@student.condition_id = params[:student][:condition_id]
@student.save
end

@next_question_list = Student.find_next_question_list(@student.condition_id,@student.current_ordering)
if @next_question_list

@student.current_question_list = @next_question_list.question_list_id
@student.current_ordering = @next_question_list.ordering
@student.save
@questions = Question.find_by_question_list_id(@student.current_question_list)
else
render_text "Hello, world"
end
end

There is more to do. The notes have to be integrated, and during testing there’s an error somewhere that needs to be caught. But everyday is one day closer to having a functioning system.

Preparing for a new Experiment Interface

After my last note on rails I took a long break, in order to critique two texts (of which Cognitive Development is the first), attend the Boyd Conference, and generally relax. But a month is long enough to go without work on what will now be the technological basis for the next installment of the Wary Guerrilla / Wary Student research program.


An Experiment with a Question List

Today’s work focused laid the foundations for a completely revamped experiment feature. Before, the experiment feature only dropped the user straight into a note-taking interface. Now, question lists are being integrated so that an entire battery can be given through the interface. The task is not completed today — indeed, all that was accomplished was presenting one question list instead of the notetaknig matrix — but it’s a start

After thanking the websites of continuous thinking, hokudai, rubyinside, and rubyonrails more practical help, read below the fold for implementation details…


A number of things being done today. The main thing is allowing the student to actually take the full experiment, and that requires keeping track of what questionlist the student is currently on. To do that, we will alter the study model to have a current_question_list field

ruby script/generat migration alter_student_add_current_question_list

And then for 025_alter_student_add_current_question_list.rb

class AlterStudentAddCurrentQuestionList < ActiveRecord::Migration
def self.up
add_column :students, :current_question_list, :integer, :default => 0
end

def self.down
remove_column :students, :current_question_list
end
end

Now in the student model, add code to retrieve the next question list

In student.rb

def self.find_next_question_list(condition_id,current_question_list)
to_find = QuestionListCondition.find(
:first,
:conditions=> [ ‘condition_id = ? AND ordering > ?’, condition_id, current_question_list ],
:order => ‘ordering ASC’
)

@to_return = to_find.question_list_id
end

In question.rb

def self.find_by_question_list_id(question_list_id)
@questions = find(:all,:conditions => [‘list_id = ?’,question_list_id], :order => "ordering ASC"
end

def self.find_question_options_array(question_id)
@options = QuestionOption.find(:all,:conditions=>[‘question_id = ?’, question_id], :order => ‘display_text ASC’)
end

def self.find_question_options_map(question_id)
@options = find_question_options_array(question_id).map {|u| [u.display_text, u.id] }
end

From student_controller.rb, update the following functions:

def select_condition_from_experiment
selected_experiment_id = params[:selected_experiment][:experiment_id]
@conditions = Condition.find_conditions(selected_experiment_id)
render :partial => "select_condition"

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

end

def select_notes_from_condition
@student = Student.find(session[:student])
@student.condition_id = params[:student][:condition_id]
@student.save

@next_question_list = Student.find_next_question_list(@student.condition_id,@student.current_question_list)
@student.current_question_list = @next_question_list
@questions = Question.find_by_question_list_id(@student.current_question_list)

render :partial => "question_list_view"
#render_text @next_question_list

#@notes_fields = NotesField.find_by_condition(@student.condition_id)
#@notes_records = NotesRecord.find_by_condition(@student.condition_id)
#render :partial => "notes_view"
end

create the new file app/views/students/_question_list_view.rhtml

<h1>Question List Page</h1>
<%= form_remote_tag(
:update => "notetaking_display",
:url => { :action => :save_student_notes },
:position => "top" )
%>
<% for question in @questions %>
<p><h3><%= question.display_text %></h3>

<% if question.type_id == QuestionType::STRING %>
<%= text_field("question",question.id) %>
<% end %>

<% if question.type_id == QuestionType::TEXT %>
<%= text_area("question",question.id) %>
<% end %>

<% if question.type_id == QuestionType::SELECTION %>
<% @options = Question.find_question_options_map(question.id) %>
<%= select :question, question.id, @options %>
<% end %>

<% if question.type_id == QuestionType::CHECKBOX %>
<% @options = Question.find_question_options_array(question.id) %>
<% for option in @options %>
<%= check_box("question",question.id) %><%= option.display_text %>

<% end %>
<% end %>

<% if question.type_id == QuestionType::RADIO %>
<% @options = Question.find_question_options_array(question.id) %>
<% for option in @options %>
<%= radio_button("question",question.id,option.id) %><%= option.display_text %>

<% end %>
<% end %>

<% if question.type_id == QuestionType::INSTRUCTION %>

<% end %>

</p>
<% end %>
<%= end_form_tag %>

Update _select_condition.rhtml

<%= form_remote_tag(:update => "notetaking_display",
:url => { :action => :select_notes_from_condition },
:position => "top" ) %>
Your Condition: <%= select :student, :condition_id, @conditions %>
<%= submit_tag "Select Condition" %>

<%= end_form_tag %>

The index.rhtml in the same folder was also updated:

<html>
<head>
<title>Notes on Rails Mock Student Interface</title>
<%= javascript_include_tag "prototype" %>
</head>
<body>
<h3>Add to list using Ajax</h3>
<%= form_remote_tag(:update => "login_with_condition",
:url => { :action => :select_condition_from_experiment },
:position => "top" ) %>

<p><label for="student_name">Your Name</label><br/>
<%= text_field ‘student’, ‘name’ %>
<br />Your Experiment:
<%= select :selected_experiment, :experiment_id, @experiments %>
<%= submit_tag "Select Experiment" %></p>

<%= end_form_tag %>

<div id="login_with_condition"></div>
<div id="notetaking_display"></div>
</body>
</html>

Extending the Question and QuestionList Interfaces

Not going to change the experimental code yet — that’s too big for this day of vacation, whatever I said previously.

Today we’ll improve the student interface to allow questions to be ordered, question lists to be ordered, and a new “question” type (instruction) to be added.

My props to Geek Skillz and Programming Ruby‘s “class Hash” for coming in useful today.


First, go into app/models/question_type-rb and add a new QuestionType item

QuestionType.add_item :INSTRUCTION, 6

Then create a new module, TextfieldType (app/modles/textfield_type.rb) with the following code:

class TextfieldType
def self.add_item(key,value)
@hash ||= {}
@hash[key]=value
end

def self.const_missing(key)
@hash[key]
end

def self.each
@hash.each {|key,value| yield(key,value)}
end

def self.find_selection_list
@hash
end

TextfieldType.add_item :TEXT, 1
TextfieldType.add_item :HTML, 2
end

Then create two migrations, because we will add ordering to both Questions and QuestionLists

ruby script/generate migration alter_questions_add_ordering
ruby script/generate migration alter_questionlists_add_ordering

022_alter_questions_add_ordering.rb reads:

class AlterQuestionsAddOrdering < ActiveRecord::Migration
def self.up
add_column :questions, :ordering, :integer
end

def self.down
remove_column :question_lists, :ordering
end
end

while 023_alter_questionlists_add_ordering.rb is:

class AlterQuestionlistsAddOrdering < ActiveRecord::Migration
def self.up
add_column :question_lists, :ordering, :integer
end

def self.down
remove_column :question_lists, :ordering
end
end

Then rake db:migrate.

Update app/views/manage_questions/list.rhtml:

<h1>Listing questions</h1>

<% form_tag :action => ‘update_ordering’ do %>

<table>
<tr>
<th>ID</th>
<th>Name</th>
<th>Order</th>
<th>Display</th>
<th>Type</th>
<th>Question List</th>
</tr>
<% for question in @questions %>
<tr>
<td><%= question.id %></td>
<td><%= question.name %></td>
<td><%= text_field :question_ordering, question.id, :value => question.ordering, :size=>3 %></td>
<td><%= question.display_text %></td>
<td><%= question.type_id %></td>
<td><%= QuestionList.find_name_by_id(question.list_id) %></td>
<td><%= link_to ‘Show’, :action => ‘show’, :id => question %></td>
<td><%= link_to ‘Edit’, :action => ‘edit’, :id => question %></td>
<td><%= link_to ‘Destroy’, { :action => ‘destroy’, :id => question }, :confirm => ‘Are you sure?’, :method => :post %></td>
</tr>
<% end %>
</table>

<%= link_to ‘Previous page’, { :page => @question_pages.current.previous } if @question_pages.current.previous %>
<%= link_to ‘Next page’, { :page => @question_pages.current.next } if @question_pages.current.next %>

<br /><%= submit_tag ‘Update Ordering’ %>
<br /><%= link_to ‘New question’, :action => ‘new’ %>
<% end %>

In manage_questions_controller.rb, create the new function update_ordering

def update_ordering
@questions_to_order = params[:question_ordering]
@questions_to_order.each { |question_id, new_ordering_value|
if new_ordering_value
@question = Question.find(question_id)
@question.ordering = new_ordering_value
@question.save
end
}
flash[:notice] = "Ordering updated as appropriate"
redirect_to :action => ‘list’
end

And update def list as so:

def list
@question_pages, @questions = paginate :questions, :per_page => 10, :order => ‘Ordering ASC, ID ASC’
end

After a short break, I realize a problem: putting Ordering in the Questions table makes sense, because each Question is in only one QuestionList table. However, each QuestionList can be in multiple conditions, so Ordering has to go out of QuestionList and into QuestionListConditions; So create a new migration

ruby script/generate migration move_order_from_questionlist_to_questionlistcondition

that file looks like:

class MoveOrderFromQuestionlistToQuestionconditionlist < ActiveRecord::Migration
def self.up
remove_column :question_lists, :ordering
add_column :question_list_conditions, :ordering, :integer
end

def self.down
add_column :question_lists, :ordering, :integer
remove_column :question_list_conditions, :ordering
end
end

Then rake db:migrate.

Now back to the main job

create a new function for the QuestionListCondition model:

def self.find_by_condition_id(condition_id)
to_find = find(:all,:conditions=> {:condition_id => condition_id }, :order => "Ordering, question_list_id")
end

UPdate app/views/manage_question_lists/list.rhtml:

<h1>Listing question_lists</h1>

<% form_tag :action => ‘update_ordering’ do %>
<table>
<tr>
<th>ID</th>
<th>Name</th>
<th>Order</th>
<th>Description</th>
</tr>

<% for question_list in @question_lists %>
<tr>
<td><%= h question_list.id %></td>
<td><%= h question_list.name %></td>
<td>(varies)</td>
<td><%= h question_list.description %></td>
<td><%= link_to ‘Show’, :action => ‘show’, :id => question_list %></td>
<td><%= link_to ‘Edit’, :action => ‘edit’, :id => question_list %></td>
<td><%= link_to ‘Destroy’, { :action => ‘destroy’, :id => question_list }, :confirm => ‘Are you sure?’, :method => :post %></td>
</tr>
<% end %>

<% for experiment in @experiments %>
<% @conditions = Condition.find_conditions_array(experiment.id) %>
<% for condition in @conditions %>
<tr><td colspan="7">  </td></tr>
<tr><td colspan="7"><b><%= experiment.name %> – <%= condition.name %></b></td></tr>
<% @question_list_conditions = QuestionListCondition.find_by_condition_id(condition.id) %>
<% for question_list_condition in @question_list_conditions %>
<% question_list = QuestionList.find(question_list_condition.question_list_id) %>
<tr>
<td><%= h question_list.id %></td>
<td><%= h question_list.name %></td>
<td><%= text_field :question_ordering, question_list_condition.id, :value => question_list_condition.ordering, :size=>3 %></td>
<td><%= h question_list.description %></td>
<td><%= link_to ‘Show’, :action => ‘show’, :id => question_list %></td>
<td><%= link_to ‘Edit’, :action => ‘edit’, :id => question_list %></td>
<td><%= link_to ‘Destroy’, { :action => ‘destroy’, :id => question_list }, :confirm => ‘Are you sure?’, :method => :post %></td>
</tr>
<% end %>
<% end %>
<% end %>
</table>
<%= link_to ‘Previous page’, { :page => @question_list_pages.current.previous } if @question_list_pages.current.previous %>
<%= link_to ‘Next page’, { :page => @question_list_pages.current.next } if @question_list_pages.current.next %>

<br /><%= submit_tag ‘Update Ordering’ %>
<% end %>
<br />s<%= link_to ‘New question_list’, :action => ‘new’ %>

Update app/controllers/manage_question_lists_controller.rb, in the function list

def list
@question_list_pages, @question_lists = paginate :question_lists, :per_page => 10
@experiments = Experiment.find(:all)
end

and create a new function, update ordering (remember we wrote a very similar function for the questions controller):

def update_ordering
@questions_to_order = params[:question_ordering]
@questions_to_order.each { |question_list_condition_id, new_ordering_value|
if new_ordering_value
@question = QuestionListCondition.find(question_list_condition_id)
@question.ordering = new_ordering_value
@question.save
end
}
flash[:notice] = "Ordering updated as appropriate"
redirect_to :action => ‘list’
end

The next day of work will, hopefully, upgrade the experiment interface

Finishing the Question and Question-List Interfaces

After creating the QuestionList and Question controllers, the next step is to update both of them so that defaults are returned when the user edits the extended fields (which conditions the QuqestionList belongs to, and what options a Question has). Because these are stored in other tables, this information is not presented as-saved to the user, but rather the “defaults” are blank.


Populating forms with defaults from the database

With the help of wiki.rubyonrails.org, the process is pretty simple…


… add a new function to question_list_condition.rb (The QuestionListCondition model)

def self.return_checked_by_condition_id_question_list_id(condition_id,question_list_id)
to_find = find_by_condition_id_question_list_id(condition_id,question_list_id)

“checked” if to_find
end

And replace the current checkbox line in views/manage_question_lists/_form.rhtml with:

<br /><%= check_box :selected_condition, condition.id,:checked => QuestionListCondition.return_checked_by_condition_id_question_list_id(condition.id,@question_list.id) %>

to QuestionOption controller will be modified in much the same way

in question_option.rb:

def self.find_display_text_by_question_id_option_id(question_id,option_id)
to_return = find(
:first,
:conditions => {
:question_id => question_id,
:option_id => option_id
}
)

return to_return.display_text if to_return
end

And in app/views/manage_questions/_form.rhtml, just one line needs to be changed:

<%= text_field :question_options, iterator, :value => QuestionOption.find_display_text_by_question_id_option_id(@question.id,iterator) %>

Next up: Display a question list

Creating a Basic Question Interface

Having created a basic question-list interface (functionally the same as MediaLab’s que file), today the admin interface is expanded to have a basic question interface.

The standard static scaffold is actually inappropriate for how we’ll want questions to be generated (something like the experiment controller’s ajax will be require), but for now let’s just get it working.


One major change is we will create a Class Constant (enumeration) for our question types, with apologies to RubyFleebie:

Create a module named question_type.rb

class QuestionType
def self.add_item(key,value)
@hash ||= {}
@hash[key]=value
end

def self.const_missing(key)
@hash[key]
end

def self.each
@hash.each {|key,value| yield(key,value)}
end

def self.find_selection_list
@hash
end

QuestionType.add_item :STRING, 1
QuestionType.add_item :TEXT, 2
QuestionType.add_item :SELECTION, 3
QuestionType.add_item :CHECKBOX, 4
QuestionType.add_item :RADIO, 5
end

Change question.rb:

class Question < ActiveRecord::Base
validates_presence_of :name, :display, :type, :list_id
validates_uniqueness_of :name
end

make sure question_list.rb is to

class QuestionList < ActiveRecord::Base
validates_presence_of :name, :description

def self.find_name_by_id(selected_id)
found_object = find(selected_id)
@to_return = found_object.name
end

def self.find_selection_list
@conditions = QuestionList.find(:all ).map {|u| [u.name, u.id] }
end

end

Uh oh. I realized I may have used another special name in a migration like I did on day one. No problem. Generate a new migration:

019_alter_questions should be:

class AlterQuestions < ActiveRecord::Migration
def self.up
add_column :questions, :type_id, :integer
remove_column :questions, :type
end

def self.down
remove_column :questions, :type_id
add_column :questions, :type_id, :integer
end
end

Then rake db:migrate

app/views/manage_questions/_form.rhtml:

<%= error_messages_for ‘question’ %>

<!–[form:question]–>
<p><label for="question_name">Name</label><br/>
<%= text_field :question, :name %></p>

<p><label for="question_display">Display</label><br/>
<%= text_area :question, :display, :rows => 3 %></p>

<p><label for="question_type_id">Type</label><br/>
<% @question_types = QuestionType.find_selection_list %>
<%= select :question, :type_id, @question_types %>

<p><label for="question_list_id">Question List</label><br/>
<% @question_lists = QuestionList.find_selection_list %>
<%= select :question, :list_id, @question_lists %>
<!–[eoform:question]–>

Apparently, display is a special word too, so create yet another migration…

ruby script/generate migration alter_questions_replace_display_with_display_text

And make 020_alter_questions_replace_display_with_display-text.rb like so:

class AlterQuestionsReplaceDisplayWithDisplayText < ActiveRecord::Migration
def self.up
remove_column :questions, :display
add_column :questions, :display_text, :text
end

def self.down
remove_column :questions, :display_text
add_column :questions, :display, :text
end
end

Then rake db:migrate

Now to to question.rb, _form.rhtml, list.rhtml changing names as appropriate

Now, only one more thing to do: create default options where appropriate.

_form should now look like:

<%= error_messages_for ‘question’ %>

<!–[form:question]–>
<p><label for="question_name">Name</label><br/>
<%= text_field :question, :name %></p>

<p><label for="question_display">Display</label><br/>
<%= text_area :question, :display_text, :rows => 3 %></p>

<p><label for="question_type_id">Type</label><br/>
<% @question_types = QuestionType.find_selection_list %>
<%= select :question, :type_id, @question_types %>

<p><label for="question_list_id">Question List</label><br/>
<% @question_lists = QuestionList.find_selection_list %>
<%= select :question, :list_id, @question_lists %>

<p><label for="question_options">Default Options</label><br />
<% 1.upto(10) do |iterator| %>
<%= text_field :question_options, iterator %>
<% end %></p>

<!–[eoform:question]–>

Now we need to add default option awareness to the controller. Again, >yeseterday’s work will be a guide.

But first, another change (aren’t you glad we have migrations?)

ruby generate/migration alter_question_options

021_alter_question_options.rb:

class AlterQuestionOptions < ActiveRecord::Migration
def self.up
remove_column :question_options, :name
remove_column :question_options, :optiontext
add_column :question_options, :option_id, :integer
add_column :question_options, :display_text, :text
end

def self.down
remove_column :question_options, :option_id
remove_column :question_options, :display_text
add_column :question_options, :name, :string
add_column :question_options, :optiontext, :text
end
end

Then rake db:migrate

Now, update two functions in manage_questions_controller.rb

def create
#render_text params[:question][:display]
@question = Question.new(params[:question])
@result_of_save = @question.save

if @result_of_save

@question_options = params[:question_options]
for question_option in @question_options
@question_option_id = question_option[0].to_i
@question_option_text = question_option[1].to_s
if @question_option_text
QuestionOption.add_by_question_id_option_id(@question.id,@question_option_id,@question_option_text)
else
QuestionOption.destroy_by_question_id_option_id(@question.id,@question_option_id)
end
end
flash[:notice] = ‘Question was successfully created.’
redirect_to :action => ‘list’
else
render :action => ‘new’
end
end

def edit
@question = Question.find(params[:id])
end

def update
@question = Question.find(params[:id])
if @question.update_attributes(params[:question])

@question_options = params[:question_options]
for question_option in @question_options
@question_option_id = question_option[0].to_i
@question_option_text = question_option[1].to_s
if @question_option_text
QuestionOption.add_by_question_id_option_id(@question.id,@question_option_id,@question_option_text)
else
QuestionOption.destroy_by_question_id_option_id(@question.id,@question_option_id)
end
end

flash[:notice] = ‘Question was successfully updated.’
redirect_to :action => ‘show’, :id => @question
else
render :action => ‘edit’
end
end

Change the question_option.rb module:

class QuestionOption < ActiveRecord::Base
validates_presence_of :question_id, :option_id, :display_text

def self.add_by_question_id_option_id(question_id,option_id,display_text)
self.destroy_by_question_id_option_id(question_id,option_id)

to_add = self.new(
:question_id => question_id,
:option_id => option_id,
:display_text => display_text
)
to_add.save
return to_add
end

def self.destroy_by_question_id_option_id(question_id,option_id)
to_destroy = find_by_question_id_option_id(question_id,option_id)

to_destroy.destroy if to_destroy
end

def self.find_by_question_id_option_id(question_id,option_id)

to_return = find(
:first,
:conditions => {
:question_id => question_id,
:option_id => option_id
}
)

to_return
end

end

The very last things that we need to do is to work on the manage_question_list_conditions_controller (which as I’ve added and deleted data spontaneously stopped working) and the question_options controller (so we can see the options we just added — sometime later we will integrate this functionality with the main controller)

The specific problem with the QuestionListConditionsController reads:

Showing app/views/manage_question_list_conditions/list.rhtml where line #13 raised:

Couldn’t find QuestionList with ID=6

With the extracted source:

<tr>
<td><%= question_list_condition.id %></td>
<td><%= Condition.find_name_by_id(question_list_condition.condition_id) %></td>
<td><%= QuestionList.find_name_by_id(question_list_condition.question_list_id) %></td>
</tr>
<% end %>
</table>

So what we need to do is to alter QuestionList’s module’s find_name_by_id to encorporate some basic exception handling (actually we should have exception handling all over the place)

So change QuestionList:find_name_by_id to

def self.find_name_by_id(selected_id)
begin
found_object = find(selected_id)
@to_return = found_object.name
rescue
@to_return = “Error: No Name found for id ” + selected_id.to_s + ” generated by question_list.rb:find_name_by_id”
end
@to_return
end

(not exactly pretty, but right now focus on the structure).

Update question.rb:

class Question < ActiveRecord::Base
validates_presence_of :name, :display_text, :type_id, :list_id
validates_uniqueness_of :name

def self.find_name_by_id
to_find = find(selected_argument_id)
@to_return = to_find.name
end
end

And finally app/views/manage_question_options/list.rhtml

<h1>Listing question_options</h1>

<table>
<tr>
<th>ID</th>
<th>Question ID</th>
<th>Question Text</th>
<th>Option</th>
<th>Display Text</th>
</tr>
<% for question_option in @question_options %>
<tr>
<td><%= question_option.id %></td>
<td><%= question_option.question_id %></td>
<td><%= Question.find_name_by_id(question_option.question_id) %></td>
<td><%= question_option.option_id %></td>
<td><%= question_option.display_text %></td>
<td><%= link_to ‘Show’, :action => ‘show’, :id => question_option %></td>
<td><%= link_to ‘Edit’, :action => ‘edit’, :id => question_option %></td>
<td><%= link_to ‘Destroy’, { :action => ‘destroy’, :id => question_option }, :confirm => ‘Are you sure?’, :method => :post %></td>
</tr>
<% end %>
</table>

<%= link_to ‘Previous page’, { :page => @question_option_pages.current.previous } if @question_option_pages.current.previous %>
<%= link_to ‘Next page’, { :page => @question_option_pages.current.next } if @question_option_pages.current.next %>

<br />

<%= link_to ‘New question_option’, :action => ‘new’ %>

Another days work done!

Tomorrow: showing the defaults on the questions and question-lists controller.

Creating a Basic Question-List Interface

The presentaiton was a hit. The next step is to make the “take the experiment” feature less fakey, by incorporating pretests and posttests. My inspiration for how to do this comes from MediaLab, which I worked with previously.

Every Question will belong to a QuestionList. Every QuestionList can belong to any number of conditions. There will also be a model that ties QuestionLists into Conditions and one that ties QuestionOptions into Questions. The table schemes are something like :


Question
-ID
-Name
-Display
-Type
-ListID

-QuestionList
-ID
-Name
-Description

QuestionOptions
-ID
-QuestionID
-Name
-OptionText

QuestionListConditions
-ID
-ConditionID
-QuestionListID

ruby script/generate model question
ruby script/generate model question_list
ruby script/generate model question_option
ruby script/generate model question_list_condition

Then generate the appropriate models

Now to through the migrations, added fields as necessary

015_create_questions.rb

class CreateQuestions < ActiveRecord::Migration
def self.up
create_table :questions do |t|
t.column :name, :string
t.column :display, :text
t.column :type, :integer
t.column :list_id, :integer
end
end

def self.down
drop_table :questions
end
end

016_create_question_lists.rb

class CreateQuestionLists < ActiveRecord::Migration
def self.up
create_table :question_lists do |t|
t.column :name, :string
t.column :description, :text
end
end

def self.down
drop_table :question_lists
end
end

017_create_question_options.rb

class CreateQuestionOptions < ActiveRecord::Migration
def self.up
create_table :question_options do |t|
t.column :question_id, :integer
t.column :name, :string
t.column :optiontext, :text
end
end

def self.down
drop_table :question_options
end
end

018_create_question_list_conditions.rb

class CreateQuestionListConditions < ActiveRecord::Migration
def self.up
create_table :question_list_conditions do |t|
t.column :condition_id, :integer
t.column :question_list_id, :integer
end
end

def self.down
drop_table :question_list_conditions
end
end

Then rake db:migrate

Next, we’ll create the controller and the scaffold for questions and question_lists, as was done on day one.

ruby script/generate controller manage_questions
ruby script/generate controller manage_question_lists

Then generate the static scaffolds, like in day two.

ruby script/generate scaffold question manage_questions
ruby script/generate scaffold question_list manage_question_lists

Now create controllers for question_options and question_list_conditions. We are actually creating two classes of controllers here — the basic steps of setting up an experiment and the more mundane database-interface controllers. But more on that in a bit. For now:

ruby script/generate controller manage_question_options
ruby script/generate controller manage_question_list_conditions
ruby script/generate scaffold question_option manage_question_options
ruby script/generate scaffold question_list_condition manage_question_list_conditions

Now add the interface code as done two days ago to our four new controllers:

layout “notesonrails”

And modify /views/layouts/notesonrails.html as appriate. The goal is to provide access to every controller while differentiating controllers designed for use, for debugging, for output, and for the experiment itself:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title><%= controller.controller_name %>: <%= controller.action_name %></title>
<%= stylesheet_link_tag ‘scaffold’ %>
</head>
<body>

<p style="color: green"><%= flash[:notice] %></p>
<h1>Notes on Rails Admin Interface</h1>
<div id="menu_area">
<p>Create an Experiment:
<%= link_to ( "Experiments",
:controller => :manage_experiments, :action => :index ) %>,
<%= link_to ( "Conditions",
:controller => :manage_conditions,
:action => :index ) %>,
<%= link_to ( "Question Lists",
:controller => :manage_question_lists ) %>,
<%= link_to ( "Questions",
:controller => :manage_questions ) %>,
<%= link_to ( "Fields",
:controller => :manage_notes_fields ) %>,
<%= link_to ( "Records",
:controller => :manage_notes_records ) %>,
</p>

<p>Other Features:
<%= link_to ( "Notes",
:controller => :manage_notes ) %>,
<%= link_to ( "Students",
:controller => :manage_students ) %>,
<%= link_to ( "QuestionList Conditions",
:controller => :manage_question_list_conditions ) %>,
<%= link_to ( "Question Options",
:controller => :manage_question_options ) %> |
<%= link_to ( "Output",
:controller => :output ) %> |
<b><%= link_to ( "Take an Experiment!",
:controller => :students ) %></b></p>
</div>
<%= yield %>

</body>
</html>

Next up, alter the manage_question_lists controller so that one may pick several conditions for the question list to b belong too

in new(), add the line “@experiments = Experiment.find(:all)” and in _form.rhtml

Now let’s add some check boxes to the manage_question_lists controller so that we can associate it with our conditions

Firs,t let’s add some methods on question_list_condition.rb controller:

class QuestionListCondition < ActiveRecord::Base
def self.destroy_by_condition_id_question_list_id(condition_id,question_list_id)
to_destroy = find_by_condition_id_question_list_id(condition_id,question_list_id)

to_destroy.destroy if to_destroy
end

def self.add_by_condition_id_question_list_id(condition_id,question_list_id)
to_add = find_by_condition_id_question_list_id(condition_id,question_list_id)

if to_add
return to_add
else
to_add = QuestionListCondition.new(
:condition_id => condition_id,
:question_list_id => question_list_id
)
to_add.save
return to_add
end
end

def self.find_by_condition_id_question_list_id(condition_id,question_list_id)

to_return = find(
:first,
:conditions => {
:condition_id => condition_id,
:question_list_id => question_list_id
}
)

to_return
end
end

update four functions in manage_question_lists_controller.rb:

def new
@question_list = QuestionList.new
@experiments = Experiment.find(:all)
end

def create
@question_list = QuestionList.new(params[:question_list])
@result_of_save = @question_list.save

@question_list_conditions = params[:selected_condition]
for question_list_condition in @question_list_conditions
@question_list_condition_id = question_list_condition[0].to_i
@question_list_condition_show = question_list_condition[1].to_i
if @question_list_condition_show == 1
QuestionListCondition.add_by_condition_id_question_list_id(@question_list_condition_id,@question_list.id)
else
QuestionListCondition.destroy_by_condition_id_question_list_id(@question_list_condition_id,@question_list.id)
end
end

if @result_of_save
flash[:notice] = ‘QuestionList was successfully created.’
redirect_to :action => ‘list’
else
render :action => ‘new’
end

end

def edit
@question_list = QuestionList.find(params[:id])
@experiments = Experiment.find(:all)

end

def update
@question_list = QuestionList.find(params[:id])

@question_list_conditions = params[:selected_condition]
for question_list_condition in @question_list_conditions
@question_list_condition_id = question_list_condition[0].to_i
@question_list_condition_show = question_list_condition[1].to_i
if @question_list_condition_show == 1
QuestionListCondition.add_by_condition_id_question_list_id(@question_list_condition_id,@question_list.id)
else
QuestionListCondition.destroy_by_condition_id_question_list_id(@question_list_condition_id,@question_list.id)
end
end

if @question_list.update_attributes(params[:question_list])
flash[:notice] = ‘QuestionList was successfully updated.’
redirect_to :action => ‘show’, :id => @question_list
else
render :action => ‘edit’
end
end</i>

and then the _form.rhtml partial:

<i><%= error_messages_for ‘question_list’ %>

<!–[form:question_list]–>
<p><label for="question_list_name">Name</label><br/>
<%= text_field ‘question_list’, ‘name’ %></p>

<p><label for="question_list_description">Description</label><br/>
<%= text_area ‘question_list’, ‘description’, :rows=>4 %></p>

<p><label for="question_list_conditions">Conditions</label><br />
<% for experiment in @experiments %>
<p><%= experiment.name %>
<% @conditions = Condition.find_conditions_array(experiment.id) %>
<% for condition in @conditions %>
<br /><%= check_box :selected_condition, condition.id %>
<%= condition.name %>
<% end %></p>
<% end %>

<!–[eoform:question_list]–>

Excellent. To finish up today, just edit the administrative question_list_condition view, as right now it’s blank because it’s looking for content_columns, of which it has none:

—-

We won’t do everything for this scaffolding, but we will fix this list and make some code more portable.

In experiment.rb, change find_name_by_id to

def self.find_name_by_id(selected_id)
found_object = find(selected_id)
@to_return = found_object.name
end

now, copy and paste the same code into question_list.rb, while also adding

validates_presence_of :name, :description

to the top

Last, change list.rhtml to:

<h1>Listing question_list_conditions</h1>

<table>
<tr>
<th>ID</th>
<th>Condition</th>
<th>Question List</th>
</tr>
<% for question_list_condition in @question_list_conditions %>
<tr>
<td><%= question_list_condition.id %></td>
<td><%= Condition.find_name_by_id(question_list_condition.condition_id) %></td>
<td><%= QuestionList.find_name_by_id(question_list_condition.question_list_id) %></td>
</tr>
<% end %>
</table>

<%= link_to ‘Previous page’, { :page => @question_list_condition_pages.current.previous } if @question_list_condition_pages.current.previous %>
<%= link_to ‘Next page’, { :page => @question_list_condition_pages.current.next } if @question_list_condition_pages.current.next %>

<br />

<%= link_to ‘New question_list_condition’, :action => ‘new’ %>

Tomorrow: adding questions!