Entry and Re-Entry from Legacy Rails App

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|

Entry and Re-Entry from Legacy Rails App

Ken Treis
I'm working on adding a Seaside layer to one of my legacy systems (it's fun to call Rails "legacy", eh?). As I try to link back and forth from the old code to the new, I'm finding that I need my session state preserved across exits and entries.

As an example, I have built a hierarchy navigation component in Seaside, and it remembers the account you selected last time you used it. So each time I call the component, the hierarchy is still open to the last account that the user selected.

I'd love to use this from Rails, but each time I follow the link to /seaside/selectAccount, it starts me with a fresh session and I lose my old state. The cool remembering trick never gets to show its stuff.

The solution I've come up with is hackish, which is why I'm asking for input here. Essentially, I'm subclassing WAApplication and using the Rails cookie as the session key. That way, my old session can be found when the user re-enters Seaside.

This works for me, at this point. LegacyOverlay is a subclass of WAApplication:

sessionCookieField
^'_session_id'

registerRequestHandler: anObject
| key |
(anObject isKindOf: Seaside.WASession)
ifFalse: [^super registerRequestHandler: anObject].
key := Seaside.WAExternalID
fromString: (anObject currentRequest cookies at: self sessionCookieField).
self shouldCollectHandlers ifTrue: [self unregisterExpiredHandlers].
self mutex critical:
[handlersByKey at: key put: anObject.
keysByHandler at: anObject put: key].
^key

handleDefaultRequest: aRequest
| sessionKey existingSession |
sessionKey := aRequest cookies at: self sessionCookieField
ifAbsent: [^self redirectToLoginPage: aRequest].
existingSession := handlersByKey at: (Seaside.WAExternalID fromString: sessionKey)
ifAbsent: [^super handleDefaultRequest: aRequest].
^existingSession handleRequest: aRequest

Am I on the right track, or is there an easier way to accomplish this? Since I'm overlaying my Seaside app on an existing system, the app needs to behave as if it has multiple entry points. I just don't want it to start over every time it is entered.

For my account selection component, I'm just storing the last selected account in an instance variable in the session. That means that simply locating the previous session is enough to make my component do its magic. At some point I'd rather do something less "global", like an instance variable in a component that I instantiate once and repeatedly call, but I'm not familiar enough with the internals quite yet to make it work that way.

--

Ken Treis

Miriam Technologies, Inc.



_______________________________________________
Seaside mailing list
[hidden email]
http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside
Reply | Threaded
Open this post in threaded view
|

Re: Entry and Re-Entry from Legacy Rails App

Giles Bowkett
I don't know the answer, but I think it's a pretty interesting
question. It means the rest of the app is in Rails, and just this one
part is in Seaside?

Smalltalk's got many strengths, but it's never been regarded as a glue
language that I'm aware of. I think you might be best off salting the
data somewhere Seaside can get to it more easily, but I'm making a
pretty wild shot in the dark, because I've never done this and I don't
know what your architecture is. Is Seaside connecting to the same
database Rails is? If you're using DB sessions, you can load the Rails
session from the database without actually making it the Seaside
session -- it's just an arbitrary set of data from which you want to
extract one particular value. Does that help?

On 2/18/07, Ken Treis <[hidden email]> wrote:

>
> I'm working on adding a Seaside layer to one of my legacy systems (it's fun
> to call Rails "legacy", eh?). As I try to link back and forth from the old
> code to the new, I'm finding that I need my session state preserved across
> exits and entries.
>
> As an example, I have built a hierarchy navigation component in Seaside, and
> it remembers the account you selected last time you used it. So each time I
> call the component, the hierarchy is still open to the last account that the
> user selected.
>
> I'd love to use this from Rails, but each time I follow the link to
> /seaside/selectAccount, it starts me with a fresh session and I lose my old
> state. The cool remembering trick never gets to show its stuff.
>
> The solution I've come up with is hackish, which is why I'm asking for input
> here. Essentially, I'm subclassing WAApplication and using the Rails cookie
> as the session key. That way, my old session can be found when the user
> re-enters Seaside.
>
> This works for me, at this point. LegacyOverlay is a subclass of
> WAApplication:
>
>
> sessionCookieField
>  ^'_session_id'
>
> registerRequestHandler: anObject
>  | key |
>  (anObject isKindOf: Seaside.WASession)
>  ifFalse: [^super registerRequestHandler: anObject].
>  key := Seaside.WAExternalID
>  fromString: (anObject currentRequest cookies at: self sessionCookieField).
>  self shouldCollectHandlers ifTrue: [self unregisterExpiredHandlers].
>  self mutex critical:
>  [handlersByKey at: key put: anObject.
>  keysByHandler at: anObject put: key].
>  ^key
>
> handleDefaultRequest: aRequest
>  | sessionKey existingSession |
>  sessionKey := aRequest cookies at: self sessionCookieField
>  ifAbsent: [^self redirectToLoginPage: aRequest].
>  existingSession := handlersByKey at: (Seaside.WAExternalID fromString:
> sessionKey)
>  ifAbsent: [^super handleDefaultRequest: aRequest].
>  ^existingSession handleRequest: aRequest
>
> Am I on the right track, or is there an easier way to accomplish this? Since
> I'm overlaying my Seaside app on an existing system, the app needs to behave
> as if it has multiple entry points. I just don't want it to start over every
> time it is entered.
>
> For my account selection component, I'm just storing the last selected
> account in an instance variable in the session. That means that simply
> locating the previous session is enough to make my component do its magic.
> At some point I'd rather do something less "global", like an instance
> variable in a component that I instantiate once and repeatedly call, but I'm
> not familiar enough with the internals quite yet to make it work that way.
>
>
>
>
> --
>
> Ken Treis
>
> Miriam Technologies, Inc.
>
> _______________________________________________
> Seaside mailing list
> [hidden email]
> http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside
>
>


