Pharo-users
By thread
pharo-users@lists.pharo.org
By month
Messages by month
- ----- 2026 -----
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- 7 participants
- 50354 messages
GTInspect auto-update in raw view?
by Esteban A. Maringolo
Hi all,
Is there a way to enable the auto-update of instance variables display in
the GTInspector?
There are cases where the instance is nil, I then initialize it somehow,
but the variable is still displayed as nil.
I know there is an "Update" button, which BTW should be labeled "refresh"
instead, to do it manually, but hey, this is Smalltalk, we invented MVC :D
We can consider the previous as a feature, but what I think it is a bug is
that if the variable is nil, and later it is initialized, nil is still
displayed, but when clicked the nodes below it shows the variables of the
new object in the instance variable.
E.g.
[image: Imágenes integradas 1]
Is this a bug or a feature?
Regards!
Esteban A. Maringolo
Aug. 16, 2017
Parser failure on FFI pragmas declaration in Pharo 5
by bdurin
Hi,
I stumbled upon what seems to me a strange issue in Pharo 5. The RBParser
fails to correctly parse the legacy FFI pragmas. This completely breaks down
the browser, the inspector and debugger (because as far as I understand all
use RBParser to correctly highlight syntax). I had the image crashed and
some red boxes at some point while insisting to inspect and debug. Overall
this is not a big issue but it raises some more general bells to me.
In order to reproduce this:
- load the official Pharo 5 (curl get.pharo.org/50+vm | bash)
- launch the image (./pharo-ui Pharo.image &)
- add the following repository
MCSmalltalkhubRepository
owner: 'panuw'
project: 'zeromq'
user: ''
password: ''
- load the last versions of ZeroMQ and ConfigurationOfZeroMQ (not sure if
the latter is needed)
- open a Nautilus Browser and look at the class method apiZmqBind:to: of the
ZmqApi class in the ZeroMQ package: you get a MessageNotUnderstood error
(receiver of keywords is nil). You can get past this by clicking on
"Abandon" but the source code is displayed in a corrupted way:
apiZmqBind: s
ocket to: endpoint <cdecl
- repeat a few times by looking at other methods until you get a red box:
then you cannot look at source code any more with this browser. If you are
obstinate and "lucky" you will succeed in crashing the image.
- you can pin the problem by running in a Playground
RBParser parseFaultyMethod: 'apiZmqBind: socket to: endpoint
<cdecl: long ''zmq_bind'' (ZmqApiSocket* char*) module:''zmq''>
^self externalCallFailed'.
and you'll see that the pragmas is not correctly parsed. (The root cause is
that the legacy adapter RBFFICallPragma does not follow the API defined by
its super class RBPragmaNode (selector, arguments, positions) and so is not
a properly defined node. I corrected the problem by computing and setting
the corresponding instance variables.)
1) As a beginner at Pharo, I find it difficult to deal with the various
versions of Pharo. ZeroMQ is the (only) Smalltalk-Pharo binding for zmq. It
dates back to Feb 2014 so I expected it to work in Pharo as of 3 years and a
half later (Pharo 6 dates back to June 2017).
I naively tried to load the package in a Pharo 6 image and it failed because
of a syntax error. As I had read a lot about the various FFI mechanisms, I
quickly understood that it must be because the FFI declarations in pragma
are not supported anymore.
I then loaded the package in a Pharo 5 image and I got the error that I
described. After finding the error and solving it, I guess that the FFI
declaration in pragma was barely supported in Pharo 5, which has already
switched to UFFI and that it is something dating back to Pharo 4. (I did not
try with Pharo 4 as I do not want to work with versions before 5).
Is there a way to know for a package what the compatible Pharo version is?
It seems that currently I have to look at dates, look at the features used
by the package and look for the history of development (fortunately the
mailing lists are easy to search) to understand which version is likely to
work.
Are not deprecations a bit too fast if a package written 3 years ago cannot
work in the latest Pharo version and trigger bugs in Pharo 5, which dates
back to May 2016 (so only a bit more than 2 years after)?
I find it a bit too fast as compared to mainstream languages. To my mind,
either deprecations should be slower or a version/dependencies system should
be there to help users.
2) Another question about versions: Pharo 6 is out since June, Pharo 7 is
under development. What is the status of Pharo 5? Already history or still
relevant?
I am asking because I corrected the problem of FFI declaration in pragma,
but it seems to me that it is not useful to publish this change as starting
from Pharo 6 this way to do FFI is not supported. So should I contribute? If
yes, how to "attach" the patch to Pharo 5?
3) As explained above, in Pharo 5, looking at the source trigger an error.
Even if this looks like a rare corner case, I think that the developer tools
should not trigger bugs when looking at source code, even less trigger a red
box in the source code viewer (in the browser, but the problem also occurs
--less strongly-- when looking at the object in an inspector: there should
not be "error printing" when it is only a syntax highlight problem). If the
code is malformed and the parser used to highlight syntax fails, there
should be a fallback such as the source code being displayed without any
highlight. It sends a very bad impression to have this kind of bugs when one
simply wants to look at code, not even running it.
I have not dug enough in this area of Pharo, but it seems to me that the
parser that is used to build the AST for code execution / method compilation
should not be the same as the parser used to highligh syntax. (Of course I
am not saying that there should be 2 distincts code base for the 2 parsers,
but they should at least run differently.) The first one must be strict with
errors as a malformed AST cannot be executed. The second one must be
lenient, as a malformed AST does not prevent to print the string of the
source code. Of course, at the end if the code is malformed there will be an
error at execution, but if the source code can be displayed even when it is
malformed, at least I have the opportunity to correct it so that it runs
correctly. (In this case, convert the old FFI pragma declaration into a
fficall:)
I may be missing something here but if this works the same in the most
up-to-date version of Pharo, the same kind of error might appear again.
What do you think?
4) A final remark: let us classify people as Beginner/Confirmed in
programming and B/C in Pharo (A BB is a beginner in programming and in
Pharo, a CC confirmed in both, a BC cannot exists and CB are those who
discover Pharo while knowing well other languages). Pharo seems to be great
for BB and CC. I went through the MOOC and the various books which are
great. My first steps in Pharo environment were great.
As a CC it seems to be great also as in the very small area of the system
where I took the time to drill into all the details, I could very easily
change things (and correct a bug), that would have been very difficult to
understand and change in a lot of other languages. Even hacking the VM seems
to be possible for a non-VM expert.
But I consider myself rather as a CB. As such I tend to try and do complex
things that I usually do in other languages and run into tricky problems.
These problems are rather dealt with and corrected by Pharo developers but
that as a user I would expect them to remain hidden to me or to be clearly
advertised in the docs. As compared to a BB, a CB is not going to stay in a
well delimited area where everything is smooth.
True, in a way it is a very strong incentive to become a Pharo expert! But I
am wondering if this aspect could be improved.
Thanks,
Bruno
--
View this message in context: http://forum.world.st/Parser-failure-on-FFI-pragmas-declaration-in-Pharo-5-…
Sent from the Pharo Smalltalk Users mailing list archive at Nabble.com.
Aug. 16, 2017
Re: [Pharo-users] How to find where pharo is killing processor power?
by Sven Van Caekenberghe
Are you using VNC ?
> On 16 Aug 2017, at 21:03, sergio ruiz <sergio.rrd(a)gmail.com> wrote:
>
>
> hey all.. my current pharo implementation is running super serious processor power, but it shouldnât be doing much.
>
> <Activity_Monitor__All_Processes__and_Pharo_image.jpg>
>
> how would I go about tracking this inside the pharo image?
>
> Thanks!
>
>
> ----
> peace,
> sergio
> photographer, journalist, visionary
>
> Public Key: http://bit.ly/29z9fG0
> #BitMessage BM-NBaswViL21xqgg9STRJjaJaUoyiNe2dV
> http://www.Village-Buzz.com
> http://www.ThoseOptimizeGuys.com
> http://www.coffee-black.com
> http://www.painlessfrugality.com
> http://www.twitter.com/sergio_101
> http://www.facebook.com/sergio101
Aug. 16, 2017
Re: [Pharo-users] Encoding Login information in your image (safely)
by Tim Mackinnon
Todd, i think you are right, I can't get around it - I need to pass something from my live environment (where it's kept encrypted) into my image.
I can however potentially simplify the setup by passing a single key that I can use to unlock a PasswordVault in my image and it can have multiple values which I can then use for my different api keys.
When creating my image, I can encode those values in a less hostile environment and pass them through like I do now, where they get stored in the PasswordVault.
This seems pretty simple now I talk it through.
Thanks to everyone for additional ideas - it's useful to know it's not a simple X thing that I've missed.
Tim
Sent from my iPhone
> On 16 Aug 2017, at 19:41, Todd Blanchard <tblanchard(a)mac.com> wrote:
>
> I do a lot of deployments on AWS elastic beanstalks.
>
> I put the credentials into environment variables on the beanstalk.
>
> When running locally, the credentials are in the environment on my machine.
>
>> On Aug 16, 2017, at 9:55 AM, Tim Mackinnon <tim(a)testit.works> wrote:
>>
>> Hi - Iâm struggling to find something that I saw that discussed this issue kind of.
>>
>> In my image (its actually a headless one - but this could apply to a fat image too) - I build an application that needs access to a service (in this case an S3 bucket).
>>
>> The AWS library Iâm using (but others are similar) has an AWSLogin class singleton where I can specify a username and password. So in a playground I can do that and test it all works etc.
>>
>> However, for deployment its never a good idea to encode this info into your code (particularly if you use Iceberg and GitHub) - SO, I am using secret variable support in GitLab - which Iâve seen many projects do in other languages. This way, I type in those details into an encrypted place in the CI and it then exposes them as temporary variables when I build my system (so far so good).
>>
>> Now in my build - I run a little script like this and pass on those variables (neatly, Gitlab doesnât show their values in its logs):
>>
>> ./pharo Pharo.image --no-default-preferences --save --quit st config.st \
>> "{â$USER'. â$PWD'}"
>>
>> In config.st I then extract these command line parameters (the ST handler nicely exposes the extra parameter array so I didnât have to do anything custom)
>>
>> "Expect image to be called with params as a last arg array"
>> config := Array readFrom: Smalltalk arguments last.
>> user := config at: 1.
>> pwd := config at: 2.
>>
>> DBConfig default
>> accessKey: user;
>> pKey: pwd;
>> yourself.
>> So it all looks pretty good so far - however it occurs to me that if you get hold of a .image and were to browse all of the Strings - e.g.
>> ./pharo Pharo.image eval "(ByteString allInstances)â
>> I think you would ulimtately find those strings unless the Class encrypts them in some way right?
>> So Iâm wondering why we donât have an EncryptedString object for just this (Iâve seen lots of cryptography libraries etc), but isnât this quite a common thing to deal with? And should Pharo provide something that library writers adopt to encourage better image safety? Or am I wrong in my analysis?
>>
>> Tim
>>
>
Aug. 16, 2017
How to find where pharo is killing processor power?
by sergio ruiz
hey all.. my current pharo implementation is running super serious processor power, but it shouldnât be doing much.
how would I go about tracking this inside the pharo image?
Thanks!
----
peace,
sergio
photographer, journalist, visionary
Public Key:Â http://bit.ly/29z9fG0
#BitMessage BM-NBaswViL21xqgg9STRJjaJaUoyiNe2dV
http://www.Village-Buzz.com
http://www.ThoseOptimizeGuys.com
http://www.coffee-black.com
http://www.painlessfrugality.com
http://www.twitter.com/sergio_101
http://www.facebook.com/sergio101
Aug. 16, 2017
Re: [Pharo-users] Encoding Login information in your image (safely)
by Todd Blanchard
I do a lot of deployments on AWS elastic beanstalks.
I put the credentials into environment variables on the beanstalk.
When running locally, the credentials are in the environment on my machine.
> On Aug 16, 2017, at 9:55 AM, Tim Mackinnon <tim(a)testit.works> wrote:
>
> Hi - Iâm struggling to find something that I saw that discussed this issue kind of.
>
> In my image (its actually a headless one - but this could apply to a fat image too) - I build an application that needs access to a service (in this case an S3 bucket).
>
> The AWS library Iâm using (but others are similar) has an AWSLogin class singleton where I can specify a username and password. So in a playground I can do that and test it all works etc.
>
> However, for deployment its never a good idea to encode this info into your code (particularly if you use Iceberg and GitHub) - SO, I am using secret variable support in GitLab - which Iâve seen many projects do in other languages. This way, I type in those details into an encrypted place in the CI and it then exposes them as temporary variables when I build my system (so far so good).
>
> Now in my build - I run a little script like this and pass on those variables (neatly, Gitlab doesnât show their values in its logs):
>
> ./pharo Pharo.image --no-default-preferences --save --quit st config.st <http://config.st/> \
> "{â$USER'. â$PWD'}"
>
> In config.st <http://config.st/> I then extract these command line parameters (the ST handler nicely exposes the extra parameter array so I didnât have to do anything custom)
>
> "Expect image to be called with params as a last arg array"
> config := Array readFrom: Smalltalk arguments last.
> user := config at: 1.
> pwd := config at: 2.
>
> DBConfig default
> accessKey: user;
> pKey: pwd;
> yourself.
> So it all looks pretty good so far - however it occurs to me that if you get hold of a .image and were to browse all of the Strings - e.g.
> ./pharo Pharo.image eval "(ByteString allInstances)â
> I think you would ulimtately find those strings unless the Class encrypts them in some way right?
> So Iâm wondering why we donât have an EncryptedString object for just this (Iâve seen lots of cryptography libraries etc), but isnât this quite a common thing to deal with? And should Pharo provide something that library writers adopt to encourage better image safety? Or am I wrong in my analysis?
>
> Tim
>
Aug. 16, 2017
Re: [Pharo-users] What is proper fix for this? (was: Re: Big Glorp problem w/ type coercion, pls help)
by Herby VojÄÃk
Esteban A. Maringolo wrote:
> Herby,
>
> Please apologize I can't take the time to look into your code.
Np, if someone eventually will.
> I granted you write access to
> <http://smalltalkhub.com/#!/~DBXTalk/Glorp/>, could you upload your
> latter version to it?
Uploaded.
> Regards!
> Esteban A. Maringolo
P.S.: I did not gain understanding of Glorp innards in that small amount
of time, of course, but I went by the intuition that at the place I made
the fix, the normal (base class, Mapping >>) behaviour is to convert the
right sides of the relation, and the only subclass which has it
overridden, namely DirectMapping, seems to do the same thing (when
looking at the code), just in an optimized way. Except, it does not
convert, which I believe, strongly, to be a bug left out while doing
that optimization.
Herby
Aug. 16, 2017
Re: [Pharo-users] Encoding Login information in your image (safely)
by Richard Sargent
Apologies for top quoting; there isn't any single place which seemed
appropriate.
A colleague of mine who worked for a Swiss bank (they take security
seriously) addressed this by NOT keeping the credentials in the image.
1. Set up a distinct user intended solely for running the application.
2. Set up a credentials file which can only be read by that user.
3. When the application runs, it can read the credentials from the file,
open whatever connection it needs and then release the credentials from
image memory.
I think this concept may be similar to how SSH keys (private keys) are
managed.
Tim Mackinnon wrote
> Hi - Iâm struggling to find something that I saw that discussed this issue
> kind of.
>
> In my image (its actually a headless one - but this could apply to a fat
> image too) - I build an application that needs access to a service (in
> this case an S3 bucket).
>
> The AWS library Iâm using (but others are similar) has an AWSLogin class
> singleton where I can specify a username and password. So in a playground
> I can do that and test it all works etc.
>
> However, for deployment its never a good idea to encode this info into
> your code (particularly if you use Iceberg and GitHub) - SO, I am using
> secret variable support in GitLab - which Iâve seen many projects do in
> other languages. This way, I type in those details into an encrypted place
> in the CI and it then exposes them as temporary variables when I build my
> system (so far so good).
>
> Now in my build - I run a little script like this and pass on those
> variables (neatly, Gitlab doesnât show their values in its logs):
>
> ./pharo Pharo.image --no-default-preferences --save --quit st config.st \
> "{â$USER'. â$PWD'}"
>
> In config.st I then extract these command line parameters (the ST handler
> nicely exposes the extra parameter array so I didnât have to do anything
> custom)
>
> "Expect image to be called with params as a last arg array"
> config := Array readFrom: Smalltalk arguments last.
> user := config at: 1.
> pwd := config at: 2.
>
> DBConfig default
> accessKey: user;
> pKey: pwd;
> yourself.
> So it all looks pretty good so far - however it occurs to me that if you
> get hold of a .image and were to browse all of the Strings - e.g.
> ./pharo Pharo.image eval "(ByteString allInstances)â
> I think you would ulimtately find those strings unless the Class encrypts
> them in some way right?
> So Iâm wondering why we donât have an EncryptedString object for just this
> (Iâve seen lots of cryptography libraries etc), but isnât this quite a
> common thing to deal with? And should Pharo provide something that library
> writers adopt to encourage better image safety? Or am I wrong in my
> analysis?
>
> Tim
--
View this message in context: http://forum.world.st/Encoding-Login-information-in-your-image-safely-tp496…
Sent from the Pharo Smalltalk Users mailing list archive at Nabble.com.
Aug. 16, 2017
Re: [Pharo-users] What is proper fix for this?
by jtuchel@objektfabrik.de
Am 16.08.17 um 19:33 schrieb Esteban Lorenzano:
>
>>> is asking for, I encourage to keep discussion also here, then solution can hit both platforms.
>> No complains about having the discussion on both groups in parallel ;-) I just don't like the idea of private fixes to Glorp on any of the non-Cincom-platforms, be it Pharo or VA Smalltalk or any other (if there are any).
> I never asked for that :)
So you volunteered to discuss problem and fix with Cincom? ;-)
Joachim
Aug. 16, 2017
Re: [Pharo-users] What is proper fix for this?
by Esteban Lorenzano
> On 16 Aug 2017, at 19:29, "jtuchel(a)objektfabrik.de" <jtuchel(a)objektfabrik.de> wrote:
>
>> Am 16.08.17 um 18:08 schrieb Esteban Lorenzano:
>> but if he is using Glorp for Pharo and cincom takes the bug and fixes it, it still will not hit Pharo until someone ports it.
>
> OTOH: if nobody at Cincom reads about it, chances are you do a complete fork of Glorp for Pharo. Not sure I like that idea... And I am not sure anybody here has enough insight into Glorp to tell whether the fix is good or not.
>
>
>> So, while I have literally no idea of what Herby
> You see: I am in the same boat and would have a hard time deciding whether the fix is good or not ;-)
>
>> is asking for, I encourage to keep discussion also here, then solution can hit both platforms.
>
> No complains about having the discussion on both groups in parallel ;-) I just don't like the idea of private fixes to Glorp on any of the non-Cincom-platforms, be it Pharo or VA Smalltalk or any other (if there are any).
I never asked for that :)
Esteban
>
> just my 2c
>
> Joachim
>
>
>>
>> Esteban
>>
>>> On 16 Aug 2017, at 00:07, herby(a)mailbox.sk wrote:
>>>
>>> BTW I took the latter way (as method tries to be as optimized as
>>> possible), it is in
>>> http://smalltalkhub.com/#!/~herby/Glorp/versions/Glorp-HerbyVojcik.127,
>>> consider merging in. Thanks.
>>>
>>> Herby VojÄÃk wrote:
>>>> Hello!
>>>>
>>>> I think I found the culprit. Few methods posted here:
>>>>
>>>>
>>>> Mapping >> expressionFor: anObject basedOn: anExpression relation:
>>>> aSymbol
>>>> "Return our expression using the object's values. e.g. if this was a
>>>> direct mapping from id->ID and the object had id: 3, then return
>>>> TABLE.ID=3. Used when rewriting object=object into field=field"
>>>>
>>>> | myValue result |
>>>> myValue := self expressionFor: anObject.
>>>> result := nil.
>>>> myValue with: self join allTargetFields do: [:eachValue :eachField |
>>>> | source |
>>>> source := anExpression get: self attribute name.
>>>> source hasDescriptor ifTrue: [source := source getField: eachField].
>>>> result := (source get: aSymbol withArguments: (Array with: eachValue))
>>>> AND: result].
>>>> ^result
>>>>
>>>>
>>>>
>>>> DirectMapping >> expressionFor: anObject basedOn: anExpression relation:
>>>> aSymbol
>>>> "Return our expression using the object's values. e.g. if this was a
>>>> direct mapping from id->ID and the object had id: 3, then return
>>>> TABLE.ID=3"
>>>>
>>>> | value |
>>>> value := anObject isNil
>>>> ifTrue: [nil]
>>>> ifFalse:
>>>> [anObject isGlorpExpression
>>>> ifTrue: [anObject getMapping: self named: self attributeName]
>>>> ifFalse: [anObject glorpIsCollection
>>>> ifTrue: [anObject collect: [:each | attribute getValueFrom: each]]
>>>> ifFalse: [attribute getValueFrom: anObject]]].
>>>> ^(anExpression get: self attribute name) get: aSymbol withArguments:
>>>> (Array with: value)
>>>>
>>>>
>>>>
>>>> Mapping >> expressionFor: anObject
>>>> "Return an expression representing the value of the object. This can be
>>>> nil, an object value or values, an expression, or a collection of
>>>> expressions (for a composite key, if we're passed an expression)"
>>>>
>>>> anObject isNil ifTrue: [^#(nil)].
>>>> anObject isGlorpExpression ifFalse: [
>>>> ^self mappedFields collect: [:each |
>>>> self valueOfField: each fromObject: anObject]].
>>>> ^self mappedFields
>>>> collect: [:each | (anObject getField: each)]
>>>>
>>>>
>>>>
>>>> Mapping >> getValueFrom: anObject
>>>>
>>>> ^self attribute getValueFrom: anObject
>>>>
>>>>
>>>>
>>>> DirectMapping >> valueOfField: aField fromObject: anObject
>>>> field = aField ifFalse: [self error: 'Mapping doesn''t describe field'].
>>>> ^self convertedDbValueOf: (self getValueFrom: anObject)
>>>>
>>>>
>>>>
>>>> DirectMapping >> mappedFields
>>>> "Return a collection of fields that this mapping will write into any of
>>>> the containing object's rows"
>>>>
>>>> ^Array with: self field
>>>>
>>>>
>>>> The thing is, both Mapping >> expressionFor:basedOn:relation: and the
>>>> overridden DirectMapping's version eventually send
>>>>
>>>> someSource get: aSymbol withArguments: (Array with: eachValue)
>>>>
>>>> but in Mapping's code, the value is taken from `myValue := self
>>>> expressionFor: anObject`. which, as seen in #expressionFor: code, gets
>>>> the value via
>>>>
>>>> self valueOfField: aMappedField fromObject: anObject
>>>>
>>>> and indeed, if tried aDirectMapping expressionFor: anObject in debugger,
>>>> it gets the value of the primary key converted in the below case (that
>>>> is, as a ByteArray). This is clear from the DirectMapping >>
>>>> valueOfField:fromObject: code above, which does `self getValueFrom:
>>>> anObject` (which passes it to `attribute getValueFrom: anObject`)
>>>> _and_converts_it_.
>>>>
>>>> But in the overridden DirectMapping >> expressionFor:basedOn:relation:,
>>>> the value to be passed in the
>>>>
>>>> someSource get: aSymbol withArguments: (Array with: value)
>>>>
>>>> is obtained by direct
>>>>
>>>> attribute getValueFrom: anObject
>>>>
>>>> but _is_not_converted_. IOW, it seems this method was heavily optimized
>>>> (`attribute getValueFrom:` instead of `self getValueFrom:`, for
>>>> example), but the conversion, normally present via expressionFor: and
>>>> ultimately valueOfField:fromObject: was optimized away as well.
>>>>
>>>>
>>>>
>>>>
>>>> Now, what is the correct way to fix the method (I hope you agree it is a
>>>> bug)?
>>>>
>>>>
>>>> This?
>>>>
>>>> DirectMapping >> expressionFor: anObject basedOn: anExpression relation:
>>>> aSymbol
>>>> "Return our expression using the object's values. e.g. if this was a
>>>> direct mapping from id->ID and the object had id: 3, then return
>>>> TABLE.ID=3"
>>>>
>>>> | value |
>>>> value := anObject isNil
>>>> ifTrue: [nil]
>>>> ifFalse:
>>>> [anObject isGlorpExpression
>>>> ifTrue: [anObject getMapping: self named: self attributeName]
>>>> ifFalse: [anObject glorpIsCollection
>>>> ifTrue: [anObject collect: [:each | self valueOfField: aField
>>>> fromObject: each]]
>>>> ifFalse: [self valueOfField: aField fromObject: anObject]]].
>>>> ^(anExpression get: self attribute name) get: aSymbol withArguments:
>>>> (Array with: value)
>>>>
>>>>
>>>>
>>>> or this?
>>>>
>>>> DirectMapping >> expressionFor: anObject basedOn: anExpression relation:
>>>> aSymbol
>>>> "Return our expression using the object's values. e.g. if this was a
>>>> direct mapping from id->ID and the object had id: 3, then return
>>>> TABLE.ID=3"
>>>>
>>>> | value |
>>>> value := anObject isNil
>>>> ifTrue: [nil]
>>>> ifFalse:
>>>> [anObject isGlorpExpression
>>>> ifTrue: [anObject getMapping: self named: self attributeName]
>>>> ifFalse: [anObject glorpIsCollection
>>>> ifTrue: [anObject collect: [:each | self convertedDbValueOf: (attribute
>>>> getValueFrom: each)]]
>>>> ifFalse: [self convertedDbValueOf: (attribute getValueFrom: anObject)]]].
>>>> ^(anExpression get: self attribute name) get: aSymbol withArguments:
>>>> (Array with: value)
>>>>
>>>>
>>>>
>>>> Or something completely different?
>>>>
>>>>
>>>> Thanks, Herby
>>>>
>>>> Herby VojÄÃk wrote:
>>>>> Hello!
>>>>>
>>>>> I encountered a problem with OneToOneMapping and type coercion. When
>>>>> writing data, thing work; when reading data, the right child of relation
>>>>> fails to convert.
>>>>>
>>>>> I tried everything possible to inject converters (even subclassing
>>>>> GlorpBlobType), but to no avail. RelationExpression passes conversion to
>>>>> its left child:
>>>>>
>>>>> convertedDbValueOf: anObject
>>>>> "Assume that our types match, so we can ask either child to do the
>>>>> conversion. That isn't guaranteed, but should at least work for the
>>>>> common cases."
>>>>> ^leftChild convertedDbValueOf: anObject.
>>>>>
>>>>> but the left child is FieldExpression in case of OneToOneMapping, which:
>>>>>
>>>>> convertedDbValueOf: anObject
>>>>> "We don't do any conversion"
>>>>> ^anObject
>>>>>
>>>>> What is strange, writing works (even the OneToOneMapping, I opened the
>>>>> sqlite file with an explorer), but second SELECT, one using the relation
>>>>> (`state := self dao findStateByAgent: agent` in clientSync), fails with
>>>>> "GlorpDatabaseReadError: Could not coerce arguments". FWIW, the first
>>>>> one _does_ convert when creating bindings, as it uses MappingExpression
>>>>> as left child (stepped over it in debugger).
>>>>>
>>>>>
>>>>>
>>>>> Is it meant to be a strange case that primary key is something
>>>>> non-primitive needing coercion (in this case, it is a UUID which needs
>>>>> coercion to ByteArray, even if it is its subclass)?
>>>>>
>>>>>
>>>>>
>>>>> Here's the stack of running the test which fails:
>>>>>
>>>>> PharoDatabaseAccessor(DatabaseAccessor)>>handleError:for:
>>>>> [ :ex | self handleError: ex for: command ] in [ | result |
>>>>> self checkPermissionFor: command.
>>>>> result := [ (self useBinding and: [ command useBinding ])
>>>>> ifTrue: [ command executeBoundIn: self ]
>>>>> ifFalse: [ command executeUnboundIn: self ] ]
>>>>> on: Dialect error
>>>>> do: [ :ex | self handleError: ex for: command ].
>>>>> aBoolean
>>>>> ifTrue: [ result ]
>>>>> ifFalse: [ result upToEnd ] ] in
>>>>> PharoDatabaseAccessor(DatabaseAccessor)>>executeCommand:returnCursor:
>>>>> BlockClosure>>cull:
>>>>> Context>>evaluateSignal:
>>>>> Context>>handleSignal:
>>>>> Error(Exception)>>signal
>>>>> Error(Exception)>>signal:
>>>>> ExternalLibraryFunction(Object)>>error:
>>>>> ExternalLibraryFunction(Object)>>externalCallFailed
>>>>> ExternalLibraryFunction(ExternalFunction)>>invokeWithArguments:
>>>>> UDBCSQLite3Library>>apiBindBlob:atColumn:with:with:with:
>>>>> UDBCSQLite3Library>>with:at:putBlob:
>>>>> UDBCSQLite3Statement>>at:putByteArray:
>>>>> UDBCSQLite3ResultSet>>execute:withIndex:withValue:
>>>>> [ :v | i := self execute: statement withIndex: i withValue: v ] in
>>>>> UDBCSQLite3ResultSet>>execute:withCollection:
>>>>> OrderedCollection>>do:
>>>>> UDBCSQLite3ResultSet>>execute:withCollection:
>>>>> UDBCSQLite3ResultSet>>execute:with:on:
>>>>> UDBCSQLite3Connection>>execute:with:
>>>>> GlorpSQLite3Driver>>basicExecuteSQLString:binding:
>>>>> PharoDatabaseAccessor>>executeCommandBound:
>>>>> QuerySelectCommand(DatabaseCommand)>>executeBoundIn:
>>>>> [ (self useBinding and: [ command useBinding ])
>>>>> ifTrue: [ command executeBoundIn: self ]
>>>>> ifFalse: [ command executeUnboundIn: self ] ] in [ | result |
>>>>> self checkPermissionFor: command.
>>>>> result := [ (self useBinding and: [ command useBinding ])
>>>>> ifTrue: [ command executeBoundIn: self ]
>>>>> ifFalse: [ command executeUnboundIn: self ] ]
>>>>> on: Dialect error
>>>>> do: [ :ex | self handleError: ex for: command ].
>>>>> aBoolean
>>>>> ifTrue: [ result ]
>>>>> ifFalse: [ result upToEnd ] ] in
>>>>> PharoDatabaseAccessor(DatabaseAccessor)>>executeCommand:returnCursor:
>>>>> BlockClosure>>on:do:
>>>>> [ | result |
>>>>> self checkPermissionFor: command.
>>>>> result := [ (self useBinding and: [ command useBinding ])
>>>>> ifTrue: [ command executeBoundIn: self ]
>>>>> ifFalse: [ command executeUnboundIn: self ] ]
>>>>> on: Dialect error
>>>>> do: [ :ex | self handleError: ex for: command ].
>>>>> aBoolean
>>>>> ifTrue: [ result ]
>>>>> ifFalse: [ result upToEnd ] ] in
>>>>> PharoDatabaseAccessor(DatabaseAccessor)>>executeCommand:returnCursor:
>>>>> [ caught := true.
>>>>> self wait.
>>>>> blockValue := mutuallyExcludedBlock value ] in Semaphore>>critical:
>>>>> BlockClosure>>ensure:
>>>>> Semaphore>>critical:
>>>>> PharoDatabaseAccessor(DatabaseAccessor)>>executeCommand:returnCursor:
>>>>> [ session accessor executeCommand: command returnCursor: true ] in
>>>>> SimpleQuery>>rowsFromDatabaseWithParameters:
>>>>> BlockClosure>>on:do:
>>>>> SimpleQuery>>rowsFromDatabaseWithParameters:
>>>>> SimpleQuery(AbstractReadQuery)>>readFromDatabaseWithParameters:
>>>>> SimpleQuery(AbstractReadQuery)>>executeWithParameters:in:
>>>>> GlorpSession>>execute:
>>>>> GlorpSession>>readOneOf:where:
>>>>> TowergameDao>>findStateByAgent:
>>>>> [ | agent state |
>>>>> agent := self dao findAgentById: anObject agentId.
>>>>> state := self dao findStateByAgent: agent.
>>>>> ^ NeoJSONObject new
>>>>> agentId: agent id;
>>>>> stateVersion: state version;
>>>>> totalAnsweredQuestions:
>>>>> (NeoJSONObject new
>>>>> good: 0;
>>>>> bad: 0;
>>>>> yourself);
>>>>> yourself ] in Towergame>>clientSync:
>>>>> [ myUnitOfWork := self hasUnitOfWork not.
>>>>> myUnitOfWork
>>>>> ifTrue: [ self beginUnitOfWork ].
>>>>> result := aBlock numArgs = 1
>>>>> ifTrue: [ aBlock value: self ]
>>>>> ifFalse: [ aBlock value ].
>>>>> myUnitOfWork
>>>>> ifTrue: [ self commitUnitOfWork ] ] in GlorpSession>>inUnitOfWorkDo:
>>>>> BlockClosure>>ifCurtailed:
>>>>> GlorpSession>>inUnitOfWorkDo:
>>>>> TowergameDao>>inUnitOfWorkDo:
>>>>> Towergame>>clientSync:
>>>>> TowergameSyncTests>>testPlayerChecksStateVersion
>>>>> TowergameSyncTests(TestCase)>>performTest
>>>>> [ self setUp.
>>>>> self performTest ] in TowergameSyncTests(TestCase)>>runCase
>>>>> BlockClosure>>ensure:
>>>>> TowergameSyncTests(TestCase)>>runCase
>>>>> [ aTestCase runCase ] in [ [ aTestCase runCase ]
>>>>> on: Halt
>>>>> do: [ :halt |
>>>>> "if test was halted we should resume all background failures
>>>>> to debug all of them together with test process"
>>>>> failedProcesses keysDo: #resume.
>>>>> halt pass ] ] in TestExecutionEnvironment>>runTestCaseSafelly:
>>>>> BlockClosure>>on:do:
>>>>> [ [ aTestCase runCase ]
>>>>> on: Halt
>>>>> do: [ :halt |
>>>>> "if test was halted we should resume all background failures
>>>>> to debug all of them together with test process"
>>>>> failedProcesses keysDo: #resume.
>>>>> halt pass ] ] in TestExecutionEnvironment>>runTestCaseSafelly:
>>>>> BlockClosure>>on:do:
>>>>> TestExecutionEnvironment>>runTestCaseSafelly:
>>>>> [ self runTestCaseSafelly: aTestCase ] in [ [ self runTestCaseSafelly:
>>>>> aTestCase ]
>>>>> ensure: [ testCompleted := true.
>>>>> watchDogSemaphore signal ]. "signal that test case completes"
>>>>> self checkForkedProcesses ] in TestExecutionEnvironment>>runTestCase:
>>>>> BlockClosure>>ensure:
>>>>> [ [ self runTestCaseSafelly: aTestCase ]
>>>>> ensure: [ testCompleted := true.
>>>>> watchDogSemaphore signal ]. "signal that test case completes"
>>>>> self checkForkedProcesses ] in TestExecutionEnvironment>>runTestCase:
>>>>> BlockClosure>>ifCurtailed:
>>>>> TestExecutionEnvironment>>runTestCase:
>>>>> [ testEnv runTestCase: aTestCase ] in
>>>>> DefaultExecutionEnvironment>>runTestCase:
>>>>> [ self value: anExecutionEnvironment.
>>>>> anExecutionEnvironment activated.
>>>>> aBlock value ] in CurrentExecutionEnvironment class>>activate:for:
>>>>> BlockClosure>>ensure:
>>>>> CurrentExecutionEnvironment class>>activate:for:
>>>>> TestExecutionEnvironment(ExecutionEnvironment)>>beActiveDuring:
>>>>> DefaultExecutionEnvironment>>runTestCase:
>>>>> CurrentExecutionEnvironment class>>runTestCase:
>>>>> TowergameSyncTests(TestCase)>>runCaseManaged
>>>>> [ aTestCase announce: TestCaseStarted withResult: self.
>>>>> aTestCase runCaseManaged.
>>>>> aTestCase announce: TestCaseEnded withResult: self.
>>>>> self addPass: aTestCase ] in TestResult>>runCaseForDebug:
>>>>> BlockClosure>>on:do:
>>>>> TestResult>>runCaseForDebug:
>>>>> [ result runCaseForDebug: self ] in TowergameSyncTests(TestCase)>>debug
>>>>> BlockClosure>>ensure:
>>>>> TowergameSyncTests(TestCase)>>debug
>>>>> [ :each |
>>>>> each debug.
>>>>> self announceTest: each.
>>>>> self changed: each ] in [ self tests
>>>>> do: [ :each |
>>>>> each debug.
>>>>> self announceTest: each.
>>>>> self changed: each ] ] in TestSuite>>debug
>>>>> OrderedCollection>>do:
>>>>> [ self tests
>>>>> do: [ :each |
>>>>> each debug.
>>>>> self announceTest: each.
>>>>> self changed: each ] ] in TestSuite>>debug
>>>>> BlockClosure>>ensure:
>>>>> TestSuite>>debug
>>>>> [ :aSuite | aSuite debug ] in TestRunner>>debugSuite:
>>>>> BlockClosure>>cull:
>>>>> BlockClosure>>cull:cull:
>>>>> [ aBlock cull: aTestSuite cull: result ] in TestRunner>>executeSuite:as:
>>>>> BlockClosure>>ensure:
>>>>> TestRunner>>executeSuite:as:
>>>>> TestRunner>>debugSuite:
>>>>> TestRunner>>debug:
>>>>> TestRunner>>errorSelected:
>>>>> PluggableListMorph>>changeModelSelection:
>>>>> PluggableListMorph>>mouseUpOnSingle:
>>>>> PluggableListMorph>>mouseUp:
>>>>> PluggableListMorph(Morph)>>handleMouseUp:
>>>>> MouseButtonEvent>>sentTo:
>>>>> PluggableListMorph(Morph)>>handleEvent:
>>>>> MorphicEventDispatcher>>dispatchDefault:with:
>>>>> MorphicEventDispatcher>>handleMouseUp:
>>>>> MouseButtonEvent>>sentTo:
>>>>> [ ^ anEvent sentTo: self ] in
>>>>> MorphicEventDispatcher>>dispatchEvent:with:
>>>>> BlockClosure>>ensure:
>>>>> MorphicEventDispatcher>>dispatchEvent:with:
>>>>> PluggableListMorph(Morph)>>processEvent:using:
>>>>> PluggableListMorph(Morph)>>processEvent:
>>>>> PluggableListMorph>>handleFocusEvent:
>>>>> [ ActiveHand := self.
>>>>> ActiveEvent := anEvent.
>>>>> result := focusHolder
>>>>> handleFocusEvent: (anEvent transformedBy: (focusHolder transformedFrom:
>>>>> self)) ] in HandMorph>>sendFocusEvent:to:clear:
>>>>> BlockClosure>>on:do:
>>>>> WorldMorph(PasteUpMorph)>>becomeActiveDuring:
>>>>> HandMorph>>sendFocusEvent:to:clear:
>>>>> HandMorph>>sendEvent:focus:clear:
>>>>> HandMorph>>sendMouseEvent:
>>>>> HandMorph>>handleEvent:
>>>>> HandMorph>>processEventsFromQueue:
>>>>> HandMorph>>processEvents
>>>>> [ :h |
>>>>> self activeHand: h.
>>>>> h processEvents.
>>>>> self activeHand: nil ] in WorldState>>doOneCycleNowFor:
>>>>> Array(SequenceableCollection)>>do:
>>>>> WorldState>>handsDo:
>>>>> WorldState>>doOneCycleNowFor:
>>>>> WorldState>>doOneCycleFor:
>>>>> WorldMorph>>doOneCycle
>>>>> WorldMorph class>>doOneCycle
>>>>> [ [ WorldMorph doOneCycle.
>>>>> Processor yield.
>>>>> false ] whileFalse: [ ] ] in MorphicUIManager>>spawnNewProcess
>>>>> [ self value.
>>>>> Processor terminateActive ] in BlockClosure>>newProcess
>>>>>
>>>>>
>>>>>
>>>>> And here's the code:
>>>>>
>>>>>
>>>>> Towergame.st:
>>>>>
>>>>> GlorpBlobType subclass: #GlorpBlob2Type
>>>>> instanceVariableNames: ''
>>>>> classVariableNames: ''
>>>>> poolDictionaries: ''
>>>>> category: 'Towergame'!
>>>>>
>>>>> !GlorpBlob2Type methodsFor: 'types' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> converterForStType: aClass
>>>>> aClass = UUID ifTrue: [ ^ UuidConverter new ].
>>>>> ^ super converterForStType: aClass! !
>>>>>
>>>>>
>>>>> Object subclass: #TgAct
>>>>> instanceVariableNames: 'agent tool timestamp'
>>>>> classVariableNames: ''
>>>>> poolDictionaries: ''
>>>>> category: 'Towergame'!
>>>>> !TgAct commentStamp: 'HerbyVojcik 8/5/2017 19:23' prior: 0!
>>>>> I represent a relationship between a player (TgAgent)
>>>>> and a device (TgTool).
>>>>>
>>>>> In particular, I am created whenever a player logs in to the game from
>>>>> different device
>>>>> than it was last time (or first time, ever).!
>>>>>
>>>>>
>>>>> !TgAct methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> agent
>>>>> ^ agent! !
>>>>>
>>>>> !TgAct methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> agent: anObject
>>>>> agent := anObject! !
>>>>>
>>>>> !TgAct methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> timestamp: anObject
>>>>> timestamp := anObject! !
>>>>>
>>>>> !TgAct methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> tool
>>>>> ^ tool! !
>>>>>
>>>>> !TgAct methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> tool: anObject
>>>>> tool := anObject! !
>>>>>
>>>>> !TgAct methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> timestamp
>>>>> ^ timestamp! !
>>>>>
>>>>>
>>>>> !TgAct methodsFor: 'initialization' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> initialize
>>>>>
>>>>> super initialize.
>>>>>
>>>>> agent := nil.
>>>>> timestamp := DateAndTime now asUTC.
>>>>> tool := nil.! !
>>>>>
>>>>> "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
>>>>>
>>>>> TgAct class
>>>>> instanceVariableNames: ''!
>>>>>
>>>>> !TgAct class methodsFor: 'instance creation' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> agent: aTgAgent tool: aTgTool
>>>>> ^ self new
>>>>> agent: aTgAgent;
>>>>> tool: aTgTool;
>>>>> yourself! !
>>>>>
>>>>>
>>>>> Object subclass: #TgAgent
>>>>> instanceVariableNames: 'id'
>>>>> classVariableNames: ''
>>>>> poolDictionaries: ''
>>>>> category: 'Towergame'!
>>>>> !TgAgent commentStamp: 'HerbyVojcik 8/5/2017 19:22' prior: 0!
>>>>> I represent a towergame player.
>>>>>
>>>>> I only contain player-related information;
>>>>> the game state itself is in TgState.!
>>>>>
>>>>>
>>>>> !TgAgent methodsFor: 'initialization' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> initialize
>>>>>
>>>>> super initialize.
>>>>>
>>>>> id := nil.! !
>>>>>
>>>>>
>>>>> !TgAgent methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> id: anObject
>>>>> id := anObject! !
>>>>>
>>>>> !TgAgent methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> id
>>>>> ^ id! !
>>>>>
>>>>> "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
>>>>>
>>>>> TgAgent class
>>>>> instanceVariableNames: ''!
>>>>>
>>>>> !TgAgent class methodsFor: 'instance creation' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> id: aString
>>>>> ^ self new
>>>>> id: aString;
>>>>> yourself! !
>>>>>
>>>>>
>>>>> Object subclass: #TgAnswers
>>>>> instanceVariableNames: 'good bad'
>>>>> classVariableNames: ''
>>>>> poolDictionaries: ''
>>>>> category: 'Towergame'!
>>>>> !TgAnswers commentStamp: 'HerbyVojcik 8/5/2017 20:23' prior: 0!
>>>>> I represent the answered question stats.
>>>>>
>>>>> I know how many good / bad answered questions there is.!
>>>>>
>>>>>
>>>>> !TgAnswers methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> good
>>>>> ^ good! !
>>>>>
>>>>> !TgAnswers methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> bad: anObject
>>>>> bad := anObject! !
>>>>>
>>>>> !TgAnswers methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> bad
>>>>> ^ bad! !
>>>>>
>>>>> !TgAnswers methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> good: anObject
>>>>> good := anObject! !
>>>>>
>>>>>
>>>>> !TgAnswers methodsFor: 'initialization' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> initialize
>>>>>
>>>>> super initialize.
>>>>>
>>>>> bad := 0.
>>>>> good := 0.! !
>>>>>
>>>>> "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
>>>>>
>>>>> TgAnswers class
>>>>> instanceVariableNames: ''!
>>>>>
>>>>> !TgAnswers class methodsFor: 'instance creation' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> good: anInteger bad: anInteger2
>>>>> ^ self new
>>>>> good: anInteger;
>>>>> bad: anInteger2;
>>>>> yourself! !
>>>>>
>>>>>
>>>>> Object subclass: #TgFloors
>>>>> instanceVariableNames: 'total reinforced'
>>>>> classVariableNames: ''
>>>>> poolDictionaries: ''
>>>>> category: 'Towergame'!
>>>>> !TgFloors commentStamp: 'HerbyVojcik 8/5/2017 20:22' prior: 0!
>>>>> I represent the floor building status.
>>>>>
>>>>> I know how many floors are build and how many of them is reinforced.!
>>>>>
>>>>>
>>>>> !TgFloors methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> total
>>>>> ^ total! !
>>>>>
>>>>> !TgFloors methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> total: anObject
>>>>> total := anObject! !
>>>>>
>>>>> !TgFloors methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> reinforced
>>>>> ^ reinforced! !
>>>>>
>>>>> !TgFloors methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> reinforced: anObject
>>>>> reinforced := anObject! !
>>>>>
>>>>>
>>>>> !TgFloors methodsFor: 'initialization' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> initialize
>>>>>
>>>>> super initialize.
>>>>>
>>>>> reinforced := 0.
>>>>> total := 0.! !
>>>>>
>>>>> "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
>>>>>
>>>>> TgFloors class
>>>>> instanceVariableNames: ''!
>>>>>
>>>>> !TgFloors class methodsFor: 'instance creation' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> total: anInteger
>>>>> ^ self total: anInteger reinforced: 0! !
>>>>>
>>>>> !TgFloors class methodsFor: 'instance creation' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> total: anInteger reinforced: anInteger2
>>>>> ^ self new
>>>>> total: anInteger;
>>>>> reinforced: anInteger2;
>>>>> yourself! !
>>>>>
>>>>>
>>>>> Object subclass: #TgState
>>>>> instanceVariableNames: 'agent version packs valuables score bestScore
>>>>> answers'
>>>>> classVariableNames: ''
>>>>> poolDictionaries: ''
>>>>> category: 'Towergame'!
>>>>> !TgState commentStamp: 'HerbyVojcik 8/5/2017 20:20' prior: 0!
>>>>> I represent the game state.
>>>>>
>>>>> I have relation to a player (TgAgent) and have a version.
>>>>> Then, I contain (directly or indirectly) other parts that
>>>>> make up the player's game state.
>>>>>
>>>>> Whenever I am changed by game progress, my version is changed as well.!
>>>>>
>>>>>
>>>>> !TgState methodsFor: 'initialization' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> initialize
>>>>>
>>>>> super initialize.
>>>>>
>>>>> agent := nil.
>>>>> answers := nil.
>>>>> bestScore := nil.
>>>>> packs := Set new.
>>>>> score := nil.
>>>>> valuables := nil.
>>>>> version := nil.! !
>>>>>
>>>>>
>>>>> !TgState methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> score: anObject
>>>>> score := anObject! !
>>>>>
>>>>> !TgState methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> bestScore: anObject
>>>>> bestScore := anObject! !
>>>>>
>>>>> !TgState methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> agent: anObject
>>>>> agent := anObject! !
>>>>>
>>>>> !TgState methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> score
>>>>> ^ score! !
>>>>>
>>>>> !TgState methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> packs
>>>>> ^ packs! !
>>>>>
>>>>> !TgState methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> version
>>>>> ^ version! !
>>>>>
>>>>> !TgState methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> bestScore
>>>>> ^ bestScore! !
>>>>>
>>>>> !TgState methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> agent
>>>>> ^ agent! !
>>>>>
>>>>> !TgState methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> answers: anObject
>>>>> answers := anObject! !
>>>>>
>>>>> !TgState methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> valuables: anObject
>>>>> valuables := anObject! !
>>>>>
>>>>> !TgState methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> valuables
>>>>> ^ valuables! !
>>>>>
>>>>> !TgState methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> version: anObject
>>>>> version := anObject! !
>>>>>
>>>>> !TgState methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> answers
>>>>> ^ answers! !
>>>>>
>>>>> !TgState methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> packs: anObject
>>>>> packs := anObject! !
>>>>>
>>>>> "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
>>>>>
>>>>> TgState class
>>>>> instanceVariableNames: ''!
>>>>>
>>>>> !TgState class methodsFor: 'instance creation' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> agent: aTgAgent version: aString
>>>>> ^ self new
>>>>> agent: aTgAgent;
>>>>> version: aString;
>>>>> yourself! !
>>>>>
>>>>>
>>>>> Object subclass: #TgTool
>>>>> instanceVariableNames: 'id'
>>>>> classVariableNames: ''
>>>>> poolDictionaries: ''
>>>>> category: 'Towergame'!
>>>>> !TgTool commentStamp: 'HerbyVojcik 8/5/2017 19:26' prior: 0!
>>>>> I represent the device (mobile phone, web browser, ..)
>>>>> that player uses to connect to game.!
>>>>>
>>>>>
>>>>> !TgTool methodsFor: 'initialization' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> initialize
>>>>>
>>>>> super initialize.
>>>>>
>>>>> id := nil.! !
>>>>>
>>>>>
>>>>> !TgTool methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> id: anObject
>>>>> id := anObject! !
>>>>>
>>>>> !TgTool methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> id
>>>>> ^ id! !
>>>>>
>>>>> "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
>>>>>
>>>>> TgTool class
>>>>> instanceVariableNames: ''!
>>>>>
>>>>> !TgTool class methodsFor: 'instance creation' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> id: aString
>>>>> ^ self new
>>>>> id: aString;
>>>>> yourself! !
>>>>>
>>>>>
>>>>> Object subclass: #TgValuables
>>>>> instanceVariableNames: 'coins gems'
>>>>> classVariableNames: ''
>>>>> poolDictionaries: ''
>>>>> category: 'Towergame'!
>>>>> !TgValuables commentStamp: 'HerbyVojcik 8/5/2017 20:22' prior: 0!
>>>>> I represent a purse.
>>>>>
>>>>> I know how many coins and gems there is.!
>>>>>
>>>>>
>>>>> !TgValuables methodsFor: 'initialization' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> initialize
>>>>>
>>>>> super initialize.
>>>>>
>>>>> coins := 0.
>>>>> gems := 0.! !
>>>>>
>>>>>
>>>>> !TgValuables methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> gems: anObject
>>>>> gems := anObject! !
>>>>>
>>>>> !TgValuables methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> coins: anObject
>>>>> coins := anObject! !
>>>>>
>>>>> !TgValuables methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> gems
>>>>> ^ gems! !
>>>>>
>>>>> !TgValuables methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> coins
>>>>> ^ coins! !
>>>>>
>>>>> "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
>>>>>
>>>>> TgValuables class
>>>>> instanceVariableNames: ''!
>>>>>
>>>>> !TgValuables class methodsFor: 'instance creation' stamp:
>>>>> 'HerbertVojÃÂÃÂk 8/14/2017 18:09:53'!
>>>>> coins: anInteger gems: anInteger2
>>>>> ^ self new
>>>>> coins: anInteger;
>>>>> gems: anInteger2;
>>>>> yourself! !
>>>>>
>>>>>
>>>>> Object subclass: #Towergame
>>>>> instanceVariableNames: 'dao'
>>>>> classVariableNames: ''
>>>>> poolDictionaries: ''
>>>>> category: 'Towergame'!
>>>>> !Towergame commentStamp: 'HerbyVojcik 5/17/2017 17:19' prior: 0!
>>>>> I am the Towergame app class.
>>>>>
>>>>> I configure and start towergame server processing.!
>>>>>
>>>>>
>>>>> !Towergame methodsFor: 'actions' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> clientSync: anObject
>>>>> self dao inUnitOfWorkDo: [
>>>>> | agent state |
>>>>> agent := self dao findAgentById: anObject agentId.
>>>>> state := self dao findStateByAgent: agent.
>>>>> ^ NeoJSONObject new
>>>>> agentId: agent id;
>>>>> stateVersion: state version;
>>>>> totalAnsweredQuestions: (NeoJSONObject new good: 0; bad: 0; yourself);
>>>>> yourself ]! !
>>>>>
>>>>>
>>>>> !Towergame methodsFor: 'initialization' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> initialize
>>>>>
>>>>> super initialize.
>>>>>
>>>>> dao := nil.
>>>>> ! !
>>>>>
>>>>>
>>>>> !Towergame methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> dao: anObject
>>>>> dao := anObject! !
>>>>>
>>>>> !Towergame methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> dao
>>>>> ^ dao! !
>>>>>
>>>>> "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
>>>>>
>>>>> Towergame class
>>>>> instanceVariableNames: 'default'!
>>>>>
>>>>> !Towergame class methodsFor: 'instance creation' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> dao: aDao
>>>>> ^ self new
>>>>> dao: aDao;
>>>>> yourself! !
>>>>>
>>>>>
>>>>> !Towergame class methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> defaultDbLogin
>>>>> | databaseFile |
>>>>> databaseFile := Smalltalk imageDirectory asFileReference /
>>>>> 'towergame.db'.
>>>>> ^ Login new
>>>>> database: UDBCSQLite3Platform new;
>>>>> host: '';
>>>>> port: '';
>>>>> username: '';
>>>>> password: '';
>>>>> databaseName: databaseFile fullPath asZnUrl asString;
>>>>> yourself ! !
>>>>>
>>>>> !Towergame class methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> default
>>>>> ^ default ifNil: [ default := self
>>>>> dao: (self daoForLogin: self defaultDbLogin)
>>>>> ]! !
>>>>>
>>>>> !Towergame class methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> defaultPort
>>>>> ^ 4998! !
>>>>>
>>>>>
>>>>> !Towergame class methodsFor: 'configuration' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> configureServer
>>>>> (self serverFor: self default on: self defaultPort) start; register
>>>>> ! !
>>>>>
>>>>> !Towergame class methodsFor: 'configuration' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> configureSqlite3
>>>>>
>>>>> PharoDatabaseAccessor DefaultDriver: GlorpSQLite3Driver! !
>>>>>
>>>>> !Towergame class methodsFor: 'configuration' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> configure
>>>>> self configureSqlite3.
>>>>> self configureServer.! !
>>>>>
>>>>>
>>>>> !Towergame class methodsFor: 'factory' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> daoForLogin: aLogin
>>>>> ^ TowergameDao forLogin: aLogin! !
>>>>>
>>>>> !Towergame class methodsFor: 'factory' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> serverFor: aTowergame on: port
>>>>> ^ (ZnServer on: port)
>>>>> delegate: (TowergameDelegate on: aTowergame);
>>>>> yourself! !
>>>>>
>>>>>
>>>>> Object subclass: #TowergameDao
>>>>> instanceVariableNames: 'glorpSession glorpLogin'
>>>>> classVariableNames: ''
>>>>> poolDictionaries: ''
>>>>> category: 'Towergame'!
>>>>>
>>>>> !TowergameDao methodsFor: 'transactions' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> inUnitOfWorkDo: aBlock
>>>>> ^ self glorpSession inUnitOfWorkDo: aBlock! !
>>>>>
>>>>>
>>>>> !TowergameDao methodsFor: 'initialization' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> initialize
>>>>>
>>>>> super initialize.
>>>>>
>>>>> glorpLogin := nil.
>>>>> glorpSession := nil.! !
>>>>>
>>>>>
>>>>> !TowergameDao methodsFor: 'initialize-release' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> reset
>>>>> glorpSession := nil.! !
>>>>>
>>>>>
>>>>> !TowergameDao methodsFor: 'query' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> findStateByAgent: anAgent
>>>>> ^ self glorpSession readOneOf: TgState where: [ :one | one agent =
>>>>> anAgent ]! !
>>>>>
>>>>> !TowergameDao methodsFor: 'query' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> findAgentById: anUUID
>>>>> ^ self glorpSession readOneOf: TgAgent where: [ :one | one id = anUUID
>>>>> ]! !
>>>>>
>>>>>
>>>>> !TowergameDao methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> glorpLogin: anObject
>>>>> glorpLogin := anObject! !
>>>>>
>>>>> !TowergameDao methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> glorpLogin
>>>>> ^ glorpLogin! !
>>>>>
>>>>> !TowergameDao methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> glorpSession
>>>>> glorpSession ifNil: [
>>>>> glorpSession := TowergameDescriptorSystem sessionForLogin: self
>>>>> glorpLogin ].
>>>>> glorpSession accessor isLoggedIn ifFalse: [
>>>>> glorpSession accessor login ].
>>>>> ^ glorpSession! !
>>>>>
>>>>> "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
>>>>>
>>>>> TowergameDao class
>>>>> instanceVariableNames: ''!
>>>>>
>>>>> !TowergameDao class methodsFor: 'instance creation' stamp:
>>>>> 'HerbertVojÃÂÃÂk 8/14/2017 18:09:53'!
>>>>> forLogin: aLogin
>>>>> ^ self new
>>>>> glorpLogin: aLogin;
>>>>> yourself! !
>>>>>
>>>>>
>>>>> ZnDispatcherDelegate subclass: #TowergameDelegate
>>>>> instanceVariableNames: 'towergame'
>>>>> classVariableNames: ''
>>>>> poolDictionaries: ''
>>>>> category: 'Towergame'!
>>>>>
>>>>> !TowergameDelegate methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> towergame
>>>>> ^ towergame! !
>>>>>
>>>>> !TowergameDelegate methodsFor: 'accessing' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> towergame: anObject
>>>>> towergame := anObject! !
>>>>>
>>>>>
>>>>> !TowergameDelegate methodsFor: 'initialization' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> initialize
>>>>> super initialize.
>>>>> towergame := nil.
>>>>> self
>>>>> map: '/api/v1/sync'
>>>>> to: [ :request :response | self syncRequest: request toResponse:
>>>>> response ]! !
>>>>>
>>>>>
>>>>> !TowergameDelegate methodsFor: 'action' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> syncRequest: request toResponse: response
>>>>> | requestPayload responsePayload uuidKeys |
>>>>> uuidKeys := #(agentId stateVersion deviceId).
>>>>> request method == #POST ifFalse: [ ^ ZnResponse methodNotAllowed:
>>>>> request ].
>>>>> requestPayload := NeoJSONObject fromString: request contents.
>>>>> requestPayload ifNotNil: [
>>>>> uuidKeys do: [ :each | requestPayload at: each ifPresentPut: [ :s | UUID
>>>>> fromString: s ] ] ].
>>>>> responsePayload := self towergame clientSync: requestPayload.
>>>>> responsePayload ifNotNil: [
>>>>> uuidKeys do: [ :each | responsePayload at: each ifPresentPut: #asString
>>>>> ] ].
>>>>> ^ response
>>>>> entity: (ZnEntity
>>>>> with: (NeoJSONWriter toString: responsePayload)
>>>>> type: ZnMimeType applicationJson);
>>>>> yourself! !
>>>>>
>>>>> "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
>>>>>
>>>>> TowergameDelegate class
>>>>> instanceVariableNames: ''!
>>>>>
>>>>> !TowergameDelegate class methodsFor: 'instance creation' stamp:
>>>>> 'HerbertVojÃÂÃÂk 8/14/2017 18:09:53'!
>>>>> on: aTowergame
>>>>> ^ self new towergame: aTowergame; yourself! !
>>>>>
>>>>>
>>>>> DescriptorSystem subclass: #TowergameDescriptorSystem
>>>>> instanceVariableNames: 'uuidConverter'
>>>>> classVariableNames: ''
>>>>> poolDictionaries: ''
>>>>> category: 'Towergame'!
>>>>>
>>>>> !TowergameDescriptorSystem methodsFor: 'glorp' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> tableForAGENT: aTable
>>>>>
>>>>> (aTable createFieldNamed: 'id' type: platform blob2) bePrimaryKey.
>>>>> ! !
>>>>>
>>>>> !TowergameDescriptorSystem methodsFor: 'glorp' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> classModelForTgAgent: aClassModel
>>>>> aClassModel
>>>>> newAttributeNamed: #id type: UUID! !
>>>>>
>>>>> !TowergameDescriptorSystem methodsFor: 'glorp' stamp: 'HerbyVojcik
>>>>> 8/14/2017 18:24'!
>>>>> tableForSTATE: aTable
>>>>>
>>>>> (aTable createFieldNamed: 'agent' type: platform blob2) in: [
>>>>> :agentField |
>>>>> agentField bePrimaryKey.
>>>>> aTable addForeignKeyFrom: agentField to: ((self tableNamed: 'AGENT')
>>>>> fieldNamed: 'id') ].
>>>>> (aTable createFieldNamed: 'version' type: platform blob) beIndexed.
>>>>> ! !
>>>>>
>>>>> !TowergameDescriptorSystem methodsFor: 'glorp' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> descriptorForTgAgent: aDescriptor
>>>>> | table |
>>>>> table := self tableNamed: 'AGENT'.
>>>>> aDescriptor table: table.
>>>>> (aDescriptor newMapping: DirectMapping)
>>>>> from: #id to: (table fieldNamed: 'id').! !
>>>>>
>>>>> !TowergameDescriptorSystem methodsFor: 'glorp' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> tableForACT: aTable
>>>>>
>>>>> (aTable createFieldNamed: 'agent' type: platform blob2) beIndexed.
>>>>> (aTable createFieldNamed: 'tool' type: platform blob2) beIndexed.
>>>>> (aTable createFieldNamed: 'timestamp' type: platform timestamp)
>>>>> beIndexed.
>>>>> ! !
>>>>>
>>>>> !TowergameDescriptorSystem methodsFor: 'glorp' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> descriptorForTgState: aDescriptor
>>>>> | table |
>>>>> table := self tableNamed: 'STATE'.
>>>>> aDescriptor table: table.
>>>>> (aDescriptor newMapping: OneToOneMapping) attributeName: #agent.
>>>>> (aDescriptor newMapping: DirectMapping)
>>>>> from: #version to: (table fieldNamed: 'version').! !
>>>>>
>>>>> !TowergameDescriptorSystem methodsFor: 'glorp' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> converterBetweenStType: aClass andField: aField
>>>>> (aClass = UUID and: [ aField impliedSmalltalkType = ByteArray])
>>>>> ifTrue: [ ^ self uuidConverter ].
>>>>> ^ super converterBetweenStType: aClass andField: aField! !
>>>>>
>>>>> !TowergameDescriptorSystem methodsFor: 'glorp' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> classModelForTgState: aClassModel
>>>>> "agent version packs valuables score bestScore answers"
>>>>> aClassModel
>>>>> newAttributeNamed: #agent type: TgAgent;
>>>>> newAttributeNamed: #version type: UUID! !
>>>>>
>>>>> !TowergameDescriptorSystem methodsFor: 'glorp' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> classModelForTgTool: aClassModel
>>>>> aClassModel
>>>>> newAttributeNamed: #id
>>>>> ! !
>>>>>
>>>>> !TowergameDescriptorSystem methodsFor: 'glorp' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> classModelForTgAct: aClassModel
>>>>> aClassModel
>>>>> newAttributeNamed: #timestamp;
>>>>> newAttributeNamed: #agent type: TgAgent;
>>>>> newAttributeNamed: #tool type: TgTool! !
>>>>>
>>>>>
>>>>> !TowergameDescriptorSystem methodsFor: 'accessing' stamp:
>>>>> 'HerbertVojÃÂÃÂk 8/14/2017 18:09:53'!
>>>>> uuidConverter
>>>>> ^ uuidConverter ifNil: [ uuidConverter := UuidConverter new name:
>>>>> 'uuid'; yourself ]! !
>>>>>
>>>>>
>>>>> DatabaseConverter subclass: #UuidConverter
>>>>> instanceVariableNames: ''
>>>>> classVariableNames: ''
>>>>> poolDictionaries: ''
>>>>> category: 'Towergame'!
>>>>>
>>>>> !UuidConverter methodsFor: 'converting' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> convert: anObject toDatabaseRepresentationAs: aDatabaseType
>>>>> ^ anObject ifNotNil: [ ByteArray withAll: anObject ]! !
>>>>>
>>>>> !UuidConverter methodsFor: 'converting' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> convert: anObject fromDatabaseRepresentationAs: aDatabaseType
>>>>> ^ anObject ifNotNil: [ UUID withAll: anObject ]! !
>>>>> 'From Pharo6.0 of 13 May 2016 [Latest update: #60510] on 14 August 2017
>>>>> at 6:26:30.67905 pm'!
>>>>>
>>>>> !DatabasePlatform methodsFor: '*Towergame' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> blob2
>>>>> ^self typeNamed: #blob ifAbsentPut: [GlorpBlob2Type new].! !
>>>>> 'From Pharo6.0 of 13 May 2016 [Latest update: #60510] on 14 August 2017
>>>>> at 6:26:30.68005 pm'!
>>>>>
>>>>> !Dictionary methodsFor: '*Towergame' stamp: 'HerbertVojÃÂÃÂk 8/14/2017
>>>>> 18:09:53'!
>>>>> at: key ifPresentPut: aBlock
>>>>> "Lookup the given key in the receiver. If it is present, update it
>>>>> with the value of evaluating the given block with the value associated
>>>>> with the key. Otherwise, answer nil."
>>>>>
>>>>> ^ self at: key ifPresent: [ :value | self at: key put: (aBlock cull:
>>>>> value) ]! !
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> TowergameTests.st:
>>>>>
>>>>> TestCase subclass: #TowergameServerTests
>>>>> instanceVariableNames: 'randomPort towergame server'
>>>>> classVariableNames: ''
>>>>> poolDictionaries: ''
>>>>> category: 'Towergame-Tests'!
>>>>>
>>>>> !TowergameServerTests methodsFor: 'running' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> uidy: aString
>>>>> ^ UUID fromString36: aString ! !
>>>>>
>>>>> !TowergameServerTests methodsFor: 'running' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> setUp
>>>>> randomPort := 1700 + 32 atRandom.
>>>>> towergame := Mock new.
>>>>> server := Towergame serverFor: towergame on: randomPort.
>>>>> server start.
>>>>> self
>>>>> assert: server isRunning & server isListening
>>>>> description: ('Failed to start server on port {1}. Is there one
>>>>> already?' format: { server port })
>>>>> ! !
>>>>>
>>>>> !TowergameServerTests methodsFor: 'running' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> tearDown
>>>>> server stop! !
>>>>>
>>>>>
>>>>> !TowergameServerTests methodsFor: 'tests' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> testEmptySyncRequest
>>>>> | znClient response |
>>>>> (towergame stub clientSync: Arg payload) willReturn: nil.
>>>>> znClient := self znClientForSync: 'null'.
>>>>> response := znClient timeout: 1; post; response.
>>>>> response should satisfy: #isSuccess.
>>>>> response contentType should equal: ZnMimeType applicationJson.
>>>>> (STON fromString: response entity contents) should equal: nil.
>>>>> Arg payload should equal: nil! !
>>>>>
>>>>> !TowergameServerTests methodsFor: 'tests' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> testRejectEmptyGetSyncRequest
>>>>> | znClient response |
>>>>> (towergame stub clientSync: Arg payload) willReturn: nil.
>>>>> znClient := self znClientForSync: 'null'.
>>>>> response := znClient timeout: 1; get; response.
>>>>> response code should equal: ZnStatusLine methodNotAllowed code.
>>>>> towergame should not receive clientSync: Any! !
>>>>>
>>>>> !TowergameServerTests methodsFor: 'tests' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> testNonEmptySyncRequest
>>>>> | znClient response |
>>>>> (towergame stub clientSync: Arg payload) willReturn: { #agentId -> (self
>>>>> uidy: '007') } asDictionary.
>>>>> znClient := self znClientForSync:
>>>>> ('\{"deviceId":"{1}","agentAnsweredQuestions":\{"good":1,"bad":2\}\}'
>>>>> format: { self uidy: 'Q' }).
>>>>> response := znClient timeout: 1; post; response.
>>>>> response should satisfy: #isSuccess.
>>>>> response contentType should equal: ZnMimeType applicationJson.
>>>>> (STON fromString: response entity contents) should equal: { 'agentId' ->
>>>>> (self uidy: '007') asString } asDictionary.
>>>>> Arg payload in: [ :arg |
>>>>> arg deviceId should equal: (self uidy: 'Q').
>>>>> arg agentAnsweredQuestions should satisfy: #notNil.
>>>>> arg agentAnsweredQuestions good should equal: 1.
>>>>> arg agentAnsweredQuestions bad should equal: 2 ]
>>>>> ! !
>>>>>
>>>>>
>>>>> !TowergameServerTests methodsFor: 'private' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> znClientForSync: jsonString
>>>>> ^ ZnClient new
>>>>> url: server localUrl;
>>>>> path: '/api/v1/sync';
>>>>> entity:
>>>>> (ZnEntity
>>>>> with: jsonString
>>>>> type: ZnMimeType applicationJson)
>>>>> ! !
>>>>>
>>>>>
>>>>> TestCase subclass: #TowergameSyncTests
>>>>> instanceVariableNames: 'towergame session dao'
>>>>> classVariableNames: ''
>>>>> poolDictionaries: ''
>>>>> category: 'Towergame-Tests'!
>>>>>
>>>>> !TowergameSyncTests methodsFor: 'tests' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> testPlayerChecksStateVersionAndIsBehind
>>>>> | result payload |
>>>>> session createTables.
>>>>> session inUnitOfWorkDo: [
>>>>> | agent state |
>>>>> agent := TgAgent id: (self uidy: '007').
>>>>> state := (TgState agent: agent version: (self uidy: '18-eff'))
>>>>> packs: #('foopack' 'barpack') asSet;
>>>>> valuables: (TgValuables coins: 20 gems: 3);
>>>>> score: (TgFloors total: 4 reinforced: 1);
>>>>> bestScore: (TgFloors total: 18);
>>>>> answers: (TgAnswers good: 2 bad: 3);
>>>>> yourself.
>>>>> session registerAll: {state. TgAct agent: agent tool: (TgTool id: (self
>>>>> uidy: 'Q7') ) } ].
>>>>> towergame := Towergame dao: dao.
>>>>> payload := NeoJSONObject new
>>>>> agentId: (self uidy: '007'); stateVersion: (self uidy: '23-fefe');
>>>>> deviceId: (self uidy: 'Q7').
>>>>> result := towergame clientSync: payload.
>>>>> result where agentId should equal: (self uidy: '007').
>>>>> result where stateVersion should equal: (self uidy: '18-eff').
>>>>> result where purchasedPacks should satisfy: [ :x | x asSet should equal:
>>>>> #('foopack' 'barpack') asSet ].
>>>>> result where valuables coins should equal: 20.
>>>>> result where valuables gems should equal: 3.
>>>>> result where floorsNumber current should equal: 4.
>>>>> result where floorsNumber best should equal: 18.
>>>>> result where floorsNumber reinforced should equal: 1.
>>>>> result where agentAnsweredQuestions good should equal: 2.
>>>>> result where agentAnsweredQuestions bad should equal: 3.
>>>>> result where totalAnsweredQuestions good should equal: 2.
>>>>> result where totalAnsweredQuestions bad should equal: 3! !
>>>>>
>>>>> !TowergameSyncTests methodsFor: 'tests' stamp: 'HerbyVojcik 8/14/2017
>>>>> 18:18'!
>>>>> testPlayerChecksStateVersion
>>>>> | result payload |
>>>>> session createTables.
>>>>> session inUnitOfWorkDo: [
>>>>> | agent state |
>>>>> agent := TgAgent id: (self uidy: '007').
>>>>> state := TgState agent: agent version: (self uidy: '23-fefe').
>>>>> session registerAll: {state. TgAct agent: agent tool: (TgTool id: (self
>>>>> uidy: 'Q7') ) } ].
>>>>> towergame := Towergame dao: dao.
>>>>> payload := NeoJSONObject new
>>>>> agentId: (self uidy: '007'); stateVersion: (self uidy: '23-fefe');
>>>>> deviceId: (self uidy: 'Q7').
>>>>> result := towergame clientSync: payload.
>>>>> result where agentId should equal: (self uidy: '007').
>>>>> result where stateVersion should equal: (self uidy: '23-fefe').
>>>>> result where totalAnsweredQuestions good should equal: 0.
>>>>> result where totalAnsweredQuestions bad should equal: 0! !
>>>>>
>>>>>
>>>>> !TowergameSyncTests methodsFor: 'running' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> uidy: aString
>>>>> ^ UUID fromString36: aString ! !
>>>>>
>>>>> !TowergameSyncTests methodsFor: 'running' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> setUp
>>>>> dao := Towergame daoForLogin: self loginToTemporaryDatabase.
>>>>> session := dao glorpSession.
>>>>> ! !
>>>>>
>>>>> !TowergameSyncTests methodsFor: 'running' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> tearDown
>>>>> session logout! !
>>>>>
>>>>> !TowergameSyncTests methodsFor: 'running' stamp: 'HerbyVojcik 8/14/2017
>>>>> 18:16'!
>>>>> loginToTemporaryDatabase
>>>>> ^ Login new
>>>>> database: UDBCSQLite3Platform new;
>>>>> host: '';
>>>>> port: '';
>>>>> username: '';
>>>>> password: '';
>>>>> databaseName: '';
>>>>> yourself! !
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> BaselineOfTowergame.st:
>>>>> BaselineOf subclass: #BaselineOfTowergame
>>>>> instanceVariableNames: ''
>>>>> classVariableNames: ''
>>>>> poolDictionaries: ''
>>>>> category: 'BaselineOfTowergame'!
>>>>>
>>>>> !BaselineOfTowergame methodsFor: 'baseline' stamp: 'HerbertVojÃÂÃÂk
>>>>> 8/14/2017 18:09:53'!
>>>>> baseline: spec
>>>>> <baseline>
>>>>> spec for: #common do: [ spec
>>>>>
>>>>> package: 'Towergame' with: [ spec
>>>>> requires: #('GlorpSQLite' 'NeoJSON') ];
>>>>> package: 'Towergame-Tests' with: [ spec
>>>>> requires: #('Towergame' 'Mocketry') ];
>>>>>
>>>>> configuration: 'GlorpSQLite' with: [ spec
>>>>> version: #stable;
>>>>> repository:
>>>>> 'http://smalltalkhub.com/mc/Pharo/MetaRepoForPharo60/main' ];
>>>>> configuration: 'NeoJSON' with: [ spec
>>>>> version: #stable;
>>>>> repository:
>>>>> 'http://smalltalkhub.com/mc/Pharo/MetaRepoForPharo60/main' ];
>>>>> baseline: 'Mocketry' with: [ spec
>>>>> repository: 'github://dionisiydk/Mocketry:v4.0.x' ];
>>>>>
>>>>> group: 'default' with: #('Core');
>>>>> group: 'development' with: #('Core' 'Tests');
>>>>> group: 'Core' with: #('Towergame');
>>>>> group: 'Tests' with: #('Towergame-Tests') ]
>>>>> ! !
>>>>>
>>>>>
>>>>>
>>>>
>>>>
>>>
>>
>>
>
>
> --
> -----------------------------------------------------------------------
> Objektfabrik Joachim Tuchel mailto:jtuchel@objektfabrik.de
> Fliederweg 1 http://www.objektfabrik.de
> D-71640 Ludwigsburg http://joachimtuchel.wordpress.com
> Telefon: +49 7141 56 10 86 0 Fax: +49 7141 56 10 86 1
>
>
Aug. 16, 2017