Rspec - PG :: UnableToSend: the server unexpectedly closed the connection. This probably means the server has shut down abnormally.

In my ruby ​​on Rails application, when creating a Deal model, I use after_create to create prizes in the DealPrize table.

The deal and DealPrize belong to the / has _many relationship: the deal has many Deal prizes, and Dealprize belongs to the deal.

It works as follows: in my admin panel (using activeadmin) inside the transaction, I have a prize number in the column, and I use after_create, so every time the administrator creates a new deal, the application accepts this column “column” and create this prize volume (inserting as many rows as necessary) inside the DealPrize table.

My tests using rspec and FactoryGirl fail. This may be due to the fact that the girl does not play well with prep_statements. I'm not sure.

Here is my code

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 ($1, $2, $3, $4, $5)') 
  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

Here is my test: I would like to be sure when the transaction will be created, if there are 340 prizes_number in the transaction, then 340 tables will be added to the Dealprizes table, as it should be.

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

I use factory for transactions:

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  

and here is the factory for DealPrizes:

FactoryGirl.define do
  factory :deal_prize do
  end
end

Here is the error I get:

PG::UnableToSend:
       server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.

If necessary, this is how I view the transaction in my 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
+4
source share
3 answers

, ( , , ). 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 ($1, $2, $3, $4, $5)') 
      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 .

:

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.

+2

CONNECTION ActiveRecord::Base.connection.raw_connection , . , . , , , .

: afaik PostgreSQL , , (, "" " ". , - Rails, DealPrize.

+1

?

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 ]. batch_size.

, , , , ? , , ().

+1

Source: https://habr.com/ru/post/1607889/


All Articles