ActiveRecord::RecordNotFound (没有 ID 找不到家):



我有一个homes表,users表和payments表。我想要这样的东西,当用户创建支付时,它应该包含user_idhome_id。我已经设法得到user_id工作,但home_id失败。

这行代码可能有问题:

home = Home.find(params[:home_id])

我想找到一个我在创建支付时使用的id的home

PaymentsController:

class Api::V1::PaymentsController < ApplicationController
before_action :set_payment, only: %i[ show edit update destroy ]
before_action :authenticate_user!
# GET /payments or /payments.json
def index
if user_signed_in?
@payments = current_user.payments.order(created_at: :desc)
render json: @payments
else
render json: {}, status: 401
end
end
# GET /payments/1 or /payments/1.json
def show
if @payment
render json: @payment
else
render json:@payment.errors  
end
end

# GET /payments/new
def new
payment = current_user.payments.new
#   home = Home.find(params[:home_id])  
end

# GET /payments/1/edit
def edit
end

# POST /payments or /payments.json
def create 
home = Home.find(params[:home_id])
if user_signed_in? 
payment = home.payments.create(payment_params.merge(user_id: current_user.id))
if payment = current_user.payments.create(payment_params, home)
render json: payment, status: :created 
else 
render json: payment.errors, status: 400
end
else 
render json: {}, status: 401
end
end

# PATCH/PUT /payments/1 or /payments/1.json
def update
if @payment.update(payment_params)
render json: {notice: "Payment was successfully updated." }
else
render json: { error: 'Unable to update payment' }
end
end

# DELETE /payments/1 or /payments/1.json
def destroy
@payment.destroy
render json: {notice: 'Payment succefully removed'}
end

private
# Use callbacks to share common setup or constraints between actions.
def set_payment
@payment = Payment.find(params[:id])
end
# Only allow a list of trusted parameters through.
def payment_params
params.require(:payment).permit(:first_name, :last_name, :phone_number, :address, :money_paid, :date, :nin_number, :user_id, :home_id)
end
end

模式:

# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2022_12_15_071948) do
create_table "homes", force: :cascade do |t|
t.string "title"
t.text "description"
t.string "image_url"
t.decimal "price", precision: 8, scale: 2
t.string "availability", default: "Available", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "payments", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.string "phone_number"
t.text "address"
t.decimal "money_paid"
t.string "nin_number"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "home_id"
t.date "date"
t.integer "user_id"
t.index ["home_id"], name: "index_payments_on_home_id"
t.index ["user_id"], name: "index_payments_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.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 "payments", "homes"
add_foreign_key "payments", "users"
end

home.rb:

class Home < ApplicationRecord
validates :title, :description, :image_url, :price,  :availability, presence: true
validates :price, numericality: { greater_than_or_equal_to: 0.01 }

validates :image_url, allow_blank: true, format: {
with:
%r{.(gif|jpg|png|jpeg)z}i,
message: 'must be a URL for GIF, JPG, JPEG or PNG image.'
}

has_many :payments
end

payment.rb:

class Payment < ApplicationRecord
validates :first_name, :last_name, :phone_number, :address, :money_paid, :date, :nin_number, :user_id, presence: true
belongs_to :user
belongs_to :home
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 :payments
end

我该怎么做呢?我希望支付包括user_idhome_id的用户正在支付。

这是我的路由,如果它可能是一个路由错误

Rails.application.routes.draw do
devise_for :users
namespace :api do
namespace :v1 do
get 'homes/index'
post 'homes/create'
get '/show/:id', to: 'homes#show'
delete '/destroy/:id', to: 'homes#destroy'
# resources :payments
get '/payments/index'
post '/payments/create'
end

end
root 'homes#index' 

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

am using react at the front end to send requests to the server and below is my payment.jsx where i pass in params

import React, { useRef } from 'react';
import { Button, Form, Input, InputNumber, Select, DatePicker } from 'antd';
import { useNavigate } from 'react-router-dom';
const Payment = () => {
const navigate = useNavigate();
const form = useRef();
const onFinish = (values) => {
// values.preventDefault();
let token = document.querySelector('meta[name="csrf-token"]').content;
const url = "/api/v1/payments/create";
fetch(url, {
method: "post",
headers: {
"Content-Type": "application/json",
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': token
},
body: JSON.stringify(values),
})
.then((data) => {
if (data.ok) {
return data.json();
}
throw new Error("Network error.");
})
// Displaying results to console
.then(data => console.log(data));
navigate('/')
};
return (
<div style={{ display: "flex", justifyContent: "center", marginTop: 20 }}>
<Form ref={form} onFinish={onFinish} layout="vertical" initialValues={{ prefix: '256', }} scrollToFirstError labelCol={{ span: 15 }} wrapperCol={{ span: 24}}>
<Form.Item
name="first_name"
label="First Name"
rules={[
{
required: true,
message: 'Please input your first name!',
whitespace: true,
},
]}
>
<Input placeholder='Input Your First Name' />
</Form.Item>
<Form.Item
name="last_name"
label="Last Name"
rules={[
{
required: true,
message: 'Please input your last name!',
whitespace: true,
},
]}
>
<Input placeholder='Input Your Last Name' />
</Form.Item>
<Form.Item
name="phone_number"
label="Phone Number"
rules={[
{
required: true,
message: 'Please input your phone number!',
},
]}
>
<Input
style={{
width: '100%',
}}
placeholder='Input Your Phone number'
/>
</Form.Item>
<Form.Item
name="nin_number"
label="NIN Number"
rules={[
{
required: true,
message: 'Please input your National Identification number!',
},
]}
>
<Input
placeholder='Input Your National Identification Number'
style={{
width: '100%',
}}
/>
</Form.Item>
<Form.Item
name="money_paid"
label="Money Paid"
rules={[
{
required: true,
message: 'Please input amount paid!',
},
]}
>
<InputNumber
style={{
width: '100%',
}}
placeholder='Input Amount Paid'
/>
</Form.Item>
<Form.Item name="date" label="Date" rules={[{ required: true, message: 'Please select date!', }]}>
< DatePicker
style={{
width: '100%',
}}
/>
</Form.Item>
<Form.Item
name="address"
label="address"
rules={[
{
required: true,
message: 'Please input For place of address',
},
]}
>
<Input.TextArea showCount maxLength={100} />
</Form.Item>
<Form.Item >
<Button type="primary" htmlType="submit" style={{ width: '100%' }}>
Make Payment
</Button>
</Form.Item>
</Form>
</div>
);
};
export default Payment;

