Models belonging to the session?

I'm implementing a shopping cart which is stored in the database rather than in the session itself. I'm using ActiveRecordStore for session. (using Edge)

I'd like to have the cart belong_to the session in a normal has_one/belongs_to relationship, e.g.,

create table cart (cart_id primary key,                     session_id integer not null unique                       references sessions);

class Cart < ActiveRecord::Base    belongs_to :session end

class Session < ActiveRecord::Base    has_one :cart end

Using the How to Change Session Store page on the wiki[0] and the active_record_store.rb documentation[1] as references, I believe I should be able to define a find_cart method that returns the current cart or creates a new one:

# in app/controllers/application.rb def find_cart    @cart = @session.model.cart || @session.model.create_cart end

I get a NoMethodError: You have a nil object when you didn't expect it! The error occurred while evaluating nil.model

However, shouldn't there already be a session created when the page is called?

As using @session is deprecated, I've also tried using session.model.cart instead:

   def find_cart      @cart = session.model.cart || session.model.create_cart    end

However, this returns an method missing error:

undefined method `cart' for #<CGI::Session::ActiveRecordStore::Session:0x33752c8>

My guess is that I'm not extending the Session model correctly: however, with Ruby's open classes, I think this should work. It's also interesting to me that the

What am I missing? Thanks for any suggestions or pointers to references.

Michael Glaesemann grzm seespotcode net

[0](Peak Obsession) [1](http://dev.rubyonrails.org/svn/rails/trunk/actionpack/lib/action_controller/session/active_record_store.rb)

undefined method `cart' for #<CGI::Session::ActiveRecordStore::Session:0x33752c8>

My guess is that I'm not extending the Session model correctly: however, with Ruby's open classes, I think this should work. It's also interesting to me that the

Perhaps the problem is that you're extending Session rather than
CGI::Session::ActiveRecordStore::Session ?

Fred

That was a good idea:

class CGI::Session::ActiveRecordStore::Session    has_one :cart end

However, this gives me the same errors. I've got session.rb in app/models. Might it need to be somewhere else?

Michael Glaesemann grzm seespotcode net

However, this gives me the same errors. I've got session.rb in app/ models. Might it need to be somewhere else?

I've apparently got it working by putting my session.rb file in lib/ and requiring it from my environment.rb configuration. I guess I'm supposed to interpret the wiki's

This will of course require creating a model ‘session.rb’ which extends ActiveRecord.

to mean I need to implement an alternative to CGI::Session::ActiveRecordStore::Session rather than just extend it. That's further backed up by this section from the API docs:

You may provide your own session class implementation, whether a feature-packed Active Record or a bare-metal high-performance SQL store, by setting

  +CGI::Session::ActiveRecordStore.session_class = MySessionClass+

You must implement these methods:

  self.find_by_session_id(session_id)   initialize(hash_of_session_id_and_data)   attr_reader :session_id   attr_accessor :data   save   destroy

I'm otherwise happy with CGI::Session::ActiveRecordStore::Session, so hopefully my little extension will suit my purposes.

Thanks, Frederick, for the push!

By the way, I'd love to hear from anyone else with experience along these lines :slight_smile:

Michael Glaesemann grzm seespotcode net

Hmn. I've been trying to do this same thing and found your thread on Google.

I tried your recommendation and sort of got it working, but kept getting errors when trying to load my added association.

So now I'm trying to put the model in app/models and have it inherit from CGI::Session::ActiveRecordStore::Session, but when I go to set session_class it uses the wrong Session class. Changing the name of my custom session to avoid a namespace collision doesn't help, and causes an uninitialized constant error to boot. Here's what I've got:

app/models/foo_session.rb   class FooSession < CGI::Session::ActiveRecordStore::Session     set_table_name :sessions     belongs_to :user   end

config/environment.rb   CGI::Session::ActiveRecordStore.session_class = FooSession

