SlideShare a Scribd company logo
Rails MVC
1
Rails MVC by Sergiy Koshovyi
Model-View-Controller
Trygve Reenskaug. 1978
Rails MVC by Sergiy Koshovyi
● Separation of business logic
● Reusing of code
● Separation of responsibility
● Flexible application
BENEFITS
● Doesn’t have a strict implementation
● No one place for busines logic
● No one place for validations
● No one place for views
PROPERTIES
Rails MVC
in the context
of user
MVC is an Architectural Pattern
8
My Banana
Stuart, give me
the banana
Ostap, are
you here?
I’m coming
put on a suit
Farewell !!!
Rails MVC
in the context
of components
10
Router
Controller ModelResponse
Views DataBase
Rails MVC
in the context
of Rails libraries
12
ROUTES
13
resource :profile, only: %i[show edit update], controller: :profile
14
RESOURCE
1 Rails.application.routes.draw do
2 root 'home#index'
3
4 resources :events, only: %i[index show] do
5 resources :visit_requests, only: %i[show create destroy]
6 end
15
RESOURCES
CUSTOM ROUTES WITHIN RESOURCES
1 resources :orders do
2 collection do
3 post :search
4 end
5 member do
6 post :accept
7 post :reject
8 post :ship
9 get :despatch_note
10 end
11 end
16
get '/:page_url', to: 'pages#show'
authenticate :user, ->(u) { u.admin? } do
namespace :admin do
get '/', to: 'home#index'
resources :accounts
17
CUSTOM ROUTES, NAMESPACE
1 # api
2 namespace :api, path: '/', constraints: { subdomain: 'api' }, defaults: { format: :json } do
3 namespace :v1 do
4 post 'sign_up' => 'registrations#create'
5 post 'sign_in' => 'sessions#create'
6 delete 'sign_out' => 'sessions#destroy'
7 end
8 end
18
NAMESPACES
api_v1_sign_up POST /v1/sign_up(.:format) api/v1/registrations#create
{:subdomain=>"api", :format=>:json}
api_v1_sign_in POST /v1/sign_in(.:format) api/v1/sessions#create
{:subdomain=>"api", :format=>:json}
api_v1_sign_out DELETE /v1/sign_out(.:format) api/v1/sessions#destroy
{:subdomain=>"api", :format=>:json}
1 # api
2 scope :api, constraints: { subdomain: 'api' }, module: :api, defaults: { format: :json }, as: :api do
3 scope '/v1', module: :v1, as: :v1 do
4 post 'sign_up' => 'registrations#create'
5 post 'sign_in' => 'sessions#create'
6 delete 'sign_out' => 'sessions#destroy'
7 end
8 end
19
SCOPES
api_v1_sign_up POST /v1/sign_up(.:format) api/v1/registrations#create
{:subdomain=>"api", :format=>:json}
api_v1_sign_in POST /v1/sign_in(.:format) api/v1/sessions#create
{:subdomain=>"api", :format=>:json}
api_v1_sign_out DELETE /v1/sign_out(.:format) api/v1/sessions#destroy
{:subdomain=>"api", :format=>:json}
20
Wait!
Aren’t namespaces and scopes essentially the same?
1 # api
2 scope :api do
3 scope :v1 do
4 post 'sign_up' => 'registrations#create'
5 post 'sign_in' => 'sessions#create'
6 delete 'sign_out' => 'sessions#destroy'
7 end
8 end
21
SCOPES WITHOUT ANY OPTIONS
sign_up POST /sign_up(.:format) registrations#create
sign_in POST /sign_in(.:format) sessions#create
sign_out DELETE /sign_out(.:format) sessions#destroy
22
NAMESPACE vs SCOPE
Namespace Scope
By default, adds the name of the
namespace to the name of the path,
prefixes actual request path, and expects
the controller to belong to appropriately
named module.
No defaults. All options are explicit.
A shortcut method when you need to
quickly nest a set of routes and controllers
under some name.
A powerful, customizable method to apply
defaults to a group of routes.
1 namespace :admin do
2 root "admin#index"
3 end
4
5 root "home#index"
23
MULTIPLE ROOT ROUTES
1 constraints(id: /[A-Z][A-Z][0-9]+/) do
2 resources :photos
3 resources :accounts
4 end
24
CONSTRAINTS
CONTROLLERS
25
1 def create
2 @product = Product.new(safe_params)
3 if @product.save
4 redirect_to :products, flash: { notice: t('shopr.products.create_notice') }
5 else
6 render action: 'new'
7 end
8 end
9
10 def update
11 if @product.update(safe_params)
12 redirect_to [:edit, @product], flash: { notice: t('products.update_notice') }
13 else
14 render action: 'edit'
15 end
16 end
26
DEFAULT ACTIONS
1 class ApplicationController < ActionController::Base
2 protect_from_forgery with: :exception
3 before_action :auth, only: :admin
4 helper_method :admin?, :about_page, :contacts_page
6 delegate :admin?, to: :current_user, allow_nil: true
7 skip_authorization_check
8 skip_before_action :authenticate_user!
27
HELPERS, BEFORE ACTIONS, ...
1 def create
2 @protocol = Protocol.create(protocol_params)
3 end
4
5 private
6
7 def protocol_params
8 params.require(:protocol).permit(
9 :competition_id,
10 :first_name,
11 :last_name,
12 :total_result,
13 :place
14 )
15 end
28
PERMITTED PARAMS
1 def create
2 @product = Product.new(safe_params)
3 if @product.save
4 redirect_to :products, flash: { notice: t('shopr.products.create_notice') }
5 else
6 render action: 'new'
7 end
8 end
29
REDIRECT VS RENDER
render :edit
render action: :edit
render "edit"
render "edit.html.erb"
render action: "edit"
render action: "edit.html.erb"
render "books/edit"
render "books/edit.html.erb"
30
RENDER
render template: "books/edit"
render template: "books/edit.html.erb"
render "/path/to/rails/app/views/books/edit"
render "/path/to/rails/app/views/books/edit.html.erb"
render file: "/path/to/rails/app/views/books/edit"
render file: "/path/to/rails/app/views/books/edit.html.erb"
1 def create
2 Shopr::Order.transaction do
3 @order = Shopr::Order.new(safe_params)
4 @order.status = 'confirming'
5 if safe_params[:customer_id]
6 @customer = Shopr::Customer.find safe_params[:customer_id]
7 @order.first_name = @customer.first_name
8 @order.last_name = @customer.last_name
9 @order.company = @customer.company
10 @order.email_address = @customer.email
11 @order.phone_number = @customer.phone
12 if @customer.addresses.billing.present?
13 billing = @customer.addresses.billing.first
14 @order.billing_address1 = billing.address1
15 @order.billing_address2 = billing.address2
16 @order.billing_address3 = billing.address3
17 @order.billing_address4 = billing.address4
18 @order.billing_postcode = billing.postcode
19 @order.billing_country_id = billing.country_id
31
FAT CONTROLLERS
1 class VisitRequestsController < ApplicationController
2 before_action :authenticate_user!, only: %i[create destroy]
3
4 def show
5 VisitRequest::FinalConfirmation.call(visit_request, params)
6 flash_success(visit_request.status) and default_redirect
7 end
8
9 def create
10 VisitRequest::Create.call(current_user, event)
11 flash_success and default_redirect
12 end
13
14 def destroy
15 visit_request.destroy
16 flash_success and default_redirect
17 end
32
THIN CONTROLLERS
MODELS
33
1 class Goal < ApplicationRecord
2 has_many :donations, dependent: :destroy
3
4 validates :title, presence: true
5 validates :amount, presence: true, numericality: true
6
7 def achieved?
8 donations_total >= amount
9 end
10
11 def donations_total
12 donations.sum(:amount)
13 end
14 end
34
MODELS
class Goal < ApplicationRecord
has_many :donations, dependent: :destroy
end
class Donation < ApplicationRecord
belongs_to :goal
end
35
ONE TO MANY/ONE RELATIONS
Goal
id:integer
title:string
Donation
id:integer
goal_id:integer
title:string
...
...
36
MANY TO MANY RELATIONS
Goal
id:integer
title:string
Donation
id:integer
title:string
... ...
class Goal < ApplicationRecord
has_and_belongs_to_many :donations
end
class Donation < ApplicationRecord
has_and_belongs_to_many :goal
end
donations_goals
goal_id:integer
donation_id:integer
37
ASSOCIATION RELATIONS
Goal
id:integer
title:string
Donation
id:integer
goal_id:integer
...
...
class Goal < ApplicationRecord
has_many :donations
has_many :users, through: :donations
end
class Donation < ApplicationRecord
belongs_to :goal
belongs_to :user
end
class User < ApplicationRecord
has_many :donations
end
User
id:integer
email:string
...
user_id:integer
38
POLYMORPHIC RELATIONS
User
id:integer
email:string
Company
id:integer
name:string
... ...
Address
id:integer
address:string
addressable_id:integer
addressable_type:string
class Address < ApplicationRecord
belongs_to :addressable, polymorphic: true
end
class User < ApplicationRecord
has_many :address, as: :assressable
end
class Company < ApplicationRecord
has_many :address, as: :assressable
end
1 class Goal < ApplicationRecord
2 validates :title, presence: true
3 end
4
5 goal = Goal.create(title: “Buying a motocycle”).valid? # => true
6 goal = Goal.create(title: nil).valid? # => false
7 goal.errors.messages # => {title:["can't be blank"]}
39
VALIDATES
1 class Goal < ApplicationRecord
2 validates_associated :donations #(only in one of related model)
3 validates_with GoalValidator #class GoalValidator < ActiveModel::Validator
4 validate :expiration_date_cannot_be_in_the_past
5
6 def expiration_date_cannot_be_in_the_past
7 if expiration_date.present? && expiration_date < Date.today
8 errors.add(:expiration_date, "can't be in the past")
9 end
10 end
11 end
40
VALIDATES
VIEWS
41
1 <h2> <%= t 'goals.singular'> </h1>
2
3 < div class="col-md-12" >
4 <%= goal.title >
5 < /div>
6 < div class="col-md-12" >
7 <%= goal.description >
8 < /div>
9 < div class="col-md-12" >
10 <%= "Amount to reach: #{goal.amount}" >
11 < /div>
12 < div class="col-md-12" >
13 <%= "Current sum: #{goal.donations_total}" >
14 < /div>
42
TEMPLATES
1 <h2> <%= t 'goals.singular'> </h1>
2
3 < div class="col-md-12" >
4 <%= goal.title >
5 < /div>
6 < div class="col-md-12" >
7 <%= goal.description >
8 < /div>
9 < div class="col-md-12" >
10 <%= "Amount to reach: #{goal.amount}" >
11 < /div>
12 < div class="col-md-12" >
13 <%= "Current sum: #{goal.donations_total}" >
14 < /div>
43
.html.erb (ERB - Embedded Ruby)
1 h2 = t 'goals.singular'
2
3 .col-md-12
4 = goal.title
5 .col-md-12
6 = goal.description
7 .col-md-12
8 = "Amount to reach: #{goal.amount}"
9 .col-md-12
10 = "Current sum: #{goal.donations_total}"
11 .col-md-12
12 - unless goal.achieved?
13 = render 'shared/donations/form', path: donate_goal_path(goal)
44
.html.slim
1 $('#notifications_list').html(
2 "<%= escape_javascript(render('notifications/list', notifications: @notifications)) %>"
3 );
4
5 $('.container-fluid .flash-msg').html(
6 "<%= escape_javascript(render 'layouts/alerts') %>"
7 );
8
9 $("#send-notification-modal").modal("hide");
45
.js.erb
1 json.extract! ticket, :id,
:uid,
:manager_id,
:customer_name,
:customer_email,
:title,
:body,
:status_id
2 json.url ticket_url(ticket, format: :json)
46
.json.jbuilder
new.html.slim
1 = render 'shared/donations/form',
path: donate_goal_path(goal)
edit.html.slim
1 = render 'shared/donations/form',
path: donate_goal_path(goal)
47
PARTIALS
_form.html.slim
= simple_form_for :credit_card, url: path do |f|
.form-inputs
.row
.col-md-6
= f.input :number, as: :string
= f.input :cvc, as: :string
= f.input :exp_month, as: :string
= f.input :exp_year, as: :string
1 <h1>Listing Books</h1>
2 <table>
3 <tr>
4 <th>Title</th>
5 <th>Summary</th>
6 <th></th>
7 <th></th>
8 <th></th>
9 </tr>
10 <% @books.each do |book| %>
11 <tr>
12 <td><%= book.title %></td>
13 <td><%= book.content %></td>
14 <td><%= link_to "Show", book %></td>
15 <td><%= link_to "Edit", edit_book_path(book) %></td>
16 <td><%= link_to "Remove", book, method: :delete, data: { confirm: "Are you sure?" } %></td>
17 </tr>
18 <% end %>
19 </table>
48
PARTIALS. MAGIC
/views/books/index.html.erb
1 <h1>Listing Books</h1>
2 <table>
3 <tr>
4 <th>Title</th>
5 <th>Summary</th>
6 <th></th>
7 <th></th>
8 <th></th>
9 </tr>
10 <%= render @books %>
11 </table> 49
PARTIALS. MAGIC
/views/books/_book.html.erb
1 <tr>
2 <td><%= book.title %></td>
3 <td><%= book.content %></td>
4 <td><%= link_to "Show", book %></td>
5 <td><%= link_to "Edit", edit_book_path(book) %></td>
16 <td><%= link_to "Remove", book, method: :delete, data:
{ confirm: "Are you sure?" } %></td>
17 </tr>
50
LAYOUTS
class HomeController < ApplicationController
def index
render 'events/show'
end
end
51
LAYOUTS
class TalksController < ApplicationController
helper_method :talks, :talk, :tags
private
def talks
scope = Talk.published.includes(:event).order('events.finished_at desc')
@talks ||= params[:tag] ? scope.tagged_with(params[:tag]) : scope
@talks.page(params[:page]).per(12)
end
end
class PagesController < ApplicationController
helper_method :page
def show
page ? render(:show) : not_found
end
private
def page
@page ||= Page.find_by(url: params[:page_url])
end
end
class AuthController < ActionController::Base
layout "devise"
end
doctype html
html
head
title
= content_for?(:title) ? yield(:title) : t('default_title')
= stylesheet_link_tag 'app/application'
meta name="theme-color" content="#ffffff"
== render 'layouts/ga/head'
body class="#{yield(:main_body_class)}"
= yield(:before_header)
== render 'layouts/app/flash'
== render 'layouts/app/header'
main
= yield
52
application.slim vs admin.slim
doctype html
html
head
title
= content_for?(:title) ? yield(:title) : t('default_admin_title')
= stylesheet_link_tag 'admin/application'
meta name="viewport" content="width=device-width,
initial-scale=1, shrink-to-fit=no"
body
.ui.sidebar.inverted.vertical.menu
== render 'layouts/admin/navigation'
.pusher
.ui.container#container
== render 'layouts/admin/header'
= yield
== render 'layouts/admin/footer'
views/events/index.slim
...
tbody
- events.each do |event|
tr
th = event.id
td = resource_link(event)
td = format_timestamp(event.started_at)
td = format_timestamp(event.finished_at)
53
HELPERS
helpers/...
module TalksHelper
def talk_link(talk, text = "", options = {})
link_to text, polymorphic_path(talk), options
end
end
module ApplicationHelper
def format_timestamp(timestamp, time: true, delimiter: '-')
return unless timestamp
formatted_date = timestamp.strftime('%Y %b %d')
formatted_time = timestamp.strftime('%H:%M')
return formatted_date if !time
"#{formatted_date} #{delimiter} #{formatted_time}"
end
● Fat model and Skinny controller
● Business logic should always be in the
model
● The view should have minimal code
● Use helpers!
● Use models
● DRY (Don't Repeat Yourself)
54
BEST
PRACTICES
https://github.com/rails/rails
http://api.rubyonrails.org/
http://guides.rubyonrails.org/
https://github.com/bbatsov/rails-style-guide
55
RESOURCES

More Related Content

PPT
Lecture n
PPTX
Cape Town MS Developer User Group: Xamarin Community Toolkit
PDF
OSDC 2009 Rails Turtorial
PDF
Working With The Symfony Admin Generator
KEY
PPT
Synapseindia reviews sharing intro cakephp
PPTX
Monkey Conf 2020: Xamarin Community Toolkit: More possibilities with Xamarin....
PPTX
Service approach for development REST API in Symfony2
Lecture n
Cape Town MS Developer User Group: Xamarin Community Toolkit
OSDC 2009 Rails Turtorial
Working With The Symfony Admin Generator
Synapseindia reviews sharing intro cakephp
Monkey Conf 2020: Xamarin Community Toolkit: More possibilities with Xamarin....
Service approach for development REST API in Symfony2

What's hot (19)

PPTX
Service approach for development Rest API in Symfony2
PPT
Workshop: Symfony2 Intruduction: (Controller, Routing, Model)
PDF
Curso Symfony - Clase 4
PDF
Resource and view
KEY
Single Page Web Apps with Backbone.js and Rails
PDF
Curso Symfony - Clase 2
PDF
Symfony2, Backbone.js &amp; socket.io - SfLive Paris 2k13 - Wisembly
PPT
CRUD with Dojo
PDF
JavaServer Faces 2.0 - JavaOne India 2011
PPT
Building Single Page Application (SPA) with Symfony2 and AngularJS
PDF
Send, pass, get variables with php, form, html & java script code
PDF
Drupal 8 simple page: Mi primer proyecto en Drupal 8.
DOCX
Layout
PPTX
Spring Surf 101
PDF
Empowering users: modifying the admin experience
PDF
PDF
2012.sandiego.wordcamp
PDF
AnkaraJUG Kasım 2012 - PrimeFaces
PPTX
Custom post-framworks
Service approach for development Rest API in Symfony2
Workshop: Symfony2 Intruduction: (Controller, Routing, Model)
Curso Symfony - Clase 4
Resource and view
Single Page Web Apps with Backbone.js and Rails
Curso Symfony - Clase 2
Symfony2, Backbone.js &amp; socket.io - SfLive Paris 2k13 - Wisembly
CRUD with Dojo
JavaServer Faces 2.0 - JavaOne India 2011
Building Single Page Application (SPA) with Symfony2 and AngularJS
Send, pass, get variables with php, form, html & java script code
Drupal 8 simple page: Mi primer proyecto en Drupal 8.
Layout
Spring Surf 101
Empowering users: modifying the admin experience
2012.sandiego.wordcamp
AnkaraJUG Kasım 2012 - PrimeFaces
Custom post-framworks
Ad

Similar to Rails MVC by Sergiy Koshovyi (20)

PDF
The Rails Way
PPTX
Ruby on Rails + AngularJS + Twitter Bootstrap
PDF
Angular.js Fundamentals
PDF
Rails antipattern-public
PDF
Rails antipatterns
PDF
SproutCore and the Future of Web Apps
PDF
Advanced RESTful Rails
PDF
Advanced RESTful Rails
KEY
Ruby/Rails
PDF
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
PDF
Rails::Engine
PDF
AngularJS vs. Ember.js vs. Backbone.js
PDF
Intro to-rails-webperf
PDF
RoR 101: Session 2
KEY
Presenters in Rails
KEY
Api development with rails
PDF
Ruby on Rails - Introduction
PDF
Building Mobile Friendly APIs in Rails
PDF
Be happy with Ruby on Rails - CEUNSP Itu
PDF
Template rendering in rails
The Rails Way
Ruby on Rails + AngularJS + Twitter Bootstrap
Angular.js Fundamentals
Rails antipattern-public
Rails antipatterns
SproutCore and the Future of Web Apps
Advanced RESTful Rails
Advanced RESTful Rails
Ruby/Rails
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
Rails::Engine
AngularJS vs. Ember.js vs. Backbone.js
Intro to-rails-webperf
RoR 101: Session 2
Presenters in Rails
Api development with rails
Ruby on Rails - Introduction
Building Mobile Friendly APIs in Rails
Be happy with Ruby on Rails - CEUNSP Itu
Template rendering in rails
Ad

More from Pivorak MeetUp (20)

PDF
Lisp(Lots of Irritating Superfluous Parentheses)
PDF
Some strange stories about mocks.
PDF
Business-friendly library for inter-service communication
PDF
How i was a team leader once
PDF
Introduction to Rails by Evgeniy Hinyuk
PPTX
Ruby OOP (in Ukrainian)
PDF
Testing in Ruby
PDF
Ruby Summer Course by #pivorak & OnApp - OOP Basics in Ruby
PDF
The Saga Pattern: 2 years later by Robert Pankowecki
PDF
Data and Bounded Contexts by Volodymyr Byno
PDF
Successful Remote Development by Alex Rozumii
PDF
Origins of Elixir programming language
PDF
Functional Immutable CSS
PDF
Multi language FBP with Flowex by Anton Mishchuk
PDF
Detective story of one clever user - Lightning Talk By Sergiy Kukunin
PDF
CryptoParty: Introduction by Olexii Markovets
PDF
How to make first million by 30 (or not, but tryin') - by Marek Piasecki
PDF
GIS on Rails by Oleksandr Kychun
PDF
Unikernels - Keep It Simple to the Bare Metal
PDF
HTML Canvas tips & tricks - Lightning Talk by Roman Rodych
Lisp(Lots of Irritating Superfluous Parentheses)
Some strange stories about mocks.
Business-friendly library for inter-service communication
How i was a team leader once
Introduction to Rails by Evgeniy Hinyuk
Ruby OOP (in Ukrainian)
Testing in Ruby
Ruby Summer Course by #pivorak & OnApp - OOP Basics in Ruby
The Saga Pattern: 2 years later by Robert Pankowecki
Data and Bounded Contexts by Volodymyr Byno
Successful Remote Development by Alex Rozumii
Origins of Elixir programming language
Functional Immutable CSS
Multi language FBP with Flowex by Anton Mishchuk
Detective story of one clever user - Lightning Talk By Sergiy Kukunin
CryptoParty: Introduction by Olexii Markovets
How to make first million by 30 (or not, but tryin') - by Marek Piasecki
GIS on Rails by Oleksandr Kychun
Unikernels - Keep It Simple to the Bare Metal
HTML Canvas tips & tricks - Lightning Talk by Roman Rodych

Recently uploaded (20)

PDF
PTS Company Brochure 2025 (1).pdf.......
PPTX
ai tools demonstartion for schools and inter college
PPT
JAVA ppt tutorial basics to learn java programming
PDF
Adobe Illustrator 28.6 Crack My Vision of Vector Design
PDF
System and Network Administraation Chapter 3
PDF
Wondershare Filmora 15 Crack With Activation Key [2025
PPTX
Odoo POS Development Services by CandidRoot Solutions
PDF
Complete React Javascript Course Syllabus.pdf
PDF
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
PPTX
Materi_Pemrograman_Komputer-Looping.pptx
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 41
PPTX
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
PPTX
Essential Infomation Tech presentation.pptx
PDF
Digital Strategies for Manufacturing Companies
PDF
medical staffing services at VALiNTRY
PPTX
CHAPTER 12 - CYBER SECURITY AND FUTURE SKILLS (1) (1).pptx
PPTX
Operating system designcfffgfgggggggvggggggggg
PPTX
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
PDF
Understanding Forklifts - TECH EHS Solution
PDF
Upgrade and Innovation Strategies for SAP ERP Customers
PTS Company Brochure 2025 (1).pdf.......
ai tools demonstartion for schools and inter college
JAVA ppt tutorial basics to learn java programming
Adobe Illustrator 28.6 Crack My Vision of Vector Design
System and Network Administraation Chapter 3
Wondershare Filmora 15 Crack With Activation Key [2025
Odoo POS Development Services by CandidRoot Solutions
Complete React Javascript Course Syllabus.pdf
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
Materi_Pemrograman_Komputer-Looping.pptx
Internet Downloader Manager (IDM) Crack 6.42 Build 41
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
Essential Infomation Tech presentation.pptx
Digital Strategies for Manufacturing Companies
medical staffing services at VALiNTRY
CHAPTER 12 - CYBER SECURITY AND FUTURE SKILLS (1) (1).pptx
Operating system designcfffgfgggggggvggggggggg
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
Understanding Forklifts - TECH EHS Solution
Upgrade and Innovation Strategies for SAP ERP Customers

Rails MVC by Sergiy Koshovyi

  • 5. ● Separation of business logic ● Reusing of code ● Separation of responsibility ● Flexible application BENEFITS
  • 6. ● Doesn’t have a strict implementation ● No one place for busines logic ● No one place for validations ● No one place for views PROPERTIES
  • 7. Rails MVC in the context of user
  • 8. MVC is an Architectural Pattern 8 My Banana Stuart, give me the banana Ostap, are you here? I’m coming put on a suit Farewell !!!
  • 9. Rails MVC in the context of components
  • 11. Rails MVC in the context of Rails libraries
  • 12. 12
  • 14. resource :profile, only: %i[show edit update], controller: :profile 14 RESOURCE
  • 15. 1 Rails.application.routes.draw do 2 root 'home#index' 3 4 resources :events, only: %i[index show] do 5 resources :visit_requests, only: %i[show create destroy] 6 end 15 RESOURCES
  • 16. CUSTOM ROUTES WITHIN RESOURCES 1 resources :orders do 2 collection do 3 post :search 4 end 5 member do 6 post :accept 7 post :reject 8 post :ship 9 get :despatch_note 10 end 11 end 16
  • 17. get '/:page_url', to: 'pages#show' authenticate :user, ->(u) { u.admin? } do namespace :admin do get '/', to: 'home#index' resources :accounts 17 CUSTOM ROUTES, NAMESPACE
  • 18. 1 # api 2 namespace :api, path: '/', constraints: { subdomain: 'api' }, defaults: { format: :json } do 3 namespace :v1 do 4 post 'sign_up' => 'registrations#create' 5 post 'sign_in' => 'sessions#create' 6 delete 'sign_out' => 'sessions#destroy' 7 end 8 end 18 NAMESPACES api_v1_sign_up POST /v1/sign_up(.:format) api/v1/registrations#create {:subdomain=>"api", :format=>:json} api_v1_sign_in POST /v1/sign_in(.:format) api/v1/sessions#create {:subdomain=>"api", :format=>:json} api_v1_sign_out DELETE /v1/sign_out(.:format) api/v1/sessions#destroy {:subdomain=>"api", :format=>:json}
  • 19. 1 # api 2 scope :api, constraints: { subdomain: 'api' }, module: :api, defaults: { format: :json }, as: :api do 3 scope '/v1', module: :v1, as: :v1 do 4 post 'sign_up' => 'registrations#create' 5 post 'sign_in' => 'sessions#create' 6 delete 'sign_out' => 'sessions#destroy' 7 end 8 end 19 SCOPES api_v1_sign_up POST /v1/sign_up(.:format) api/v1/registrations#create {:subdomain=>"api", :format=>:json} api_v1_sign_in POST /v1/sign_in(.:format) api/v1/sessions#create {:subdomain=>"api", :format=>:json} api_v1_sign_out DELETE /v1/sign_out(.:format) api/v1/sessions#destroy {:subdomain=>"api", :format=>:json}
  • 20. 20 Wait! Aren’t namespaces and scopes essentially the same?
  • 21. 1 # api 2 scope :api do 3 scope :v1 do 4 post 'sign_up' => 'registrations#create' 5 post 'sign_in' => 'sessions#create' 6 delete 'sign_out' => 'sessions#destroy' 7 end 8 end 21 SCOPES WITHOUT ANY OPTIONS sign_up POST /sign_up(.:format) registrations#create sign_in POST /sign_in(.:format) sessions#create sign_out DELETE /sign_out(.:format) sessions#destroy
  • 22. 22 NAMESPACE vs SCOPE Namespace Scope By default, adds the name of the namespace to the name of the path, prefixes actual request path, and expects the controller to belong to appropriately named module. No defaults. All options are explicit. A shortcut method when you need to quickly nest a set of routes and controllers under some name. A powerful, customizable method to apply defaults to a group of routes.
  • 23. 1 namespace :admin do 2 root "admin#index" 3 end 4 5 root "home#index" 23 MULTIPLE ROOT ROUTES
  • 24. 1 constraints(id: /[A-Z][A-Z][0-9]+/) do 2 resources :photos 3 resources :accounts 4 end 24 CONSTRAINTS
  • 26. 1 def create 2 @product = Product.new(safe_params) 3 if @product.save 4 redirect_to :products, flash: { notice: t('shopr.products.create_notice') } 5 else 6 render action: 'new' 7 end 8 end 9 10 def update 11 if @product.update(safe_params) 12 redirect_to [:edit, @product], flash: { notice: t('products.update_notice') } 13 else 14 render action: 'edit' 15 end 16 end 26 DEFAULT ACTIONS
  • 27. 1 class ApplicationController < ActionController::Base 2 protect_from_forgery with: :exception 3 before_action :auth, only: :admin 4 helper_method :admin?, :about_page, :contacts_page 6 delegate :admin?, to: :current_user, allow_nil: true 7 skip_authorization_check 8 skip_before_action :authenticate_user! 27 HELPERS, BEFORE ACTIONS, ...
  • 28. 1 def create 2 @protocol = Protocol.create(protocol_params) 3 end 4 5 private 6 7 def protocol_params 8 params.require(:protocol).permit( 9 :competition_id, 10 :first_name, 11 :last_name, 12 :total_result, 13 :place 14 ) 15 end 28 PERMITTED PARAMS
  • 29. 1 def create 2 @product = Product.new(safe_params) 3 if @product.save 4 redirect_to :products, flash: { notice: t('shopr.products.create_notice') } 5 else 6 render action: 'new' 7 end 8 end 29 REDIRECT VS RENDER
  • 30. render :edit render action: :edit render "edit" render "edit.html.erb" render action: "edit" render action: "edit.html.erb" render "books/edit" render "books/edit.html.erb" 30 RENDER render template: "books/edit" render template: "books/edit.html.erb" render "/path/to/rails/app/views/books/edit" render "/path/to/rails/app/views/books/edit.html.erb" render file: "/path/to/rails/app/views/books/edit" render file: "/path/to/rails/app/views/books/edit.html.erb"
  • 31. 1 def create 2 Shopr::Order.transaction do 3 @order = Shopr::Order.new(safe_params) 4 @order.status = 'confirming' 5 if safe_params[:customer_id] 6 @customer = Shopr::Customer.find safe_params[:customer_id] 7 @order.first_name = @customer.first_name 8 @order.last_name = @customer.last_name 9 @order.company = @customer.company 10 @order.email_address = @customer.email 11 @order.phone_number = @customer.phone 12 if @customer.addresses.billing.present? 13 billing = @customer.addresses.billing.first 14 @order.billing_address1 = billing.address1 15 @order.billing_address2 = billing.address2 16 @order.billing_address3 = billing.address3 17 @order.billing_address4 = billing.address4 18 @order.billing_postcode = billing.postcode 19 @order.billing_country_id = billing.country_id 31 FAT CONTROLLERS
  • 32. 1 class VisitRequestsController < ApplicationController 2 before_action :authenticate_user!, only: %i[create destroy] 3 4 def show 5 VisitRequest::FinalConfirmation.call(visit_request, params) 6 flash_success(visit_request.status) and default_redirect 7 end 8 9 def create 10 VisitRequest::Create.call(current_user, event) 11 flash_success and default_redirect 12 end 13 14 def destroy 15 visit_request.destroy 16 flash_success and default_redirect 17 end 32 THIN CONTROLLERS
  • 34. 1 class Goal < ApplicationRecord 2 has_many :donations, dependent: :destroy 3 4 validates :title, presence: true 5 validates :amount, presence: true, numericality: true 6 7 def achieved? 8 donations_total >= amount 9 end 10 11 def donations_total 12 donations.sum(:amount) 13 end 14 end 34 MODELS
  • 35. class Goal < ApplicationRecord has_many :donations, dependent: :destroy end class Donation < ApplicationRecord belongs_to :goal end 35 ONE TO MANY/ONE RELATIONS Goal id:integer title:string Donation id:integer goal_id:integer title:string ... ...
  • 36. 36 MANY TO MANY RELATIONS Goal id:integer title:string Donation id:integer title:string ... ... class Goal < ApplicationRecord has_and_belongs_to_many :donations end class Donation < ApplicationRecord has_and_belongs_to_many :goal end donations_goals goal_id:integer donation_id:integer
  • 37. 37 ASSOCIATION RELATIONS Goal id:integer title:string Donation id:integer goal_id:integer ... ... class Goal < ApplicationRecord has_many :donations has_many :users, through: :donations end class Donation < ApplicationRecord belongs_to :goal belongs_to :user end class User < ApplicationRecord has_many :donations end User id:integer email:string ... user_id:integer
  • 38. 38 POLYMORPHIC RELATIONS User id:integer email:string Company id:integer name:string ... ... Address id:integer address:string addressable_id:integer addressable_type:string class Address < ApplicationRecord belongs_to :addressable, polymorphic: true end class User < ApplicationRecord has_many :address, as: :assressable end class Company < ApplicationRecord has_many :address, as: :assressable end
  • 39. 1 class Goal < ApplicationRecord 2 validates :title, presence: true 3 end 4 5 goal = Goal.create(title: “Buying a motocycle”).valid? # => true 6 goal = Goal.create(title: nil).valid? # => false 7 goal.errors.messages # => {title:["can't be blank"]} 39 VALIDATES
  • 40. 1 class Goal < ApplicationRecord 2 validates_associated :donations #(only in one of related model) 3 validates_with GoalValidator #class GoalValidator < ActiveModel::Validator 4 validate :expiration_date_cannot_be_in_the_past 5 6 def expiration_date_cannot_be_in_the_past 7 if expiration_date.present? && expiration_date < Date.today 8 errors.add(:expiration_date, "can't be in the past") 9 end 10 end 11 end 40 VALIDATES
  • 42. 1 <h2> <%= t 'goals.singular'> </h1> 2 3 < div class="col-md-12" > 4 <%= goal.title > 5 < /div> 6 < div class="col-md-12" > 7 <%= goal.description > 8 < /div> 9 < div class="col-md-12" > 10 <%= "Amount to reach: #{goal.amount}" > 11 < /div> 12 < div class="col-md-12" > 13 <%= "Current sum: #{goal.donations_total}" > 14 < /div> 42 TEMPLATES
  • 43. 1 <h2> <%= t 'goals.singular'> </h1> 2 3 < div class="col-md-12" > 4 <%= goal.title > 5 < /div> 6 < div class="col-md-12" > 7 <%= goal.description > 8 < /div> 9 < div class="col-md-12" > 10 <%= "Amount to reach: #{goal.amount}" > 11 < /div> 12 < div class="col-md-12" > 13 <%= "Current sum: #{goal.donations_total}" > 14 < /div> 43 .html.erb (ERB - Embedded Ruby)
  • 44. 1 h2 = t 'goals.singular' 2 3 .col-md-12 4 = goal.title 5 .col-md-12 6 = goal.description 7 .col-md-12 8 = "Amount to reach: #{goal.amount}" 9 .col-md-12 10 = "Current sum: #{goal.donations_total}" 11 .col-md-12 12 - unless goal.achieved? 13 = render 'shared/donations/form', path: donate_goal_path(goal) 44 .html.slim
  • 45. 1 $('#notifications_list').html( 2 "<%= escape_javascript(render('notifications/list', notifications: @notifications)) %>" 3 ); 4 5 $('.container-fluid .flash-msg').html( 6 "<%= escape_javascript(render 'layouts/alerts') %>" 7 ); 8 9 $("#send-notification-modal").modal("hide"); 45 .js.erb
  • 46. 1 json.extract! ticket, :id, :uid, :manager_id, :customer_name, :customer_email, :title, :body, :status_id 2 json.url ticket_url(ticket, format: :json) 46 .json.jbuilder
  • 47. new.html.slim 1 = render 'shared/donations/form', path: donate_goal_path(goal) edit.html.slim 1 = render 'shared/donations/form', path: donate_goal_path(goal) 47 PARTIALS _form.html.slim = simple_form_for :credit_card, url: path do |f| .form-inputs .row .col-md-6 = f.input :number, as: :string = f.input :cvc, as: :string = f.input :exp_month, as: :string = f.input :exp_year, as: :string
  • 48. 1 <h1>Listing Books</h1> 2 <table> 3 <tr> 4 <th>Title</th> 5 <th>Summary</th> 6 <th></th> 7 <th></th> 8 <th></th> 9 </tr> 10 <% @books.each do |book| %> 11 <tr> 12 <td><%= book.title %></td> 13 <td><%= book.content %></td> 14 <td><%= link_to "Show", book %></td> 15 <td><%= link_to "Edit", edit_book_path(book) %></td> 16 <td><%= link_to "Remove", book, method: :delete, data: { confirm: "Are you sure?" } %></td> 17 </tr> 18 <% end %> 19 </table> 48 PARTIALS. MAGIC
  • 49. /views/books/index.html.erb 1 <h1>Listing Books</h1> 2 <table> 3 <tr> 4 <th>Title</th> 5 <th>Summary</th> 6 <th></th> 7 <th></th> 8 <th></th> 9 </tr> 10 <%= render @books %> 11 </table> 49 PARTIALS. MAGIC /views/books/_book.html.erb 1 <tr> 2 <td><%= book.title %></td> 3 <td><%= book.content %></td> 4 <td><%= link_to "Show", book %></td> 5 <td><%= link_to "Edit", edit_book_path(book) %></td> 16 <td><%= link_to "Remove", book, method: :delete, data: { confirm: "Are you sure?" } %></td> 17 </tr>
  • 51. class HomeController < ApplicationController def index render 'events/show' end end 51 LAYOUTS class TalksController < ApplicationController helper_method :talks, :talk, :tags private def talks scope = Talk.published.includes(:event).order('events.finished_at desc') @talks ||= params[:tag] ? scope.tagged_with(params[:tag]) : scope @talks.page(params[:page]).per(12) end end class PagesController < ApplicationController helper_method :page def show page ? render(:show) : not_found end private def page @page ||= Page.find_by(url: params[:page_url]) end end class AuthController < ActionController::Base layout "devise" end
  • 52. doctype html html head title = content_for?(:title) ? yield(:title) : t('default_title') = stylesheet_link_tag 'app/application' meta name="theme-color" content="#ffffff" == render 'layouts/ga/head' body class="#{yield(:main_body_class)}" = yield(:before_header) == render 'layouts/app/flash' == render 'layouts/app/header' main = yield 52 application.slim vs admin.slim doctype html html head title = content_for?(:title) ? yield(:title) : t('default_admin_title') = stylesheet_link_tag 'admin/application' meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" body .ui.sidebar.inverted.vertical.menu == render 'layouts/admin/navigation' .pusher .ui.container#container == render 'layouts/admin/header' = yield == render 'layouts/admin/footer'
  • 53. views/events/index.slim ... tbody - events.each do |event| tr th = event.id td = resource_link(event) td = format_timestamp(event.started_at) td = format_timestamp(event.finished_at) 53 HELPERS helpers/... module TalksHelper def talk_link(talk, text = "", options = {}) link_to text, polymorphic_path(talk), options end end module ApplicationHelper def format_timestamp(timestamp, time: true, delimiter: '-') return unless timestamp formatted_date = timestamp.strftime('%Y %b %d') formatted_time = timestamp.strftime('%H:%M') return formatted_date if !time "#{formatted_date} #{delimiter} #{formatted_time}" end
  • 54. ● Fat model and Skinny controller ● Business logic should always be in the model ● The view should have minimal code ● Use helpers! ● Use models ● DRY (Don't Repeat Yourself) 54 BEST PRACTICES