--
Giles Bowkett
http://www.gilesgoatboy.org
http://gilesbowkett.blogspot.com
http://gilesgoatboy.blogspot.com
_______________________________________________
Seaside mailing list
[hidden email]
http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside
Reply | Threaded
Open this post in threaded view
|

Re: Entry and Re-Entry from Legacy Rails App

Ken Treis
In reply to this post by Ken Treis
I realized overnight that I was making this a lot harder than it needed to be. If I just configured my Seaside app to use session cookies, I'd get the same session at every re-entry. There's no reason why the Seaside cookie needs to match the Rails cookie.

But I'd still appreciate some feedback on how to keep some state (like the last selected account in my example component below) across entries. Is a custom subclass of WASession the best place to store this?


Ken
 
On Feb 18, 2007, at 12:26 AM, Ken Treis wrote:

I'm working on adding a Seaside layer to one of my legacy systems (it's fun to call Rails "legacy", eh?). As I try to link back and forth from the old code to the new, I'm finding that I need my session state preserved across exits and entries.

As an example, I have built a hierarchy navigation component in Seaside, and it remembers the account you selected last time you used it. So each time I call the component, the hierarchy is still open to the last account that the user selected.

I'd love to use this from Rails, but each time I follow the link to /seaside/selectAccount, it starts me with a fresh session and I lose my old state. The cool remembering trick never gets to show its stuff.

The solution I've come up with is hackish, which is why I'm asking for input here. Essentially, I'm subclassing WAApplication and using the Rails cookie as the session key. That way, my old session can be found when the user re-enters Seaside.

This works for me, at this point. LegacyOverlay is a subclass of WAApplication:

sessionCookieField
^'_session_id'

registerRequestHandler: anObject
| key |
(anObject isKindOf: Seaside.WASession)
ifFalse: [^super registerRequestHandler: anObject].
key := Seaside.WAExternalID
fromString: (anObject currentRequest cookies at: self sessionCookieField).
self shouldCollectHandlers ifTrue: [self unregisterExpiredHandlers].
self mutex critical:
[handlersByKey at: key put: anObject.
keysByHandler at: anObject put: key].
^key

handleDefaultRequest: aRequest
| sessionKey existingSession |
sessionKey := aRequest cookies at: self sessionCookieField
ifAbsent: [^self redirectToLoginPage: aRequest].
existingSession := handlersByKey at: (Seaside.WAExternalID fromString: sessionKey)
ifAbsent: [^super handleDefaultRequest: aRequest].
^existingSession handleRequest: aRequest

Am I on the right track, or is there an easier way to accomplish this? Since I'm overlaying my Seaside app on an existing system, the app needs to behave as if it has multiple entry points. I just don't want it to start over every time it is entered.

For my account selection component, I'm just storing the last selected account in an instance variable in the session. That means that simply locating the previous session is enough to make my component do its magic. At some point I'd rather do something less "global", like an instance variable in a component that I instantiate once and repeatedly call, but I'm not familiar enough with the internals quite yet to make it work that way.

--
Ken Treis
Miriam Technologies, Inc.

_______________________________________________
Seaside mailing list

--

Ken Treis

Miriam Technologies, Inc.



_______________________________________________
Seaside mailing list
[hidden email]
http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside
Reply | Threaded
Open this post in threaded view
|

Re: Entry and Re-Entry from Legacy Rails App

Ken Treis
In reply to this post by Giles Bowkett
On Feb 18, 2007, at 7:59 AM, Giles Bowkett wrote:

> I don't know the answer, but I think it's a pretty interesting
> question. It means the rest of the app is in Rails, and just this one
> part is in Seaside?

Yes -- that way I can build new stuff in Seaside without having to  
first port forward all of the old stuff.