The documentation implies it's all so damn easy that there doesn't need to be an example :wink: But even with that I still get

  undefined method `user' for #<FooSession:0xb73d5a10>

Any ideas?

Why are you doing it like this? Just create your cart and store the id in the session.

def find_cart   @shopping_cart ||= Cart.find(session[:cart_id]) end

def cart=(cart)   if !cart.nil?     session[:cart_id] = cart.id   else     session[:cart_id] = nil   end end

Model objects should not know anything about the session.

Pat

Why are you doing it like this? Just create your cart and store the id in the session.

<snip />

Model objects should not know anything about the session.

I agree. There are two reasons why I've been attempting to treat the include the Session model in a has_one/belong_to association:

(a) Right now I don't have any attributes associated with the Cart model other than its id, i.e., the table corresponding to the model is just

create table carts (cart_id serial primary key)

This feels wrong. There's really no information there at all. Plus, ActiveRecord doesn't know how to create new rows that consist of only an autogenerated primary key. It tries to INSERT INTO "carts" () VALUES () which leads to an error (at least with a PostgreSQL backend). Perhaps there's a way to educate ActiveRecord on how to do this, but I wasn't particularly motivated to figure this out.

(b) Having the carts table reference sessions allows the you to cascade deletes from sessions to the carts table (and on to the cart_items table as well). This is handy because then you're able to easily clean up stale carts when you're cleaning out stale sessions.

However, I must say it's been an uphill battle, including issues such as those raised by I.E. Smith-Heisters in another response, and figuring out a way to preload session data before creation of fixtures during testing. Learned more about the backend, but I haven't been overly satisfied with the result.

I think I'm just going to go back and add a cart_created_at column to the carts table to work around ActiveRecord's inability to deal with the situation where there's only a single attribute. I'm too tired right now to consider patching ActiveRecord. Probably the simplest all-around solution.

I wish you had piped up a couple of days ago :slight_smile: I'd love to hear your opinions on the two points I raised above. I'm sure I'm missing something.

Thanks for your response!

Michael Glaesemann grzm seespotcode net

PS This makes me think I should move away from ActiveRecordStore entirely to prevent myself from considering something like this in the future. :slight_smile:

[Please don't top post as it makes the discussion more difficult to follow.]

So now I'm trying to put the model in app/models and have it inherit from CGI::Session::ActiveRecordStore::Session, but when I go to set session_class it uses the wrong Session class. Changing the name of my custom session to avoid a namespace collision doesn't help, and causes an uninitialized constant error to boot.

I believe load order is the root of this problem (see below). I don't think you'd have to worry about namespace issues though. I think you'd need to always explicitly refer to ActiveRecordStore::Session by it's full name.

The documentation implies it's all so damn easy that there doesn't need to be an example :wink:

I hear you :slight_smile: During my experiments I pulled ActiveRecordStore::Session into it's own file and placed it in lib/ and ran into underlying session library errors. I thought at least that would work :confused:

I'm pretty sure one of the issues is with load order: I think the model needs to be in lib/ so it's available to when the server is started (presumably prior to when models are loaded).

But even with that I still get

  undefined method `user' for #<FooSession:0xb73d5a10>

Any ideas?

Not really. As indicated in my response to Pat Maddox, I'm through with this experiment, at least for now. Perhaps when I'm a better coder I can revisit this and see what's up.

If you figure it out, let me know. I'd love to hear details on how you got it working :wink: Good luck!

Michael Glaesemann grzm seespotcode net

> Why are you doing it like this? Just create your cart and store the > id in the session.

<snip />

> Model objects should not know anything about the session.

I agree. There are two reasons why I've been attempting to treat the include the Session model in a has_one/belong_to association:

(a) Right now I don't have any attributes associated with the Cart model other than its id, i.e., the table corresponding to the model is just

create table carts (cart_id serial primary key)

This feels wrong. There's really no information there at all. Plus, ActiveRecord doesn't know how to create new rows that consist of only an autogenerated primary key. It tries to INSERT INTO "carts" () VALUES () which leads to an error (at least with a PostgreSQL backend). Perhaps there's a way to educate ActiveRecord on how to do this, but I wasn't particularly motivated to figure this out.

Just create an empty column if you want. Call it name and default it to blank. Or, make it created_at, which brings us to...

(b) Having the carts table reference sessions allows the you to cascade deletes from sessions to the carts table (and on to the cart_items table as well). This is handy because then you're able to easily clean up stale carts when you're cleaning out stale sessions.

In your session maintenance script, just add Cart.destroy_all ["created_at < ?", 24.hours.ago]

However, I must say it's been an uphill battle, including issues such as those raised by I.E. Smith-Heisters in another response, and figuring out a way to preload session data before creation of fixtures during testing. Learned more about the backend, but I haven't been overly satisfied with the result.

Well yeah it's been an uphill battle. It should be - you're doing things the wrong way.

I think I'm just going to go back and add a cart_created_at column to the carts table to work around ActiveRecord's inability to deal with the situation where there's only a single attribute. I'm too tired right now to consider patching ActiveRecord. Probably the simplest all-around solution.

ooh. You almost got the right solution :slight_smile:

