您如何使用表单对象来编辑另一个 class 实例?

How do you use form objects to edit another class instance?

我需要能够通过名为 UserForm 的表单对象创建和编辑 class User 的实例。表单未持久化,User 使用表单 save 方法保存。

我得到了 new/create 操作,但是因为 UserForm 没有持久化(没有 id)我不知道如何加载现有的 User 编辑通过 UserForm.

如何使用现有 User 数据“填充”UserForm 对象?我尝试使用 url id 加载 User 但表单字段仍未填充 User 数据。

UserForm.rb

class UserForm
  include ActiveModel::Model
  include ActiveModel::Validations::Callbacks

  attr_accessor :fname, :lname, :email

  before_validation :build_user

  def initialize(params = {})
   super(params)
   @account = Account.find(account_id)
   @user = User.find(user_id)
  end

  def build_user
    @user ||= User.new do |user|
      user.fname = fname
      user.lname = lname
      user.email = email
    end
  end
    
  def save
    user.account_id = @account.id
    user.save
  end
end

UsersController.rb

class Admin::UsersController < AdminController
  def new
    @user_form = UserForm.new(account_id: current_account.id)
  end

  def create
    @user_form = UserForm.new(user_form_params)
    
    if @user = @user_form.save
      flash[:success] = "User created"
      redirect_to admin_user_path(@user)
    else
      render "new"
    end
  end   
    
    
  def edit
    @user = current_account.users.find(params[:id])
    @user_form = UserForm.new(user: @user)
  end

  def update
    if @user.update(user_form_params)
      flash[:success] = "User updated"
      redirect_to admin_user_path(@user)
    else
      render "edit"
    end
  end
end

new/edit form.html.erb

<%= simple_form_for @user_form, url: admin_users_path do |f| %>

    <%= f.input :fname  %>
    <%= f.input :lname %>
    <%= f.input :email %>
    
end

问题:

我可以通过表单对象创建一个新的 User,但我无法通过 UserForm 加载和编辑相同的 User,因为表单没有填充现有 User 数据。

我认为你的问题是

attr_accessor :fname, :lname, :email

没有意义,因为你有,例如表单上没有 fname 个属性。

试试

delegate_missing_to :user

https://www.rubydoc.info/github/rails/rails/Module%3Adelegate_missing_to

此外,您的初始化程序中可能没有 user_idaccount_id 属性。因此,您要么必须通过 params[:user_id] 访问它们,要么还为它们指定属性读取器。

我会使用 delegation pattern 以便您的表单对象包装底层用户而不是复制属性:

class UserForm
  include ActiveModel::Model

  attr_reader :user
  delegate :fname, :lname, :email, :account, to: :user

  # Just an example of how the validation uses delegation
  validates :fname, presence: true

  def initialize(object, **attributes)
    case object
    when User
      @user = object
    when Account
      @user = object.users.new(**attributes)
    end
  end

  def save
    # this triggers the validation callbacks
    return false unless valid?
    @user.save
  end

  def update(**attributes)
    @user.assign_attributes(**attributes)
    save
  end

  def to_model
    @user
  end
end

与其滥用验证回调来执行赋值,不如将属性传递给底层模型。

module Admin
  class UsersController < AdminController
    before_action :set_user, except: [:new, :index, :create]

    def new
      @user_form = UserForm.new(current_account)
    end

    def create
      @user_form = UserForm.new(current_account, **user_form_params)
      if @user_form.save
        flash[:success] = "User created"
        redirect_to [:admin, @user_form]
      else
        render "new"
      end
    end

    def edit
      @user_form = UserForm.new(@user)
    end

    def update
      @user_form = UserForm.new(@user)
      if @user_form.update(**user_form_params)
        flash[:success] = "User updated"
        redirect_to [:admin, @user_form]
      else
        render "edit"
      end
    end

    private 

    def set_user
      @user = current_account.users.find(params[:id])
    end
  end
end

一些注意事项:

避免params = {}并使用真正的关键字参数。不要使用范围结果运算符 :: 声明嵌套 classes/modules。它会导致自动加载错误和令人惊讶的持续查找。

使用关联而不是直接分配 ID。这将触发关联的回调并避免将模型的实现细节泄露到控制器层。

class Account < ApplicationRecord
  has_many :users
  # ...
end

您的 save 方法很可能具有与您正在包装的对象相同的 return 签名和 return 布尔值。