CS312 - Spring 2012 - Class 11
administration
- don't forget to take the quiz by Friday at 6pm
quick recap from last time
- building a student class roster application
- generated scaffolding for students
- generated a model, view and controller for us
- setup basic routing for a viewing, editing and deleting students
- added in the model/database for the assignments
- at the end, generated the controller/view for the assignment
now that we have assignments, let's add a form to our student show page allowing use to add scores
- add the following to our app/views/students/show.html.erb
<%= form_for([@student, @student.assignments.build]) do |f| %>
<div class="field">
<%= f.label :assignment_number %><br />
<%= f.text_field :assignment_number %>
</div>
<div class="field">
<%= f.label :score %><br />
<%= f.text_field :score %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
- the form_for function creates a form for us (we've seen this method before)
- @student is the student (i.e. an model object, inheriting form ActiveRecord)
- @student.assignments gets all of the assignments from the student
- @student.assignments.build returns a new assignments object so that we can instantiate the table
- the other components setup the different parts of the form
- if we now look at a student (i.e. visit show.html.erb), we see there is a form for entering data
handling the submit/post request
- right now, if we tried to submit, we'd get an error since we haven't setup anything in our controller to handle this
- recall form last time that a POST request gets automatically routed for a resource to the "create" method inside that controller
- notice when we try it rails complains
- let's add a create method then in AssignmentController
def create
@student = Student.find(params[:student_id])
@new_score = @student.assign:w
ments.create(params[:assignment])
redirect_to student_path(@student)
end
- using the Student model, find the student
- because we declared that a student has_many assignments, each student has a .assignments method which
- gives us an ActiveRecord (i.e. model object)
- allows us to create a new assignment associated with the student
- finally, after adding the student, we will send the user back to the student_path
let's make the assignments show up when we display a student
- we can add assignments, but they don't show up anywhere
- let's add them so that when we view a student, we see their scores
- again, edit app/views/students/show.html.erb
<table border="1">
<tr>
<th>Name:</th>
<% @student.assignments.each do |assignment| %>
<th><%= assignment.assignment_number %></th>
<% end %>
</tr>
<tr>
<td><%= @student.name %></td>
<% @student.assignments.each do |assignment| %>
<td><%= assignment.score %></td>
<% end %>
</tr>
</table>
- we're going to build a table with the student's name and the assignment scores
- the first loop creates all of the table headers
- notice again we make use of the @stuent.assignments call to get all the assignments associated with a student
- and then traverse over them with "each"
- the second loop then looks over the assignments and puts the scores in
summary page
- right now we can only view one student's grades at a time
- let's create a summary page
- to do this, we need to create another controller (i.e. view/controller pair)
rails generate controller Grades index
- we'll call it index since this is the default action for displaying the contents of a resource (even though this isn't a resource)
- now let's edit our controller method
def index
@students = Student.order(:name)
@assignments = Assignment.find(:all, :select => 'distinct assignment_number')
end
- this grabs all of the students and orders them by :name (i.e. their names)
- we also want to grab all of the assignment numbers so that we can display the correct number of headers
- the find command tries to find data in the database
- the :select symbol allows us to issue a select query
- now let's edit our view to display all of the students
- to do this, we'd be copying a lot of html from our individual student portion
- code reuse!
- let's make a partial
- a partial is a .html.erb file that you can call within another .html.erb file and it will render within the original .html.erb file
- code reuse! :)
- we're going to make a "student" partial
- edit views/students/_student.html.erb
- copy and paste the code for rendering the student from show.html.erb
<tr>
<td><%= student.name %></td>
<% student.assignments.each do |assignment| %>
<td><%= assignment.score %></td>
<% end %>
</tr>
- we can then replace where this was in show.html.erb as
<%= render @student %>
- now, we can render a student
- now, we're ready to edit our grades view
- edit app/views/grades
<h1>Student grades</h1>
<table border="1">
<tr>
<th>Name</th>
<% @assignments.each do |assignment| %>
<th><%= assignment.assignment_number %></th>
<% end %>
</tr>
- this creates the header from our @assignments variable
<% @students.each do |student| %>
<%= render student %>
<% end %>
</table>
- this then uses the partial to render the student
other things we might want to do:
- let's change it so that we can just go to
http://localhost:3000/grades/
- in config/routes.rb we can tell it to add this route
match "/grades/" => "grades#index"
- technically, our partial isn't correct, since it assumes that all students have each grade
- we could either fix this in the view or fix this in the model
- we won't do that now for time
validating data
- right now, we can add any arbitrary values into the form fields and it will allow it
- let's fix this for students
- we can add "validates" lines to validate attributes in the model files
- for example, we can add the following for app/models/student.rb
validates :name, presence: true
validates :name, uniqueness: true
validates :name, :length => { :minimum => 4 }
validates :name, :format => { :with => /^[A-Z][a-z]* [A-Z][a-z]*$/}
- first checks to make sure that a name is entered
- 2nd checks to make sure that the name is unique
- 3rd that is at least 4 characters
- 4th that is matches that regular expression
- for a list of all the possible validations see:
http://guides.rubyonrails.org/active_record_validations_callbacks.html
checking validations
- we can check to see if using the rails console
rails console
>> s = Student.new
=> #<Student id: nil, name: nil, created_at: nil, updated_at: nil>
>> s.valid?
(0.1ms) SELECT 1 FROM "students" WHERE "students"."name" = '' LIMIT 1
=> false
>> s.errors
=> #<ActiveModel::Errors:0x000001033cb9b0 @base=#<Student id: nil, name: nil, created_at: nil, updated_at: nil>, @messages={:name=>["can't be blank"]}>
>> s = Student.new(:name => "Big Dog")
=> #<Student id: nil, name: "Big Dog", created_at: nil, updated_at: nil>
>> s.valid?
(0.2ms) SELECT 1 FROM "students" WHERE "students"."name" = 'Big Dog' LIMIT 1
=> true
>> s = Student.new(:name => "Bobby Z")
=> #<Student id: nil, name: "Bobby Z", created_at: nil, updated_at: nil>
>> s.valid?
(0.3ms) SELECT 1 FROM "students" WHERE "students"."name" = 'Bobby Z' LIMIT 1
=> false
>> s.errors
=> #<ActiveModel::Errors:0x000001032d2400 @base=#<Student id: nil, name: "Bobby Z", created_at: nil, updated_at: nil>, @messages={:name=>["has already been taken"]}>
notice now that if we try to add a student now that doesn't meet the specifications, we get an error and it tells us what the error is
writing unit test
- edit test/unit/student.rb
test "students can't be empty" do
s = Student.new
assert s.invalid?
end
test "students can't be lowercased" do
s = Student.new(:name => "dave kauchak")
assert s.invalid?
end
- we can then run this test using
rake test:units