Ruby on Rails 快速入门

1,527 阅读12分钟

Rails

ruby china wiki:ruby-china.org/wiki

The Rails Doctrine: ruby-china.org/wiki/the-ra…

Guides:

ruby toolbox: www.ruby-toolbox.com/

0. 前言

dev

最近国内互联网就业市场持续低迷,作为主要着力于前端技术的我,也开始探索后端、部署相关的技术。之前除了接触过 Node.js,还有一门我一直想学习和应用的语言就是 Ruby 了。

第一次听说 Ruby 是在 2020 年的夏天,当时还是土木 🐶 的我,从前辈口中听说了 Ruby 这个词,他说他要亲自教我,但是没有天赋的我还是去土木和设计行业卷了一阵子才正式返航计算机。虽迟但到,在 2023 年的夏天,我写下这一篇快速入门,也算是一种重逢吧!

Ruby 同样是一门面向对象的语言,抛开性能不谈,它的语法非常简单,行文甚至比 Python 还要简洁,而且足够语义化,这也是我选择它的重要原因。Rails 作为 Ruby 的开发框架,和大部分其他语言框架一样采用 MVC 架构(附录 A 中我添加了 MVC 的说明),它提供了一些工具和函数来快速开发 Web 应用程序。另一方面,它还支持主流的数据库,比如:MySQL、PostgreSQL 和 SQLite 等。

目前,我正在开发一个 H5 小应用,数据库使用的是 PostgreSQL,总体体验下来,还算 OK,如果要问我,什么是 Rails?

Rails = Routes + MVC

以下是正文。

1. 创建 Rails API

安装 ruby 的过程我就省略了,安装完成后执行 ruby --version 来查看版本。

1.1 生成 rails api 服务

在 docker 容器中执行以下命令生成一个 rails api 服务

gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/
bundle config mirror.https://rubygems.org https://gems.ruby-china.com
gem install rails -v 7.0.2.3
pacman -S postgresql-libs
cd ~/repos
rails new --api --database=postgresql --skip-test catdou-1
code catdou-1
bin/rails server

会报错,需要启动一个数据库,如下。

1.2 启动 postgres 数据库

在宿主机终端执行以下命令启动一个 postgres 数据库(版本为 14)

docker run -d --name db-for-catdou -e POSTGRES_USER=catdou -e POSTGRES_PASSWORD=123456 -e POSTGRES_DB=catdou_dev -e PGDATA=/var/lib/postgresql/data/pgdata -v catdou-data:/var/lib/postgresql/data --network=network1 postgres:14

1.3 连接数据库

配置开发环境变量,config/database.yml

development:
  <<: *default
  database: catdou_dev
  username: catdou
  password: 123456
  host: db-for-catdou

重新运行 server

bin/rails server

1.4 model 定义

user 是表名,email 和 name 是字段

bin/rails g model user email:string name:string

生成数据库 migrate 文件以及 model 文件:

  • db/migrate/20230607152514_create_users.rb

    class CreateUsers < ActiveRecord::Migration[7.0] # 定义一个类,用于创建表
      def change
        create_table :users do |t| # 方法名为 create_table,表名为 users
          t.string :email # 创建字段 email,类型为 string
          t.string :name # 创建字段 name,类型为 string
          
          t.timestamps # 创建时间字段:updated_at 更新时间,created_at 创建时间
        end
      end
    end
    
  • app/models/user.rb

    class User < ApplicationRecord
    end
    

1.5 数据库迁移同步

将创建的 user 模型迁移到 postgres 数据库(会生成一个 users 表)

bin/rails db:migrate
bin/rails db:rollback step=1 # 如果需要的话,可以用它将数据库回退到上一步

1.6 controller 定义

生成 users 的 controller,包括 show 和 create 方法

bin/rails g controller users show create

