美文网首页Ruby
Rails 学习杂记一

Rails 学习杂记一

作者: YM雨蒙 | 来源:发表于2021-10-21 16:22 被阅读0次

    系统学习 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

    1. 先了解 HTTP -> 短连接 无状态

    2. http 无状态, 怎么保存状态的呢? -> Cookies

      • http response 会 Set-Cookies
      • 前端发送请求可以 cookie: 后端返回的值
    3. session 和 cookie 的区别?

      • Session是后端实现 -> session 的 key 存储基于 cookie, 也可以基于后端存储, 比如数据库
      • Cookie是浏览器的实现
      • Session 支持存储后端的数据结构, 比如对象; Cookie只支持字符串
      • session没有大小限制; cookie 大小有限制
    4. session 操作

      • session 方法; session key; session 加密的
    5. 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 请求到后端 Controlleraction 的分发规则

    自上而下的路由

    例子

    # 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相关操作
    <%= 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. 示例1
      • Bob 在访问留言板时浏览了一篇黑客发布的帖子,其中有一个精心设计的 HTML 图像元素。这个元素实际指向的是 Bob 的项目管理应用中的某个操作,而不是真正的图像文件:<img src="http://www.webapp.com/project/1/destroy">
      • Bob 在 www.webapp.com 上的会话仍然是活动的,因为几分钟前他访问这个应用后没有退出。
      • 当 Bob 浏览这篇帖子时,浏览器发现了这个图像标签,于是尝试从 www.webapp.com 中加载图像。如前文所述,浏览器在发送请求时包含 cookie,其中就有有效的会话 ID
    2. 允许用户自定义 超链接
    // 注入 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="..." />
    
    1. rails 应对措施
      • 使用正确的 http 请求
      • protect_from_forgery with: :exception
      • 对用户输入做限制

    Controller

    • actionpack gem
    • ActionController::Base 命名空间下的

    1. 使用

      • app/controllers 目录
      • 命名规则 names_controller.rb
      • 支持命名空间, 以module组织
    2. 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...
    3. Class Methods in Controller

      • Filters
        • before_action after_action around_action

    Exception 异常

    1. 捕获错误后如果想做更详尽的处理,可以使用 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
    
    1. 不过只要你能捕获异常,就可以做任何想做的处理。例如,可以新建一个异常类,当用户无权查看页面时抛出
    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 了解

    1. 约束

      • 表名(table_name)
      # user model 对应数据库中的 users 表
      class user < ActiveRecord::Base
      end
      
      • columns
        • 对应我的 users 表, column -> id username password created_at updated_at
    2. 覆盖表名

      class Product < ApplicationRecord
         self.primary_key = "product_id" # 指定表的主键
         self.table_name = "my_products" # default -> products
      end
      
    3. 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
      
    4. 表间关联关系

    • 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 字段 外键
    
    1. 多对多
    • 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
    1. 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...
    
    1. 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
    
    1. 事务 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 文件,以匹配最新的数据库结构
    1. 作用
    • 使用 Ruby DSL 管理数据库的设计模式,因此不必手动编写 SQL
    • 通用 RDB 模式管理, 方便在不同数据库之间使用
    • 支持版本管理和团队协作
    • 支持数据库 rollback
    1. 使用
    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
    
    1. 运行迁移
    • 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 对象时执行的代码

    1. 回调触发分类
    • 创建对象
      • 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
    

    相关文章

      网友评论

        本文标题:Rails 学习杂记一

        本文链接:https://www.haomeiwen.com/subject/axofaltx.html