Mathieu
Mathieu

Reputation: 4787

Update massive number of rows of associated objects in has_many/belong_to relations (Rails 4, postgresql 9.4, activeadmin)

When creating a model Deal, I use an after_create to create prizes on the DealPrize table.

Deal and DealPrize have a belong to/has_many relations: a Deal has many Deal prizes and a Dealprize belongs to a Deal.

It works like this: on my admin panel (using activeadmin), inside a Deal, I have a column 'prize-number' and I use an after_create so that every time the admin creates a new deal, the app takes this prize_number column, and create this volume of prizes (inserting as many rows as necessary=> often more than 300,000) inside the DealPrize table.

So I create a Deal, and automatically, the app creates a huge number of associated objects (prizes) say 300,000.

The problem is I might wish 2 days later to change the prize number from 300,000 to 272,000 for example (less prizes) or 324,000 (more prizes).

How can I update the created Deal and tell my Rails app to update the number of associated Prizes (removing some or adding some) without deleeting and recreating the whole Deal object ?

Also, I need a fast way (like the one I use to create the Deal => raw sql and transaction as the number of rows to create at once is very high).

modals Deals.rb

has_many   :deal_prizes,  dependent: :delete_all

after_create :create_deal_prizes

CONNECTION = ActiveRecord::Base.connection.raw_connection

    def create_deal_prizes
      begin 
        CONNECTION.describe_prepared('yokoatxz')
      rescue PG::InvalidSqlStatementName
        CONNECTION.prepare('yokoatxz', 'INSERT INTO deal_prizes (deal_id,created_at,updated_at,admin_user_id) values ($1, $2, $3, $4)') 
      end

      Deal.transaction do  
        self.prizes_number.times do |i| 
          CONNECTION.exec_prepared('yokoatxz',  [ { value: self.id},
                                                  { value: '2009-01-23 20:21:13' },
                                                  { value: '2009-01-23 20:21:13' },
                                                  { value: self.admin_user_id }
                                                ] )
        end
      end
    end

Thanks for your help, Mathieu

Upvotes: 0

Views: 75

Answers (1)

David Aldridge
David Aldridge

Reputation: 52346

Well I think that if you just wanted to change the number of deal_prizes then you could use a before_save callback on the Deal to test for a difference between prize_number and prize_number_was, and issue either an insert or delete statement appropriately.

I expect for the delete you'd do something like:

deal_prizes.limit(prize_number_was - prize_number).delete_all

... if you didn't care which were deleted.

You would probably want to use a scope on the deal_prizes:

def self.deletable
  where(:taken => false)
end

... to apply the condition by which rows can be deleted though, and then merge this into the association on deal ...

has_many :deletable_deal_prizes, -> {merge(DealPrize.deletable)}, :class_name => "DealPrize"

... so you can:

deleteable_deal_prizes.limit(prize_number_was - prize_number).delete_all

Upvotes: 1

Related Questions