生成 users 的控制器文件以及添加相应路由

  • app/controllers/users_controller.rb(源文件已做修改)

    class UsersController < ApplicationController
      def show
        user = User.find_by_id params[:id] # 在 users 表中找到对应 id 的那一行记录
        if user
          render json: user # 如果存在,返回 user
        else
          head 404 # 如果不存在,返回一个 404 响应码
        end
      end
    
      def create
        # 在 users 表中新建一条记录,email 是 'eric@x.com',name 是 'eric'
        user = User.new email: 'eric@x.com', name: 'eric'
        if user.save # 如果保存成功了
          render json: user # 返回给前端 json 数据(新建的 user)
        else # 否则
          render json: user.errors # 返回给前端 json 数据(user 的报错)
      end
    end
    
  • config/routes.rb(路由文件中会自动添加以下两行内容)

    get 'users/show'
    get 'users/create'
    

    路由文件需要修改,上面自动生成的两句删除不用,添加以下两句代码到 routes.rb 中:

    get "/users/:id", to: "users#show" # 当用户对 "/users/:id" 发起 get 请求时,对应 controller 动作为 show
    post "/users/", to: "users#create" # 当用户对 "/users/" 发起 post 请求时,对应 controller 动作为 create
    

1.7 model 数据校验

数据校验一般在 model 层进行。假设,需要对 users 表的 email 字段进行必填校验。

那么在 app/models/users.rb 中添加以下代码即可:

class User < ApplicationRecord
  validates :email, presence: true # email 是必填的
end
  • validates :email 告诉它要校验的字段是 email
  • presence: true 校验规则是 email 必须到场(presence)

2. RESTful API

restfulapi.cn/

medium.com/@haldar.mah…

HTTP MethodAPI PathDescription
GETapi/v1/zoos列出所有动物园
POSTapi/v1/zoos新建一个动物园
GETapi/v1/zoos/:id获取某个指定动物园的信息
PUTapi/v1/zoos/:id更新某个指定动物园的全部信息
PATCHapi/v1/zoos/:id更新某个指定动物园的部分信息
DELETEapi/v1/zoos/:id删除某个动物园
GETapi/v1/zoos/:id/animals列出某个指定动物园的所有动物
DELETEapi/v1/zoos/:id/animals/:id删除某个指定动物园的指定动物

3. API 开发

根据产品需求和 UI、UX 设计数据库与 API。

3.1 路由表生成

利用 namespace 构建以以 /api/v1 开头的 api 路由表:

config/routes.rb

Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      # /api/v1
      resources :validation_codes, only: [:create]
      resource :session, only: [:create, :destroy]
      resource :me, only: [:show]
      resources :items
      resources :tags
      # resources :taggings, only: [:create, :index, :destroy]
    end
  end
end

生成路由表:bin/rails routes

$ bin/rails routes
                 Prefix Verb URI Pattern                        Controller#Action
# 验证码(申请或删除账号时)
api_v1_validation_codes POST /api/v1/validation_codes(.:format) api/v1/validation_codes#create
# 登录登出
       api_v1_session DELETE /api/v1/session(.:format)          api/v1/sessions#destroy
                        POST /api/v1/session(.:format)          api/v1/sessions#create
# 个人信息(获取姓名、邮箱)
               api_v1_me GET /api/v1/me(.:format)               api/v1/mes#show
# 所有账单(获取、新增)
            api_v1_items GET /api/v1/items(.:format)            api/v1/items#index
                        POST /api/v1/items(.:format)            api/v1/items#create
# 单笔账单(获取、更新、删除)
             api_v1_item GET /api/v1/items/:id(.:format)        api/v1/items#show
                       PATCH /api/v1/items/:id(.:format)        api/v1/items#update
                         PUT /api/v1/items/:id(.:format)        api/v1/items#update
                      DELETE /api/v1/items/:id(.:format)        api/v1/items#destroy
# 所有标签(获取,新增)
             api_v1_tags GET /api/v1/tags(.:format)             api/v1/tags#index
                        POST /api/v1/tags(.:format)             api/v1/tags#create
              api_v1_tag GET /api/v1/tags/:id(.:format)         api/v1/tags#show
                       PATCH /api/v1/tags/:id(.:format)         api/v1/tags#update
                         PUT /api/v1/tags/:id(.:format)         api/v1/tags#update
                      DELETE /api/v1/tags/:id(.:format)         api/v1/tags#destroy
# 其他

3.2 model > migrate > controller

按照附录 A 依次创建 model、迁移数据库,创建 controller

3.3 分页功能

4. 单元测试(RSpec)- 过一遍,忘了就去问 chatGPT

rspec # BDD for Rails(行为驱动开发)

目的:对 model、controller 进行测试

前提:需要一个测试环境的数据库