> Smalltalk's got many strengths, but it's never been regarded as a glue
> language that I'm aware of. I think you might be best off salting the
> data somewhere Seaside can get to it more easily, but I'm making a
> pretty wild shot in the dark, because I've never done this and I don't
> know what your architecture is. Is Seaside connecting to the same
> database Rails is? If you're using DB sessions, you can load the Rails
> session from the database without actually making it the Seaside
> session -- it's just an arbitrary set of data from which you want to
> extract one particular value. Does that help?

That part I've actually already got figured out.

Seaside and Rails talk to the same database. I'm not using DB  
sessions, but the file-based sessions are pretty easy to read from  
Smalltalk. I've used a similar trick to read PHP sessions from Rails.  
The cookie from the old framework tells you which file to load, and  
the only thing I've ever needed from the old session is the current  
user's ID.

Login is always done in the old framework, and the new framework  
redirects you back to the old if you haven't logged in yet. Logout is  
a two-step process, where the new framework clears its session and  
then redirects to an old framework page that does the same.

As for loading Rails sessions, I've already written a Smalltalk class  
that can read Ruby "Marshal" data, so it's easy to pluck the data  
from the Rails session. It's VisualWorks code, but it should port to  
Squeak pretty easily if anybody's interested.

If I store persistent data in the session explicitly (by adding  
instance variables), then I've accomplished what I needed. But it  
gets ugly as the number of Seaside components grows. And it seems  
like a very non-Seaside way to solve the problem. "Just make a new  
global every time you need to store something!"

That's the main issue I'd like some tips on. The other part that I  
haven't solved yet is how to handle multiple entry points into  
Seaside in a clean way. Rails will link to a particular URL for each  
of these, and I'd like it to work something like this:

URL: /seaside/overlay/someMagicKey
   => rootComponent main call: (SomeComponent new...)

Most of the time, these will be very coarse-grained divisions. In  
other words, I'd use Seaside to build a new area on the site, not  
just to accomplish a small task that's part of a larger Rails  
workflow. The account selector I mentioned is an exception to this  
and will require more data to be pushed back and forth, but I think I  
can handle all of that using URL queries.

It looks like WARenderLoopMain>>start: is the place to handle custom  
URLs, and it also has access to the root component. Maybe that's the  
place to solve both of these things. I might have to play with that  
next.

--
Ken Treis
Miriam Technologies, Inc.

_______________________________________________
Seaside mailing list
[hidden email]
http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside
Reply | Threaded
Open this post in threaded view
|

Re: Entry and Re-Entry from Legacy Rails App

Giles Bowkett
Ken, I'm actually moving today, but I'm hoping to get a longer/more
useful reply off to you later this evening. In the meantime I just
want to suggest -- have you looked at Pier? I know Pier provides REST
support, so it could contain classes you can use to handle the URL
side of the equation.

By the way I'd definitely be interested to see the Marshall-reading
stuff. I'm using Squeak, so I'll be able to test its portability
pretty quickly. It's probably pretty straight-forward, but I'm still a
bit of a n00b with Smalltalk, so it'll help me learn as well.

On 2/18/07, Ken Treis <[hidden email]> wrote:

> On Feb 18, 2007, at 7:59 AM, Giles Bowkett wrote:
>
> > I don't know the answer, but I think it's a pretty interesting
> > question. It means the rest of the app is in Rails, and just this one
> > part is in Seaside?
>
> Yes -- that way I can build new stuff in Seaside without having to
> first port forward all of the old stuff.
>
> > Smalltalk's got many strengths, but it's never been regarded as a glue
> > language that I'm aware of. I think you might be best off salting the
> > data somewhere Seaside can get to it more easily, but I'm making a
> > pretty wild shot in the dark, because I've never done this and I don't
> > know what your architecture is. Is Seaside connecting to the same
> > database Rails is? If you're using DB sessions, you can load the Rails
> > session from the database without actually making it the Seaside
> > session -- it's just an arbitrary set of data from which you want to
> > extract one particular value. Does that help?
>
> That part I've actually already got figured out.
>
> Seaside and Rails talk to the same database. I'm not using DB
> sessions, but the file-based sessions are pretty easy to read from
> Smalltalk. I've used a similar trick to read PHP sessions from Rails.
> The cookie from the old framework tells you which file to load, and
> the only thing I've ever needed from the old session is the current
> user's ID.
>
> Login is always done in the old framework, and the new framework
> redirects you back to the old if you haven't logged in yet. Logout is
> a two-step process, where the new framework clears its session and
> then redirects to an old framework page that does the same.
>
> As for loading Rails sessions, I've already written a Smalltalk class
> that can read Ruby "Marshal" data, so it's easy to pluck the data
> from the Rails session. It's VisualWorks code, but it should port to
> Squeak pretty easily if anybody's interested.
>
> If I store persistent data in the session explicitly (by adding
> instance variables), then I've accomplished what I needed. But it
> gets ugly as the number of Seaside components grows. And it seems
> like a very non-Seaside way to solve the problem. "Just make a new
> global every time you need to store something!"
>
> That's the main issue I'd like some tips on. The other part that I
> haven't solved yet is how to handle multiple entry points into
> Seaside in a clean way. Rails will link to a particular URL for each
> of these, and I'd like it to work something like this:
>
> URL: /seaside/overlay/someMagicKey
>    => rootComponent main call: (SomeComponent new...)
>
> Most of the time, these will be very coarse-grained divisions. In
> other words, I'd use Seaside to build a new area on the site, not
> just to accomplish a small task that's part of a larger Rails
> workflow. The account selector I mentioned is an exception to this
> and will require more data to be pushed back and forth, but I think I
> can handle all of that using URL queries.
>
> It looks like WARenderLoopMain>>start: is the place to handle custom
> URLs, and it also has access to the root component. Maybe that's the
> place to solve both of these things. I might have to play with that
> next.
>
> --
> Ken Treis
> Miriam Technologies, Inc.
>
> _______________________________________________
> Seaside mailing list
> [hidden email]
> http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside
>


