使用form_with与ActiveModel对象?



我有一个使用ActiveModel::Model概念的对象:

class TransitProvider
include ActiveModel::Model

# ... more stuff

在表面之下,这个对象是Provider记录和Service记录的聚合。

一切似乎都工作得很好,但form_with助手不承认TransitProvider实例为持久(因为它没有自己的ID),因此edit动作显示表单的数据,但提交它作为create而不是update

是否有办法添加到一个ActiveModel类的东西,使form_with将其视为一个现有的实例,而不是一个新的实例?

我需要在idpersisted?或类似的东西周围定义一些东西吗?

我似乎找不到任何特定于这个用例的东西。

谢谢!

覆盖persisted?方法。它在ActiveModel::API中定义:

def persisted?
false
end

这个方法被表单构建器用来决定它是否需要发送postpatch请求。

# app/models/transit_provider.rb
class TransitProvider
include ActiveModel::Model
attr_accessor :provider, :service
# NOTE: This is set to `false` by default. See `ActiveModel::API`.
# TODO: Decide what it means for `TransitProvider`
#       to be persisted. Could `provider` be persisted while 
#       `service` is not?
def persisted?
provider.persisted? && service.persisted?
end
# NOTE: `id` would be required for the update route
#       for plural `resources`.
#       Don't need it for a singular `resource`. See routes.rb.
# def id
#   1
# end
end
# config/routes.rb
Rails.application.routes.draw do
resource :transit_provider, only: [:create, :update]
#       ^
# NOTE: Singular. Don't need `id` in routes, we're not asking
#       for any data from this controller.
# NOTE: Make url mapping always resolve to singular route.
#
#         `transit_provider_path`
#
#       Otherwise, in the form url would resolve to undefined
#       plural `transit_providers_path` for `create` action.
resolve("TransitProvider") { [:transit_provider] }
# NOTE: Change it if you need `id`. Also add `id` method to 
#       `TransitProvider`
# resources :transit_providers
end
# app/controllers/transit_providers_controller.rb
class TransitProvidersController < ApplicationController
def create
# TODO: create
end
def update
# TODO: update
end
end
# NOTE: persisted
<% model = TransitProvider.new(
provider: Provider.first,
service:  Service.first)
%>
# NOTE: not persisted
# model = TransitProvider.new(provider: Provider.new, service: Service.new)
<%= form_with model: model do |f| %>
<%= f.fields_for :provider, model.provider do |ff| %>
<%= ff.text_field :id if ff.object.persisted? %>
<%= ff.text_field :name %>
<% end %>
<%= f.fields_for :service, model.service do |ff| %>
<%= ff.text_field :id if ff.object.persisted? %>
<%= ff.text_field :name %>
<% end %>
<%= f.submit %>
<% end %>

对于持久化TransitProvider表单执行PATCH请求来更新。

Started PATCH "/transit_provider" for 127.0.0.1 at 2022-07-03 15:47:57 -0400
Processing by TransitProvidersController#update as TURBO_STREAM
Parameters: {"authenticity_token"=>"[FILTERED]", "transit_provider"=>{"provider"=>{"id"=>"1", "name"=>"provide"}, "service"=>{"id"=>"1", "name"=>"service"}}, "commit"=>"Update Transit provider"}

否则是一个POST来创建。

Started POST "/transit_provider" for 127.0.0.1 at 2022-07-03 16:13:43 -0400
Processing by TransitProvidersController#create as TURBO_STREAM
Parameters: {"authenticity_token"=>"[FILTERED]", "transit_provider"=>{"provider"=>{"name"=>""}, "service"=>{"name"=>""}}, "commit"=>"Create Transit provider"}

更新什么是persisted?

ActiveModel得到它的持久化?方法从ActiveModel::API它是无关的ActiveRecord持续?方法。也不考虑id属性来决定记录是否被持久化:

# ActiveModel's persisted? is just `false`
# ActiveRecord
Service.create(name: "one")            # => #<Service: id: 1, name: "one">
Service.new.persisted?                 # => false
Service.first.persisted?               # => true
Service.new(id: 1).persisted?          # => false
Service.new(id: 1).reload.persisted?   # => true

s = Service.select(:name).first
s.id                                   # => nil
s.persisted?                           # => true
s = Service.first.destroy
s.id                                   # => 1
s.persisted?                           # => false

这是很重要的,因为表单生成器使用这种方法来选择补丁方法和url_for助手使用它来构建多态路线。

url_for(Service.first)               # => "/services/1"  
url_for(Service.new(id: 1))          # => "/services"
url_for(Service.new)                 # => "/services"
# NOTE: it is different from named route helpers,
#       which will grab required `params` from anything
#       argument, hash, model, or url params.
service_path(Service.first)          # => "/services/1"                  
service_path(Service.new(id: 1))     # => "/services/1"
service_path({id: 1})                # => "/services/1"
service_path(1)                      # => "/services/1"
# and if `params` have id: 1 (as in show action)
service_path                         # => "/services/1"

注意,默认情况下,ActiveModel::API实现了persist ?来返回false,这是最常见的情况。你可能需要重写在你的类中模拟一个不同的场景。

https://api.rubyonrails.org/classes/ActiveModel/API.html method-i-persisted-3F

https://api.rubyonrails.org/classes/ActiveModel/Model.html

https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Resources.html method-i-resource

https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/CustomUrls.html method-i-resolve

最新更新