现在已经有开发环境的数据库(catdou_dev)了,现在以这个数据库为例,做数据表的迁移。

在迁移之前,需要配置并新建一个测试环境的数据库(catdou_test)。

4.1 测试数据库:环境参数配置与新建

环境参数配置

config/database.yml

# 其他环境配置
test:
  <<: *default
  database: catdou_test
  username: catdou
  password: 123456
  host: db-for-catdou
# 其他环境配置

新建数据库

bin/rails db:create RAILS_ENV=test

4.2 数据表迁移

bin/rails db:migrate RAILS_ENV=test

如果缺少 model,执行 rails 提供的 model 生成命令即可。

4.3 rspec 依赖安装

Add rspec-rails to both the :development and :test groups of your app’s Gemfile:

# Run against this stable release
group :development, :test do
  gem 'rspec-rails', '~> 6.0.0'
end

Then, in your project directory:

# Download and install
$ bundle install

# Generate boilerplate configuration files
# (check the comments in each generated file for more information)
$ rails generate rspec:install
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

4.4 rspec 使用之 model 测试

Creating boilerplate specs with rails generate: (如果 user 模型已经存在就不用重复生成了!直接运行第二个指令。)

# RSpec hooks into built-in generators
$ rails generate model user
      invoke  active_record
      create    db/migrate/20181017040312_create_users.rb
      create    app/models/user.rb
      invoke    rspec
      create      spec/models/user_spec.rb

# RSpec also provides its own spec file generators
$ rails generate rspec:model user
      create  spec/models/user_spec.rb

# List all RSpec generators
$ rails generate --help | grep rspec

Running specs:

# Default: Run all spec files (i.e., those matching spec/**/*_spec.rb)
$ bundle exec rspec

# Run all spec files in a single directory (recursively)
$ bundle exec rspec spec/models

# Run a single spec file
$ bundle exec rspec spec/controllers/accounts_controller_spec.rb

# Run a single example from a spec file (by line number)
$ bundle exec rspec spec/controllers/accounts_controller_spec.rb:8

# See all options for running specs
$ bundle exec rspec --help

🌰:测试 user 模型:rails generate rspec:model user

之后会创建出一个 spec/models/user_spec.rb 文件(已改源码):

require "rails_helper"

RSpec.describe User, type: :model do
  it '有 email' do
    user = User.new email: 'eric@x.com' # 新建一个实例,有一个 email 为 'eric@x.com'
    expect(user.email).to eq('eric@x.com') # 期望A等于B
  end
end

运行测试用例:bundle exec rspec

$ bundle exec rspec
.
Finished in 0.11852 seconds (files took 4.61 seconds to load)
1 example, 0 failures

一个点代表一个测试用例成功执行。

4.5 rspec 使用之 controller 测试

🌰:测试 validation_codes controller:bin/rails g rspec:request validation_codes

app/controllers/api/v1/validation_codes_controller.rb

class Api::V1::ValidationCodesController < ApplicationController
  def create
    code = SecureRandom.random_number.to_s[2..7] # 随机数取6位
    validation_code = ValidationCode.new email: params[:email],
                                         kind: "sign_in",
                                         code: code
    if validation_code.save
      head 201
    else
      render json: { errors: validation_code.errors }
    end
  end
end

spec/requests/validation_codes_spec.rb

require "rails_helper"

RSpec.describe "ValidationCodes", type: :request do
  describe "POST validation_codes#create" do
    it "can be created" do
      post "/api/v1/validation_codes", params: { email: "eric@x.com" }
      expect(response).to have_http_status(201)
    end
  end
end

附录

附录A MVC 架构

+----------------------+
|   User's Request     |
+----------------------+
           |
           v
+----------------------+
|   Controller Layer   |
+----------------------+
           |
           v
+----------------------+
|    Model Layer       |
+----------------------+
           |
           v 
+-----------------------+
|   Database or Service |
+-----------------------+
           |
           v
+-----------------------+
|   Response to User    |
+-----------------------+

