The information on this page refers to versions < 5. While the core concepts have not changed, some information may be outdated.
Customizing The Graph #
Ignoring Instance Variables #
It can happen that instance variables should never be serialized. A practical way to do this is overriding the hook method #fuelIgnoredInstanceVariableNames. Let’s say we have the class User and we do not want to serialize the instance variables ‘acumulatedLogins’ and ‘applications’. So we implement:
User class >> fuelIgnoredInstanceVariableNames
^#('acumulatedLogins' 'applications')
When materialized, such instance variables will be nil. If you want to re-initialize and set values to those instance variables, you can use #fuelAfterMaterialization for that.Be aware that in case of renaming those instance variables, you should rename that method as well. Notice also that the method #fuelIgnoredInstanceVariableNames is implemented at class side. This means that all instances of such class will ignore the defined instances variables. We test this feature in FLIgnoredVariablesTest.In StOMP serializer this same hook is called #stompTransientInstVarNames and in SIXX it is #sixxIgnorableInstVarNames.
Post-Materialization Action
The method #fuelAfterMaterialization
lets us execute something once an object has been materialized. For example, let’s say we would like to set back the instance variable ‘acumulatedLogins’ during materialization. Hence, we can implement:
User >> fuelAfterMaterialization
acumulatedLogins := 0.
Substitution on Serialization #
Sometimes you may want to serialize something different than the original object, without altering them.
Dynamic way #
You can establish a pluggable substitution to a particular serialization. Let’s illustrate with an example, where your graph includes a Stream and you want to serialize nil instead.
objectToSerialize := Array with: 'hello' with: '' writeStream.
FileStream forceNewFileNamed: 'demo.fuel' do: [ :aStream |
aSerializer := FLSerializer newDefault.
aSerializer analyzer
when: [ :o | o isStream ]
substituteBy: [ :o | nil ].
aSerializer
serialize: objectToSerialize
on: aStream binary ].
So, when loading you will get #('hello' nil)
, without any instance of a stream.You can find this code in FLUserGuidesTest>>testPluggableSubstitution
.
Static way #
You have to override #fuelAccept:
in the class of the object to be substituted. Fuel visits each object in the graph by sending this message, to determine how to trace and serialize it. Note that this will affect every serialization, in contrast with the ‘dynamic way’ we explained above; but it could be much faster.As an example, imagine we want to replace an object directly with nil. In other words, we want to make a whole object transient, say CachedResult. For that, we should implement:
CachedResult >> fuelAccept: aGeneralMapper
^ aGeneralMapper visitSubstitution: self by: nil
As another example, we have a Proxy class and when serializing we want to serialize its target instead of the proxy. So we implement:
Proxy >> fuelAccept: aGeneralMapper
^ aGeneralMapper visitSubstitution: self by: target
Notice that #fuelAccept:
is the same as the previous example. The last example is when an object needs to change the value of its instance variables. Say we have again the class User and we want to nil the instance variable ‘history’ when its size is greater than 100.
User >> fuelAccept: aGeneralMapper
^self history size > 100
ifTrue: [
aGeneralMapper
visitSubstitution: self
by: (self copy history: Array new) ].
ifFalse: [ super fuelAccept: aGeneralMapper ]
Note we are substituting the original user by another instance of User, which Fuel will visit with the same #fuelAccept: method. We could easily fall in an infinite sequence of substitutions if we don’t take care. To avoid this problem, it is useful #visitSubstitution:by:onRecursionDo:, where you define an alternative mapping for the case of mapping an object which is already a substitute of another one:
User >> fuelAccept: aGeneralMapper
aGeneralMapper
visitSubstitution: self
by: (self copy history: #())
onRecursionDo: [ super fuelAccept: aGeneralMapper ]
In the case, the substitute user (i.e. the one with the empty history) is will be visited via its super implementation.You can see tests for this functionality at FLHookedSubstitutionTest.
Substitution on Materialization #
Global Sends #
Suppose we have a special instance of User that represents the admin user, and it is an unique instance in the image. In case the admin user is referenced in our graph, we want to treat that object as a global. We can do that in this way:
User >> fuelAccept: aGeneralMapper
^self == User admin
ifTrue: [
aGeneralMapper
visitGlobalSend: self
name: #User
selector: #admin ]
ifFalse: [ super fuelAccept: aGeneralMapper ]
So what will happen is that during serialization, the admin user won’t be completly serialized (with all its intance variables) but instead its global name and selector are stored. Then, at materialization time, Fuel will send #admin to the class User, and use what that answers as the admin user of the materialized graph. We test this feature in FLGlobalSendSerializationTest.
Hooking instance creation #
Fuel provides two hook methods to customise how instances are created: #fuelNew
and #fuelNew:
. For (regular) fixed objects, the method #fuelNew
is defined in Behavior as:
fuelNew
^ self basicNew
But we can override it to our needs, for example:
fuelNew
^ self uniqueInstance
This similarly applies to variable objects through the method #fuelNew:
, which by default answers #basicNew:
. We test this feature in FLSingletonTest.
Not Serializable Objects #
You may want to be sure that some objects are not serialized. For this case we provide #visitNotSerializable:
, which in next example forbids serialization of any instance of MyNotSerializableObject.
MyNotSerializableObject >> fuelAccept: aGeneralMapper
aGeneralMapper visitNotSerializable: self
We test this feature in FLBasicSerializationTest>>testNotSerializableObject
.