Rapid Application Development
COSC2675 2021 Week 10
A/Prof. Andy Song andy.song@rmit.edu.au
Last Week
• Debugging
• Logging
• Working with the console
• Debugger
• Rails three modes
• Fixtures
• Model testing
• Controller testing
• Assertion methods
• Integration testing
A/Prof Andy Song RMIT University 2021
2
MVC
• Model-View-Controller,asoftwarearchitecturewhichdividesan application into three interconnected parts.
• MVCfacilitatesefficientcodereuseandparalleldevelopment
• It was introduced into Smalltalk in the 1970s.
• Highcohesion:logicalgroupingofrelatedactions
• Low coupling: separation among models, views or controllers
• Models can have multiple views
• Easytomodify
• Navigation through code can be complex
• Requirement on consistency at multiple places
• Steep learning curve, especially when multiple technologies are involved.
A/Prof Andy Song RMIT University 2021
3
MVC in Rails
A/Prof Andy Song RMIT University 2021
(Ariel Krakowski’s example)
4
MVC in Rails – Model
We can see the MVC structure in Rails’ structure.
Models inherit from ActiveRecord, an Object Relational Mapping (ORM) framework, treating database entries as Ruby objects.
Code that relates to data should be in the model.
rails g model Product string:name description:text rails g migration AddPriceToProducts price:integer
rails db:migrate
CRUD Create — Product.create(name:“X”, description: “Y”) Read — product = Product.find_by(name:“X”) Update — product.description = “Z”
Delete — product.destroy
A/Prof Andy Song RMIT University 2021
(Ariel Krakowski’s example)
5
MVC in Rails – Controller
A controller controls access to an application and makes the data from the model available to the view. You shouldn’t place too much code in the Controller; keep a “fat model, skinny controller”.
rails g controller Product index
Controllers can access GET/POST parameters from params hash:
class ProductsController < ApplicationController def index
end
(Ariel Krakowski’s example)
if params[:status] == "sale”
@products = Product.on_sale
else
@products = Product.all
end end
A/Prof Andy Song RMIT University 2021
6
MVC in Rails - View
Erb pages will be processed and presented to viewers. Views contain Ruby code to display data from the controller. The actual logic and database operations are not in the views.
<% %> Execute code without returning anything <%= %> Execute code and display its output
Example: to loops through products and display all product names.
All Products
<% @products.each do |product| %>
<%= product.name %>
<% end %>
A/Prof Andy Song RMIT University 2021
(Ariel Krakowski’s example)
7
Sessions
• HTTP is stateless but many requests are dependent to each other.
• Rails supports several ways to maintain a state.
• Tokeeptrackoftransactionsmanually,youcanusecookies.
• Oryoucanusethebuilt-insessionsofRails.
• For long-term session, you may need use authentication to assist with the control.
• Sessionsmakethewebstateful.
A/Prof Andy Song RMIT University 2021
8
Cookies
• Smallpiecesoftext,usuallylessthan4KB.
• Set on browsers by servers, can be passed back to servers
• Browserskeeptrackofwherecookiescamefrom
• Browsers only pass cookies back to the original server
• Cookiesareveryhandyforstoringuserinformationonclient- side
• Butcanbeabusedforadvertisingetc.Potentialprivacyviolation.
• Itisgoodtosetthelifespanofacookietoashortperiodoftime
• Neverstoresensitiveinformationdirectlyincookies.
• In most cases, your application probably doesn’t need to access cookies directly unless you need cookies to provide a key value, for example for a JS library.
A/Prof Andy Song RMIT University 2021
9
Cookies in HTTP
Cookie data flow (Learning Rails 5, Mark Locklear Eric Gruber) 10
A/Prof Andy Song RMIT University 2021
Guestbook using Cookies
Suppose we have a simple application created.
$ docker-compose up
$ docker-compose run web bash
$ rails generate controller entries
Routes have been correctly configured config/routes.rb
In the Entries controller
class EntriesController < ApplicationController def sign_in
@previous_name = cookies[:name]
@name = params[:visitor_name]
cookies[:name]= @name
end end
In the view app/views/entries/sign_in.html.erb
<% unless @previous_name.blank? %>
Hmmm… the last time you were here, you said you
were <%= @previous_name %>.
A/Prof Andy Song RMIT University 2021
11
Guestbook using Cookies…
Now you will see:
A/Prof Andy Song RMIT University 2021
12
Inspecting the Cookies
In Chrome, cookies can be viewed under Developer, Developer Tools, Application, Cookies. You can see the current value of Cookie “name” is “Tito”.
A/Prof Andy Song RMIT University 2021
13
Set Cookies
• The last example uses Rails mechanisms cookies[:name]= @name
• You can set cookie directly as:
cookies[:name] = { value: @name, path: ‘/’ }
• The parameters include:
:value usuallyashortstring,adatabasekey
:domain has to match the application’s domain, icode.com is OK for myapp.icode.com, testapp.icode.com to set cookies
:path all or part of the path where the call is being made, / and /entries are OK for requesm ts fro/entries/sign_in
:expires 5.minutes, 2.hours.from_now
:secure true to accept HTTPS connection only :httponly true to accept HTTP/HTTPS, not JS code…
A/Prof Andy Song RMIT University 2021
14
Sessions
• When inspect the cookie name , you may have noticed another cookie _guestbook_session
• That is a session cookie automatically created by Rails.
• Sessions can be viewed as a series user requests and session
cookies are to keep track of user data between requests.
• A session object is available to the controller and view.
class EntriesController < ApplicationController def sign_in
@names = session[:names]
unless @names
@names =[]
end
@name = params[:visitor_name]
if @name
@names << @name
end
session[:names]=@names
end end
15
A/Prof Andy Song RMIT University 2021
Sessions...
• Let us update the view
Hello <%= @name %>
<%= form_tag action: 'sign_in' do %>
Enter your name:
<%= text_field_tag 'visitor_name', @name %>
<%= submit_tag 'Sign in' %>
<% end %>
<% if @names %>
-
<% @names.each do | name | %>
- <%= name %>
<% end %>
<% end %>
A/Prof Andy Song RMIT University 2021
16
Inspect the Session Cookie
A/Prof Andy Song RMIT University 2021
17
Session Settings
• Willyouseecookievaluesifchangetoadifferentbrowser?
• Session cookies will be deleted when the browser quits.
• Objectsessionisbuiltin.Itcanbeturnedofftomakethe application a bit faster. Add this at the top of controller.
session :off
• Can be set in app/controllers/application_controller.rb
• session :on at the top of a controller to enable session.
• :session_secure => true to make session work with HTTPS connections only.
• CheckthedocofActionController::SessionManagement for more options for sessions.
A/Prof Andy Song RMIT University 2021
18
Cookie Storage
• ActionDispatch::Session::CookieStore – all on the client.
• ActionDispatch::Session::CacheStore – all in the Rails cache.
• ActionDispatch::Session::ActiveRecordStore – in data (require activerecord-session_store gem).
• ActionDispatch::Session::MemCacheStore – legacy memcached cluster
• CookieStore saves the session as a hash. The server retrieves the
hash from the cookie. So session ID is not needed.
• SinceRails4,thedefaultisEncryptedCookieStorewhichstores sessions all encrypted. The encryption before 5.2 is done with a
server-side secret key secrets.secret_key_based in config/secrets.yml
• Since Rails 5.2 is config/credentials.yml.enc
• Ifyoursourcecodeispubliclyviewable,changethesecretkeyin production.
A/Prof Andy Song RMIT University 2021
19
User Authentication
• User-only areas are very common in web applications, e.g.
$ rails g resource user email password_digest
$ rails db:migrate
$ rails g controller sessions new
• Usingresourcecreatesanemptycontrol,moresuitablefor authentication than g scaffolding and g model.
• Add in the user model has_secure_password
• Enable gem ‘brcypt’, ‘~>3.1.12’
• In the controller session[:use_id] = @user.id
• Private helper
def user_params params.require(:user).permit(:email, :password, :password_confirmation)
end
A/Prof Andy Song RMIT University 2021
20
User Authentication
• In the session controller,
def create
user = User.find_by( email: params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to root_url, notice: “Logged in!”
else
flash.now.notice = “Email or password is invalid”
render “new”
end
end
If a user is found and the user is authenticated, then a session will be created based on the user ID, and redirect the page to the root with a notice.
Otherwise an error message will be displayed.
A/Prof Andy Song RMIT University 2021
21
User Authentication
• You may want to display the user details. A helper can be defined in ApplicationController
class ApplicationController < ActionController::Base protect_from_forgery with: :exception
private def current_user
@current_user ||= User.find( session[:user_id]) if session[:user_id]
end
helper_method :current_user
end
• The view can then be coded as:
<% if current_user %>
Logged in as <%= current_user.email %> |
<%= link_to "Log Out", logout_path %>
<% else %> . . .
• Notenormalhelpersarenotavailableforcontrollers,but views. The helper_method can share a method between controllers and views.
A/Prof Andy Song RMIT University 2021
22
User Authentication
• Beforefilterscanbeusedaswellforaccesscontrol:
class StudentsController < ApplicationController before_action :set_student,
only: [:show, :edit, :update, :destroy]
before_action :authorize
...
• IntheApplicationControllerweaddthedefinitionofmethod authorize
define authorize
redirect_to login_url, alert: "Not authorized” if current_user.nil?
end
• Thefilterwillbeappliedbeforeactions.
• Typing on the browser /students, students/new, student/1
should lead to the login page.
A/Prof Andy Song RMIT University 2021
23
Admin
• Admin-onlyareasarealsooftenrequiredinwebapplications, $ rails generate migration AddAdminFlagToUsers
$ rails db:migrate
• The migration will look like this:
add_column :users, :admin, :boolean, default: false,
null: false
• class StudentsController < ApplicationController before_action :set_student,
only: [:show, :edit, :update, :destroy]
before_action :authorize
before_action :is_admin,
only: [:new, :edit, :update, :destroy]
• In ApplicationController
def is_admin
redirect_to students_path, alert: "Not authorized” if
current_user.nil? or !current_user.admin? end
A/Prof Andy Song RMIT University 2021
24
Admin
• In the view, links to edit and destroy will ONLY show to admin users.
<% if current_user.admin? %>
<% end %>
It might be a good idea to update all relevant controllers as well to prevent user access views directly using the RESTful URL.
A/Prof Andy Song RMIT University 2021
25
More Options/Todos
• Logging in through Twitter, Facebook, Google etc.
• Letting users stay logged in to their account on a given
browser
• Finer-grainedpermissionfordifferentcategoriesofusers
• Passwords reset
• Emailaddressverification
• Allowuserssetpreferences
• Captcha
• Biometric authentication
A/Prof Andy Song RMIT University 2021
26
Routing in Rails
• Rails routing may look strange.
• Rails turns URI requests into calls to corresponding
controllers
• Thedefaultroutingespeciallyscaffoldingisoftengoodto start.
• Howeveryoucanre-definetherouting.
• Youcanchangerouteswithoutaffectingyourapplication
• Keepitmind,youshouldnotchangeroutingtoooften
• Routingdoessignificantimpactonyourapplication
• Some parts like JSON-based services will not know where to go if the routes are different.
A/Prof Andy Song RMIT University 2021
27
config/routes.rb
• Under development mode, it will be reloaded whenever there is a change.
• In production mode, the application has to be rebooted. get ‘students/:id’ => ‘students#show’
http://localhost:3000/students/20
• With the above routing and URL, students controller will be
called to invoke action show with parameter id value as 20.
• Iftherequestis‘controller’or‘controller/’thentheaction
would be index.
• Routing also works in reverse (with the link_to helper ) <% = link_to 'Student 20', controller: 'students', action: 'show', id: 20 %>
produces http://localhost:3000/students/20 A/Prof Andy Song RMIT University 2021
28
•
•
•
config/routes.rb
For JSON request such as
http://localhost:3000/students/20.json
there is an additional parameter :format which is json
You can specify URIs precisely
get ‘this/uri/exactly’ => ‘students#new’
Then the URL below will call new in students controller. http://localhost:3000/this/uri/exactly
Note extra parameters are possible to be passed through the request. So users may be able to inject unexpected info this way.
A/Prof Andy Song RMIT University 2021
29
•
Globbing
Some applications may have multiple but related URLs
http://example.com/download/java/angrybird http://example.com/download/python/angrybird http://example.com/download/ruby/angrybird
You can use one routes for the above URLs
get ’download/*path’ to: ’download#find_path’
Inside of action find_path def find_path
path = params[:path]
… end
• Variablepathwillcontain[‘java’,‘angrybird’]for example.com/download/java/angrybird
[‘ruby’, ‘2020’, ‘new’, ‘superman’] for
example.com/download/ruby/2020/new/superman
A/Prof Andy Song RMIT University 2021
30
Regular Expression
• Railscanbemorespecificonincomingrequests. • You can add regular expression as constraints
get ’students/:id’ to: ’students#show’,
constraints: { id: /\d/}
get ’students/:id’ to: ’students#show’,
to: errors#bad_id
• Thefirstrulecheckswhethertheidisdigitonly
• Ifnot,Railscontinuestothenextlineandcallthebad_id
action in errors controller to do something.
A/Prof Andy Song RMIT University 2021
31
Default Root
• Oftendeveloperswantvisitorsenteringadomainnameand always land on a certain page, the “front door”.
• Two ways to do that.
• One is to place a static HTML page as public/index.html
and let the page link users to the deep. Or
root to: “welcome#entry”
• Theentrymethodofwelcomecontrollerisnowresponsible
for the root page.
• Itisgoodtomovetherootsettingtothetopofroutes.rb.
A/Prof Andy Song RMIT University 2021
32
Mapping Resources
• Routingforresourcesisstraightforward resources :people
• ItconvertssevenroutesforRESTfulactions.
(Learning Rails 5, Mark Locklear Eric Gruber)
• For a singleton objects, you can use resource, no s, resource Vader:
A/Prof Andy Song RMIT University 2021
33
Mapping Resources
• You may want to add new controller without changing the original seven RESTful routes.
• It is done by using member. For example
resources :courses do
member do
get :roll end
end
• A new route is now roll_course and the method is GET.
• Ofcoursewehavetocreatetherollmethodinthecourses controller.
A/Prof Andy Song RMIT University 2021
34
Mapping Resources
• You may need multiple extra methods. Just list them in the member block.
• It is done by using member. For example resources :students do
resources :awards
member do
get :courses
post :course_add
post :course_remove
end end
• Theaboveexampleisalsoanexampleofnestedresources. The url will become
students/:student_id/awards/:award_id
A/Prof Andy Song RMIT University 2021
35
Misc on Routes
• Railslooksforroutesfromthetopofroutes.rb
• Onceitfoundamatch,thenitwouldnotlookanyfurther
• Routing rules that appear earlier have higher priority
• Inpractice,youneedtoplacemorespecificrulesnearthetop
• Rules that use wildcards, or just more general, should be further down.
• Somorespecificrulesgetprocessedbeforegeneralrules.
• To check the current routing, run $rails routes
• $rails routes | grep root
A/Prof Andy Song RMIT University 2021
36
Generating URIs
• Settinguprouteshelpconnectdifferentpartsofanapplication.
• Rails has form_for, link_to and many to generate URIs.
• They all rely on url_for(option = {})
url_for action: ’exam’, controller: ’students’, id: ’12’
generates /students/exam/12
url_for @course
Assume @course id is 2675, Rails will use the naming convention to
– check a named route that matches
– call _path helper course_path
– find the right route
– then generate/courses/2675
A/Prof Andy Song RMIT University 2021
37
Generating URIs…
• For nested resources url_for [@student, @award]
generates /students/1/awards/2 • Calling the _path helper also works
url_for student_award_path(@student, @award)
• Someoftheoptions
− :anchor to add a fragment identifier to the end of URI, #
− :only_path, default true, so the protocol, hostname, port will not show
− :trailing_slash to add a slash at the end of URIs
− :host, :port, :protocol to specify host, port, protocol.
A/Prof Andy Song RMIT University 2021
38
Asset Pipeline
• Assetpipelineisaframework/tool/mechanismtoprocessand prepare JS files, style sheets and images.
• Itcanminifyandcompresstheseassets.
• These assets are not in Ruby.
• The problem it tries to solve is that HTML retrieves these components separately, leading to many HTTP requests and long load time.
• Javascripts/CSS take many bandwidth
• Coffeescript,Sassarebetter,butbrowserscannotinterpret
them directly.
• So preprocessing is necessary.
A/Prof Andy Song RMIT University 2021
39
Asset Pipeline
• The asset pipeline is implemented by sprockets-rails gem. It can be disabled by $ rails new myapp –skip-sprockets
• Rails automatically adds gem ‘sass-rails’
gem ‘uglifier’ –for asset compression gem ‘coffee-rails’
• SprocketsconcatenatesallJSfilesintoonemaster.js.
• ThesametoCSSfiles.
• Whitespace and comments are removed. Assets are compressed.
• It allows coding assets in a higher level language, then precompiling down to actual assets. Sass for CSS, CoffeeScript for JS, ERB for both by default.
40
A/Prof Andy Song RMIT University 2021
Asset Organization
The assets are in three places
− app/assets : assets owned by the application
− lib/assets : own library code that does not fit in the application or shared with other applications
− vendor/assets : owned by other people, e.g. JS plugin
A/Prof Andy Song RMIT University 2021
41
Asset Path
Sprockets will search for all three assets directory.
app/assets/javascripts/rad.js lib/assets/javascripts/project.js vendor/assets/javascripts/blackboard.js app/assets/my/booklist.js
They can be referenced in a manifest
//= require rad
//= require project
//= require blackboard
//= require my/booklist
An asset directory with index can be accessed as a whole
lib/assets/javascripts/mylib lib/assets/javascripts/mylib/index.js
They can be referenced in a manifest
//= require mylib
A/Prof Andy Song RMIT University 2021
42
Use Assets
Access a style sheet through Turbolink
<%= stylesheet_link_tag "application", media: "all", "data- turbolinks-track" => “reload” %>
Access a JavaScript through Turbolink
<%= javascript_include_tag "application", "data-turbolinks-track" => “reload” %>
Access an image through Turbolink
<%= image_tag "rails.png" %>
Rails look for paths specified in config.assets.paths A/Prof Andy Song RMIT University 2021
43
Manifest Files
• Sprocketsusemanifestfilestotellwhichassetstouse.
• Thesefilescontaindirectives–instructiononwhichfilesare
required in what order to build a single CSS/JS file.
For example a new app/assets/javascripts/application.js contains the following // …
//= require jquery
//= require jquery_ujs
//= require_tree .
• Thismanifestrequiresjquery.jsandjquery_ujs.js
• The require_tree directive tells sprockets to recursively include all JS files in the specified directory, which must be relative to the manifest file.
• //=require_directory is not recursive. A/Prof Andy Song RMIT University 2021
44
Preprocessing
• Preprocessingisdeterminedbythefileextensions.
• Whenthefilesarerequiredtheywillbepreprocessedandplaced in the public/assets directory for serving either the app or server.
• Additionallayersofpreprocessingcanbeaddedbasedonfile extensions in a right to left sequence.
• app/assets/stylesheets/rad.scss.erb is processed as ERB, then SCSS, then used as CSS.
• app/assets/javascripts/rad.coffee.erbisprocessedasERB,then Coffee Script, then used as Javascript.
• Theorderofextensionisimportant.Afilelikethis app/assets/javascripts/projects.erb.coffee is problematic as the
CoffeeScript interpreter would not understand ERB.
A/Prof Andy Song RMIT University 2021
45
Misc of Asset Pipeline
• You may use asset helpers image-url, image-path, asset-url, asset-path to access assets.
• Largefilessuchasvideos,PDFsarenotsuitabletobeincluded
• Assets can work with CDN (content delivery network). They
work nicely with CDN. See Rails doc for details.
• You can use YUI ( yui-compressor gem) to compress the assets config.assets.css_compressor = :yui config.assets.js_compressor = :yui
• Another CSS compression option is sass (sass-rails gem)
• JS compression options include uglifier and closure
(closure-compiler gem)
• Asset pipeline can overcome problems in delivery static assets.
A/Prof Andy Song RMIT University 2021
46
Summary
• MVC/MVC in Rails
• Cookies
• Sessions
• User Authentication
• Admin
• Routing in Rails
• Globbing, Regex in Routing
• Generating URIs
• Asset Pipeline
• Manifest
47
A/Prof Andy Song RMIT University 2021