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

Posted

技术标签:

【中文标题】你如何使用表单对象来编辑另一个类实例?【英文标题】:How do you use form objects to edit another class instance? 【发布时间】:2021-10-24 02:20:51 【问题描述】:

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

我得到了新/创建操作,但因为 UserForm 没有持久化(没有 id)我不知道如何加载现有的 User 以通过 @987654328 进行编辑@。

如何使用现有的 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

新建/编辑 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 数据。

【问题讨论】:

【参考方案1】:

我会使用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 = 并使用真正的关键字参数。不要使用范围结果运算符:: 声明嵌套类/模块。它会导致自动加载错误和令人惊讶的不断查找。

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

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

您的 save 方法应该很可能与您正在包装的对象具有相同的返回签名并返回一个布尔值。

【讨论】:

【参考方案2】:

我认为你的问题是

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] 访问它们,要么还为它们指定属性读取器。

【讨论】:

以上是关于你如何使用表单对象来编辑另一个类实例?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用相同的 django 表单编辑/添加对象?

如何从 Django 中的编辑表单更新对象?

C# 如何根据指定变量来实例化对象?

使变量指向另一个变量

如何使用其他模块中的表单对象执行操作?

如何使用非常量值来实例化一个类的多个对象?