rspec - PG::UnableToSend:server 意外关闭连接这可能意味着服务器异常终止
rspec - PG::UnableToSend:server closed the connection unexpectedly This probably means the server terminated abnormally
在 Rails 应用程序的 ruby 上,当创建模型交易时,我使用 after_create 在 DealPrize table 上创建奖品。
Deal和DealPrize有属于to/has_many的关系:一个Deal有多个Deal奖品,一个Dealprize属于一个Deal。
它是这样工作的:在我的管理面板上(使用 activeadmin),在一个 Deal 中,我有一列 'prize-number' 并且我使用了一个 after_create 这样每次管理员创建一个新的deal,应用程序使用此 prize_number 列,并在 DealPrize table.
中创建此数量的奖品(根据需要插入尽可能多的行)
我使用 rspec 和 FactoryGirl 的测试失败了。这可能是因为 rspec/factory 女孩和 prepared_statements 玩得不好。 我不确定。
这是我的代码
models/deal.rb
has_many :deal_prizes, dependent: :delete_all
after_create :create_dealprizes
# Constants
TIME_SET = Time.zone.now
CONNECTION = ActiveRecord::Base.connection.raw_connection
def create_dealprizes
begin
CONNECTION.describe_prepared('create_deal_prizes')
rescue PG::InvalidSqlStatementName
CONNECTION.prepare('create_deal_prizes', 'INSERT INTO deal_prizes (deal_id,created_at,updated_at,admin_user_id,prize_id) values (, , , , )')
end
Deal.transaction do
self.prizes_number.times do |i|
CONNECTION.exec_prepared('create_deal_prizes', [
{ value: self.id},
{ value: TIME_SET },
{ value: TIME_SET },
{ value: self.admin_user_id },
{ value: 5 }
])
end
end
end
这是我的测试:我想确定交易何时创建,如果交易的 prizes_number 是 340,那么在 table Dealprizes 上添加 340 行,这是应该的。
require 'spec_helper'
describe DealPrize do
describe "the right number of rows are created inside DealPrize table when a Deal is created" do
before do
@initial_prize_count = DealPrize.count
@deal_prize = FactoryGirl.create(:deal_prize)
@deal = FactoryGirl.create(:deal_prizes => [@deal_prize], :prizes_number => 277)
end
it "does create right nb of rows" do
expect(DealPrize.count).to eq( @initial_prize_count + 277 )
end
end
end
我使用工厂进行交易:
FactoryGirl.define do
factory :deal do
country "France"
title "Neque porro quisquam est qui dolorem"
description "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum"
factory :deal_skips_validate do
to_create {|instance| instance.save(validate: false) }
end
end
end
这是 DealPrizes 的工厂:
FactoryGirl.define do
factory :deal_prize do
end
end
这是我收到的错误:
PG::UnableToSend:
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
如果需要,这里是我在 spec_helper.rb
中处理交易的方式
config.use_transactional_fixtures = false
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation, :except => %w(roles))
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, js: true) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
您只能从 ActiveRecord::Base.connection.raw_connection
中检索 CONNECTION
一次,因为正在加载 class。因此,框架可以自由关闭该连接并创建一个新连接。你应该在你真正需要它的时候取回它,然后我想它可能会起作用。
更新:据我所知,PostgreSQL 适配器也将创建准备好的语句,并且数据库应该能够重新使用它们,因此手动执行这些操作所带来的性能提升应该不会那么大(尤其是因为您"only" 谈论几百个对象)。当您可以只创建 DealPrize 对象时,由于非惯用 Rails 代码,您失去了与其他适配器的兼容性和可维护性。
每次运行方法时,您都需要从池中获取一个新连接(并确保在完成后 return 将其添加到池中)。看看with_connection.
像这样的东西应该有用。
class Deal < ActiveRecord::Base
has_many :deal_prizes, dependent: :delete_all
after_create :create_dealprizes
# Constants
TIME_SET = Time.zone.now
def create_dealprizes
self.class.connection_pool.with_connection do |connection|
begin
connection.describe_prepared('create_deal_prizes')
rescue PG::InvalidSqlStatementName
connection.prepare('create_deal_prizes', 'INSERT INTO deal_prizes (deal_id,created_at,updated_at,admin_user_id,prize_id) values (, , , , )')
end
connection.execute("START TRANSACTION")
prizes_number.times do |i|
connection.exec_prepared('create_deal_prizes', [
{ value: self.id},
{ value: TIME_SET },
{ value: TIME_SET },
{ value: self.admin_user_id },
{ value: 5 }
])
end
connection.execute("COMMIT")
end
end
end
这应该可行,但在我看来(就其价值而言)它不是很 Ruby-ish。听起来你很关心性能,所以我会用一个 INSERT 语句添加数据。您可以手动构建它,但是 activerecord-import gem 让它变得非常简单。
使用 gem 可能类似于:
class Deal < ActiveRecord::Base
has_many :deal_prizes, dependent: :delete_all
after_create :create_dealprizes
def create_dealprizes
prizes = prizes_number.times.map do
deal_prizes.build(created_by: admin_user, prize_id: 5)
end
DealPrize.import(prizes)
end
end
这是一个使用 ActiveRecord 导入的 sample app。
我可以提出以下建议吗?
def create_deal_prizes
batch_size = 1000
deal_prizes = []
time = Time.zone.now
prize_id = 5 # wasn't sure where you got this id from
Deal.transaction do
deal_prizes = []
prizes_number.times do
deal_prizes << "(#{self.id}, #{self.admin_user_id}, #{prize_id}, '#{time}', '#{time}')"
if deal_prizes.size >= batch_size
sql = "INSERT INTO deal_prizes (deal_id, admin_user_id, prize_id, created_at, updated_at) VALUES #{deal_prizes.join(", ")}"
ActiveRecord::Base.connection.execute sql
deal_prizes = []
end
end
if deal_prizes.any?
sql = "INSERT INTO deal_prizes (deal_id, admin_user_id, prize_id, created_at, updated_at) VALUES #{deal_prizes.join(", ")}"
ActiveRecord::Base.connection.execute sql
end
end
end
这更 rails-esque,高效,并且当您切换到 [INSERT DB HERE] 时不必重新编写。也可以随意使用 batch_size。
另一个注意事项,如果您将时间设置为常数,几乎是肯定的,它不会改变,这是您的意图吗?相反,我将它移到了方法中,因此它将锁定到它被调用的时间,而不是 class 初始化(引导)的时间。
在 Rails 应用程序的 ruby 上,当创建模型交易时,我使用 after_create 在 DealPrize table 上创建奖品。
Deal和DealPrize有属于to/has_many的关系:一个Deal有多个Deal奖品,一个Dealprize属于一个Deal。
它是这样工作的:在我的管理面板上(使用 activeadmin),在一个 Deal 中,我有一列 'prize-number' 并且我使用了一个 after_create 这样每次管理员创建一个新的deal,应用程序使用此 prize_number 列,并在 DealPrize table.
中创建此数量的奖品(根据需要插入尽可能多的行)我使用 rspec 和 FactoryGirl 的测试失败了。这可能是因为 rspec/factory 女孩和 prepared_statements 玩得不好。 我不确定。
这是我的代码
models/deal.rb
has_many :deal_prizes, dependent: :delete_all
after_create :create_dealprizes
# Constants
TIME_SET = Time.zone.now
CONNECTION = ActiveRecord::Base.connection.raw_connection
def create_dealprizes
begin
CONNECTION.describe_prepared('create_deal_prizes')
rescue PG::InvalidSqlStatementName
CONNECTION.prepare('create_deal_prizes', 'INSERT INTO deal_prizes (deal_id,created_at,updated_at,admin_user_id,prize_id) values (, , , , )')
end
Deal.transaction do
self.prizes_number.times do |i|
CONNECTION.exec_prepared('create_deal_prizes', [
{ value: self.id},
{ value: TIME_SET },
{ value: TIME_SET },
{ value: self.admin_user_id },
{ value: 5 }
])
end
end
end
这是我的测试:我想确定交易何时创建,如果交易的 prizes_number 是 340,那么在 table Dealprizes 上添加 340 行,这是应该的。
require 'spec_helper'
describe DealPrize do
describe "the right number of rows are created inside DealPrize table when a Deal is created" do
before do
@initial_prize_count = DealPrize.count
@deal_prize = FactoryGirl.create(:deal_prize)
@deal = FactoryGirl.create(:deal_prizes => [@deal_prize], :prizes_number => 277)
end
it "does create right nb of rows" do
expect(DealPrize.count).to eq( @initial_prize_count + 277 )
end
end
end
我使用工厂进行交易:
FactoryGirl.define do
factory :deal do
country "France"
title "Neque porro quisquam est qui dolorem"
description "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum"
factory :deal_skips_validate do
to_create {|instance| instance.save(validate: false) }
end
end
end
这是 DealPrizes 的工厂:
FactoryGirl.define do
factory :deal_prize do
end
end
这是我收到的错误:
PG::UnableToSend:
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
如果需要,这里是我在 spec_helper.rb
中处理交易的方式config.use_transactional_fixtures = false
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation, :except => %w(roles))
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, js: true) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
您只能从 ActiveRecord::Base.connection.raw_connection
中检索 CONNECTION
一次,因为正在加载 class。因此,框架可以自由关闭该连接并创建一个新连接。你应该在你真正需要它的时候取回它,然后我想它可能会起作用。
更新:据我所知,PostgreSQL 适配器也将创建准备好的语句,并且数据库应该能够重新使用它们,因此手动执行这些操作所带来的性能提升应该不会那么大(尤其是因为您"only" 谈论几百个对象)。当您可以只创建 DealPrize 对象时,由于非惯用 Rails 代码,您失去了与其他适配器的兼容性和可维护性。
每次运行方法时,您都需要从池中获取一个新连接(并确保在完成后 return 将其添加到池中)。看看with_connection.
像这样的东西应该有用。
class Deal < ActiveRecord::Base
has_many :deal_prizes, dependent: :delete_all
after_create :create_dealprizes
# Constants
TIME_SET = Time.zone.now
def create_dealprizes
self.class.connection_pool.with_connection do |connection|
begin
connection.describe_prepared('create_deal_prizes')
rescue PG::InvalidSqlStatementName
connection.prepare('create_deal_prizes', 'INSERT INTO deal_prizes (deal_id,created_at,updated_at,admin_user_id,prize_id) values (, , , , )')
end
connection.execute("START TRANSACTION")
prizes_number.times do |i|
connection.exec_prepared('create_deal_prizes', [
{ value: self.id},
{ value: TIME_SET },
{ value: TIME_SET },
{ value: self.admin_user_id },
{ value: 5 }
])
end
connection.execute("COMMIT")
end
end
end
这应该可行,但在我看来(就其价值而言)它不是很 Ruby-ish。听起来你很关心性能,所以我会用一个 INSERT 语句添加数据。您可以手动构建它,但是 activerecord-import gem 让它变得非常简单。
使用 gem 可能类似于:
class Deal < ActiveRecord::Base
has_many :deal_prizes, dependent: :delete_all
after_create :create_dealprizes
def create_dealprizes
prizes = prizes_number.times.map do
deal_prizes.build(created_by: admin_user, prize_id: 5)
end
DealPrize.import(prizes)
end
end
这是一个使用 ActiveRecord 导入的 sample app。
我可以提出以下建议吗?
def create_deal_prizes
batch_size = 1000
deal_prizes = []
time = Time.zone.now
prize_id = 5 # wasn't sure where you got this id from
Deal.transaction do
deal_prizes = []
prizes_number.times do
deal_prizes << "(#{self.id}, #{self.admin_user_id}, #{prize_id}, '#{time}', '#{time}')"
if deal_prizes.size >= batch_size
sql = "INSERT INTO deal_prizes (deal_id, admin_user_id, prize_id, created_at, updated_at) VALUES #{deal_prizes.join(", ")}"
ActiveRecord::Base.connection.execute sql
deal_prizes = []
end
end
if deal_prizes.any?
sql = "INSERT INTO deal_prizes (deal_id, admin_user_id, prize_id, created_at, updated_at) VALUES #{deal_prizes.join(", ")}"
ActiveRecord::Base.connection.execute sql
end
end
end
这更 rails-esque,高效,并且当您切换到 [INSERT DB HERE] 时不必重新编写。也可以随意使用 batch_size。
另一个注意事项,如果您将时间设置为常数,几乎是肯定的,它不会改变,这是您的意图吗?相反,我将它移到了方法中,因此它将锁定到它被调用的时间,而不是 class 初始化(引导)的时间。