用户发送请求后,请求会进入 Controller 层。Controller 层负责接收和解析请求,处理业务逻辑,然后将数据传递给 Model 层进行处理。Model 层负责数据的读取、修改、删除等操作,并将处理后的结果保存到数据库或者其他服务中。最终,Controller 层将经过处理的数据返回给用户。整个过程中,View 层负责将数据渲染成最终的呈现形式(如 HTML 页面、JSON 响应等),但这并不是 MVC 模式的必要组成部分,因为 Ruby on Rails 中有一个概念叫做 Action View,它将 View 层的功能集成在了 Controller 层中,使得开发更加高效简洁。【以上绘图和回答来自 ChatGPT】

A-1 view

view 层,用于数据渲染和视图展示,用户访问路径时会向后端发送请求。

A-2 controller

controller 层,负责响应用户的请求(HTTP:GET、POST、PUT/PATCH、DELETE),这些请求通过路由映射到 controller 中,执行 CRUD 相关操作。

  • 从请求对象中获取参数
  • 访问 session,查看是否有权限
  • 执行查询操作
  • 响应类型:HTML 页面、JSON API 响应、文件下载等
  • CSRF(跨站点请求伪造)保护功能

A-3 model

model 层,用于数据处理和存储。比如:数据的验证、转换、过滤、存储。

附录B curl 命令

B-1 GET 请求

curl http://127.0.0.1:3000/api/users/1
curl http://127.0.0.1:3000/api/users/1 -v # -v 表示 verbose 详细模式,请求头、响应头之类的会被打印出来

B-2 POST 请求

curl -X POST http://127.0.0.1:3000/api/users

附录C VSCode 插件 - PostgreSQL

postgresql explorer: postgresql

附录D 快速构建 API

前提:构建了 Rails 服务(并连接了数据库):rails new --api --database=postgresql --skip-test catdou-1

D-1 routes

第一步、routes,生成路由表: bin/rails routes

🌰:利用 namespace 生成一个以 /api/v1 开头的 api 路由表:(对应 controller 文件要放在 controllers/api/v1 目录下)

config/routes.rb

Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      # /api/v1
      resources :validation_codes, only: [:create]
      resource :session, only: [:create, :destroy]
      resource :me, only: [:show]
      resources :items
      resources :tags
    end
  end
end

D-2 model

第二步、model,生成(User)模型并在数据库中生成(users)表:bin/rails g model user email:string name:string

Tip:模型是单数,如:User,对应的数据库表名是复数,如:users

  • model file(User 模型相关,数据验证可以在这里做):app/models/user.rb
  • db migrate file(users 表对应的数据库迁移文件,相关的字段类型定义在这里设定,这个文件决定了如何创建数据表):20230607152514_create_users.rb

为什么model 类是单数,而迁移文件的表会自动对应到复数?ruby-china.org/wiki/the-ra…

和 Matz 一样,我有时候为了实现我的理念也会做出一些蠢事。一个例子便是 Inflector,一个可以对英文做不规则转化的类,譬如 Person 类对应到 People 表、Analysis 对应到 Analyses,Comment 对应到 Comments 等。这个东西现在已经是 Rails 不可分割的元素了,但早期争议的怒火延烧到今日,伤口仍未愈合,由此可见其重要性。

以及指导手册上的 🌰

  • Model Class - Singular with the first letter of each word capitalized (e.g., BookClub).
  • Database Table - Plural with underscores separating words (e.g., book_clubs).
Model / ClassTable / Schema
Articlearticles
LineItemline_items
Deerdeers
Mousemice
Personpeople

D-3 migrate

第三步、migrate,迁移文件,创建数据库表(users):bin/rails db:migrate

  • 如有失败,则在后面加上环境变量:bin/rails db:migrate RAILS_ENV=development
  • 数据库回滚:bin/rails db:rollback
  • 数据库删除:bin/rails db:drop

D-4 controller

第四步、controller,生成控制器(要写复数):bin/rails g controller Api::V1::Users show

🌰:因为第一步的路由文件设置了 namespace,所以 controller 文件中第一行的 Class 类要以 Api::V1:: 开头!

class Api::V1::UsersController < ApplicationController
end

附录E HTTP 状态码

developer.mozilla.org/zh-CN/docs/…

HTTP 状态码含义备注
200OK 成功
201Created 创建成功资源创建成功
404Not Found 未找到
403Forbidden 拒绝授权
401Unauthorized 未登录缺少身份验证凭证
422Unprocessable Entity 无法处理,参数有问题
402Payment Required 需付费
412Precondition Failed 不满足前提条件
429Too Many Requests 请求太频繁频次限制
400Bad Request 其他错误请求错误详情放在 body 里
  • 4xx 为客户端错误
  • 5xx 为服务器错误

