views:

131

answers:

2

All Rails expert sites say not to store ActiveRecords in the Session. However, I have a multiple-field ActiveRecord object that spans several pages. The code I'm trying to clean up used hidden fields to pass the data forward, which seems like a bad idea (user can tamper for one thing). What's the typical, or good, way to divide up a model-filling page into several pages?

Note: I could save the ActiveRecord to the DB and then get it with the ID, which I store in session... the problem is that the ActiveRecord has several validations which won't let it save without all the data. I could store the params themselves in session, or something.... there must a standard way to do this...

+2  A: 

Convert the object/s into yaml using obj.to_yaml and save the yaml as text in the db (or even a flat file). store the id in the session. reload it when needed. obj.to_yaml serializes the object without need for saving, skipping rails activerecord validations.

Ryan Oberoi
serializing it myself? I was hoping that we didn't get down to that... but does it work with ActiveRecords? If so, you could store the Yaml in the session itself...
Yar
Yes, to_yaml works on all objects. You can store it anywhere you want. I willl remove the saving to file code from my solution, as it kind of clouds the underlying message which is that: obj.to_yaml allows you to save the partial state of objects.
Ryan Oberoi
You do not typically want to store partial objects in the database along with "full fledged" objects. Create a simple table to store the YAML text (id, yaml) and use it.
Ryan Oberoi
Thanks for your answer, Ryan. I wonder if it's necessary to serialize to yaml (or marshall in whatever way) to store in session. I think that the built-in session storage would take care of that. At the same time, I have always questioned home-grown methods compared to using built-in session storage for, for example, partially filled in forms...people think the object is "big" but the default session storage can even use a DB, if you like.
Yar
+5  A: 

We use the following pattern which appears to work well.

  1. Add an attribute accessor called wizard_stage to the model:

    attr_accessor :wizard_stage

  2. Add a hidden field :wizard_stage to the form for the record in each of pages. Set the value of the field to something which reflects what the page does, so for example:

    f.hidden_field :wizard_stage, :value => 'contact_details'

  3. In the model validation, add a condition like the following:

    validates ... :if => lambda{|m| m.wizard_stage == 'contact_details'}

Now the record can be saved for every submission, gradually filling in more attributes.

Note that this allows an attacker to bypass validation if they want, but in our case (most cases?) that doesn't really matter.

Tor Erik Linnerud
Good call. This is the simplest approach, and also makes sure the user can't proceed with invalid data at any given stage. +1
fig
2nd comment: The correct stage should be verified server-side. Avoids tampering.
fig
+1, and +1 for Dave's comments :)
Yar