I wish you had piped up a couple of days ago :slight_smile: I'd love to hear your opinions on the two points I raised above. I'm sure I'm missing something.

Thanks for your response!

Michael Glaesemann grzm seespotcode net

PS This makes me think I should move away from ActiveRecordStore entirely to prevent myself from considering something like this in the future. :slight_smile:

No, just don't consider stuff like this. ActiveRecordStore is an implementation detail for your sessions, but your app code doesn't care about it. Basically you set it up and then forget all about it.

Just create an empty column if you want. Call it name and default it to blank.

Again, that's meaningless. No more meaningful than a table of integers.

In your session maintenance script, just add Cart.destroy_all ["created_at < ?", 24.hours.ago]

It'd be a little more involved than that, as what you really want to look at is the last time the cart has been modified. I'd want to know the last time the cart was active: when items were last added or deleted. Maybe even the last time it was opened. This is all taken care of in the updated_at column of the sessions table. Adding the proper update code in either my app or the database is repeating myself, and prone to error.

Yes, ActiveRecord/Rails is opinionated, so I'll just hold my tongue now.

PS This makes me think I should move away from ActiveRecordStore entirely to prevent myself from considering something like this in the future. :slight_smile:

No, just don't consider stuff like this. ActiveRecordStore is an implementation detail for your sessions, but your app code doesn't care about it. Basically you set it up and then forget all about it.

This was partly motivated by what I've heard about the cookie-session store. I've heard it's faster, and recently Ara Howard has been doing some research showing that database-backed sessions in Rails slowly leak memory.

Michael Glaesemann grzm seespotcode net

> Just create an empty column if you want. Call it name and default it > to blank.

Again, that's meaningless. No more meaningful than a table of integers.

> In your session maintenance script, just add > Cart.destroy_all ["created_at < ?", 24.hours.ago]

It'd be a little more involved than that, as what you really want to look at is the last time the cart has been modified. I'd want to know the last time the cart was active: when items were last added or deleted. Maybe even the last time it was opened. This is all taken care of in the updated_at column of the sessions table.

It's also taken care of by an updated_at column in the Cart table. Rails automatically updates that whenever you save the cart. If you want whenever it was last accessed, you can do a before_filter that updates the cart every time the page loads.

Adding the proper update code in either my app or the database is repeating myself, and prone to error.

Hrm. Prone to error in relation to what? Because it'll work a loooooooooot better than the solutions you've discussed in this thread.

Yes, ActiveRecord/Rails is opinionated, so I'll just hold my tongue now.

>> PS This makes me think I should move away from ActiveRecordStore >> entirely to prevent myself from considering something like this in >> the future. :slight_smile: > > No, just don't consider stuff like this. ActiveRecordStore is an > implementation detail for your sessions, but your app code doesn't > care about it. Basically you set it up and then forget all about it.

This was partly motivated by what I've heard about the cookie-session store. I've heard it's faster, and recently Ara Howard has been doing some research showing that database-backed sessions in Rails slowly leak memory.

Bottom line, what you're trying to do is retarded. Anyone who has a clue about Rails will confirm that.

Pat

It's also taken care of by an updated_at column in the Cart table. Rails automatically updates that whenever you save the cart.

Thanks for reminding me. I tend to think in terms of the database, where adding or deleting an item from the cart would only require an update to the cart_items table. (That would be the most efficient way to go about it from the database side as well: no need to hit the carts table.)

Bottom line, what you're trying to do is retarded. Anyone who has a clue about Rails will confirm that.

Wow. Retarded? Perhaps not just as experienced as others so there may be areas where I might be ignorant, but retarded? C'mon, Pat. You can do better than that.

Michael Glaesemann grzm seespotcode net

I didn't call you retarded. I said what you're trying to do is retarded. There's nothing wrong with being inexperienced. In fact, when I started using Rails, I tried to do the exact same thing you're doing. I was building a shopping cart and I wanted to connect the cart to the session. That's how I know it's so bad :slight_smile:

Sorry to use the harsh language, but I showed you a simpler, far less error-prone, and idiomatic way to do what you want, and you're fighting it. I wanted to heavily discourage you from using an atrocious solution. This is no reflection on you personally, but simply has to do with not being familiar with Rails and how the pieces work together.

You'll find that the Rails community is incredibly nice, so when bad words come out, you're probably doing something horribly, horribly wrong.

Pat

I have also been playing with this same idea, of linking sessions to the cart. Has anyone had success with this? I am also a little confused why this would be a horrible idea if someone could explain in a little more detail.