3

I have a simple model with 3 attributes id, paylod (binary data, big), created_at.

I need to extract a bunch of values from the payload data for further processing which I do in the after_initialize callback method. As the payload can be quite big (~20MB), I want to dispose this data by setting @payload = nil after extracting the necessary information to prevent out-of-memory situations when loading a bunch of entries. Note: The model only reads from the DB, no need to persist any changes.

class Payload < ActiveRecord::Base
  after_initialize do |data|
    # extract required values from binary data
    # ... 

    # dispose big data 
    error.payload = nil
    # at this point error.changed_attributes['payload'] 
    # contains the previous payload data (~20MB)
  end
end

How can I prevent the model of preserving the previous value in the @changed_attributes hash?

Oliver
  • 89
  • 1
  • 8

3 Answers3

1

Call clear_changes_information after setting payload to nil. Of course this only works if there are no other changes you wish to preserve.

UPDATE

Example:

> user.name
=> "Dave Smith"
> user.name = nil
=> nil
> user.changes
=> {"name"=>["Dave Smith", nil]}
> user.clear_changes_information
=> {}
> user.changes
=> {}

Also, there is this method restore_attributes

Note, I am using ActiveRecord 4.2.4.

Community
  • 1
  • 1
Wizard of Ogz
  • 12,543
  • 2
  • 41
  • 43
  • I saw the `clear_changes_information` method in the `ActiveModel::Dirty` documentation, but it is not available on my object. I've also tried `include ActiveModel::Dirty` in my model, without any success. What am I doing wrong? Can you make an example? – Oliver Nov 18 '15 at 14:17
  • The example provides just what I would like, yet doesn't work for me. I receive `undefined method `clear_changes_information'` after `error.payload = nil; error.clear_changes_information;` in the `after_initialize` block. This is using jruby-9.0.1.9 with ActiveRecord 4.1.8. – Oliver Nov 18 '15 at 15:33
1

If the attribute setter method is overloaded without calling super(), all callbacks are disabled and no changes are tracked.

def payload= ( new_payload )
  @payload = new_payload
end

In this use-case with a read-only model this works fine. Just be aware that the model also isn't marked dirty either and other side-effects may occur.

Oliver
  • 89
  • 1
  • 8
  • Do you even need to store the new value in `@new_payload`? Would this work just as well:`def payload=(new_payload); end` ? – Wizard of Ogz Nov 18 '15 at 15:20
  • I like the idea! In this particular case it should work, assuming one would only want to set `@payload` to `nil` to clear it. But the example above provides more flexibility by providing a proper implementation where you can assign it any value. – Oliver Nov 18 '15 at 15:25
0

What do you think about the different approach? I mean you call a Active Job worker which performs some work by invoking Service Object. This last are going to make operation on your data ex. extract some fields and then save it in a database. I think it could save a lot of memory.

MC2DX
  • 562
  • 1
  • 7
  • 19
  • Nice idea and a possible way in a different scenario. Here it's about analysing data from an external database. The extracted values are only used temporarily and therefor don't need to be persisted. Using Active Job to do background-processing doesn't seem to be the the right approach as it's meant to be a direct interaction. Otherwise the user process would have to change to a 2-step operation with 1) analyse and 2) view/process. – Oliver Nov 18 '15 at 14:35