如何为 rails 中具有 2 belongs_to 关系的嵌套资源更新脚手架生成的 MVC + 路由
How to update scaffold generated MVC + routes for a nested resource with 2 belongs_to relationships in rails
我正在 Rails 中创建一个虚拟库,当我试图为嵌套资源上的基本 CRUD 创建视图时,我 运行 遇到了错误。我最好的猜测是我没有在视图中正确传递变量 and/or 我生成的控制器没有正确更新。或者模型本身未正确设置为使用外键。我遵循了本教程:
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
我仍在获取活动记录,因此我尝试了多种方法。索引应显示 @book 的所有 checkout_logs,它应该设置为 get_book.
非常感谢任何帮助,谢谢。如果我没有提供足够的信息,请告诉我。
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
user.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_log)
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 %>
在 checkout_logs_controller 中,我需要将 before_action 分两行。我想出于某种原因我可以把它们放在一个列表中。
before_action :get_book
before_action :set_checkout_log, only: [:show, :edit, :update, :destroy]
我正在 Rails 中创建一个虚拟库,当我试图为嵌套资源上的基本 CRUD 创建视图时,我 运行 遇到了错误。我最好的猜测是我没有在视图中正确传递变量 and/or 我生成的控制器没有正确更新。或者模型本身未正确设置为使用外键。我遵循了本教程:
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
我仍在获取活动记录,因此我尝试了多种方法。索引应显示 @book 的所有 checkout_logs,它应该设置为 get_book.
非常感谢任何帮助,谢谢。如果我没有提供足够的信息,请告诉我。
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
user.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_log)
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 %>
在 checkout_logs_controller 中,我需要将 before_action 分两行。我想出于某种原因我可以把它们放在一个列表中。
before_action :get_book
before_action :set_checkout_log, only: [:show, :edit, :update, :destroy]