I need to store an encoding-state value on a video while it’s been encoded
I have a video object. While the video is being encoded it needs to lock edits on its comments.
The video therefore needs to store its current encoding state (is it happening yes/no?) and allow child comments to query that property.
Please note
I know that there are better ways to solve this particular problem. I actually need to solve a slightly different problem however I felt the nuances of it would confuse the question so I’ve chosen this one instead. My question is specifically around the nuances of isntance variables and not how to better-solve this encoding problem (which obviously needs a queue).
class Video
has_many :comments
after_initialize do
@encoding_in_process = false
end
def encode
@encoding_in_process = true
...
@encoding_in_process = false
end
def encoding_in_process?
@encoding_in_process
end
end
class Comment
belongs_to :video
before_update
raise "locked" if video.encoding_in_process?
end
...
end
As you can see, each video instance is storing an instance variable @encoding_in_process which is used to determine whether a comment can be updated.
The problem
There is a danger there will be multiple in-memory instances of the same video each with different values for @encoding_in_process.
e.g.
bieber_video = Video.find_all_by_artist('Bieber').last
bieber_video.encode
# assume this takes a while...
bieber_video.encoding_in_process?
# => true
bieber_copy = Video.find_by_id bieber_video.id
bieber_copy.encoding_in_process?
# => false
# Each ActiveRecord objects refer to the same Bieber video
bieber_copy.id == bieber_video.id
# => true
# ...however they refer to different objects in memory:
puts bieber_video
#<Video:0x00000105a9e948>
puts bieber_copy
#<Video:0x00000105a11111>
# and hence each instance has a different version of commenting_locked?
# bieber_video.encoding_in_process? != bieber_copy.encoding_in_process?
The question
Given that the same database row might generate two different in-memory instances, what is a safe way to store transient non-database-backed information about those instances?
EDIT
The actual problem I’m trying to solve is setting a flag on an object when destroy is initiated such that its child objects can determine whether or not they’re eligible to be destroyed themselves.
It’s therefore a very instantaneous problem and not suitable for backing into the database. I used this video example because I thought it was a bit clearer however I may have simply muddied the waters.
THE SOLUTION (courtesy of one of the answers below
@Alex D’s suggestion did solve the problem but to add further clarity to this for anyone wanting to repeat, the actual code was this:
class Video
# set a class variable containing an array of all videos
# which are currently being encoded
@@ids_of_videos_being_encoded = []
...
def encode
store_encoding_state true
begin
encode()
ensure
# make sure we switch this off after
# encoding finishes or fails
store_encoding_state false
end
end
private
def store_encoding_state encoding_in_progress
if encoding_in_progress
@@ids_of_videos_being_encoded.push(id)
else
@@ids_of_videos_being_encoded.delete(id)
end
end
def encoding_initiated?
@@ids_of_videos_being_encoded.include? id
end
end
The answer to your question depends on whether you may use multiple server processes or not. If you may want to run multiple server processes (which is a good assumption), the problem is not just multiple in-memory ActiveRecord objects representing the same DB row, the problem is multiple objects in different memory spaces.
If you have multiple processes which are somehow collaboratively working with the same data, you must keep that data in a shared store (i.e. a database), and you must flush changes to the store, and refresh your in-memory data as needed. In this case, you cannot rely on transient in-memory data being kept in synchronization (because there is no way it possibly could be).
If constantly writing/reading your transient data to the DB sounds expensive, that’s because it is. In general, whenever you have multiple processes (on the same or different servers) working together, you want to design things so each process can grab a chunk of data and work on it for a while without having to communicate with the others. Fine-grained data sharing in a distributed system = bad performance.
If you are sure that you will only ever use a single server process, and you want to simulate the effect of instance variables which are shared between multiple ActiveRecord objects representing the same DB row, keep the data in a hash, keyed by the record ID, and use getters/setters which read/write the hash. If you are doing a lot of this, you can do some metaprogramming “magic” to have the getters/setters automatically generated (a la “attr_accessor”). If you need help writing that metaprogramming code, post a question and I’ll answer it.