我有一个使用ActiveModel::Model概念的对象:
class TransitProvider
include ActiveModel::Model
# ... more stuff
在表面之下,这个对象是Provider
记录和Service
记录的聚合。
一切似乎都工作得很好,但form_with
助手不承认TransitProvider
实例为持久(因为它没有自己的ID),因此edit
动作显示表单的数据,但提交它作为create
而不是update
。
是否有办法添加到一个ActiveModel类的东西,使form_with
将其视为一个现有的实例,而不是一个新的实例?
我需要在id
或persisted?
或类似的东西周围定义一些东西吗?
我似乎找不到任何特定于这个用例的东西。
谢谢!
覆盖persisted?
方法。它在ActiveModel::API
中定义:
def persisted?
false
end
这个方法被表单构建器用来决定它是否需要发送post或patch请求。
# 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