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