--
Giles Bowkett
http://www.gilesgoatboy.org
http://gilesbowkett.blogspot.com
http://gilesgoatboy.blogspot.com
_______________________________________________
Seaside mailing list
[hidden email]
http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside
Reply | Threaded
Open this post in threaded view
|

Re: Entry and Re-Entry from Legacy Rails App

Ken Treis
On Feb 18, 2007, at 11:19 AM, Giles Bowkett wrote:

> Ken, I'm actually moving today, but I'm hoping to get a longer/more
> useful reply off to you later this evening. In the meantime I just
> want to suggest -- have you looked at Pier? I know Pier provides REST
> support, so it could contain classes you can use to handle the URL
> side of the equation.
>
> By the way I'd definitely be interested to see the Marshall-reading
> stuff. I'm using Squeak, so I'll be able to test its portability
> pretty quickly. It's probably pretty straight-forward, but I'm still a
> bit of a n00b with Smalltalk, so it'll help me learn as well.

Thanks Giles, I'll finish up my unit tests for my RubyMarshal and  
then I'll post it here on the list.

--
Ken Treis
Miriam Technologies, Inc.

_______________________________________________
Seaside mailing list
[hidden email]
http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside
Reply | Threaded
Open this post in threaded view
|

Re: Entry and Re-Entry from Legacy Rails App

Ken Treis
In reply to this post by Giles Bowkett
On Feb 18, 2007, at 11:19 AM, Giles Bowkett wrote:

> By the way I'd definitely be interested to see the Marshall-reading
> stuff. I'm using Squeak, so I'll be able to test its portability
> pretty quickly.

OK, here it is. I attached it as a VisualWorks 3.0 fileout. Let me  
know if this doesn't work for you, and I can try the "dangerous"  
squeak fileout goodie.

I've been able to use this to load all of the file-serialized Rails  
sessions I can easily get my hands on, and it is able to handle them  
all.

Here's the class comment from RubyMarshal:

======

RubyMarshal is an implementation of the Ruby built-in "Marshal"  
class. Based in part on marshal.c from the Ruby 1.8.5 codebase.

Booleans, Strings, Symbols, Numbers, Arrays, and Hashes are returned  
as equivalent Smalltalk objects. Class names are returned as symbols.  
All other objects are returned as instances of RubyObject.

Things that aren't supported:

* The internal Ruby "DATA" type, which is used for wrapped C pointers
* Options for Regexps (the flags that follow the final slash, e.g.  
the $i from /$foo/i. We just return regexps as their inner strings  
anyway (see below).

Caveats:

* Regexps are returned as strings that do *not* include leading and  
trailing slashes.

* User-marshaled classes (classes that define _dump and _load or use  
the old marshal_dump/marshal_load protocol) are supported, but their  
raw dump data is the only thing we know how to load. This raw data is  
accessible as RubyObject>>marshalData.

* Hashes with default values (defined like: "Hash.new(5)") are  
supported, but returned as a Dictionary. The default value is stored  
under the key #rubyDefault.

* Floats include some information about the size of the mantissa, but  
we discard that and let Smalltalk handle it. This seems to work, but  
I make no guarantees.

* Some old Modules were serialized as classes. I don't have a test  
for this (MODULE_OLD), but the code should work.

* When particular instances have been extended (like "obj.extend
(Module)"), we read the module name but throw it away.

There are undoubtedly some edge cases that I missed. If you find one  
of these, send it to me and I'll figure out what we're missing.

--
Ken Treis
Miriam Technologies, Inc.



_______________________________________________
Seaside mailing list
[hidden email]
http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside

RubyMarshal.st (27K) Download Attachment