Rails 学习杂记一
系统学习 rails
Github项目地址
MVC
-
Controller
应用逻辑 -
Model
数据 -
View
用户界面 Model -> Controller -> View
目录
-
app/
=>assets 资源
,controller 模型
,helpers 主要服务于 views
,mailers 邮件
,models 数据库层面的东西
,view 视图
-
bin/
=> 用于启动应用的 rails 脚本,以及用于安装、更新、部署或运行应用的其他脚本。 -
config/
=> 配置应用的路由、初始化、数据库等 -
db/
=> 当前数据库的模式,以及数据库迁移文件 -
log/
=> 应用请求日志 ...
Controller
- rails 里面所有的
controller
都继承ApplicationController
-
ApplicationController
继承ActionController
-
welcome_controller.rb
里面的index
会默认查找views/welcome/index.html.erb
安装 bootstrap
Model
- 数据映射 Mapping ==>
Database <-> Interface
Virtual Data <-> Interface
可以是表, 也可以是虚拟数据 - Ruby 里面是
ActiveRecord
==> ORM - Rails 对关系型数据库的支持
SQlite, MySQL, PostgreSQL, Oracle, etc...
创建 User Model
rails g model user
- 模型的命名使用 单数的 形式
user
, 数据库在映射的数据库表映射为复数形式CreateUsers
-
controller
的命名 复数的 形式
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :username
t.string :password
# 自动维护 时间戳 创建 & 更新
t.timestamps
end
end
end
bin/rails db:migrate
rails c
- 打开一个当前的控制台
# User
# 创建一条 user
User.create username: 'yym', password: 123456
# 查询所有
User.all
# 查询第一条
User.first
# 更新
user = User.first
user.username = 'yym_2'
user.save
# 删除
user.destroy
routes
users
method | path | action
列表
-
get /users users#index
输出当前资源的列表 ==> users -
post /users users#create
创建一个资源 ==> users
表单相关
-
get /users/new users#new
和表单相关, 提供用户输入 ==> new_user -
get /users/:id/edit users#edit
和表单相关, 编辑一个资源 ==> edit_user
单一
-
get /users/:id users#show
获取一个资源 ==> user -
patch /users/:id users#update
更新一个资源 ==> user -
put /users/:id users#update
更新一个资源 ==> user -
delete /users/:id users#destroy
删除一个资源 ==> user
Session & Cookie
-
先了解 HTTP -> 短连接 无状态
-
http 无状态, 怎么保存状态的呢? -> Cookies
- http response 会 Set-Cookies
- 前端发送请求可以 cookie: 后端返回的值
-
session 和 cookie 的区别?
- Session是后端实现 -> session 的 key 存储基于 cookie, 也可以基于后端存储, 比如数据库
- Cookie是浏览器的实现
- Session 支持存储后端的数据结构, 比如对象; Cookie只支持字符串
- session没有大小限制; cookie 大小有限制
-
session 操作
- session 方法; session key; session 加密的
-
cookie 操作
- cookie 方法; cookie 有效期, 域名等信息
Controller Filter
-
before_action
-> action 执行前 around_action
after_action
我们希望 看到用户列表, 需要登录
before_action :auth_user
private
def auth_user
unless session[:user_id]
flash[:notice] = '请登录'
redirect_to new_session_path
end
end
Routes 详解
规定了特定格式的 URL
请求到后端 Controller
的 action
的分发规则
自上而下的路由
例子
# GET /users/2
# method url controller#action
get '/users/:id', to: 'users#show'
# or
get '/users/:id' => 'users#show'
# 命名路由 => 通过 :as 选项,我们可以为路由命名
# logout GET /exit(.:format) sessions#destroy
get 'exit', to: 'sessions#destroy', as: :logout
RESTful 资源路由
- 浏览器使用特定的 HTTP 方法向 Rails 应用请求页面,例如 GET、POST、PATCH、PUT 和 DELETE。每个 HTTP 方法对应对资源的一种操作
- 同一个 url 可以拥有不同的请求方式
-
定义资源的method ==> resources :names
==>resources :photos
HTTP 方法 路径 控制器#动作 用途 GET /photos/new photos#new 返回用于新建照片的 HTML 表单 GET /photos/:id/edit photos#edit 返回用于修改照片的 HTML 表单 GET /photos photos#index 显示所有照片的列表 POST /photos photos#create 新建照片 GET /photos/:id photos#show 显示指定照片 PATCH/PUT /photos/:id photos#update 更新指定照片 DELETE /photos/:id photos#destroy 删除指定照片
# 同时定义多个资源
resources :photos, :books, :videos
# ==>
resources :photos
resources :books
resources :videos
# 单数资源
# 有时我们希望不使用 ID 就能查找资源。例如,让 /profile 总是显示当前登录用户的个人信息
get 'profile', to: 'users#show'
# ==>
get 'profile', to: :show, controller: 'users'
命名空间 namespace
namespace :admin do
resources :articles
end
HTTP | 方法 | 路径 | 控制器#动作 | 具名辅助方法 |
---|---|---|---|---|
GET | /admin/articles | admin/articles#index | admin_articles_path | |
GET | /admin/articles/new | admin/articles#new | new_admin_article_path | |
POST | /admin/articles | admin/articles#create | admin_articles_path | |
GET | /admin/articles/:id | admin/articles#show | admin_article_path(:id) | |
GET | /admin/articles/:id/edit | admin/articles#edit | edit_admin_article_path(:id) | |
PATCH/PUT | /admin/articles/:id | admin/articles#update | admin_article_path(:id) | |
DELETE | /admin/articles/:id | admin/articles#destroy | admin_article_path(:id) |
如果我想要 /articles
, 而不是 /admin/articals
, 在 admin
文件夹下
# url 改变了, 但是 文件还是在 admin 对应的文件夹下
# 可以设置多个 resources
# module 模块 admin 模块下
scope module: 'admin' do
resources :articles, :comments
end
# ==> 单个资源科以这样声明
resources :articles, module: 'admin'
但是如果我想要 /admin/articals
, 但是不在 admin
文件夹下
# url 是 /admin/articles, 不在 admin 文件夹下
scope '/admin' do
resources :articles, :comments
end
# 单个资源
resources :articles, path: '/admin/articles'
嵌套路由
-
url
语义更加明确
class User < ApplicationRecord
has_many :blogs
end
class Blog < ApplicationRecord
belongs_to :user
end
# 嵌套资源的层级不应超过 1 层。
resources :users do
resources :blogs
end
- 排除不需要的 action 和 请求方式
resources :users, only: [:index, :destroy]
集合路由(collection route)和成员路由(member route)
resources :users do
# 成员路由, 一般 带 id 的
# post /users/:id/status => users#status
member do
post :status
end
# 集合路由, 不带 id 的
# get /users/online => users#online
collection do
get :online
end
end
# 如果只需要定义一条方法 => :on 选项
resources :users do
post :status, on: :member
get :online, on: :collection
end
非资源式路由
除了资源路由之外,对于把任意 URL 地址映射到控制器动作的路由,Rails 也提供了强大的支持。和资源路由自动生成一系列路由不同,这时我们需要分别声明各个路由
# () 里面的内容为可选参数
# /photos/1 or /photos photos#display
# 绑定参数
get 'photos(/:id)', to: :display
# 动态片段
# /photos/1/2 params[:id] = 1 params[:user_id] = 2
get 'photos/:id/:user_id', to: 'photos#show'
# 静态片段
# /photos/1/with_user/2
get 'photos/:id/with_user/:user_id', to: 'photos#show'
# 查询字符串
# /photos/1?user_id=2 -> id: 1 user_id: 2
get 'photos/:id', to: 'photos#show'
# 定义默认值
# /photos/12 -> show action; params[:format] = 'jpg'
get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' }
# http 方法约束
match 'photos', to: 'photos#show', via: [:get, :post]
match 'photos', to: 'photos#show', via: :all
# 片段约束
# /photos/A12345
get 'photos/:id', to: 'photos#show', constraints: { id: /[A-Z]\d{5}/ }
# ->
get 'photos/:id', to: 'photos#show', id: /[A-Z]\d{5}/
# 重定向
get '/stories', to: redirect('/articles')
get '/stories/:name', to: redirect('/articles/%{name}')
自定义资源路由
# :controller 选项用于显式指定资源使用的控制器
resources :photos, controller: 'images'
# -> get /photos iamges#index
# 命名空间中额控制器目录表示法
resources :user_permissions, controller: 'admin/user_permissions'
# 覆盖具名路由辅助方法
# GET /photos photos#index as -> images_path
resources :photos, as: 'images'
创建带有命名空间的 controller
bin/rails g controller admin::users
路由创建命名空间 & 添加 search
namespace :admin do
# admin 下跟路由
root 'users#index'
# users 资源路由
resources :users do
# 创建 search
collection do
get :search
end
end
end
View
<% %> # => 没有输出, 用于循环 遍历 赋值等操作
<%= %> # => 输出内容
-
index.html.erb
index: action, html: 文件类型, erb: 解释引擎
Render 作用
- 生成
HTTP response
- 渲染和解释 子视图
(sub-view)
-> 处理视图
render in controller
- 一个
action
只能执行一次render
, 否则会报错DoubleRenderError
# 避免多次 render
def index
@blogs = Blog.all
if @blogs
render 'new'
end
render 'edit'
end
def search
@users = User.all
# 渲染到 index 动作 页面
render action: :index
# 渲染其他控制器视图
render "products/show"
end
def search
# ... render others
render plain: "OK" # 纯文本
# html html_safe 防止转义
render html: "<strong>Not Found</strong>".html_safe, status: 200
# 渲染 json
render json: @product, status: 404
render json: {
status: 'ok',
msg: 'success'
}
# 渲染 xml
render xml: @product, status: :no_content
# 渲染普通 js
render js: "alert('Hello Rails');"
# 渲染文件
render file: "/path/to/rails/app/views/books/edit.html.erb"
end
render 参数
# :layout -> 视图使用
# :location -> 用于设置 HTTP Location 首部
# :content_type
render file: filename, content_type: 'application/pdf'
# :status
render status: 500
render status: :forbidden
redirect_to 重定向
redirect_to user_path, status: 200
# 可以调试 api http 请求
curl -i http://localhostt:3000
form helper
- form_tag => form_tag 方法会创建 <form> 标签,在提交表单时会向当前页面发起 POST 请求
- form_for 表单元素
- 生成一个 HTML from , 用于
模型或者 resource
相关操作
- 生成一个 HTML from , 用于
<%= form_for @article, url: {action: "create"}, html: {class: "nifty_form"} do |f| %>
<%= f.text_field :title %>
<%= f.text_area :body, size: "60x12" %>
<%= f.submit "Create" %>
<% end %>
- 实际需要修改的对象是 @article。
- form_for 辅助方法的选项是一个散列,其中 :url 键对应的值是路由选项,:html 键对应的值是 HTML 选项,这两个选项本身也是散列。还可以提供:namespace 选项来确保表单元素具有唯一的 ID 属性,自动生成的 ID 会以 :namespace 选项的值和下划线作为前缀。
- form_for 辅助方法会产出一个表单生成器对象,即变量 f。
- 用于生成表单控件的辅助方法都在表单生成器对象 f 上调用。
CSRF Cross-site Resouces Forgery
原理: 通过在页面中包含恶意代码或链接,访问已验证用户才能访问的 Web 应用。如果该 Web 应用的会话未超时,攻击者就能执行未经授权的操作
- 示例1
- Bob 在访问留言板时浏览了一篇黑客发布的帖子,其中有一个精心设计的 HTML 图像元素。这个元素实际指向的是 Bob 的项目管理应用中的某个操作,而不是真正的图像文件:
<img src="http://www.webapp.com/project/1/destroy">
。 - Bob 在 www.webapp.com 上的会话仍然是活动的,因为几分钟前他访问这个应用后没有退出。
- 当 Bob 浏览这篇帖子时,浏览器发现了这个图像标签,于是尝试从 www.webapp.com 中加载图像。如前文所述,浏览器在发送请求时包含 cookie,其中就有有效的会话 ID
- Bob 在访问留言板时浏览了一篇黑客发布的帖子,其中有一个精心设计的 HTML 图像元素。这个元素实际指向的是 Bob 的项目管理应用中的某个操作,而不是真正的图像文件:
- 允许用户自定义
超链接
// 注入 js
<a href="http://www.harmless.com/" onclick="
var f = document.createElement('form');
f.style.display = 'none';
this.parentNode.appendChild(f);
f.method = 'POST';
f.action = 'http://www.example.com/account/destroy';
f.submit();
return false;">To the harmless survey</a>
<img src="http://www.harmless.com/img" width="400" height="400" onmouseover="..." />
- rails 应对措施
- 使用正确的 http 请求
protect_from_forgery with: :exception
- 对用户输入做限制
Controller
actionpack gem
ActionController::Base 命名空间下的
-
使用
-
app/controllers
目录 - 命名规则
names_controller.rb
- 支持命名空间, 以
module
组织
-
-
Instance Methods in Controller
- params
- 获取
http
请求中get post
的 参数 - 可以使用
Symbol 和 String
的方式访问, 比如params[:user] params['user']
- 获取
- session & cookies
- render -> 上面有讲
- redirect_to
- send_data & send_file -> 以数据流的方式发送数据
-
send_file
方法很方便,只要提供磁盘中文件的名称,就会用数据流发送文件内容
def download send_file '/file/to/download.pdf' end def download send_data image.data, type: image.content_type end
-
- request
request.get? request.headers request.query_string etc...
- response
response.location response.body etc...
- params
-
Class Methods in Controller
- Filters
before_action after_action around_action
- Filters
Exception 异常
- 捕获错误后如果想做更详尽的处理,可以使用
rescue_from
,rescue_from
可以处理整个控制器及其子类中的某种(或多种)异常
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
private
def record_not_found
render plain: "404 Not Found", status: 404
end
end
- 不过只要你能捕获异常,就可以做任何想做的处理。例如,可以新建一个异常类,当用户无权查看页面时抛出
class ApplicationController < ActionController::Base
rescue_from User::NotAuthorized, with: :user_not_authorized
private
def user_not_authorized
flash[:error] = "You don't have access to this section."
redirect_back(fallback_location: root_path)
end
end
class ClientsController < ApplicationController
# 检查是否授权用户访问客户信息
before_action :check_authorization
# 注意,这个动作无需关心任何身份验证操作
def edit
@client = Client.find(params[:id])
end
private
# 如果用户没有授权,抛出异常
def check_authorization
raise User::NotAuthorized unless current_user.admin?
end
end
Model 了解
-
约束
- 表名
(table_name)
# user model 对应数据库中的 users 表 class user < ActiveRecord::Base end
- columns
- 对应我的 users 表, column ->
id username password created_at updated_at
- 对应我的 users 表, column ->
- 表名
-
覆盖表名
class Product < ApplicationRecord self.primary_key = "product_id" # 指定表的主键 self.table_name = "my_products" # default -> products end
-
CURD
-
new
方法创建一个新对象,create
方法创建新对象,并将其存入数据库
class user < ActiveRecord::Base end # create # 1 user = User.new username = "David", password = "123456" user.save # 2 User.create username = "David", password = "123456" # Reader users = User.all # 返回所有用户组成的集合 user = User.first # 返回第一个用户 david = User.find_by(name: 'David') # 返回第一个名为 David 的用户 # 查找所有名为 David,职业为 Code Artists 的用户,而且按照 created_at 反向排列 users = User.where(name: 'David', occupation: 'Code Artist').order(created_at: :desc) # Update # 1 user = User.find_by(name: 'David') user.name = 'Dave' user.save # 2 user = User.find_by(name: 'David') user.update(name: 'Dave') # 3 User.update_all "max_login_attempts = 3, must_change_password = 'true'" # Delete user = User.find_by(name: 'David') user.destroy
-
-
表间关联关系
-
has_many:
一对多, 多对多 -
has_one:
一对一 -
belongs_to:
一对一 -
has_and_belongs_to_many:
多对多
# 示例
class User < ActiveRecord::Base
has_many :blogs
end
class Blog < ActiveRecord::Base
belongs_to :user
end
model table column key
User users id primary_key => User 模型 users 表 id 字段 主键
Blog blogs user_id foreign_key => Blog 模型 blogs表 user_id 字段 外键
- 多对多
-
has_and_belongs_to_many
- 使用场景: 一片博客会有很多个标签, 一个标签能匹配很多博客
- 示例: 创建三张表
blogs tags blogs_tags
class Blog < ApplicationRecord
has_and_belongs_to_many :tags
end
class Tag < ApplicationRecord
has_and_belongs_to_many :blogs
end
# 创建表
class CreateTags < ActiveRecord::Migration[6.0]
def change
create_table :tags do |t|
t.string :title
t.timestamps
end
create_table :blogs_tags do |t|
t.integer :blog_id
t.integer :tag_id
end
end
end
-
has_many
- 使用场景: 我们需要访问关联关系表
blogs_tags
, 并且在关联关系表blogs_tags
添加一些自定义字段, 回调, 属性检查等 has_many :through
- 使用场景: 我们需要访问关联关系表
- CURD 深入
# 查找
# find vs find_by 区别
# 1. 传参不同; 2: 错误时 find_by 返回 nil, find 抛出异常; 3. find 只允许传入 id, find_by 可以传入其他参数
find(id) # 直接传入 id
find_by(id: 1) # 传入 hash 值
find_by! # ! 错误会抛出异常
# 指定 sql 语句 来查询数据库
find_by_sql # 总是返回对象的数组
User.find_by_sql "select * from users where id = 1"
ids # 获得关联的所有 ID
User.ids # [1, 10, 11, 12]
# where
# 在没有操作之前. 只是封装查询对象
Blog.where(["id = ? and title = ?", params[:id], params[:title]]) # 自定义数组 ? 替换
Blog.where(id: 3, title: 'test') # hash 的模式
# 什么是: n+1 查询 => 例如博客列表页面, 我们一次请求 列表次数的 sql, 但每个列表都有 标签. 每行列表都要请求一次 标签 sql, 所以是 n + 1 次 查询
includes(:tags, :user) # 自动把关联关系一次查出来
# 更新
# Blog 模型
# 1
b = Blog.first
b.title = 'chenges'
b.save
# 2 update_attributes 赋值 + save
b.update_attributes title: 'test', content: 'content'
# 3. update_attribute 赋值 + save
b.update_attribute :title, '我是标题'
changed? # 返回 boolean
changed_attributes # 返回上次记录
# ! 方法
# 会抛出异常
save!
create!
update_attributes!
# Exception
ActiveRecord::RecordNotFound # 未发现记录
ActiveRecord::UnknownAttributeError # 未知属性
ActiveRecord::RecordInvalid # 验证未通过
ActiveRecord::Rollback #
etc...
- Model 自定义属性
class Blog < ApplicationRecord
def tags_string= one_tags
one_tags.split(',').each do |tag|
one_tag = Tag.find_by(title: tag)
one_tag = Tag.new(title: tag) unless one_tag
# 博客就在当前范围
self.tags << one_tag
end
end
def content= one_content
write_attributes :content, one_content * 2
end
# 转化拼音
def title_pinyib
Pingyin.t self.title
end
end
- 事务 transaction
-
事务文章
事务用来确保多条 SQL 语句要么全部执行成功、要么不执行。事务可以帮助开发者保证应用中的数据一致性, 使用事务的场景是银行转账,钱从一个账户转移到另外一个账户。如果中间的某一步出错,那么整个过程应该重置 -
触发事务回滚
- 事务通过
rollback
过程把记录的状态进行重置。在 Rails 中,rollback
只会被一个exception
触发。这是非常关键的一点,很多事务块中的代码不会触发异常,因此即使出错,事务也不会回滚
- 事务通过
-
事务是基于
database
层级不是针对一个表
# 会触发 rollback
Blog.transaction do
blog.save!
end
# 会一直触发 rollback
Blog.transaction do
raise 'error'
end
- 应用场景: 项目博客和标签创建
数据验证
- 数据验证
-
activemodel gem
提供给我们使用的 validates & validates_*_of
class Person < ApplicationRecord
validates :name, presence: true
valiedates_prensence_of :name
end
-
valid? & invalid?
-> valid: 有效的; invalid: 无效的
class Person < ApplicationRecord
validates :name, presence: true
end
# valid? 方法会触发数据验证,如果对象上没有错误,返回 true,否则返回 false
Person.create(name: "John Doe").valid? # => true
Person.create(name: nil).valid? # => false
-
处理验证错误
ActiveModel::Errors
-
errors
-> 键是每个属性的名称,值是一个数组,包含错误消息字符串。
class Person < ApplicationRecord validates :name, presence: true, length: { minimum: 3 } end person = Person.new person.valid? # => false person.errors.messages # => {:name=>["can't be blank", "is too short (minimum is 3 characters)"]} person = Person.new(name: "John Doe") person.valid? # => true person.errors.messages # => {}
-
errors[]
-> 若想检查对象的某个属性是否有效,可以使用 errors[:attribute]。errors[:attribute] 中包含与 :attribute 有关的所有错误。如果某个属性没有错误,就会返回空数组 -
errors.add
-> 手动添加某属性的错误消息,它的参数是属性和错误消息 errors.details
-
自定义validate
- 自定义验证类: 自定义的验证类继承自 ActiveModel::Validator,必须实现 validate 方法,其参数是要验证的记录,然后验证这个记录是否有效。自定义的验证类通过 validates_with 方法调用
class MyValidator < ActiveModel::Validator def validate(record) unless record.name.starts_with? 'X' record.errors[:name] << 'Need a name starting with X please!' end end end class Person include ActiveModel::Validations validates_with MyValidator end
-
触发时间
create save update
# on 啥时候
class Person < ApplicationRecord
# 更新时允许电子邮件地址重复
validates :email, uniqueness: true, on: :create
# 创建记录时允许年龄不是数字
validates :age, numericality: true, on: :update
# 默认行为(创建和更新时都验证)
validates :name, presence: true
end
scopes 作用域
- 作用域
- 作用域允许我们把常用查询定义为方法,然后通过在关联对象或模型上调用方法来引用这些查询
- 指定默认的查询参数, 限定查询范围
# 定义作用域和通过定义类方法来定义作用域效果完全相同
class User < ApplicationRecord
scope :published, -> { where(published: true) }
# 等同于
def self.published
where(published: true)
end
end
# 调用
User.published.first
User.published.new
# 传入参数
class Article < ApplicationRecord
# 创建之前
scope :created_before, ->(time) { where("created_at < ?", time) }
end
Article.created_before(Time.zone.now)
关联关系参数
- proc
class User < ActiveRecord::Base
has_many :blogs
# -> 代码块, 类的名称: Blog 类似 scope
has_many :public_blogs, -> { where(is_public: true) }, class_name: :Blog
# 查出用户最新的一篇博客 has_one 查一个
has_one :latest_blog, -> { order("id desc") }, class_name: :Blog
# 自身关联 用户里面有管理者 和 普通员工
has_many :普通人, class_name: :User, foreign_key: 'manager_id'
belongs_to :管理者, class_name: :User
end
# 自身关联的使用
@user.普通人 和 @user.管理者
# 约束复习
blogs -> Blog # blogs 对应 Blog Model
User -> user_id
User primary_key -> id
Blog foreign_key -> user_id
# 指定关联参数
class User < ActiveRecord::Base
# 模型名称 class_name 指定, 主键: primary_key 指定, 外键: foreign_key
has_many :blogs, class_name: :Blog, primary_key: :id, foreign_key: :user_id
end
# :dependent
class User < ActiveRecord::Base
# :dependent 选项控制属主销毁后怎么处理关联的对象
# :destroy 销毁关联的对象
has_many :blogs, dependent: :destroy
end
user = User.first
user.destroy # blogs 也会被销毁
Migration
- 数据库模式一开始并不包含任何内容,之后通过一个个迁移来添加或删除数据表、字段和记录。 Active Record 知道如何沿着时间线更新数据库模式,使其从任何历史版本更新为最新版本。
Active Record
还会更新db/schema.rb
文件,以匹配最新的数据库结构
- 作用
- 使用 Ruby DSL 管理数据库的设计模式,因此不必手动编写 SQL
- 通用 RDB 模式管理, 方便在不同数据库之间使用
- 支持版本管理和团队协作
- 支持数据库
rollback
- 使用
bin/rails g model
# 已存在的 model, 添加或者修改一些字段, 不需要生成 model, 只需要生成一个单独的移植文件
bin/rails g migration
bin/rails db:migrate
# 示例: 为用户添加一个 style 字段
# bin/rails g migration add_users_style_column
class AddUsersStyleColumn < ActiveRecord::Migration[6.0]
def change
# 添加字段 :针对 users 表, :字段名, :字段类型
add_column :users, :style, :string
end
end
- 运行迁移
- Rails 提供了一套用于运行迁移的 bin/rails 任务
# 常用 调用所有未运行的迁移中的 change up 方法
bin/rails db:migrate (VERSION=20080906120000) # version 可选参数
# 查看迁移的状态
bin/rails db:migrate:status
# up 20211019121601 Create tags
# down 20211021070702 Add users style column
# 回滚 : 通过撤销 change 方法或调用 down 方法来回滚最后一个迁移
bin/rails db:rollback
# 会撤销最后三个迁移
bin/rails db:rollback STEP=3
- up & down 方法
- up 方法用于描述对数据库模式所做的改变,down 方法用于撤销 up 方法所做的改变
class AddUsersStyleColumn < ActiveRecord::Migration[6.0]
# migrate 运行
def up
add_column :users, :style, :string
end
# rollback 运行
def dowm
remove_column :users, :style, :string
end
end
class AddUsersStyleColumn < ActiveRecord::Migration[6.0]
def change
# 添加字段
add_column :users, :style, :string
# 删除字段
remove_column :table_name, :column_name
# 修改字段
change_column :table_name, :column_name, :column_type, :column_options
# 重命名
rename_column :table_name, :old_column_name, :new_column_name
end
end
- 永远不要修改已经提交的
migration
- 创建新的 migration 修复 之前的错误
Callback
什么是 callback
-> 回调是在对象生命周期的某些时刻被调用的方法。通过回调,我们可以编写在创建、保存、更新、删除、验证或从数据库中加载 Active Record 对象时执行的代码
- 回调触发分类
- 创建对象
-
before_validation
-> 数据验证前 -
after_validation
-> 数据验证后 -
before_save
-> 保存前 around_save
-
before_create
-> 创建才会执行 around_create
after_create
after_save
after_commit/after_rollback
-
- 更新对象
before_validation
after_validation
before_save
around_save
before_update
around_update
after_update
after_save
after_commit/after_rollback
- 删除对象
before_destroy
around_destroy
after_destroy
after_commit/after_rollback
- 查询对象
after_initialize
after_find
- 事务回调
after_commit/after_rollback
- 所有回调自动放入事务中
- 如果一个
before_* callback
返回值为false,name当前事务就会回滚 (还未进入数据库)
- 无论按什么顺序注册回调,在创建和更新对象时,after_save 回调总是在更明确的 after_create 和 after_update 回调之后被调用
class User < ApplicationRecord
before_save do
self.username = self.username.downcase
end
# or
before_save :update_username
private
def update_username
self.username = self.username.downcase
end
end
- 触发
callback
的方法create
create!
decrement!
destroy
destroy!
destroy_all
increment!
save
save!
save(validate: false)
toggle!
update_attribute
update
update!
valid?
- 跳过回调的操作
decrement
decrement_counter
delete
delete_all
increment
increment_counter
toggle
touch
update_column
update_columns
update_all
update_counters
# before_save & after_save
class User < ApplicationRecord
# 是在保存之前调用, 直接赋值即可
before_save do
self.username = self.username.downcase
end
# 保存之后, 所以需要 save
# 做一些和本模型无关的操作, 其他模型相关操作
after_save do
self.username = self.username.downcase
# 会死循环
self.save # update_column 代替
end
end