Notice: These are personal notes for a course at my university on Web Development. I do not own any of this intellectual property, this is only used for personal education.
Table of Contents:
Lecture 7
- Outline:
- Deep Dive ActiveRecords
Web Framekwork Pipeline:
- Routes -> Parsing -> Controller -> View
- Step 1: define routes inside
config/routes.rb
- Step 2:create the
controller
file, with class and methods- has access to
params
hash params hash + definition:- params hash is the collection of data that comes through with the http request
- forms, get request parameters, urls, gets data into the params hash
- controller methods provide data
- has access to
- Step 3: Create the view folder and file
- differences between
<%= >
and<% %>
- TODO - when you find out difference )i think one is embedded html
- differences between
- Step 1: define routes inside
The CRUD Pipeline:
- There is a dual representation of the site/data: Ruby vs. Database
- Models are ruby representation of databse records
- children classes of
ActiveRecord::Base
- methods inhereted from
ActiveRecord::Base
- ActiveRecord::Base
- provides CRUD API - TODO- what is the CRUD API?
- children classes of
- Migrations modify database schema
- stored in
db/migrate
- looks like this
- looks like this for PenninTouch clone
- creates tables
- adds and removes columns
- generates/updates a schema.rb file,
- type
rails db:migrate
to start, can only be done once
- stored in
Generating a Model
- To generate a Model, run in root folder:
rails g model ModelName attr_name:attr_type
- ex:
rails g model Post tilte:string body:text
- to destroy,
rails destroy
Generators for Migration
- migration generators crete migration files which alter database record
CreateXXXs
creates a table with table nameXXX
and specified columnsAddXXXToYYYs
adds specified columns to tableYYY
RemoveXXXFromYYs
will remove specified columns fromYYY
table- ex1:
rails g migration CreatAuthors name:string bio:text
- makes table with Authors, which has a name and bio column
- ex2:
rails g migration AddCategoryToPosts category:string
- adds Category column to Posts table
Commonly Used Data Types for models
- string
- text
- integer, float, decimal
- datetime, timestamp, time, date
- boolean
- references
- creates reference to another table
Arel : Complex Query Methods for models
- SQL is powerful
- ActiveRecord ORM is Ruby’s method of writing SQL
- ActiveRecord is built on top of Arel
- Arel = Relational Algebra
- SQL Abstract Syntax Tree manager (AST)
Common Arels (Relational Algebra)
User.where(name: 'Patrick')
#=> sends out query for all users names Patrick
User.find_by(name: 'Patrick')
#=> sends out query for ONE user named Patrick
User.limit(2)
#=> returns first two users, the order is ascending by id by default- first to last
User.order(id :desc)
#=> returns all users last to first
- you can also chain commands, the order matters!:
Post.where(view_count: >20).limit(5).order(id :desc) #=> select from post table #=> where view_count is greater than 20 #=> 5 users #=> in order from last to first
Validations: “Check before you commit!
- what are the differences between Client-side and Server-side validations?
- validations guard against invalid data commiting to database
- ex: blank fields, incoreect format, making sure there isnt another identical entry (uniqueness)
- Client-side validation : enforced by client program
- server isn’t sure who client is
- what if client is malicious? (a hack trying to commit to db)?
- Server-side val done by Rails is useful because it is invariant with respect to the client
- validation code in model files:
- you need to write it inside the model file
- validations guard against invalid data commiting to database
Simple Validations
- validations run right before save to db happens
validates :column_name, criteria
- ex1:
presence
: field empty? - ex2:
uniqueness
: is this value unique- is there another like it in db - ex3:
length
: content length - note: these validations work only because each model has an errors method, when something doesnt pass a validation it gets added to the error method of the model.
#=> simple validations inside Post model file
class Post < ActiveRecord:Base
validates :name, prescence: true
validates :name, uniqueness: true
validates :content, lenth: {minimum: 5}
end
Errors Method
- every model instance has
errors
method - if model did not pass a validation,
error
object added to model- model with non-empty (there’s something in it!)
errors
field is invalid to the database - validations decide whether to add an
error
object to theerrors
field
- model with non-empty (there’s something in it!)
- view uses
errors
method and displays theerror.full_messages
for eacherror
- note: when you create a model make sure that it saves successfully! if it doean’t, it encountered some invalid data.
- this method below makes sure to describe the error message(in
form_html_erb
for hw2):
<% if @user.errors.any?%>
<div id= "error_explanation">
<h2>
<%=@user.errors.count%> errors prohibited this from...
</h2>
<ul>
<& @user.errors.full_messages.each do |msg|%>
<li><%= msg &></li>
<% end %>
</ul>
<% end %>
Defining Custom Validations
- Step 1: Define a method in the model file with this logic:
- Step 1a: Check if the desired validation passes
- Step 1b: if it doesn’t pass,
- add the column name as a symbol
- add the error message to the errors object
- Step 2: Pass the method as a symbol to
validate
ex:#=> example of a custom validation in User model file class User < ActiveRecord::Base validate :penn_email def penn_email unless email.include? 'upenn.edu' errors.add(:email, 'it is not a Penn email') end end end
Association: Lvl 1- One to Many
- One to Many Association: when one model_A may logically have many model_B’s, but the reverse is not true:
- ex: a
Post
has manyComments
, but not the other way around - ex: a
Book
has manyChapters
, but not the other way around
- ex: a
- counterexamples:
- ex: a
Teacher
has manyStudents
, but the reverse is also true - ex: a
Shop
has manyCustomers
, but the reverse is also true
- ex: a
Implementation in Database
- Step 1: Initialize
Posts
table andComments
table - Step 2: Declare the association:
Comments
table has columnn that says whichPost
record theComment
redord belongs to- foreign key: reference to primary key in different table
- ex: the column in
Comments
that connects the post record and comment record
- ex: the column in
- foreign key: reference to primary key in different table
- new CRUD available:
- create a comment for a post
- find all comments belonging to a post
- find the post a comment belongs to
Dual Implementation in Ruby/Rails
- this is where associations implementations take place, abstracted away by ruby.
- through ActiveRecord ORM
- Step 1: use generator for
Post
model (in previous ex) - Step 2: use generator for
Comments
model- add
post:references
as a column (needs to be plural)
- add
- Step 3:
rails db:migrate
- Step 4: in
Comment.rb
model file, addbelong_to :user
- Step 5: in
Post.rb
model file under class declaration addhas_many :comments, dependent: destroy
- why add
dependent: destroy
? - because when you destroy the post you will destroy all the comments associated with that post.
- why add
- Step 1: use generator for
- new CRUD available:
@post = Post.find(1)
#=>get post with id 1
@new_comment = @post.comments.build
#=>equiv to post.new?
@new_comment.body = 'Comment for post 1'
@new_comment.save
#=>alternative comment creation:
@new_comment = @post.comments.create({body: 'Comment for post 1'})
#=>find all comments belonging to a post:
@post = Post.find(1)
@comments = @post.comments
#=>find the post that a comment belongs to:
@comment = Comment.find(20)
@post = @comment.post
# recall that :
@post.comment.post == @post
Association Lvl2: Many to Many
- happens when two models can be associated with multiple instances of each other
Students
andCourses
Houses
andOwners
- A model can have many to many association with iteself!:
Users
can have manyUsers
as friends
Wrong Implementation in Database:
- Add foreign keys to both tables
- you run into a duplicate primary key issue
- you can fix with RID (ref id?), but still redundant !
Correct Implementation in Database
- Use three tables, the third one is to keep records
- notice
course_id
is foreign key andstudent_id
is reference?
Implementation of Many to Many in Ruby/Rails
- Step 1: generate
Course
andStudent
model migration like above examples- no
reference
column this time!
- no
- Step 2:
rails g Model Registration Course:references Student:references
- Step 3: in
Course.rb
model file, writehas_many :registrations, dependent: :destroy
#why do we write this?has_many :students, through: registrations
- Step 4: do the same for the
Student.rb
model file - Step 5: and in the
Registration.rb
model file (join table)belongs_to :student
belongs_to :course
Forms: Making POST Requests
<form>
s are html elements that make POST requests for a bundle of data- Rails erb use
form_with
to create the HTML forms: make sure to include:- destination url
- whether the data is associated with a model
- field names
- field input type
- display name of fields
- (field name, value) become key-val pairs
- URL vs Model (form_with):
form_with
with a url becomes url params:
<%= form_with url: '/login' do |form| &>
<%= form.label :email %>
<%= form.text_field :email %>
<%= form.submit%>
<& end &>
form_with
with a model becomes body params:
<%= form_with model: @user do |form| &>
<%= form.label :email %>
<%= form.text_field :email %>
<%= form.submit%>
<& end &>
- why bother making the distinction?
- body params need to be
permitted
to prevent mass assignment attacks. - url params do not need to
permitted
.
- body params need to be
form_with_model
are database critical- always check what data the form is sending! need to whiteliest certain fields
- whitelisting stops bad data from being commited to the databse.
params.require(:post).permit(:title, :content)
- always check what data the form is sending! need to whiteliest certain fields
Designing Forms: Log In Form
- making a login form, what do I need?
- S1: need user email + user password
- this decides field name, type, and label.
- S2: is the form associated with a model?
- if yes: use
form_with model
- if not: use
form_with url
- if yes: use
- what end point does this form
POST
to?- this posts to
/login
with the data:
- this posts to
- S1: need user email + user password
{
email: '[email_field_value]'
password: '[password_field_value]'
}
TODO: FINISH LEC 7
Lecture 8
- Demo Lecture.
- Outline:
- Scaffold Generator
- Arel
- Validation
- One to Many
- Many to Many
Scaffold Generator:
- Basucally a buffed up model generator.
- What does the scaffold generator generate?
- model/migration file
- controller file + its implementation
- entire view folder - all erbs are there!
- all RESTful routes
- wow this is a strong boy
- so purpose of HW2 was to practice routing, all of it can be done with this one line
- there are differences:
- (1) instead of explicitly defining routes in
routes.rb
, only writesresources: posts
, which are all CRUD resources/ seven RESTful CRUD routes. - (2) usage of before action in
posts_controller.rb
.- it just says, before running any method of
PostsController
, it will runset_post
to find the post in question.
- it just says, before running any method of
- (3) usage of named routes/ route helper methods
- seen in
posts_controller.rb
, alias for RESTful routes used. - also in
inex.html.erb
, there are link-to’s that are sent to the alias of the RESTful route.- ex: instead of
link_to ... "/posts/#{post.id}/edit"
, it useslink_to 'Edit', edit_post_path(post)
- ex: instead of
- seen in
- How to use? check it out:
#=> to generate:
rails g scaffold Post title:string content:text
#=> to destroy:
rails destroy scaffold Post
Scaffold Generator Demo
- S1: navigate to your desired dir and rails new a new project
rails 5.2.4 new scaffold_blog
- cd into
scaffold_blog
and runrails s -b 0.0.0.0
to boot server,localhost:3000
is up. - S2: use scaffold generator by
rails g scaffold Post title:string content:text
- S3: Run
rails db:migrate
- S4: cd into
scaffold_blog
and runrails s -b 0.0.0.0
to boot server,localhost:3000/posts
is up.
Arel Demo
- TODO
Validations Demo
- here we generated a new project and scaffold generated
rails g scaffold User first_name:string last_name:string email:string
- in the model file,
- makes sure
first_name, last_name
not blank: validates :first_name, :last_name, presence: true
- makes sure length of email is valid, it’s unique, and it exists:
validates :email, presence: true, uniqueness: true, ;length: {minimum: 5}
- makes sure
- in the model file, let’s make custom validations:
- begin with
private
keyword, see code: - use
validate
keyword for custom validations,validates
for simple ones. - after you are done, boot up the server and test to make sure your validations are working.
- run
rails db:migrate
to apply changes. - play around to make sure error messages get added with invalid input
- run
- begin with
Class User < ApplicationController
validates: first_name, :last_name, :presence: true
validates :email, presence: true, uniqueness: true, length: {minimum: 5}
validate :first_name_capitalized, :last_name_capitalized
#=> custom validations:
private
def first_name_capitalized
errors.add(:first_name, "must be capitalized") if first_name && first_name.capitalize!
#=> first_name is boolean as to whether its there or blank
#=> first_name.capitalize is boolean true if capitalized
end
def last_name_capitalized
errors.add(:last_name, "must be capitalized") if last_name && last_name.capitalize!
end
end
Associations Demo
- One to Many Association Sub-Demo:
- Here I scaffold generate a Post model and Comment model, and establish the association that a Post has many Comments, but a Comment does not have many Posts.
- as always, generate your
Post
modelrails g scafold Post title:string content:text
- the one model is agnostic of the existence of many model.
- however, when you generate comment, make sure to establish
post:references
to have a post column of type reference, since there are many comments to one post. rails g scaffold Comment content:string post:references
- however, when you generate comment, make sure to establish
- but you are not done yet, need to edit model files.
- S1: nav to
post.rb
class Post < ApplicationRecord has_many :comments, dependent:destroy #=> dep destroy destroys all associated comments when the post is deleted end
- S2: nav to
comment.rb
, make sure there is an appropriatebelong_to
- S1: nav to
- S2: Test your associations in
rails c
rails c
- create a new post
@post = Post.create(title : "hello", content: "first post!")
- create a comment associated with the post:
- way 1:
@comment = Comment.new
@comment.content = "great writing"
- how to associate ?
- basic way :
@comment.post_id = @post.id
- preffered rails method:
@comment.post = @post
- what’s happening under the hood?
- the
.post
method is not a method ofComment
model file. - but since we declared in scaffold generation, and verified in the model file, that we have a comment
belongs_to
, we can use that method.
- preffered rails method:
- check that
@comment.save
works.
- way 2:
@comment = @post.comments.build
- this is essentially the same as
@comment = Comment.new
except by default it is providing a post id for the current@post
- then just set content
@comment.content = "second comment"
@comment.save
should be true- now check save by looking at
@post.comments
- check contents of comment by
@comment.post
- this is essentially the same as
- way 3: how to do everything in one line?
@comment3 = Comment.create({content: "comment3", post:@post})
- check by
@post.comments
- the blog with posts and comments as we’ve built it currently requires nested resources to fully work - the routes in
routes.rb
are incorrect.- comments routes should be nested under posts routes.
- also in the controller, comments controller needs to find its post first
- should be something like:
def create
@post = Post.find(...)
@comment = Comment.new(...)
end
- Many to Many Association Sub-Demo
- nav to your directory, generate your models with appropriate generators.
- create
Students
andCourses
models agnostic of each other, then create aRegistrations
object that connects them by holding references between them. rails g scaffold Course title:string desc:test code:integer
rails g scaffold Student first_name:string last_name
- default type is string, you can omit (nice).
rails g scaffold Registrations course:references student:references
rails db:migrate
- create
- now further declare the associations between
STudent
andCourse
through the model files usingRegistrations
.- S1: in
student.rb
:has_many :registrations dependent: :destroy
has_many :courses, through: :registration
- S2: in
courses.rb
has_many :registrations dependent: :destroy
has_many :students, through: :registration
- S1: in
- how do you test this? boot up
rails c
- create a course
@course = Course.create({title: "Ruby course", desc: "course on web dev", code:196})
- create a student
@student = Student.create({first_name: "Patrick", last_name: "Puppy"})
- shovel that
student
into the students array ofcourse
@course.students << @student
- test
@course.students
to see if Patrick is there. - test
@student.courses
to see if the course is there.
- create a course
Lecture 9
- Outline:
- Route Helpers
- Resources
- Nested Resources
- Form/FOrm Helpers
- Skipped ahead a bunch make sure to finish the lecture
How Sessions Work
- HTTP requests are stateless
- the server doesnt memorize the historic request information, not enough space.
- note: clients == web browser, user == a user record in the database
- so sessions associate a client and a user instance.
- you can usually think of a clinet as a person, since people tend to use their personal browser.
- What are Sessions?
- Sessions are hash data structures the represent all the request info.
- who is the user
- what time was the request made, etc.
- server can only hold limited amount of sessions
- sessions stored in
session_store
- the key for a session is a
session_id
- cookies pass in a
session_id
- cookies stored by the user browser.
- cookies pass in a
- Sessions are hash data structures the represent all the request info.
- Coffeeshop Analogy:
- you order a lattee from a shop
- two days later, you come back and say you want to order the last item you ordered
- …so what was it???
- with sessions:
- order a latte from a coffe shop
- shop keeps
uid: [last_order: latte]
on a peice of paper, and gives you theuid
- two days later, come back with the
uid
and say you want to order the last thing you ordered.