如何更新脚手架生成的 MVC + 路由,用于在 Rails 中具有 2 个 belongs_to 关系的嵌套资源

Posted

技术标签:

【中文标题】如何更新脚手架生成的 MVC + 路由,用于在 Rails 中具有 2 个 belongs_to 关系的嵌套资源【英文标题】:How to update scaffold generated MVC + routes for a nested resource with 2 belongs_to relationships in rails 【发布时间】:2021-04-09 01:45:59 【问题描述】:

我正在 Rails 中创建一个虚拟库,当我尝试为嵌套资源上的基本 CRUD 创建视图时遇到了错误。我最好的猜测是我没有在视图中正确传递变量和/或我生成的控制器没有正确更新。或者模型本身没有正确设置为使用外键。我遵循了本教程:

Walk-through to update scaffold generated MVC to use nested resource

主要区别在于我的嵌套资源 (checkout_log) 有 2 个外键(user_id 和 book_id),而不仅仅是示例中的 1。也许这就是答案,我无法创建仅依赖于 2 个外键中的 1 个的路由?例如book/1/checkout_logs/1

目前的错误是:(控制器第 7 行)

undefined method `checkout_logs' for nil:NilClass

我仍然持有活跃的记录,所以我尝试了很多方法。索引应该显示应该使用 get_book 设置的 @book 的所有 checkout_logs。

任何帮助将不胜感激,谢谢。如果我没有提供足够的信息,请告诉我。

checkout_log.rb

class CheckoutLog < ApplicationRecord
  belongs_to :user
  belongs_to :book
end

book.rb

class Book < ApplicationRecord
    has_many :checkout_logs, dependent: :destroy
end

用户.rb

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  has_many :checkout_logs
end

schema.rb

ActiveRecord::Schema.define(version: 2020_12_30_171415) do

  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"

  create_table "books", force: :cascade do |t|
    t.string "title"
    t.string "author"
    t.string "genre"
    t.string "subgenre"
    t.integer "pages"
    t.string "publisher"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.integer "book_number"
  end

  create_table "checkout_logs", force: :cascade do |t|
    t.datetime "checkout_date"
    t.datetime "due_date"
    t.datetime "returned_date"
    t.bigint "user_id", null: false
    t.bigint "book_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["book_id"], name: "index_checkout_logs_on_book_id"
    t.index ["user_id"], name: "index_checkout_logs_on_user_id"
  end

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.boolean "admin", default: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

  add_foreign_key "checkout_logs", "books"
  add_foreign_key "checkout_logs", "users"
end

routes.rb(目前主要关注书籍和 checkout_logs)

Rails.application.routes.draw do    
  devise_for :users
  
  get 'my_books/index'
  get 'my_books/borrow'
  get 'my_books/return'
  get 'books/listing'
  get 'books/borrow'
  
  get 'home/index'
  
  resources :books do    
    resources :checkout_logs
  end

  root to: "home#index"
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

checkout_logs_controller.rb

    class CheckoutLogsController < ApplicationController
  before_action :get_book, :set_checkout_log, only: [:show, :edit, :update, :destroy]

  # GET /checkout_logs
  # GET /checkout_logs.json
  def index
    @checkout_logs = @book.checkout_logs
  end

  # GET /checkout_logs/1
  # GET /checkout_logs/1.json
  def show    
  end

  # GET /checkout_logs/new
  def new
    @checkout_log = @book.checkout_logs.build
  end

  # GET /checkout_logs/1/edit
  def edit
  end

  # POST /checkout_logs
  # POST /checkout_logs.json
  def create
    @checkout_log = CheckoutLog.new(checkout_log_params)

    respond_to do |format|
      if @checkout_log.save
        format.html  redirect_to @checkout_log, notice: 'Checkout log was successfully created.' 
        format.json  render :show, status: :created, location: @checkout_log 
      else
        format.html  render :new 
        format.json  render json: @checkout_log.errors, status: :unprocessable_entity 
      end
    end
  end

  # PATCH/PUT /checkout_logs/1
  # PATCH/PUT /checkout_logs/1.json
  def update
    respond_to do |format|
      if @checkout_log.update(checkout_log_params)
        format.html  redirect_to @checkout_log, notice: 'Checkout log was successfully updated.' 
        format.json  render :show, status: :ok, location: @checkout_log 
      else
        format.html  render :edit 
        format.json  render json: @checkout_log.errors, status: :unprocessable_entity 
      end
    end
  end

  # DELETE /checkout_logs/1
  # DELETE /checkout_logs/1.json
  def destroy
    @checkout_log.destroy
    respond_to do |format|
      format.html  redirect_to checkout_logs_url, notice: 'Checkout log was successfully destroyed.' 
      format.json  head :no_content 
    end
  end

  private
    #get book associated with this checkout_log
    def get_book
      @book = Book.find(params[:book_id])
    end

    # Use callbacks to share common setup or constraints between actions.
    def set_checkout_log
      @checkout_log = @book.checkout_logs.find(params[:id])      
    end

    # Only allow a list of trusted parameters through.
    def checkout_log_params
      params.require(:checkout_log).permit(:checkout_date, :due_date, :returned_date, :user_id, :book_id)
    end
end

index.html.erb(checkout_log 索引)

<p id="notice"><%= notice %></p>

<h1>Checkout Logs</h1>

<table>
  <thead>
    <tr>
      <th>User</th>
      <th>Book</th>
      <th>CheckoutDate</th>
      <th>DueDate</th>
      <th>ReturnedDate</th>      
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% Array(@checkout_logs).each do |checkout_log| %>
      <tr>
        <td><%= checkout_log.user_id %></td>
        <td><%= checkout_log.book_id %></td>
        <td><%= checkout_log.checkout_date %></td>
        <td><%= checkout_log.due_date %></td>
        <td><%= checkout_log.returned_date %></td>        
        <td><%= link_to 'Show', book_checkout_log_path(checkout_log) %></td>
        <td><%= link_to 'Edit', edit_book_checkout_log_path(checkout_log) %></td>
        <td><%= link_to 'Destroy', book_checkout_log_path(checkout_log), method: :delete, data:  confirm: 'Are you sure?'  %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Checkout Log', new_book_checkout_log_path %>

<%= link_to 'Back', books_path %>

_form.html.erb(checkout_log表单部分,我只改了第一行添加书)

<%= form_with(model: [@book, @checkout_log], local: true) do |form| %>
  <% if checkout_log.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(checkout_log.errors.count, "error") %> prohibited this checkout_log from being saved:</h2>

      <ul>
        <% checkout_log.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :UserId %>
    <%= form.number_field :UserId %>
  </div>

  <div class="field">
    <%= form.label :BookId %>
    <%= form.number_field :BookId %>
  </div>

  <div class="field">
    <%= form.label :checkout_date %>
    <%= form.datetime_select :checkout_date %>
  </div>

  <div class="field">
    <%= form.label :due_date %>
    <%= form.datetime_select :due_date %>
  </div>

  <div class="field">
    <%= form.label :returned_date %>
    <%= form.datetime_select :returned_date %>
  </div>

  <div class="field">
    <%= form.label :user_id %>
    <%= form.text_field :user_id %>
  </div>

  <div class="field">
    <%= form.label :book_id %>
    <%= form.text_field :book_id %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

【问题讨论】:

【参考方案1】:

在 checkout_logs_controller 中,我需要将 before_action 放在 2 个单独的行上。出于某种原因,我认为我可以将它们放在一个列表中。

 before_action :get_book
 before_action :set_checkout_log, only: [:show, :edit, :update, :destroy]

【讨论】:

以上是关于如何更新脚手架生成的 MVC + 路由,用于在 Rails 中具有 2 个 belongs_to 关系的嵌套资源的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Visual Studio 2017 的 ASP.NET MVC 中创建自定义生成/脚手架模板(Razor)?

Asp.Net MVC 从模型创建视图脚手架不生成 int 属性

如何在 Rider IDE 中进行 ASP.NET MVC 视图脚手架?

vue脚手架---普通脚手架及路由的安装

YbSoftwareFactory 代码生成插件二十四:MVC中实现动态自定义路由

django的路由系统