经典 MVC 架构

  • 路由(router)根据请求路径来选择控制器(Controller)
  • 控制器选择合适的视图(View),渲染给用户
  • 如果涉及到数据库,控制器会调用 Modle 拿数据
  • Modle 会调用 ORM 来简化数据操作
  • ORM 会直接与数据库打交道
  • Rails 的 ActiveRecord 是一个超级强大的 ORM

这么看起来,Rails 很简单?

  • 上面的架构图隐藏了 Rails 的大部分功能
  • 简单的东西往往更耐用(Rails 始于 2004 火于 2012)
  • 但那时的中国开发者居然喜欢上了 PHP
  • 目前大部分的开发者对 Rails 的印象就是慢,其实这么多年过去了他已经不慢了

Rails 的依赖 – Rack

功能

  • 提供非常简单的 API
  • 封装了 HTTP request 和 HTTP response
  • 提出了中间件模型

一个简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
require 'rack'
require 'webrick'

class RackApp
def call(env)
[
200,
{},
['<h1>Rack</h1>']
]
end
end

Rack::Handler::WEBrick.run RackApp.new, Host: '0.0.0.0', Port: '1234'
// lambda
require 'rack'
require 'webrick'
Rack::Handler::WEBrick.run ->(env) {
[
200,
{"Content-Type" => "text/html;charset=utf-8"},
["<h1>rack_lambda</h1>", "<p>第二行内容</p>"]
]
}, Host: '0.0.0.0', Port: 1234

请求

用法env

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
require 'rack'
require 'webrick'

class RackApp
def call(env)
request = Rack::Request.new(env)
p "你请求的路径是:#{request.path}"
[
200,
{},
['<h1>Rack</h1>']
]
end
end

Rack::Handler::WEBrick.run RackApp.new, Host: '0.0.0.0', Port: '1234'
s

中间件模型

用户请求之后,服务器响应之前,放在

  • Rack 模仿管道 pipe 实现了自己的中间件模型
  • 也就是把 req 和 res 依次传给不同类,得到最终结果
  • 举例CommonLogger
    1
    2
    3
    4
    5
    6
    7
    8
    9
    require 'rack'
    require 'webrick'
    app = Rack::Builder.app do
    use Rack::CommonLogger
    run -> (env){
    [200, {}, ['<h1>CommonLogger</h1>']]
    }
    end
    Rack::Handler::WEBrick.run app, Host: '0.0.0.0', Port: 1234, AccessLog: []

    先用 AccessLog: [] 禁用默认的 log
    再使用 use Rack::CommonLogger 来引入中间件

对比其他

发现 Rack 和 Express 很像

  • 查阅发现,Express 一开始就是受 Rack 启发而写的
  • Issue

Rack 和 Rails 的区别

  • Rack 只是对请求和响应进行了简单封装
  • 由于它很简单易学,所以他被各种框架和中间件采用了
  • Rails 包涵了你需要的一切功能,也包含了 Rack

创建 Rails 项目

首先为了防止 gem install 很慢

  • gem install 后面添加 –verbose
  • 编辑 ~/.gemrc ,最后一行写入 gem: "--no-document",因为默认 gem 在下载包的时候会下载文档,而一般这个文档没什么用
  • Ruby China说的做
gem 是什么
  • 类似于 Node.js 里面的 npm
  • gem install 用于全局安装依赖
  • 局部安装依赖可以用 bundle install

步骤

  • gem install rails –version=6.0.2.2
  • rails –help 看看选项
  • rails new morney-rails1 --database=postgresql --skip-action-mailbox --skip-action-text --skip-sprockets --skip-javascript --skip-turbolinks --skip-system-test --skip-test --api --skip-webpack-insall(postgresql 数据库,不要收件箱,不要富文本,不要 sprockets,不要 js,不要turbolinks,不要系统测试,不要测试,使用api模式,api模式是一个比较轻量的模式,不要 webpack)
  • 在 .gitignore 里添加 .idea

Hello Rails

在 routes.rb 里写
1
2
3
Rails.application.routes.draw do
get '/hello', to: 'first#hello'
end
创建 app/controllers/first_controller.rb
1
2
3
4
5
class FirstController < ApplicationController
def hello
render plain: 'hello'
end
end
运行命令 bin/rails serve (可缩写为s)
发现数据库报错,因为默认 rails 是有数据库的,但是却发现连接不上

安装数据库

1
docker run -v morney-rails1-data:/var/lib/postgresql/data -p 5001:5432 -e POSTGRES_USER=morney -e POSTGRES_PASSWORD=123456 -d postgres:12.2

配置数据库:config/database.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: morney
password: 123456
host: localhost // 或者 192.168.99.100 你的 docker ip
port: 5001

development:
<<: *default
database: morney_rails1_development
test:
<<: *default
database: morney_rails1_test
production:
<<: *default
database: morney_rails1_production
username: morney_rails1
password: <%= ENV['MORNEY_RAILS_1_DATABASE_PASSWORD'] %>

注意账户密码是安装psql时帐号密码,
注意生产环境的密码不能直接写在配置文件中,一般以环境变量的形式

创建数据库

1
./bin/rails db:create

让 rails 根据配置文件自动创建数据库

成功运行

配置修改文件自动重启

只有 windows 用户才存在这个问题(新版好像也不会出现这个问题了)

  • 打开 config/environments/development.rb
  • 将文件末尾的 config.file_watcher 接触注释
  • 然后在 Gemfile 加上这两句话
    1
    2
    3
    4
    group :development do
    gem 'listen'
    gem 'wdm', '>= 0.1.0', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
    end
  • 安装依赖 bin/bundle install

渲染 JSON

1
2
3
4
5
class FirstController < ApplicationController
def hello
render json: {name: 'zch', age: 18}
end
end

渲染 HTML

如果前后端彻底分离

  • 那么 Rails 服务器一般是不负责 HTML 的
  • 我们会让 nginx 来负责 HTML

如果分离不彻底呢?

  • Rails 也是可以用来渲染 HTML 的
  • 在 application_controller 里加上 include ActionView::Layouts
  • Rails 就会按照上约定寻找对应的 HTML 模版
  • 如果需要在 HTML 中插入 ruby,就以 .erb 结尾
  • <% ___ %> 用于写语句(一般没有值),如:if/else
    1
    2
    3
    4
    5
    6
    7
    <h1>我是一个页面</h1>
    <% a = 3 %>
    <% if a > 5%>
    a 很大
    <% else %>
    a 很小
    <% end %>
  • <%= __ %> 用于写表达式(一定有值),如: @xxx
    1
    2
    3
    <h1>我是一个页面</h1>
    <% a = 3 %>
    <%= a * 2 %>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class FirstController < ApplicationController
    def hello
    @xxx = 'Controller 里面的 xxx 变量'
    render 'first/hello'
    end
    end

    <h1>我是一个页面</h1>

    <%= @xxx %>
  • 如果让 Nginx 负责 HTML,就不用使用这些标签了

如何使用 layout

代码复用

  • 默认的 layout 是 application.html.erb
  • 可以使用 <%= yield %> 作为插槽
  • 可以使用 <%= yield :footer %> 作为具名插槽
    1
    2
    3
    4
    5
    6
    7
    8
    <body>
    <%= yield %>
    <% if content_for?(:footer) %>
    <footer style="background-color: #c03">
    <%= yield :footer %>
    </footer>
    <% end %>
    </body>