这里的主要问题是你的路由。首先使用resources :homes宏并创建符合习惯的正确路由:

Prefix     Verb     URI Pattern              Controller#Action
homes      GET      /homes(.:format)         homes#index
POST     /homes(.:format)         homes#create
home       GET      /homes/:id(.:format)     homes#show
PATCH    /homes/:id(.:format)     homes#update
PUT      /homes/:id(.:format)     homes#update
DELETE   /homes/:id(.:format)     homes#destroy

浏览器通过使用特定的HTTP方法(如GET、POST、PATCH、PUT和DELETE)对URL发出请求来从Rails请求页面。每个方法都是对资源执行操作的请求。资源路由将多个相关请求映射到单个控制器中的操作。

您不需要或不想将/create/delete添加到路径中,这样做不会很好地反映您的能力。Rails是强烈的约定驱动的,只要你遵守约定,事情就会正常工作。

/new/edit动作是此规则的例外,但它们只是用于在经典应用程序中呈现表单。它们在api中并不常用。

要在路由中创建不同关系之间的关系,可以使用嵌套路由:

resources :homes do 
resources :payments, shallow: true
end

这将创建POST /homes/:home_id/payments路由来创建特定家庭的付款,并创建GET /homes/:home_id/payments路由来显示所有付款。确保你更新了你的JS或表单以提交到正确的路径。

控制器真是一团糟。大部分混乱源于您没有真正理解如何使用身份验证系统,甚至没有定义一组一致的规则。

我现在就把它设置为只允许经过身份验证的用户,如果你以后想让未经身份验证的用户创建支付,那么请先考虑一下编写测试并首先考虑所有的含义和边缘情况。

# Use explicit nesting to avoid unexpected constant lookup
# and autoloading bugs 
# https://github.com/rubocop/ruby-style-guide#namespace-definition
class Api
module V1
class PaymentsController < ApplicationController
before_action :set_payment, only: %i[ show update destroy ]
# before_action :authenticate_user! will raise an 
# error if the user isn't signed in
# if you want to allow unauthenticated users use:
#  before_action :authenticate_user!, except: [:foo, :bar, :baz]
before_action :authenticate_user!
# GET homes/:home_id/payments 
def index
render json: home.payments.order(created_at: :desc)
end
# GET /payments/1 or /payments/1.json
def show
render json: @payment
end

# you do not need new or edit in an API.
# POST /homes/:home_id/payments 
def create 
# Use `.new` instead of create which also saves the record 
payment = home.payments.new(payment_params) do |p|
p.user = current_user # if user_signed_in?
end
if payment.save
render json: payment, status: :created 
else
render json: payment.errors, status: :unprocessable_entity
end
end

# @todo How do you plan on authorizing that the user can only edit 
# payments they have created? And is letting users update the payment 
# even a good idea?
# PATCH/PUT /payments/1 or /payments/1.json
def update
if @payment.update(payment_params)
# Avoid the "JSON messages" anti-pattern and make sure 
# your API always returns meaningful HTTP status codes
render json: { notice: "Payment was successfully updated." },
status: :ok
else
render json: { error: 'Unable to update payment' },
status: :unprocessable_entity
end
end
# DELETE /payments/1 or /payments/1.json
def destroy
@payment.destroy
render json: {notice: 'Payment succefully removed'}
end
private
def home
@home ||= Home.find(params[:home_id])
end
# Use callbacks to share common setup or constraints between actions.
def set_payment
@payment = Payment.find(params[:id])
end
# Only allow a list of trusted parameters through.
def payment_params
params.require(:payment)
.permit(
:first_name, :last_name, :phone_number, 
:address, :money_paid, :date, :nin_number
# user_id and home_id should not be in the whitelist
)
end
end 
end 
end

然后我将在一个单独的控制器中处理显示用户付款。

# you can also do this with `devise_scope`
resource :user, only: [] do
resources :payments, 
only: [:index],
controller: :user_payments
end
class Api
module V1
# Displays payments for the current user
class UserPaymentsController < ApplicationController
before_action :authenticate_user!
# GET /user/payments
def index
@payments = current_user.payments.order(created_at: :desc)
end 
end
end
end

最新更新