Rails 4 具有 simple_form_for、has_many 通过和嵌套属性
Rails 4 with simple_form_for, has_many through and nested attributes
我需要与此几乎相同的功能:
https://robots.thoughtbot.com/accepts-nested-attributes-for-with-has-many-through
我一直在绕来绕去(又绕来绕去)试图让它正常运行,但让 运行 陷入障碍。我对 Ruby 和 Rails 还是有些陌生,在前进的过程中需要帮助。这是我目前存在的实现:
/models/transfer.rb
class Transfer < ActiveRecord::Base
validates :name, presence: true,
uniqueness: { case_sensitive: false }
has_many :transfer_accounts, inverse_of: :transfer
has_many :accounts, through: :transfer_accounts
accepts_nested_attributes_for :transfer_accounts
end
/models/transfer_account.rb
class TransferAccount < ActiveRecord::Base
validates :account_transfer_role, presence: true
belongs_to :account, inverse_of: :transfer_accounts
belongs_to :transfer, inverse_of: :transfer_accounts
validates :account, presence: true
validates :transfer, presence: true
accepts_nested_attributes_for :account
end
/models/account.rb
class Account < ActiveRecord::Base
validates :name, presence: true,
uniqueness: { case_sensitive: false }
validates :user_name, presence: true
validates :password, presence: true
validates :account_number, presence: true,
uniqueness: { case_sensitive: false }
validates :routing_number, presence: true
has_many :transfer_accounts, inverse_of: :account
has_many :transfers, through: :transfer_accounts
belongs_to :bank, inverse_of: :accounts
end
/models/bank.rb
class Bank < ActiveRecord::Base
validates :name, presence: true,
uniqueness: { case_sensitive: false }
validates :connect_uri, presence: true
has_many :accounts
end
/controllers/transfers_controller.rb
class TransfersController < ApplicationController
def new
@transfer = Transfer.new
@transfer.transfer_accounts.build(account_transfer_role: 'source').build_account
@transfer.transfer_accounts.build(account_transfer_role: 'destination').build_account
@valid_banks = Bank.all.collect {|c| [c.name, c.id]} # available banks seeded in database
end
def index
@transfers = Transfer.all
end
def show
@transfer = resource
end
def create
@transfer = Transfer.new(transfer_params)
if @transfer.save
redirect_to transfers_path, notice: "Transfer Created"
else
redirect_to transfers_path, alert: "Transfer Not Created"
end
end
def edit
resource
end
def update
if resource.update_attributes(transfer_params)
redirect_to transfers_path(resource), notice: "Transfer Updated"
else
redirect_to edit_transfer_path(resource), alert: "Transfer Not Updated"
end
end
def destroy
resource.destroy
end
private
def resource
@transfer ||= transfer.find(params[:id])
end
def transfer_params
params.require(:transfer).
permit(:name, :description,
transfer_accounts_attributes:
[:account_transfer_role,
account_attributes:
[:name, :description, :user_name, :password,
:routing_number, :account_number
]
])
end
end
/controllers/banks_controller.rb
class BanksController < ApplicationController
def index
@bank = Bank.new
@banks = Bank.by_last_updated_at
end
def show
@bank = resource
end
def create
@bank = Bank.new(bank_params)
if @bank.save
redirect_to banks_path, notice: "Bank Created"
else
redirect_to banks_path, alert: "Bank Not Created"
end
end
def edit
resource
end
def update
if resource.update_attributes(bank_params)
redirect_to banks_path(resource), notice: "Bank Updated"
else
redirect_to edit_bank_path(resource), alert: "Bank Not Updated"
end
end
def destroy
resource.destroy
end
private
def resource
@bank ||= Bank.find(params[:id])
end
def bank_params
params.require(:bank).
permit(:name, :description, :connection_uri)
end
end
/views/transfers/_form.html.haml
= simple_form_for :transfer do |t|
.form-inputs
= t.input :name, label: "Transfer Name"
= t.input :description, required: false, label: "Transfer Description"
= t.simple_fields_for :transfer_accounts do |ta|
- role = ta.object.account_transfer_role.titleize
= ta.input :account_transfer_role, as: :hidden
= ta.simple_fields_for :account do |a|
= a.input :bank_id, collection: @valid_banks,
include_blank: 'Select bank...',
id: 'bank',
class: 'bank_selector',
label: '#{role} Bank',
error: '#{role} bank selection is required.'
= a.input :name, label: "#{role} Account Name"
= a.input :description, required: false, label: "#{role} Account Description"
= a.input :user_name, label: "#{role} Account User Name"
= a.input :password, label: "#{role} Account Password"
= a.input :account_number, label: "#{role} Account Number"
= a.input :routing_number, label: "#{role} Account Routing Number"
= t.submit
/db/migrate/20151102001000_create_transfers.rb
class CreateTransfers < ActiveRecord::Migration
def change
create_table :transfers do |t|
t.string :name, null: false, default: ''
t.text :description
t.timestamps
end
end
end
/db/migrate/20151102002000_create_transfer_accounts.rb
class CreateTransferAccounts < ActiveRecord::Migration
def change
create_table :transfer_accounts do |t|
t.string :account_transfer_role, null: false, default: ''
t.references :transfer, index: true
t.references :account, index: true
t.timestamps null: false
end
end
end
/db/migrate/20151102003000_create_accounts.rb
class CreateAccounts < ActiveRecord::Migration
def change
create_table :accounts do |t|
t.string :name, null: false, default: ''
t.string :description
t.string :user_name, null: false, default: ''
t.string :password, null: false, default: ''
t.string :account_number, null: false, default: ''
t.string :routing_number, null: false, default: ''
t.references :bank, index: true
t.timestamps
end
end
end
/db/migrate/20151102004000_create_banks.rb
class CreateBanks < ActiveRecord::Migration
def change
create_table :banks do |t|
t.string :name, null: false, default: ''
t.string :description
t.string :connection_uri, null: false, default: ''
t.timestamps
end
end
end
/db/migrate/20151102005000_add_foreign_keys_to_transfer_accounts.rb
class AddForeignKeysToTransferAccounts < ActiveRecord::Migration
def change
add_foreign_key :transfer_accounts, :accounts
add_foreign_key :transfer_accounts, :transfers
end
end
/db/migrate/20151102006000_add_foreign_keys_to_accounts.rb
class AddForeignKeysToAccounts < ActiveRecord::Migration
def change
add_foreign_key :accounts, :banks
end
end
/db/seeds.rb
Bank.create(name: 'Acme Savings and Loan',
description: 'The number one bank in the northeast',
connection_uri: 'https://www.acmesavings.com')
Bank.create(name: 'First Bank of Anytown',
description: 'The first and only bank in Anytown',
connection_uri: 'https://www.firstbankanytown.com')
Bank.create(name: 'Generibank',
description: 'The most generic bank in the country',
connection_uri: 'https://www.generibank.com')
/config/routes.rb
Rails.application.routes.draw do
resources :transfers
resources :accounts
resources :banks
root to: 'dashboard#index'
end
所以,目前我的问题是:
表单视图中的这一行- role = ta.object.account_transfer_role.titleize
给我一个“undefined method `account_transfer_role' for nil:NilClass”错误,那么我在那里做错了什么?
为什么(或为什么会)transfer_account
模型中的 accepts_nested_attributes_for
行有效?我的印象是 accepts_nested_attributes_for
在关联的 belongs_to
端不起作用,因为它不是父级(或类似的东西)。
如果我注释掉与问题 #1 相关的代码以避免该错误,表单会呈现,但我只得到一组 account
嵌套属性的输入框。每个 transfer
在 transfers_controller
的“new”操作中构建并关联了两个 transfer_accounts
和两个 accounts
(使用 transfer_account
:account_transfer_role
值 'source' 和 'destination'),我不应该得到两套吗account
个嵌套属性输入框?
我的嵌套属性的 singulars/plurals 是否正确?基本上,我让他们保持一致,从他们的联想开始。例如,transfer
has_many :transfer_accounts
,因此 transfer_accounts
在以下所有情况下都是复数:
/models/transfer.rb
accepts_nested_attributes_for :transfer_accounts
/controllers/transfers_controller.rb
@transfer.transfer_accounts.build(account_transfer_role: 'source').build_account
@transfer.transfer_accounts.build(account_transfer_role: 'destination').build_account
.
.
.
def transfer_params
params.require(:transfer).
permit(:name, :description,
transfer_accounts_attributes:
[:account_transfer_role,
account_attributes:
[:name, :description, :user_name, :password,
:routing_number, :account_number
]
])
end
/views/transfers/_form.html.haml
= m.simple_fields_for :transfer_accounts do |ma|
account
也是如此,除了单数。
如果我执行第 3 步,使用提供的一组 account
属性填写表单,然后提交,我会得到一个'没有路由匹配 [POST] "/transfers/new' 错误。所以,显然我的路由有问题。我不确定 transfers
和 accounts
应该如何出现在routes.rb 文件.as
resources :transfers
resources :accounts
或
resources :transfers do
resources :accounts
end
或者还有其他方式。另外,我不知道路由文件中是否也需要 transfer_accounts
。
如果您已到达此行,感谢您的耐心等待。:>) 非常感谢您提供的任何帮助。
干杯,
蒂姆
事实证明解决方案非常简单,解决了我的问题 1、3、4 和 5 中提出的每个问题。
在表单中,我更改了这一行:
= simple_form_for :transfer do |t|
对此:
= simple_form_for @transfer do |t|
对于问题 1,account_transfer_role
不再为零。
对于问题 3,它开始为 transfer_accounts
.
渲染输入框
对于第4题,strong参数中的单数和复数证明是正确的,如图所示
对于问题 5,这个版本的路由被证明是正确的:
resources :transfers
resources :accounts
尽管我将其简化为:
resources :transfers, :accounts
至于问题 2,我得出的最佳答案是我的理解似乎不正确,accepts_nested_attributes_for:
确实在关联的 belongs_to
方面起作用,因为它是为我工作。
我希望这对那些在 has_many, through
关联的具有嵌套属性的表单上苦苦挣扎的人有所帮助。
干杯!
我需要与此几乎相同的功能:
https://robots.thoughtbot.com/accepts-nested-attributes-for-with-has-many-through
我一直在绕来绕去(又绕来绕去)试图让它正常运行,但让 运行 陷入障碍。我对 Ruby 和 Rails 还是有些陌生,在前进的过程中需要帮助。这是我目前存在的实现:
/models/transfer.rb
class Transfer < ActiveRecord::Base
validates :name, presence: true,
uniqueness: { case_sensitive: false }
has_many :transfer_accounts, inverse_of: :transfer
has_many :accounts, through: :transfer_accounts
accepts_nested_attributes_for :transfer_accounts
end
/models/transfer_account.rb
class TransferAccount < ActiveRecord::Base
validates :account_transfer_role, presence: true
belongs_to :account, inverse_of: :transfer_accounts
belongs_to :transfer, inverse_of: :transfer_accounts
validates :account, presence: true
validates :transfer, presence: true
accepts_nested_attributes_for :account
end
/models/account.rb
class Account < ActiveRecord::Base
validates :name, presence: true,
uniqueness: { case_sensitive: false }
validates :user_name, presence: true
validates :password, presence: true
validates :account_number, presence: true,
uniqueness: { case_sensitive: false }
validates :routing_number, presence: true
has_many :transfer_accounts, inverse_of: :account
has_many :transfers, through: :transfer_accounts
belongs_to :bank, inverse_of: :accounts
end
/models/bank.rb
class Bank < ActiveRecord::Base
validates :name, presence: true,
uniqueness: { case_sensitive: false }
validates :connect_uri, presence: true
has_many :accounts
end
/controllers/transfers_controller.rb
class TransfersController < ApplicationController
def new
@transfer = Transfer.new
@transfer.transfer_accounts.build(account_transfer_role: 'source').build_account
@transfer.transfer_accounts.build(account_transfer_role: 'destination').build_account
@valid_banks = Bank.all.collect {|c| [c.name, c.id]} # available banks seeded in database
end
def index
@transfers = Transfer.all
end
def show
@transfer = resource
end
def create
@transfer = Transfer.new(transfer_params)
if @transfer.save
redirect_to transfers_path, notice: "Transfer Created"
else
redirect_to transfers_path, alert: "Transfer Not Created"
end
end
def edit
resource
end
def update
if resource.update_attributes(transfer_params)
redirect_to transfers_path(resource), notice: "Transfer Updated"
else
redirect_to edit_transfer_path(resource), alert: "Transfer Not Updated"
end
end
def destroy
resource.destroy
end
private
def resource
@transfer ||= transfer.find(params[:id])
end
def transfer_params
params.require(:transfer).
permit(:name, :description,
transfer_accounts_attributes:
[:account_transfer_role,
account_attributes:
[:name, :description, :user_name, :password,
:routing_number, :account_number
]
])
end
end
/controllers/banks_controller.rb
class BanksController < ApplicationController
def index
@bank = Bank.new
@banks = Bank.by_last_updated_at
end
def show
@bank = resource
end
def create
@bank = Bank.new(bank_params)
if @bank.save
redirect_to banks_path, notice: "Bank Created"
else
redirect_to banks_path, alert: "Bank Not Created"
end
end
def edit
resource
end
def update
if resource.update_attributes(bank_params)
redirect_to banks_path(resource), notice: "Bank Updated"
else
redirect_to edit_bank_path(resource), alert: "Bank Not Updated"
end
end
def destroy
resource.destroy
end
private
def resource
@bank ||= Bank.find(params[:id])
end
def bank_params
params.require(:bank).
permit(:name, :description, :connection_uri)
end
end
/views/transfers/_form.html.haml
= simple_form_for :transfer do |t|
.form-inputs
= t.input :name, label: "Transfer Name"
= t.input :description, required: false, label: "Transfer Description"
= t.simple_fields_for :transfer_accounts do |ta|
- role = ta.object.account_transfer_role.titleize
= ta.input :account_transfer_role, as: :hidden
= ta.simple_fields_for :account do |a|
= a.input :bank_id, collection: @valid_banks,
include_blank: 'Select bank...',
id: 'bank',
class: 'bank_selector',
label: '#{role} Bank',
error: '#{role} bank selection is required.'
= a.input :name, label: "#{role} Account Name"
= a.input :description, required: false, label: "#{role} Account Description"
= a.input :user_name, label: "#{role} Account User Name"
= a.input :password, label: "#{role} Account Password"
= a.input :account_number, label: "#{role} Account Number"
= a.input :routing_number, label: "#{role} Account Routing Number"
= t.submit
/db/migrate/20151102001000_create_transfers.rb
class CreateTransfers < ActiveRecord::Migration
def change
create_table :transfers do |t|
t.string :name, null: false, default: ''
t.text :description
t.timestamps
end
end
end
/db/migrate/20151102002000_create_transfer_accounts.rb
class CreateTransferAccounts < ActiveRecord::Migration
def change
create_table :transfer_accounts do |t|
t.string :account_transfer_role, null: false, default: ''
t.references :transfer, index: true
t.references :account, index: true
t.timestamps null: false
end
end
end
/db/migrate/20151102003000_create_accounts.rb
class CreateAccounts < ActiveRecord::Migration
def change
create_table :accounts do |t|
t.string :name, null: false, default: ''
t.string :description
t.string :user_name, null: false, default: ''
t.string :password, null: false, default: ''
t.string :account_number, null: false, default: ''
t.string :routing_number, null: false, default: ''
t.references :bank, index: true
t.timestamps
end
end
end
/db/migrate/20151102004000_create_banks.rb
class CreateBanks < ActiveRecord::Migration
def change
create_table :banks do |t|
t.string :name, null: false, default: ''
t.string :description
t.string :connection_uri, null: false, default: ''
t.timestamps
end
end
end
/db/migrate/20151102005000_add_foreign_keys_to_transfer_accounts.rb
class AddForeignKeysToTransferAccounts < ActiveRecord::Migration
def change
add_foreign_key :transfer_accounts, :accounts
add_foreign_key :transfer_accounts, :transfers
end
end
/db/migrate/20151102006000_add_foreign_keys_to_accounts.rb
class AddForeignKeysToAccounts < ActiveRecord::Migration
def change
add_foreign_key :accounts, :banks
end
end
/db/seeds.rb
Bank.create(name: 'Acme Savings and Loan',
description: 'The number one bank in the northeast',
connection_uri: 'https://www.acmesavings.com')
Bank.create(name: 'First Bank of Anytown',
description: 'The first and only bank in Anytown',
connection_uri: 'https://www.firstbankanytown.com')
Bank.create(name: 'Generibank',
description: 'The most generic bank in the country',
connection_uri: 'https://www.generibank.com')
/config/routes.rb
Rails.application.routes.draw do
resources :transfers
resources :accounts
resources :banks
root to: 'dashboard#index'
end
所以,目前我的问题是:
表单视图中的这一行
- role = ta.object.account_transfer_role.titleize
给我一个“undefined method `account_transfer_role' for nil:NilClass”错误,那么我在那里做错了什么?为什么(或为什么会)
transfer_account
模型中的accepts_nested_attributes_for
行有效?我的印象是accepts_nested_attributes_for
在关联的belongs_to
端不起作用,因为它不是父级(或类似的东西)。如果我注释掉与问题 #1 相关的代码以避免该错误,表单会呈现,但我只得到一组
account
嵌套属性的输入框。每个transfer
在transfers_controller
的“new”操作中构建并关联了两个transfer_accounts
和两个accounts
(使用transfer_account
:account_transfer_role
值 'source' 和 'destination'),我不应该得到两套吗account
个嵌套属性输入框?我的嵌套属性的 singulars/plurals 是否正确?基本上,我让他们保持一致,从他们的联想开始。例如,
transfer
has_many :transfer_accounts
,因此transfer_accounts
在以下所有情况下都是复数:/models/transfer.rb accepts_nested_attributes_for :transfer_accounts /controllers/transfers_controller.rb @transfer.transfer_accounts.build(account_transfer_role: 'source').build_account @transfer.transfer_accounts.build(account_transfer_role: 'destination').build_account . . . def transfer_params params.require(:transfer). permit(:name, :description, transfer_accounts_attributes: [:account_transfer_role, account_attributes: [:name, :description, :user_name, :password, :routing_number, :account_number ] ]) end /views/transfers/_form.html.haml = m.simple_fields_for :transfer_accounts do |ma|
account
也是如此,除了单数。如果我执行第 3 步,使用提供的一组
account
属性填写表单,然后提交,我会得到一个'没有路由匹配 [POST] "/transfers/new' 错误。所以,显然我的路由有问题。我不确定transfers
和accounts
应该如何出现在routes.rb 文件.asresources :transfers resources :accounts
或
resources :transfers do resources :accounts end
或者还有其他方式。另外,我不知道路由文件中是否也需要
transfer_accounts
。
如果您已到达此行,感谢您的耐心等待。:>) 非常感谢您提供的任何帮助。
干杯, 蒂姆
事实证明解决方案非常简单,解决了我的问题 1、3、4 和 5 中提出的每个问题。
在表单中,我更改了这一行:
= simple_form_for :transfer do |t|
对此:
= simple_form_for @transfer do |t|
对于问题 1,account_transfer_role
不再为零。
对于问题 3,它开始为 transfer_accounts
.
渲染输入框
对于第4题,strong参数中的单数和复数证明是正确的,如图所示
对于问题 5,这个版本的路由被证明是正确的:
resources :transfers
resources :accounts
尽管我将其简化为:
resources :transfers, :accounts
至于问题 2,我得出的最佳答案是我的理解似乎不正确,accepts_nested_attributes_for:
确实在关联的 belongs_to
方面起作用,因为它是为我工作。
我希望这对那些在 has_many, through
关联的具有嵌套属性的表单上苦苦挣扎的人有所帮助。
干杯!