Hi all... I just got my GS/SS installation up (thanks to Sean!) and running and am starting to poke around and learn this stuff.
My first big question is what the 'correct' persistence pattern is. I've read elsewhere that the GS implementation of Seaside does a commit every request to simplify the transaction handling, and I'm wondering how I should map that over from my existing code.
For example, I'm used to something like this using Glorp: html button callback: [ user bePersistent; commitUnitOfWork. self answer: user ]; with: 'Save'.
html button callback: [ user rollbackUnitOfWork. self answer: nil ]; with: 'Cancel'. Leaving aside whether that's actually correct usage for Glorp, how should I be doing that in GemStone?
--chris
|
I want to make sure what I am seeing...
the scenario is... I make some changes, get to another screen for review and can then either save those changes or roll them back to the previous state? On Tue, Apr 27, 2010 at 8:17 AM, Chris Curtis <[hidden email]> wrote: > Hi all... I just got my GS/SS installation up (thanks to Sean!) and running > and am starting to poke around and learn this stuff. > My first big question is what the 'correct' persistence pattern is. I've > read elsewhere that the GS implementation of Seaside does a commit every > request to simplify the transaction handling, and I'm wondering how I should > map that over from my existing code. > For example, I'm used to something like this using Glorp: > html button callback: [ user bePersistent; commitUnitOfWork. self > answer: user ]; with: 'Save'. > html button callback: [ user rollbackUnitOfWork. self answer: nil > ]; with: 'Cancel'. > Leaving aside whether that's actually correct usage for Glorp, how should I > be doing that in GemStone? > --chris |
Sort of ... it's slightly more interactive than that. Here's a more complete fragment of the code:
renderContentOn: html html div class: 'generic'; with: [
html form: [ html text: 'First Name:'; space. html textInput
text: user firstname; callback: [ :firstname | user firstname: firstname ].
html break. html text: 'Last Name:'; space. html textInput text: user lastname; callback: [ :lastname | user lastname: lastname ].
html break. html text: 'ZIP Code:'; space. html textInput
text: user zipcode; callback: [ :zipcode | cityFound := self validateZipCode: zipcode.
user zipcode: zipcode ]. html break. html button callback: [ user bePersistent; commitUnitOfWork. self answer: user ]; with: 'Save'.
html button callback: [ user rollbackUnitOfWork. self answer: nil ]; with: 'Cancel' ] ]
So the textInput field callbacks are updating the underlying model object on the fly, and then Cancel/Save either throws away the changes or makes them permanent. There's no additional screen, though.
--chris On Tue, Apr 27, 2010 at 8:39 AM, Sean Allen <[hidden email]> wrote: I want to make sure what I am seeing... |
So until save is hit, you dont update anything.
So taking GLORP to Gemstone. Your sql database with glorp is your persistent store ala Gemstone OODB. Save moves the data to the persistent store. With Gemstone, anything that is reachable from a persistent root is persisted so. With sql, you create a smalltak object for a user, this is an intermediate representation of a 'persistent user' in your sql database. Do the same thing with gemstone, create a non persistent user to make modifications to and then on save, update the persistent user, on cancel, return to the original state. So when you hand off a user to your Seaside component, don't give it the persistent user, give it a copy of the persistent user that isnt reachable ( or give it a persistent user and then copy it behind the scenes ). When they save, sync the persistent user with the temporary one and then let the temp instance get garbage collected. On Tue, Apr 27, 2010 at 8:54 AM, Chris Curtis <[hidden email]> wrote: > Sort of ... it's slightly more interactive than that. Here's a more complete > fragment of the code: > renderContentOn: html > html div class: 'generic'; with: [ > html form: [ > html text: 'First Name:'; space. > html textInput > text: user firstname; > callback: [ :firstname | user firstname: firstname ]. > html break. > html text: 'Last Name:'; space. > html textInput text: user lastname; callback: [ :lastname | user lastname: > lastname ]. > html break. > html text: 'ZIP Code:'; space. > html textInput > text: user zipcode; > callback: [ :zipcode | > cityFound := self validateZipCode: zipcode. > user zipcode: zipcode ]. > html break. > html button callback: [ user bePersistent; commitUnitOfWork. self answer: > user ]; with: 'Save'. > html button callback: [ user rollbackUnitOfWork. self answer: nil ]; with: > 'Cancel' ] ] > So the textInput field callbacks are updating the underlying model object on > the fly, and then Cancel/Save either throws away the changes or makes them > permanent. There's no additional screen, though. > --chris > > On Tue, Apr 27, 2010 at 8:39 AM, Sean Allen <[hidden email]> > wrote: >> >> I want to make sure what I am seeing... >> >> the scenario is... >> >> I make some changes, get to another screen for review and can then >> either save those changes >> or roll them back to the previous state? |
One more thought I had after the fact,
a copy might get messy, Creating a new temporary user from an existing one might work better if your object has a deep graph of complicated objects. On Tue, Apr 27, 2010 at 9:02 AM, Sean Allen <[hidden email]> wrote: > So until save is hit, you dont update anything. > > So taking GLORP to Gemstone. > > Your sql database with glorp is your persistent store ala Gemstone OODB. > Save moves the data to the persistent store. With Gemstone, anything that is > reachable from a persistent root is persisted so. > > With sql, you create a smalltak object for a user, this is an > intermediate representation > of a 'persistent user' in your sql database. Do the same thing with gemstone, > create a non persistent user to make modifications to and then on save, > update the persistent user, on cancel, return to the original state. > > So when you hand off a user to your Seaside component, don't give it > the persistent user, > give it a copy of the persistent user that isnt reachable ( or give it > a persistent user > and then copy it behind the scenes ). When they save, sync the persistent user > with the temporary one and then let the temp instance get garbage collected. > > On Tue, Apr 27, 2010 at 8:54 AM, Chris Curtis <[hidden email]> wrote: >> Sort of ... it's slightly more interactive than that. Here's a more complete >> fragment of the code: >> renderContentOn: html >> html div class: 'generic'; with: [ >> html form: [ >> html text: 'First Name:'; space. >> html textInput >> text: user firstname; >> callback: [ :firstname | user firstname: firstname ]. >> html break. >> html text: 'Last Name:'; space. >> html textInput text: user lastname; callback: [ :lastname | user lastname: >> lastname ]. >> html break. >> html text: 'ZIP Code:'; space. >> html textInput >> text: user zipcode; >> callback: [ :zipcode | >> cityFound := self validateZipCode: zipcode. >> user zipcode: zipcode ]. >> html break. >> html button callback: [ user bePersistent; commitUnitOfWork. self answer: >> user ]; with: 'Save'. >> html button callback: [ user rollbackUnitOfWork. self answer: nil ]; with: >> 'Cancel' ] ] >> So the textInput field callbacks are updating the underlying model object on >> the fly, and then Cancel/Save either throws away the changes or makes them >> permanent. There's no additional screen, though. >> --chris >> >> On Tue, Apr 27, 2010 at 8:39 AM, Sean Allen <[hidden email]> >> wrote: >>> >>> I want to make sure what I am seeing... >>> >>> the scenario is... >>> >>> I make some changes, get to another screen for review and can then >>> either save those changes >>> or roll them back to the previous state? > |
In reply to this post by Chris Curtis
Chris,
Welcome to GemStone. Sean has given you some good advice, and I'll throw in my comments. First, I'd suggest you take a look at my Seaside tutorial (http://seaside.gemstone.com/tutorial). Given that you are already familiar with Seaside, the relevant part would be Chapter 11 on forms. Basically, Seaside supports the HTML concept of buttons for submit, cancel, and reset. When a user clicks the submit button, then all the entry fields on the form have their callbacks evaluated before the submit button's callback. When a user clicks the cancel button, none of the entry fields have their callbacks evaluated. Thus, the simplest model is that each entry field updates the model and the submit button ensures that the model is part of a persistent collection: html textInput callback: [:value | model name: value]; with: model value. html submitButton callback: [self persistentIdentitySetOfModels add: model. self answer model]; with: 'Save'. "commit is automatic" html cancelButton callback: [self answer: nil]; with: 'Cancel'. "Input callbacks will not have run, so no need for rollback" This is a simple approach. Things can get more complex from here... James On Apr 27, 2010, at 5:17 AM, Chris Curtis wrote: > Hi all... I just got my GS/SS installation up (thanks to Sean!) and running and am starting to poke around and learn this stuff. > > My first big question is what the 'correct' persistence pattern is. I've read elsewhere that the GS implementation of Seaside does a commit every request to simplify the transaction handling, and I'm wondering how I should map that over from my existing code. > > For example, I'm used to something like this using Glorp: > > html button callback: [ user bePersistent; commitUnitOfWork. self answer: user ]; with: 'Save'. > html button callback: [ user rollbackUnitOfWork. self answer: nil ]; with: 'Cancel'. > > Leaving aside whether that's actually correct usage for Glorp, how should I be doing that in GemStone? > > --chris |
One thing I have found and others will probably disagree( and some
might agree ) but I strongly suggest not storing your data in class variables on objects. Create a graph like this: MySystem <-- global variable, gets persisted UserSystem then store your users in an instance variable of user system. It will allow you to run multiple versions of your application if needed in the same code by just letting it know which system to use. On Tue, Apr 27, 2010 at 11:08 AM, James Foster <[hidden email]> wrote: > Chris, > > Welcome to GemStone. Sean has given you some good advice, and I'll throw in my comments. > > First, I'd suggest you take a look at my Seaside tutorial (http://seaside.gemstone.com/tutorial). Given that you are already familiar with Seaside, the relevant part would be Chapter 11 on forms. > > Basically, Seaside supports the HTML concept of buttons for submit, cancel, and reset. When a user clicks the submit button, then all the entry fields on the form have their callbacks evaluated before the submit button's callback. When a user clicks the cancel button, none of the entry fields have their callbacks evaluated. Thus, the simplest model is that each entry field updates the model and the submit button ensures that the model is part of a persistent collection: > > html textInput callback: [:value | model name: value]; with: model value. > html submitButton callback: [self persistentIdentitySetOfModels add: model. self answer model]; with: 'Save'. "commit is automatic" > html cancelButton callback: [self answer: nil]; with: 'Cancel'. "Input callbacks will not have run, so no need for rollback" > > This is a simple approach. Things can get more complex from here... > > James > > On Apr 27, 2010, at 5:17 AM, Chris Curtis wrote: > >> Hi all... I just got my GS/SS installation up (thanks to Sean!) and running and am starting to poke around and learn this stuff. >> >> My first big question is what the 'correct' persistence pattern is. I've read elsewhere that the GS implementation of Seaside does a commit every request to simplify the transaction handling, and I'm wondering how I should map that over from my existing code. >> >> For example, I'm used to something like this using Glorp: >> >> html button callback: [ user bePersistent; commitUnitOfWork. self answer: user ]; with: 'Save'. >> html button callback: [ user rollbackUnitOfWork. self answer: nil ]; with: 'Cancel'. >> >> Leaving aside whether that's actually correct usage for Glorp, how should I be doing that in GemStone? >> >> --chris > > |
While we've typically suggested class (instance) variables as a first approach for persistence, Sean's suggestion is excellent. And, not just for the reason he mentions, but also because GemStone historically has not migrated class (instance) variables when you create a new version of the class, so the instances can seem to disappear.
James On Apr 27, 2010, at 8:31 AM, Sean Allen wrote: > One thing I have found and others will probably disagree( and some > might agree ) but I strongly suggest not storing your data in class > variables on > objects. Create a graph like this: > > MySystem <-- global variable, gets persisted > UserSystem > > then store your users in an instance variable of user system. > > It will allow you to run multiple versions of your application if > needed in the same code by just letting it know which system to use. > > On Tue, Apr 27, 2010 at 11:08 AM, James Foster > <[hidden email]> wrote: >> Chris, >> >> Welcome to GemStone. Sean has given you some good advice, and I'll throw in my comments. >> >> First, I'd suggest you take a look at my Seaside tutorial (http://seaside.gemstone.com/tutorial). Given that you are already familiar with Seaside, the relevant part would be Chapter 11 on forms. >> >> Basically, Seaside supports the HTML concept of buttons for submit, cancel, and reset. When a user clicks the submit button, then all the entry fields on the form have their callbacks evaluated before the submit button's callback. When a user clicks the cancel button, none of the entry fields have their callbacks evaluated. Thus, the simplest model is that each entry field updates the model and the submit button ensures that the model is part of a persistent collection: >> >> html textInput callback: [:value | model name: value]; with: model value. >> html submitButton callback: [self persistentIdentitySetOfModels add: model. self answer model]; with: 'Save'. "commit is automatic" >> html cancelButton callback: [self answer: nil]; with: 'Cancel'. "Input callbacks will not have run, so no need for rollback" >> >> This is a simple approach. Things can get more complex from here... >> >> James >> >> On Apr 27, 2010, at 5:17 AM, Chris Curtis wrote: >> >>> Hi all... I just got my GS/SS installation up (thanks to Sean!) and running and am starting to poke around and learn this stuff. >>> >>> My first big question is what the 'correct' persistence pattern is. I've read elsewhere that the GS implementation of Seaside does a commit every request to simplify the transaction handling, and I'm wondering how I should map that over from my existing code. >>> >>> For example, I'm used to something like this using Glorp: >>> >>> html button callback: [ user bePersistent; commitUnitOfWork. self answer: user ]; with: 'Save'. >>> html button callback: [ user rollbackUnitOfWork. self answer: nil ]; with: 'Cancel'. >>> >>> Leaving aside whether that's actually correct usage for Glorp, how should I be doing that in GemStone? >>> >>> --chris >> >> |
In reply to this post by James Foster
Thanks for the pointers!
I'm not sure how I missed earlier that the form callbacks didn't run until the form gets submitted... that seems blindingly obvious in retrospect. And it definitely makes the simple case pretty easy to deal with.
If I'm understanding Sean's suggestion correctly, then if I wanted to work with a transient version of the model, I could just do something like: formBackingUser := aUser shallowCopy. "or deepCopy, depending on how the graph is used."
Does that seem about right? The other big question is vis-à-vis the graph root.... merging James and Sean's comments about not storing stuff in the class-side variables, I'm imagining I'd need to do something like this in my RootTask class>>initialize method:
initialize | application myMasterRoot | application := WAAdmin register: self asApplicationAt: 'MyApp'. (System myUserProfile resolveSymbol: #MyMasterRoot) isNil
ifTrue: [ myMasterRoot := (System myUserProfile createDictionary: #MyMasterRoot). ] ifFalse: [ myMasterRoot := (System myUserProfile resolveSymbol: #MyMasterRoot) value ]. self userCollection: (myMasterRoot at: #UserCollection).
(self userCollection) isNil ifTrue: [ self userCollection: IdentityKeyValueDictionary new. myMasterRoot at: #UserCollection put: self userCollection. ] And then similarly for the other entity collections. Then by changing the #MyMasterRoot symbol to #MyOtherMasterRoot, as Sean suggested I could switch between versions of the system easily. (Pardon my ugly code... I've only been Smalltalking for about 6 months!)
Any suggestions are more than welcome, of course. I also started looking at the GemStone tutorial, but obviously that won't run against the GLASS-licensed version of GS I have.
--chris |
Chris,
As you note, creating a transient version of a model by using some variation of #'copy' is highly dependent on "how the graph is used." I find that approach somewhat complex and have managed to avoid it so far. If you can't rely on having the callbacks run only on form submit and find that something more sophisticated is needed, then I'd suggest adding instance variables to your subclass of WAComponent and keeping intermediate data there (based on widget-specific callbacks) and then updating the model in the submit callback. As to where to put the graph root, you have a decent start in your sample RootTask class>>#'initialize' method, but I'd suggest another approach. Create a top-level domain model class with a name ending in 'System' (e.g., VideoStoreSystem), and on its class-side have an accessor, #'default' or #'current', that does lazy initialization: VideoStoreSystem class>>#'current' ^(System myUserProfile objectNamed: #'UserGlobals') at: #'VideoStoreSystem_Current' ifAbsentPut: [self new]. At this point, you send messages to the instance of VideoStoreSystem, like #'users' or #'userWithID:' or #'addUser:'. The general idea is that using a Dictionary to hold objects is not as nice as creating a new class to encapsulate your domain. Now, if you want to run tests, you copy the global to another place, install a test system, run the tests, then revert the main system. As to the tutorial, you should be able to use the GLASS license for GemStone Seaside work. James On Apr 27, 2010, at 6:42 PM, Chris Curtis wrote: > Thanks for the pointers! > > I'm not sure how I missed earlier that the form callbacks didn't run until the form gets submitted... that seems blindingly obvious in retrospect. And it definitely makes the simple case pretty easy to deal with. > > If I'm understanding Sean's suggestion correctly, then if I wanted to work with a transient version of the model, I could just do something like: > > formBackingUser := aUser shallowCopy. "or deepCopy, depending on how the graph is used." > > Does that seem about right? > > The other big question is vis-à-vis the graph root.... merging James and Sean's comments about not storing stuff in the class-side variables, I'm imagining I'd need to do something like this in my RootTask class>>initialize method: > > initialize > | application myMasterRoot | > application := WAAdmin register: self asApplicationAt: 'MyApp'. > (System myUserProfile resolveSymbol: #MyMasterRoot) isNil > ifTrue: [ myMasterRoot := (System myUserProfile createDictionary: #MyMasterRoot). ] > ifFalse: [ myMasterRoot := (System myUserProfile resolveSymbol: #MyMasterRoot) value ]. > self userCollection: (myMasterRoot at: #UserCollection). > (self userCollection) isNil > ifTrue: [ self userCollection: IdentityKeyValueDictionary new. > myMasterRoot at: #UserCollection put: self userCollection. ] > > And then similarly for the other entity collections. Then by changing the #MyMasterRoot symbol to #MyOtherMasterRoot, as Sean suggested I could switch between versions of the system easily. (Pardon my ugly code... I've only been Smalltalking for about 6 months!) > > Any suggestions are more than welcome, of course. I also started looking at the GemStone tutorial, but obviously that won't run against the GLASS-licensed version of GS I have. > > --chris |
On Tue, Apr 27, 2010 at 10:22 PM, James Foster <[hidden email]> wrote:
Chris, Right... I'm definitely in favor of that. (I also maintain a very complex system in C# that has never been very careful about what's a copy and what isn't, so I know the pain and agree it is worth avoiding.)
As to where to put the graph root, you have a decent start in your sample RootTask class>>#'initialize' method, but I'd suggest another approach. Create a top-level domain model class with a name ending in 'System' (e.g., VideoStoreSystem), and on its class-side have an accessor, #'default' or #'current', that does lazy initialization: Ah, excellent. That's a much cleaner way to handle things. My existing codebase was built where the domain model classes themselves managed the accesses via class-side methods, which makes it much harder to do any swapping of the root.
So if I'm understanding this right, then VideoStoreSystem would have instance variables for, say, the users collection? If that's correct, is the recommended pattern exposing the collections via an accessor and having the caller do the selection, or do you create the domain-specific methods (like findUserByEmail) that in turn encapsulate the selection? While I'm learning, I figure I might as well learn the best practices.
As to the tutorial, you should be able to use the GLASS license for GemStone Seaside work. Oh, sorry... that was unclear because you'd mentioned your GLASS tutorial previously. I was actually referring to the GBS-based tutorial, as I was looking at it to get some bearings in GemStone independent of seaside.
Thanks again for the advice.... it's really helping me get up to speed quite quickly. --chris |
On Apr 27, 2010, at 7:52 PM, Chris Curtis wrote:
Yes, exactly.
In a sense, you are asking how strictly to enforce the 'Law of Demeter' (http://en.wikipedia.org/wiki/Law_of_Demeter). I don't think that Smalltalkers have a particularly strong consensus on this matter. I recently had a situation where I used to return the internal collection of users in response to the #'users' method, but then changed it to only return active users and added another method for #'allUsers'. For the most part this simple change gave the the desired result of ignoring inactive users in most situations (without going back and changing all the senders of #'users' to #'activeUsers'). The disadvantage is that the process of adding a users was MySystem current users add: myNewUser. Since the implementation of #'users' changed to return a new collection of active users, the new user was added to the temporary collection rather than the persistent collection. In this case it would have been better to have an #'addUser:' method in MySystem. Another example of information hiding is if you have a large collection and you choose to create an index on it (using the GemStone-specific capabilities). If you both create the index and do the lookup inside the *System instance, then the callers don't have to know about the internal implementation (which collections have which indexes). On the other hand, adding #'selectUsersWithNameMatching:', #'findUserByEmail:' , and every other possible lookup becomes rather unmanageable. Reimplementing variations on the entire collection protocol for every instance variable seems like a lot of extra code and (notwithstanding the problem I described above) I'm inclined to start by implementing the *System class as mostly a data structure with minimal behavior. Refactor to remove duplicate code as it occurs. James
|
In reply to this post by James Foster
On Apr 27, 2010, at 9:38 AM, James Foster wrote:
> While we've typically suggested class (instance) variables as a first approach for persistence, Sean's suggestion is excellent. True. > And, not just for the reason he mentions, but also because GemStone historically has not migrated class (instance) variables when you create a new version of the class, so the instances can seem to disappear. But since GLASS has migrated class (instance) variables this historic problem is fixed so the reason is moot and probably should not have been mentioned since it adds more confusion than clarification... James |
In reply to this post by Chris Curtis
Today I would say that James and I are leaning toward storing globals separately from the class for a couple of different reasons:
- using class (instance) vars makes it difficult to share code amongst multiple GemStone users (ala DataCurator and SystemUser) - upgrade/migration customization easier when avoiding class (instace vars) In GemStone it is possible to create multiple users at the data base level which allows one to have multiple independent instances of Seaside or Pier running in the same stone. In fact, with appropriate segments and permissions it is possible to completely isolate the independent instances from each other....Furthermore with the GemStone namespace model it is possible to share the common code as well, except if you use class (instance) vars. As far as upgrade/migration goes, suffice to say that being able to remove a class completely from the system without losing data (i.e., not relying upon class (instance) vars for persistence) is useful in certain circumstances. In the end, we are starting the think that Sean's suggestion is a good one... Dale ----- "James Foster" <[hidden email]> wrote: | While we've typically suggested class (instance) variables as a first | approach for persistence, Sean's suggestion is excellent. And, not | just for the reason he mentions, but also because GemStone | historically has not migrated class (instance) variables when you | create a new version of the class, so the instances can seem to | disappear. | | James | | On Apr 27, 2010, at 8:31 AM, Sean Allen wrote: | | > One thing I have found and others will probably disagree( and some | > might agree ) but I strongly suggest not storing your data in class | > variables on | > objects. Create a graph like this: | > | > MySystem <-- global variable, gets persisted | > UserSystem | > | > then store your users in an instance variable of user system. | > | > It will allow you to run multiple versions of your application if | > needed in the same code by just letting it know which system to | use. | > | > On Tue, Apr 27, 2010 at 11:08 AM, James Foster | > <[hidden email]> wrote: | >> Chris, | >> | >> Welcome to GemStone. Sean has given you some good advice, and I'll | throw in my comments. | >> | >> First, I'd suggest you take a look at my Seaside tutorial | (http://seaside.gemstone.com/tutorial). Given that you are already | familiar with Seaside, the relevant part would be Chapter 11 on | forms. | >> | >> Basically, Seaside supports the HTML concept of buttons for submit, | cancel, and reset. When a user clicks the submit button, then all the | entry fields on the form have their callbacks evaluated before the | submit button's callback. When a user clicks the cancel button, none | of the entry fields have their callbacks evaluated. Thus, the simplest | model is that each entry field updates the model and the submit button | ensures that the model is part of a persistent collection: | >> | >> html textInput callback: [:value | model name: value]; with: | model value. | >> html submitButton callback: [self | persistentIdentitySetOfModels add: model. self answer model]; with: | 'Save'. "commit is automatic" | >> html cancelButton callback: [self answer: nil]; with: | 'Cancel'. "Input callbacks will not have run, so no need for | rollback" | >> | >> This is a simple approach. Things can get more complex from | here... | >> | >> James | >> | >> On Apr 27, 2010, at 5:17 AM, Chris Curtis wrote: | >> | >>> Hi all... I just got my GS/SS installation up (thanks to Sean!) | and running and am starting to poke around and learn this stuff. | >>> | >>> My first big question is what the 'correct' persistence pattern | is. I've read elsewhere that the GS implementation of Seaside does a | commit every request to simplify the transaction handling, and I'm | wondering how I should map that over from my existing code. | >>> | >>> For example, I'm used to something like this using Glorp: | >>> | >>> html button callback: [ user bePersistent; commitUnitOfWork. | self answer: user ]; with: 'Save'. | >>> html button callback: [ user rollbackUnitOfWork. self answer: | nil ]; with: 'Cancel'. | >>> | >>> Leaving aside whether that's actually correct usage for Glorp, how | should I be doing that in GemStone? | >>> | >>> --chris | >> | >> |
On Thu, Apr 29, 2010 at 3:18 PM, Dale Henrichs <[hidden email]> wrote: Today I would say that James and I are leaning toward storing globals separately from the class for a couple of different reasons: Here's what I ended up implementing: MySystem class>>#current ^(System myUserProfile objectNamed: #'UserGlobals') at: #MySystem_Current
ifAbsentPut: [ self new ]. along with, for example, an instvar 'users' that gets set to 'IdentitySet new' in MySystem>>#initialize. So my usage pattern ends up being, say:
MySystem current users detect: { :e | e.name = 'my_name' }. Hopefully that's something along the lines of what y'all have been suggesting. :)
--chris
|
Chris,
That looks good. I assume that your initializer is setting up an index. James On Apr 29, 2010, at 5:18 PM, Chris Curtis wrote:
|
In reply to this post by Chris Curtis
Hi,
On 27.04.2010, at 14:17, Chris Curtis wrote: > Hi all... I just got my GS/SS installation up (thanks to Sean!) and running and am starting to poke around and learn this stuff. > > My first big question is what the 'correct' persistence pattern is. I've read elsewhere that the GS implementation of Seaside does a commit every request to simplify the transaction handling, and I'm wondering how I should map that over from my existing code. > > For example, I'm used to something like this using Glorp: > > html button callback: [ user bePersistent; commitUnitOfWork. self answer: user ]; with: 'Save'. > html button callback: [ user rollbackUnitOfWork. self answer: nil ]; with: 'Cancel'. > > Leaving aside whether that's actually correct usage for Glorp, how should I be doing that in GemStone? > I started to use class vars for holding instances a while ago. It bit me every single time I upgraded something. Knowing that you just need to copy your instances from an old class version makes it easier but the overall setup becomes error prone. The same goes for using a class as key somewhere. Back then I also tried something like UserGlobals at: MyClass This does not work either. After an upgrade the class MyClass is just different from the class when you used it as a key. Sorry but true these are two rules for me to make my life easier: avoid using class vars for persistent data and don't use classes directly as key anywhere. As I am a lazy person I changed the setup to work more generic than to care for every single domain class in *System approach. Now I use this approach (which is nearly the same as you advized). HRModel class>>default ^ default ifNil: [ default := HRModel new ] HRModel class>>instancesOf: aClass ^ self default instancesOf: aClass HRModel>>root ^ root ifNil: [ root := IdentityDictionary new ] HRModel>>at: aString ^ self root at: aString ifAbsentPut: [ OrderedCollection new ] HRModel>>instancesOf: aClass ^ self at: aClass name Then in the base class or any class of my model I add HRSomeThing class>>instances ^ HRModel instancesOf: self This way I avoid using class vars and the classes are resolved by name. Works out pretty well so far. For the other topic about providing safe cancel operations I like to put Magritte in the mix. It does exactly this. It handles mementos of your object model as long as you are working on it. The memento itself has a save and cancel operation to write the content back to the object or throw it away. From a gemstone point of view where everything is written as soon as it has been changed it is the perfect match. Well, it is another piece of not-so-lightweight software but I think it is worth it. Norbert |
> I'm late in the thread. There were quite a few very good response. As I don't know where to reply I just do it to the first message. I don't know if I can add some valuable but I want to share my experience on this.
> > I started to use class vars for holding instances a while ago. It bit me every single time I upgraded something. Knowing that you just need to copy your instances from an old class version makes it easier but the overall setup becomes error prone. The same goes for using a class as key somewhere. Back then I also tried something like > > UserGlobals at: MyClass > > This does not work either. After an upgrade the class MyClass is just different from the class when you used it as a key. Sorry but true these are two rules for me to make my life easier: avoid using class vars for persistent data and don't use classes directly as key anywhere. I second this. Use a symbol and lookup the class from the symbol. We have to go back through our code and fix this. We have several places where if we change the shape of a class and remove the history of said class, it breaks unless we reregister with the new version of the class. |
Free forum by Nabble | Edit this page |