Rapid Application Development
COSC2675 2021 Week 8
A/Prof. Andy Song andy.song@rmit.edu.au
Before Week 7
• REST
• RESTfulresources(withdifferentformats)
• RESTfulmethods
• Multiple tables
• Association methods
• Dependent tables
A/Prof. Andy Song RMIT University 2021
2
Many-to-Many
• What about the relationship between students and courses?
• Students and courses do not belong to each other.
• Howevertherealotofconnectionsbetweenthem.
• Firstly we need deal with the models.
• Then update the controllers.
• Then the views.
• Railsprovidesaverygoodfoundation.
• But we still need to add a lot.
• Warning: don’t name a table “classes” otherwise you might experience many Rails disasters because of name conflicts.
A/Prof. Andy Song RMIT University 2021
3
Create Tables
• Creating a course table is not enough. We need additional table that joins courses and students.
$ rails generate scaffold course name:string
• Thejointablerequiresafewtables.Firstlycreateamigration $ rails generate migration CreateCoursesStudents
It created a file
db/migrate/[time_stamp]_create_courses_students.rb
class CreateCoursesStudents < ActiveRecord::Migration[5.2] def change
create_table :courses_students do |t|
end
end
A/Prof. Andy Song RMIT University 2021
4
Coding the migration
• Wehavereachedtheboundariesofcodeauto-generation.
• Need to write the migration
class CreateCoursesStudents < ActiveRecord:: Migration
def change
create_table :courses_students, id: false do |t|
t.integer :course_id, null: false
t.integer :student_id, null: false
end end
end
• Rails naming conventions is the key here.
• Thetablenameisthecombinationoftwojoiningmodelsin
alphabetical order.
• Thefieldswithinthetableareidvaluesforeachofthemodels
A/Prof. Andy Song RMIT University 2021
5
Coding the migration...
• create_table method is a ActiveRecord method. See API http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatem
•
ents.html#method-i-create_table
create_table(table_name, comment: nil, **options) create_table(:books) do |t|
t.column :name, :string, limit: 80 end
create_table(:books) do |t| # with shorthand t.string :name, limit: 80
end
create_table(:books)
add_column(:books, :name, :string, {limit: 80})
The option hash include keys like :id, :primary_key, :temporary, :force, :as
:id Whether to automatically add a primary key column. Defaults to true. Join tables for ActiveRecord::Base.has_and_belongs_to_many should set it to false.
•
A/Prof. Andy Song RMIT University 2021
6
Index in join table
• Railsuseidvaluefortablestoimproveperformance,sodatacan be processed rapidly.
• The id value is automatically indexed.
• To use your own index, you can add this method in migration:
add_index :courses_students, [:course_id, :student_id], unique: true
• add_index method’s API documentation can be read here:
http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatem ents.html#method-i-add_index
add_index(table_name, column_name, options = {})
• Now run the migration $ rails db:migrate
A/Prof. Andy Song RMIT University 2021
7
Connecting the Models
• Add the many-to-many relationship to app/models/student.rb has_and_belongs_to_many :courses
• Add the many-to-many relationship to app/models/course.rb has_and_belongs_to_many :students
• That’sallforestablishingtheconnection
• Thanks to Rails naming conventions.
• Nowtablecourses_studentscankeeptrackoftheconnections.
• Note: has_and_belongs_to_many can be controversial; some developers may prefer has_many :through relationship, by creating an intermediate table.
A/Prof. Andy Song RMIT University 2021
8
Connecting the Models ...
(Learning Rails 5, Mark Locklear Eric Gruber)
A/Prof. Andy Song RMIT University 2021
9
Methods which may be useful
• app/models/student.rb to check whether a student is enrolled?
def enrolled_in?(course)
self.courses.include?(course)
end
def unenrolled_courses
Course.all - self.courses
end
• Remember the minus – operator operating on arrays?
A/Prof. Andy Song RMIT University 2021
10
Controllers
• Controllersformany-to-manyaresimplerthannestedresources. However we may need some supporting methods.
• app/controllers/courses_controller.rb to add after destroy
# GET /courses/1/roll
def roll
@course = Course.find(params[:id])
end # What is this for?
• app/controllers/students_controller.rb
# GET /students/1/courses
def courses
@student = Student.find(params[:id])
@courses = @student.courses
end
A/Prof. Andy Song RMIT University 2021
# What is this for?
11
Add course method
# POST /students/1/course_add?course_id=2
def course_add
#Convert ids from routing to objects
@student = Student.find(params[:id])
@course = Course.find(params[:course])
unless @student.enrolled_in?(@course)
#add course to list using << operator
@student.courses << @course
flash[:notice] = 'Student was successfully enrolled'
else
flash[:error] = 'Student was already enrolled'
end
redirect_to action: "courses", id: @student
end
A/Prof. Andy Song RMIT University 2021
12
Remove course method
def course_remove
#Convert ids from routing to object
@student = Student.find(params[:id])
#get list of courses to remove from query string
course_ids = params[:courses]
if course_ids.any?
course_ids.each do |course_id|
course = Course.find(course_id)
if @student.enrolled_in?(course)
logger.info "Removing student from course #{course.id}" @student.courses.delete(course)
flash[:notice] = 'Course was successfully deleted'
end end
end
redirect_to action: "courses", id: @student
end
13
A/Prof. Andy Song RMIT University 2021
Routing
Update the routing: config/routes.rb resources :courses
resources :students
resources :courses do
member do
get :roll end
end
resources :students do
member do
get :courses
post :course_add
post :course_remove
end end
A/Prof. Andy Song RMIT University 2021
14
Now the Views
A/Prof. Andy Song RMIT University 2021
15
Making the Views
• Create app/views/application/_navigation.html.erb
• Write the navigation partial
<%= link_to "Students", students_path %> |
<%= link_to "Courses", courses_path %>
• Add this partial to app/views/layouts/application.html.erb
<%= render 'navigation' %>
<%= yield %>
• Student views
Add course to student list app/views/students/index.html.erb
Table head, insert before Awards
Table body, insert before Awards
16
A/Prof. Andy Song RMIT University 2021
•
•
More work on the views…
Course view app/views/courses/index.html.erb Table head, insert after (course) name
Table body, insert after course name
Students show app/views/students/show.html.erb
Add the course enrollment info
Courses: <% if !@student.courses.empty? %>
<%= @student.courses.collect {|c|
link_to( c.name, c)}.join(",").html_safe %>
<% else %>
Not enrolled on any courses yet.
<% end %>
Insert another link in between Edit and Back
<%= link_to 'Courses', courses_student_path(@student) %> |
A/Prof. Andy Song RMIT University 2021
17
Courses in students views
• Create app/views/students/courses.html.erb
<%= @student.name %>‘s courses
<% if @courses.length > 0 %>
<%= form_tag( course_remove_student_path(@student)) do %>
Course | Remove? |
---|---|
<%= course.name %> | <%= check_box_tag "courses[]", course.id %> |
18
Courses in students views…
• In app/views/students/courses.html.erb
<%= submit_tag 'Remove checked courses' %> <% end %>
<% else %>
Not enrolled in any courses yet.
<% end %>
Enroll in new course
<% if @student.courses.count < Course.count then %>
<%= form_tag( course_add_student_path(@student)) do %> <%= select_tag(:course,
options_from_collection_for_select (@student.unenrolled_courses, :id, :name)) %>
<%= submit_tag 'Enroll' %> <% end %>
<% else %>
<%= @student.name %> is enrolled in every course.
<% end %>
<%= link_to 'Back', @student %>
A/Prof. Andy Song RMIT University 2021
19
• •
Courses views…
In app/views/courses/show.html.erb Insert another link in between Edit and Back
<%= link_to 'Roll', roll_course_path(@course) %> |
Create app/views/courses/roll.html.erb
Roll for <%= @course.name %>
<% if @course.students.count > 0 %>
Student | GP |
---|---|
<%= link_to student.name, student %> | <%= student.grade_point_average %> |
No students are enrolled.
<% end %>
A/Prof. Andy Song RMIT University 2021
20
Viewing the Roll
• Localhost:3000/courses/[x]/roll
A/Prof. Andy Song RMIT University 2021
21
Nested Resources
• Students and awards aren’t really parallel, not like students and courses. That dependency can be better modeled.
• Manually changeconfig/routes.rb resources :awards
resources :students
resources :students do
resources :awards
end
• The students page http://…../students is OK. However the
awards page http://…../awards is no longer working.
• Accessing the awards page needs to go through students like
http://…../students/1/awards/2
A/Prof. Andy Song RMIT University 2021
22
Implementing nested awards
• Changing the controller is a bit complicated.
• Mainly to add student as context in AwardsController.
before_action :set_award, only: [:show, :edit, :update, :destroy]
before_action :get_student
before_action :set_award, only: [:show, :edit, :update, :destroy]
Method index: Method new:
Method create:
@awards = Award.all
@awards = @student.awards @award = Award.new(award_params)
@award = @student.awards.new
@award = Award.new(award_params)
@award = @student.awards.new(award_params)
format.html { redirect_to @award, notice: ‘Award..’} format.html { redirect_to student_awards_url(@student),
A/Prof. Andy Song RMIT University 2021
23
Implementing nest awards…
Method update: if @award.update(award_params)
if @award = @student.awards.update( award_params)
format.html { redirect_to @award, notice: ‘Award..’} format.html { redirect_to student_awards_url(@student),
Method destory: format.html { redirect_to @award, notice: ‘Award..’} format.html { redirect_to student_awards_url(@student),
Private methods:
Method set_award: @award = Award.find(params[:id])
@award = @student.awards.find(params[:id]) Add method get_student
def get_student
@student = Student.find(params[:student_id])
end
A/Prof. Andy Song RMIT University 2021
24
•
Change the views for awards
Update app/views/awards/index.html.erb
Awards
Awards for <%= @student.name %>
<% if !@student.awards.empty? %>
<%# the whole table body here .... then the else %>
<% else %>
<%= @student.given_name %> has no awards yet.
<% end %>
Inside the table:
A/Prof. Andy Song RMIT University 2021
•
25
Change the views for awards …
• Inside the table in app/views/awards/index.html.erb
<%= link_to 'New Award', new_student_award_path(@student) %> | <%= link_to 'Back', @student %>
26
More views to change (show)
• Also need to change app/views/awards/show.html.erb <%= link_to 'Edit', edit_award_path(@award) %>
| <%= link_to 'Back', awards_path %>
<%= link_to 'Edit', edit_student_award_path(@student, @award) %> | <%= link_to 'Back', student_awards_path(@student) %>
A/Prof. Andy Song RMIT University 2021
27
•
More views to change (new)
Also need to change app/views/awards/new.html.erb
New Award
<%= render 'form', award: @award %>
<%= link_to 'Back', awards_path %>
New Award for <%= @student.name %>
<%= render 'form', award: @award %>
<%= link_to 'Back', student_awards_path(@student) %>
• Changethefirstlineofpartialapp/views/awards/_form.html.erb <%= form_with(model: award, ...) do |form| %>
<%= form_with(model: [@student, award], ...) do |form| %> • Deletethestudentselectorfromtheform
A/Prof. Andy Song RMIT University 2021
28
More views to change (new) …
• Create is viewable at https://…students/[x]/awards/new
A/Prof. Andy Song RMIT University 2021
29
More views to change (edit)
• Change app/views/awards/edit.html.erb
Editing Award
Editing Award for <%= @student.name %>
<%= link_to 'Show', @award %> | <%= link_to 'Back', awards_path %>
<%= link_to 'Show', [@student, @award] %> |
<%= link_to 'Back', student_awards_path(@student) %>
A/Prof. Andy Song RMIT University 2021
30
Connecting the student views
• Change app/views/students/show.html.erb
<%= link_to 'Edit', edit_student_path(@student) %> |
<%= link_to 'Back', students_path %>
• <%= link_to 'Edit', edit_student_path(@student) %> | <%= link_to 'Awards', student_awards_path(@student) %> | <%= link_to 'Back', students_path %>
A/Prof. Andy Song RMIT University 2021
31
• •
Connecting the student views…
Change app/views/students/index.html.erb
Add behind the edit link
A/Prof. Andy Song RMIT University 2021
32
Is Nesting Worth It?
• Itwasalotofwork!!
• However it is the “right” approach in Rails.
• It makes the has_many/belongs_to relationship explicit on every level, not just in the model
• Routing and controllers are updated so both the web interface and the RESTful web services interface work in nested way.
• Whether to use nesting depends on the application. For the award example, general users may want easy navigation so nesting is better. For an admin, direct interface might be better.
• Ifyouaregoingtonestresources,doitearly.
• Nestedresourcesmayrequireadditionalinterfaces. A/Prof. Andy Song RMIT University 2021
33
Summary
• Many-to-many
• Join tables
• Many-to-many controllers
• Many-to-many views
• Nested resources
A/Prof. Andy Song RMIT University 2020
34