winton / acts_as_archive

Don't delete your records, move them to a different table
MIT License
372 stars 87 forks source link

Archive objects don't mimic the methods in the original object #2

Closed nielsm closed 14 years ago

nielsm commented 14 years ago

class Foo < ActiveRecord::Base has_many :bar belongs_to :account

def name return "#{first_name} #{last_name}" end end

Build a foo: foo = Foo.create(:first_name => "John", :last_name => "Adams", :account_id => 1) bar = Bar.new bar.foo = foo bar.save foo_id = foo.id bar_id = bar.id

Now we can call foo.bars & get back the bar created. If we call foo.name, we get "John Adams". If we call foo.account, we get Account with id = 1. If we call foo.destroy, it is moved to the archive table. However, if we now load it as:

foo = Foo::Archive.find(foo_id) foo.name #NoMethodError: undefined method name' for #<Foo::Archive:0x105ffdcb8> foo.bars #NoMethodError: undefined methodbars' for #Foo::Archive:0x105ffdcb8 foo.account #NoMethodError: undefined method `account' for #Foo::Archive:0x105ffdcb8

Is there any way to archive it in a way that the methods are still maintained? How can we handle relationships, such that they are also maintained?

winton commented 14 years ago

Hello,

I purposefully left this functionality out, partially to keep things simple, and partially because it seems like you should be restoring the archived record if you really wish to use all of the model's features. Off the top of my head, type columns and polymorphic columns would not behave correctly under the ::Archive class.

Could you provide a scenario where having the full model functionality in the ::Archive class makes more sense than restoring the record and using your original model?

nielsm commented 14 years ago

I have an object that is used for building something for a client. Other tables reference that object when activities occur which are billable. The client might choose to delete that original object, & therefore no new actions would be created for it, but if actions have occurred that are billable to that item, I'd like to still reference some criteria of the original object when I generate the bill.

Essentially, I need a way to retrieve all of the Foos (archived or not) when collecting the billable bars. Ideally this would involve some way to pass (:include_archives => true), but I can do a fallback to Foo::Archive.find(id) if Foo.find(id) fails. Once I have each "Foo", I need to grab some perhaps method-scoped values like associations or the name method. Restoring the object isn't appropriate, since I only need it for the billing purpose & don't want the client to suddenly have the Foo's back in their list of manageable items.

winton commented 14 years ago

Ah, I see. Honestly adding this functionality is not a path I want to go down since it does entail a lot of work, and possibly a lot more support questions when certain Rails features do not work as expected. I do have a few suggestions though.

Try doing a Foo.new(Foo::Archive.find(id).attributes) and see if you can use the methods on that object in the way you expect.

If not, consider restoring the record for the brief amount of time that it needs to be used (I'm guessing this is milliseconds in computer land) and then destroy it again when finished. The chance that your client loads the page in this brief span is very unlikely.

Third, consider taking a look at acts_as_paranoid. I made this plugin because we had performance issues with acts_as_paranoid adding the extra SQL on every query, but if you are not under high load, this might be a viable option.

I am going to close this out, but please comment back on what you end up going with.

nielsm commented 14 years ago

I completely understand wanting to keep the scope of functionality simple, but figured it was worth asking about.

The Foo.new(Foo::Archive.find(id).attributes)) is an interesting idea. It won't work directly due to the deleted_at column, but it can work like this:

archived_attributes = Foo::Archive.find(id).attributes archived_attributes.delete("deleted_at") Foo.new(archived_attributes)

I actually am trying to move away from acts_as_paranoid for many of the same reasons you did & liked the approach you've taken with this project.

winton commented 14 years ago

Ah yes, I forgot about the deleted_at. I'll be sure to remember that if anybody asks about this again. If I have time, this weekend I'll add a method on ::Archive that generates that object automatically.

Yes, acts_as_paranoid gave us a few headaches on the road to scalability. I hope that technique works out for you.

tpickett66 commented 13 years ago

I know this is an old post but in case anyone still needs this. The way we're getting around this is Foo.new(Foo::Archive.find(id).attributes.reject{|key,val| key.to_s == "deleted_at"})