20

How do I destroy all but the newest n records using Rails' ActiveRecord?

I can get the newest n records using order and limit but how do I destroy the inverse?

dteoh
  • 5,794
  • 3
  • 27
  • 35

7 Answers7

26

Either of these methods would do it:

# Fetch your latest N records
newest_n_records = Foo.find(:all, :order => 'created_at DESC', :limit => n)

# Then do:
Foo.destroy_all(['id NOT IN (?)', newest_n_records.collect(&:id)])

# Or:
Foo.destroy_all('created_at < ?', newest_n_records.last.created_at)
vonconrad
  • 25,227
  • 7
  • 68
  • 69
  • In Rails 4.2.0 you must send correct message to AR object: `Foo.all.order('created_at DESC').limit(n)` – borjagvo Feb 17 '16 at 12:15
  • Note that this will work on an [ActiveRecord::Relation](http://apidock.com/rails/ActiveRecord/Relation/destroy_all) because it accepts `conditions`. It will NOT work on an ActiveRecord::CollectionProxy because the `destroy_all` method simply runs `@association.destroy_all` without arguments. (https://github.com/rails/rails/blob/58772397e9b790e80bcd4d8e51937dc82ecb719e/activerecord/lib/active_record/associations/collection_proxy.rb#L504-L506) Trying to use arguments in `destroy_all` for a CollectionProxy will raise an `ArgumentError` exception. – anothermh May 17 '16 at 00:09
20

I have two methods of doing this, assuming n = 5:

Foo.order('id desc').offset(5).destroy_all

This sorts records with latest first, and destroys everything past the 5th records. Or

Foo.destroy_all(['id <= ?', Foo.order('id desc').limit(1).offset(5).first.id])

This finds the 6th latest record id and deletes all records with id <= 6th latest record id.

Also, you might want to look at this SO question.

Community
  • 1
  • 1
konyak
  • 10,818
  • 4
  • 59
  • 65
7
Foo.destroy_all(['id NOT IN (?)', Foo.last(1000).collect(&:id)])
eychu
  • 863
  • 7
  • 3
5

Person.destroy_all("last_login < '2004-04-04'")

This will destroy all persons who meet the condition. So all you need is inverted conditions and destroy_all

Utsav T
  • 1,515
  • 2
  • 24
  • 42
Tadas T
  • 2,492
  • 20
  • 20
  • 1
    Worked for me, and if you're not worried about your model callbacks running and associations, you can call `delete_all` instead of `destroy_all` to do this in a single SQL DELETE statement to save instantiating a model object for each record. – Eliot Sykes Aug 06 '13 at 15:43
1

Previous answers use find or last require the creation of ActiveModel, which take extra computation time.

I think using pluck is better, since it only creates an Array of ids.

ids = Foo.limit(n).order('id DESC').pluck(:id)
Foo.where('id NOT IN (?)', ids).destroy_all
Steven Yue
  • 966
  • 1
  • 8
  • 16
0

[Rails 5 / ActiveRecord::Relation]

destroy_all no longer takes parameters... Actually, the ActiveRecord::Relation never allowed parameters I don't think... Anyway, you should just put the condition before it, but use destroy_all after the query, like this:

Person.destroy_all("last_login < '2004-04-04'")
Person.destroy_all(status: "inactive")
Person.where(age: 0..18).destroy_all
richardun
  • 693
  • 5
  • 11
0

None of these work in Rails 6, but delete_by does.

keep_ids = [2345, 345256, 34]
Component.delete_by('id NOT IN (?) AND status = "inactive"', keep_ids) }
t56k
  • 6,769
  • 9
  • 52
  • 115