附录F 数据类型

  1. string:字符串类型,用于表示文本数据。
  2. text:字符串类型,用于表示大文本数据。
  3. integer:整数类型,用于表示整数数据。
  4. float:浮点数类型,用于表示浮点数数据。
  5. decimal:高精度小数类型,用于表示精度比较高的小数数据。
  6. datetime:日期和时间类型,用于表示日期和时间数据。
  7. timestamp:时间戳类型,用于记录数据的创建和更新时间。
  8. time:时间类型,用于表示时间数据。
  9. date:日期类型,用于表示日期数据。
  10. binary:二进制类型,用于存储二进制数据,如图片等。
  11. boolean:布尔类型,用于表示真假数据。

附录G Gem 使用

Ruby 是“红宝石”的意思,而 Gem 是“宝石”的意思。

Ruby

Gem 相当于 NPM,都是包管理器。

Gemfile, Gemfile.lock 相当于 Node 项目中的 package.json, package-lock.json,描述了项目需要的依赖关系。

但一般使用 bundle 来安装项目所需的所有 gem 包bundle installbundle(查看安装详情:bundle --verbose

换国内的源,加速下载:打开 Gemfile,找到第一行,将默认的 source "https://rubygems.org" 换成 source "https://gems.ruby-china.com/"

注意:每次安装完成后,因为依赖变了,和前端一样,需要重启一下服务!


gem 常用指令:

  1. gem install <gem_name>:安装指定的 gem 包。
  2. gem uninstall <gem_name>:删除指定的 gem 包。
  3. gem update <gem_name>:更新指定的 gem 包。
  4. gem list:列出所有已安装的 gem 包。
  5. gem search <keyword>:搜索包含关键词的 gem 包。
  6. gem env:显示当前 gem 环境的配置信息。
  7. gem sources:列出所有可用的 gem 源。
  8. gem sources --add <source_uri>:添加一个新的 gem 源。
  9. gem help:查看 gem 命令的帮助信息。

bundle 常用指令:

  • bundle install:根据 Gemfile.lock 文件中的依赖关系,安装所有需要的 gem 包。如果 Gemfile.lock 文件不存在,它会先生成这个文件,然后再进行安装。当然,如果我们在 Gemfile 中添加了新的依赖项,执行 bundle install 还会安装这些新的依赖项。

  • bundle update:检查 Gemfile 中的所有 gem 包的最新版本,并更新 Gemfile.lock 文件,同时安装更新后的 gem 包。

  • bundle exec:运行特定版本的 gem 包。如果你在多个项目间使用了不同版本的 gem 包,可以使用这个命令来使用指定项目的 gem 包。这个命令用法如下:

    bundle exec <command>
    

    其中 <command> 指运行的命令,例如 rails server 等。

  • bundle package:将项目所需的 gem 包打成 gem 包并放到 vendor/cache 目录下,方便离线安装。

附录H zsh 常用命令

zsh 是一种 Unix shell,是 Bourne shell(sh)的扩展,也是 Bash、Ksh 等 shell 的改进版。它提供了更多的功能和自定义选项,如支持自动补全、历史命令搜索、别名等。

  1. cd:切换当前工作目录

    cd ~ # 回到家目录
    cd .. # 回到上层
    cd - # 回到上一次
    
  2. ls:列出当前目录的文件和子目录

  3. pwd:显示当前所在的工作目录的完整路径

  4. alias:创建别名

  5. unalias:删除别名

  6. source:重新执行当前shell环境

  7. emacs:进入emacs编辑器模式

  8. vi:进入vi编辑器模式

  9. history:查看历史命令

  10. echo:输出文本信息

  11. grep:查找文件中的文本内容

  12. chmod:修改文件或目录的权限

  13. chown:修改文件或目录的所有者

  14. rm:删除文件或目录

  15. cp:复制文件或目录

  16. mv:移动文件或目录

  17. mkdir:创建新目录

  18. rmdir:删除空目录

  19. cat:查看文件内容

  20. touch:创建新空文件或更改文件的时间戳。

以上,如有谬误,还请斧正,希望这篇文章对你有所帮助,感谢您的阅读~