Today’s task is to finish up the admin interface for Notes on Rails. If this post had a subtitle, it would be “Ooops!”
No more “columns” or “rows”
I accidentally used reversed names in naming some of the entities yesterday, and I found that out in mid-stream. So a good portion of the work today is undoing stuff previously done. Still, the work ends pretty well. The admin interface is functionally complete after today. Tomorrow should be working on student interface.
A lot of work was done yesterday, but the main goal today is to use the databse knowledge we added to help with administration. For instance, if there is a field in the condition table for the experiment it belongs to, why not populate the new-condition field with a list of experiments already in the database.
A lot of what’s happening here is from 6.4 “Prettier Listings,” which beings on page 85 of Agile Web Development with Rails, 2nd Edition.
However, first I need to change something from yesterday. I created all the foreign keys to be text fields, but I want them to be numeric fields. This is because the Name of an Experiment, Condition, etc, can change, but rails internally will hold an id that makes it unique.
In rals, all database work is done through migrations. So enter
ruby script/generate migration alter_foreign_keys
Then open db/migrate/006_alter_foreign_keys.rb. Recall code for altering tables is on (79). Alter 006_alter_foreign-keys.rb to read:
class AlterForeignKeys < ActiveRecord::Migration
def self.up
remove_column :conditions, :experiment_name
remove_column :rows, :condition_name
remove_column :columns, :condition_name
add_column :conditions , :experiment_id, :integer
add_column :rows , :condition_id, :integer
add_column :columns , :condition_id, :integer
end
def self.down
remove_column :conditions, :experiment_id
remove_column :rows, :condition_id
remove_column :columns, :condition_id
add_column :conditions , :experiment_name, :string
add_column :rows , :condition_name, :string
add_column :columns , :condition_name, :string
end
end
then rake db:migrate.
Unlike last time, when we generated a dynamic scaffold with “ruby script/generate model condition” this time create a static scaffold with (86)
ruby script/generate scaffold condition manage_conditions
and enter “y” when it asks you to overwrite the work done yesterday
Ruby on Rails’ MVC (Model, View, Controller) philosophy means that code is split up between model, view, and controller logic. The model we created yesterday is in app/models/condition.rb while the controller was just regenerated by our command and is 50 lines long. Ruby automatically split up the view into many sub-pages: for now, open for editing _form.rhtml.
Jumping ahead to 486 we see basic logic for working with selection lists /dropdown boxes. I had trouble understanding this bit at first, but railsforum helped out. Replace the single line in app/views/manage_conditions/_form.rhtml
<%=
@experiments = Experiment.find(:all,
rder => "name" ).map {|u| [u.name, u.id] }
select :condition, :experiment_id, @experiments
%>
While we’re at it, edit /app/models/condition.rb to contain the line
validates_presence_of :name, :description, :experiment_id
Next up, do a similar thing with the rows.
ruby script/generate scaffold
ruby script/generate scaffold row manage_rows
Open up app/views/manage_rows/_form.rhtml.
Add the following two lines:
<p><label for="row_condition_id">Condition ID</label><br/>
<%=
@conditions = Condition.find(:all,
rder => "name" ).map {|u| [u.name, u.id] }
select :row, :condition_id, @conditions
%></p>
Add the following line to app/models/row.rb (the Row Model)
validates_presence_of :name, :description, :condition_id
And then identical work for manage_columns
+ generate the static scaffold
+ add to _form.rhtml
+ add to Column Model
Can you get this to work? I can’t.
OOPS!
It looks like “Columns” is a special name in Ruby (or at least SQL).
(What an interesting system we’re coming up with!)
So let’s destroy columns and create a replacement called “Field.” Our method (from DreamInCode and wiki.rubyonrails.org is
ruby script/destroy scaffold column manage_columns
And as long as we’re renaming one thing, let’s rename the other too. Get rid of the row scaffold. The row concept only makes sense in relations to columns, and if we’re getting rid of one, just as well get rid of the other
ruby script/destroy scaffold row manage_rows
Now add code to drop them from the database
ruby script/generate migration drop_columns
ruby script/generate migration drop_rows
008_drop_rows.rb looks like:
class DropRows < ActiveRecord::Migration
def self.up
drop_table :rows
end
def self.down
create_table :rows do |t|
t.column :name, :string
t.column :description, :text
t.column :condition_id, :integer
end
end
end
and 007-drop_columns.rb is similar. Then rake db:migrate.
Now let’s see if we can create these objects correctly, as notes_record and notes_field.
ruby script/generate model notes_record
ruby script/generate model notes_field
Edit 010_create_notes_fields.rb and 009_create_notes_records.rb to add the following three lines where appropriate:
t.column :name, :string
t.column :description, :text
t.column :condition_id, :integer
Then rake db:migrate, of course.
Now for some housekeeping.
In app/models/condition.rb change has_many :rows | has_many :columns to has_many :notes_records | has_many :notes_fields
In the same folder, open up notes_field.rb so it reads
class NotesField < ActiveRecord::Base
validates_presence_of :name, :description, :condition_id
validates_uniqueness_of :name
belongs_to :condition
end
Now add the controllers
ruby script/generate controller manage_notes_records
ruby script/generate controller manage_notes_fields
Now create some static scaffolds
ruby script/generate scaffold notes_record manage_notes_records
ruby script/generate scaffold notes_field manage_notes_fields
Now we’ll drop recreate them as notes_record and notes_field.
Phew! Now back to adding in the drop-down list. Let’s start with app/views/manage_notes_fields/_form.rhtml
Add the following:
<p><label for="notes_field_condition_id">Condition ID</label><br/>
<%=
@conditions = Condition.find(:all,
rder => "name" ).map {|u| [u.name, u.id] }
select :notes_field, :condition_id, @conditions
%></p>
Then app/views/manage_notes_records/_form.rhtml
<p><label for="notes_record_condition_id">Condition ID</label><br/>
<%=
@conditions = Condition.find(:all,
rder => "name" ).map {|u| [u.name, u.id] }
select :notes_record, :condition_id, @conditions
%></p>
Nearly done. The “edit” functionality of the manage_ now works because it uses the same _form.rhtml logic. What we need to do, though, is to edit the show functionality. Edit this file so it looks like:
<p>
<br /><b>Name</b>: <%= @notes_record_name %>
<br /><b>Description</b>: <%= @notes_record_description %>
<br /><b>Condition</b>: <%= @notes_record_condition %>
</p>
<%= link_to ‘Edit’, :action => ‘edit’, :id => @notes_record %> |
<%= link_to ‘Back’, :action => ‘list’ %>
And in app/controllers/manage_notes_records_controller.rb, replace the def show module like so:
def show
@notes_record = NotesRecord.find(params[:id])
@notes_record_name = @notes_record['name']
@notes_record_description = @notes_record['description']
condition = Condition.find(@notes_record['condition_id'])
@notes_record_condition = condition.name
end
Now do the same thing with manage_notes_fields_controller.rb and its related show.rhtml view. (We won’t mess with generalizing this over to the list command, though we could do that in a pretty straightforward manner.)
We’re on a role, so let’s add this same basic logic to manage_conditions.rb and its show.
def show
@condition = Condition.find(params[:id])
@condition_name = @condition['name']
@condition_description = @condition['description']
experiment = Experiment.find(@condition['experiment_id'])
@condition_condition = experiment.name
end
And the show.rhtml for the condition view is
<p>
<br /><b>Name</b>: <%= @condition_name %>
<br /><b>Description</b>: <%= @condition_description %>
<br /><b>Condition</b>: <%= @condition_condition %>
</p>
<%= link_to ‘Edit’, :action => ‘edit’, :id => @condition %> |
<%= link_to ‘Back’, :action => ‘list’ %>
You should now have four locations at localhost:3000/ where you can add and edit information. The show module in manage_conditions_controller.rb is now:
- manage_experiments : still using the dynamic scaffolding
- manage_conditions : now using the dynamic scaffolding
- manage_note_records: now using the dyanamic scaffolding
- manage_note_fields: now using the dynamic scaffolding
Try them!